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 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
77 | 77 |
78 // GetMetaVersionNumber() successfully completed. | 78 // GetMetaVersionNumber() successfully completed. |
79 RECOVERY_SUCCESS_META_VERSION, | 79 RECOVERY_SUCCESS_META_VERSION, |
80 | 80 |
81 // Failed in querying recovery meta table. | 81 // Failed in querying recovery meta table. |
82 RECOVERY_FAILED_META_QUERY, | 82 RECOVERY_FAILED_META_QUERY, |
83 | 83 |
84 // No version key in recovery meta table. | 84 // No version key in recovery meta table. |
85 RECOVERY_FAILED_META_NO_VERSION, | 85 RECOVERY_FAILED_META_NO_VERSION, |
86 | 86 |
87 // Automatically recovered entire database successfully. | |
88 RECOVERY_SUCCESS_AUTORECOVERDB, | |
89 | |
90 // Database was so broken recovery couldn't be entered. | |
91 RECOVERY_FAILED_AUTORECOVERDB_BEGIN, | |
92 | |
93 // Failed to schema from corrupt database. | |
94 RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT, | |
95 | |
96 // Failed to create copy of schema in recovery database. | |
97 RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE, | |
98 | |
99 // Failed querying tables to recover. Should be impossible. | |
100 RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT, | |
101 | |
102 // Failed to recover an individual table. | |
103 RECOVERY_FAILED_AUTORECOVERDB_TABLE, | |
104 | |
105 // Failed to recover [sqlite_sequence] table. | |
106 RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE, | |
107 | |
108 // Failed to recover triggers or views or virtual tables. | |
109 RECOVERY_FAILED_AUTORECOVERDB_AUX, | |
110 | |
87 // Always keep this at the end. | 111 // Always keep this at the end. |
88 RECOVERY_EVENT_MAX, | 112 RECOVERY_EVENT_MAX, |
89 }; | 113 }; |
90 | 114 |
91 void RecordRecoveryEvent(RecoveryEventType recovery_event) { | 115 void RecordRecoveryEvent(RecoveryEventType recovery_event) { |
92 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", | 116 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", |
93 recovery_event, RECOVERY_EVENT_MAX); | 117 recovery_event, RECOVERY_EVENT_MAX); |
94 } | 118 } |
95 | 119 |
96 } // namespace | 120 } // namespace |
(...skipping 404 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
501 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION); | 525 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION); |
502 } | 526 } |
503 return false; | 527 return false; |
504 } | 528 } |
505 | 529 |
506 RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION); | 530 RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION); |
507 *version = recovery_version.ColumnInt(0); | 531 *version = recovery_version.ColumnInt(0); |
508 return true; | 532 return true; |
509 } | 533 } |
510 | 534 |
535 namespace { | |
536 | |
537 // Collect statements from [corrupt.sqlite_master.sql] which start with |prefix| | |
538 // (which should be a valid SQL string ending with the space before a table | |
539 // name), then apply the statements to [main]. Skip any table named | |
540 // 'sqlite_sequence', as that table is created on demand by SQLite if any tables | |
541 // use AUTOINCREMENT. | |
542 // | |
543 // Returns |true| if all of the matching items were created in the main | |
544 // database. Returns |false| if an item fails on creation, or if the corrupt | |
545 // database schema cannot be queried. | |
546 bool SchemaCopyHelper(Connection* db, const char* prefix) { | |
547 const size_t prefix_len = strlen(prefix); | |
548 DCHECK_EQ(' ', prefix[prefix_len-1]); | |
549 | |
550 sql::Statement s(db->GetUniqueStatement( | |
551 "SELECT DISTINCT sql FROM corrupt.sqlite_master " | |
552 "WHERE name<>'sqlite_sequence'")); | |
553 while (s.Step()) { | |
554 std::string sql = s.ColumnString(0); | |
555 | |
556 // Skip statements that don't start with |prefix|. | |
557 if (sql.compare(0, prefix_len, prefix) != 0) | |
558 continue; | |
559 | |
560 sql.insert(prefix_len, "main."); | |
561 if (!db->Execute(sql.c_str())) { | |
562 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMACREATE); | |
563 return false; | |
564 } | |
565 } | |
566 if (!s.Succeeded()) { | |
567 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SCHEMASELECT); | |
568 return false; | |
569 } | |
570 return true; | |
571 } | |
572 | |
573 } // namespace | |
574 | |
575 // This method is derived from SQLite's vacuum.c. VACUUM operates very | |
576 // similarily, creating a new database, populating the schema, then copying the | |
577 // data. | |
578 // | |
579 // TODO(shess): This conservatively uses Rollback() rather than Unrecoverable(). | |
580 // With Rollback(), it is expected that the database will continue to generate | |
581 // errors. Change the failure cases to Unrecoverable() if/when histogram | |
582 // results indicate that everything is working reasonably. | |
583 // | |
584 // static | |
585 void Recovery::RecoverDatabase(Connection* db, | |
586 const base::FilePath& db_path) { | |
587 std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); | |
588 if (!recovery) { | |
589 // TODO(shess): If recovery can't even get started, Raze() or Delete(). | |
590 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_BEGIN); | |
591 db->Poison(); | |
592 return; | |
593 } | |
594 | |
595 #if DCHECK_IS_ON() | |
596 // This code silently fails to recover fts3 virtual tables. At this time no | |
597 // browser database contain fts3 tables. Just to be safe, complain loudly if | |
598 // the database contains virtual tables. | |
599 // | |
600 // fts3 has an [x_segdir] table containing a column [end_block INTEGER]. But | |
601 // it actually stores either an integer or a text containing a pair of | |
602 // integers separated by a space. AutoRecoverTable() trusts the INTEGER tag | |
603 // when setting up the recover vtable, so those rows get dropped. Setting | |
604 // that column to ANY may work. | |
605 if (db->is_open()) { | |
606 sql::Statement s(db->GetUniqueStatement( | |
607 "SELECT 1 FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE %'")); | |
608 DCHECK(!s.Step()) << "Recovery of virtual tables not supported"; | |
609 } | |
610 #endif | |
611 | |
612 // TODO(shess): vacuum.c turns off checks and foreign keys. | |
613 | |
614 // TODO(shess): vacuum.c turns synchronous=OFF for the target. I do not fully | |
615 // understand this, as the temporary db should not have a journal file at all. | |
616 // Perhaps it does in case of cache spill? | |
617 | |
618 // Copy table schema from [corrupt] to [main]. | |
619 if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") || | |
620 !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") || | |
621 !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) { | |
622 // No RecordRecoveryEvent() here because SchemaCopyHelper() already did. | |
623 Recovery::Rollback(std::move(recovery)); | |
624 return; | |
625 } | |
626 | |
627 // Run auto-recover against each table, skipping the sequence table. This is | |
628 // necessary because table recovery can create the sequence table as a side | |
629 // effect, so recovering that table inline could lead to duplicate data. | |
630 { | |
631 sql::Statement s(recovery->db()->GetUniqueStatement( | |
632 "SELECT name FROM sqlite_master WHERE sql LIKE 'CREATE TABLE %' " | |
633 "AND name!='sqlite_sequence'")); | |
634 while (s.Step()) { | |
635 const std::string name = s.ColumnString(0); | |
636 size_t rows_recovered; | |
637 if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) { | |
638 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_TABLE); | |
639 Recovery::Rollback(std::move(recovery)); | |
640 return; | |
641 } | |
642 } | |
643 if (!s.Succeeded()) { | |
644 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_NAMESELECT); | |
645 Recovery::Rollback(std::move(recovery)); | |
646 return; | |
647 } | |
648 } | |
649 | |
650 // Overwrite any sequences created. | |
651 if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) { | |
652 ignore_result(recovery->db()->Execute("DELETE FROM main.sqlite_sequence")); | |
653 size_t rows_recovered; | |
654 if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) { | |
655 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_SEQUENCE); | |
656 Recovery::Rollback(std::move(recovery)); | |
657 return; | |
658 } | |
659 } | |
660 | |
661 // Copy triggers and views directly to sqlite_master. Any tables they refer | |
662 // to should already exist. | |
663 char kCreateMetaItems[] = | |
664 "INSERT INTO main.sqlite_master " | |
665 "SELECT type, name, tbl_name, rootpage, sql " | |
666 "FROM corrupt.sqlite_master WHERE type='view' OR type='trigger'"; | |
667 if (!recovery->db()->Execute(kCreateMetaItems)) { | |
668 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVERDB_AUX); | |
669 Recovery::Rollback(std::move(recovery)); | |
670 return; | |
671 } | |
672 | |
673 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVERDB); | |
674 ignore_result(Recovery::Recovered(std::move(recovery))); | |
675 } | |
676 | |
677 // static | |
678 bool Recovery::ShouldRecover(int extended_error) { | |
679 // Trim extended error codes. | |
680 int error = extended_error & 0xFF; | |
681 switch (error) { | |
682 case SQLITE_NOTADB: | |
683 // SQLITE_NOTADB happens if the SQLite header is broken. Some earlier | |
684 // versions of SQLite return this where other versions return | |
685 // SQLITE_CORRUPT, which is a recoverable case. Later versions only | |
686 // return this error only in unrecoverable cases, in which case recovery | |
687 // will fail with no changes to the database, so there's no harm to | |
Mark P
2016/07/01 22:40:55
nit: "to attempt" or "in attempting", not "to atte
| |
688 // attempting recovery in this case. | |
689 return true; | |
690 | |
691 case SQLITE_CORRUPT: | |
692 // SQLITE_CORRUPT generally means that the database is readable as a | |
693 // SQLite database, but some inconsistency has been detected by SQLite. | |
694 // In many cases the inconsistency is relatively trivial, such as if an | |
695 // index refers to a row which was deleted, in which case most or even all | |
696 // of the data can be recovered. This can also be reported if parts of | |
697 // the file have been overwritten with garbage data, in which recovery | |
698 // should be able to recover partial data. | |
699 return true; | |
700 | |
701 // TODO(shess): Possible future options for automated fixing: | |
702 // - SQLITE_CANTOPEN - delete the broken symlink or directory. | |
703 // - SQLITE_PERM - permissions could be fixed. | |
704 // - SQLITE_READONLY - permissions could be fixed. | |
705 // - SQLITE_IOERR - rewrite using new blocks. | |
706 // - SQLITE_FULL - recover in memory and rewrite subset of data. | |
707 | |
708 default: | |
709 return false; | |
710 } | |
711 } | |
712 | |
511 } // namespace sql | 713 } // namespace sql |
OLD | NEW |