diff options
Diffstat (limited to 'webkit/fileapi')
-rw-r--r-- | webkit/fileapi/file_system_database_test_helper.cc | 84 | ||||
-rw-r--r-- | webkit/fileapi/file_system_database_test_helper.h | 23 | ||||
-rw-r--r-- | webkit/fileapi/file_system_directory_database.cc | 321 | ||||
-rw-r--r-- | webkit/fileapi/file_system_directory_database.h | 9 | ||||
-rw-r--r-- | webkit/fileapi/file_system_directory_database_unittest.cc | 244 | ||||
-rw-r--r-- | webkit/fileapi/file_system_origin_database_unittest.cc | 69 | ||||
-rw-r--r-- | webkit/fileapi/file_system_usage_cache.cc | 3 | ||||
-rw-r--r-- | webkit/fileapi/file_system_usage_cache.h | 2 | ||||
-rw-r--r-- | webkit/fileapi/file_system_usage_cache_unittest.cc | 2 | ||||
-rw-r--r-- | webkit/fileapi/sandbox_mount_point_provider.cc | 4 |
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( |