blob: 074015046ba775d7c1e0fe5ef555135fa7959a69 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include <stdint.h>
#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "components/history/core/browser/history_constants.h"
#include "components/webdata/common/webdata_constants.h"
#include "content/public/common/content_constants.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "storage/browser/database/database_tracker.h"
#include "third_party/sqlite/sqlite3.h"
#if defined(OS_CHROMEOS)
#include "chromeos/chromeos_constants.h"
#endif // defined(OS_CHROMEOS)
namespace diagnostics {
namespace {
// Generic diagnostic test class for checking SQLite database integrity.
class SqliteIntegrityTest : public DiagnosticsTest {
public:
// These are bit flags, so each value should be a power of two.
enum Flags {
NO_FLAGS_SET = 0,
CRITICAL = 0x01,
REMOVE_IF_CORRUPT = 0x02,
};
SqliteIntegrityTest(uint32_t flags,
DiagnosticsTestId id,
const base::FilePath& db_path)
: DiagnosticsTest(id), flags_(flags), db_path_(db_path) {}
bool RecoveryImpl(DiagnosticsModel::Observer* observer) override {
int outcome_code = GetOutcomeCode();
if (flags_ & REMOVE_IF_CORRUPT) {
switch (outcome_code) {
case DIAG_SQLITE_ERROR_HANDLER_CALLED:
case DIAG_SQLITE_CANNOT_OPEN_DB:
case DIAG_SQLITE_DB_LOCKED:
case DIAG_SQLITE_PRAGMA_FAILED:
case DIAG_SQLITE_DB_CORRUPTED:
LOG(WARNING) << "Removing broken SQLite database: "
<< db_path_.value();
sql::Connection::Delete(db_path_);
break;
case DIAG_SQLITE_SUCCESS:
case DIAG_SQLITE_FILE_NOT_FOUND_OK:
case DIAG_SQLITE_FILE_NOT_FOUND:
break;
default:
DCHECK(false) << "Invalid outcome code: " << outcome_code;
break;
}
}
return true;
}
bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
// If we're given an absolute path, use it. If not, then assume it's under
// the profile directory.
base::FilePath path;
if (!db_path_.IsAbsolute())
path = GetUserDefaultProfileDir().Append(db_path_);
else
path = db_path_;
if (!base::PathExists(path)) {
if (flags_ & CRITICAL) {
RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND,
"File not found",
DiagnosticsModel::TEST_FAIL_CONTINUE);
} else {
RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK,
"File not found (but that is OK)",
DiagnosticsModel::TEST_OK);
}
return true;
}
int errors = 0;
{ // Scope the statement and database so they close properly.
sql::Connection database;
database.set_exclusive_locking();
scoped_refptr<ErrorRecorder> recorder(new ErrorRecorder);
// Set the error callback so that we can get useful results in a debug
// build for a corrupted database. Without setting the error callback,
// sql::Connection will just DCHECK.
database.set_error_callback(
base::Bind(&SqliteIntegrityTest::ErrorRecorder::RecordSqliteError,
recorder->AsWeakPtr(),
&database));
if (!database.Open(path)) {
RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB,
"Cannot open DB. Possibly corrupted");
return true;
}
if (recorder->has_error()) {
RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
recorder->FormatError());
return true;
}
sql::Statement statement(
database.GetUniqueStatement("PRAGMA integrity_check;"));
if (recorder->has_error()) {
RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
recorder->FormatError());
return true;
}
if (!statement.is_valid()) {
int error = database.GetErrorCode();
if (SQLITE_BUSY == error) {
RecordFailure(DIAG_SQLITE_DB_LOCKED,
"Database locked by another process");
} else {
std::string str("Pragma failed. Error: ");
str += base::IntToString(error);
RecordFailure(DIAG_SQLITE_PRAGMA_FAILED, str);
}
return false;
}
while (statement.Step()) {
std::string result(statement.ColumnString(0));
if ("ok" != result)
++errors;
}
if (recorder->has_error()) {
RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
recorder->FormatError());
return true;
}
}
// All done. Report to the user.
if (errors != 0) {
std::string str("Database corruption detected: ");
str += base::IntToString(errors) + " errors";
RecordFailure(DIAG_SQLITE_DB_CORRUPTED, str);
return true;
}
RecordSuccess("No corruption detected");
return true;
}
private:
class ErrorRecorder : public base::RefCounted<ErrorRecorder>,
public base::SupportsWeakPtr<ErrorRecorder> {
public:
ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
void RecordSqliteError(sql::Connection* connection,
int sqlite_error,
sql::Statement* statement) {
has_error_ = true;
sqlite_error_ = sqlite_error;
last_errno_ = connection->GetLastErrno();
message_ = connection->GetErrorMessage();
}
bool has_error() const { return has_error_; }
std::string FormatError() {
return base::StringPrintf("SQLite error: %d, Last Errno: %d: %s",
sqlite_error_,
last_errno_,
message_.c_str());
}
private:
friend class base::RefCounted<ErrorRecorder>;
~ErrorRecorder() {}
bool has_error_;
int sqlite_error_;
int last_errno_;
std::string message_;
DISALLOW_COPY_AND_ASSIGN(ErrorRecorder);
};
uint32_t flags_;
base::FilePath db_path_;
DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest);
};
} // namespace
std::unique_ptr<DiagnosticsTest> MakeSqliteCookiesDbTest() {
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::CRITICAL, DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST,
base::FilePath(chrome::kCookieFilename));
}
std::unique_ptr<DiagnosticsTest> MakeSqliteWebDatabaseTrackerDbTest() {
base::FilePath databases_dir(storage::kDatabaseDirectoryName);
base::FilePath tracker_db =
databases_dir.Append(storage::kTrackerDatabaseFileName);
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::NO_FLAGS_SET,
DIAGNOSTICS_SQLITE_INTEGRITY_DATABASE_TRACKER_TEST, tracker_db);
}
std::unique_ptr<DiagnosticsTest> MakeSqliteHistoryDbTest() {
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::CRITICAL, DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST,
base::FilePath(history::kHistoryFilename));
}
#if defined(OS_CHROMEOS)
std::unique_ptr<DiagnosticsTest> MakeSqliteNssCertDbTest() {
base::FilePath home_dir;
PathService::Get(base::DIR_HOME, &home_dir);
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::REMOVE_IF_CORRUPT,
DIAGNOSTICS_SQLITE_INTEGRITY_NSS_CERT_TEST,
home_dir.Append(chromeos::kNssCertDbPath));
}
std::unique_ptr<DiagnosticsTest> MakeSqliteNssKeyDbTest() {
base::FilePath home_dir;
PathService::Get(base::DIR_HOME, &home_dir);
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::REMOVE_IF_CORRUPT,
DIAGNOSTICS_SQLITE_INTEGRITY_NSS_KEY_TEST,
home_dir.Append(chromeos::kNssKeyDbPath));
}
#endif // defined(OS_CHROMEOS)
std::unique_ptr<DiagnosticsTest> MakeSqliteFaviconsDbTest() {
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::NO_FLAGS_SET,
DIAGNOSTICS_SQLITE_INTEGRITY_FAVICONS_TEST,
base::FilePath(history::kFaviconsFilename));
}
std::unique_ptr<DiagnosticsTest> MakeSqliteTopSitesDbTest() {
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::NO_FLAGS_SET,
DIAGNOSTICS_SQLITE_INTEGRITY_TOPSITES_TEST,
base::FilePath(history::kTopSitesFilename));
}
std::unique_ptr<DiagnosticsTest> MakeSqliteWebDataDbTest() {
return base::MakeUnique<SqliteIntegrityTest>(
SqliteIntegrityTest::CRITICAL, DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST,
base::FilePath(kWebDataFilename));
}
} // namespace diagnostics