Add utility functions needed for rect-based event targeting
[chromium-blink-merge.git] / sql / recovery.cc
blobc750fd01c3f36730527e05fa4246e2c15bc3a18b
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sql/recovery.h"
7 #include "base/files/file_path.h"
8 #include "base/logging.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "sql/connection.h"
11 #include "third_party/sqlite/sqlite3.h"
13 namespace sql {
15 // static
16 bool Recovery::FullRecoverySupported() {
17 // TODO(shess): See comment in Init().
18 #if defined(USE_SYSTEM_SQLITE)
19 return false;
20 #else
21 return true;
22 #endif
25 // static
26 scoped_ptr<Recovery> Recovery::Begin(
27 Connection* connection,
28 const base::FilePath& db_path) {
29 scoped_ptr<Recovery> r(new Recovery(connection));
30 if (!r->Init(db_path)) {
31 // TODO(shess): Should Init() failure result in Raze()?
32 r->Shutdown(POISON);
33 return scoped_ptr<Recovery>();
36 return r.Pass();
39 // static
40 bool Recovery::Recovered(scoped_ptr<Recovery> r) {
41 return r->Backup();
44 // static
45 void Recovery::Unrecoverable(scoped_ptr<Recovery> r) {
46 CHECK(r->db_);
47 // ~Recovery() will RAZE_AND_POISON.
50 // static
51 void Recovery::Rollback(scoped_ptr<Recovery> r) {
52 // TODO(shess): HISTOGRAM to track? Or just have people crash out?
53 // Crash and dump?
54 r->Shutdown(POISON);
57 Recovery::Recovery(Connection* connection)
58 : db_(connection),
59 recover_db_() {
60 // Result should keep the page size specified earlier.
61 if (db_->page_size_)
62 recover_db_.set_page_size(db_->page_size_);
64 // TODO(shess): This may not handle cases where the default page
65 // size is used, but the default has changed. I do not think this
66 // has ever happened. This could be handled by using "PRAGMA
67 // page_size", at the cost of potential additional failure cases.
70 Recovery::~Recovery() {
71 Shutdown(RAZE_AND_POISON);
74 bool Recovery::Init(const base::FilePath& db_path) {
75 // Prevent the possibility of re-entering this code due to errors
76 // which happen while executing this code.
77 DCHECK(!db_->has_error_callback());
79 // Break any outstanding transactions on the original database to
80 // prevent deadlocks reading through the attached version.
81 // TODO(shess): A client may legitimately wish to recover from
82 // within the transaction context, because it would potentially
83 // preserve any in-flight changes. Unfortunately, any attach-based
84 // system could not handle that. A system which manually queried
85 // one database and stored to the other possibly could, but would be
86 // more complicated.
87 db_->RollbackAllTransactions();
89 // Disable exclusive locking mode so that the attached database can
90 // access things. The locking_mode change is not active until the
91 // next database access, so immediately force an access. Enabling
92 // writable_schema allows processing through certain kinds of
93 // corruption.
94 // TODO(shess): It would be better to just close the handle, but it
95 // is necessary for the final backup which rewrites things. It
96 // might be reasonable to close then re-open the handle.
97 ignore_result(db_->Execute("PRAGMA writable_schema=1"));
98 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL"));
99 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master"));
101 if (!recover_db_.OpenTemporary())
102 return false;
104 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The
105 // virtual table implementation relies on SQLite internals for some
106 // types and functions, which could be copied inline to make it
107 // standalone. Or an alternate implementation could try to read
108 // through errors entirely at the SQLite level.
110 // For now, defer to the caller. The setup will succeed, but the
111 // later CREATE VIRTUAL TABLE call will fail, at which point the
112 // caller can fire Unrecoverable().
113 #if !defined(USE_SYSTEM_SQLITE)
114 int rc = recoverVtableInit(recover_db_.db_);
115 if (rc != SQLITE_OK) {
116 LOG(ERROR) << "Failed to initialize recover module: "
117 << recover_db_.GetErrorMessage();
118 return false;
120 #endif
122 // Turn on |SQLITE_RecoveryMode| for the handle, which allows
123 // reading certain broken databases.
124 if (!recover_db_.Execute("PRAGMA writable_schema=1"))
125 return false;
127 if (!recover_db_.AttachDatabase(db_path, "corrupt"))
128 return false;
130 return true;
133 bool Recovery::Backup() {
134 CHECK(db_);
135 CHECK(recover_db_.is_open());
137 // TODO(shess): Some of the failure cases here may need further
138 // exploration. Just as elsewhere, persistent problems probably
139 // need to be razed, while anything which might succeed on a future
140 // run probably should be allowed to try. But since Raze() uses the
141 // same approach, even that wouldn't work when this code fails.
143 // The documentation for the backup system indicate a relatively
144 // small number of errors are expected:
145 // SQLITE_BUSY - cannot lock the destination database. This should
146 // only happen if someone has another handle to the
147 // database, Chromium generally doesn't do that.
148 // SQLITE_LOCKED - someone locked the source database. Should be
149 // impossible (perhaps anti-virus could?).
150 // SQLITE_READONLY - destination is read-only.
151 // SQLITE_IOERR - since source database is temporary, probably
152 // indicates that the destination contains blocks
153 // throwing errors, or gross filesystem errors.
154 // SQLITE_NOMEM - out of memory, should be transient.
156 // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered
157 // transient, with SQLITE_LOCKED being unclear.
159 // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a
160 // strong chance that Raze() would not resolve them. If Delete()
161 // deletes the database file, the code could then re-open the file
162 // and attempt the backup again.
164 // For now, this code attempts a best effort and records histograms
165 // to inform future development.
167 // Backup the original db from the recovered db.
168 const char* kMain = "main";
169 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain,
170 recover_db_.db_, kMain);
171 if (!backup) {
172 // Error code is in the destination database handle.
173 int err = sqlite3_errcode(db_->db_);
174 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err);
175 LOG(ERROR) << "sqlite3_backup_init() failed: "
176 << sqlite3_errmsg(db_->db_);
177 return false;
180 // -1 backs up the entire database.
181 int rc = sqlite3_backup_step(backup, -1);
182 int pages = sqlite3_backup_pagecount(backup);
183 // TODO(shess): sqlite3_backup_finish() appears to allow returning a
184 // different value from sqlite3_backup_step(). Circle back and
185 // figure out if that can usefully inform the decision of whether to
186 // retry or not.
187 sqlite3_backup_finish(backup);
188 DCHECK_GT(pages, 0);
190 if (rc != SQLITE_DONE) {
191 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc);
192 LOG(ERROR) << "sqlite3_backup_step() failed: "
193 << sqlite3_errmsg(db_->db_);
196 // The destination database was locked. Give up, but leave the data
197 // in place. Maybe it won't be locked next time.
198 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
199 Shutdown(POISON);
200 return false;
203 // Running out of memory should be transient, retry later.
204 if (rc == SQLITE_NOMEM) {
205 Shutdown(POISON);
206 return false;
209 // TODO(shess): For now, leave the original database alone, pending
210 // results from Sqlite.RecoveryStep. Some errors should probably
211 // route to RAZE_AND_POISON.
212 if (rc != SQLITE_DONE) {
213 Shutdown(POISON);
214 return false;
217 // Clean up the recovery db, and terminate the main database
218 // connection.
219 Shutdown(POISON);
220 return true;
223 void Recovery::Shutdown(Recovery::Disposition raze) {
224 if (!db_)
225 return;
227 recover_db_.Close();
228 if (raze == RAZE_AND_POISON) {
229 db_->RazeAndClose();
230 } else if (raze == POISON) {
231 db_->Poison();
233 db_ = NULL;
236 } // namespace sql