| // 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/metrics/histogram_macros_local.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "sql/database.h" |
| #include "sql/error_delegate_util.h" |
| |
| namespace net { |
| |
| SQLitePersistentStoreBackendBase::SQLitePersistentStoreBackendBase( |
| const base::FilePath& path, |
| std::string 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_(std::move(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/1430231): 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{ |
| .exclusive_locking = false, |
| .exclusive_database_file_lock = enable_exclusive_access_}); |
| |
| db_->set_histogram_tag(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))); |
| |
| bool has_been_preloaded = false; |
| // It is not possible to preload a database opened with exclusive access, |
| // because the file cannot be opened again to preload it. In this case, |
| // preload before opening the database. |
| if (enable_exclusive_access_) { |
| // See coments in Database::Preload for explanation of these values. |
| constexpr int kPreReadSize = 128 * 1024 * 1024; // 128 MB |
| // TODO(crbug.com/1434166): Consider moving preload behind a database |
| // option. |
| base::PreReadFile(path_, /*is_executable=*/false, kPreReadSize); |
| has_been_preloaded = true; |
| } |
| |
| if (!db_->Open(path_)) { |
| DLOG(ERROR) << "Unable to open " << histogram_tag_ << " DB."; |
| RecordOpenDBProblem(); |
| Reset(); |
| return false; |
| } |
| |
| // Only attempt a preload if the database hasn't already been preloaded above. |
| if (!has_been_preloaded) { |
| db_->Preload(); |
| } |
| |
| if (!MigrateDatabaseSchema() || !CreateDatabaseSchema()) { |
| DLOG(ERROR) << "Unable to update or initialize " << histogram_tag_ |
| << " DB tables."; |
| RecordDBMigrationProblem(); |
| Reset(); |
| return false; |
| } |
| |
| base::UmaHistogramCustomTimes(histogram_tag_ + ".TimeInitializeDB", |
| timer.Elapsed(), base::Milliseconds(1), |
| base::Minutes(1), 50); |
| |
| initialized_ = DoInitializeDatabase(); |
| |
| if (!initialized_) { |
| DLOG(ERROR) << "Unable to initialize " << histogram_tag_ << " 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_ << " database is too new."; |
| return false; |
| } |
| |
| // |cur_version| is the version that the database ends up at, after all the |
| // database upgrade statements. |
| absl::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>(); |
| bool recovered = sql::Database::Delete(path_) && db()->Open(path_) && |
| meta_table_.Init(db(), current_version_number_, |
| compatible_version_number_); |
| LOCAL_HISTOGRAM_BOOLEAN("Net.SQLite.CorruptMetaTableRecovered", recovered); |
| if (!recovered) { |
| NOTREACHED() << "Unable to reset the " << histogram_tag_ << " 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; |
| |
| // 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 |