xref: /aosp_15_r20/external/cronet/net/extras/sqlite/sqlite_persistent_store_backend_base.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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