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