|  | // Copyright 2019 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "sqlite_persistent_store_backend_base.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/strings/strcat.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/timer/elapsed_timer.h" | 
|  | #include "sql/error_delegate_util.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | #include <windows.h> | 
|  | #endif  // BUILDFLAG(IS_WIN) | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | SQLitePersistentStoreBackendBase::SQLitePersistentStoreBackendBase( | 
|  | const base::FilePath& path, | 
|  | sql::Database::Tag histogram_tag, | 
|  | const int current_version_number, | 
|  | const int compatible_version_number, | 
|  | scoped_refptr<base::SequencedTaskRunner> background_task_runner, | 
|  | scoped_refptr<base::SequencedTaskRunner> client_task_runner, | 
|  | bool enable_exclusive_access) | 
|  | : path_(path), | 
|  | histogram_tag_(histogram_tag), | 
|  | current_version_number_(current_version_number), | 
|  | compatible_version_number_(compatible_version_number), | 
|  | background_task_runner_(std::move(background_task_runner)), | 
|  | client_task_runner_(std::move(client_task_runner)), | 
|  | enable_exclusive_access_(enable_exclusive_access) {} | 
|  |  | 
|  | SQLitePersistentStoreBackendBase::~SQLitePersistentStoreBackendBase() { | 
|  | // If `db_` hasn't been reset by the time this destructor is called, | 
|  | // a use-after-free could occur if the `db_` error callback is ever | 
|  | // invoked. To guard against this, crash if `db_` hasn't been reset | 
|  | // so that this use-after-free doesn't happen and so that we'll be | 
|  | // alerted to the fact that a closer look at this code is needed. | 
|  | CHECK(!db_.get()) << "Close should already have been called."; | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::Flush(base::OnceClosure callback) { | 
|  | DCHECK(!background_task_runner_->RunsTasksInCurrentSequence()); | 
|  | PostBackgroundTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | &SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground, this, | 
|  | std::move(callback))); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::Close() { | 
|  | if (background_task_runner_->RunsTasksInCurrentSequence()) { | 
|  | DoCloseInBackground(); | 
|  | } else { | 
|  | // Must close the backend on the background runner. | 
|  | PostBackgroundTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&SQLitePersistentStoreBackendBase::DoCloseInBackground, | 
|  | this)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::SetBeforeCommitCallback( | 
|  | base::RepeatingClosure callback) { | 
|  | base::AutoLock locked(before_commit_callback_lock_); | 
|  | before_commit_callback_ = std::move(callback); | 
|  | } | 
|  |  | 
|  | bool SQLitePersistentStoreBackendBase::InitializeDatabase() { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (initialized_ || corruption_detected_) { | 
|  | // Return false if we were previously initialized but the DB has since been | 
|  | // closed, or if corruption caused a database reset during initialization. | 
|  | return db_ != nullptr; | 
|  | } | 
|  |  | 
|  | base::ElapsedTimer timer; | 
|  |  | 
|  | const base::FilePath dir = path_.DirName(); | 
|  | if (!base::PathExists(dir) && !base::CreateDirectory(dir)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/40262972): Remove explicit_locking = false. This currently | 
|  | // needs to be set to false because of several failing MigrationTests. | 
|  | db_ = std::make_unique<sql::Database>( | 
|  | sql::DatabaseOptions() | 
|  | .set_exclusive_locking(false) | 
|  | .set_exclusive_database_file_lock(enable_exclusive_access_) | 
|  | .set_preload(true), | 
|  | histogram_tag_); | 
|  |  | 
|  | // base::Unretained is safe because |this| owns (and therefore outlives) the | 
|  | // sql::Database held by |db_|. | 
|  | db_->set_error_callback(base::BindRepeating( | 
|  | &SQLitePersistentStoreBackendBase::DatabaseErrorCallback, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | if (!db_->Open(path_)) { | 
|  | DLOG(ERROR) << "Unable to open " << histogram_tag_.value << " DB."; | 
|  | RecordOpenDBProblem(); | 
|  | Reset(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!MigrateDatabaseSchema() || !CreateDatabaseSchema()) { | 
|  | DLOG(ERROR) << "Unable to update or initialize " << histogram_tag_.value | 
|  | << " DB tables."; | 
|  | RecordDBMigrationProblem(); | 
|  | Reset(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | base::UmaHistogramCustomTimes( | 
|  | base::StrCat({histogram_tag_.value, ".TimeInitializeDB"}), | 
|  | timer.Elapsed(), base::Milliseconds(1), base::Minutes(1), 50); | 
|  |  | 
|  | initialized_ = DoInitializeDatabase(); | 
|  |  | 
|  | if (!initialized_) { | 
|  | DLOG(ERROR) << "Unable to initialize " << histogram_tag_.value << " DB."; | 
|  | RecordOpenDBProblem(); | 
|  | Reset(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SQLitePersistentStoreBackendBase::DoInitializeDatabase() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::Reset() { | 
|  | if (db_ && db_->is_open()) | 
|  | db_->Raze(); | 
|  | meta_table_.Reset(); | 
|  | db_.reset(); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::Commit() { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | { | 
|  | base::AutoLock locked(before_commit_callback_lock_); | 
|  | if (!before_commit_callback_.is_null()) | 
|  | before_commit_callback_.Run(); | 
|  | } | 
|  |  | 
|  | DoCommit(); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::PostBackgroundTask( | 
|  | const base::Location& origin, | 
|  | base::OnceClosure task) { | 
|  | if (!background_task_runner_->PostTask(origin, std::move(task))) { | 
|  | LOG(WARNING) << "Failed to post task from " << origin.ToString() | 
|  | << " to background_task_runner_."; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::PostClientTask( | 
|  | const base::Location& origin, | 
|  | base::OnceClosure task) { | 
|  | if (!client_task_runner_->PostTask(origin, std::move(task))) { | 
|  | LOG(WARNING) << "Failed to post task from " << origin.ToString() | 
|  | << " to client_task_runner_."; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool SQLitePersistentStoreBackendBase::MigrateDatabaseSchema() { | 
|  | // Version check. | 
|  | if (!meta_table_.Init(db_.get(), current_version_number_, | 
|  | compatible_version_number_)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (meta_table_.GetCompatibleVersionNumber() > current_version_number_) { | 
|  | LOG(WARNING) << histogram_tag_.value << " database is too new."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // |cur_version| is the version that the database ends up at, after all the | 
|  | // database upgrade statements. | 
|  | std::optional<int> cur_version = DoMigrateDatabaseSchema(); | 
|  | if (!cur_version.has_value()) | 
|  | return false; | 
|  |  | 
|  | // Metatable is corrupted. Try to recover. | 
|  | if (cur_version.value() < current_version_number_) { | 
|  | meta_table_.Reset(); | 
|  | db_ = std::make_unique<sql::Database>(histogram_tag_); | 
|  | bool recovered = sql::Database::Delete(path_) && db()->Open(path_) && | 
|  | meta_table_.Init(db(), current_version_number_, | 
|  | compatible_version_number_); | 
|  | base::UmaHistogramBoolean( | 
|  | base::StrCat({histogram_tag_.value, ".CorruptMetaTableRecovered"}), | 
|  | recovered); | 
|  | if (!recovered) { | 
|  | DLOG(ERROR) << "Unable to recover the " << histogram_tag_.value << " DB."; | 
|  | meta_table_.Reset(); | 
|  | db_.reset(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground( | 
|  | base::OnceClosure callback) { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | Commit(); | 
|  | if (callback) | 
|  | PostClientTask(FROM_HERE, std::move(callback)); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::DoCloseInBackground() { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  | // Commit any pending operations | 
|  | Commit(); | 
|  |  | 
|  | meta_table_.Reset(); | 
|  | db_.reset(); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::DatabaseErrorCallback( | 
|  | int error, | 
|  | sql::Statement* stmt) { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (!sql::IsErrorCatastrophic(error)) | 
|  | return; | 
|  |  | 
|  | // TODO(shess): Running KillDatabase() multiple times should be | 
|  | // safe. | 
|  | if (corruption_detected_) | 
|  | return; | 
|  |  | 
|  | corruption_detected_ = true; | 
|  |  | 
|  | if (!initialized_) { | 
|  | sql::UmaHistogramSqliteResult( | 
|  | base::StrCat({histogram_tag_.value, ".ErrorInitializeDB"}), error); | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | base::UmaHistogramSparse( | 
|  | base::StrCat({histogram_tag_.value, ".WinGetLastErrorInitializeDB"}), | 
|  | ::GetLastError()); | 
|  | #endif  // BUILDFLAG(IS_WIN) | 
|  | } | 
|  |  | 
|  | // Don't just do the close/delete here, as we are being called by |db| and | 
|  | // that seems dangerous. | 
|  | // TODO(shess): Consider just calling RazeAndPoison() immediately. | 
|  | // db_ may not be safe to reset at this point, but RazeAndPoison() | 
|  | // would cause the stack to unwind safely with errors. | 
|  | PostBackgroundTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&SQLitePersistentStoreBackendBase::KillDatabase, this)); | 
|  | } | 
|  |  | 
|  | void SQLitePersistentStoreBackendBase::KillDatabase() { | 
|  | DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (db_) { | 
|  | // This Backend will now be in-memory only. In a future run we will recreate | 
|  | // the database. Hopefully things go better then! | 
|  | db_->RazeAndPoison(); | 
|  | meta_table_.Reset(); | 
|  | db_.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace net |