File: web_database_backend.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (200 lines) | stat: -rw-r--r-- 7,318 bytes parent folder | download | duplicates (5)
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();
}