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 const char kSQLiteIntegrityAppCacheTest[] = "SQLiteIntegrityAppCache";
     31 const char kSQLiteIntegrityArchivedHistoryTest[] =
     32     "SQLiteIntegrityArchivedHistory";
     33 const char kSQLiteIntegrityCookieTest[] = "SQLiteIntegrityCookie";
     34 const char kSQLiteIntegrityDatabaseTrackerTest[] =
     35     "SQLiteIntegrityDatabaseTracker";
     36 const char kSQLiteIntegrityHistoryTest[] = "SQLiteIntegrityHistory";
     37 const char kSQLiteIntegrityThumbnailsTest[] = "SQLiteIntegrityThumbnails";
     38 const char kSQLiteIntegrityWebTest[] = "SQLiteIntegrityWeb";
     39 
     40 #if defined(OS_CHROMEOS)
     41 const char kSQLiteIntegrityNSSCertTest[] = "SQLiteIntegrityNSSCert";
     42 const char kSQLiteIntegrityNSSKeyTest[] = "SQLiteIntegrityNSSKey";
     43 #endif
     44 
     45 namespace {
     46 
     47 // Generic diagnostic test class for checking SQLite database integrity.
     48 class SqliteIntegrityTest : public DiagnosticsTest {
     49  public:
     50   // These are bit flags, so each value should be a power of two.
     51   enum Flags {
     52     NO_FLAGS_SET = 0,
     53     CRITICAL = 0x01,
     54     REMOVE_IF_CORRUPT = 0x02,
     55   };
     56 
     57   SqliteIntegrityTest(uint32 flags,
     58                       const std::string& id,
     59                       const std::string& title,
     60                       const base::FilePath& db_path)
     61       : DiagnosticsTest(id, title),
     62         flags_(flags),
     63         db_path_(db_path) {}
     64 
     65   virtual bool RecoveryImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
     66     int outcome_code = GetOutcomeCode();
     67     if (flags_ & REMOVE_IF_CORRUPT) {
     68       switch (outcome_code) {
     69         case DIAG_SQLITE_ERROR_HANDLER_CALLED:
     70         case DIAG_SQLITE_CANNOT_OPEN_DB:
     71         case DIAG_SQLITE_DB_LOCKED:
     72         case DIAG_SQLITE_PRAGMA_FAILED:
     73         case DIAG_SQLITE_DB_CORRUPTED:
     74           LOG(WARNING) << "Removing broken SQLite database: "
     75                        << db_path_.value();
     76           base::DeleteFile(db_path_, false);
     77           break;
     78         case DIAG_SQLITE_SUCCESS:
     79         case DIAG_SQLITE_FILE_NOT_FOUND_OK:
     80         case DIAG_SQLITE_FILE_NOT_FOUND:
     81           break;
     82         default:
     83           DCHECK(false) << "Invalid outcome code: " << outcome_code;
     84           break;
     85       }
     86     }
     87     return true;
     88   }
     89 
     90   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
     91     // If we're given an absolute path, use it. If not, then assume it's under
     92     // the profile directory.
     93     base::FilePath path;
     94     if (!db_path_.IsAbsolute())
     95       path = GetUserDefaultProfileDir().Append(db_path_);
     96     else
     97       path = db_path_;
     98 
     99     if (!base::PathExists(path)) {
    100       if (flags_ & CRITICAL) {
    101         RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND,
    102                       "File not found",
    103                       DiagnosticsModel::TEST_FAIL_CONTINUE);
    104       } else {
    105         RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK,
    106                       "File not found (but that is OK)",
    107                       DiagnosticsModel::TEST_OK);
    108       }
    109       return true;
    110     }
    111 
    112     int errors = 0;
    113     {  // Scope the statement and database so they close properly.
    114       sql::Connection database;
    115       database.set_exclusive_locking();
    116       scoped_refptr<ErrorRecorder> recorder(new ErrorRecorder);
    117 
    118       // Set the error callback so that we can get useful results in a debug
    119       // build for a corrupted database. Without setting the error callback,
    120       // sql::Connection will just DCHECK.
    121       database.set_error_callback(
    122           base::Bind(&SqliteIntegrityTest::ErrorRecorder::RecordSqliteError,
    123                      recorder->AsWeakPtr(),
    124                      &database));
    125       if (!database.Open(path)) {
    126         RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB,
    127                       "Cannot open DB. Possibly corrupted");
    128         return true;
    129       }
    130       if (recorder->has_error()) {
    131         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    132                       recorder->FormatError());
    133         return true;
    134       }
    135       sql::Statement statement(
    136           database.GetUniqueStatement("PRAGMA integrity_check;"));
    137       if (recorder->has_error()) {
    138         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    139                       recorder->FormatError());
    140         return true;
    141       }
    142       if (!statement.is_valid()) {
    143         int error = database.GetErrorCode();
    144         if (SQLITE_BUSY == error) {
    145           RecordFailure(DIAG_SQLITE_DB_LOCKED,
    146                         "Database locked by another process");
    147         } else {
    148           std::string str("Pragma failed. Error: ");
    149           str += base::IntToString(error);
    150           RecordFailure(DIAG_SQLITE_PRAGMA_FAILED, str);
    151         }
    152         return false;
    153       }
    154 
    155       while (statement.Step()) {
    156         std::string result(statement.ColumnString(0));
    157         if ("ok" != result)
    158           ++errors;
    159       }
    160       if (recorder->has_error()) {
    161         RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
    162                       recorder->FormatError());
    163         return true;
    164       }
    165     }
    166 
    167     // All done. Report to the user.
    168     if (errors != 0) {
    169       std::string str("Database corruption detected: ");
    170       str += base::IntToString(errors) + " errors";
    171       RecordFailure(DIAG_SQLITE_DB_CORRUPTED, str);
    172       return true;
    173     }
    174     RecordSuccess("No corruption detected");
    175     return true;
    176   }
    177 
    178  private:
    179   class ErrorRecorder : public base::RefCounted<ErrorRecorder>,
    180                         public base::SupportsWeakPtr<ErrorRecorder> {
    181    public:
    182     ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
    183 
    184     void RecordSqliteError(sql::Connection* connection,
    185                            int sqlite_error,
    186                            sql::Statement* statement) {
    187       has_error_ = true;
    188       sqlite_error_ = sqlite_error;
    189       last_errno_ = connection->GetLastErrno();
    190       message_ = connection->GetErrorMessage();
    191     }
    192 
    193     bool has_error() const { return has_error_; }
    194 
    195     std::string FormatError() {
    196       return base::StringPrintf("SQLite error: %d, Last Errno: %d: %s",
    197                                 sqlite_error_,
    198                                 last_errno_,
    199                                 message_.c_str());
    200     }
    201 
    202    private:
    203     friend class base::RefCounted<ErrorRecorder>;
    204     ~ErrorRecorder() {}
    205 
    206     bool has_error_;
    207     int sqlite_error_;
    208     int last_errno_;
    209     std::string message_;
    210 
    211     DISALLOW_COPY_AND_ASSIGN(ErrorRecorder);
    212   };
    213 
    214   uint32 flags_;
    215   base::FilePath db_path_;
    216   DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest);
    217 };
    218 
    219 }  // namespace
    220 
    221 DiagnosticsTest* MakeSqliteWebDbTest() {
    222   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    223                                  kSQLiteIntegrityWebTest,
    224                                  "Web Database",
    225                                  base::FilePath(kWebDataFilename));
    226 }
    227 
    228 DiagnosticsTest* MakeSqliteCookiesDbTest() {
    229   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    230                                  kSQLiteIntegrityCookieTest,
    231                                  "Cookies Database",
    232                                  base::FilePath(chrome::kCookieFilename));
    233 }
    234 
    235 DiagnosticsTest* MakeSqliteHistoryDbTest() {
    236   return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
    237                                  kSQLiteIntegrityHistoryTest,
    238                                  "History Database",
    239                                  base::FilePath(chrome::kHistoryFilename));
    240 }
    241 
    242 DiagnosticsTest* MakeSqliteArchivedHistoryDbTest() {
    243   return new SqliteIntegrityTest(
    244       SqliteIntegrityTest::NO_FLAGS_SET,
    245       kSQLiteIntegrityArchivedHistoryTest,
    246       "Archived History Database",
    247       base::FilePath(chrome::kArchivedHistoryFilename));
    248 }
    249 
    250 DiagnosticsTest* MakeSqliteThumbnailsDbTest() {
    251   return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
    252                                  kSQLiteIntegrityThumbnailsTest,
    253                                  "Thumbnails Database",
    254                                  base::FilePath(chrome::kThumbnailsFilename));
    255 }
    256 
    257 DiagnosticsTest* MakeSqliteAppCacheDbTest() {
    258   base::FilePath appcache_dir(content::kAppCacheDirname);
    259   base::FilePath appcache_db =
    260       appcache_dir.Append(appcache::kAppCacheDatabaseName);
    261   return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
    262                                  kSQLiteIntegrityAppCacheTest,
    263                                  "Application Cache Database",
    264                                  appcache_db);
    265 }
    266 
    267 DiagnosticsTest* MakeSqliteWebDatabaseTrackerDbTest() {
    268   base::FilePath databases_dir(webkit_database::kDatabaseDirectoryName);
    269   base::FilePath tracker_db =
    270       databases_dir.Append(webkit_database::kTrackerDatabaseFileName);
    271   return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
    272                                  kSQLiteIntegrityDatabaseTrackerTest,
    273                                  "Database Tracker Database",
    274                                  tracker_db);
    275 }
    276 
    277 #if defined(OS_CHROMEOS)
    278 DiagnosticsTest* MakeSqliteNssCertDbTest() {
    279   base::FilePath home_dir = file_util::GetHomeDir();
    280   return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
    281                                  kSQLiteIntegrityNSSCertTest,
    282                                  "NSS Certificate Database",
    283                                  home_dir.Append(chromeos::kNssCertDbPath));
    284 }
    285 
    286 DiagnosticsTest* MakeSqliteNssKeyDbTest() {
    287   base::FilePath home_dir = file_util::GetHomeDir();
    288   return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
    289                                  kSQLiteIntegrityNSSKeyTest,
    290                                  "NSS Key Database",
    291                                  home_dir.Append(chromeos::kNssKeyDbPath));
    292 }
    293 #endif  // defined(OS_CHROMEOS)
    294 }       // namespace diagnostics
    295