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