OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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. |
| 4 |
| 5 #include "sql/recovery.h" |
| 6 |
| 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" |
| 12 |
| 13 namespace sql { |
| 14 |
| 15 // static |
| 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()? |
| 22 r->Shutdown(POISON); |
| 23 return scoped_ptr<Recovery>(); |
| 24 } |
| 25 |
| 26 return r.Pass(); |
| 27 } |
| 28 |
| 29 // static |
| 30 bool Recovery::Recovered(scoped_ptr<Recovery> r) { |
| 31 return r->Backup(); |
| 32 } |
| 33 |
| 34 // static |
| 35 void Recovery::Unrecoverable(scoped_ptr<Recovery> r) { |
| 36 CHECK(r->db_); |
| 37 // ~Recovery() will RAZE_AND_POISON. |
| 38 } |
| 39 |
| 40 Recovery::Recovery(Connection* connection) |
| 41 : db_(connection), |
| 42 recover_db_() { |
| 43 // Result should keep the page size specified earlier. |
| 44 if (db_->page_size_) |
| 45 recover_db_.set_page_size(db_->page_size_); |
| 46 |
| 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. |
| 51 } |
| 52 |
| 53 Recovery::~Recovery() { |
| 54 Shutdown(RAZE_AND_POISON); |
| 55 } |
| 56 |
| 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()); |
| 61 |
| 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 |
| 69 // more complicated. |
| 70 db_->RollbackAllTransactions(); |
| 71 |
| 72 if (!recover_db_.OpenTemporary()) |
| 73 return false; |
| 74 |
| 75 // Turn on |SQLITE_RecoveryMode| for the handle, which allows |
| 76 // reading certain broken databases. |
| 77 if (!recover_db_.Execute("PRAGMA writable_schema=1")) |
| 78 return false; |
| 79 |
| 80 if (!recover_db_.AttachDatabase(db_path, "corrupt")) |
| 81 return false; |
| 82 |
| 83 return true; |
| 84 } |
| 85 |
| 86 bool Recovery::Backup() { |
| 87 CHECK(db_); |
| 88 CHECK(recover_db_.is_open()); |
| 89 |
| 90 // TODO(shess): Some of the failure cases here may need further |
| 91 // exploration. Just as elsewhere, persistent problems probably |
| 92 // need to be razed, while anything which might succeed on a future |
| 93 // run probably should be allowed to try. But since Raze() uses the |
| 94 // same approach, even that wouldn't work when this code fails. |
| 95 // |
| 96 // The documentation for the backup system indicate a relatively |
| 97 // small number of errors are expected: |
| 98 // SQLITE_BUSY - cannot lock the destination database. This should |
| 99 // only happen if someone has another handle to the |
| 100 // database, Chromium generally doesn't do that. |
| 101 // SQLITE_LOCKED - someone locked the source database. Should be |
| 102 // impossible (perhaps anti-virus could?). |
| 103 // SQLITE_READONLY - destination is read-only. |
| 104 // SQLITE_IOERR - since source database is temporary, probably |
| 105 // indicates that the destination contains blocks |
| 106 // throwing errors, or gross filesystem errors. |
| 107 // SQLITE_NOMEM - out of memory, should be transient. |
| 108 // |
| 109 // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered |
| 110 // transient, with SQLITE_LOCKED being unclear. |
| 111 // |
| 112 // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a |
| 113 // strong chance that Raze() would not resolve them. If Delete() |
| 114 // deletes the database file, the code could then re-open the file |
| 115 // and attempt the backup again. |
| 116 // |
| 117 // For now, this code attempts a best effort and records histograms |
| 118 // to inform future development. |
| 119 |
| 120 // Backup the original db from the recovered db. |
| 121 const char* kMain = "main"; |
| 122 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, |
| 123 recover_db_.db_, kMain); |
| 124 if (!backup) { |
| 125 // Error code is in the destination database handle. |
| 126 int err = sqlite3_errcode(db_->db_); |
| 127 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); |
| 128 LOG(ERROR) << "sqlite3_backup_init() failed: " |
| 129 << sqlite3_errmsg(db_->db_); |
| 130 return false; |
| 131 } |
| 132 |
| 133 // -1 backs up the entire database. |
| 134 int rc = sqlite3_backup_step(backup, -1); |
| 135 int pages = sqlite3_backup_pagecount(backup); |
| 136 // TODO(shess): sqlite3_backup_finish() appears to allow returning a |
| 137 // different value from sqlite3_backup_step(). Circle back and |
| 138 // figure out if that can usefully inform the decision of whether to |
| 139 // retry or not. |
| 140 sqlite3_backup_finish(backup); |
| 141 DCHECK_GT(pages, 0); |
| 142 |
| 143 if (rc != SQLITE_DONE) { |
| 144 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); |
| 145 LOG(ERROR) << "sqlite3_backup_step() failed: " |
| 146 << sqlite3_errmsg(db_->db_); |
| 147 } |
| 148 |
| 149 // The destination database was locked. Give up, but leave the data |
| 150 // in place. Maybe it won't be locked next time. |
| 151 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { |
| 152 Shutdown(POISON); |
| 153 return false; |
| 154 } |
| 155 |
| 156 // Running out of memory should be transient, retry later. |
| 157 if (rc == SQLITE_NOMEM) { |
| 158 Shutdown(POISON); |
| 159 return false; |
| 160 } |
| 161 |
| 162 // TODO(shess): For now, leave the original database alone, pending |
| 163 // results from Sqlite.RecoveryStep. Some errors should probably |
| 164 // route to RAZE_AND_POISON. |
| 165 if (rc != SQLITE_DONE) { |
| 166 Shutdown(POISON); |
| 167 return false; |
| 168 } |
| 169 |
| 170 // Clean up the recovery db, and terminate the main database |
| 171 // connection. |
| 172 Shutdown(POISON); |
| 173 return true; |
| 174 } |
| 175 |
| 176 void Recovery::Shutdown(Recovery::Disposition raze) { |
| 177 if (!db_) |
| 178 return; |
| 179 |
| 180 recover_db_.Close(); |
| 181 if (raze == RAZE_AND_POISON) { |
| 182 db_->RazeAndClose(); |
| 183 } else if (raze == POISON) { |
| 184 db_->Poison(); |
| 185 } |
| 186 db_ = NULL; |
| 187 } |
| 188 |
| 189 } // namespace sql |
OLD | NEW |