1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
// 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();
}
|