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