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"
16 scoped_ptr
<Recovery
> Recovery::Begin(
17 Connection
* connection
,
18 const base::FilePath
& db_path
) {
19 scoped_ptr
<Recovery
> r(new Recovery(connection
));
20 if (!r
->Init(db_path
)) {
21 // TODO(shess): Should Init() failure result in Raze()?
23 return scoped_ptr
<Recovery
>();
30 bool Recovery::Recovered(scoped_ptr
<Recovery
> r
) {
35 void Recovery::Unrecoverable(scoped_ptr
<Recovery
> r
) {
37 // ~Recovery() will RAZE_AND_POISON.
40 Recovery::Recovery(Connection
* connection
)
43 // Result should keep the page size specified earlier.
45 recover_db_
.set_page_size(db_
->page_size_
);
47 // TODO(shess): This may not handle cases where the default page
48 // size is used, but the default has changed. I do not think this
49 // has ever happened. This could be handled by using "PRAGMA
50 // page_size", at the cost of potential additional failure cases.
53 Recovery::~Recovery() {
54 Shutdown(RAZE_AND_POISON
);
57 bool Recovery::Init(const base::FilePath
& db_path
) {
58 // Prevent the possibility of re-entering this code due to errors
59 // which happen while executing this code.
60 DCHECK(!db_
->has_error_callback());
62 // Break any outstanding transactions on the original database to
63 // prevent deadlocks reading through the attached version.
64 // TODO(shess): A client may legitimately wish to recover from
65 // within the transaction context, because it would potentially
66 // preserve any in-flight changes. Unfortunately, any attach-based
67 // system could not handle that. A system which manually queried
68 // one database and stored to the other possibly could, but would be
70 db_
->RollbackAllTransactions();
72 if (!recover_db_
.OpenTemporary())
75 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The
76 // virtual table implementation relies on SQLite internals for some
77 // types and functions, which could be copied inline to make it
78 // standalone. Or an alternate implementation could try to read
79 // through errors entirely at the SQLite level.
81 // For now, defer to the caller. The setup will succeed, but the
82 // later CREATE VIRTUAL TABLE call will fail, at which point the
83 // caller can fire Unrecoverable().
84 #if !defined(USE_SYSTEM_SQLITE)
85 int rc
= recoverVtableInit(recover_db_
.db_
);
86 if (rc
!= SQLITE_OK
) {
87 LOG(ERROR
) << "Failed to initialize recover module: "
88 << recover_db_
.GetErrorMessage();
93 // Turn on |SQLITE_RecoveryMode| for the handle, which allows
94 // reading certain broken databases.
95 if (!recover_db_
.Execute("PRAGMA writable_schema=1"))
98 if (!recover_db_
.AttachDatabase(db_path
, "corrupt"))
104 bool Recovery::Backup() {
106 CHECK(recover_db_
.is_open());
108 // TODO(shess): Some of the failure cases here may need further
109 // exploration. Just as elsewhere, persistent problems probably
110 // need to be razed, while anything which might succeed on a future
111 // run probably should be allowed to try. But since Raze() uses the
112 // same approach, even that wouldn't work when this code fails.
114 // The documentation for the backup system indicate a relatively
115 // small number of errors are expected:
116 // SQLITE_BUSY - cannot lock the destination database. This should
117 // only happen if someone has another handle to the
118 // database, Chromium generally doesn't do that.
119 // SQLITE_LOCKED - someone locked the source database. Should be
120 // impossible (perhaps anti-virus could?).
121 // SQLITE_READONLY - destination is read-only.
122 // SQLITE_IOERR - since source database is temporary, probably
123 // indicates that the destination contains blocks
124 // throwing errors, or gross filesystem errors.
125 // SQLITE_NOMEM - out of memory, should be transient.
127 // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered
128 // transient, with SQLITE_LOCKED being unclear.
130 // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a
131 // strong chance that Raze() would not resolve them. If Delete()
132 // deletes the database file, the code could then re-open the file
133 // and attempt the backup again.
135 // For now, this code attempts a best effort and records histograms
136 // to inform future development.
138 // Backup the original db from the recovered db.
139 const char* kMain
= "main";
140 sqlite3_backup
* backup
= sqlite3_backup_init(db_
->db_
, kMain
,
141 recover_db_
.db_
, kMain
);
143 // Error code is in the destination database handle.
144 int err
= sqlite3_errcode(db_
->db_
);
145 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err
);
146 LOG(ERROR
) << "sqlite3_backup_init() failed: "
147 << sqlite3_errmsg(db_
->db_
);
151 // -1 backs up the entire database.
152 int rc
= sqlite3_backup_step(backup
, -1);
153 int pages
= sqlite3_backup_pagecount(backup
);
154 // TODO(shess): sqlite3_backup_finish() appears to allow returning a
155 // different value from sqlite3_backup_step(). Circle back and
156 // figure out if that can usefully inform the decision of whether to
158 sqlite3_backup_finish(backup
);
161 if (rc
!= SQLITE_DONE
) {
162 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc
);
163 LOG(ERROR
) << "sqlite3_backup_step() failed: "
164 << sqlite3_errmsg(db_
->db_
);
167 // The destination database was locked. Give up, but leave the data
168 // in place. Maybe it won't be locked next time.
169 if (rc
== SQLITE_BUSY
|| rc
== SQLITE_LOCKED
) {
174 // Running out of memory should be transient, retry later.
175 if (rc
== SQLITE_NOMEM
) {
180 // TODO(shess): For now, leave the original database alone, pending
181 // results from Sqlite.RecoveryStep. Some errors should probably
182 // route to RAZE_AND_POISON.
183 if (rc
!= SQLITE_DONE
) {
188 // Clean up the recovery db, and terminate the main database
194 void Recovery::Shutdown(Recovery::Disposition raze
) {
199 if (raze
== RAZE_AND_POISON
) {
201 } else if (raze
== POISON
) {