1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "sqlite_persistent_store_backend_base.h"
6
7 #include <utility>
8
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/functional/bind.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/task/sequenced_task_runner.h"
15 #include "base/time/time.h"
16 #include "base/timer/elapsed_timer.h"
17 #include "sql/database.h"
18 #include "sql/error_delegate_util.h"
19
20 #if BUILDFLAG(IS_WIN)
21 #include <windows.h>
22 #endif // BUILDFLAG(IS_WIN)
23
24 namespace net {
25
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)26 SQLitePersistentStoreBackendBase::SQLitePersistentStoreBackendBase(
27 const base::FilePath& path,
28 std::string histogram_tag,
29 const int current_version_number,
30 const int compatible_version_number,
31 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
32 scoped_refptr<base::SequencedTaskRunner> client_task_runner,
33 bool enable_exclusive_access)
34 : path_(path),
35 histogram_tag_(std::move(histogram_tag)),
36 current_version_number_(current_version_number),
37 compatible_version_number_(compatible_version_number),
38 background_task_runner_(std::move(background_task_runner)),
39 client_task_runner_(std::move(client_task_runner)),
40 enable_exclusive_access_(enable_exclusive_access) {}
41
~SQLitePersistentStoreBackendBase()42 SQLitePersistentStoreBackendBase::~SQLitePersistentStoreBackendBase() {
43 // If `db_` hasn't been reset by the time this destructor is called,
44 // a use-after-free could occur if the `db_` error callback is ever
45 // invoked. To guard against this, crash if `db_` hasn't been reset
46 // so that this use-after-free doesn't happen and so that we'll be
47 // alerted to the fact that a closer look at this code is needed.
48 CHECK(!db_.get()) << "Close should already have been called.";
49 }
50
Flush(base::OnceClosure callback)51 void SQLitePersistentStoreBackendBase::Flush(base::OnceClosure callback) {
52 DCHECK(!background_task_runner_->RunsTasksInCurrentSequence());
53 PostBackgroundTask(
54 FROM_HERE,
55 base::BindOnce(
56 &SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground, this,
57 std::move(callback)));
58 }
59
Close()60 void SQLitePersistentStoreBackendBase::Close() {
61 if (background_task_runner_->RunsTasksInCurrentSequence()) {
62 DoCloseInBackground();
63 } else {
64 // Must close the backend on the background runner.
65 PostBackgroundTask(
66 FROM_HERE,
67 base::BindOnce(&SQLitePersistentStoreBackendBase::DoCloseInBackground,
68 this));
69 }
70 }
71
SetBeforeCommitCallback(base::RepeatingClosure callback)72 void SQLitePersistentStoreBackendBase::SetBeforeCommitCallback(
73 base::RepeatingClosure callback) {
74 base::AutoLock locked(before_commit_callback_lock_);
75 before_commit_callback_ = std::move(callback);
76 }
77
InitializeDatabase()78 bool SQLitePersistentStoreBackendBase::InitializeDatabase() {
79 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
80
81 if (initialized_ || corruption_detected_) {
82 // Return false if we were previously initialized but the DB has since been
83 // closed, or if corruption caused a database reset during initialization.
84 return db_ != nullptr;
85 }
86
87 base::ElapsedTimer timer;
88
89 const base::FilePath dir = path_.DirName();
90 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) {
91 return false;
92 }
93
94 // TODO(crbug.com/1430231): Remove explicit_locking = false. This currently
95 // needs to be set to false because of several failing MigrationTests.
96 db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
97 .exclusive_locking = false,
98 .exclusive_database_file_lock = enable_exclusive_access_});
99
100 db_->set_histogram_tag(histogram_tag_);
101
102 // base::Unretained is safe because |this| owns (and therefore outlives) the
103 // sql::Database held by |db_|.
104 db_->set_error_callback(base::BindRepeating(
105 &SQLitePersistentStoreBackendBase::DatabaseErrorCallback,
106 base::Unretained(this)));
107
108 bool has_been_preloaded = false;
109 // It is not possible to preload a database opened with exclusive access,
110 // because the file cannot be opened again to preload it. In this case,
111 // preload before opening the database.
112 if (enable_exclusive_access_) {
113 has_been_preloaded = true;
114
115 // Can only attempt to preload before Open if the file exists.
116 if (base::PathExists(path_)) {
117 // See comments in Database::Preload for explanation of these values.
118 constexpr int kPreReadSize = 128 * 1024 * 1024; // 128 MB
119 // TODO(crbug.com/1434166): Consider moving preload behind a database
120 // option.
121 base::PreReadFile(path_, /*is_executable=*/false, /*sequential=*/false,
122 kPreReadSize);
123 }
124 }
125
126 if (!db_->Open(path_)) {
127 DLOG(ERROR) << "Unable to open " << histogram_tag_ << " DB.";
128 RecordOpenDBProblem();
129 Reset();
130 return false;
131 }
132
133 // Only attempt a preload if the database hasn't already been preloaded above.
134 if (!has_been_preloaded) {
135 db_->Preload();
136 }
137
138 if (!MigrateDatabaseSchema() || !CreateDatabaseSchema()) {
139 DLOG(ERROR) << "Unable to update or initialize " << histogram_tag_
140 << " DB tables.";
141 RecordDBMigrationProblem();
142 Reset();
143 return false;
144 }
145
146 base::UmaHistogramCustomTimes(histogram_tag_ + ".TimeInitializeDB",
147 timer.Elapsed(), base::Milliseconds(1),
148 base::Minutes(1), 50);
149
150 initialized_ = DoInitializeDatabase();
151
152 if (!initialized_) {
153 DLOG(ERROR) << "Unable to initialize " << histogram_tag_ << " DB.";
154 RecordOpenDBProblem();
155 Reset();
156 return false;
157 }
158
159 return true;
160 }
161
DoInitializeDatabase()162 bool SQLitePersistentStoreBackendBase::DoInitializeDatabase() {
163 return true;
164 }
165
Reset()166 void SQLitePersistentStoreBackendBase::Reset() {
167 if (db_ && db_->is_open())
168 db_->Raze();
169 meta_table_.Reset();
170 db_.reset();
171 }
172
Commit()173 void SQLitePersistentStoreBackendBase::Commit() {
174 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
175
176 {
177 base::AutoLock locked(before_commit_callback_lock_);
178 if (!before_commit_callback_.is_null())
179 before_commit_callback_.Run();
180 }
181
182 DoCommit();
183 }
184
PostBackgroundTask(const base::Location & origin,base::OnceClosure task)185 void SQLitePersistentStoreBackendBase::PostBackgroundTask(
186 const base::Location& origin,
187 base::OnceClosure task) {
188 if (!background_task_runner_->PostTask(origin, std::move(task))) {
189 LOG(WARNING) << "Failed to post task from " << origin.ToString()
190 << " to background_task_runner_.";
191 }
192 }
193
PostClientTask(const base::Location & origin,base::OnceClosure task)194 void SQLitePersistentStoreBackendBase::PostClientTask(
195 const base::Location& origin,
196 base::OnceClosure task) {
197 if (!client_task_runner_->PostTask(origin, std::move(task))) {
198 LOG(WARNING) << "Failed to post task from " << origin.ToString()
199 << " to client_task_runner_.";
200 }
201 }
202
MigrateDatabaseSchema()203 bool SQLitePersistentStoreBackendBase::MigrateDatabaseSchema() {
204 // Version check.
205 if (!meta_table_.Init(db_.get(), current_version_number_,
206 compatible_version_number_)) {
207 return false;
208 }
209
210 if (meta_table_.GetCompatibleVersionNumber() > current_version_number_) {
211 LOG(WARNING) << histogram_tag_ << " database is too new.";
212 return false;
213 }
214
215 // |cur_version| is the version that the database ends up at, after all the
216 // database upgrade statements.
217 std::optional<int> cur_version = DoMigrateDatabaseSchema();
218 if (!cur_version.has_value())
219 return false;
220
221 // Metatable is corrupted. Try to recover.
222 if (cur_version.value() < current_version_number_) {
223 meta_table_.Reset();
224 db_ = std::make_unique<sql::Database>();
225 bool recovered = sql::Database::Delete(path_) && db()->Open(path_) &&
226 meta_table_.Init(db(), current_version_number_,
227 compatible_version_number_);
228 base::UmaHistogramBoolean(histogram_tag_ + ".CorruptMetaTableRecovered",
229 recovered);
230 if (!recovered) {
231 NOTREACHED() << "Unable to reset the " << histogram_tag_ << " DB.";
232 meta_table_.Reset();
233 db_.reset();
234 return false;
235 }
236 }
237
238 return true;
239 }
240
FlushAndNotifyInBackground(base::OnceClosure callback)241 void SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground(
242 base::OnceClosure callback) {
243 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
244
245 Commit();
246 if (callback)
247 PostClientTask(FROM_HERE, std::move(callback));
248 }
249
DoCloseInBackground()250 void SQLitePersistentStoreBackendBase::DoCloseInBackground() {
251 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
252 // Commit any pending operations
253 Commit();
254
255 meta_table_.Reset();
256 db_.reset();
257 }
258
DatabaseErrorCallback(int error,sql::Statement * stmt)259 void SQLitePersistentStoreBackendBase::DatabaseErrorCallback(
260 int error,
261 sql::Statement* stmt) {
262 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
263
264 if (!sql::IsErrorCatastrophic(error))
265 return;
266
267 // TODO(shess): Running KillDatabase() multiple times should be
268 // safe.
269 if (corruption_detected_)
270 return;
271
272 corruption_detected_ = true;
273
274 if (!initialized_) {
275 sql::UmaHistogramSqliteResult(histogram_tag_ + ".ErrorInitializeDB", error);
276
277 #if BUILDFLAG(IS_WIN)
278 base::UmaHistogramSparse(histogram_tag_ + ".WinGetLastErrorInitializeDB",
279 ::GetLastError());
280 #endif // BUILDFLAG(IS_WIN)
281 }
282
283 // Don't just do the close/delete here, as we are being called by |db| and
284 // that seems dangerous.
285 // TODO(shess): Consider just calling RazeAndPoison() immediately.
286 // db_ may not be safe to reset at this point, but RazeAndPoison()
287 // would cause the stack to unwind safely with errors.
288 PostBackgroundTask(
289 FROM_HERE,
290 base::BindOnce(&SQLitePersistentStoreBackendBase::KillDatabase, this));
291 }
292
KillDatabase()293 void SQLitePersistentStoreBackendBase::KillDatabase() {
294 DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
295
296 if (db_) {
297 // This Backend will now be in-memory only. In a future run we will recreate
298 // the database. Hopefully things go better then!
299 db_->RazeAndPoison();
300 meta_table_.Reset();
301 db_.reset();
302 }
303 }
304
305 } // namespace net
306