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/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/webdata/common/webdata_constants.h"
22#include "content/public/common/content_constants.h"
23#include "sql/connection.h"
24#include "sql/statement.h"
25#include "storage/browser/database/database_tracker.h"
26#include "third_party/sqlite/sqlite3.h"
27
28namespace diagnostics {
29
30namespace {
31
32// Generic diagnostic test class for checking SQLite database integrity.
33class 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
203DiagnosticsTest* MakeSqliteCookiesDbTest() {
204  return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
205                                 DIAGNOSTICS_SQLITE_INTEGRITY_COOKIE_TEST,
206                                 base::FilePath(chrome::kCookieFilename));
207}
208
209DiagnosticsTest* MakeSqliteWebDatabaseTrackerDbTest() {
210  base::FilePath databases_dir(storage::kDatabaseDirectoryName);
211  base::FilePath tracker_db =
212      databases_dir.Append(storage::kTrackerDatabaseFileName);
213  return new SqliteIntegrityTest(
214      SqliteIntegrityTest::NO_FLAGS_SET,
215      DIAGNOSTICS_SQLITE_INTEGRITY_DATABASE_TRACKER_TEST,
216      tracker_db);
217}
218
219DiagnosticsTest* MakeSqliteHistoryDbTest() {
220  return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
221                                 DIAGNOSTICS_SQLITE_INTEGRITY_HISTORY_TEST,
222                                 base::FilePath(chrome::kHistoryFilename));
223}
224
225#if defined(OS_CHROMEOS)
226DiagnosticsTest* MakeSqliteNssCertDbTest() {
227  base::FilePath home_dir;
228  PathService::Get(base::DIR_HOME, &home_dir);
229  return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
230                                 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_CERT_TEST,
231                                 home_dir.Append(chromeos::kNssCertDbPath));
232}
233
234DiagnosticsTest* MakeSqliteNssKeyDbTest() {
235  base::FilePath home_dir;
236  PathService::Get(base::DIR_HOME, &home_dir);
237  return new SqliteIntegrityTest(SqliteIntegrityTest::REMOVE_IF_CORRUPT,
238                                 DIAGNOSTICS_SQLITE_INTEGRITY_NSS_KEY_TEST,
239                                 home_dir.Append(chromeos::kNssKeyDbPath));
240}
241#endif  // defined(OS_CHROMEOS)
242
243DiagnosticsTest* MakeSqliteThumbnailsDbTest() {
244  return new SqliteIntegrityTest(SqliteIntegrityTest::NO_FLAGS_SET,
245                                 DIAGNOSTICS_SQLITE_INTEGRITY_THUMBNAILS_TEST,
246                                 base::FilePath(chrome::kThumbnailsFilename));
247}
248
249DiagnosticsTest* MakeSqliteWebDataDbTest() {
250  return new SqliteIntegrityTest(SqliteIntegrityTest::CRITICAL,
251                                 DIAGNOSTICS_SQLITE_INTEGRITY_WEB_DATA_TEST,
252                                 base::FilePath(kWebDataFilename));
253}
254
255}  // namespace diagnostics
256