summaryrefslogtreecommitdiffstats
path: root/webkit/fileapi
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/fileapi')
-rw-r--r--webkit/fileapi/file_system_database_test_helper.cc84
-rw-r--r--webkit/fileapi/file_system_database_test_helper.h23
-rw-r--r--webkit/fileapi/file_system_directory_database.cc321
-rw-r--r--webkit/fileapi/file_system_directory_database.h9
-rw-r--r--webkit/fileapi/file_system_directory_database_unittest.cc244
-rw-r--r--webkit/fileapi/file_system_origin_database_unittest.cc69
-rw-r--r--webkit/fileapi/file_system_usage_cache.cc3
-rw-r--r--webkit/fileapi/file_system_usage_cache.h2
-rw-r--r--webkit/fileapi/file_system_usage_cache_unittest.cc2
-rw-r--r--webkit/fileapi/sandbox_mount_point_provider.cc4
10 files changed, 668 insertions, 93 deletions
diff --git a/webkit/fileapi/file_system_database_test_helper.cc b/webkit/fileapi/file_system_database_test_helper.cc
new file mode 100644
index 0000000..28951d08
--- /dev/null
+++ b/webkit/fileapi/file_system_database_test_helper.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/fileapi/file_system_database_test_helper.h"
+
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+void CorruptDatabase(const FilePath& db_path,
+ leveldb::FileType type,
+ ptrdiff_t offset,
+ size_t size) {
+ file_util::FileEnumerator file_enum(
+ db_path, false /* recursive */,
+ static_cast<file_util::FileEnumerator::FileType>(
+ file_util::FileEnumerator::DIRECTORIES |
+ file_util::FileEnumerator::FILES));
+ FilePath file_path;
+ FilePath picked_file_path;
+ uint64 picked_file_number = kuint64max;
+
+ while (!(file_path = file_enum.Next()).empty()) {
+ uint64 number = kuint64max;
+ leveldb::FileType file_type;
+ EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()),
+ &number, &file_type));
+ if (file_type == type &&
+ (picked_file_number == kuint64max || picked_file_number < number)) {
+ picked_file_path = file_path;
+ picked_file_number = number;
+ }
+ }
+
+ EXPECT_FALSE(picked_file_path.empty());
+ EXPECT_NE(kuint64max, picked_file_number);
+
+ bool created = true;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file =
+ CreatePlatformFile(picked_file_path,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, error);
+ EXPECT_FALSE(created);
+
+ base::PlatformFileInfo file_info;
+ EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info));
+ if (offset < 0)
+ offset += file_info.size;
+ EXPECT_GE(offset, 0);
+ EXPECT_LE(offset, file_info.size);
+
+ size = std::min(size, static_cast<size_t>(file_info.size - offset));
+
+ std::vector<char> buf(size);
+ int read_size = base::ReadPlatformFile(file, offset,
+ vector_as_array(&buf), buf.size());
+ EXPECT_LT(0, read_size);
+ EXPECT_GE(buf.size(), static_cast<size_t>(read_size));
+ buf.resize(read_size);
+
+ std::transform(buf.begin(), buf.end(), buf.begin(),
+ std::logical_not<char>());
+
+ int written_size = base::WritePlatformFile(file, offset,
+ vector_as_array(&buf), buf.size());
+ EXPECT_GT(written_size, 0);
+ EXPECT_EQ(buf.size(), static_cast<size_t>(written_size));
+
+ base::ClosePlatformFile(file);
+}
+
+} // namespace fileapi
diff --git a/webkit/fileapi/file_system_database_test_helper.h b/webkit/fileapi/file_system_database_test_helper.h
new file mode 100644
index 0000000..87e35e0
--- /dev/null
+++ b/webkit/fileapi/file_system_database_test_helper.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_FILEAPI_FILE_SYSTEM_DATABASE_TEST_HELPER_H_
+#define WEBKIT_FILEAPI_FILE_SYSTEM_DATABASE_TEST_HELPER_H_
+
+#include <cstddef>
+
+#include "third_party/leveldatabase/src/db/filename.h"
+
+class FilePath;
+
+namespace fileapi {
+
+void CorruptDatabase(const FilePath& db_path,
+ leveldb::FileType type,
+ ptrdiff_t offset,
+ size_t size);
+
+} // namespace fileapi
+
+#endif // WEBKIT_FILEAPI_FILE_SYSTEM_DATABASE_TEST_HELPER_H_
diff --git a/webkit/fileapi/file_system_directory_database.cc b/webkit/fileapi/file_system_directory_database.cc
index b5c92ab..048f193 100644
--- a/webkit/fileapi/file_system_directory_database.cc
+++ b/webkit/fileapi/file_system_directory_database.cc
@@ -4,6 +4,7 @@
#include "webkit/fileapi/file_system_directory_database.h"
+#include <algorithm>
#include <math.h>
#include "base/file_util.h"
@@ -14,6 +15,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 {
@@ -106,6 +108,266 @@ 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")
+// where FileInfo has |parent_id|, |data_path|, |name| and
+// |modification_time|,
+// Constraints:
+// - Each file in the database has unique backing file.
+// - 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();
+ // These 3 methods need to be called in the order. Each method requires its
+ // previous method finished successfully. They also require the database is
+ // not empty.
+ bool ScanDatabase();
+ 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),
+ 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;
+ std::set<FileId> file_ids;
+
+ 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_ids.insert(file_id).second)
+ return false;
+
+ if (file_info.is_directory()) {
+ ++num_directories_in_db_;
+ DCHECK(file_info.data_path.empty());
+ } else {
+ // 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) {
+ // leveldb::Iterator iterates a snapshot of the database.
+ // So even after RemoveFileInfo() call, we'll visit hierarchy link
+ // from |parent_id| to |file_id|.
+ if (!dir_db_->RemoveFileInfo(file_id))
+ return false;
+ --num_hierarchy_links_in_db_;
+ files_in_db_.erase(file_info.data_path);
+ } else {
+ ++num_files_in_db_;
+ }
+ }
+ }
+ }
+
+ // 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),
+ };
+
+ // Any path in |pending_directories| is relative to |path_|.
+ std::stack<FilePath> pending_directories;
+ pending_directories.push(FilePath());
+
+ while (!pending_directories.empty()) {
+ FilePath dir_path = pending_directories.top();
+ pending_directories.pop();
+
+ file_util::FileEnumerator file_enum(
+ dir_path.empty() ? path_ : path_.Append(dir_path),
+ false /* recursive */,
+ static_cast<file_util::FileEnumerator::FileType>(
+ file_util::FileEnumerator::DIRECTORIES |
+ file_util::FileEnumerator::FILES));
+
+ FilePath absolute_file_path;
+ while (!(absolute_file_path = file_enum.Next()).empty()) {
+ file_util::FileEnumerator::FindInfo find_info;
+ file_enum.GetFindInfo(&find_info);
+
+ FilePath relative_file_path;
+ if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
+ return false;
+
+ if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
+ relative_file_path) != kExcludes + arraysize(kExcludes))
+ continue;
+
+ if (file_util::FileEnumerator::IsDirectory(find_info)) {
+ pending_directories.push(relative_file_path);
+ continue;
+ }
+
+ // Check if the file has a database entry.
+ std::set<FilePath>::iterator itr = files_in_db_.find(relative_file_path);
+ if (itr == files_in_db_.end()) {
+ if (!file_util::Delete(absolute_file_path, false))
+ return false;
+ } else {
+ 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 {
@@ -126,7 +388,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);
@@ -167,7 +429,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);
@@ -190,7 +452,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);
@@ -217,7 +479,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);
@@ -259,7 +521,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))
@@ -276,7 +538,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;
@@ -360,7 +622,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;
@@ -422,15 +684,46 @@ 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:
+ LOG(WARNING) << "Corrupted FileSystemDirectoryDatabase detected."
+ << " Attempting to repair.";
+ if (RepairDatabase(path))
+ return true;
+ LOG(WARNING) << "Failed to repair FileSystemDirectoryDatabase.";
+ // fall through
+ case DELETE_ON_CORRUPTION:
+ LOG(WARNING) << "Clearing FileSystemDirectoryDatabase.";
+ 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))
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(
@@ -484,7 +777,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;
diff --git a/webkit/fileapi/file_system_directory_database.h b/webkit/fileapi/file_system_directory_database.h
index 47ac60a..fb51f0d 100644
--- a/webkit/fileapi/file_system_directory_database.h
+++ b/webkit/fileapi/file_system_directory_database.h
@@ -32,8 +32,6 @@ namespace fileapi {
// TODO(ericu): Safe mode, which does more checks such as the above on debug
// builds.
-// TODO(ericu): FSCK, for a full-database check [data file validation possibly
-// done elsewhere].
// TODO(ericu): Add a method that will give a unique filename for a data file.
class FileSystemDirectoryDatabase {
public:
@@ -86,15 +84,22 @@ class FileSystemDirectoryDatabase {
// creation/destruction of FileSystemDirectoryDatabase objects.
bool GetNextInteger(int64* next);
+ // Returns true if the database looks consistent with local filesystem.
+ bool IsFileSystemConsistent();
+
static bool DestroyDatabase(const FilePath& path);
private:
enum RecoveryOption {
DELETE_ON_CORRUPTION,
+ REPAIR_ON_CORRUPTION,
FAIL_ON_CORRUPTION,
};
+ friend class FileSystemDirectoryDatabaseTest;
+
bool Init(RecoveryOption recovery_option);
+ bool RepairDatabase(const std::string& db_path);
void ReportInitStatus(const leveldb::Status& status);
bool StoreDefaultValues();
bool GetLastFileId(FileId* file_id);
diff --git a/webkit/fileapi/file_system_directory_database_unittest.cc b/webkit/fileapi/file_system_directory_database_unittest.cc
index 2f78449..55fe68c 100644
--- a/webkit/fileapi/file_system_directory_database_unittest.cc
+++ b/webkit/fileapi/file_system_directory_database_unittest.cc
@@ -6,14 +6,25 @@
#include <math.h>
+#include "base/file_util.h"
+#include "base/platform_file.h"
#include "base/memory/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "webkit/fileapi/file_system_database_test_helper.h"
+#include "webkit/fileapi/file_system_util.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
namespace fileapi {
+namespace {
+const FilePath::CharType kDirectoryDatabaseName[] = FPL("Paths");
+}
+
class FileSystemDirectoryDatabaseTest : public testing::Test {
public:
typedef FileSystemDirectoryDatabase::FileId FileId;
@@ -29,10 +40,14 @@ class FileSystemDirectoryDatabaseTest : public testing::Test {
}
void InitDatabase() {
- // First reset() is to avoid multiple database instance for single
- // directory at once.
+ // Call CloseDatabase() to avoid having multiple database instances for
+ // single directory at once.
+ CloseDatabase();
+ db_.reset(new FileSystemDirectoryDatabase(path()));
+ }
+
+ void CloseDatabase() {
db_.reset();
- db_.reset(new FileSystemDirectoryDatabase(base_.path()));
}
bool AddFileInfo(FileId parent_id, const FilePath::StringType& name) {
@@ -43,6 +58,87 @@ class FileSystemDirectoryDatabaseTest : public testing::Test {
return db_->AddFileInfo(info, &file_id);
}
+ void CreateDirectory(FileId parent_id,
+ const FilePath::StringType& name,
+ FileId* file_id_out) {
+ FileId file_id;
+
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ ASSERT_TRUE(db_->AddFileInfo(info, &file_id));
+
+ if (file_id_out)
+ *file_id_out = file_id;
+ }
+
+ void CreateFile(FileId parent_id,
+ const FilePath::StringType& name,
+ const FilePath::StringType& data_path,
+ FileId* file_id_out) {
+ FileId file_id;
+
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ info.data_path = FilePath(data_path).NormalizePathSeparators();
+ ASSERT_TRUE(db_->AddFileInfo(info, &file_id));
+
+ FilePath local_path = path().Append(data_path);
+ if (!file_util::DirectoryExists(local_path.DirName()))
+ ASSERT_TRUE(file_util::CreateDirectory(local_path.DirName()));
+
+ bool created = false;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file = base::CreatePlatformFile(
+ local_path,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_TRUE(created);
+ ASSERT_TRUE(base::ClosePlatformFile(file));
+
+ if (file_id_out)
+ *file_id_out = file_id;
+ }
+
+ void ClearDatabaseAndDirectory() {
+ db_.reset();
+ ASSERT_TRUE(file_util::Delete(path(), true /* recursive */));
+ ASSERT_TRUE(file_util::CreateDirectory(path()));
+ db_.reset(new FileSystemDirectoryDatabase(path()));
+ }
+
+ bool RepairDatabase() {
+ return db()->RepairDatabase(
+ FilePathToString(path().Append(kDirectoryDatabaseName)));
+ }
+
+ const FilePath& path() {
+ return base_.path();
+ }
+
+ // Makes link from |parent_id| to |child_id| with |name|.
+ void MakeHierarchyLink(FileId parent_id,
+ FileId child_id,
+ const FilePath::StringType& name) {
+ ASSERT_TRUE(db()->db_->Put(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(parent_id) + ":" +
+ FilePathToString(FilePath(name)),
+ base::Int64ToString(child_id)).ok());
+ }
+
+ // Deletes link from parent of |file_id| to |file_id|.
+ void DeleteHierarchyLink(FileId file_id) {
+ FileInfo file_info;
+ ASSERT_TRUE(db()->GetFileInfo(file_id, &file_info));
+ ASSERT_TRUE(db()->db_->Delete(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(file_info.parent_id) + ":" +
+ FilePathToString(FilePath(file_info.name))).ok());
+ }
+
protected:
// Common temp base for nondestructive uses.
ScopedTempDir base_;
@@ -403,7 +499,7 @@ TEST_F(FileSystemDirectoryDatabaseTest, TestOverwritingMoveFileSuccess) {
}
TEST_F(FileSystemDirectoryDatabaseTest, TestGetNextInteger) {
- int64 next;
+ int64 next = -1;
EXPECT_TRUE(db()->GetNextInteger(&next));
EXPECT_EQ(0, next);
EXPECT_TRUE(db()->GetNextInteger(&next));
@@ -418,4 +514,144 @@ TEST_F(FileSystemDirectoryDatabaseTest, TestGetNextInteger) {
EXPECT_EQ(4, next);
}
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_Empty) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ int64 next = -1;
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(0, next);
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_Consistent) {
+ FileId dir_id;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateDirectory(0, FPL("bar"), &dir_id);
+ CreateFile(dir_id, FPL("baz"), FPL("fuga"), NULL);
+ CreateFile(dir_id, FPL("fizz"), FPL("buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest,
+ TestConsistencyCheck_BackingMultiEntry) {
+ const FilePath::CharType kBackingFileName[] = FPL("the celeb");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(file_util::Delete(path().Append(kBackingFileName), false));
+ CreateFile(0, FPL("bar"), kBackingFileName, NULL);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_FileLost) {
+ const FilePath::CharType kBackingFileName[] = FPL("hoge");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(file_util::Delete(path().Append(kBackingFileName), false));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_OrphanFile) {
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ bool created = false;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file = base::CreatePlatformFile(
+ path().Append(FPL("Orphan File")),
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_TRUE(created);
+ ASSERT_TRUE(base::ClosePlatformFile(file));
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_RootLoop) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(0, 0, FPL(""));
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_DirectoryLoop) {
+ FileId dir1_id;
+ FileId dir2_id;
+ FilePath::StringType dir1_name = FPL("foo");
+ CreateDirectory(0, dir1_name, &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(dir2_id, dir1_id, dir1_name);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_NameMismatch) {
+ FileId dir_id;
+ FileId file_id;
+ CreateDirectory(0, FPL("foo"), &dir_id);
+ CreateFile(dir_id, FPL("bar"), FPL("hoge/fuga/piyo"), &file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(file_id);
+ MakeHierarchyLink(dir_id, file_id, FPL("baz"));
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestConsistencyCheck_WreckedEntries) {
+ FileId dir1_id;
+ FileId dir2_id;
+ CreateDirectory(0, FPL("foo"), &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+ CreateFile(dir2_id, FPL("baz"), FPL("fizz/buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(dir2_id); // Delete link from |dir1_id| to |dir2_id|.
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestRepairDatabase_Success) {
+ FilePath::StringType kFileName = FPL("bar");
+
+ FileId file_id_prev;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);
+
+ const FilePath kDatabaseDirectory = path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_EQ(file_id_prev, file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestRepairDatabase_Failure) {
+ FilePath::StringType kFileName = FPL("bar");
+
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), NULL);
+
+ const FilePath kDatabaseDirectory = path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ CorruptDatabase(kDatabaseDirectory, leveldb::kLogFile,
+ -1, 1);
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
} // namespace fileapi
diff --git a/webkit/fileapi/file_system_origin_database_unittest.cc b/webkit/fileapi/file_system_origin_database_unittest.cc
index e92b814..68ad6e3 100644
--- a/webkit/fileapi/file_system_origin_database_unittest.cc
+++ b/webkit/fileapi/file_system_origin_database_unittest.cc
@@ -15,83 +15,16 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/db/filename.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "webkit/fileapi/file_system_database_test_helper.h"
#include "webkit/fileapi/file_system_origin_database.h"
#include "webkit/fileapi/file_system_util.h"
namespace fileapi {
namespace {
-
const FilePath::CharType kFileSystemDirName[] =
FILE_PATH_LITERAL("File System");
const FilePath::CharType kOriginDatabaseName[] = FILE_PATH_LITERAL("Origins");
-
-void CorruptDatabase(const FilePath& db_path,
- leveldb::FileType type,
- ptrdiff_t offset,
- size_t size) {
- file_util::FileEnumerator file_enum(
- db_path, false /* recursive */,
- static_cast<file_util::FileEnumerator::FileType>(
- file_util::FileEnumerator::DIRECTORIES |
- file_util::FileEnumerator::FILES));
- FilePath file_path;
- FilePath picked_file_path;
- uint64 picked_file_number = kuint64max;
-
- while (!(file_path = file_enum.Next()).empty()) {
- uint64 number = kuint64max;
- leveldb::FileType file_type;
- EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()),
- &number, &file_type));
- if (file_type == type &&
- (picked_file_number == kuint64max || picked_file_number < number)) {
- picked_file_path = file_path;
- picked_file_number = number;
- }
- }
-
- EXPECT_FALSE(picked_file_path.empty());
- EXPECT_NE(kuint64max, picked_file_number);
-
- bool created = true;
- base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
- base::PlatformFile file =
- CreatePlatformFile(picked_file_path,
- base::PLATFORM_FILE_OPEN |
- base::PLATFORM_FILE_READ |
- base::PLATFORM_FILE_WRITE,
- &created, &error);
- EXPECT_EQ(base::PLATFORM_FILE_OK, error);
- EXPECT_FALSE(created);
-
- base::PlatformFileInfo file_info;
- EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info));
- if (offset < 0)
- offset += file_info.size;
- EXPECT_GE(offset, 0);
- EXPECT_LE(offset, file_info.size);
-
- size = std::min(size, static_cast<size_t>(file_info.size - offset));
-
- std::vector<char> buf(size);
- int read_size = base::ReadPlatformFile(file, offset,
- vector_as_array(&buf), buf.size());
- EXPECT_LT(0, read_size);
- EXPECT_GE(buf.size(), static_cast<size_t>(read_size));
- buf.resize(read_size);
-
- std::transform(buf.begin(), buf.end(), buf.begin(),
- std::logical_not<char>());
-
- int written_size = base::WritePlatformFile(file, offset,
- vector_as_array(&buf), buf.size());
- EXPECT_GT(written_size, 0);
- EXPECT_EQ(buf.size(), static_cast<size_t>(written_size));
-
- base::ClosePlatformFile(file);
-}
-
} // namespace
TEST(FileSystemOriginDatabaseTest, BasicTest) {
diff --git a/webkit/fileapi/file_system_usage_cache.cc b/webkit/fileapi/file_system_usage_cache.cc
index 18d13c7..d1bc5c0 100644
--- a/webkit/fileapi/file_system_usage_cache.cc
+++ b/webkit/fileapi/file_system_usage_cache.cc
@@ -10,7 +10,8 @@
namespace fileapi {
-const char FileSystemUsageCache::kUsageFileName[] = ".usage";
+const FilePath::CharType FileSystemUsageCache::kUsageFileName[] =
+ FILE_PATH_LITERAL(".usage");
const char FileSystemUsageCache::kUsageFileHeader[] = "FSU4";
const int FileSystemUsageCache::kUsageFileHeaderSize = 4;
diff --git a/webkit/fileapi/file_system_usage_cache.h b/webkit/fileapi/file_system_usage_cache.h
index 71198ab..5f9c3f6 100644
--- a/webkit/fileapi/file_system_usage_cache.h
+++ b/webkit/fileapi/file_system_usage_cache.h
@@ -42,7 +42,7 @@ class FileSystemUsageCache {
static bool Exists(const FilePath& usage_file_path);
static bool Delete(const FilePath& usage_file_path);
- static const char kUsageFileName[];
+ static const FilePath::CharType kUsageFileName[];
static const char kUsageFileHeader[];
static const int kUsageFileSize;
static const int kUsageFileHeaderSize;
diff --git a/webkit/fileapi/file_system_usage_cache_unittest.cc b/webkit/fileapi/file_system_usage_cache_unittest.cc
index 3fa92dd..90c6a4d 100644
--- a/webkit/fileapi/file_system_usage_cache_unittest.cc
+++ b/webkit/fileapi/file_system_usage_cache_unittest.cc
@@ -21,7 +21,7 @@ class FileSystemUsageCacheTest : public testing::Test {
protected:
FilePath GetUsageFilePath() {
- return data_dir_.path().AppendASCII(FileSystemUsageCache::kUsageFileName);
+ return data_dir_.path().Append(FileSystemUsageCache::kUsageFileName);
}
private:
diff --git a/webkit/fileapi/sandbox_mount_point_provider.cc b/webkit/fileapi/sandbox_mount_point_provider.cc
index 138629c..777b5ba 100644
--- a/webkit/fileapi/sandbox_mount_point_provider.cc
+++ b/webkit/fileapi/sandbox_mount_point_provider.cc
@@ -522,7 +522,7 @@ int64 SandboxMountPointProvider::GetOriginUsageOnFileThread(
GetBaseDirectoryForOriginAndType(origin_url, type, false);
if (base_path.empty() || !file_util::DirectoryExists(base_path)) return 0;
FilePath usage_file_path =
- base_path.AppendASCII(FileSystemUsageCache::kUsageFileName);
+ base_path.Append(FileSystemUsageCache::kUsageFileName);
bool is_valid = FileSystemUsageCache::IsValid(usage_file_path);
int32 dirty_status = FileSystemUsageCache::GetDirty(usage_file_path);
@@ -621,7 +621,7 @@ FilePath SandboxMountPointProvider::GetUsageCachePathForOriginAndType(
GetBaseDirectoryForOriginAndType(origin_url, type, false);
if (base_path.empty())
return FilePath();
- return base_path.AppendASCII(FileSystemUsageCache::kUsageFileName);
+ return base_path.Append(FileSystemUsageCache::kUsageFileName);
}
FilePath SandboxMountPointProvider::OldCreateFileSystemRootPath(