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. 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 // 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. 80 // 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(); 89 return false; 90 } 91 #endif 92 93 // Turn on |SQLITE_RecoveryMode| for the handle, which allows 94 // reading certain broken databases. 95 if (!recover_db_.Execute("PRAGMA writable_schema=1")) 96 return false; 97 98 if (!recover_db_.AttachDatabase(db_path, "corrupt")) 99 return false; 100 101 return true; 102 } 103 104 bool Recovery::Backup() { 105 CHECK(db_); 106 CHECK(recover_db_.is_open()); 107 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. 113 // 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. 126 // 127 // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered 128 // transient, with SQLITE_LOCKED being unclear. 129 // 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. 134 // 135 // For now, this code attempts a best effort and records histograms 136 // to inform future development. 137 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); 142 if (!backup) { 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_); 148 return false; 149 } 150 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 157 // retry or not. 158 sqlite3_backup_finish(backup); 159 DCHECK_GT(pages, 0); 160 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_); 165 } 166 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) { 170 Shutdown(POISON); 171 return false; 172 } 173 174 // Running out of memory should be transient, retry later. 175 if (rc == SQLITE_NOMEM) { 176 Shutdown(POISON); 177 return false; 178 } 179 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) { 184 Shutdown(POISON); 185 return false; 186 } 187 188 // Clean up the recovery db, and terminate the main database 189 // connection. 190 Shutdown(POISON); 191 return true; 192 } 193 194 void Recovery::Shutdown(Recovery::Disposition raze) { 195 if (!db_) 196 return; 197 198 recover_db_.Close(); 199 if (raze == RAZE_AND_POISON) { 200 db_->RazeAndClose(); 201 } else if (raze == POISON) { 202 db_->Poison(); 203 } 204 db_ = NULL; 205 } 206 207 } // namespace sql 208