• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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