| OLD | NEW | 
|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include "sql/recovery.h" | 5 #include "sql/recovery.h" | 
| 6 | 6 | 
| 7 #include <stddef.h> | 7 #include <stddef.h> | 
| 8 | 8 | 
| 9 #include "base/files/file_path.h" | 9 #include "base/files/file_path.h" | 
| 10 #include "base/format_macros.h" | 10 #include "base/format_macros.h" | 
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 119   RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN, | 119   RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN, | 
| 120 | 120 | 
| 121   // After SQLITE_NOTADB failure setting up for recovery, Delete() and Open() | 121   // After SQLITE_NOTADB failure setting up for recovery, Delete() and Open() | 
| 122   // succeeded, then querying the database failed. | 122   // succeeded, then querying the database failed. | 
| 123   RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY, | 123   RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY, | 
| 124 | 124 | 
| 125   // After SQLITE_NOTADB failure setting up for recovery, the database was | 125   // After SQLITE_NOTADB failure setting up for recovery, the database was | 
| 126   // successfully deleted. | 126   // successfully deleted. | 
| 127   RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE, | 127   RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE, | 
| 128 | 128 | 
|  | 129   // Failed to find required [meta.version] information. | 
|  | 130   RECOVERY_FAILED_AUTORECOVERDB_META_VERSION, | 
|  | 131 | 
| 129   // Add new items before this one, always keep this one at the end. | 132   // Add new items before this one, always keep this one at the end. | 
| 130   RECOVERY_EVENT_MAX, | 133   RECOVERY_EVENT_MAX, | 
| 131 }; | 134 }; | 
| 132 | 135 | 
| 133 void RecordRecoveryEvent(RecoveryEventType recovery_event) { | 136 void RecordRecoveryEvent(RecoveryEventType recovery_event) { | 
| 134   UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", | 137   UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", | 
| 135                             recovery_event, RECOVERY_EVENT_MAX); | 138                             recovery_event, RECOVERY_EVENT_MAX); | 
| 136 } | 139 } | 
| 137 | 140 | 
| 138 }  // namespace | 141 }  // namespace | 
| (...skipping 456 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 595 // This method is derived from SQLite's vacuum.c.  VACUUM operates very | 598 // This method is derived from SQLite's vacuum.c.  VACUUM operates very | 
| 596 // similarily, creating a new database, populating the schema, then copying the | 599 // similarily, creating a new database, populating the schema, then copying the | 
| 597 // data. | 600 // data. | 
| 598 // | 601 // | 
| 599 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable(). | 602 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable(). | 
| 600 // With Rollback(), it is expected that the database will continue to generate | 603 // With Rollback(), it is expected that the database will continue to generate | 
| 601 // errors.  Change the failure cases to Unrecoverable() if/when histogram | 604 // errors.  Change the failure cases to Unrecoverable() if/when histogram | 
| 602 // results indicate that everything is working reasonably. | 605 // results indicate that everything is working reasonably. | 
| 603 // | 606 // | 
| 604 // static | 607 // static | 
| 605 void Recovery::RecoverDatabase(Connection* db, | 608 std::unique_ptr<Recovery> Recovery::BeginRecoverDatabase( | 
| 606                                const base::FilePath& db_path) { | 609     Connection* db, | 
|  | 610     const base::FilePath& db_path) { | 
| 607   std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); | 611   std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); | 
| 608   if (!recovery) { | 612   if (!recovery) { | 
| 609     // Close the underlying sqlite* handle.  Windows does not allow deleting | 613     // Close the underlying sqlite* handle.  Windows does not allow deleting | 
| 610     // open files, and all platforms block opening a second sqlite3* handle | 614     // open files, and all platforms block opening a second sqlite3* handle | 
| 611     // against a database when exclusive locking is set. | 615     // against a database when exclusive locking is set. | 
| 612     db->Poison(); | 616     db->Poison(); | 
| 613 | 617 | 
| 614     // Histograms from Recovery::Begin() show all current failures are in | 618     // Histograms from Recovery::Begin() show all current failures are in | 
| 615     // attaching the corrupt database, with 2/3 being SQLITE_NOTADB.  Don't | 619     // attaching the corrupt database, with 2/3 being SQLITE_NOTADB.  Don't | 
| 616     // delete the database except for that specific failure case. | 620     // delete the database except for that specific failure case. | 
| 617     { | 621     { | 
| 618       Connection probe_db; | 622       Connection probe_db; | 
| 619       if (!probe_db.OpenInMemory() || | 623       if (!probe_db.OpenInMemory() || | 
| 620           probe_db.AttachDatabase(db_path, "corrupt") || | 624           probe_db.AttachDatabase(db_path, "corrupt") || | 
| 621           probe_db.GetErrorCode() != SQLITE_NOTADB) { | 625           probe_db.GetErrorCode() != SQLITE_NOTADB) { | 
| 622         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN); | 626         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN); | 
| 623         return; | 627         return nullptr; | 
| 624       } | 628       } | 
| 625     } | 629     } | 
| 626 | 630 | 
| 627     // The database has invalid data in the SQLite header, so it is almost | 631     // The database has invalid data in the SQLite header, so it is almost | 
| 628     // certainly not recoverable without manual intervention (and likely not | 632     // certainly not recoverable without manual intervention (and likely not | 
| 629     // recoverable _with_ manual intervention).  Clear away the broken database. | 633     // recoverable _with_ manual intervention).  Clear away the broken database. | 
| 630     if (!sql::Connection::Delete(db_path)) { | 634     if (!sql::Connection::Delete(db_path)) { | 
| 631       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_DELETE); | 635       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_DELETE); | 
| 632       return; | 636       return nullptr; | 
| 633     } | 637     } | 
| 634 | 638 | 
| 635     // Windows deletion is complicated by file scanners and malware - sometimes | 639     // Windows deletion is complicated by file scanners and malware - sometimes | 
| 636     // Delete() appears to succeed, even though the file remains.  The following | 640     // Delete() appears to succeed, even though the file remains.  The following | 
| 637     // attempts to track if this happens often enough to cause concern. | 641     // attempts to track if this happens often enough to cause concern. | 
| 638     { | 642     { | 
| 639       Connection probe_db; | 643       Connection probe_db; | 
| 640       if (!probe_db.Open(db_path)) { | 644       if (!probe_db.Open(db_path)) { | 
| 641         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN); | 645         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_REOPEN); | 
| 642         return; | 646         return nullptr; | 
| 643       } | 647       } | 
| 644       if (!probe_db.Execute("PRAGMA auto_vacuum")) { | 648       if (!probe_db.Execute("PRAGMA auto_vacuum")) { | 
| 645         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY); | 649         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NOTADB_QUERY); | 
| 646         return; | 650         return nullptr; | 
| 647       } | 651       } | 
| 648     } | 652     } | 
| 649 | 653 | 
| 650     // The rest of the recovery code could be run on the re-opened database, but | 654     // The rest of the recovery code could be run on the re-opened database, but | 
| 651     // the database is empty, so there would be no point. | 655     // the database is empty, so there would be no point. | 
| 652     RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE); | 656     RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB_NOTADB_DELETE); | 
| 653     return; | 657     return nullptr; | 
| 654   } | 658   } | 
| 655 | 659 | 
| 656 #if DCHECK_IS_ON() | 660 #if DCHECK_IS_ON() | 
| 657   // This code silently fails to recover fts3 virtual tables.  At this time no | 661   // This code silently fails to recover fts3 virtual tables.  At this time no | 
| 658   // browser database contain fts3 tables.  Just to be safe, complain loudly if | 662   // browser database contain fts3 tables.  Just to be safe, complain loudly if | 
| 659   // the database contains virtual tables. | 663   // the database contains virtual tables. | 
| 660   // | 664   // | 
| 661   // fts3 has an [x_segdir] table containing a column [end_block INTEGER].  But | 665   // fts3 has an [x_segdir] table containing a column [end_block INTEGER].  But | 
| 662   // it actually stores either an integer or a text containing a pair of | 666   // it actually stores either an integer or a text containing a pair of | 
| 663   // integers separated by a space.  AutoRecoverTable() trusts the INTEGER tag | 667   // integers separated by a space.  AutoRecoverTable() trusts the INTEGER tag | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
| 675   // TODO(shess): vacuum.c turns synchronous=OFF for the target.  I do not fully | 679   // TODO(shess): vacuum.c turns synchronous=OFF for the target.  I do not fully | 
| 676   // understand this, as the temporary db should not have a journal file at all. | 680   // understand this, as the temporary db should not have a journal file at all. | 
| 677   // Perhaps it does in case of cache spill? | 681   // Perhaps it does in case of cache spill? | 
| 678 | 682 | 
| 679   // Copy table schema from [corrupt] to [main]. | 683   // Copy table schema from [corrupt] to [main]. | 
| 680   if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") || | 684   if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") || | 
| 681       !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") || | 685       !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") || | 
| 682       !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) { | 686       !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) { | 
| 683     // No RecordRecoveryEvent() here because SchemaCopyHelper() already did. | 687     // No RecordRecoveryEvent() here because SchemaCopyHelper() already did. | 
| 684     Recovery::Rollback(std::move(recovery)); | 688     Recovery::Rollback(std::move(recovery)); | 
| 685     return; | 689     return nullptr; | 
| 686   } | 690   } | 
| 687 | 691 | 
| 688   // Run auto-recover against each table, skipping the sequence table.  This is | 692   // Run auto-recover against each table, skipping the sequence table.  This is | 
| 689   // necessary because table recovery can create the sequence table as a side | 693   // necessary because table recovery can create the sequence table as a side | 
| 690   // effect, so recovering that table inline could lead to duplicate data. | 694   // effect, so recovering that table inline could lead to duplicate data. | 
| 691   { | 695   { | 
| 692     sql::Statement s(recovery->db()->GetUniqueStatement( | 696     sql::Statement s(recovery->db()->GetUniqueStatement( | 
| 693         "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' " | 697         "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' " | 
| 694         "AND name!='sqlite_sequence'")); | 698         "AND name!='sqlite_sequence'")); | 
| 695     while (s.Step()) { | 699     while (s.Step()) { | 
| 696       const std::string name = s.ColumnString(0); | 700       const std::string name = s.ColumnString(0); | 
| 697       size_t rows_recovered; | 701       size_t rows_recovered; | 
| 698       if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) { | 702       if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) { | 
| 699         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE); | 703         RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE); | 
| 700         Recovery::Rollback(std::move(recovery)); | 704         Recovery::Rollback(std::move(recovery)); | 
| 701         return; | 705         return nullptr; | 
| 702       } | 706       } | 
| 703     } | 707     } | 
| 704     if (!s.Succeeded()) { | 708     if (!s.Succeeded()) { | 
| 705       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT); | 709       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT); | 
| 706       Recovery::Rollback(std::move(recovery)); | 710       Recovery::Rollback(std::move(recovery)); | 
| 707       return; | 711       return nullptr; | 
| 708     } | 712     } | 
| 709   } | 713   } | 
| 710 | 714 | 
| 711   // Overwrite any sequences created. | 715   // Overwrite any sequences created. | 
| 712   if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) { | 716   if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) { | 
| 713     ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence")); | 717     ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence")); | 
| 714     size_t rows_recovered; | 718     size_t rows_recovered; | 
| 715     if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) { | 719     if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) { | 
| 716       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE); | 720       RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE); | 
| 717       Recovery::Rollback(std::move(recovery)); | 721       Recovery::Rollback(std::move(recovery)); | 
| 718       return; | 722       return nullptr; | 
| 719     } | 723     } | 
| 720   } | 724   } | 
| 721 | 725 | 
| 722   // Copy triggers and views directly to sqlite_master.  Any tables they refer | 726   // Copy triggers and views directly to sqlite_master.  Any tables they refer | 
| 723   // to should already exist. | 727   // to should already exist. | 
| 724   char kCreateMetaItems[] = | 728   char kCreateMetaItems[] = | 
| 725       "INSERT INTO main.sqlite_master " | 729       "INSERT INTO main.sqlite_master " | 
| 726       "SELECT type, name, tbl_name, rootpage, sql " | 730       "SELECT type, name, tbl_name, rootpage, sql " | 
| 727       "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'"; | 731       "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'"; | 
| 728   if (!recovery->db()->Execute(kCreateMetaItems)) { | 732   if (!recovery->db()->Execute(kCreateMetaItems)) { | 
| 729     RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX); | 733     RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX); | 
| 730     Recovery::Rollback(std::move(recovery)); | 734     Recovery::Rollback(std::move(recovery)); | 
|  | 735     return nullptr; | 
|  | 736   } | 
|  | 737 | 
|  | 738   RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB); | 
|  | 739   return recovery; | 
|  | 740 } | 
|  | 741 | 
|  | 742 void Recovery::RecoverDatabase(Connection* db, const base::FilePath& db_path) { | 
|  | 743   std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path); | 
|  | 744 | 
|  | 745   // ignore_result() because BeginRecoverDatabase() and Recovered() already | 
|  | 746   // provide suitable histogram coverage. | 
|  | 747   if (recovery) | 
|  | 748     ignore_result(Recovery::Recovered(std::move(recovery))); | 
|  | 749 } | 
|  | 750 | 
|  | 751 void Recovery::RecoverDatabaseWithMetaVersion(Connection* db, | 
|  | 752                                               const base::FilePath& db_path) { | 
|  | 753   std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path); | 
|  | 754   if (!recovery) | 
|  | 755     return; | 
|  | 756 | 
|  | 757   int version = 0; | 
|  | 758   if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { | 
|  | 759     sql::Recovery::Unrecoverable(std::move(recovery)); | 
|  | 760     RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_META_VERSION); | 
| 731     return; | 761     return; | 
| 732   } | 762   } | 
| 733 | 763 | 
| 734   RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB); | 764   // ignore_result() because BeginRecoverDatabase() and Recovered() already | 
|  | 765   // provide suitable histogram coverage. | 
| 735   ignore_result(Recovery::Recovered(std::move(recovery))); | 766   ignore_result(Recovery::Recovered(std::move(recovery))); | 
| 736 } | 767 } | 
| 737 | 768 | 
| 738 // static | 769 // static | 
| 739 bool Recovery::ShouldRecover(int extended_error) { | 770 bool Recovery::ShouldRecover(int extended_error) { | 
| 740   // Trim extended error codes. | 771   // Trim extended error codes. | 
| 741   int error = extended_error & 0xFF; | 772   int error = extended_error & 0xFF; | 
| 742   switch (error) { | 773   switch (error) { | 
| 743     case SQLITE_NOTADB: | 774     case SQLITE_NOTADB: | 
| 744       // SQLITE_NOTADB happens if the SQLite header is broken.  Some earlier | 775       // SQLITE_NOTADB happens if the SQLite header is broken.  Some earlier | 
| (...skipping 20 matching lines...) Expand all  Loading... | 
| 765       // - SQLITE_READONLY - permissions could be fixed. | 796       // - SQLITE_READONLY - permissions could be fixed. | 
| 766       // - SQLITE_IOERR - rewrite using new blocks. | 797       // - SQLITE_IOERR - rewrite using new blocks. | 
| 767       // - SQLITE_FULL - recover in memory and rewrite subset of data. | 798       // - SQLITE_FULL - recover in memory and rewrite subset of data. | 
| 768 | 799 | 
| 769     default: | 800     default: | 
| 770       return false; | 801       return false; | 
| 771   } | 802   } | 
| 772 } | 803 } | 
| 773 | 804 | 
| 774 }  // namespace sql | 805 }  // namespace sql | 
| OLD | NEW | 
|---|