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.
GetHeaderDBKey()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.
IsChildEntryKey(const leveldb::Slice & key)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.
IsCacheEntryKey(const leveldb::Slice & key)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.
GetIdFromCacheEntryKey(const leveldb::Slice & 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.
GetIdEntryKey(const std::string & resource_id)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.
IsIdEntryKey(const leveldb::Slice & key)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.
GetResourceIdFromIdEntryKey(const leveldb::Slice & 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.
LevelDBStatusToDBInitStatus(const leveldb::Status & status)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.
LevelDBStatusToFileError(const leveldb::Status & status)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
GetDefaultHeaderEntry()169 ResourceMetadataHeader GetDefaultHeaderEntry() {
170 ResourceMetadataHeader header;
171 header.set_version(ResourceMetadataStorage::kDBVersion);
172 return header;
173 }
174
MoveIfPossible(const base::FilePath & from,const base::FilePath & to)175 bool MoveIfPossible(const base::FilePath& from, const base::FilePath& to) {
176 return !base::PathExists(from) || base::Move(from, to);
177 }
178
RecordCheckValidityFailure(CheckValidityFailureReason reason)179 void RecordCheckValidityFailure(CheckValidityFailureReason reason) {
180 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBValidityCheckFailureReason",
181 reason,
182 CHECK_VALIDITY_FAILURE_MAX_VALUE);
183 }
184
185 } // namespace
186
Iterator(scoped_ptr<leveldb::Iterator> it)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
~Iterator()200 ResourceMetadataStorage::Iterator::~Iterator() {
201 base::ThreadRestrictions::AssertIOAllowed();
202 }
203
IsAtEnd() const204 bool ResourceMetadataStorage::Iterator::IsAtEnd() const {
205 base::ThreadRestrictions::AssertIOAllowed();
206 return !it_->Valid();
207 }
208
GetID() const209 std::string ResourceMetadataStorage::Iterator::GetID() const {
210 return it_->key().ToString();
211 }
212
GetValue() const213 const ResourceEntry& ResourceMetadataStorage::Iterator::GetValue() const {
214 base::ThreadRestrictions::AssertIOAllowed();
215 DCHECK(!IsAtEnd());
216 return entry_;
217 }
218
Advance()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
HasError() const232 bool ResourceMetadataStorage::Iterator::HasError() const {
233 base::ThreadRestrictions::AssertIOAllowed();
234 return !it_->status().ok();
235 }
236
237 // static
UpgradeOldDB(const base::FilePath & directory_path)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
ResourceMetadataStorage(const base::FilePath & directory_path,base::SequencedTaskRunner * blocking_task_runner)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
Destroy()517 void ResourceMetadataStorage::Destroy() {
518 blocking_task_runner_->PostTask(
519 FROM_HERE,
520 base::Bind(&ResourceMetadataStorage::DestroyOnBlockingPool,
521 base::Unretained(this)));
522 }
523
Initialize()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
RecoverCacheInfoFromTrashedResourceMap(RecoveredCacheInfoMap * out_info)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
SetLargestChangestamp(int64 largest_changestamp)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
GetLargestChangestamp(int64 * largest_changestamp)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
PutEntry(const ResourceEntry & entry)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
GetEntry(const std::string & id,ResourceEntry * out_entry)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
RemoveEntry(const std::string & id)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>
GetIterator()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
GetChild(const std::string & parent_id,const std::string & child_name,std::string * child_id)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
GetChildren(const std::string & parent_id,std::vector<std::string> * children)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
RecoveredCacheInfo()851 ResourceMetadataStorage::RecoveredCacheInfo::RecoveredCacheInfo()
852 : is_dirty(false) {}
853
~RecoveredCacheInfo()854 ResourceMetadataStorage::RecoveredCacheInfo::~RecoveredCacheInfo() {}
855
GetIdByResourceId(const std::string & resource_id,std::string * out_id)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
~ResourceMetadataStorage()869 ResourceMetadataStorage::~ResourceMetadataStorage() {
870 base::ThreadRestrictions::AssertIOAllowed();
871 }
872
DestroyOnBlockingPool()873 void ResourceMetadataStorage::DestroyOnBlockingPool() {
874 delete this;
875 }
876
877 // static
GetChildEntryKey(const std::string & parent_id,const std::string & child_name)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
PutHeader(const ResourceMetadataHeader & header)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
GetHeader(ResourceMetadataHeader * header)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
CheckValidity()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