Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / diagnostics / sqlite_diagnostics.cc
blobb342bba12048aec87fe71812cfabf4b9a01960a6
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 {
31 namespace {
33 // Generic diagnostic test class for checking SQLite database integrity.
34 class SqliteIntegrityTest : public DiagnosticsTest {
35 public:
36 // These are bit flags, so each value should be a power of two.
37 enum Flags {
38 NO_FLAGS_SET = 0,
39 CRITICAL = 0x01,
40 REMOVE_IF_CORRUPT = 0x02,
43 SqliteIntegrityTest(uint32 flags,
44 DiagnosticsTestId id,
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: "
58 << db_path_.value();
59 sql::Connection::Delete(db_path_);
60 break;
61 case DIAG_SQLITE_SUCCESS:
62 case DIAG_SQLITE_FILE_NOT_FOUND_OK:
63 case DIAG_SQLITE_FILE_NOT_FOUND:
64 break;
65 default:
66 DCHECK(false) << "Invalid outcome code: " << outcome_code;
67 break;
70 return true;
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.
76 base::FilePath path;
77 if (!db_path_.IsAbsolute())
78 path = GetUserDefaultProfileDir().Append(db_path_);
79 else
80 path = db_path_;
82 if (!base::PathExists(path)) {
83 if (flags_ & CRITICAL) {
84 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND,
85 "File not found",
86 DiagnosticsModel::TEST_FAIL_CONTINUE);
87 } else {
88 RecordOutcome(DIAG_SQLITE_FILE_NOT_FOUND_OK,
89 "File not found (but that is OK)",
90 DiagnosticsModel::TEST_OK);
92 return true;
95 int errors = 0;
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(),
107 &database));
108 if (!database.Open(path)) {
109 RecordFailure(DIAG_SQLITE_CANNOT_OPEN_DB,
110 "Cannot open DB. Possibly corrupted");
111 return true;
113 if (recorder->has_error()) {
114 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
115 recorder->FormatError());
116 return true;
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());
123 return true;
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");
130 } else {
131 std::string str("Pragma failed. Error: ");
132 str += base::IntToString(error);
133 RecordFailure(DIAG_SQLITE_PRAGMA_FAILED, str);
135 return false;
138 while (statement.Step()) {
139 std::string result(statement.ColumnString(0));
140 if ("ok" != result)
141 ++errors;
143 if (recorder->has_error()) {
144 RecordFailure(DIAG_SQLITE_ERROR_HANDLER_CALLED,
145 recorder->FormatError());
146 return true;
150 // All done. Report to the user.
151 if (errors != 0) {
152 std::string str("Database corruption detected: ");
153 str += base::IntToString(errors) + " errors";
154 RecordFailure(DIAG_SQLITE_DB_CORRUPTED, str);
155 return true;
157 RecordSuccess("No corruption detected");
158 return true;
161 private:
162 class ErrorRecorder : public base::RefCounted<ErrorRecorder>,
163 public base::SupportsWeakPtr<ErrorRecorder> {
164 public:
165 ErrorRecorder() : has_error_(false), sqlite_error_(0), last_errno_(0) {}
167 void RecordSqliteError(sql::Connection* connection,
168 int sqlite_error,
169 sql::Statement* statement) {
170 has_error_ = true;
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",
180 sqlite_error_,
181 last_errno_,
182 message_.c_str());
185 private:
186 friend class base::RefCounted<ErrorRecorder>;
187 ~ErrorRecorder() {}
189 bool has_error_;
190 int sqlite_error_;
191 int last_errno_;
192 std::string message_;
194 DISALLOW_COPY_AND_ASSIGN(ErrorRecorder);
197 uint32 flags_;
198 base::FilePath db_path_;
199 DISALLOW_COPY_AND_ASSIGN(SqliteIntegrityTest);
202 } // namespace
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,
217 tracker_db);
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