Home | History | Annotate | Download | only in sql
      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 
     18 namespace sql {
     19 
     20 namespace {
     21 
     22 enum 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 
     88 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
     89   UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents",
     90                             recovery_event, RECOVERY_EVENT_MAX);
     91 }
     92 
     93 }  // namespace
     94 
     95 // static
     96 bool 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
    106 scoped_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
    120 bool Recovery::Recovered(scoped_ptr<Recovery> r) {
    121   return r->Backup();
    122 }
    123 
    124 // static
    125 void Recovery::Unrecoverable(scoped_ptr<Recovery> r) {
    126   CHECK(r->db_);
    127   // ~Recovery() will RAZE_AND_POISON.
    128 }
    129 
    130 // static
    131 void 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 
    137 Recovery::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 
    150 Recovery::~Recovery() {
    151   Shutdown(RAZE_AND_POISON);
    152 }
    153 
    154 bool 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 
    228 bool 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 
    323 void 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 
    336 bool 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 = 0;  // 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(1, pk_column);
    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 
    497 bool 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 
    513 bool 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