Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / webkit / fileapi / file_system_directory_database.cc
blob58f5a4db29987acfe4819ce12d147e65568cb141
1 // Copyright (c) 2012 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.
5 #include "webkit/fileapi/file_system_directory_database.h"
7 #include <math.h>
8 #include <algorithm>
9 #include <set>
10 #include <stack>
12 #include "base/file_util.h"
13 #include "base/location.h"
14 #include "base/metrics/histogram.h"
15 #include "base/pickle.h"
16 #include "base/string_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "third_party/leveldatabase/src/include/leveldb/db.h"
19 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
20 #include "webkit/fileapi/file_system_usage_cache.h"
21 #include "webkit/fileapi/file_system_util.h"
23 namespace {
25 bool PickleFromFileInfo(
26 const fileapi::FileSystemDirectoryDatabase::FileInfo& info,
27 Pickle* pickle) {
28 DCHECK(pickle);
29 std::string data_path;
30 // Round off here to match the behavior of the filesystem on real files.
31 base::Time time =
32 base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
33 std::string name;
35 data_path = fileapi::FilePathToString(info.data_path);
36 name = fileapi::FilePathToString(base::FilePath(info.name));
38 if (pickle->WriteInt64(info.parent_id) &&
39 pickle->WriteString(data_path) &&
40 pickle->WriteString(name) &&
41 pickle->WriteInt64(time.ToInternalValue()))
42 return true;
44 NOTREACHED();
45 return false;
48 bool FileInfoFromPickle(
49 const Pickle& pickle,
50 fileapi::FileSystemDirectoryDatabase::FileInfo* info) {
51 PickleIterator iter(pickle);
52 std::string data_path;
53 std::string name;
54 int64 internal_time;
56 if (pickle.ReadInt64(&iter, &info->parent_id) &&
57 pickle.ReadString(&iter, &data_path) &&
58 pickle.ReadString(&iter, &name) &&
59 pickle.ReadInt64(&iter, &internal_time)) {
60 info->data_path = fileapi::StringToFilePath(data_path);
61 info->name = fileapi::StringToFilePath(name).value();
62 info->modification_time = base::Time::FromInternalValue(internal_time);
63 return true;
65 LOG(ERROR) << "Pickle could not be digested!";
66 return false;
69 const base::FilePath::CharType kDirectoryDatabaseName[] = FILE_PATH_LITERAL("Paths");
70 const char kChildLookupPrefix[] = "CHILD_OF:";
71 const char kChildLookupSeparator[] = ":";
72 const char kLastFileIdKey[] = "LAST_FILE_ID";
73 const char kLastIntegerKey[] = "LAST_INTEGER";
74 const int64 kMinimumReportIntervalHours = 1;
75 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
77 enum InitStatus {
78 INIT_STATUS_OK = 0,
79 INIT_STATUS_CORRUPTION,
80 INIT_STATUS_IO_ERROR,
81 INIT_STATUS_UNKNOWN_ERROR,
82 INIT_STATUS_MAX
85 std::string GetChildLookupKey(
86 fileapi::FileSystemDirectoryDatabase::FileId parent_id,
87 const base::FilePath::StringType& child_name) {
88 std::string name;
89 name = fileapi::FilePathToString(base::FilePath(child_name));
90 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
91 std::string(kChildLookupSeparator) + name;
94 std::string GetChildListingKeyPrefix(
95 fileapi::FileSystemDirectoryDatabase::FileId parent_id) {
96 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
97 std::string(kChildLookupSeparator);
100 const char* LastFileIdKey() {
101 return kLastFileIdKey;
104 const char* LastIntegerKey() {
105 return kLastIntegerKey;
108 std::string GetFileLookupKey(
109 fileapi::FileSystemDirectoryDatabase::FileId file_id) {
110 return base::Int64ToString(file_id);
113 // Assumptions:
114 // - Any database entry is one of:
115 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
116 // - ("LAST_FILE_ID", "|last_file_id|"),
117 // - ("LAST_INTEGER", "|last_integer|"),
118 // - ("|file_id|", "pickled FileInfo")
119 // where FileInfo has |parent_id|, |data_path|, |name| and
120 // |modification_time|,
121 // Constraints:
122 // - Each file in the database has unique backing file.
123 // - Each file in |filesystem_data_directory_| has a database entry.
124 // - Directory structure is tree, i.e. connected and acyclic.
125 class DatabaseCheckHelper {
126 public:
127 typedef fileapi::FileSystemDirectoryDatabase::FileId FileId;
128 typedef fileapi::FileSystemDirectoryDatabase::FileInfo FileInfo;
130 DatabaseCheckHelper(fileapi::FileSystemDirectoryDatabase* dir_db,
131 leveldb::DB* db,
132 const base::FilePath& path);
134 bool IsFileSystemConsistent() {
135 return IsDatabaseEmpty() ||
136 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
139 private:
140 bool IsDatabaseEmpty();
141 // These 3 methods need to be called in the order. Each method requires its
142 // previous method finished successfully. They also require the database is
143 // not empty.
144 bool ScanDatabase();
145 bool ScanDirectory();
146 bool ScanHierarchy();
148 fileapi::FileSystemDirectoryDatabase* dir_db_;
149 leveldb::DB* db_;
150 base::FilePath path_;
152 std::set<base::FilePath> files_in_db_;
154 size_t num_directories_in_db_;
155 size_t num_files_in_db_;
156 size_t num_hierarchy_links_in_db_;
158 FileId last_file_id_;
159 FileId last_integer_;
162 DatabaseCheckHelper::DatabaseCheckHelper(
163 fileapi::FileSystemDirectoryDatabase* dir_db,
164 leveldb::DB* db,
165 const base::FilePath& path)
166 : dir_db_(dir_db), db_(db), path_(path),
167 num_directories_in_db_(0),
168 num_files_in_db_(0),
169 num_hierarchy_links_in_db_(0),
170 last_file_id_(-1), last_integer_(-1) {
171 DCHECK(dir_db_);
172 DCHECK(db_);
173 DCHECK(!path_.empty() && file_util::DirectoryExists(path_));
176 bool DatabaseCheckHelper::IsDatabaseEmpty() {
177 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
178 itr->SeekToFirst();
179 return !itr->Valid();
182 bool DatabaseCheckHelper::ScanDatabase() {
183 // Scans all database entries sequentially to verify each of them has unique
184 // backing file.
185 int64 max_file_id = -1;
186 std::set<FileId> file_ids;
188 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
189 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
190 std::string key = itr->key().ToString();
191 if (StartsWithASCII(key, kChildLookupPrefix, true)) {
192 // key: "CHILD_OF:<parent_id>:<name>"
193 // value: "<child_id>"
194 ++num_hierarchy_links_in_db_;
195 } else if (key == kLastFileIdKey) {
196 // key: "LAST_FILE_ID"
197 // value: "<last_file_id>"
198 if (last_file_id_ >= 0 ||
199 !base::StringToInt64(itr->value().ToString(), &last_file_id_))
200 return false;
202 if (last_file_id_ < 0)
203 return false;
204 } else if (key == kLastIntegerKey) {
205 // key: "LAST_INTEGER"
206 // value: "<last_integer>"
207 if (last_integer_ >= 0 ||
208 !base::StringToInt64(itr->value().ToString(), &last_integer_))
209 return false;
210 } else {
211 // key: "<entry_id>"
212 // value: "<pickled FileInfo>"
213 FileInfo file_info;
214 if (!FileInfoFromPickle(
215 Pickle(itr->value().data(), itr->value().size()), &file_info))
216 return false;
218 FileId file_id = -1;
219 if (!base::StringToInt64(key, &file_id) || file_id < 0)
220 return false;
222 if (max_file_id < file_id)
223 max_file_id = file_id;
224 if (!file_ids.insert(file_id).second)
225 return false;
227 if (file_info.is_directory()) {
228 ++num_directories_in_db_;
229 DCHECK(file_info.data_path.empty());
230 } else {
231 // Ensure any pair of file entry don't share their data_path.
232 if (!files_in_db_.insert(file_info.data_path).second)
233 return false;
235 // Ensure the backing file exists as a normal file.
236 base::PlatformFileInfo platform_file_info;
237 if (!file_util::GetFileInfo(
238 path_.Append(file_info.data_path), &platform_file_info) ||
239 platform_file_info.is_directory ||
240 platform_file_info.is_symbolic_link) {
241 // leveldb::Iterator iterates a snapshot of the database.
242 // So even after RemoveFileInfo() call, we'll visit hierarchy link
243 // from |parent_id| to |file_id|.
244 if (!dir_db_->RemoveFileInfo(file_id))
245 return false;
246 --num_hierarchy_links_in_db_;
247 files_in_db_.erase(file_info.data_path);
248 } else {
249 ++num_files_in_db_;
255 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
256 // data path confliction on ObfuscatedFileUtil.
257 return max_file_id <= last_file_id_;
260 bool DatabaseCheckHelper::ScanDirectory() {
261 // TODO(kinuko): Scans all local file system entries to verify each of them
262 // has a database entry.
263 const base::FilePath kExcludes[] = {
264 base::FilePath(kDirectoryDatabaseName),
265 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName),
268 // Any path in |pending_directories| is relative to |path_|.
269 std::stack<base::FilePath> pending_directories;
270 pending_directories.push(base::FilePath());
272 while (!pending_directories.empty()) {
273 base::FilePath dir_path = pending_directories.top();
274 pending_directories.pop();
276 file_util::FileEnumerator file_enum(
277 dir_path.empty() ? path_ : path_.Append(dir_path),
278 false /* not recursive */,
279 file_util::FileEnumerator::DIRECTORIES |
280 file_util::FileEnumerator::FILES);
282 base::FilePath absolute_file_path;
283 while (!(absolute_file_path = file_enum.Next()).empty()) {
284 file_util::FileEnumerator::FindInfo find_info;
285 file_enum.GetFindInfo(&find_info);
287 base::FilePath relative_file_path;
288 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
289 return false;
291 if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
292 relative_file_path) != kExcludes + arraysize(kExcludes))
293 continue;
295 if (file_util::FileEnumerator::IsDirectory(find_info)) {
296 pending_directories.push(relative_file_path);
297 continue;
300 // Check if the file has a database entry.
301 std::set<base::FilePath>::iterator itr = files_in_db_.find(relative_file_path);
302 if (itr == files_in_db_.end()) {
303 if (!file_util::Delete(absolute_file_path, false))
304 return false;
305 } else {
306 files_in_db_.erase(itr);
311 return files_in_db_.empty();
314 bool DatabaseCheckHelper::ScanHierarchy() {
315 size_t visited_directories = 0;
316 size_t visited_files = 0;
317 size_t visited_links = 0;
319 std::stack<FileId> directories;
320 directories.push(0);
322 // Check if the root directory exists as a directory.
323 FileInfo file_info;
324 if (!dir_db_->GetFileInfo(0, &file_info))
325 return false;
326 if (file_info.parent_id != 0 ||
327 !file_info.is_directory())
328 return false;
330 while (!directories.empty()) {
331 ++visited_directories;
332 FileId dir_id = directories.top();
333 directories.pop();
335 std::vector<FileId> children;
336 if (!dir_db_->ListChildren(dir_id, &children))
337 return false;
338 for (std::vector<FileId>::iterator itr = children.begin();
339 itr != children.end();
340 ++itr) {
341 // Any directory must not have root directory as child.
342 if (!*itr)
343 return false;
345 // Check if the child knows the parent as its parent.
346 FileInfo file_info;
347 if (!dir_db_->GetFileInfo(*itr, &file_info))
348 return false;
349 if (file_info.parent_id != dir_id)
350 return false;
352 // Check if the parent knows the name of its child correctly.
353 FileId file_id;
354 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
355 file_id != *itr)
356 return false;
358 if (file_info.is_directory())
359 directories.push(*itr);
360 else
361 ++visited_files;
362 ++visited_links;
366 // Check if we've visited all database entries.
367 return num_directories_in_db_ == visited_directories &&
368 num_files_in_db_ == visited_files &&
369 num_hierarchy_links_in_db_ == visited_links;
372 // Returns true if the given |data_path| contains no parent references ("..")
373 // and does not refer to special system files.
374 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
375 // ensure we're only dealing with valid data paths.
376 bool VerifyDataPath(const base::FilePath& data_path) {
377 // |data_path| should not contain any ".." and should be a relative path
378 // (to the filesystem_data_directory_).
379 if (data_path.ReferencesParent() || data_path.IsAbsolute())
380 return false;
381 // See if it's not pointing to the special system paths.
382 const base::FilePath kExcludes[] = {
383 base::FilePath(kDirectoryDatabaseName),
384 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName),
386 for (size_t i = 0; i < arraysize(kExcludes); ++i) {
387 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
388 return false;
390 return true;
393 } // namespace
395 namespace fileapi {
397 FileSystemDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
400 FileSystemDirectoryDatabase::FileInfo::~FileInfo() {
403 FileSystemDirectoryDatabase::FileSystemDirectoryDatabase(
404 const base::FilePath& filesystem_data_directory)
405 : filesystem_data_directory_(filesystem_data_directory) {
408 FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() {
411 bool FileSystemDirectoryDatabase::GetChildWithName(
412 FileId parent_id, const base::FilePath::StringType& name, FileId* child_id) {
413 if (!Init(REPAIR_ON_CORRUPTION))
414 return false;
415 DCHECK(child_id);
416 std::string child_key = GetChildLookupKey(parent_id, name);
417 std::string child_id_string;
418 leveldb::Status status =
419 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
420 if (status.IsNotFound())
421 return false;
422 if (status.ok()) {
423 if (!base::StringToInt64(child_id_string, child_id)) {
424 LOG(ERROR) << "Hit database corruption!";
425 return false;
427 return true;
429 HandleError(FROM_HERE, status);
430 return false;
433 bool FileSystemDirectoryDatabase::GetFileWithPath(
434 const base::FilePath& path, FileId* file_id) {
435 std::vector<base::FilePath::StringType> components;
436 VirtualPath::GetComponents(path, &components);
437 FileId local_id = 0;
438 std::vector<base::FilePath::StringType>::iterator iter;
439 for (iter = components.begin(); iter != components.end(); ++iter) {
440 base::FilePath::StringType name;
441 name = *iter;
442 if (name == FILE_PATH_LITERAL("/"))
443 continue;
444 if (!GetChildWithName(local_id, name, &local_id))
445 return false;
447 *file_id = local_id;
448 return true;
451 bool FileSystemDirectoryDatabase::ListChildren(
452 FileId parent_id, std::vector<FileId>* children) {
453 // Check to add later: fail if parent is a file, at least in debug builds.
454 if (!Init(REPAIR_ON_CORRUPTION))
455 return false;
456 DCHECK(children);
457 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
459 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
460 iter->Seek(child_key_prefix);
461 children->clear();
462 while (iter->Valid() &&
463 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
464 std::string child_id_string = iter->value().ToString();
465 FileId child_id;
466 if (!base::StringToInt64(child_id_string, &child_id)) {
467 LOG(ERROR) << "Hit database corruption!";
468 return false;
470 children->push_back(child_id);
471 iter->Next();
473 return true;
476 bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
477 if (!Init(REPAIR_ON_CORRUPTION))
478 return false;
479 DCHECK(info);
480 std::string file_key = GetFileLookupKey(file_id);
481 std::string file_data_string;
482 leveldb::Status status =
483 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
484 if (status.ok()) {
485 bool success = FileInfoFromPickle(
486 Pickle(file_data_string.data(), file_data_string.length()), info);
487 if (!success)
488 return false;
489 if (!VerifyDataPath(info->data_path)) {
490 LOG(ERROR) << "Resolved data path is invalid: "
491 << info->data_path.value();
492 return false;
494 return true;
496 // Special-case the root, for databases that haven't been initialized yet.
497 // Without this, a query for the root's file info, made before creating the
498 // first file in the database, will fail and confuse callers.
499 if (status.IsNotFound() && !file_id) {
500 info->name = base::FilePath::StringType();
501 info->data_path = base::FilePath();
502 info->modification_time = base::Time::Now();
503 info->parent_id = 0;
504 return true;
506 HandleError(FROM_HERE, status);
507 return false;
510 bool FileSystemDirectoryDatabase::AddFileInfo(
511 const FileInfo& info, FileId* file_id) {
512 if (!Init(REPAIR_ON_CORRUPTION))
513 return false;
514 DCHECK(file_id);
515 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
516 std::string child_id_string;
517 leveldb::Status status =
518 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
519 if (status.ok()) {
520 LOG(ERROR) << "File exists already!";
521 return false;
523 if (!status.IsNotFound()) {
524 HandleError(FROM_HERE, status);
525 return false;
528 if (!VerifyIsDirectory(info.parent_id))
529 return false;
531 // This would be a fine place to limit the number of files in a directory, if
532 // we decide to add that restriction.
534 FileId temp_id;
535 if (!GetLastFileId(&temp_id))
536 return false;
537 ++temp_id;
539 leveldb::WriteBatch batch;
540 if (!AddFileInfoHelper(info, temp_id, &batch))
541 return false;
543 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
544 status = db_->Write(leveldb::WriteOptions(), &batch);
545 if (!status.ok()) {
546 HandleError(FROM_HERE, status);
547 return false;
549 *file_id = temp_id;
550 return true;
553 bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id) {
554 if (!Init(REPAIR_ON_CORRUPTION))
555 return false;
556 leveldb::WriteBatch batch;
557 if (!RemoveFileInfoHelper(file_id, &batch))
558 return false;
559 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
560 if (!status.ok()) {
561 HandleError(FROM_HERE, status);
562 return false;
564 return true;
567 bool FileSystemDirectoryDatabase::UpdateFileInfo(
568 FileId file_id, const FileInfo& new_info) {
569 // TODO(ericu): We should also check to see that this doesn't create a loop,
570 // but perhaps only in a debug build.
571 if (!Init(REPAIR_ON_CORRUPTION))
572 return false;
573 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
574 FileInfo old_info;
575 if (!GetFileInfo(file_id, &old_info))
576 return false;
577 if (old_info.parent_id != new_info.parent_id &&
578 !VerifyIsDirectory(new_info.parent_id))
579 return false;
580 if (old_info.parent_id != new_info.parent_id ||
581 old_info.name != new_info.name) {
582 // Check for name clashes.
583 FileId temp_id;
584 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
585 LOG(ERROR) << "Name collision on move.";
586 return false;
589 leveldb::WriteBatch batch;
590 if (!RemoveFileInfoHelper(file_id, &batch) ||
591 !AddFileInfoHelper(new_info, file_id, &batch))
592 return false;
593 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
594 if (!status.ok()) {
595 HandleError(FROM_HERE, status);
596 return false;
598 return true;
601 bool FileSystemDirectoryDatabase::UpdateModificationTime(
602 FileId file_id, const base::Time& modification_time) {
603 FileInfo info;
604 if (!GetFileInfo(file_id, &info))
605 return false;
606 info.modification_time = modification_time;
607 Pickle pickle;
608 if (!PickleFromFileInfo(info, &pickle))
609 return false;
610 leveldb::Status status = db_->Put(
611 leveldb::WriteOptions(),
612 GetFileLookupKey(file_id),
613 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
614 pickle.size()));
615 if (!status.ok()) {
616 HandleError(FROM_HERE, status);
617 return false;
619 return true;
622 bool FileSystemDirectoryDatabase::OverwritingMoveFile(
623 FileId src_file_id, FileId dest_file_id) {
624 FileInfo src_file_info;
625 FileInfo dest_file_info;
627 if (!GetFileInfo(src_file_id, &src_file_info))
628 return false;
629 if (!GetFileInfo(dest_file_id, &dest_file_info))
630 return false;
631 if (src_file_info.is_directory() || dest_file_info.is_directory())
632 return false;
633 leveldb::WriteBatch batch;
634 // This is the only field that really gets moved over; if you add fields to
635 // FileInfo, e.g. ctime, they might need to be copied here.
636 dest_file_info.data_path = src_file_info.data_path;
637 if (!RemoveFileInfoHelper(src_file_id, &batch))
638 return false;
639 Pickle pickle;
640 if (!PickleFromFileInfo(dest_file_info, &pickle))
641 return false;
642 batch.Put(
643 GetFileLookupKey(dest_file_id),
644 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
645 pickle.size()));
646 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
647 if (!status.ok()) {
648 HandleError(FROM_HERE, status);
649 return false;
651 return true;
654 bool FileSystemDirectoryDatabase::GetNextInteger(int64* next) {
655 if (!Init(REPAIR_ON_CORRUPTION))
656 return false;
657 DCHECK(next);
658 std::string int_string;
659 leveldb::Status status =
660 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
661 if (status.ok()) {
662 int64 temp;
663 if (!base::StringToInt64(int_string, &temp)) {
664 LOG(ERROR) << "Hit database corruption!";
665 return false;
667 ++temp;
668 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
669 base::Int64ToString(temp));
670 if (!status.ok()) {
671 HandleError(FROM_HERE, status);
672 return false;
674 *next = temp;
675 return true;
677 if (!status.IsNotFound()) {
678 HandleError(FROM_HERE, status);
679 return false;
681 // The database must not yet exist; initialize it.
682 if (!StoreDefaultValues())
683 return false;
685 return GetNextInteger(next);
688 // static
689 bool FileSystemDirectoryDatabase::DestroyDatabase(const base::FilePath& path) {
690 std::string name = FilePathToString(path.Append(kDirectoryDatabaseName));
691 leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options());
692 if (status.ok())
693 return true;
694 LOG(WARNING) << "Failed to destroy a database with status " <<
695 status.ToString();
696 return false;
699 bool FileSystemDirectoryDatabase::Init(RecoveryOption recovery_option) {
700 if (db_.get())
701 return true;
703 std::string path =
704 FilePathToString(filesystem_data_directory_.Append(
705 kDirectoryDatabaseName));
706 leveldb::Options options;
707 options.create_if_missing = true;
708 leveldb::DB* db;
709 leveldb::Status status = leveldb::DB::Open(options, path, &db);
710 ReportInitStatus(status);
711 if (status.ok()) {
712 db_.reset(db);
713 return true;
715 HandleError(FROM_HERE, status);
717 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
718 // of Corruption error.
719 // Try to repair database even when IOError case.
720 if (!status.IsCorruption() && !status.IsIOError())
721 return false;
723 switch (recovery_option) {
724 case FAIL_ON_CORRUPTION:
725 return false;
726 case REPAIR_ON_CORRUPTION:
727 LOG(WARNING) << "Corrupted FileSystemDirectoryDatabase detected."
728 << " Attempting to repair.";
729 if (RepairDatabase(path))
730 return true;
731 LOG(WARNING) << "Failed to repair FileSystemDirectoryDatabase.";
732 // fall through
733 case DELETE_ON_CORRUPTION:
734 LOG(WARNING) << "Clearing FileSystemDirectoryDatabase.";
735 if (!file_util::Delete(filesystem_data_directory_, true))
736 return false;
737 if (!file_util::CreateDirectory(filesystem_data_directory_))
738 return false;
739 return Init(FAIL_ON_CORRUPTION);
742 NOTREACHED();
743 return false;
746 bool FileSystemDirectoryDatabase::RepairDatabase(const std::string& db_path) {
747 DCHECK(!db_.get());
748 if (!leveldb::RepairDB(db_path, leveldb::Options()).ok())
749 return false;
750 if (!Init(FAIL_ON_CORRUPTION))
751 return false;
752 if (IsFileSystemConsistent())
753 return true;
754 db_.reset();
755 return false;
758 bool FileSystemDirectoryDatabase::IsFileSystemConsistent() {
759 if (!Init(FAIL_ON_CORRUPTION))
760 return false;
761 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
762 return helper.IsFileSystemConsistent();
765 void FileSystemDirectoryDatabase::ReportInitStatus(
766 const leveldb::Status& status) {
767 base::Time now = base::Time::Now();
768 const base::TimeDelta minimum_interval =
769 base::TimeDelta::FromHours(kMinimumReportIntervalHours);
770 if (last_reported_time_ + minimum_interval >= now)
771 return;
772 last_reported_time_ = now;
774 if (status.ok()) {
775 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
776 INIT_STATUS_OK, INIT_STATUS_MAX);
777 } else if (status.IsCorruption()) {
778 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
779 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
780 } else if (status.IsIOError()) {
781 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
782 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
783 } else {
784 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
785 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
789 bool FileSystemDirectoryDatabase::StoreDefaultValues() {
790 // Verify that this is a totally new database, and initialize it.
791 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
792 iter->SeekToFirst();
793 if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
794 LOG(ERROR) << "File system origin database is corrupt!";
795 return false;
797 // This is always the first write into the database. If we ever add a
798 // version number, it should go in this transaction too.
799 FileInfo root;
800 root.parent_id = 0;
801 root.modification_time = base::Time::Now();
802 leveldb::WriteBatch batch;
803 if (!AddFileInfoHelper(root, 0, &batch))
804 return false;
805 batch.Put(LastFileIdKey(), base::Int64ToString(0));
806 batch.Put(LastIntegerKey(), base::Int64ToString(-1));
807 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
808 if (!status.ok()) {
809 HandleError(FROM_HERE, status);
810 return false;
812 return true;
815 bool FileSystemDirectoryDatabase::GetLastFileId(FileId* file_id) {
816 if (!Init(REPAIR_ON_CORRUPTION))
817 return false;
818 DCHECK(file_id);
819 std::string id_string;
820 leveldb::Status status =
821 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
822 if (status.ok()) {
823 if (!base::StringToInt64(id_string, file_id)) {
824 LOG(ERROR) << "Hit database corruption!";
825 return false;
827 return true;
829 if (!status.IsNotFound()) {
830 HandleError(FROM_HERE, status);
831 return false;
833 // The database must not yet exist; initialize it.
834 if (!StoreDefaultValues())
835 return false;
836 *file_id = 0;
837 return true;
840 bool FileSystemDirectoryDatabase::VerifyIsDirectory(FileId file_id) {
841 FileInfo info;
842 if (!file_id)
843 return true; // The root is a directory.
844 if (!GetFileInfo(file_id, &info))
845 return false;
846 if (!info.is_directory()) {
847 LOG(ERROR) << "New parent directory is a file!";
848 return false;
850 return true;
853 // This does very few safety checks!
854 bool FileSystemDirectoryDatabase::AddFileInfoHelper(
855 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
856 if (!VerifyDataPath(info.data_path)) {
857 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
858 return false;
860 std::string id_string = GetFileLookupKey(file_id);
861 if (!file_id) {
862 // The root directory doesn't need to be looked up by path from its parent.
863 DCHECK(!info.parent_id);
864 DCHECK(info.data_path.empty());
865 } else {
866 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
867 batch->Put(child_key, id_string);
869 Pickle pickle;
870 if (!PickleFromFileInfo(info, &pickle))
871 return false;
872 batch->Put(
873 id_string,
874 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
875 pickle.size()));
876 return true;
879 // This does very few safety checks!
880 bool FileSystemDirectoryDatabase::RemoveFileInfoHelper(
881 FileId file_id, leveldb::WriteBatch* batch) {
882 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
883 FileInfo info;
884 if (!GetFileInfo(file_id, &info))
885 return false;
886 if (info.data_path.empty()) { // It's a directory
887 std::vector<FileId> children;
888 // TODO(ericu): Make a faster is-the-directory-empty check.
889 if (!ListChildren(file_id, &children))
890 return false;
891 if (children.size()) {
892 LOG(ERROR) << "Can't remove a directory with children.";
893 return false;
896 batch->Delete(GetChildLookupKey(info.parent_id, info.name));
897 batch->Delete(GetFileLookupKey(file_id));
898 return true;
901 void FileSystemDirectoryDatabase::HandleError(
902 const tracked_objects::Location& from_here,
903 const leveldb::Status& status) {
904 LOG(ERROR) << "FileSystemDirectoryDatabase failed at: "
905 << from_here.ToString() << " with error: " << status.ToString();
906 db_.reset();
909 } // namespace fileapi