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/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/history/core/browser/history_constants.h"
22 #include "components/webdata/common/webdata_constants.h"
23 #include "content/public/common/content_constants.h"
24 #include "sql/connection.h"
25 #include "sql/statement.h"
26 #include "storage/browser/database/database_tracker.h"
27 #include "third_party/sqlite/sqlite3.h"
29 namespace diagnostics
{
33 // Generic diagnostic test class for checking SQLite database integrity.
34 class SqliteIntegrityTest
: public DiagnosticsTest
{
36 // These are bit flags, so each value should be a power of two.
40 REMOVE_IF_CORRUPT
= 0x02,
43 SqliteIntegrityTest(uint32 flags
,
45 const base::FilePath
& db_path
)
46 : DiagnosticsTest(id
), flags_(flags
), db_path_(db_path
) {}
48 bool RecoveryImpl(DiagnosticsModel::Observer
* observer
) override
{
49 int outcome_code
= GetOutcomeCode();
50 if (flags_
& REMOVE_IF_CORRUPT
) {
51 switch (outcome_code
) {
52 case DIAG_SQLITE_ERROR_HANDLER_CALLED
:
53 case DIAG_SQLITE_CANNOT_OPEN_DB
:
54 case DIAG_SQLITE_DB_LOCKED
:
55 case DIAG_SQLITE_PRAGMA_FAILED
:
56 case DIAG_SQLITE_DB_CORRUPTED
:
57 LOG(WARNING
) << "Removing broken SQLite database: "
59 sql::Connection::Delete(db_path_
);
61 case DIAG_SQLITE_SUCCESS
:
62 case DIAG_SQLITE_FILE_NOT_FOUND_OK
:
63 case DIAG_SQLITE_FILE_NOT_FOUND
:
66 DCHECK(false) << "Invalid outcome code: " << outcome_code
;
73 bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) override
{
74 // If we're given an absolute path, use it. If not, then assume it's under
75 // the profile directory.
77 if (!db_path_
.IsAbsolute())
78 path
= GetUserDefaultProfileDir().Append(db_path_
);
82 if (!base::PathExists(path
)) {
83 if (flags_
& CRITICAL
) {
84 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND
,
86 DiagnosticsModel::TEST_FAIL_CONTINUE
);
88 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK
,
89 "File not found (but that is OK)",
90 DiagnosticsModel::TEST_OK
);
96 { // Scope the statement and database so they close properly.
97 sql::Connection database
;
98 database
.set_exclusive_locking();
99 scoped_refptr
<ErrorRecorder
> recorder(new ErrorRecorder
);
101 // Set the error callback so that we can get useful results in a debug
102 // build for a corrupted database. Without setting the error callback,
103 // sql::Connection will just DCHECK.
104 database
.set_error_callback(
105 base::Bind(&SqliteIntegrityTest::ErrorRecorder::RecordSqliteError
,
106 recorder
->AsWeakPtr(),
108 if (!database
.Open(path
)) {
109 RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB
,
110 "Cannot open DB. Possibly corrupted");
113 if (recorder
->has_error()) {
114 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED
,
115 recorder
->FormatError());
118 sql::Statement
statement(
119 database
.GetUniqueStatement("PRAGMA integrity_check;"));
120 if (recorder
->has_error()) {
121 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED
,
122 recorder
->FormatError());
125 if (!statement
.is_valid()) {
126 int error
= database
.GetErrorCode();
127 if (SQLITE_BUSY
== error
) {
128 RecordFailure(DIAG_SQLITE_DB_LOCKED
,
129 "Database locked by another process");
131 std::string
str("Pragma failed. Error: ");
132 str
+= base::IntToString(error
);
133 RecordFailure(DIAG_SQLITE_PRAGMA_FAILED
, str
);
138 while (statement
.Step()) {
139 std::string
result(statement
.ColumnString(0));
143 if (recorder
->has_error()) {
144 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED
,
145 recorder
->FormatError());
150 // All done. Report to the user.
152 std::string
str("Database corruption detected: ");
153 str
+= base::IntToString(errors
) + " errors";
154 RecordFailure(DIAG_SQLITE_DB_CORRUPTED
, str
);
157 RecordSuccess("No corruption detected");
162 class ErrorRecorder
: public base::RefCounted
<ErrorRecorder
>,
163 public base::SupportsWeakPtr
<ErrorRecorder
> {
165 ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
167 void RecordSqliteError(sql::Connection
* connection
,
169 sql::Statement
* statement
) {
171 sqlite_error_
= sqlite_error
;
172 last_errno_
= connection
->GetLastErrno();
173 message_
= connection
->GetErrorMessage();
176 bool has_error() const { return has_error_
; }
178 std::string
FormatError() {
179 return base::StringPrintf("SQLite error: %d, Last Errno: %d: %s",
186 friend class base::RefCounted
<ErrorRecorder
>;
192 std::string message_
;
194 DISALLOW_COPY_AND_ASSIGN(ErrorRecorder
);
198 base::FilePath db_path_
;
199 DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest
);
204 DiagnosticsTest
* MakeSqliteCookiesDbTest() {
205 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
206 DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST
,
207 base::FilePath(chrome::kCookieFilename
));
210 DiagnosticsTest
* MakeSqliteWebDatabaseTrackerDbTest() {
211 base::FilePath
databases_dir(storage::kDatabaseDirectoryName
);
212 base::FilePath tracker_db
=
213 databases_dir
.Append(storage::kTrackerDatabaseFileName
);
214 return new SqliteIntegrityTest(
215 SqliteIntegrityTest::NO_FLAGS_SET
,
216 DIAGNOSTICS_SQLITE_INTEGRITY_DATABASE_TRACKER_TEST
,
220 DiagnosticsTest
* MakeSqliteHistoryDbTest() {
221 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
222 DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST
,
223 base::FilePath(history::kHistoryFilename
));
226 #if defined(OS_CHROMEOS)
227 DiagnosticsTest
* MakeSqliteNssCertDbTest() {
228 base::FilePath home_dir
;
229 PathService::Get(base::DIR_HOME
, &home_dir
);
230 return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT
,
231 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_CERT_TEST
,
232 home_dir
.Append(chromeos::kNssCertDbPath
));
235 DiagnosticsTest
* MakeSqliteNssKeyDbTest() {
236 base::FilePath home_dir
;
237 PathService::Get(base::DIR_HOME
, &home_dir
);
238 return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT
,
239 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_KEY_TEST
,
240 home_dir
.Append(chromeos::kNssKeyDbPath
));
242 #endif // defined(OS_CHROMEOS)
244 DiagnosticsTest
* MakeSqliteThumbnailsDbTest() {
245 return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET
,
246 DIAGNOSTICS_SQLITE_INTEGRITY_THUMBNAILS_TEST
,
247 base::FilePath(history::kThumbnailsFilename
));
250 DiagnosticsTest
* MakeSqliteWebDataDbTest() {
251 return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL
,
252 DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST
,
253 base::FilePath(kWebDataFilename
));
256 } // namespace diagnostics