From f857c5a3d8a0ffe742955bdc36d0553d489dfbf9 Mon Sep 17 00:00:00 2001 From: "jsbell@chromium.org" Date: Mon, 31 Mar 2014 23:23:56 +0000 Subject: [PATCH] Un-revert Handling LevelDB errors encountered after open. The previous IndexedDB implementation only checked for (and reported) corrupted LevelDB's during open. If they are determined to be corrupted during use then the database was simply closed. The page would be unaware of this, and would likely reopen the database at a later time (which would work) only to fail again during a read/write operation. There was no way for the page to get out of that corrupted state - the user would have to delete all origin data. This change will record the fact that the backing store was corrupted, and during open this would be read and, if present, result in the same dataLoss state being reported to the page in the upgradeneeded event. Also split IndexedDBBackingStore's REPORT_ERROR macro in two: REPORT_ERROR_UNTESTED (which calls NOTREACHED) and REPORT_ERROR. REPORT_ERROR is used for errors which are encountered during tests. This was done so that runtime errors could still be caught during a debug build run. The original change (r260147) had a buffer overrun in a unit test fixed in this CL. BUG=322707 R=isherman@chromium.org, jochen@chromium.org, jsbell@chromium.org Review URL: https://codereview.chromium.org/197333009 Patch from Christopher Mumford . git-svn-id: svn://svn.chromium.org/chrome/trunk/src@260697 0039d316-1c4b-4281-b951-d872f2087c98 --- .../indexed_db/indexed_db_backing_store.cc | 282 +++++++++++++----- .../indexed_db/indexed_db_backing_store.h | 11 + .../indexed_db/indexed_db_browsertest.cc | 141 +++++++++ .../browser/indexed_db/indexed_db_database.cc | 103 +++++-- .../browser/indexed_db/indexed_db_factory.cc | 19 ++ .../browser/indexed_db/indexed_db_factory.h | 4 +- .../indexed_db/leveldb/leveldb_database.cc | 2 + .../indexed_db/leveldb/leveldb_database.h | 1 + content/test/data/indexeddb/common.js | 6 + .../corrupted_open_db_detection.html | 114 +++++++ .../indexeddb/corrupted_open_db_recovery.html | 46 +++ tools/metrics/histograms/histograms.xml | 3 + 12 files changed, 618 insertions(+), 114 deletions(-) create mode 100644 content/test/data/indexeddb/corrupted_open_db_detection.html create mode 100644 content/test/data/indexeddb/corrupted_open_db_recovery.html diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc index e78ec1b0d73d12..089a03af5246dc 100644 --- a/content/browser/indexed_db/indexed_db_backing_store.cc +++ b/content/browser/indexed_db/indexed_db_backing_store.cc @@ -5,10 +5,14 @@ #include "content/browser/indexed_db/indexed_db_backing_store.h" #include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" #include "base/logging.h" #include "base/metrics/histogram.h" +#include "base/platform_file.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" #include "content/browser/indexed_db/indexed_db_leveldb_coding.h" #include "content/browser/indexed_db/indexed_db_metadata.h" #include "content/browser/indexed_db/indexed_db_tracing.h" @@ -41,6 +45,11 @@ static base::FilePath ComputeFileName(const GURL& origin_url) { .AddExtension(FILE_PATH_LITERAL(".indexeddb.leveldb")); } +static base::FilePath ComputeCorruptionFileName(const GURL& origin_url) { + return ComputeFileName(origin_url) + .Append(FILE_PATH_LITERAL("corruption_info.json")); +} + } // namespace static const int64 kKeyGeneratorInitialNumber = @@ -84,13 +93,12 @@ static void RecordInternalError(const char* type, ->Add(location); } -// Use to signal conditions that usually indicate developer error, but -// could be caused by data corruption. A macro is used instead of an -// inline function so that the assert and log report the line number. +// Use to signal conditions caused by data corruption. +// A macro is used instead of an inline function so that the assert and log +// report the line number. #define REPORT_ERROR(type, location) \ do { \ LOG(ERROR) << "IndexedDB " type " Error: " #location; \ - NOTREACHED(); \ RecordInternalError(type, location); \ } while (0) @@ -99,6 +107,25 @@ static void RecordInternalError(const char* type, REPORT_ERROR("Consistency", location) #define INTERNAL_WRITE_ERROR(location) REPORT_ERROR("Write", location) +// Use to signal conditions that usually indicate developer error, but +// could be caused by data corruption. A macro is used instead of an +// inline function so that the assert and log report the line number. +// TODO: Improve test coverage so that all error conditions are "tested" and +// then delete this macro. +#define REPORT_ERROR_UNTESTED(type, location) \ + do { \ + LOG(ERROR) << "IndexedDB " type " Error: " #location; \ + NOTREACHED(); \ + RecordInternalError(type, location); \ + } while (0) + +#define INTERNAL_READ_ERROR_UNTESTED(location) \ + REPORT_ERROR_UNTESTED("Read", location) +#define INTERNAL_CONSISTENCY_ERROR_UNTESTED(location) \ + REPORT_ERROR_UNTESTED("Consistency", location) +#define INTERNAL_WRITE_ERROR_UNTESTED(location) \ + REPORT_ERROR_UNTESTED("Write", location) + static void PutBool(LevelDBTransaction* transaction, const StringPiece& key, bool value) { @@ -276,7 +303,7 @@ WARN_UNUSED_RESULT static bool SetUpMetadata( leveldb::Status s = GetInt(transaction.get(), schema_version_key, &db_schema_version, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(SET_UP_METADATA); + INTERNAL_READ_ERROR_UNTESTED(SET_UP_METADATA); return false; } if (!found) { @@ -303,11 +330,11 @@ WARN_UNUSED_RESULT static bool SetUpMetadata( found = false; s = GetInt(transaction.get(), it->Key(), &database_id, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(SET_UP_METADATA); + INTERNAL_READ_ERROR_UNTESTED(SET_UP_METADATA); return false; } if (!found) { - INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(SET_UP_METADATA); return false; } std::string int_version_key = DatabaseMetaDataKey::Encode( @@ -329,11 +356,11 @@ WARN_UNUSED_RESULT static bool SetUpMetadata( found = false; s = GetInt(transaction.get(), data_version_key, &db_data_version, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(SET_UP_METADATA); + INTERNAL_READ_ERROR_UNTESTED(SET_UP_METADATA); return false; } if (!found) { - INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(SET_UP_METADATA); return false; } if (db_data_version < latest_known_data_version) { @@ -346,7 +373,7 @@ WARN_UNUSED_RESULT static bool SetUpMetadata( s = transaction->Commit(); if (!s.ok()) { - INTERNAL_WRITE_ERROR(SET_UP_METADATA); + INTERNAL_WRITE_ERROR_UNTESTED(SET_UP_METADATA); return false; } return true; @@ -437,6 +464,7 @@ enum IndexedDBBackingStoreOpenResult { INDEXED_DB_BACKING_STORE_OPEN_DISK_FULL_DEPRECATED, INDEXED_DB_BACKING_STORE_OPEN_ORIGIN_TOO_LONG, INDEXED_DB_BACKING_STORE_OPEN_NO_RECOVERY, + INDEXED_DB_BACKING_STORE_OPEN_FAILED_PRIOR_CORRUPTION, INDEXED_DB_BACKING_STORE_OPEN_MAX, }; @@ -513,6 +541,91 @@ static bool IsPathTooLong(const base::FilePath& leveldb_dir) { return false; } +leveldb::Status IndexedDBBackingStore::DestroyBackingStore( + const base::FilePath& path_base, + const GURL& origin_url) { + const base::FilePath file_path = + path_base.Append(ComputeFileName(origin_url)); + DefaultLevelDBFactory leveldb_factory; + return leveldb_factory.DestroyLevelDB(file_path); +} + +bool IndexedDBBackingStore::ReadCorruptionInfo(const base::FilePath& path_base, + const GURL& origin_url, + std::string& message) { + + const base::FilePath info_path = + path_base.Append(ComputeCorruptionFileName(origin_url)); + + if (IsPathTooLong(info_path)) + return false; + + const int64 max_json_len = 4096; + int64 file_size(0); + if (!GetFileSize(info_path, &file_size) || file_size > max_json_len) + return false; + if (!file_size) { + NOTREACHED(); + return false; + } + + bool created(false); + base::PlatformFileError error(base::PLATFORM_FILE_OK); + base::PlatformFile file = base::CreatePlatformFile( + info_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + &created, + &error); + bool success = false; + if (file) { + std::vector bytes(file_size); + if (file_size == base::ReadPlatformFile(file, 0, &bytes[0], file_size)) { + std::string input_js(&bytes[0], file_size); + base::JSONReader reader; + scoped_ptr val(reader.ReadToValue(input_js)); + if (val && val->GetType() == base::Value::TYPE_DICTIONARY) { + base::DictionaryValue* dict_val = + static_cast(val.get()); + success = dict_val->GetString("message", &message); + } + } + base::ClosePlatformFile(file); + } + + base::DeleteFile(info_path, false); + + return success; +} + +bool IndexedDBBackingStore::RecordCorruptionInfo( + const base::FilePath& path_base, + const GURL& origin_url, + const std::string& message) { + const base::FilePath info_path = + path_base.Append(ComputeCorruptionFileName(origin_url)); + if (IsPathTooLong(info_path)) + return false; + + base::DictionaryValue root_dict; + root_dict.SetString("message", message); + std::string output_js; + base::JSONWriter::Write(&root_dict, &output_js); + + bool created(false); + base::PlatformFileError error(base::PLATFORM_FILE_OK); + base::PlatformFile file = base::CreatePlatformFile( + info_path, + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE, + &created, + &error); + if (!file) + return false; + int written = + base::WritePlatformFile(file, 0, output_js.c_str(), output_js.length()); + base::ClosePlatformFile(file); + return size_t(written) == output_js.length(); +} + // static scoped_refptr IndexedDBBackingStore::Open( const GURL& origin_url, @@ -566,8 +679,17 @@ scoped_refptr IndexedDBBackingStore::Open( bool is_schema_known = false; if (db) { - bool ok = IsSchemaKnown(db.get(), &is_schema_known); - if (!ok) { + std::string corruption_message; + if (ReadCorruptionInfo(path_base, origin_url, corruption_message)) { + LOG(ERROR) << "IndexedDB recovering from a corrupted (and deleted) " + "database."; + HistogramOpenStatus(INDEXED_DB_BACKING_STORE_OPEN_FAILED_PRIOR_CORRUPTION, + origin_url); + db.reset(); + *data_loss = blink::WebIDBDataLossTotal; + *data_loss_message = + "IndexedDB (database was corrupt): " + corruption_message; + } else if (!IsSchemaKnown(db.get(), &is_schema_known)) { LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as " "failure to open"; HistogramOpenStatus( @@ -689,7 +811,7 @@ std::vector IndexedDBBackingStore::GetDatabaseNames() { StringPiece slice(it->Key()); DatabaseNameKey database_name_key; if (!DatabaseNameKey::Decode(&slice, &database_name_key)) { - INTERNAL_CONSISTENCY_ERROR(GET_DATABASE_NAMES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_DATABASE_NAMES); continue; } found_names.push_back(database_name_key.database_name()); @@ -706,7 +828,7 @@ leveldb::Status IndexedDBBackingStore::GetIDBDatabaseMetaData( leveldb::Status s = GetInt(db_.get(), key, &metadata->id, found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); return s; } if (!*found) @@ -718,11 +840,11 @@ leveldb::Status IndexedDBBackingStore::GetIDBDatabaseMetaData( &metadata->version, found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); return s; } if (!*found) { - INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); return InternalInconsistencyStatus(); } @@ -732,11 +854,11 @@ leveldb::Status IndexedDBBackingStore::GetIDBDatabaseMetaData( &metadata->int_version, found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); return s; } if (!*found) { - INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); return InternalInconsistencyStatus(); } @@ -746,7 +868,7 @@ leveldb::Status IndexedDBBackingStore::GetIDBDatabaseMetaData( s = GetMaxObjectStoreId( db_.get(), metadata->id, &metadata->max_object_store_id); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA); + INTERNAL_READ_ERROR_UNTESTED(GET_IDBDATABASE_METADATA); } return s; @@ -761,7 +883,7 @@ WARN_UNUSED_RESULT static leveldb::Status GetNewDatabaseId( leveldb::Status s = GetInt(transaction, MaxDatabaseIdKey::Encode(), &max_database_id, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_NEW_DATABASE_ID); + INTERNAL_READ_ERROR_UNTESTED(GET_NEW_DATABASE_ID); return s; } if (!found) @@ -804,7 +926,7 @@ leveldb::Status IndexedDBBackingStore::CreateIDBDatabaseMetaData( int_version); s = transaction->Commit(); if (!s.ok()) - INTERNAL_WRITE_ERROR(CREATE_IDBDATABASE_METADATA); + INTERNAL_WRITE_ERROR_UNTESTED(CREATE_IDBDATABASE_METADATA); return s; } @@ -860,7 +982,7 @@ leveldb::Status IndexedDBBackingStore::DeleteDatabase( s = transaction->Commit(); if (!s.ok()) { - INTERNAL_WRITE_ERROR(DELETE_DATABASE); + INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE); return s; } db_->Compact(start_key, stop_key); @@ -908,7 +1030,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( bool ok = ObjectStoreMetaDataKey::Decode(&slice, &meta_data_key); DCHECK(ok); if (meta_data_key.MetaDataType() != ObjectStoreMetaDataKey::NAME) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); // Possible stale metadata, but don't fail the load. it->Next(); continue; @@ -922,7 +1044,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( { StringPiece slice(it->Value()); if (!DecodeString(&slice, &object_store_name) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); } it->Next(); @@ -930,14 +1052,14 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( stop_key, object_store_id, ObjectStoreMetaDataKey::KEY_PATH)) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } IndexedDBKeyPath key_path; { StringPiece slice(it->Value()); if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); } it->Next(); @@ -946,14 +1068,14 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( stop_key, object_store_id, ObjectStoreMetaDataKey::AUTO_INCREMENT)) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } bool auto_increment; { StringPiece slice(it->Value()); if (!DecodeBool(&slice, &auto_increment) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); } it->Next(); // Is evicatble. @@ -961,7 +1083,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( stop_key, object_store_id, ObjectStoreMetaDataKey::EVICTABLE)) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } @@ -971,7 +1093,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( stop_key, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION)) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } @@ -981,14 +1103,14 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( stop_key, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID)) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } int64 max_index_id; { StringPiece slice(it->Value()); if (!DecodeInt(&slice, &max_index_id) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); } it->Next(); // [optional] has key path (is not null) @@ -1000,7 +1122,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( { StringPiece slice(it->Value()); if (!DecodeBool(&slice, &has_key_path)) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); } // This check accounts for two layers of legacy coding: // (1) Initially, has_key_path was added to distinguish null vs. string. @@ -1009,7 +1131,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( if (!has_key_path && (key_path.type() == blink::WebIDBKeyPathTypeString && !key_path.string().empty())) { - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); break; } if (!has_key_path) @@ -1025,7 +1147,7 @@ leveldb::Status IndexedDBBackingStore::GetObjectStores( ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)) { StringPiece slice(it->Value()); if (!DecodeInt(&slice, &key_generator_current_number) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_OBJECT_STORES); // TODO(jsbell): Return key_generator_current_number, cache in // object store, and write lazily to backing store. For now, @@ -1058,18 +1180,20 @@ WARN_UNUSED_RESULT static leveldb::Status SetMaxObjectStoreId( leveldb::Status s = GetMaxObjectStoreId( transaction, max_object_store_id_key, &max_object_store_id); if (!s.ok()) { - INTERNAL_READ_ERROR(SET_MAX_OBJECT_STORE_ID); + INTERNAL_READ_ERROR_UNTESTED(SET_MAX_OBJECT_STORE_ID); return s; } if (object_store_id <= max_object_store_id) { - INTERNAL_CONSISTENCY_ERROR(SET_MAX_OBJECT_STORE_ID); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(SET_MAX_OBJECT_STORE_ID); return InternalInconsistencyStatus(); } PutInt(transaction, max_object_store_id_key, object_store_id); return s; } +void IndexedDBBackingStore::Compact() { db_->CompactAll(); } + leveldb::Status IndexedDBBackingStore::CreateObjectStore( IndexedDBBackingStore::Transaction* transaction, int64 database_id, @@ -1139,11 +1263,11 @@ leveldb::Status IndexedDBBackingStore::DeleteObjectStore( &object_store_name, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(DELETE_OBJECT_STORE); + INTERNAL_READ_ERROR_UNTESTED(DELETE_OBJECT_STORE); return s; } if (!found) { - INTERNAL_CONSISTENCY_ERROR(DELETE_OBJECT_STORE); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(DELETE_OBJECT_STORE); return InternalInconsistencyStatus(); } @@ -1191,14 +1315,14 @@ leveldb::Status IndexedDBBackingStore::GetRecord( if (!found) return s; if (data.empty()) { - INTERNAL_READ_ERROR(GET_RECORD); + INTERNAL_READ_ERROR_UNTESTED(GET_RECORD); return leveldb::Status::NotFound("Record contained no data"); } int64 version; StringPiece slice(data); if (!DecodeVarInt(&slice, &version)) { - INTERNAL_READ_ERROR(GET_RECORD); + INTERNAL_READ_ERROR_UNTESTED(GET_RECORD); return InternalInconsistencyStatus(); } @@ -1220,7 +1344,7 @@ WARN_UNUSED_RESULT static leveldb::Status GetNewVersionNumber( leveldb::Status s = GetInt(transaction, last_version_key, &last_version, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_NEW_VERSION_NUMBER); + INTERNAL_READ_ERROR_UNTESTED(GET_NEW_VERSION_NUMBER); return s; } if (!found) @@ -1336,13 +1460,13 @@ leveldb::Status IndexedDBBackingStore::GetKeyGeneratorCurrentNumber( leveldb::Status s = leveldb_transaction->Get(key_generator_current_number_key, &data, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER); + INTERNAL_READ_ERROR_UNTESTED(GET_KEY_GENERATOR_CURRENT_NUMBER); return s; } if (found && !data.empty()) { StringPiece slice(data); if (!DecodeInt(&slice, key_generator_current_number) || !slice.empty()) { - INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER); + INTERNAL_READ_ERROR_UNTESTED(GET_KEY_GENERATOR_CURRENT_NUMBER); return InternalInconsistencyStatus(); } return s; @@ -1367,7 +1491,7 @@ leveldb::Status IndexedDBBackingStore::GetKeyGeneratorCurrentNumber( StringPiece slice(it->Key()); ObjectStoreDataKey data_key; if (!ObjectStoreDataKey::Decode(&slice, &data_key)) { - INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER); + INTERNAL_READ_ERROR_UNTESTED(GET_KEY_GENERATOR_CURRENT_NUMBER); return InternalInconsistencyStatus(); } scoped_ptr user_key = data_key.user_key(); @@ -1429,13 +1553,13 @@ leveldb::Status IndexedDBBackingStore::KeyExistsInObjectStore( leveldb::Status s = transaction->transaction()->Get(leveldb_key, &data, found); if (!s.ok()) { - INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE); + INTERNAL_READ_ERROR_UNTESTED(KEY_EXISTS_IN_OBJECT_STORE); return s; } if (!*found) return leveldb::Status::OK(); if (!data.size()) { - INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE); + INTERNAL_READ_ERROR_UNTESTED(KEY_EXISTS_IN_OBJECT_STORE); return InternalInconsistencyStatus(); } @@ -1492,7 +1616,7 @@ leveldb::Status IndexedDBBackingStore::GetIndexes( bool ok = IndexMetaDataKey::Decode(&slice, &meta_data_key); DCHECK(ok); if (meta_data_key.meta_data_type() != IndexMetaDataKey::NAME) { - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); // Possible stale metadata due to http://webkit.org/b/85557 but don't fail // the load. it->Next(); @@ -1506,33 +1630,33 @@ leveldb::Status IndexedDBBackingStore::GetIndexes( { StringPiece slice(it->Value()); if (!DecodeString(&slice, &index_name) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); } it->Next(); // unique flag if (!CheckIndexAndMetaDataKey( it.get(), stop_key, index_id, IndexMetaDataKey::UNIQUE)) { - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); break; } bool index_unique; { StringPiece slice(it->Value()); if (!DecodeBool(&slice, &index_unique) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); } it->Next(); // key_path if (!CheckIndexAndMetaDataKey( it.get(), stop_key, index_id, IndexMetaDataKey::KEY_PATH)) { - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); break; } IndexedDBKeyPath key_path; { StringPiece slice(it->Value()); if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); } it->Next(); // [optional] multi_entry flag @@ -1541,7 +1665,7 @@ leveldb::Status IndexedDBBackingStore::GetIndexes( it.get(), stop_key, index_id, IndexMetaDataKey::MULTI_ENTRY)) { StringPiece slice(it->Value()); if (!DecodeBool(&slice, &index_multi_entry) || !slice.empty()) - INTERNAL_CONSISTENCY_ERROR(GET_INDEXES); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(GET_INDEXES); it->Next(); } @@ -1564,14 +1688,14 @@ WARN_UNUSED_RESULT static leveldb::Status SetMaxIndexId( leveldb::Status s = GetInt(transaction, max_index_id_key, &max_index_id, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(SET_MAX_INDEX_ID); + INTERNAL_READ_ERROR_UNTESTED(SET_MAX_INDEX_ID); return s; } if (!found) max_index_id = kMinimumIndexId; if (index_id <= max_index_id) { - INTERNAL_CONSISTENCY_ERROR(SET_MAX_INDEX_ID); + INTERNAL_CONSISTENCY_ERROR_UNTESTED(SET_MAX_INDEX_ID); return InternalInconsistencyStatus(); } @@ -1709,7 +1833,7 @@ static leveldb::Status VersionExists(LevelDBTransaction* transaction, leveldb::Status s = transaction->Get(key, &data, exists); if (!s.ok()) { - INTERNAL_READ_ERROR(VERSION_EXISTS); + INTERNAL_READ_ERROR_UNTESTED(VERSION_EXISTS); return s; } if (!*exists) @@ -1753,7 +1877,7 @@ leveldb::Status IndexedDBBackingStore::FindKeyInIndex( int64 version; if (!DecodeVarInt(&slice, &version)) { - INTERNAL_READ_ERROR(FIND_KEY_IN_INDEX); + INTERNAL_READ_ERROR_UNTESTED(FIND_KEY_IN_INDEX); return InternalInconsistencyStatus(); } *found_encoded_primary_key = slice.as_string(); @@ -1799,13 +1923,13 @@ leveldb::Status IndexedDBBackingStore::GetPrimaryKeyViaIndex( &found_encoded_primary_key, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX); + INTERNAL_READ_ERROR_UNTESTED(GET_PRIMARY_KEY_VIA_INDEX); return s; } if (!found) return s; if (!found_encoded_primary_key.size()) { - INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX); + INTERNAL_READ_ERROR_UNTESTED(GET_PRIMARY_KEY_VIA_INDEX); return InvalidDBKeyStatus(); } @@ -1838,13 +1962,13 @@ leveldb::Status IndexedDBBackingStore::KeyExistsInIndex( &found_encoded_primary_key, exists); if (!s.ok()) { - INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX); + INTERNAL_READ_ERROR_UNTESTED(KEY_EXISTS_IN_INDEX); return s; } if (!*exists) return leveldb::Status::OK(); if (found_encoded_primary_key.empty()) { - INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX); + INTERNAL_READ_ERROR_UNTESTED(KEY_EXISTS_IN_INDEX); return InvalidDBKeyStatus(); } @@ -2087,7 +2211,7 @@ bool ObjectStoreKeyCursorImpl::LoadCurrentRow() { StringPiece slice(iterator_->Key()); ObjectStoreDataKey object_store_data_key; if (!ObjectStoreDataKey::Decode(&slice, &object_store_data_key)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2096,7 +2220,7 @@ bool ObjectStoreKeyCursorImpl::LoadCurrentRow() { int64 version; slice = StringPiece(iterator_->Value()); if (!DecodeVarInt(&slice, &version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2144,7 +2268,7 @@ bool ObjectStoreCursorImpl::LoadCurrentRow() { StringPiece key_slice(iterator_->Key()); ObjectStoreDataKey object_store_data_key; if (!ObjectStoreDataKey::Decode(&key_slice, &object_store_data_key)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2153,7 +2277,7 @@ bool ObjectStoreCursorImpl::LoadCurrentRow() { int64 version; StringPiece value_slice = StringPiece(iterator_->Value()); if (!DecodeVarInt(&value_slice, &version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2218,7 +2342,7 @@ bool IndexKeyCursorImpl::LoadCurrentRow() { StringPiece slice(iterator_->Key()); IndexDataKey index_data_key; if (!IndexDataKey::Decode(&slice, &index_data_key)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2228,12 +2352,12 @@ bool IndexKeyCursorImpl::LoadCurrentRow() { slice = StringPiece(iterator_->Value()); int64 index_data_version; if (!DecodeVarInt(&slice, &index_data_version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } if (!DecodeIDBKey(&slice, &primary_key_) || !slice.empty()) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2246,7 +2370,7 @@ bool IndexKeyCursorImpl::LoadCurrentRow() { bool found = false; leveldb::Status s = transaction_->Get(primary_leveldb_key, &result, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } if (!found) { @@ -2254,14 +2378,14 @@ bool IndexKeyCursorImpl::LoadCurrentRow() { return false; } if (!result.size()) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } int64 object_store_data_version; slice = StringPiece(result); if (!DecodeVarInt(&slice, &object_store_data_version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2326,7 +2450,7 @@ bool IndexCursorImpl::LoadCurrentRow() { StringPiece slice(iterator_->Key()); IndexDataKey index_data_key; if (!IndexDataKey::Decode(&slice, &index_data_key)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2336,11 +2460,11 @@ bool IndexCursorImpl::LoadCurrentRow() { slice = StringPiece(iterator_->Value()); int64 index_data_version; if (!DecodeVarInt(&slice, &index_data_version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } if (!DecodeIDBKey(&slice, &primary_key_)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2353,7 +2477,7 @@ bool IndexCursorImpl::LoadCurrentRow() { bool found = false; leveldb::Status s = transaction_->Get(primary_leveldb_key_, &result, &found); if (!s.ok()) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } if (!found) { @@ -2361,14 +2485,14 @@ bool IndexCursorImpl::LoadCurrentRow() { return false; } if (!result.size()) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } int64 object_store_data_version; slice = StringPiece(result); if (!DecodeVarInt(&slice, &object_store_data_version)) { - INTERNAL_READ_ERROR(LOAD_CURRENT_ROW); + INTERNAL_READ_ERROR_UNTESTED(LOAD_CURRENT_ROW); return false; } @@ -2639,7 +2763,7 @@ leveldb::Status IndexedDBBackingStore::Transaction::Commit() { leveldb::Status s = transaction_->Commit(); transaction_ = NULL; if (!s.ok()) - INTERNAL_WRITE_ERROR(TRANSACTION_COMMIT_METHOD); + INTERNAL_WRITE_ERROR_UNTESTED(TRANSACTION_COMMIT_METHOD); return s; } diff --git a/content/browser/indexed_db/indexed_db_backing_store.h b/content/browser/indexed_db/indexed_db_backing_store.h index e7aa21e16c01f1..83ce99f98df6a8 100644 --- a/content/browser/indexed_db/indexed_db_backing_store.h +++ b/content/browser/indexed_db/indexed_db_backing_store.h @@ -78,6 +78,8 @@ class CONTENT_EXPORT IndexedDBBackingStore const GURL& origin_url, LevelDBFactory* factory); + // Compact is public for testing. + virtual void Compact(); virtual std::vector GetDatabaseNames(); virtual leveldb::Status GetIDBDatabaseMetaData( const base::string16& name, @@ -94,6 +96,12 @@ class CONTENT_EXPORT IndexedDBBackingStore int64 int_version); virtual leveldb::Status DeleteDatabase(const base::string16& name); + // Assumes caller has already closed the backing store. + static leveldb::Status DestroyBackingStore(const base::FilePath& path_base, + const GURL& origin_url); + static bool RecordCorruptionInfo(const base::FilePath& path_base, + const GURL& origin_url, + const std::string& message); leveldb::Status GetObjectStores( int64 database_id, IndexedDBDatabaseMetadata::ObjectStoreMap* map) WARN_UNUSED_RESULT; @@ -325,6 +333,9 @@ class CONTENT_EXPORT IndexedDBBackingStore const GURL& origin_url, scoped_ptr db, scoped_ptr comparator); + static bool ReadCorruptionInfo(const base::FilePath& path_base, + const GURL& origin_url, + std::string& message); leveldb::Status FindKeyInIndex( IndexedDBBackingStore::Transaction* transaction, diff --git a/content/browser/indexed_db/indexed_db_browsertest.cc b/content/browser/indexed_db/indexed_db_browsertest.cc index 478a07b8237897..79ed82e1576bb3 100644 --- a/content/browser/indexed_db/indexed_db_browsertest.cc +++ b/content/browser/indexed_db/indexed_db_browsertest.cc @@ -5,9 +5,11 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/file_util.h" +#include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" +#include "base/platform_file.h" #include "base/strings/utf_string_conversions.h" #include "base/test/thread_test_helper.h" #include "content/browser/browser_main_loop.h" @@ -24,6 +26,10 @@ #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" +#include "net/base/net_errors.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" #include "webkit/browser/database/database_util.h" #include "webkit/browser/quota/quota_manager.h" @@ -356,6 +362,140 @@ IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CanDeleteWhenOverQuotaTest) { SimpleTest(GetTestUrl("indexeddb", "delete_over_quota.html")); } +namespace { + +static void CompactIndexedDBBackingStore( + scoped_refptr context, + const GURL& origin_url) { + IndexedDBFactory* factory = context->GetIDBFactory(); + + std::pair range = + factory->GetOpenDatabasesForOrigin(origin_url); + + if (range.first == range.second) // If no open db's for this origin + return; + + // Compact the first db's backing store since all the db's are in the same + // backing store. + IndexedDBDatabase* db = range.first->second; + IndexedDBBackingStore* backing_store = db->backing_store(); + backing_store->Compact(); +} + +static void CorruptIndexedDBDatabase( + IndexedDBContextImpl* context, + const GURL& origin_url, + base::WaitableEvent* signal_when_finished) { + + CompactIndexedDBBackingStore(context, origin_url); + + int numFiles = 0; + int numErrors = 0; + base::FilePath idb_data_path = context->GetFilePath(origin_url); + const bool recursive = false; + base::FileEnumerator enumerator( + idb_data_path, recursive, base::FileEnumerator::FILES); + for (base::FilePath idb_file = enumerator.Next(); !idb_file.empty(); + idb_file = enumerator.Next()) { + int64 size(0); + GetFileSize(idb_file, &size); + + if (idb_file.Extension() == FILE_PATH_LITERAL(".ldb")) { + numFiles++; + base::PlatformFile f = base::CreatePlatformFile( + idb_file, + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_OPEN_TRUNCATED, + NULL, + NULL); + if (f) { + // Was opened truncated, expand back to the original + // file size and fill with zeros (corrupting the file). + base::TruncatePlatformFile(f, size); + if (!base::ClosePlatformFile(f)) + numErrors++; + } else { + numErrors++; + } + } + } + + VLOG(0) << "There were " << numFiles << " in " << idb_data_path.value() + << " with " << numErrors << " errors"; + signal_when_finished->Signal(); +} + +const std::string s_corrupt_db_test_prefix = "/corrupt/test/"; + +static scoped_ptr CorruptDBRequestHandler( + IndexedDBContextImpl* context, + const GURL& origin_url, + const std::string& path, + const net::test_server::HttpRequest& request) { + + std::string request_path; + if (path.find(s_corrupt_db_test_prefix) != std::string::npos) + request_path = request.relative_url.substr(s_corrupt_db_test_prefix.size()); + else + return scoped_ptr(); + + // Remove the query string if present. + std::string request_query; + size_t query_pos = request_path.find('?'); + if (query_pos != std::string::npos) { + request_query = request_path.substr(query_pos + 1); + request_path = request_path.substr(0, query_pos); + } + + if (request_path == "corruptdb" && !request_query.empty()) { + VLOG(0) << "Requested to corrupt IndexedDB: " << request_query; + base::WaitableEvent signal_when_finished(false, false); + context->TaskRunner()->PostTask(FROM_HERE, + base::Bind(&CorruptIndexedDBDatabase, + base::ConstRef(context), + origin_url, + &signal_when_finished)); + signal_when_finished.Wait(); + + scoped_ptr http_response( + new net::test_server::BasicHttpResponse); + http_response->set_code(net::HTTP_OK); + return http_response.PassAs(); + } + + // A request for a test resource + base::FilePath resourcePath = + content::GetTestFilePath("indexeddb", request_path.c_str()); + scoped_ptr http_response( + new net::test_server::BasicHttpResponse); + http_response->set_code(net::HTTP_OK); + std::string file_contents; + if (!base::ReadFileToString(resourcePath, &file_contents)) + return scoped_ptr(); + http_response->set_content(file_contents); + return http_response.PassAs(); +} + +} // namespace + +IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CorruptedOpenDatabase) { + ASSERT_TRUE(embedded_test_server()->Started() || + embedded_test_server()->InitializeAndWaitUntilReady()); + const GURL& origin_url = embedded_test_server()->base_url(); + embedded_test_server()->RegisterRequestHandler( + base::Bind(&CorruptDBRequestHandler, + base::ConstRef(GetContext()), + origin_url, + s_corrupt_db_test_prefix)); + + std::string test_file = + s_corrupt_db_test_prefix + "corrupted_open_db_detection.html"; + SimpleTest(embedded_test_server()->GetURL(test_file)); + + test_file = s_corrupt_db_test_prefix + "corrupted_open_db_recovery.html"; + SimpleTest(embedded_test_server()->GetURL(test_file)); +} + IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteCompactsBackingStore) { const GURL test_url = GetTestUrl("indexeddb", "delete_compact.html"); SimpleTest(GURL(test_url.spec() + "#fill")); @@ -449,6 +589,7 @@ IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ForceCloseEventTest) { base::string16 expected_title16(ASCIIToUTF16("connection closed")); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); + title_watcher.AlsoWaitForTitle(ASCIIToUTF16("connection closed with error")); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } diff --git a/content/browser/indexed_db/indexed_db_database.cc b/content/browser/indexed_db/indexed_db_database.cc index 839d7af3fc1971..c97048b5612094 100644 --- a/content/browser/indexed_db/indexed_db_database.cc +++ b/content/browser/indexed_db/indexed_db_database.cc @@ -14,6 +14,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/indexed_db/indexed_db_connection.h" +#include "content/browser/indexed_db/indexed_db_context_impl.h" #include "content/browser/indexed_db/indexed_db_cursor.h" #include "content/browser/indexed_db/indexed_db_factory.h" #include "content/browser/indexed_db/indexed_db_index_writer.h" @@ -294,17 +295,22 @@ void IndexedDBDatabase::CreateObjectStoreOperation( const IndexedDBObjectStoreMetadata& object_store_metadata, IndexedDBTransaction* transaction) { IDB_TRACE("IndexedDBDatabase::CreateObjectStoreOperation"); - if (!backing_store_->CreateObjectStore( - transaction->BackingStoreTransaction(), - transaction->database()->id(), - object_store_metadata.id, - object_store_metadata.name, - object_store_metadata.key_path, - object_store_metadata.auto_increment).ok()) { - transaction->Abort(IndexedDBDatabaseError( + leveldb::Status s = + backing_store_->CreateObjectStore(transaction->BackingStoreTransaction(), + transaction->database()->id(), + object_store_metadata.id, + object_store_metadata.name, + object_store_metadata.key_path, + object_store_metadata.auto_increment); + if (!s.ok()) { + IndexedDBDatabaseError error( blink::WebIDBDatabaseExceptionUnknownError, ASCIIToUTF16("Internal error creating object store '") + - object_store_metadata.name + ASCIIToUTF16("'."))); + object_store_metadata.name + ASCIIToUTF16("'.")); + transaction->Abort(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } } @@ -436,8 +442,12 @@ void IndexedDBDatabase::DeleteIndexOperation( base::string16 error_string = ASCIIToUTF16("Internal error deleting index '") + index_metadata.name + ASCIIToUTF16("'."); - transaction->Abort(IndexedDBDatabaseError( - blink::WebIDBDatabaseExceptionUnknownError, error_string)); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + error_string); + transaction->Abort(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); } } @@ -569,9 +579,13 @@ void IndexedDBDatabase::GetOperation( *key, &value); if (!s.ok()) { - callbacks->OnError( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error in GetRecord.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error in GetRecord."); + callbacks->OnError(error); + + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } @@ -599,9 +613,12 @@ void IndexedDBDatabase::GetOperation( *key, &primary_key); if (!s.ok()) { - callbacks->OnError( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error in GetPrimaryKeyViaIndex.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error in GetPrimaryKeyViaIndex."); + callbacks->OnError(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } if (!primary_key) { @@ -622,9 +639,12 @@ void IndexedDBDatabase::GetOperation( *primary_key, &value); if (!s.ok()) { - callbacks->OnError( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error in GetRecord.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error in GetRecord."); + callbacks->OnError(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } @@ -761,9 +781,12 @@ void IndexedDBDatabase::PutOperation(scoped_ptr params, &record_identifier, &found); if (!s.ok()) { - params->callbacks->OnError( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error checking key existence.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error checking key existence."); + params->callbacks->OnError(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } if (found) { @@ -809,9 +832,13 @@ void IndexedDBDatabase::PutOperation(scoped_ptr params, params->value, &record_identifier); if (!s.ok()) { - params->callbacks->OnError(IndexedDBDatabaseError( + IndexedDBDatabaseError error( blink::WebIDBDatabaseExceptionUnknownError, - "Internal error: backing store error performing put/add.")); + "Internal error: backing store error performing put/add."); + params->callbacks->OnError(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } @@ -834,9 +861,12 @@ void IndexedDBDatabase::PutOperation(scoped_ptr params, *key, !key_was_generated); if (!s.ok()) { - params->callbacks->OnError( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error updating key generator.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error updating key generator."); + params->callbacks->OnError(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } } @@ -866,9 +896,12 @@ void IndexedDBDatabase::SetIndexKeys(int64 transaction_id, &record_identifier, &found); if (!s.ok()) { - transaction->Abort( - IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError, - "Internal error setting index keys.")); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + "Internal error setting index keys."); + transaction->Abort(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); return; } if (!found) { @@ -1206,8 +1239,12 @@ void IndexedDBDatabase::DeleteObjectStoreOperation( base::string16 error_string = ASCIIToUTF16("Internal error deleting object store '") + object_store_metadata.name + ASCIIToUTF16("'."); - transaction->Abort(IndexedDBDatabaseError( - blink::WebIDBDatabaseExceptionUnknownError, error_string)); + IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError, + error_string); + transaction->Abort(error); + if (s.IsCorruption()) + factory_->HandleBackingStoreCorruption(backing_store_->origin_url(), + error); } } diff --git a/content/browser/indexed_db/indexed_db_factory.cc b/content/browser/indexed_db/indexed_db_factory.cc index 86af571319c656..113060324000d2 100644 --- a/content/browser/indexed_db/indexed_db_factory.cc +++ b/content/browser/indexed_db/indexed_db_factory.cc @@ -9,6 +9,7 @@ #include "base/time/time.h" #include "content/browser/indexed_db/indexed_db_backing_store.h" #include "content/browser/indexed_db/indexed_db_context_impl.h" +#include "content/browser/indexed_db/indexed_db_database_error.h" #include "content/browser/indexed_db/indexed_db_tracing.h" #include "content/browser/indexed_db/indexed_db_transaction_coordinator.h" #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h" @@ -234,6 +235,24 @@ void IndexedDBFactory::HandleBackingStoreFailure(const GURL& origin_url) { IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE); } +void IndexedDBFactory::HandleBackingStoreCorruption( + const GURL& origin_url, + const IndexedDBDatabaseError& error) { + // Make a copy of origin_url as this is likely a reference to a member of a + // backing store which this function will be deleting. + GURL saved_origin_url(origin_url); + DCHECK(context_); + base::FilePath path_base = context_->data_path(); + IndexedDBBackingStore::RecordCorruptionInfo( + path_base, saved_origin_url, base::UTF16ToUTF8(error.message())); + HandleBackingStoreFailure(saved_origin_url); + // Note: DestroyBackingStore only deletes LevelDB files, leaving all others, + // so our corruption info file will remain. + if (!IndexedDBBackingStore::DestroyBackingStore(path_base, saved_origin_url) + .ok()) + DLOG(ERROR) << "Unable to delete backing store"; +} + bool IndexedDBFactory::IsDatabaseOpen(const GURL& origin_url, const base::string16& name) const { diff --git a/content/browser/indexed_db/indexed_db_factory.h b/content/browser/indexed_db/indexed_db_factory.h index af2ba5427fdc67..de2e1dd75aae85 100644 --- a/content/browser/indexed_db/indexed_db_factory.h +++ b/content/browser/indexed_db/indexed_db_factory.h @@ -50,12 +50,12 @@ class CONTENT_EXPORT IndexedDBFactory const base::FilePath& data_directory); void HandleBackingStoreFailure(const GURL& origin_url); + void HandleBackingStoreCorruption(const GURL& origin_url, + const IndexedDBDatabaseError& error); std::pair GetOpenDatabasesForOrigin( const GURL& origin_url) const; - // Called by IndexedDBContext after all connections are closed, to - // ensure the backing store closed immediately. void ForceClose(const GURL& origin_url); // Called by the IndexedDBContext destructor so the factory can do cleanup. diff --git a/content/browser/indexed_db/leveldb/leveldb_database.cc b/content/browser/indexed_db/leveldb/leveldb_database.cc index 3c27340e18da16..96218b3d62b645 100644 --- a/content/browser/indexed_db/leveldb/leveldb_database.cc +++ b/content/browser/indexed_db/leveldb/leveldb_database.cc @@ -459,4 +459,6 @@ void LevelDBDatabase::Compact(const base::StringPiece& start, db_->CompactRange(&start_slice, &stop_slice); } +void LevelDBDatabase::CompactAll() { db_->CompactRange(NULL, NULL); } + } // namespace content diff --git a/content/browser/indexed_db/leveldb/leveldb_database.h b/content/browser/indexed_db/leveldb/leveldb_database.h index 27ffcd54db72f8..e4f6af95c306b3 100644 --- a/content/browser/indexed_db/leveldb/leveldb_database.h +++ b/content/browser/indexed_db/leveldb/leveldb_database.h @@ -87,6 +87,7 @@ class CONTENT_EXPORT LevelDBDatabase { scoped_ptr CreateIterator(const LevelDBSnapshot* = 0); const LevelDBComparator* Comparator() const; void Compact(const base::StringPiece& start, const base::StringPiece& stop); + void CompactAll(); protected: LevelDBDatabase(); diff --git a/content/test/data/indexeddb/common.js b/content/test/data/indexeddb/common.js index 2e12d4447e2aec..1410a5af055356 100644 --- a/content/test/data/indexeddb/common.js +++ b/content/test/data/indexeddb/common.js @@ -147,3 +147,9 @@ function indexedDBTest(upgradeCallback, optionalOpenCallback) { openRequest.onsuccess = optionalOpenCallback; }; } + +if (typeof String.prototype.startsWith !== 'function') { + String.prototype.startsWith = function (str) { + return this.indexOf(str) === 0; + }; +} diff --git a/content/test/data/indexeddb/corrupted_open_db_detection.html b/content/test/data/indexeddb/corrupted_open_db_detection.html new file mode 100644 index 00000000000000..198aed551b8562 --- /dev/null +++ b/content/test/data/indexeddb/corrupted_open_db_detection.html @@ -0,0 +1,114 @@ + + + + +IDB test that db's corrupted while open are properly handled Part 1 / 2 + + + + +
Starting...
+ + diff --git a/content/test/data/indexeddb/corrupted_open_db_recovery.html b/content/test/data/indexeddb/corrupted_open_db_recovery.html new file mode 100644 index 00000000000000..b1f7deed5aa838 --- /dev/null +++ b/content/test/data/indexeddb/corrupted_open_db_recovery.html @@ -0,0 +1,46 @@ + + + + +IDB test that db's corrupted while open are properly handled Part 2 / 2 + + + + +
Starting...
+ + diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 444e448ebd0acc..63bc2248195d4f 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -34210,6 +34210,9 @@ other types of suffix sets. An open attempt failed with an I/O error that doesn't necessitate a recovery attempt. + + The corrupted open database was deleted. +