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