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