| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/webdata/common/web_database_backend.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "components/os_crypt/async/common/encryptor.h" |
| #include "components/webdata/common/web_data_request_manager.h" |
| #include "components/webdata/common/web_database.h" |
| #include "components/webdata/common/web_database_table.h" |
| #include "sql/error_delegate_util.h" |
| #include "sql/sqlite_result_code.h" |
| |
| WebDatabaseBackend::WebDatabaseBackend( |
| const base::FilePath& path, |
| std::unique_ptr<Delegate> delegate, |
| const scoped_refptr<base::SequencedTaskRunner>& db_thread) |
| : base::RefCountedDeleteOnSequence<WebDatabaseBackend>(db_thread), |
| db_path_(path), |
| delegate_(std::move(delegate)) { |
| // WebDatabaseBackend is created on the client sequence then accessed on the |
| // DB sequence. |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| void WebDatabaseBackend::AddTable(std::unique_ptr<WebDatabaseTable> table) { |
| CHECK(!init_complete_) << "AddTable must be called before init starts."; |
| tables_.push_back(std::move(table)); |
| } |
| |
| void WebDatabaseBackend::MaybeInitEncryptorOnUiSequence( |
| os_crypt_async::Encryptor encryptor) { |
| CHECK(!encryptor_) << "Init should only be called once."; |
| // Encryptor is thread safe. This transfers it from the UI sequence to the DB |
| // sequence. The CHECKs above and below guarantee this only happens once, |
| // before any tasks have been posted to the DB sequence. |
| encryptor_.emplace(std::move(encryptor)); |
| } |
| |
| void WebDatabaseBackend::InitDatabase() { |
| // MaybeInitEncryptorOnUiSequence must be called first. |
| CHECK(encryptor_); |
| LoadDatabaseIfNecessary(); |
| if (delegate_) { |
| delegate_->DBLoaded(init_status_, diagnostics_); |
| } |
| } |
| |
| void WebDatabaseBackend::ShutdownDatabase() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (db_ && init_status_ == sql::INIT_OK) { |
| db_->CommitTransaction(); |
| } |
| db_.reset(); |
| init_complete_ = true; // Ensures the init sequence is not re-run. |
| init_status_ = sql::INIT_FAILURE; |
| } |
| |
| void WebDatabaseBackend::DBWriteTaskWrapper( |
| WebDatabaseService::WriteTask task, |
| std::unique_ptr<WebDataRequest> request) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(init_complete_) << "Init must be complete before running a DB task."; |
| if (!request->IsActive()) { |
| return; |
| } |
| ExecuteWriteTask(std::move(task)); |
| request_manager_->RequestCompleted(std::move(request), nullptr); |
| } |
| |
| void WebDatabaseBackend::ExecuteWriteTask(WebDatabaseService::WriteTask task) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(init_complete_) << "Init must be complete before running a DB task."; |
| // The database is not opened or have been shutdown. |
| if (!db_) { |
| return; |
| } |
| |
| auto transaction = database()->AcquireTransaction(); |
| if (init_status_ == sql::INIT_OK) { |
| WebDatabase::State state = std::move(task).Run(db_.get()); |
| if (state == WebDatabase::COMMIT_NEEDED) { |
| // Either commit the changes using the Commit(...) call or commit the |
| // changes via the scoped transaction. This is controlled through the |
| // Finch experiment 'SqlScopedTransactionWebDatabase'. |
| Commit(); |
| if (transaction) { |
| transaction->Commit(); |
| } |
| } |
| } |
| } |
| |
| void WebDatabaseBackend::DBReadTaskWrapper( |
| WebDatabaseService::ReadTask task, |
| std::unique_ptr<WebDataRequest> request) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(init_complete_) << "Init must be complete before running a DB task."; |
| if (!request->IsActive()) { |
| return; |
| } |
| std::unique_ptr<WDTypedResult> result = ExecuteReadTask(std::move(task)); |
| request_manager_->RequestCompleted(std::move(request), std::move(result)); |
| } |
| |
| std::unique_ptr<WDTypedResult> WebDatabaseBackend::ExecuteReadTask( |
| WebDatabaseService::ReadTask task) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(init_complete_) << "Init must be complete before running a DB task."; |
| return (db_ && init_status_ == sql::INIT_OK) ? std::move(task).Run(db_.get()) |
| : nullptr; |
| } |
| |
| WebDatabaseBackend::~WebDatabaseBackend() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ShutdownDatabase(); |
| } |
| |
| void WebDatabaseBackend::LoadDatabaseIfNecessary() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (init_complete_ || db_path_.empty()) { |
| return; |
| } |
| |
| init_complete_ = true; |
| db_ = std::make_unique<WebDatabase>(); |
| |
| for (const auto& table : tables_) { |
| db_->AddTable(table.get()); |
| } |
| |
| // Unretained to avoid a ref loop since we own |db_|. |
| db_->set_error_callback(base::BindRepeating( |
| &WebDatabaseBackend::DatabaseErrorCallback, base::Unretained(this))); |
| diagnostics_.clear(); |
| catastrophic_error_occurred_ = false; |
| init_status_ = db_->Init(db_path_, &(*encryptor_)); |
| |
| if (init_status_ != sql::INIT_OK) { |
| // The database failed to be opened. This can be caused by a third-party |
| // that locks or has an exclusive sql query running. In that scenario, |
| // the initial error code is stored in `init_status_` and |
| // `catastrophic_error_occurred_` to ensure the user is getting the window |
| // notification about the profile being corrupt. |
| // |
| // Since Chrome keeps running after the error windows, we do mitigate the |
| // assumption of the WebDatabase not being null by opening an in-memory |
| // and empty database. |
| db_->GetSQLConnection()->Close(); |
| sql::InitStatus memory_init_status = |
| db_->Init(base::FilePath(WebDatabase::kInMemoryPath), &(*encryptor_)); |
| CHECK_EQ(memory_init_status, sql::INIT_OK); |
| } |
| |
| DCHECK(db_->GetSQLConnection()); |
| DCHECK(db_->GetSQLConnection()->is_open()); |
| |
| // A catastrophic error might have happened and recovered. |
| if (catastrophic_error_occurred_) { |
| init_status_ = sql::INIT_OK_WITH_DATA_LOSS; |
| } |
| db_->BeginTransaction(); |
| } |
| |
| void WebDatabaseBackend::DatabaseErrorCallback(int error, |
| sql::Statement* statement) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| sql::UmaHistogramSqliteResult("WebDatabase.DatabaseErrors", error); |
| |
| // We ignore any further error callbacks after the first catastrophic error. |
| if (!catastrophic_error_occurred_ && sql::IsErrorCatastrophic(error)) { |
| catastrophic_error_occurred_ = true; |
| diagnostics_.clear(); |
| // If the error is triggered during the call to Database::Open(...), it is |
| // possible that the database is not opened (https://crbug.com/420369590). |
| // The call to GetDiagnosticInfo(...) will execute sql statements; which is |
| // invalid on a closed database. |
| if (db_->GetSQLConnection()->is_open()) { |
| diagnostics_ += db_->GetDiagnosticInfo(error, statement); |
| } |
| diagnostics_ += sql::GetCorruptFileDiagnosticsInfo(db_path_); |
| |
| db_->GetSQLConnection()->RazeAndPoison(); |
| } |
| } |
| |
| void WebDatabaseBackend::Commit() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(db_); |
| DCHECK_EQ(sql::INIT_OK, init_status_); |
| db_->CommitTransaction(); |
| db_->BeginTransaction(); |
| } |