resource_metadata_storage.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/callback.h" 8#include "base/file_util.h" 9#include "base/logging.h" 10#include "base/metrics/histogram.h" 11#include "base/threading/thread_restrictions.h" 12#include "chrome/browser/chromeos/drive/drive.pb.h" 13#include "third_party/leveldatabase/src/include/leveldb/db.h" 14#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 15 16namespace drive { 17 18namespace { 19 20// Enum to describe DB initialization status. 21enum DBInitStatus { 22 DB_INIT_SUCCESS, 23 DB_INIT_NOT_FOUND, 24 DB_INIT_CORRUPTION, 25 DB_INIT_IO_ERROR, 26 DB_INIT_FAILED, 27 DB_INIT_INCOMPATIBLE, 28 DB_INIT_BROKEN, 29 DB_INIT_MAX_VALUE, 30}; 31 32const base::FilePath::CharType kResourceMapDBName[] = 33 FILE_PATH_LITERAL("resource_metadata_resource_map.db"); 34const base::FilePath::CharType kChildMapDBName[] = 35 FILE_PATH_LITERAL("resource_metadata_child_map.db"); 36 37// Meant to be a character which never happen to be in real resource IDs. 38const char kDBKeyDelimeter = '\0'; 39 40// Returns a string to be used as the key for the header. 41std::string GetHeaderDBKey() { 42 std::string key; 43 key.push_back(kDBKeyDelimeter); 44 key.append("HEADER"); 45 return key; 46} 47 48// Returns true if |key| is a key for a child entry. 49bool IsChildEntryKey(const leveldb::Slice& key) { 50 return !key.empty() && key[key.size() - 1] == kDBKeyDelimeter; 51} 52 53// Converts leveldb::Status to DBInitStatus. 54DBInitStatus LevelDBStatusToDBInitStatus(const leveldb::Status status) { 55 if (status.ok()) 56 return DB_INIT_SUCCESS; 57 if (status.IsNotFound()) 58 return DB_INIT_NOT_FOUND; 59 if (status.IsCorruption()) 60 return DB_INIT_CORRUPTION; 61 if (status.IsIOError()) 62 return DB_INIT_IO_ERROR; 63 return DB_INIT_FAILED; 64} 65 66} // namespace 67 68ResourceMetadataStorage::ResourceMetadataStorage( 69 const base::FilePath& directory_path) 70 : directory_path_(directory_path) { 71} 72 73ResourceMetadataStorage::~ResourceMetadataStorage() { 74 base::ThreadRestrictions::AssertIOAllowed(); 75} 76 77bool ResourceMetadataStorage::Initialize() { 78 base::ThreadRestrictions::AssertIOAllowed(); 79 80 // Remove unused child map DB. 81 const base::FilePath child_map_path = directory_path_.Append(kChildMapDBName); 82 file_util::Delete(child_map_path, true /* recursive */); 83 84 resource_map_.reset(); 85 86 const base::FilePath resource_map_path = 87 directory_path_.Append(kResourceMapDBName); 88 89 // Try to open the existing DB. 90 leveldb::DB* db = NULL; 91 leveldb::Options options; 92 options.create_if_missing = false; 93 94 leveldb::Status status = 95 leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db); 96 DBInitStatus open_existing_result = LevelDBStatusToDBInitStatus(status); 97 98 if (open_existing_result == DB_INIT_SUCCESS) { 99 resource_map_.reset(db); 100 101 // Check the validity of existing DB. 102 scoped_ptr<ResourceMetadataHeader> header = GetHeader(); 103 if (!header || header->version() != kDBVersion) { 104 open_existing_result = DB_INIT_INCOMPATIBLE; 105 LOG(INFO) << "Reject incompatible DB."; 106 } else if (!CheckValidity()) { 107 open_existing_result = DB_INIT_BROKEN; 108 LOG(ERROR) << "Reject invalid DB."; 109 } 110 111 if (open_existing_result != DB_INIT_SUCCESS) 112 resource_map_.reset(); 113 } 114 115 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBOpenExistingResult", 116 open_existing_result, 117 DB_INIT_MAX_VALUE); 118 119 DBInitStatus init_result = DB_INIT_SUCCESS; 120 121 // Failed to open the existing DB, create new DB. 122 if (!resource_map_) { 123 resource_map_.reset(); 124 125 // Clean up the destination. 126 const bool kRecursive = true; 127 file_util::Delete(resource_map_path, kRecursive); 128 129 // Create DB. 130 options.create_if_missing = true; 131 132 status = leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db); 133 if (status.ok()) { 134 resource_map_.reset(db); 135 136 // Set up header. 137 ResourceMetadataHeader header; 138 header.set_version(kDBVersion); 139 if (!PutHeader(header)) { 140 init_result = DB_INIT_FAILED; 141 resource_map_.reset(); 142 } 143 } else { 144 LOG(ERROR) << "Failed to create resource map DB: " << status.ToString(); 145 init_result = LevelDBStatusToDBInitStatus(status); 146 } 147 } 148 149 UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBInitResult", 150 init_result, 151 DB_INIT_MAX_VALUE); 152 return resource_map_; 153} 154 155bool ResourceMetadataStorage::SetLargestChangestamp( 156 int64 largest_changestamp) { 157 base::ThreadRestrictions::AssertIOAllowed(); 158 159 scoped_ptr<ResourceMetadataHeader> header = GetHeader(); 160 if (!header) { 161 DLOG(ERROR) << "Failed to get the header."; 162 return false; 163 } 164 header->set_largest_changestamp(largest_changestamp); 165 return PutHeader(*header); 166} 167 168int64 ResourceMetadataStorage::GetLargestChangestamp() { 169 base::ThreadRestrictions::AssertIOAllowed(); 170 scoped_ptr<ResourceMetadataHeader> header = GetHeader(); 171 if (!header) { 172 DLOG(ERROR) << "Failed to get the header."; 173 return 0; 174 } 175 return header->largest_changestamp(); 176} 177 178bool ResourceMetadataStorage::PutEntry(const ResourceEntry& entry) { 179 base::ThreadRestrictions::AssertIOAllowed(); 180 DCHECK(!entry.resource_id().empty()); 181 182 std::string serialized_entry; 183 if (!entry.SerializeToString(&serialized_entry)) { 184 DLOG(ERROR) << "Failed to serialize the entry: " << entry.resource_id(); 185 return false; 186 } 187 188 leveldb::WriteBatch batch; 189 190 // Remove from the old parent. 191 scoped_ptr<ResourceEntry> old_entry = GetEntry(entry.resource_id()); 192 if (old_entry && !old_entry->parent_resource_id().empty()) { 193 batch.Delete(GetChildEntryKey(old_entry->parent_resource_id(), 194 old_entry->base_name())); 195 } 196 197 // Add to the new parent. 198 if (!entry.parent_resource_id().empty()) { 199 batch.Put(GetChildEntryKey(entry.parent_resource_id(), entry.base_name()), 200 entry.resource_id()); 201 } 202 203 // Put the entry itself. 204 batch.Put(entry.resource_id(), serialized_entry); 205 206 const leveldb::Status status = resource_map_->Write(leveldb::WriteOptions(), 207 &batch); 208 return status.ok(); 209} 210 211scoped_ptr<ResourceEntry> ResourceMetadataStorage::GetEntry( 212 const std::string& resource_id) { 213 base::ThreadRestrictions::AssertIOAllowed(); 214 DCHECK(!resource_id.empty()); 215 216 std::string serialized_entry; 217 const leveldb::Status status = resource_map_->Get(leveldb::ReadOptions(), 218 leveldb::Slice(resource_id), 219 &serialized_entry); 220 if (!status.ok()) 221 return scoped_ptr<ResourceEntry>(); 222 223 scoped_ptr<ResourceEntry> entry(new ResourceEntry); 224 if (!entry->ParseFromString(serialized_entry)) 225 return scoped_ptr<ResourceEntry>(); 226 return entry.Pass(); 227} 228 229bool ResourceMetadataStorage::RemoveEntry(const std::string& resource_id) { 230 base::ThreadRestrictions::AssertIOAllowed(); 231 DCHECK(!resource_id.empty()); 232 233 scoped_ptr<ResourceEntry> entry = GetEntry(resource_id); 234 if (!entry) 235 return false; 236 237 leveldb::WriteBatch batch; 238 239 // Remove from the parent. 240 if (!entry->parent_resource_id().empty()) { 241 batch.Delete(GetChildEntryKey(entry->parent_resource_id(), 242 entry->base_name())); 243 } 244 // Remove the entry itself. 245 batch.Delete(resource_id); 246 247 const leveldb::Status status = resource_map_->Write(leveldb::WriteOptions(), 248 &batch); 249 return status.ok(); 250} 251 252void ResourceMetadataStorage::Iterate(const IterateCallback& callback) { 253 base::ThreadRestrictions::AssertIOAllowed(); 254 DCHECK(!callback.is_null()); 255 256 scoped_ptr<leveldb::Iterator> it( 257 resource_map_->NewIterator(leveldb::ReadOptions())); 258 259 // Skip the header entry. 260 // Note: The header entry comes before all other entries because its key 261 // starts with kDBKeyDelimeter. (i.e. '\0') 262 it->Seek(leveldb::Slice(GetHeaderDBKey())); 263 it->Next(); 264 265 ResourceEntry entry; 266 for (; it->Valid(); it->Next()) { 267 if (!IsChildEntryKey(it->key()) && 268 entry.ParseFromArray(it->value().data(), it->value().size())) 269 callback.Run(entry); 270 } 271} 272 273std::string ResourceMetadataStorage::GetChild( 274 const std::string& parent_resource_id, 275 const std::string& child_name) { 276 base::ThreadRestrictions::AssertIOAllowed(); 277 278 std::string child_resource_id; 279 resource_map_->Get( 280 leveldb::ReadOptions(), 281 leveldb::Slice(GetChildEntryKey(parent_resource_id, child_name)), 282 &child_resource_id); 283 return child_resource_id; 284} 285 286void ResourceMetadataStorage::GetChildren( 287 const std::string& parent_resource_id, 288 std::vector<std::string>* children) { 289 base::ThreadRestrictions::AssertIOAllowed(); 290 291 // Iterate over all entries with keys starting with |parent_resource_id|. 292 scoped_ptr<leveldb::Iterator> it( 293 resource_map_->NewIterator(leveldb::ReadOptions())); 294 for (it->Seek(parent_resource_id); 295 it->Valid() && it->key().starts_with(leveldb::Slice(parent_resource_id)); 296 it->Next()) { 297 if (IsChildEntryKey(it->key())) 298 children->push_back(it->value().ToString()); 299 } 300 DCHECK(it->status().ok()); 301} 302 303// static 304std::string ResourceMetadataStorage::GetChildEntryKey( 305 const std::string& parent_resource_id, 306 const std::string& child_name) { 307 std::string key = parent_resource_id; 308 key.push_back(kDBKeyDelimeter); 309 key.append(child_name); 310 key.push_back(kDBKeyDelimeter); 311 return key; 312} 313 314bool ResourceMetadataStorage::PutHeader( 315 const ResourceMetadataHeader& header) { 316 base::ThreadRestrictions::AssertIOAllowed(); 317 318 std::string serialized_header; 319 if (!header.SerializeToString(&serialized_header)) { 320 DLOG(ERROR) << "Failed to serialize the header"; 321 return false; 322 } 323 324 const leveldb::Status status = resource_map_->Put( 325 leveldb::WriteOptions(), 326 leveldb::Slice(GetHeaderDBKey()), 327 leveldb::Slice(serialized_header)); 328 return status.ok(); 329} 330 331scoped_ptr<ResourceMetadataHeader> 332ResourceMetadataStorage::GetHeader() { 333 base::ThreadRestrictions::AssertIOAllowed(); 334 335 std::string serialized_header; 336 const leveldb::Status status = resource_map_->Get( 337 leveldb::ReadOptions(), 338 leveldb::Slice(GetHeaderDBKey()), 339 &serialized_header); 340 if (!status.ok()) 341 return scoped_ptr<ResourceMetadataHeader>(); 342 343 scoped_ptr<ResourceMetadataHeader> header( 344 new ResourceMetadataHeader); 345 if (!header->ParseFromString(serialized_header)) 346 return scoped_ptr<ResourceMetadataHeader>(); 347 return header.Pass(); 348} 349 350bool ResourceMetadataStorage::CheckValidity() { 351 base::ThreadRestrictions::AssertIOAllowed(); 352 353 // Perform read with checksums verification enalbed. 354 leveldb::ReadOptions options; 355 options.verify_checksums = true; 356 357 scoped_ptr<leveldb::Iterator> it(resource_map_->NewIterator(options)); 358 it->SeekToFirst(); 359 360 // Check the header. 361 ResourceMetadataHeader header; 362 if (!it->Valid() || 363 it->key() != GetHeaderDBKey() || // Header entry must come first. 364 !header.ParseFromArray(it->value().data(), it->value().size()) || 365 header.version() != kDBVersion) { 366 DLOG(ERROR) << "Invalid header detected. version = " << header.version(); 367 return false; 368 } 369 370 // Check all entires. 371 size_t num_entries_with_parent = 0; 372 size_t num_child_entries = 0; 373 ResourceEntry entry; 374 std::string serialized_parent_entry; 375 std::string child_resource_id; 376 for (it->Next(); it->Valid(); it->Next()) { 377 // Count child entries. 378 if (IsChildEntryKey(it->key())) { 379 ++num_child_entries; 380 continue; 381 } 382 383 // Check if stored data is broken. 384 if (!entry.ParseFromArray(it->value().data(), it->value().size()) || 385 entry.resource_id() != it->key()) { 386 DLOG(ERROR) << "Broken entry detected"; 387 return false; 388 } 389 390 if (!entry.parent_resource_id().empty()) { 391 // Check if the parent entry is stored. 392 leveldb::Status status = resource_map_->Get( 393 options, 394 leveldb::Slice(entry.parent_resource_id()), 395 &serialized_parent_entry); 396 if (!status.ok()) { 397 DLOG(ERROR) << "Can't get parent entry. status = " << status.ToString(); 398 return false; 399 } 400 401 // Check if parent-child relationship is stored correctly. 402 status = resource_map_->Get( 403 options, 404 leveldb::Slice(GetChildEntryKey(entry.parent_resource_id(), 405 entry.base_name())), 406 &child_resource_id); 407 if (!status.ok() || child_resource_id != entry.resource_id()) { 408 DLOG(ERROR) << "Child map is broken. status = " << status.ToString(); 409 return false; 410 } 411 ++num_entries_with_parent; 412 } 413 } 414 if (!it->status().ok() || num_child_entries != num_entries_with_parent) { 415 DLOG(ERROR) << "Error during checking resource map. status = " 416 << it->status().ToString(); 417 return false; 418 } 419 return true; 420} 421 422} // namespace drive 423