Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(148)

Unified Diff: webkit/fileapi/file_system_directory_database.cc

Issue 9910005: Add database recovery for FileSystemDirectoryDatabase. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: -TODO Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: webkit/fileapi/file_system_directory_database.cc
diff --git a/webkit/fileapi/file_system_directory_database.cc b/webkit/fileapi/file_system_directory_database.cc
index 6eb1df9567d42a6126b7af8d932938d048af22a5..a80743ce4221a3a65db9a8fe7883a709508ba394 100644
--- a/webkit/fileapi/file_system_directory_database.cc
+++ b/webkit/fileapi/file_system_directory_database.cc
@@ -14,6 +14,7 @@
#include "base/string_util.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+#include "webkit/fileapi/file_system_usage_cache.h"
#include "webkit/fileapi/file_system_util.h"
namespace {
@@ -105,6 +106,249 @@ std::string GetFileLookupKey(
return base::Int64ToString(file_id);
}
+// Assumptions:
+// - Any database entry is one of:
+// - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
+// - ("LAST_FILE_ID", "|last_file_id|"),
+// - ("LAST_INTEGER", "|last_integer|"),
+// - ("|file_id|", "pickled FileInfo")
+// whire FileInfo has |parent_id|, |data_path|, |name| and
+// |modification_time|,
+// Constraints:
+// - Each |file_id| has unique backing file.
ericu 2012/04/03 01:52:11 No--there are file_ids for directories, which have
tzik 2012/04/04 06:50:39 Done.
+// - Each file in |filesystem_data_directory_| has a database entry.
+// - Directory structure is tree, i.e. connected and acyclic.
+class DatabaseCheckHelper {
+ public:
+ typedef fileapi::FileSystemDirectoryDatabase::FileId FileId;
+ typedef fileapi::FileSystemDirectoryDatabase::FileInfo FileInfo;
+
+ DatabaseCheckHelper(fileapi::FileSystemDirectoryDatabase* dir_db,
+ leveldb::DB* db,
+ const FilePath& path);
+
+ bool IsFileSystemConsistent() {
+ return IsDatabaseEmpty() ||
+ (ScanDatabase() && ScanDirectory() && ScanHierarchy());
+ }
+
+ private:
+ bool IsDatabaseEmpty();
+ bool ScanDatabase();
ericu 2012/04/03 01:52:11 It's probably worth a comment that these need to b
tzik 2012/04/04 06:50:39 Done.
+ bool ScanDirectory();
+ bool ScanHierarchy();
+
+ fileapi::FileSystemDirectoryDatabase* dir_db_;
+ leveldb::DB* db_;
+ FilePath path_;
+
+ std::set<FilePath> files_in_db_;
+
+ size_t num_directories_in_db_;
+ size_t num_files_in_db_;
+ size_t num_hierarchy_links_in_db_;
+
+ FileId last_file_id_;
+ FileId last_integer_;
+};
+
+DatabaseCheckHelper::DatabaseCheckHelper(
+ fileapi::FileSystemDirectoryDatabase* dir_db,
+ leveldb::DB* db,
+ const FilePath& path)
+ : dir_db_(dir_db), db_(db), path_(path),
+ files_in_db_(),
ericu 2012/04/03 01:52:11 Omit the default constructor for files_in_db.
tzik 2012/04/04 06:50:39 Done.
+ num_directories_in_db_(0),
+ num_files_in_db_(0),
+ num_hierarchy_links_in_db_(0),
+ last_file_id_(-1), last_integer_(-1) {
+ DCHECK(dir_db_);
+ DCHECK(db_);
+ DCHECK(!path_.empty() && file_util::DirectoryExists(path_));
+}
+
+bool DatabaseCheckHelper::IsDatabaseEmpty() {
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
+ itr->SeekToFirst();
+ return !itr->Valid();
+}
+
+bool DatabaseCheckHelper::ScanDatabase() {
+ // Scans all database entries sequentially to verify each of them has unique
+ // backing file.
+ int64 max_file_id = -1;
+
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
+ for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
+ std::string key = itr->key().ToString();
+ if (StartsWithASCII(key, kChildLookupPrefix, true)) {
+ // key: "CHILD_OF:<parent_id>:<name>"
+ // value: "<child_id>"
+ ++num_hierarchy_links_in_db_;
+ } else if (key == kLastFileIdKey) {
+ // key: "LAST_FILE_ID"
+ // value: "<last_file_id>"
+ if (last_file_id_ >= 0 ||
+ !base::StringToInt64(itr->value().ToString(), &last_file_id_))
+ return false;
+
+ if (last_file_id_ < 0)
+ return false;
+ } else if (key == kLastIntegerKey) {
+ // key: "LAST_INTEGER"
+ // value: "<last_integer>"
+ if (last_integer_ >= 0 ||
+ !base::StringToInt64(itr->value().ToString(), &last_integer_))
+ return false;
+ } else {
+ // key: "<entry_id>"
+ // value: "<pickled FileInfo>"
+ FileInfo file_info;
+ if (!FileInfoFromPickle(
+ Pickle(itr->value().data(), itr->value().size()), &file_info))
+ return false;
+
+ FileId file_id = -1;
+ if (!base::StringToInt64(key, &file_id) || file_id < 0)
+ return false;
+
+ if (max_file_id < file_id)
+ max_file_id = file_id;
+
+ if (file_info.is_directory()) {
+ ++num_directories_in_db_;
ericu 2012/04/03 01:52:11 Verify that there's no data path in the pickle.
tzik 2012/04/04 06:50:39 Done. Implementation of FileInfo::is_directory() i
+ } else {
+ ++num_files_in_db_;
ericu 2012/04/03 01:52:11 Verify that the file id is unique.
tzik 2012/04/04 06:50:39 Done.
+
+ // Ensure any pair of file entry don't share their data_path.
+ if (!files_in_db_.insert(file_info.data_path).second)
+ return false;
+
+ // Ensure the backing file exists as a normal file.
+ base::PlatformFileInfo platform_file_info;
+ if (!file_util::GetFileInfo(
+ path_.Append(file_info.data_path), &platform_file_info) ||
+ platform_file_info.is_directory ||
+ platform_file_info.is_symbolic_link)
+ return false;
+ }
+ }
+ }
+
+ // TODO(tzik): Add constraint for |last_integer_| to avoid possible
+ // data path confliction on ObfuscatedFileUtil.
+ return max_file_id <= last_file_id_;
+}
+
+bool DatabaseCheckHelper::ScanDirectory() {
+ // Scans all local file system entries to verify each of them has a database
+ // entry.
+ const FilePath kExcludes[] = {
+ FilePath(kDirectoryDatabaseName),
+ FilePath(fileapi::FileSystemUsageCache::kUsageFileName),
+ };
+
+ std::stack<FilePath> pending_directories;
+ pending_directories.push(FilePath());
+
+ while (!pending_directories.empty()) {
+ FilePath dir_path = pending_directories.top();
+ pending_directories.pop();
+
+ FilePath absolute_path = dir_path.empty() ? path_ : path_.Append(dir_path);
+ file_util::FileEnumerator file_enum(
+ path_.Append(dir_path), false /* recursive */,
+ static_cast<file_util::FileEnumerator::FileType>(
+ file_util::FileEnumerator::DIRECTORIES |
+ file_util::FileEnumerator::FILES));
+
+ FilePath file_path;
+ while (!(file_path = file_enum.Next()).empty()) {
+ file_util::FileEnumerator::FindInfo find_info;
+ file_enum.GetFindInfo(&find_info);
+
+ if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
+ dir_path) != kExcludes + arraysize(kExcludes))
ericu 2012/04/03 01:52:11 Add a counter to make sure that you find the right
tzik 2012/04/04 06:50:39 IMO, it is not needed for now. After patchset 5, t
ericu 2012/04/05 00:22:26 That works.
+ continue;
+
+ if (file_util::FileEnumerator::IsLink(find_info))
+ return false;
+
+ FilePath data_path = dir_path.Append(
+ file_util::FileEnumerator::GetFilename(find_info));
+ pending_directories.push(data_path);
ericu 2012/04/03 01:52:11 You're adding all files to pending_directories, no
tzik 2012/04/04 06:50:39 Done. This part of CL was broken..
+ if (file_util::FileEnumerator::IsDirectory(find_info))
+ continue;
+
+ // Check if the file has a database entry.
+ std::set<FilePath>::iterator itr = files_in_db_.find(data_path);
+ if (itr == files_in_db_.end())
+ return false;
+ files_in_db_.erase(itr);
+ }
+ }
+
+ return files_in_db_.empty();
+}
+
+bool DatabaseCheckHelper::ScanHierarchy() {
+ size_t visited_directories = 0;
+ size_t visited_files = 0;
+ size_t visited_links = 0;
+
+ std::stack<FileId> directories;
+ directories.push(0);
+
+ // Check if the root directory exists as a directory.
+ FileInfo file_info;
+ if (!dir_db_->GetFileInfo(0, &file_info))
+ return false;
+ if (file_info.parent_id != 0 ||
+ !file_info.is_directory())
+ return false;
+
+ while (!directories.empty()) {
+ ++visited_directories;
+ FileId dir_id = directories.top();
+ directories.pop();
+
+ std::vector<FileId> children;
+ if (!dir_db_->ListChildren(dir_id, &children))
+ return false;
+ for (std::vector<FileId>::iterator itr = children.begin();
+ itr != children.end();
+ ++itr) {
+ // Any directory must not have root directory as child.
+ if (!*itr)
+ return false;
+
+ // Check if the child knows the parent as its parent.
+ FileInfo file_info;
+ if (!dir_db_->GetFileInfo(*itr, &file_info))
+ return false;
+ if (file_info.parent_id != dir_id)
+ return false;
+
+ // Check if the parent knows the name of its child correctly.
+ FileId file_id;
+ if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
+ file_id != *itr)
+ return false;
+
+ if (file_info.is_directory())
+ directories.push(*itr);
+ else
+ ++visited_files;
+ ++visited_links;
+ }
+ }
+
+ // Check if we've visited all database entries.
+ return num_directories_in_db_ == visited_directories &&
+ num_files_in_db_ == visited_files &&
+ num_hierarchy_links_in_db_ == visited_links;
+}
+
} // namespace
namespace fileapi {
@@ -125,7 +369,7 @@ FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() {
bool FileSystemDirectoryDatabase::GetChildWithName(
FileId parent_id, const FilePath::StringType& name, FileId* child_id) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(child_id);
std::string child_key = GetChildLookupKey(parent_id, name);
@@ -166,7 +410,7 @@ bool FileSystemDirectoryDatabase::GetFileWithPath(
bool FileSystemDirectoryDatabase::ListChildren(
FileId parent_id, std::vector<FileId>* children) {
// Check to add later: fail if parent is a file, at least in debug builds.
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(children);
std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
@@ -189,7 +433,7 @@ bool FileSystemDirectoryDatabase::ListChildren(
}
bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(info);
std::string file_key = GetFileLookupKey(file_id);
@@ -216,7 +460,7 @@ bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
bool FileSystemDirectoryDatabase::AddFileInfo(
const FileInfo& info, FileId* file_id) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(file_id);
std::string child_key = GetChildLookupKey(info.parent_id, info.name);
@@ -258,7 +502,7 @@ bool FileSystemDirectoryDatabase::AddFileInfo(
}
bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
leveldb::WriteBatch batch;
if (!RemoveFileInfoHelper(file_id, &batch))
@@ -275,7 +519,7 @@ bool FileSystemDirectoryDatabase::UpdateFileInfo(
FileId file_id, const FileInfo& new_info) {
// TODO: We should also check to see that this doesn't create a loop, but
// perhaps only in a debug build.
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
FileInfo old_info;
@@ -359,7 +603,7 @@ bool FileSystemDirectoryDatabase::OverwritingMoveFile(
}
bool FileSystemDirectoryDatabase::GetNextInteger(int64* next) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(next);
std::string int_string;
@@ -421,15 +665,42 @@ bool FileSystemDirectoryDatabase::Init(RecoveryOption recovery_option) {
}
HandleError(FROM_HERE, status);
- if (recovery_option == FAIL_ON_CORRUPTION)
- return false;
+ switch (recovery_option) {
+ case FAIL_ON_CORRUPTION:
+ return false;
+ case REPAIR_ON_CORRUPTION:
+ if (RepairDatabase(path))
+ return Init(FAIL_ON_CORRUPTION);
+ // fall through
+ case DELETE_ON_CORRUPTION:
+ if (!file_util::Delete(filesystem_data_directory_, true))
+ return false;
+ if (!file_util::CreateDirectory(filesystem_data_directory_))
+ return false;
+ return Init(FAIL_ON_CORRUPTION);
+ }
- DCHECK_EQ(DELETE_ON_CORRUPTION, recovery_option);
- if (!file_util::Delete(filesystem_data_directory_, true))
+ NOTREACHED();
+ return false;
+}
+
+bool FileSystemDirectoryDatabase::RepairDatabase(const std::string& db_path) {
+ DCHECK(!db_.get());
+ if (!leveldb::RepairDB(db_path, leveldb::Options()).ok())
+ return false;
+ if (!Init(FAIL_ON_CORRUPTION))
ericu 2012/04/03 01:52:11 You're calling Init(FAIL_ON_CORRUPTION) both here
tzik 2012/04/04 06:50:39 Done.
return false;
- if (!file_util::CreateDirectory(filesystem_data_directory_))
+ if (IsFileSystemConsistent())
+ return true;
+ db_.reset();
+ return false;
+}
+
+bool FileSystemDirectoryDatabase::IsFileSystemConsistent() {
+ if (!Init(FAIL_ON_CORRUPTION))
return false;
- return Init(FAIL_ON_CORRUPTION);
+ DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
+ return helper.IsFileSystemConsistent();
}
void FileSystemDirectoryDatabase::ReportInitStatus(
@@ -477,7 +748,7 @@ bool FileSystemDirectoryDatabase::StoreDefaultValues() {
}
bool FileSystemDirectoryDatabase::GetLastFileId(FileId* file_id) {
- if (!Init(FAIL_ON_CORRUPTION))
+ if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(file_id);
std::string id_string;

Powered by Google App Engine
This is Rietveld 408576698