Index: sql/recovery.cc |
diff --git a/sql/recovery.cc b/sql/recovery.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..32c49b7dda915c0adeab178b629cb3612097bd8b |
--- /dev/null |
+++ b/sql/recovery.cc |
@@ -0,0 +1,121 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "sql/recovery.h" |
+ |
+#include "base/files/file_path.h" |
+#include "base/logging.h" |
+#include "sql/connection.h" |
+#include "third_party/sqlite/sqlite3.h" |
+ |
+namespace sql { |
+ |
+Recovery::Recovery(Connection* connection) |
+ : db_(connection), |
+ recover_db_(), |
+ attach_(&recover_db_) { |
+ // TODO(shess): Consider only applying this for small databases. |
+ |
+ // Result should keep the page size specified earlier. |
+ if (db_->page_size_) |
+ recover_db_.set_page_size(db_->page_size_); |
+} |
+ |
+Recovery::~Recovery() { |
+ if (db_) |
+ Unrecoverable(); |
+} |
+ |
+bool Recovery::Open(const base::FilePath& db_path) { |
+ // Prevent re-entrancy if some call to db_ happens to fail. |
+ // TODO(shess): Should this be scoped? Should the caller manage it? |
+ db_->reset_error_callback(); |
+ |
+ if (!recover_db_.OpenTemporary()) |
+ return false; |
+ |
+ // Turn on |SQLITE_RecoveryMode|, which allows reading certain |
+ // broken databases. |
+ // TODO(shess): Double-check that this works for attached dbs. |
+ if (!recover_db_.Execute("PRAGMA writable_schema=1")) { |
+ recover_db_.Close(); |
+ return false; |
+ } |
+ |
+ // Break any outstanding transactions on original database to |
+ // prevent deadlocks reading through the attached version. |
+ // TODO(shess): A client may legitimately wish to recover from |
+ // within the transaction context, because it would potentially |
+ // preserve the in-process changes. Unfortunately, any attach-based |
+ // system could not handle that. A system which manually queried |
+ // one database and stored to the other could. |
+ db_->RollbackAllTransactions(); |
+ |
+ if (!attach_.Attach(db_path, "corrupt")) { |
+ recover_db_.Close(); |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+void Recovery::Unrecoverable() { |
+ CHECK(db_); |
+ attach_.Detach(); |
+ recover_db_.Close(); |
+ db_->RazeAndClose(); |
+ db_ = NULL; |
+} |
+ |
+bool Recovery::Recovered() { |
+ CHECK(db_); |
+ CHECK(recover_db_.is_open()); |
+ |
+ sqlite3_backup* backup = sqlite3_backup_init(db_->db_, "main", |
+ recover_db_.db_, "main"); |
+ if (!backup) { |
+ LOG(ERROR) << "Failed to make backup?"; |
+ return false; |
+ } |
+ |
+ // -1 backs up the entire database. |
+ int rc = sqlite3_backup_step(backup, -1); |
+ int pages = sqlite3_backup_pagecount(backup); |
+ sqlite3_backup_finish(backup); |
+ DCHECK_GT(pages, 0); |
+ |
+ // The destination database was locked. |
+ if (rc == SQLITE_BUSY) { |
+ // TODO(shess): Ignore it? What if it's ALWAYS locked? If it's |
+ // locked, it probably can't be deleted, either, and Raze() will |
+ // fail the same way. |
+ attach_.Detach(); |
+ recover_db_.Close(); |
+ db_->CloseAndPoison(); |
+ db_ = NULL; |
+ return false; |
+ } |
+ |
+ // It is unclear what errors are possible at this point, and how to |
+ // handle them. Some could perhaps be resolved with a future retry, |
+ // but it is unclear how that could possibly work. |
+ // TODO(shess): I wonder about try/sleep/try :-). |
+ if (rc != SQLITE_DONE) { |
+ attach_.Detach(); |
+ recover_db_.Close(); |
+ db_->RazeAndClose(); |
+ db_ = NULL; |
+ return false; |
+ } |
+ |
+ // Clean up the recovery db, and terminate the main database |
+ // connection. |
+ attach_.Detach(); |
+ recover_db_.Close(); |
+ db_->CloseAndPoison(); |
+ db_ = NULL; |
+ return true; |
+} |
+ |
+} // namespace sql |