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 "sql/connection.h" |
| 10 #include "third_party/sqlite/sqlite3.h" |
| 11 |
| 12 namespace sql { |
| 13 |
| 14 Recovery::Recovery(Connection* connection) |
| 15 : db_(connection), |
| 16 recover_db_(), |
| 17 attach_(&recover_db_) { |
| 18 // TODO(shess): Consider only applying this for small databases. |
| 19 |
| 20 // Result should keep the page size specified earlier. |
| 21 if (db_->page_size_) |
| 22 recover_db_.set_page_size(db_->page_size_); |
| 23 } |
| 24 |
| 25 Recovery::~Recovery() { |
| 26 if (db_) |
| 27 Unrecoverable(); |
| 28 } |
| 29 |
| 30 bool Recovery::Open(const base::FilePath& db_path) { |
| 31 // Prevent re-entrancy if some call to db_ happens to fail. |
| 32 // TODO(shess): Should this be scoped? Should the caller manage it? |
| 33 db_->reset_error_callback(); |
| 34 |
| 35 if (!recover_db_.OpenTemporary()) |
| 36 return false; |
| 37 |
| 38 // Turn on |SQLITE_RecoveryMode|, which allows reading certain |
| 39 // broken databases. |
| 40 // TODO(shess): Double-check that this works for attached dbs. |
| 41 if (!recover_db_.Execute("PRAGMA writable_schema=1")) { |
| 42 recover_db_.Close(); |
| 43 return false; |
| 44 } |
| 45 |
| 46 // Break any outstanding transactions on original database to |
| 47 // prevent deadlocks reading through the attached version. |
| 48 // TODO(shess): A client may legitimately wish to recover from |
| 49 // within the transaction context, because it would potentially |
| 50 // preserve the in-process changes. Unfortunately, any attach-based |
| 51 // system could not handle that. A system which manually queried |
| 52 // one database and stored to the other could. |
| 53 db_->RollbackAllTransactions(); |
| 54 |
| 55 if (!attach_.Attach(db_path, "corrupt")) { |
| 56 recover_db_.Close(); |
| 57 return false; |
| 58 } |
| 59 |
| 60 return true; |
| 61 } |
| 62 |
| 63 void Recovery::Unrecoverable() { |
| 64 CHECK(db_); |
| 65 attach_.Detach(); |
| 66 recover_db_.Close(); |
| 67 db_->RazeAndClose(); |
| 68 db_ = NULL; |
| 69 } |
| 70 |
| 71 bool Recovery::Recovered() { |
| 72 CHECK(db_); |
| 73 CHECK(recover_db_.is_open()); |
| 74 |
| 75 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, "main", |
| 76 recover_db_.db_, "main"); |
| 77 if (!backup) { |
| 78 LOG(ERROR) << "Failed to make backup?"; |
| 79 return false; |
| 80 } |
| 81 |
| 82 // -1 backs up the entire database. |
| 83 int rc = sqlite3_backup_step(backup, -1); |
| 84 int pages = sqlite3_backup_pagecount(backup); |
| 85 sqlite3_backup_finish(backup); |
| 86 DCHECK_GT(pages, 0); |
| 87 |
| 88 // The destination database was locked. |
| 89 if (rc == SQLITE_BUSY) { |
| 90 // TODO(shess): Ignore it? What if it's ALWAYS locked? If it's |
| 91 // locked, it probably can't be deleted, either, and Raze() will |
| 92 // fail the same way. |
| 93 attach_.Detach(); |
| 94 recover_db_.Close(); |
| 95 db_->CloseAndPoison(); |
| 96 db_ = NULL; |
| 97 return false; |
| 98 } |
| 99 |
| 100 // It is unclear what errors are possible at this point, and how to |
| 101 // handle them. Some could perhaps be resolved with a future retry, |
| 102 // but it is unclear how that could possibly work. |
| 103 // TODO(shess): I wonder about try/sleep/try :-). |
| 104 if (rc != SQLITE_DONE) { |
| 105 attach_.Detach(); |
| 106 recover_db_.Close(); |
| 107 db_->RazeAndClose(); |
| 108 db_ = NULL; |
| 109 return false; |
| 110 } |
| 111 |
| 112 // Clean up the recovery db, and terminate the main database |
| 113 // connection. |
| 114 attach_.Detach(); |
| 115 recover_db_.Close(); |
| 116 db_->CloseAndPoison(); |
| 117 db_ = NULL; |
| 118 return true; |
| 119 } |
| 120 |
| 121 } // namespace sql |
OLD | NEW |