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