Home | History | Annotate | Download | only in diagnostics
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/diagnostics/sqlite_diagnostics.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/logging.h"
      9 #include "base/memory/ref_counted.h"
     10 #include "base/memory/singleton.h"
     11 #include "base/memory/weak_ptr.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/path_service.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "chrome/common/chrome_constants.h"
     18 #include "chrome/common/chrome_paths.h"
     19 #include "chromeos/chromeos_constants.h"
     20 #include "components/webdata/common/webdata_constants.h"
     21 #include "content/public/common/content_constants.h"
     22 #include "sql/connection.h"
     23 #include "sql/statement.h"
     24 #include "third_party/sqlite/sqlite3.h"
     25 #include "webkit/browser/database/database_tracker.h"
     26 #include "webkit/common/appcache/appcache_interfaces.h"
     27 
     28 namespace diagnostics {
     29 
     30 namespace {
     31 
     32 // Generic diagnostic test class for checking SQLite database integrity.
     33 class SqliteIntegrityTest : public DiagnosticsTest {
     34  public:
     35   // These are bit flags, so each value should be a power of two.
     36   enum Flags {
     37     NO_FLAGS_SET = 0,
     38     CRITICAL = 0x01,
     39     REMOVE_IF_CORRUPT = 0x02,
     40   };
     41 
     42   SqliteIntegrityTest(uint32 flags,
     43                       DiagnosticsTestId id,
     44                       const base::FilePath& db_path)
     45       : DiagnosticsTest(id), flags_(flags), db_path_(db_path) {}
     46 
     47   virtual bool RecoveryImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
     48     int outcome_code = GetOutcomeCode();
     49     if (flags_ & REMOVE_IF_CORRUPT) {
     50       switch (outcome_code) {
     51         case DIAG_SQLITE_ERROR_HANDLER_CALLED:
     52         case DIAG_SQLITE_CANNOT_OPEN_DB:
     53         case DIAG_SQLITE_DB_LOCKED:
     54         case DIAG_SQLITE_PRAGMA_FAILED:
     55         case DIAG_SQLITE_DB_CORRUPTED:
     56           LOG(WARNING) << "Removing broken SQLite database: "
     57                        << db_path_.value();
     58           base::DeleteFile(db_path_, false);
     59           break;
     60         case DIAG_SQLITE_SUCCESS:
     61         case DIAG_SQLITE_FILE_NOT_FOUND_OK:
     62         case DIAG_SQLITE_FILE_NOT_FOUND:
     63           break;
     64         default:
     65           DCHECK(false) << "Invalid outcome code: " << outcome_code;
     66           break;
     67       }
     68     }
     69     return true;
     70   }
     71 
     72   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
     73     // If we're given an absolute path, use it. If not, then assume it's under
     74     // the profile directory.
     75     base::FilePath path;
     76     if (!db_path_.IsAbsolute())
     77       path = GetUserDefaultProfileDir().Append(db_path_);
     78     else
     79       path = db_path_;
     80 
     81     if (!base::PathExists(path)) {
     82       if (flags_ & CRITICAL) {
     83         RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND,
     84                       "File not found",
     85                       DiagnosticsModel::TEST_FAIL_CONTINUE);
     86       } else {
     87         RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK,
     88                       "File not found (but that is OK)",
     89                       DiagnosticsModel::TEST_OK);
     90       }
     91       return true;
     92     }
     93 
     94     int errors = 0;
     95     {  // Scope the statement and database so they close properly.
     96       sql::Connection database;
     97       database.set_exclusive_locking();
     98       scoped_refptr<ErrorRecorder> recorder(new ErrorRecorder);
     99 
    100       // Set the error callback so that we can get useful results in a debug
    101       // build for a corrupted database. Without setting the error callback,
    102       // sql::Connection will just DCHECK.
    103       database.set_error_callback(
    104           base::Bind(&SqliteIntegrityTest::ErrorRecorder::RecordSqliteError,
    105                      recorder->AsWeakPtr(),
    106                      &database));
    107       if (!database.Open(path)) {
    108         RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB,
    109                       "Cannot open DB. Possibly corrupted");
    110         return true;
    111       }
    112       if (recorder->has_error()) {
    113         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    114                       recorder->FormatError());
    115         return true;
    116       }
    117       sql::Statement statement(
    118           database.GetUniqueStatement("PRAGMA integrity_check;"));
    119       if (recorder->has_error()) {
    120         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    121                       recorder->FormatError());
    122         return true;
    123       }
    124       if (!statement.is_valid()) {
    125         int error = database.GetErrorCode();
    126         if (SQLITE_BUSY == error) {
    127           RecordFailure(DIAG_SQLITE_DB_LOCKED,
    128                         "Database locked by another process");
    129         } else {
    130           std::string str("Pragma failed. Error: ");
    131           str += base::IntToString(error);
    132           RecordFailure(DIAG_SQLITE_PRAGMA_FAILED, str);
    133         }
    134         return false;
    135       }
    136 
    137       while (statement.Step()) {
    138         std::string result(statement.ColumnString(0));
    139         if ("ok" != result)
    140           ++errors;
    141       }
    142       if (recorder->has_error()) {
    143         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    144                       recorder->FormatError());
    145         return true;
    146       }
    147     }
    148 
    149     // All done. Report to the user.
    150     if (errors != 0) {
    151       std::string str("Database corruption detected: ");
    152       str += base::IntToString(errors) + " errors";
    153       RecordFailure(DIAG_SQLITE_DB_CORRUPTED, str);
    154       return true;
    155     }
    156     RecordSuccess("No corruption detected");
    157     return true;
    158   }
    159 
    160  private:
    161   class ErrorRecorder : public base::RefCounted<ErrorRecorder>,
    162                         public base::SupportsWeakPtr<ErrorRecorder> {
    163    public:
    164     ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
    165 
    166     void RecordSqliteError(sql::Connection* connection,
    167                            int sqlite_error,
    168                            sql::Statement* statement) {
    169       has_error_ = true;
    170       sqlite_error_ = sqlite_error;
    171       last_errno_ = connection->GetLastErrno();
    172       message_ = connection->GetErrorMessage();
    173     }
    174 
    175     bool has_error() const { return has_error_; }
    176 
    177     std::string FormatError() {
    178       return base::StringPrintf("SQLite error: %d, Last Errno: %d: %s",
    179                                 sqlite_error_,
    180                                 last_errno_,
    181                                 message_.c_str());
    182     }
    183 
    184    private:
    185     friend class base::RefCounted<ErrorRecorder>;
    186     ~ErrorRecorder() {}
    187 
    188     bool has_error_;
    189     int sqlite_error_;
    190     int last_errno_;
    191     std::string message_;
    192 
    193     DISALLOW_COPY_AND_ASSIGN(ErrorRecorder);
    194   };
    195 
    196   uint32 flags_;
    197   base::FilePath db_path_;
    198   DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest);
    199 };
    200 
    201 }  // namespace
    202 
    203 DiagnosticsTest* MakeSqliteAppCacheDbTest() {
    204   base::FilePath appcache_dir(content::kAppCacheDirname);
    205   base::FilePath appcache_db =
    206       appcache_dir.Append(appcache::kAppCacheDatabaseName);
    207   return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
    208                                  DIAGNOSTICS_SQLITE_INTEGRITY_APP_CACHE_TEST,
    209                                  appcache_db);
    210 }
    211 
    212 DiagnosticsTest* MakeSqliteArchivedHistoryDbTest() {
    213   return new SqliteIntegrityTest(
    214       SqliteIntegrityTest::NO_FLAGS_SET,
    215       DIAGNOSTICS_SQLITE_INTEGRITY_ARCHIVED_HISTORY_TEST,
    216       base::FilePath(chrome::kArchivedHistoryFilename));
    217 }
    218 
    219 DiagnosticsTest* MakeSqliteCookiesDbTest() {
    220   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    221                                  DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST,
    222                                  base::FilePath(chrome::kCookieFilename));
    223 }
    224 
    225 DiagnosticsTest* MakeSqliteWebDatabaseTrackerDbTest() {
    226   base::FilePath databases_dir(webkit_database::kDatabaseDirectoryName);
    227   base::FilePath tracker_db =
    228       databases_dir.Append(webkit_database::kTrackerDatabaseFileName);
    229   return new SqliteIntegrityTest(
    230       SqliteIntegrityTest::NO_FLAGS_SET,
    231       DIAGNOSTICS_SQLITE_INTEGRITY_DATABASE_TRACKER_TEST,
    232       tracker_db);
    233 }
    234 
    235 DiagnosticsTest* MakeSqliteHistoryDbTest() {
    236   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    237                                  DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST,
    238                                  base::FilePath(chrome::kHistoryFilename));
    239 }
    240 
    241 #if defined(OS_CHROMEOS)
    242 DiagnosticsTest* MakeSqliteNssCertDbTest() {
    243   base::FilePath home_dir = base::GetHomeDir();
    244   return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
    245                                  DIAGNOSTICS_SQLITE_INTEGRITY_NSS_CERT_TEST,
    246                                  home_dir.Append(chromeos::kNssCertDbPath));
    247 }
    248 
    249 DiagnosticsTest* MakeSqliteNssKeyDbTest() {
    250   base::FilePath home_dir = base::GetHomeDir();
    251   return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
    252                                  DIAGNOSTICS_SQLITE_INTEGRITY_NSS_KEY_TEST,
    253                                  home_dir.Append(chromeos::kNssKeyDbPath));
    254 }
    255 #endif  // defined(OS_CHROMEOS)
    256 
    257 DiagnosticsTest* MakeSqliteThumbnailsDbTest() {
    258   return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
    259                                  DIAGNOSTICS_SQLITE_INTEGRITY_THUMBNAILS_TEST,
    260                                  base::FilePath(chrome::kThumbnailsFilename));
    261 }
    262 
    263 DiagnosticsTest* MakeSqliteWebDataDbTest() {
    264   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    265                                  DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST,
    266                                  base::FilePath(kWebDataFilename));
    267 }
    268 
    269 }  // namespace diagnostics
    270