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 |