0

[sql] Prevent recovery against a closed database.

The referenced bug is a crash which appears to happen when recovery is
attempted against a poisoned (or closed) database.  Modify
sql::Recovery::Begin() to explicitly short-circuit recovery if the
passed connection handle is not open.  Otherwise the recovery code will
work until the recovered data is restored over the original, and then
fail.

Also modify the sql::Connection::ReportDiagnosticInfo() to clear the
error callback while doing any integrity check.  This is a bit of a
hack in the interests of not rooting out the crazy error-callback
assumptions at this time.

BUG=583106

Review URL: https://codereview.chromium.org/1657593005

Cr-Commit-Position: refs/heads/master@{#372896}
This commit is contained in:
shess
2016-02-01 21:15:06 -08:00
committed by Commit bot
parent 461ea36889
commit 874ea1bd6e
3 changed files with 29 additions and 0 deletions

@ -268,7 +268,15 @@ void Connection::ReportDiagnosticInfo(int extended_error, Statement* stmt) {
std::string debug_info;
const int error = (extended_error & 0xFF);
if (error == SQLITE_CORRUPT) {
// CollectCorruptionInfo() is implemented in terms of sql::Connection,
// prevent reentrant calls to the error callback.
// TODO(shess): Rewrite IntegrityCheckHelper() in terms of raw SQLite.
ErrorCallback original_callback = std::move(error_callback_);
reset_error_callback();
debug_info = CollectCorruptionInfo();
error_callback_ = std::move(original_callback);
} else {
debug_info = CollectErrorInfo(extended_error, stmt);
}

@ -108,6 +108,16 @@ bool Recovery::FullRecoverySupported() {
scoped_ptr<Recovery> Recovery::Begin(
Connection* connection,
const base::FilePath& db_path) {
// Recovery is likely to be used in error handling. Since recovery changes
// the state of the handle, protect against multiple layers attempting the
// same recovery.
if (!connection->is_open()) {
// Warn about API mis-use.
DLOG_IF(FATAL, !connection->poisoned_)
<< "Illegal to recover with closed database";
return scoped_ptr<Recovery>();
}
scoped_ptr<Recovery> r(new Recovery(connection));
if (!r->Init(db_path)) {
// TODO(shess): Should Init() failure result in Raze()?

@ -103,6 +103,17 @@ TEST_F(SQLRecoveryTest, RecoverBasic) {
EXPECT_TRUE(db().is_open());
ASSERT_EQ("", GetSchema(&db()));
// Attempting to recover a previously-recovered handle fails early.
{
scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path());
ASSERT_TRUE(recovery.get());
recovery.reset();
recovery = sql::Recovery::Begin(&db(), db_path());
ASSERT_FALSE(recovery.get());
}
ASSERT_TRUE(Reopen());
// Recreate the database.
ASSERT_TRUE(db().Execute(kCreateSql));
ASSERT_TRUE(db().Execute(kInsertSql));