| 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
|
|
|