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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h" 6 7 #include "base/bind.h" 8 #include "base/files/file_util.h" 9 #include "base/location.h" 10 #include "base/logging.h" 11 #include "base/metrics/histogram.h" 12 #include "base/metrics/sparse_histogram.h" 13 #include "base/sequenced_task_runner.h" 14 #include "base/threading/thread_restrictions.h" 15 #include "chrome/browser/chromeos/drive/drive.pb.h" 16 #include "chrome/browser/drive/drive_api_util.h" 17 #include "third_party/leveldatabase/env_chromium.h" 18 #include "third_party/leveldatabase/src/include/leveldb/db.h" 19 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 20 21 namespace drive { 22 namespace internal { 23 24 namespace { 25 26 // Enum to describe DB initialization status. 27 enum DBInitStatus { 28 DB_INIT_SUCCESS, 29 DB_INIT_NOT_FOUND, 30 DB_INIT_CORRUPTION, 31 DB_INIT_IO_ERROR, 32 DB_INIT_FAILED, 33 DB_INIT_INCOMPATIBLE, 34 DB_INIT_BROKEN, 35 DB_INIT_OPENED_EXISTING_DB, 36 DB_INIT_CREATED_NEW_DB, 37 DB_INIT_REPLACED_EXISTING_DB_WITH_NEW_DB, 38 DB_INIT_MAX_VALUE, 39 }; 40 41 // Enum to describe DB validity check failure reason. 42 enum CheckValidityFailureReason { 43 CHECK_VALIDITY_FAILURE_INVALID_HEADER, 44 CHECK_VALIDITY_FAILURE_BROKEN_ID_ENTRY, 45 CHECK_VALIDITY_FAILURE_BROKEN_ENTRY, 46 CHECK_VALIDITY_FAILURE_INVALID_LOCAL_ID, 47 CHECK_VALIDITY_FAILURE_INVALID_PARENT_ID, 48 CHECK_VALIDITY_FAILURE_BROKEN_CHILD_MAP, 49 CHECK_VALIDITY_FAILURE_CHILD_ENTRY_COUNT_MISMATCH, 50 CHECK_VALIDITY_FAILURE_ITERATOR_ERROR, 51 CHECK_VALIDITY_FAILURE_MAX_VALUE, 52 }; 53 54 // The name of the DB which stores the metadata. 55 const base::FilePath::CharType kResourceMapDBName[] = 56 FILE_PATH_LITERAL("resource_metadata_resource_map.db"); 57 58 // The name of the DB which couldn't be opened, but is preserved just in case. 59 const base::FilePath::CharType kPreservedResourceMapDBName[] = 60 FILE_PATH_LITERAL("resource_metadata_preserved_resource_map.db"); 61 62 // The name of the DB which couldn't be opened, and was replaced with a new one. 63 const base::FilePath::CharType kTrashedResourceMapDBName[] = 64 FILE_PATH_LITERAL("resource_metadata_trashed_resource_map.db"); 65 66 // Meant to be a character which never happen to be in real IDs. 67 const char kDBKeyDelimeter = '\0'; 68 69 // String used as a suffix of a key for a cache entry. 70 const char kCacheEntryKeySuffix[] = "CACHE"; 71 72 // String used as a prefix of a key for a resource-ID-to-local-ID entry. 73 const char kIdEntryKeyPrefix[] = "ID"; 74 75 // Returns a string to be used as the key for the header. 76 std::string GetHeaderDBKey() { 77 std::string key; 78 key.push_back(kDBKeyDelimeter); 79 key.append("HEADER"); 80 return key; 81 } 82 83 // Returns true if |key| is a key for a child entry. 84 bool IsChildEntryKey(const leveldb::Slice& key) { 85 return !key.empty() && key[key.size() - 1] == kDBKeyDelimeter; 86 } 87 88 // Returns true if |key| is a key for a cache entry. 89 bool IsCacheEntryKey(const leveldb::Slice& key) { 90 // A cache entry key should end with |kDBKeyDelimeter + kCacheEntryKeySuffix|. 91 const leveldb::Slice expected_suffix(kCacheEntryKeySuffix, 92 arraysize(kCacheEntryKeySuffix) - 1); 93 if (key.size() < 1 + expected_suffix.size() || 94 key[key.size() - expected_suffix.size() - 1] != kDBKeyDelimeter) 95 return false; 96 97 const leveldb::Slice key_substring( 98 key.data() + key.size() - expected_suffix.size(), expected_suffix.size()); 99 return key_substring.compare(expected_suffix) == 0; 100 } 101 102 // Returns ID extracted from a cache entry key. 103 std::string GetIdFromCacheEntryKey(const leveldb::Slice& key) { 104 DCHECK(IsCacheEntryKey(key)); 105 // Drop the suffix |kDBKeyDelimeter + kCacheEntryKeySuffix| from the key. 106 const size_t kSuffixLength = arraysize(kCacheEntryKeySuffix) - 1; 107 const int id_length = key.size() - 1 - kSuffixLength; 108 return std::string(key.data(), id_length); 109 } 110 111 // Returns a string to be used as a key for a resource-ID-to-local-ID entry. 112 std::string GetIdEntryKey(const std::string& resource_id) { 113 std::string key; 114 key.push_back(kDBKeyDelimeter); 115 key.append(kIdEntryKeyPrefix); 116 key.push_back(kDBKeyDelimeter); 117 key.append(resource_id); 118 return key; 119 } 120 121 // Returns true if |key| is a key for a resource-ID-to-local-ID entry. 122 bool IsIdEntryKey(const leveldb::Slice& key) { 123 // A resource-ID-to-local-ID entry key should start with 124 // |kDBKeyDelimeter + kIdEntryKeyPrefix + kDBKeyDelimeter|. 125 const leveldb::Slice expected_prefix(kIdEntryKeyPrefix, 126 arraysize(kIdEntryKeyPrefix) - 1); 127 if (key.size() < 2 + expected_prefix.size()) 128 return false; 129 const leveldb::Slice key_substring(key.data() + 1, expected_prefix.size()); 130 return key[0] == kDBKeyDelimeter && 131 key_substring.compare(expected_prefix) == 0 && 132 key[expected_prefix.size() + 1] == kDBKeyDelimeter; 133 } 134 135 // Returns the resource ID extracted from a resource-ID-to-local-ID entry key. 136 std::string GetResourceIdFromIdEntryKey(const leveldb::Slice& key) { 137 DCHECK(IsIdEntryKey(key)); 138 // Drop the prefix |kDBKeyDelimeter + kIdEntryKeyPrefix + kDBKeyDelimeter| 139 // from the key. 140 const size_t kPrefixLength = arraysize(kIdEntryKeyPrefix) - 1; 141 const int offset = kPrefixLength + 2; 142 return std::string(key.data() + offset, key.size() - offset); 143 } 144 145 // Converts leveldb::Status to DBInitStatus. 146 DBInitStatus LevelDBStatusToDBInitStatus(const leveldb::Status& status) { 147 if (status.ok()) 148 return DB_INIT_SUCCESS; 149 if (status.IsNotFound()) 150 return DB_INIT_NOT_FOUND; 151 if (status.IsCorruption()) 152 return DB_INIT_CORRUPTION; 153 if (status.IsIOError()) 154 return DB_INIT_IO_ERROR; 155 return DB_INIT_FAILED; 156 } 157 158 // Converts leveldb::Status to FileError. 159 FileError LevelDBStatusToFileError(const leveldb::Status& status) { 160 if (status.ok()) 161 return FILE_ERROR_OK; 162 if (status.IsNotFound()) 163 return FILE_ERROR_NOT_FOUND; 164 if (leveldb_env::IndicatesDiskFull(status)) 165 return FILE_ERROR_NO_LOCAL_SPACE; 166 return FILE_ERROR_FAILED; 167 } 168 169 ResourceMetadataHeader GetDefaultHeaderEntry() { 170 ResourceMetadataHeader header; 171 header.set_version(ResourceMetadataStorage::kDBVersion); 172 return header; 173 } 174 175 bool MoveIfPossible(const base::FilePath& from, const base::FilePath& to) { 176 return !base::PathExists(from) || base::Move(from, to); 177 } 178 179 void RecordCheckValidityFailure(CheckValidityFailureReason reason) { 180 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBValidityCheckFailureReason", 181 reason, 182 CHECK_VALIDITY_FAILURE_MAX_VALUE); 183 } 184 185 } // namespace 186 187 ResourceMetadataStorage::Iterator::Iterator(scoped_ptr<leveldb::Iterator> it) 188 : it_(it.Pass()) { 189 base::ThreadRestrictions::AssertIOAllowed(); 190 DCHECK(it_); 191 192 // Skip the header entry. 193 // Note: The header entry comes before all other entries because its key 194 // starts with kDBKeyDelimeter. (i.e. '\0') 195 it_->Seek(leveldb::Slice(GetHeaderDBKey())); 196 197 Advance(); 198 } 199 200 ResourceMetadataStorage::Iterator::~Iterator() { 201 base::ThreadRestrictions::AssertIOAllowed(); 202 } 203 204 bool ResourceMetadataStorage::Iterator::IsAtEnd() const { 205 base::ThreadRestrictions::AssertIOAllowed(); 206 return !it_->Valid(); 207 } 208 209 std::string ResourceMetadataStorage::Iterator::GetID() const { 210 return it_->key().ToString(); 211 } 212 213 const ResourceEntry& ResourceMetadataStorage::Iterator::GetValue() const { 214 base::ThreadRestrictions::AssertIOAllowed(); 215 DCHECK(!IsAtEnd()); 216 return entry_; 217 } 218 219 void ResourceMetadataStorage::Iterator::Advance() { 220 base::ThreadRestrictions::AssertIOAllowed(); 221 DCHECK(!IsAtEnd()); 222 223 for (it_->Next() ; it_->Valid(); it_->Next()) { 224 if (!IsChildEntryKey(it_->key()) && 225 !IsIdEntryKey(it_->key()) && 226 entry_.ParseFromArray(it_->value().data(), it_->value().size())) { 227 break; 228 } 229 } 230 } 231 232 bool ResourceMetadataStorage::Iterator::HasError() const { 233 base::ThreadRestrictions::AssertIOAllowed(); 234 return !it_->status().ok(); 235 } 236 237 // static 238 bool ResourceMetadataStorage::UpgradeOldDB( 239 const base::FilePath& directory_path) { 240 base::ThreadRestrictions::AssertIOAllowed(); 241 COMPILE_ASSERT( 242 kDBVersion == 13, 243 db_version_and_this_function_should_be_updated_at_the_same_time); 244 245 const base::FilePath resource_map_path = 246 directory_path.Append(kResourceMapDBName); 247 const base::FilePath preserved_resource_map_path = 248 directory_path.Append(kPreservedResourceMapDBName); 249 250 if (base::PathExists(preserved_resource_map_path)) { 251 // Preserved DB is found. The previous attempt to create a new DB should not 252 // be successful. Discard the imperfect new DB and restore the old DB. 253 if (!base::DeleteFile(resource_map_path, false /* recursive */) || 254 !base::Move(preserved_resource_map_path, resource_map_path)) 255 return false; 256 } 257 258 if (!base::PathExists(resource_map_path)) 259 return false; 260 261 // Open DB. 262 leveldb::DB* db = NULL; 263 leveldb::Options options; 264 options.max_open_files = 0; // Use minimum. 265 options.create_if_missing = false; 266 if (!leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db).ok()) 267 return false; 268 scoped_ptr<leveldb::DB> resource_map(db); 269 270 // Check DB version. 271 std::string serialized_header; 272 ResourceMetadataHeader header; 273 if (!resource_map->Get(leveldb::ReadOptions(), 274 leveldb::Slice(GetHeaderDBKey()), 275 &serialized_header).ok() || 276 !header.ParseFromString(serialized_header)) 277 return false; 278 UMA_HISTOGRAM_SPARSE_SLOWLY("Drive.MetadataDBVersionBeforeUpgradeCheck", 279 header.version()); 280 281 if (header.version() == kDBVersion) { 282 // Before r272134, UpgradeOldDB() was not deleting unused ID entries. 283 // Delete unused ID entries to fix crbug.com/374648. 284 std::set<std::string> used_ids; 285 286 scoped_ptr<leveldb::Iterator> it( 287 resource_map->NewIterator(leveldb::ReadOptions())); 288 it->Seek(leveldb::Slice(GetHeaderDBKey())); 289 it->Next(); 290 for (; it->Valid(); it->Next()) { 291 if (IsCacheEntryKey(it->key())) { 292 used_ids.insert(GetIdFromCacheEntryKey(it->key())); 293 } else if (!IsChildEntryKey(it->key()) && !IsIdEntryKey(it->key())) { 294 used_ids.insert(it->key().ToString()); 295 } 296 } 297 if (!it->status().ok()) 298 return false; 299 300 leveldb::WriteBatch batch; 301 for (it->SeekToFirst(); it->Valid(); it->Next()) { 302 if (IsIdEntryKey(it->key()) && !used_ids.count(it->value().ToString())) 303 batch.Delete(it->key()); 304 } 305 if (!it->status().ok()) 306 return false; 307 308 return resource_map->Write(leveldb::WriteOptions(), &batch).ok(); 309 } else if (header.version() < 6) { // Too old, nothing can be done. 310 return false; 311 } else if (header.version() < 11) { // Cache entries can be reused. 312 leveldb::ReadOptions options; 313 options.verify_checksums = true; 314 scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options)); 315 316 leveldb::WriteBatch batch; 317 // First, remove all entries. 318 for (it->SeekToFirst(); it->Valid(); it->Next()) 319 batch.Delete(it->key()); 320 321 // Put ID entries and cache entries. 322 for (it->SeekToFirst(); it->Valid(); it->Next()) { 323 if (IsCacheEntryKey(it->key())) { 324 FileCacheEntry cache_entry; 325 if (!cache_entry.ParseFromArray(it->value().data(), it->value().size())) 326 return false; 327 328 // The resource ID might be in old WAPI format. We need to canonicalize 329 // to the format of API service currently in use. 330 const std::string& id = GetIdFromCacheEntryKey(it->key()); 331 const std::string& id_new = util::CanonicalizeResourceId(id); 332 333 // Before v11, resource ID was directly used as local ID. Such entries 334 // can be migrated by adding an identity ID mapping. 335 batch.Put(GetIdEntryKey(id_new), id_new); 336 337 // Put cache state into a ResourceEntry. 338 ResourceEntry entry; 339 entry.set_local_id(id_new); 340 entry.set_resource_id(id_new); 341 *entry.mutable_file_specific_info()->mutable_cache_state() = 342 cache_entry; 343 344 std::string serialized_entry; 345 if (!entry.SerializeToString(&serialized_entry)) { 346 DLOG(ERROR) << "Failed to serialize the entry: " << id; 347 return false; 348 } 349 batch.Put(id_new, serialized_entry); 350 } 351 } 352 if (!it->status().ok()) 353 return false; 354 355 // Put header with the latest version number. 356 std::string serialized_header; 357 if (!GetDefaultHeaderEntry().SerializeToString(&serialized_header)) 358 return false; 359 batch.Put(GetHeaderDBKey(), serialized_header); 360 361 return resource_map->Write(leveldb::WriteOptions(), &batch).ok(); 362 } else if (header.version() < 12) { // Cache and ID map entries are reusable. 363 leveldb::ReadOptions options; 364 options.verify_checksums = true; 365 scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options)); 366 367 // First, get the set of local IDs associated with cache entries. 368 std::set<std::string> cached_entry_ids; 369 for (it->SeekToFirst(); it->Valid(); it->Next()) { 370 if (IsCacheEntryKey(it->key())) 371 cached_entry_ids.insert(GetIdFromCacheEntryKey(it->key())); 372 } 373 if (!it->status().ok()) 374 return false; 375 376 // Remove all entries except used ID entries. 377 leveldb::WriteBatch batch; 378 std::map<std::string, std::string> local_id_to_resource_id; 379 for (it->SeekToFirst(); it->Valid(); it->Next()) { 380 const bool is_used_id = IsIdEntryKey(it->key()) && 381 cached_entry_ids.count(it->value().ToString()); 382 if (is_used_id) { 383 local_id_to_resource_id[it->value().ToString()] = 384 GetResourceIdFromIdEntryKey(it->key()); 385 } else { 386 batch.Delete(it->key()); 387 } 388 } 389 if (!it->status().ok()) 390 return false; 391 392 // Put cache entries. 393 for (it->SeekToFirst(); it->Valid(); it->Next()) { 394 if (IsCacheEntryKey(it->key())) { 395 const std::string& id = GetIdFromCacheEntryKey(it->key()); 396 397 std::map<std::string, std::string>::const_iterator iter_resource_id = 398 local_id_to_resource_id.find(id); 399 if (iter_resource_id == local_id_to_resource_id.end()) 400 continue; 401 402 FileCacheEntry cache_entry; 403 if (!cache_entry.ParseFromArray(it->value().data(), it->value().size())) 404 return false; 405 406 // Put cache state into a ResourceEntry. 407 ResourceEntry entry; 408 entry.set_local_id(id); 409 entry.set_resource_id(iter_resource_id->second); 410 *entry.mutable_file_specific_info()->mutable_cache_state() = 411 cache_entry; 412 413 std::string serialized_entry; 414 if (!entry.SerializeToString(&serialized_entry)) { 415 DLOG(ERROR) << "Failed to serialize the entry: " << id; 416 return false; 417 } 418 batch.Put(id, serialized_entry); 419 } 420 } 421 if (!it->status().ok()) 422 return false; 423 424 // Put header with the latest version number. 425 std::string serialized_header; 426 if (!GetDefaultHeaderEntry().SerializeToString(&serialized_header)) 427 return false; 428 batch.Put(GetHeaderDBKey(), serialized_header); 429 430 return resource_map->Write(leveldb::WriteOptions(), &batch).ok(); 431 } else if (header.version() < 13) { // Reuse all entries. 432 leveldb::ReadOptions options; 433 options.verify_checksums = true; 434 scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options)); 435 436 // First, get local ID to resource ID map. 437 std::map<std::string, std::string> local_id_to_resource_id; 438 for (it->SeekToFirst(); it->Valid(); it->Next()) { 439 if (IsIdEntryKey(it->key())) { 440 local_id_to_resource_id[it->value().ToString()] = 441 GetResourceIdFromIdEntryKey(it->key()); 442 } 443 } 444 if (!it->status().ok()) 445 return false; 446 447 leveldb::WriteBatch batch; 448 // Merge cache entries to ResourceEntry. 449 for (it->SeekToFirst(); it->Valid(); it->Next()) { 450 if (IsCacheEntryKey(it->key())) { 451 const std::string& id = GetIdFromCacheEntryKey(it->key()); 452 453 FileCacheEntry cache_entry; 454 if (!cache_entry.ParseFromArray(it->value().data(), it->value().size())) 455 return false; 456 457 std::string serialized_entry; 458 leveldb::Status status = resource_map->Get(options, 459 leveldb::Slice(id), 460 &serialized_entry); 461 462 std::map<std::string, std::string>::const_iterator iter_resource_id = 463 local_id_to_resource_id.find(id); 464 465 // No need to keep cache-only entries without resource ID. 466 if (status.IsNotFound() && 467 iter_resource_id == local_id_to_resource_id.end()) 468 continue; 469 470 ResourceEntry entry; 471 if (status.ok()) { 472 if (!entry.ParseFromString(serialized_entry)) 473 return false; 474 } else if (status.IsNotFound()) { 475 entry.set_local_id(id); 476 entry.set_resource_id(iter_resource_id->second); 477 } else { 478 DLOG(ERROR) << "Failed to get the entry: " << id; 479 return false; 480 } 481 *entry.mutable_file_specific_info()->mutable_cache_state() = 482 cache_entry; 483 484 if (!entry.SerializeToString(&serialized_entry)) { 485 DLOG(ERROR) << "Failed to serialize the entry: " << id; 486 return false; 487 } 488 batch.Delete(it->key()); 489 batch.Put(id, serialized_entry); 490 } 491 } 492 if (!it->status().ok()) 493 return false; 494 495 // Put header with the latest version number. 496 header.set_version(ResourceMetadataStorage::kDBVersion); 497 std::string serialized_header; 498 if (!header.SerializeToString(&serialized_header)) 499 return false; 500 batch.Put(GetHeaderDBKey(), serialized_header); 501 502 return resource_map->Write(leveldb::WriteOptions(), &batch).ok(); 503 } 504 505 LOG(WARNING) << "Unexpected DB version: " << header.version(); 506 return false; 507 } 508 509 ResourceMetadataStorage::ResourceMetadataStorage( 510 const base::FilePath& directory_path, 511 base::SequencedTaskRunner* blocking_task_runner) 512 : directory_path_(directory_path), 513 cache_file_scan_is_needed_(true), 514 blocking_task_runner_(blocking_task_runner) { 515 } 516 517 void ResourceMetadataStorage::Destroy() { 518 blocking_task_runner_->PostTask( 519 FROM_HERE, 520 base::Bind(&ResourceMetadataStorage::DestroyOnBlockingPool, 521 base::Unretained(this))); 522 } 523 524 bool ResourceMetadataStorage::Initialize() { 525 base::ThreadRestrictions::AssertIOAllowed(); 526 527 resource_map_.reset(); 528 529 const base::FilePath resource_map_path = 530 directory_path_.Append(kResourceMapDBName); 531 const base::FilePath preserved_resource_map_path = 532 directory_path_.Append(kPreservedResourceMapDBName); 533 const base::FilePath trashed_resource_map_path = 534 directory_path_.Append(kTrashedResourceMapDBName); 535 536 // Discard unneeded DBs. 537 if (!base::DeleteFile(preserved_resource_map_path, true /* recursive */) || 538 !base::DeleteFile(trashed_resource_map_path, true /* recursive */)) { 539 LOG(ERROR) << "Failed to remove unneeded DBs."; 540 return false; 541 } 542 543 // Try to open the existing DB. 544 leveldb::DB* db = NULL; 545 leveldb::Options options; 546 options.max_open_files = 0; // Use minimum. 547 options.create_if_missing = false; 548 549 DBInitStatus open_existing_result = DB_INIT_NOT_FOUND; 550 leveldb::Status status; 551 if (base::PathExists(resource_map_path)) { 552 status = leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db); 553 open_existing_result = LevelDBStatusToDBInitStatus(status); 554 } 555 556 if (open_existing_result == DB_INIT_SUCCESS) { 557 resource_map_.reset(db); 558 559 // Check the validity of existing DB. 560 int db_version = -1; 561 ResourceMetadataHeader header; 562 if (GetHeader(&header) == FILE_ERROR_OK) 563 db_version = header.version(); 564 565 bool should_discard_db = true; 566 if (db_version != kDBVersion) { 567 open_existing_result = DB_INIT_INCOMPATIBLE; 568 DVLOG(1) << "Reject incompatible DB."; 569 } else if (!CheckValidity()) { 570 open_existing_result = DB_INIT_BROKEN; 571 LOG(ERROR) << "Reject invalid DB."; 572 } else { 573 should_discard_db = false; 574 } 575 576 if (should_discard_db) 577 resource_map_.reset(); 578 else 579 cache_file_scan_is_needed_ = false; 580 } 581 582 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBOpenExistingResult", 583 open_existing_result, 584 DB_INIT_MAX_VALUE); 585 586 DBInitStatus init_result = DB_INIT_OPENED_EXISTING_DB; 587 588 // Failed to open the existing DB, create new DB. 589 if (!resource_map_) { 590 // Move the existing DB to the preservation path. The moved old DB is 591 // deleted once the new DB creation succeeds, or is restored later in 592 // UpgradeOldDB() when the creation fails. 593 MoveIfPossible(resource_map_path, preserved_resource_map_path); 594 595 // Create DB. 596 options.max_open_files = 0; // Use minimum. 597 options.create_if_missing = true; 598 options.error_if_exists = true; 599 600 status = leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db); 601 if (status.ok()) { 602 resource_map_.reset(db); 603 604 // Set up header and trash the old DB. 605 if (PutHeader(GetDefaultHeaderEntry()) == FILE_ERROR_OK && 606 MoveIfPossible(preserved_resource_map_path, 607 trashed_resource_map_path)) { 608 init_result = open_existing_result == DB_INIT_NOT_FOUND ? 609 DB_INIT_CREATED_NEW_DB : DB_INIT_REPLACED_EXISTING_DB_WITH_NEW_DB; 610 } else { 611 init_result = DB_INIT_FAILED; 612 resource_map_.reset(); 613 } 614 } else { 615 LOG(ERROR) << "Failed to create resource map DB: " << status.ToString(); 616 init_result = LevelDBStatusToDBInitStatus(status); 617 } 618 } 619 620 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBInitResult", 621 init_result, 622 DB_INIT_MAX_VALUE); 623 return resource_map_; 624 } 625 626 void ResourceMetadataStorage::RecoverCacheInfoFromTrashedResourceMap( 627 RecoveredCacheInfoMap* out_info) { 628 const base::FilePath trashed_resource_map_path = 629 directory_path_.Append(kTrashedResourceMapDBName); 630 631 if (!base::PathExists(trashed_resource_map_path)) 632 return; 633 634 leveldb::Options options; 635 options.max_open_files = 0; // Use minimum. 636 options.create_if_missing = false; 637 638 // Trashed DB may be broken, repair it first. 639 leveldb::Status status; 640 status = leveldb::RepairDB(trashed_resource_map_path.AsUTF8Unsafe(), options); 641 if (!status.ok()) { 642 LOG(ERROR) << "Failed to repair trashed DB: " << status.ToString(); 643 return; 644 } 645 646 // Open it. 647 leveldb::DB* db = NULL; 648 status = leveldb::DB::Open(options, trashed_resource_map_path.AsUTF8Unsafe(), 649 &db); 650 if (!status.ok()) { 651 LOG(ERROR) << "Failed to open trashed DB: " << status.ToString(); 652 return; 653 } 654 scoped_ptr<leveldb::DB> resource_map(db); 655 656 // Check DB version. 657 std::string serialized_header; 658 ResourceMetadataHeader header; 659 if (!resource_map->Get(leveldb::ReadOptions(), 660 leveldb::Slice(GetHeaderDBKey()), 661 &serialized_header).ok() || 662 !header.ParseFromString(serialized_header) || 663 header.version() != kDBVersion) { 664 LOG(ERROR) << "Incompatible DB version: " << header.version(); 665 return; 666 } 667 668 // Collect cache entries. 669 scoped_ptr<leveldb::Iterator> it( 670 resource_map->NewIterator(leveldb::ReadOptions())); 671 for (it->SeekToFirst(); it->Valid(); it->Next()) { 672 if (!IsChildEntryKey(it->key()) && 673 !IsIdEntryKey(it->key())) { 674 const std::string id = it->key().ToString(); 675 ResourceEntry entry; 676 if (entry.ParseFromArray(it->value().data(), it->value().size()) && 677 entry.file_specific_info().has_cache_state()) { 678 RecoveredCacheInfo* info = &(*out_info)[id]; 679 info->is_dirty = entry.file_specific_info().cache_state().is_dirty(); 680 info->md5 = entry.file_specific_info().cache_state().md5(); 681 info->title = entry.title(); 682 } 683 } 684 } 685 } 686 687 FileError ResourceMetadataStorage::SetLargestChangestamp( 688 int64 largest_changestamp) { 689 base::ThreadRestrictions::AssertIOAllowed(); 690 691 ResourceMetadataHeader header; 692 FileError error = GetHeader(&header); 693 if (error != FILE_ERROR_OK) { 694 DLOG(ERROR) << "Failed to get the header."; 695 return error; 696 } 697 header.set_largest_changestamp(largest_changestamp); 698 return PutHeader(header); 699 } 700 701 FileError ResourceMetadataStorage::GetLargestChangestamp( 702 int64* largest_changestamp) { 703 base::ThreadRestrictions::AssertIOAllowed(); 704 ResourceMetadataHeader header; 705 FileError error = GetHeader(&header); 706 if (error != FILE_ERROR_OK) { 707 DLOG(ERROR) << "Failed to get the header."; 708 return error; 709 } 710 *largest_changestamp = header.largest_changestamp(); 711 return FILE_ERROR_OK; 712 } 713 714 FileError ResourceMetadataStorage::PutEntry(const ResourceEntry& entry) { 715 base::ThreadRestrictions::AssertIOAllowed(); 716 717 const std::string& id = entry.local_id(); 718 DCHECK(!id.empty()); 719 720 // Try to get existing entry. 721 std::string serialized_entry; 722 leveldb::Status status = resource_map_->Get(leveldb::ReadOptions(), 723 leveldb::Slice(id), 724 &serialized_entry); 725 if (!status.ok() && !status.IsNotFound()) // Unexpected errors. 726 return LevelDBStatusToFileError(status); 727 728 ResourceEntry old_entry; 729 if (status.ok() && !old_entry.ParseFromString(serialized_entry)) 730 return FILE_ERROR_FAILED; 731 732 // Construct write batch. 733 leveldb::WriteBatch batch; 734 735 // Remove from the old parent. 736 if (!old_entry.parent_local_id().empty()) { 737 batch.Delete(GetChildEntryKey(old_entry.parent_local_id(), 738 old_entry.base_name())); 739 } 740 // Add to the new parent. 741 if (!entry.parent_local_id().empty()) 742 batch.Put(GetChildEntryKey(entry.parent_local_id(), entry.base_name()), id); 743 744 // Refresh resource-ID-to-local-ID mapping entry. 745 if (old_entry.resource_id() != entry.resource_id()) { 746 // Resource ID should not change. 747 DCHECK(old_entry.resource_id().empty() || entry.resource_id().empty()); 748 749 if (!old_entry.resource_id().empty()) 750 batch.Delete(GetIdEntryKey(old_entry.resource_id())); 751 if (!entry.resource_id().empty()) 752 batch.Put(GetIdEntryKey(entry.resource_id()), id); 753 } 754 755 // Put the entry itself. 756 if (!entry.SerializeToString(&serialized_entry)) { 757 DLOG(ERROR) << "Failed to serialize the entry: " << id; 758 return FILE_ERROR_FAILED; 759 } 760 batch.Put(id, serialized_entry); 761 762 status = resource_map_->Write(leveldb::WriteOptions(), &batch); 763 return LevelDBStatusToFileError(status); 764 } 765 766 FileError ResourceMetadataStorage::GetEntry(const std::string& id, 767 ResourceEntry* out_entry) { 768 base::ThreadRestrictions::AssertIOAllowed(); 769 DCHECK(!id.empty()); 770 771 std::string serialized_entry; 772 const leveldb::Status status = resource_map_->Get(leveldb::ReadOptions(), 773 leveldb::Slice(id), 774 &serialized_entry); 775 if (!status.ok()) 776 return LevelDBStatusToFileError(status); 777 if (!out_entry->ParseFromString(serialized_entry)) 778 return FILE_ERROR_FAILED; 779 return FILE_ERROR_OK; 780 } 781 782 FileError ResourceMetadataStorage::RemoveEntry(const std::string& id) { 783 base::ThreadRestrictions::AssertIOAllowed(); 784 DCHECK(!id.empty()); 785 786 ResourceEntry entry; 787 FileError error = GetEntry(id, &entry); 788 if (error != FILE_ERROR_OK) 789 return error; 790 791 leveldb::WriteBatch batch; 792 793 // Remove from the parent. 794 if (!entry.parent_local_id().empty()) 795 batch.Delete(GetChildEntryKey(entry.parent_local_id(), entry.base_name())); 796 797 // Remove resource ID-local ID mapping entry. 798 if (!entry.resource_id().empty()) 799 batch.Delete(GetIdEntryKey(entry.resource_id())); 800 801 // Remove the entry itself. 802 batch.Delete(id); 803 804 const leveldb::Status status = resource_map_->Write(leveldb::WriteOptions(), 805 &batch); 806 return LevelDBStatusToFileError(status); 807 } 808 809 scoped_ptr<ResourceMetadataStorage::Iterator> 810 ResourceMetadataStorage::GetIterator() { 811 base::ThreadRestrictions::AssertIOAllowed(); 812 813 scoped_ptr<leveldb::Iterator> it( 814 resource_map_->NewIterator(leveldb::ReadOptions())); 815 return make_scoped_ptr(new Iterator(it.Pass())); 816 } 817 818 FileError ResourceMetadataStorage::GetChild(const std::string& parent_id, 819 const std::string& child_name, 820 std::string* child_id) { 821 base::ThreadRestrictions::AssertIOAllowed(); 822 DCHECK(!parent_id.empty()); 823 DCHECK(!child_name.empty()); 824 825 const leveldb::Status status = 826 resource_map_->Get( 827 leveldb::ReadOptions(), 828 leveldb::Slice(GetChildEntryKey(parent_id, child_name)), 829 child_id); 830 return LevelDBStatusToFileError(status); 831 } 832 833 FileError ResourceMetadataStorage::GetChildren( 834 const std::string& parent_id, 835 std::vector<std::string>* children) { 836 base::ThreadRestrictions::AssertIOAllowed(); 837 DCHECK(!parent_id.empty()); 838 839 // Iterate over all entries with keys starting with |parent_id|. 840 scoped_ptr<leveldb::Iterator> it( 841 resource_map_->NewIterator(leveldb::ReadOptions())); 842 for (it->Seek(parent_id); 843 it->Valid() && it->key().starts_with(leveldb::Slice(parent_id)); 844 it->Next()) { 845 if (IsChildEntryKey(it->key())) 846 children->push_back(it->value().ToString()); 847 } 848 return LevelDBStatusToFileError(it->status()); 849 } 850 851 ResourceMetadataStorage::RecoveredCacheInfo::RecoveredCacheInfo() 852 : is_dirty(false) {} 853 854 ResourceMetadataStorage::RecoveredCacheInfo::~RecoveredCacheInfo() {} 855 856 FileError ResourceMetadataStorage::GetIdByResourceId( 857 const std::string& resource_id, 858 std::string* out_id) { 859 base::ThreadRestrictions::AssertIOAllowed(); 860 DCHECK(!resource_id.empty()); 861 862 const leveldb::Status status = resource_map_->Get( 863 leveldb::ReadOptions(), 864 leveldb::Slice(GetIdEntryKey(resource_id)), 865 out_id); 866 return LevelDBStatusToFileError(status); 867 } 868 869 ResourceMetadataStorage::~ResourceMetadataStorage() { 870 base::ThreadRestrictions::AssertIOAllowed(); 871 } 872 873 void ResourceMetadataStorage::DestroyOnBlockingPool() { 874 delete this; 875 } 876 877 // static 878 std::string ResourceMetadataStorage::GetChildEntryKey( 879 const std::string& parent_id, 880 const std::string& child_name) { 881 DCHECK(!parent_id.empty()); 882 DCHECK(!child_name.empty()); 883 884 std::string key = parent_id; 885 key.push_back(kDBKeyDelimeter); 886 key.append(child_name); 887 key.push_back(kDBKeyDelimeter); 888 return key; 889 } 890 891 FileError ResourceMetadataStorage::PutHeader( 892 const ResourceMetadataHeader& header) { 893 base::ThreadRestrictions::AssertIOAllowed(); 894 895 std::string serialized_header; 896 if (!header.SerializeToString(&serialized_header)) { 897 DLOG(ERROR) << "Failed to serialize the header"; 898 return FILE_ERROR_FAILED; 899 } 900 901 const leveldb::Status status = resource_map_->Put( 902 leveldb::WriteOptions(), 903 leveldb::Slice(GetHeaderDBKey()), 904 leveldb::Slice(serialized_header)); 905 return LevelDBStatusToFileError(status); 906 } 907 908 FileError ResourceMetadataStorage::GetHeader(ResourceMetadataHeader* header) { 909 base::ThreadRestrictions::AssertIOAllowed(); 910 911 std::string serialized_header; 912 const leveldb::Status status = resource_map_->Get( 913 leveldb::ReadOptions(), 914 leveldb::Slice(GetHeaderDBKey()), 915 &serialized_header); 916 if (!status.ok()) 917 return LevelDBStatusToFileError(status); 918 return header->ParseFromString(serialized_header) ? 919 FILE_ERROR_OK : FILE_ERROR_FAILED; 920 } 921 922 bool ResourceMetadataStorage::CheckValidity() { 923 base::ThreadRestrictions::AssertIOAllowed(); 924 925 // Perform read with checksums verification enalbed. 926 leveldb::ReadOptions options; 927 options.verify_checksums = true; 928 929 scoped_ptr<leveldb::Iterator> it(resource_map_->NewIterator(options)); 930 it->SeekToFirst(); 931 932 // DB is organized like this: 933 // 934 // <key> : <value> 935 // "\0HEADER" : ResourceMetadataHeader 936 // "\0ID\0|resource ID 1|" : Local ID associated to resource ID 1. 937 // "\0ID\0|resource ID 2|" : Local ID associated to resource ID 2. 938 // ... 939 // "|ID of A|" : ResourceEntry for entry A. 940 // "|ID of A|\0|child name 1|\0" : ID of the 1st child entry of entry A. 941 // "|ID of A|\0|child name 2|\0" : ID of the 2nd child entry of entry A. 942 // ... 943 // "|ID of A|\0|child name n|\0" : ID of the nth child entry of entry A. 944 // "|ID of B|" : ResourceEntry for entry B. 945 // ... 946 947 // Check the header. 948 ResourceMetadataHeader header; 949 if (!it->Valid() || 950 it->key() != GetHeaderDBKey() || // Header entry must come first. 951 !header.ParseFromArray(it->value().data(), it->value().size()) || 952 header.version() != kDBVersion) { 953 DLOG(ERROR) << "Invalid header detected. version = " << header.version(); 954 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_INVALID_HEADER); 955 return false; 956 } 957 958 // Check all entries. 959 size_t num_entries_with_parent = 0; 960 size_t num_child_entries = 0; 961 ResourceEntry entry; 962 std::string serialized_entry; 963 std::string child_id; 964 for (it->Next(); it->Valid(); it->Next()) { 965 // Count child entries. 966 if (IsChildEntryKey(it->key())) { 967 ++num_child_entries; 968 continue; 969 } 970 971 // Check if resource-ID-to-local-ID mapping is stored correctly. 972 if (IsIdEntryKey(it->key())) { 973 leveldb::Status status = resource_map_->Get( 974 options, it->value(), &serialized_entry); 975 // Resource-ID-to-local-ID mapping without entry for the local ID is ok. 976 if (status.IsNotFound()) 977 continue; 978 // When the entry exists, its resource ID must be consistent. 979 const bool ok = status.ok() && 980 entry.ParseFromString(serialized_entry) && 981 !entry.resource_id().empty() && 982 leveldb::Slice(GetIdEntryKey(entry.resource_id())) == it->key(); 983 if (!ok) { 984 DLOG(ERROR) << "Broken ID entry. status = " << status.ToString(); 985 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_ID_ENTRY); 986 return false; 987 } 988 continue; 989 } 990 991 // Check if stored data is broken. 992 if (!entry.ParseFromArray(it->value().data(), it->value().size())) { 993 DLOG(ERROR) << "Broken entry detected"; 994 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_ENTRY); 995 return false; 996 } 997 998 if (leveldb::Slice(entry.local_id()) != it->key()) { 999 DLOG(ERROR) << "Wrong local ID."; 1000 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_INVALID_LOCAL_ID); 1001 return false; 1002 } 1003 1004 if (!entry.parent_local_id().empty()) { 1005 // Check if the parent entry is stored. 1006 leveldb::Status status = resource_map_->Get( 1007 options, 1008 leveldb::Slice(entry.parent_local_id()), 1009 &serialized_entry); 1010 if (!status.ok()) { 1011 DLOG(ERROR) << "Can't get parent entry. status = " << status.ToString(); 1012 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_INVALID_PARENT_ID); 1013 return false; 1014 } 1015 1016 // Check if parent-child relationship is stored correctly. 1017 status = resource_map_->Get( 1018 options, 1019 leveldb::Slice(GetChildEntryKey(entry.parent_local_id(), 1020 entry.base_name())), 1021 &child_id); 1022 if (!status.ok() || leveldb::Slice(child_id) != it->key()) { 1023 DLOG(ERROR) << "Child map is broken. status = " << status.ToString(); 1024 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_CHILD_MAP); 1025 return false; 1026 } 1027 ++num_entries_with_parent; 1028 } 1029 } 1030 if (!it->status().ok()) { 1031 DLOG(ERROR) << "Error during checking resource map. status = " 1032 << it->status().ToString(); 1033 RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_ITERATOR_ERROR); 1034 return false; 1035 } 1036 if (num_child_entries != num_entries_with_parent) { 1037 DLOG(ERROR) << "Child entry count mismatch"; 1038 RecordCheckValidityFailure( 1039 CHECK_VALIDITY_FAILURE_CHILD_ENTRY_COUNT_MISMATCH); 1040 return false; 1041 } 1042 return true; 1043 } 1044 1045 } // namespace internal 1046 } // namespace drive 1047