recovery.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 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 "sql/recovery.h"
6
7#include "base/files/file_path.h"
8#include "base/format_macros.h"
9#include "base/logging.h"
10#include "base/metrics/histogram.h"
11#include "base/metrics/sparse_histogram.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "sql/connection.h"
15#include "sql/statement.h"
16#include "third_party/sqlite/sqlite3.h"
17
18namespace sql {
19
20namespace {
21
22enum RecoveryEventType {
23  // Init() completed successfully.
24  RECOVERY_SUCCESS_INIT = 0,
25
26  // Failed to open temporary database to recover into.
27  RECOVERY_FAILED_OPEN_TEMPORARY,
28
29  // Failed to initialize recover vtable system.
30  RECOVERY_FAILED_VIRTUAL_TABLE_INIT,
31
32  // System SQLite doesn't support vtable.
33  RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE,
34
35  // Failed attempting to enable writable_schema.
36  RECOVERY_FAILED_WRITABLE_SCHEMA,
37
38  // Failed to attach the corrupt database to the temporary database.
39  RECOVERY_FAILED_ATTACH,
40
41  // Backup() successfully completed.
42  RECOVERY_SUCCESS_BACKUP,
43
44  // Failed sqlite3_backup_init().  Error code in Sqlite.RecoveryHandle.
45  RECOVERY_FAILED_BACKUP_INIT,
46
47  // Failed sqlite3_backup_step().  Error code in Sqlite.RecoveryStep.
48  RECOVERY_FAILED_BACKUP_STEP,
49
50  // AutoRecoverTable() successfully completed.
51  RECOVERY_SUCCESS_AUTORECOVER,
52
53  // The target table contained a type which the code is not equipped
54  // to handle.  This should only happen if things are fubar.
55  RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE,
56
57  // The target table does not exist.
58  RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE,
59
60  // The recovery virtual table creation failed.
61  RECOVERY_FAILED_AUTORECOVER_CREATE,
62
63  // Copying data from the recovery table to the target table failed.
64  RECOVERY_FAILED_AUTORECOVER_INSERT,
65
66  // Dropping the recovery virtual table failed.
67  RECOVERY_FAILED_AUTORECOVER_DROP,
68
69  // SetupMeta() successfully completed.
70  RECOVERY_SUCCESS_SETUP_META,
71
72  // Failure creating recovery meta table.
73  RECOVERY_FAILED_META_CREATE,
74
75  // GetMetaVersionNumber() successfully completed.
76  RECOVERY_SUCCESS_META_VERSION,
77
78  // Failed in querying recovery meta table.
79  RECOVERY_FAILED_META_QUERY,
80
81  // No version key in recovery meta table.
82  RECOVERY_FAILED_META_NO_VERSION,
83
84  // Always keep this at the end.
85  RECOVERY_EVENT_MAX,
86};
87
88void RecordRecoveryEvent(RecoveryEventType recovery_event) {
89  UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents",
90                            recovery_event, RECOVERY_EVENT_MAX);
91}
92
93}  // namespace
94
95// static
96bool Recovery::FullRecoverySupported() {
97  // TODO(shess): See comment in Init().
98#if defined(USE_SYSTEM_SQLITE)
99  return false;
100#else
101  return true;
102#endif
103}
104
105// static
106scoped_ptr<Recovery> Recovery::Begin(
107    Connection* connection,
108    const base::FilePath& db_path) {
109  scoped_ptr<Recovery> r(new Recovery(connection));
110  if (!r->Init(db_path)) {
111    // TODO(shess): Should Init() failure result in Raze()?
112    r->Shutdown(POISON);
113    return scoped_ptr<Recovery>();
114  }
115
116  return r.Pass();
117}
118
119// static
120bool Recovery::Recovered(scoped_ptr<Recovery> r) {
121  return r->Backup();
122}
123
124// static
125void Recovery::Unrecoverable(scoped_ptr<Recovery> r) {
126  CHECK(r->db_);
127  // ~Recovery() will RAZE_AND_POISON.
128}
129
130// static
131void Recovery::Rollback(scoped_ptr<Recovery> r) {
132  // TODO(shess): HISTOGRAM to track?  Or just have people crash out?
133  // Crash and dump?
134  r->Shutdown(POISON);
135}
136
137Recovery::Recovery(Connection* connection)
138    : db_(connection),
139      recover_db_() {
140  // Result should keep the page size specified earlier.
141  if (db_->page_size_)
142    recover_db_.set_page_size(db_->page_size_);
143
144  // TODO(shess): This may not handle cases where the default page
145  // size is used, but the default has changed.  I do not think this
146  // has ever happened.  This could be handled by using "PRAGMA
147  // page_size", at the cost of potential additional failure cases.
148}
149
150Recovery::~Recovery() {
151  Shutdown(RAZE_AND_POISON);
152}
153
154bool Recovery::Init(const base::FilePath& db_path) {
155  // Prevent the possibility of re-entering this code due to errors
156  // which happen while executing this code.
157  DCHECK(!db_->has_error_callback());
158
159  // Break any outstanding transactions on the original database to
160  // prevent deadlocks reading through the attached version.
161  // TODO(shess): A client may legitimately wish to recover from
162  // within the transaction context, because it would potentially
163  // preserve any in-flight changes.  Unfortunately, any attach-based
164  // system could not handle that.  A system which manually queried
165  // one database and stored to the other possibly could, but would be
166  // more complicated.
167  db_->RollbackAllTransactions();
168
169  // Disable exclusive locking mode so that the attached database can
170  // access things.  The locking_mode change is not active until the
171  // next database access, so immediately force an access.  Enabling
172  // writable_schema allows processing through certain kinds of
173  // corruption.
174  // TODO(shess): It would be better to just close the handle, but it
175  // is necessary for the final backup which rewrites things.  It
176  // might be reasonable to close then re-open the handle.
177  ignore_result(db_->Execute("PRAGMA writable_schema=1"));
178  ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL"));
179  ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master"));
180
181  // TODO(shess): If this is a common failure case, it might be
182  // possible to fall back to a memory database.  But it probably
183  // implies that the SQLite tmpdir logic is busted, which could cause
184  // a variety of other random issues in our code.
185  if (!recover_db_.OpenTemporary()) {
186    RecordRecoveryEvent(RECOVERY_FAILED_OPEN_TEMPORARY);
187    return false;
188  }
189
190  // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE.  The
191  // virtual table implementation relies on SQLite internals for some
192  // types and functions, which could be copied inline to make it
193  // standalone.  Or an alternate implementation could try to read
194  // through errors entirely at the SQLite level.
195  //
196  // For now, defer to the caller.  The setup will succeed, but the
197  // later CREATE VIRTUAL TABLE call will fail, at which point the
198  // caller can fire Unrecoverable().
199#if !defined(USE_SYSTEM_SQLITE)
200  int rc = recoverVtableInit(recover_db_.db_);
201  if (rc != SQLITE_OK) {
202    RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT);
203    LOG(ERROR) << "Failed to initialize recover module: "
204               << recover_db_.GetErrorMessage();
205    return false;
206  }
207#else
208  // If this is infrequent enough, just wire it to Raze().
209  RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE);
210#endif
211
212  // Turn on |SQLITE_RecoveryMode| for the handle, which allows
213  // reading certain broken databases.
214  if (!recover_db_.Execute("PRAGMA writable_schema=1")) {
215    RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA);
216    return false;
217  }
218
219  if (!recover_db_.AttachDatabase(db_path, "corrupt")) {
220    RecordRecoveryEvent(RECOVERY_FAILED_ATTACH);
221    return false;
222  }
223
224  RecordRecoveryEvent(RECOVERY_SUCCESS_INIT);
225  return true;
226}
227
228bool Recovery::Backup() {
229  CHECK(db_);
230  CHECK(recover_db_.is_open());
231
232  // TODO(shess): Some of the failure cases here may need further
233  // exploration.  Just as elsewhere, persistent problems probably
234  // need to be razed, while anything which might succeed on a future
235  // run probably should be allowed to try.  But since Raze() uses the
236  // same approach, even that wouldn't work when this code fails.
237  //
238  // The documentation for the backup system indicate a relatively
239  // small number of errors are expected:
240  // SQLITE_BUSY - cannot lock the destination database.  This should
241  //               only happen if someone has another handle to the
242  //               database, Chromium generally doesn't do that.
243  // SQLITE_LOCKED - someone locked the source database.  Should be
244  //                 impossible (perhaps anti-virus could?).
245  // SQLITE_READONLY - destination is read-only.
246  // SQLITE_IOERR - since source database is temporary, probably
247  //                indicates that the destination contains blocks
248  //                throwing errors, or gross filesystem errors.
249  // SQLITE_NOMEM - out of memory, should be transient.
250  //
251  // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered
252  // transient, with SQLITE_LOCKED being unclear.
253  //
254  // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a
255  // strong chance that Raze() would not resolve them.  If Delete()
256  // deletes the database file, the code could then re-open the file
257  // and attempt the backup again.
258  //
259  // For now, this code attempts a best effort and records histograms
260  // to inform future development.
261
262  // Backup the original db from the recovered db.
263  const char* kMain = "main";
264  sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain,
265                                               recover_db_.db_, kMain);
266  if (!backup) {
267    RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT);
268
269    // Error code is in the destination database handle.
270    int err = sqlite3_extended_errcode(db_->db_);
271    UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err);
272    LOG(ERROR) << "sqlite3_backup_init() failed: "
273               << sqlite3_errmsg(db_->db_);
274
275    return false;
276  }
277
278  // -1 backs up the entire database.
279  int rc = sqlite3_backup_step(backup, -1);
280  int pages = sqlite3_backup_pagecount(backup);
281  // TODO(shess): sqlite3_backup_finish() appears to allow returning a
282  // different value from sqlite3_backup_step().  Circle back and
283  // figure out if that can usefully inform the decision of whether to
284  // retry or not.
285  sqlite3_backup_finish(backup);
286  DCHECK_GT(pages, 0);
287
288  if (rc != SQLITE_DONE) {
289    RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP);
290    UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc);
291    LOG(ERROR) << "sqlite3_backup_step() failed: "
292               << sqlite3_errmsg(db_->db_);
293  }
294
295  // The destination database was locked.  Give up, but leave the data
296  // in place.  Maybe it won't be locked next time.
297  if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
298    Shutdown(POISON);
299    return false;
300  }
301
302  // Running out of memory should be transient, retry later.
303  if (rc == SQLITE_NOMEM) {
304    Shutdown(POISON);
305    return false;
306  }
307
308  // TODO(shess): For now, leave the original database alone, pending
309  // results from Sqlite.RecoveryStep.  Some errors should probably
310  // route to RAZE_AND_POISON.
311  if (rc != SQLITE_DONE) {
312    Shutdown(POISON);
313    return false;
314  }
315
316  // Clean up the recovery db, and terminate the main database
317  // connection.
318  RecordRecoveryEvent(RECOVERY_SUCCESS_BACKUP);
319  Shutdown(POISON);
320  return true;
321}
322
323void Recovery::Shutdown(Recovery::Disposition raze) {
324  if (!db_)
325    return;
326
327  recover_db_.Close();
328  if (raze == RAZE_AND_POISON) {
329    db_->RazeAndClose();
330  } else if (raze == POISON) {
331    db_->Poison();
332  }
333  db_ = NULL;
334}
335
336bool Recovery::AutoRecoverTable(const char* table_name,
337                                size_t extend_columns,
338                                size_t* rows_recovered) {
339  // Query the info for the recovered table in database [main].
340  std::string query(
341      base::StringPrintf("PRAGMA main.table_info(%s)", table_name));
342  Statement s(db()->GetUniqueStatement(query.c_str()));
343
344  // The columns of the recover virtual table.
345  std::vector<std::string> create_column_decls;
346
347  // The columns to select from the recover virtual table when copying
348  // to the recovered table.
349  std::vector<std::string> insert_columns;
350
351  // If PRIMARY KEY is a single INTEGER column, then it is an alias
352  // for ROWID.  The primary key can be compound, so this can only be
353  // determined after processing all column data and tracking what is
354  // seen.  |pk_column_count| counts the columns in the primary key.
355  // |rowid_decl| stores the ROWID version of the last INTEGER column
356  // seen, which is at |rowid_ofs| in |create_column_decls|.
357  size_t pk_column_count = 0;
358  size_t rowid_ofs;  // Only valid if rowid_decl is set.
359  std::string rowid_decl;  // ROWID version of column |rowid_ofs|.
360
361  while (s.Step()) {
362    const std::string column_name(s.ColumnString(1));
363    const std::string column_type(s.ColumnString(2));
364    const bool not_null = s.ColumnBool(3);
365    const int default_type = s.ColumnType(4);
366    const bool default_is_null = (default_type == COLUMN_TYPE_NULL);
367    const int pk_column = s.ColumnInt(5);
368
369    if (pk_column > 0) {
370      // TODO(shess): http://www.sqlite.org/pragma.html#pragma_table_info
371      // documents column 5 as the index of the column in the primary key
372      // (zero for not in primary key).  I find that it is always 1 for
373      // columns in the primary key.  Since this code is very dependent on
374      // that pragma, review if the implementation changes.
375      DCHECK_EQ(pk_column, 1);
376      ++pk_column_count;
377    }
378
379    // Construct column declaration as "name type [optional constraint]".
380    std::string column_decl = column_name;
381
382    // SQLite's affinity detection is documented at:
383    // http://www.sqlite.org/datatype3.html#affname
384    // The gist of it is that CHAR, TEXT, and INT use substring matches.
385    // TODO(shess): It would be nice to unit test the type handling,
386    // but it is not obvious to me how to write a test which would
387    // fail appropriately when something was broken.  It would have to
388    // somehow use data which would allow detecting the various type
389    // coercions which happen.  If STRICT could be enabled, type
390    // mismatches could be detected by which rows are filtered.
391    if (column_type.find("INT") != std::string::npos) {
392      if (pk_column == 1) {
393        rowid_ofs = create_column_decls.size();
394        rowid_decl = column_name + " ROWID";
395      }
396      column_decl += " INTEGER";
397    } else if (column_type.find("CHAR") != std::string::npos ||
398               column_type.find("TEXT") != std::string::npos) {
399      column_decl += " TEXT";
400    } else if (column_type == "BLOB") {
401      column_decl += " BLOB";
402    } else if (column_type.find("DOUB") != std::string::npos) {
403      column_decl += " FLOAT";
404    } else {
405      // TODO(shess): AFAICT, there remain:
406      // - contains("CLOB") -> TEXT
407      // - contains("REAL") -> FLOAT
408      // - contains("FLOA") -> FLOAT
409      // - other -> "NUMERIC"
410      // Just code those in as they come up.
411      NOTREACHED() << " Unsupported type " << column_type;
412      RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE);
413      return false;
414    }
415
416    // If column has constraint "NOT NULL", then inserting NULL into
417    // that column will fail.  If the column has a non-NULL DEFAULT
418    // specified, the INSERT will handle it (see below).  If the
419    // DEFAULT is also NULL, the row must be filtered out.
420    // TODO(shess): The above scenario applies to INSERT OR REPLACE,
421    // whereas INSERT OR IGNORE drops such rows.
422    // http://www.sqlite.org/lang_conflict.html
423    if (not_null && default_is_null)
424      column_decl += " NOT NULL";
425
426    create_column_decls.push_back(column_decl);
427
428    // Per the NOTE in the header file, convert NULL values to the
429    // DEFAULT.  All columns could be IFNULL(column_name,default), but
430    // the NULL case would require special handling either way.
431    if (default_is_null) {
432      insert_columns.push_back(column_name);
433    } else {
434      // The default value appears to be pre-quoted, as if it is
435      // literally from the sqlite_master CREATE statement.
436      std::string default_value = s.ColumnString(4);
437      insert_columns.push_back(base::StringPrintf(
438          "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str()));
439    }
440  }
441
442  // Receiving no column information implies that the table doesn't exist.
443  if (create_column_decls.empty()) {
444    RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE);
445    return false;
446  }
447
448  // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID.
449  if (pk_column_count == 1 && !rowid_decl.empty())
450    create_column_decls[rowid_ofs] = rowid_decl;
451
452  // Additional columns accept anything.
453  // TODO(shess): ignoreN isn't well namespaced.  But it will fail to
454  // execute in case of conflicts.
455  for (size_t i = 0; i < extend_columns; ++i) {
456    create_column_decls.push_back(
457        base::StringPrintf("ignore%" PRIuS " ANY", i));
458  }
459
460  std::string recover_create(base::StringPrintf(
461      "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)",
462      table_name,
463      table_name,
464      JoinString(create_column_decls, ',').c_str()));
465
466  std::string recover_insert(base::StringPrintf(
467      "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s",
468      table_name,
469      JoinString(insert_columns, ',').c_str(),
470      table_name));
471
472  std::string recover_drop(base::StringPrintf(
473      "DROP TABLE temp.recover_%s", table_name));
474
475  if (!db()->Execute(recover_create.c_str())) {
476    RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE);
477    return false;
478  }
479
480  if (!db()->Execute(recover_insert.c_str())) {
481    RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT);
482    ignore_result(db()->Execute(recover_drop.c_str()));
483    return false;
484  }
485
486  *rows_recovered = db()->GetLastChangeCount();
487
488  // TODO(shess): Is leaving the recover table around a breaker?
489  if (!db()->Execute(recover_drop.c_str())) {
490    RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_DROP);
491    return false;
492  }
493  RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVER);
494  return true;
495}
496
497bool Recovery::SetupMeta() {
498  const char kCreateSql[] =
499      "CREATE VIRTUAL TABLE temp.recover_meta USING recover"
500      "("
501      "corrupt.meta,"
502      "key TEXT NOT NULL,"
503      "value ANY"  // Whatever is stored.
504      ")";
505  if (!db()->Execute(kCreateSql)) {
506    RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE);
507    return false;
508  }
509  RecordRecoveryEvent(RECOVERY_SUCCESS_SETUP_META);
510  return true;
511}
512
513bool Recovery::GetMetaVersionNumber(int* version) {
514  DCHECK(version);
515  // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta"));
516  // Unfortunately, DoesTableExist() queries sqlite_master, not
517  // sqlite_temp_master.
518
519  const char kVersionSql[] =
520      "SELECT value FROM temp.recover_meta WHERE key = 'version'";
521  sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql));
522  if (!recovery_version.Step()) {
523    if (!recovery_version.Succeeded()) {
524      RecordRecoveryEvent(RECOVERY_FAILED_META_QUERY);
525    } else {
526      RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION);
527    }
528    return false;
529  }
530
531  RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION);
532  *version = recovery_version.ColumnInt(0);
533  return true;
534}
535
536}  // namespace sql
537