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.
5 #include "chrome/browser/diagnostics/sqlite_diagnostics.h"
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"
28 namespace diagnostics
{
32 // Generic diagnostic test class for checking SQLite database integrity.
33 class SqliteIntegrityTest
: public DiagnosticsTest
{
35 // These are bit flags, so each value should be a power of two.
39 REMOVE_IF_CORRUPT
= 0x02,
42 SqliteIntegrityTest(uint32 flags
,
44 const base::FilePath
& db_path
)
45 : DiagnosticsTest(id
), flags_(flags
), db_path_(db_path
) {}
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: "
58 base::DeleteFile(db_path_
, false);
60 case DIAG_SQLITE_SUCCESS
:
61 case DIAG_SQLITE_FILE_NOT_FOUND_OK
:
62 case DIAG_SQLITE_FILE_NOT_FOUND
:
65 DCHECK(false) << "Invalid outcome code: " << outcome_code
;
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.
76 if (!db_path_
.IsAbsolute())
77 path
= GetUserDefaultProfileDir().Append(db_path_
);
81 if (!base::PathExists(path
)) {
82 if (flags_
& CRITICAL
) {
83 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND
,
85 DiagnosticsModel::TEST_FAIL_CONTINUE
);
87 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK
,
88 "File not found (but that is OK)",
89 DiagnosticsModel::TEST_OK
);
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
);
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(),
107 if (!database
.Open(path
)) {
108 RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB
,
109 "Cannot open DB. Possibly corrupted");
112 if (recorder
->has_error()) {
113 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED
,
114 recorder
->FormatError());
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());
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");
130 std::string
str("Pragma failed. Error: ");
131 str
+= base::IntToString(error
);
132 RecordFailure(DIAG_SQLITE_PRAGMA_FAILED
, str
);
137 while (statement
.Step()) {
138 std::string
result(statement
.ColumnString(0));
142 if (recorder
->has_error()) {
143 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED
,
144 recorder
->FormatError());
149 // All done. Report to the user.
151 std::string
str("Database corruption detected: ");
152 str
+= base::IntToString(errors
) + " errors";
153 RecordFailure(DIAG_SQLITE_DB_CORRUPTED
, str
);
156 RecordSuccess("No corruption detected");
161 class ErrorRecorder
: public base::RefCounted
<ErrorRecorder
>,
162 public base::SupportsWeakPtr
<ErrorRecorder
> {
164 ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
166 void RecordSqliteError(sql::Connection
* connection
,
168 sql::Statement
* statement
) {
170 sqlite_error_
= sqlite_error
;
171 last_errno_
= connection
->GetLastErrno();
172 message_
= connection
->GetErrorMessage();
175 bool has_error() const { return has_error_
; }
177 std::string
FormatError() {
178 return base::StringPrintf("SQLite error: %d, Last Errno: %d: %s",
185 friend class base::RefCounted
<ErrorRecorder
>;
191 std::string message_
;
193 DISALLOW_COPY_AND_ASSIGN(ErrorRecorder
);
197 base::FilePath db_path_
;
198 DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest
);
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
,
212 DiagnosticsTest
* MakeSqliteArchivedHistoryDbTest() {
213 return new SqliteIntegrityTest(
214 SqliteIntegrityTest::NO_FLAGS_SET
,
215 DIAGNOSTICS_SQLITE_INTEGRITY_ARCHIVED_HISTORY_TEST
,
216 base::FilePath(chrome::kArchivedHistoryFilename
));
219 DiagnosticsTest
* MakeSqliteCookiesDbTest() {
220 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
221 DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST
,
222 base::FilePath(chrome::kCookieFilename
));
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
,
235 DiagnosticsTest
* MakeSqliteHistoryDbTest() {
236 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
237 DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST
,
238 base::FilePath(chrome::kHistoryFilename
));
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
));
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
));
255 #endif // defined(OS_CHROMEOS)
257 DiagnosticsTest
* MakeSqliteThumbnailsDbTest() {
258 return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET
,
259 DIAGNOSTICS_SQLITE_INTEGRITY_THUMBNAILS_TEST
,
260 base::FilePath(chrome::kThumbnailsFilename
));
263 DiagnosticsTest
* MakeSqliteWebDataDbTest() {
264 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
265 DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST
,
266 base::FilePath(kWebDataFilename
));
269 } // namespace diagnostics