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