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/base_paths.h" 8#include "base/files/file_util.h" 9#include "base/logging.h" 10#include "base/memory/ref_counted.h" 11#include "base/memory/singleton.h" 12#include "base/memory/weak_ptr.h" 13#include "base/metrics/histogram.h" 14#include "base/path_service.h" 15#include "base/strings/string_number_conversions.h" 16#include "base/strings/stringprintf.h" 17#include "base/strings/utf_string_conversions.h" 18#include "chrome/common/chrome_constants.h" 19#include "chrome/common/chrome_paths.h" 20#include "chromeos/chromeos_constants.h" 21#include "components/webdata/common/webdata_constants.h" 22#include "content/public/common/content_constants.h" 23#include "sql/connection.h" 24#include "sql/statement.h" 25#include "storage/browser/database/database_tracker.h" 26#include "third_party/sqlite/sqlite3.h" 27 28namespace diagnostics { 29 30namespace { 31 32// Generic diagnostic test class for checking SQLite database integrity. 33class 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 203DiagnosticsTest* MakeSqliteCookiesDbTest() { 204 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL, 205 DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST, 206 base::FilePath(chrome::kCookieFilename)); 207} 208 209DiagnosticsTest* MakeSqliteWebDatabaseTrackerDbTest() { 210 base::FilePath databases_dir(storage::kDatabaseDirectoryName); 211 base::FilePath tracker_db = 212 databases_dir.Append(storage::kTrackerDatabaseFileName); 213 return new SqliteIntegrityTest( 214 SqliteIntegrityTest::NO_FLAGS_SET, 215 DIAGNOSTICS_SQLITE_INTEGRITY_DATABASE_TRACKER_TEST, 216 tracker_db); 217} 218 219DiagnosticsTest* MakeSqliteHistoryDbTest() { 220 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL, 221 DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST, 222 base::FilePath(chrome::kHistoryFilename)); 223} 224 225#if defined(OS_CHROMEOS) 226DiagnosticsTest* MakeSqliteNssCertDbTest() { 227 base::FilePath home_dir; 228 PathService::Get(base::DIR_HOME, &home_dir); 229 return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT, 230 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_CERT_TEST, 231 home_dir.Append(chromeos::kNssCertDbPath)); 232} 233 234DiagnosticsTest* MakeSqliteNssKeyDbTest() { 235 base::FilePath home_dir; 236 PathService::Get(base::DIR_HOME, &home_dir); 237 return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT, 238 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_KEY_TEST, 239 home_dir.Append(chromeos::kNssKeyDbPath)); 240} 241#endif // defined(OS_CHROMEOS) 242 243DiagnosticsTest* MakeSqliteThumbnailsDbTest() { 244 return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET, 245 DIAGNOSTICS_SQLITE_INTEGRITY_THUMBNAILS_TEST, 246 base::FilePath(chrome::kThumbnailsFilename)); 247} 248 249DiagnosticsTest* MakeSqliteWebDataDbTest() { 250 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL, 251 DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST, 252 base::FilePath(kWebDataFilename)); 253} 254 255} // namespace diagnostics 256