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 in |
| 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 |