| Index: webkit/browser/fileapi/sandbox_directory_database.cc
|
| diff --git a/webkit/browser/fileapi/sandbox_directory_database.cc b/webkit/browser/fileapi/sandbox_directory_database.cc
|
| deleted file mode 100644
|
| index 7183fb8e1397efc6e73ea07ded96a60389c49d2e..0000000000000000000000000000000000000000
|
| --- a/webkit/browser/fileapi/sandbox_directory_database.cc
|
| +++ /dev/null
|
| @@ -1,939 +0,0 @@
|
| -// 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/browser/fileapi/sandbox_directory_database.h"
|
| -
|
| -#include <math.h>
|
| -#include <algorithm>
|
| -#include <set>
|
| -#include <stack>
|
| -
|
| -#include "base/file_util.h"
|
| -#include "base/files/file_enumerator.h"
|
| -#include "base/location.h"
|
| -#include "base/metrics/histogram.h"
|
| -#include "base/pickle.h"
|
| -#include "base/strings/string_number_conversions.h"
|
| -#include "base/strings/string_util.h"
|
| -#include "third_party/leveldatabase/src/include/leveldb/db.h"
|
| -#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
|
| -#include "webkit/browser/fileapi/file_system_usage_cache.h"
|
| -#include "webkit/common/fileapi/file_system_util.h"
|
| -
|
| -namespace {
|
| -
|
| -bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
|
| - Pickle* pickle) {
|
| - DCHECK(pickle);
|
| - std::string data_path;
|
| - // Round off here to match the behavior of the filesystem on real files.
|
| - base::Time time =
|
| - base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
|
| - std::string name;
|
| -
|
| - data_path = storage::FilePathToString(info.data_path);
|
| - name = storage::FilePathToString(base::FilePath(info.name));
|
| -
|
| - if (pickle->WriteInt64(info.parent_id) &&
|
| - pickle->WriteString(data_path) &&
|
| - pickle->WriteString(name) &&
|
| - pickle->WriteInt64(time.ToInternalValue()))
|
| - return true;
|
| -
|
| - NOTREACHED();
|
| - return false;
|
| -}
|
| -
|
| -bool FileInfoFromPickle(const Pickle& pickle,
|
| - storage::SandboxDirectoryDatabase::FileInfo* info) {
|
| - PickleIterator iter(pickle);
|
| - std::string data_path;
|
| - std::string name;
|
| - int64 internal_time;
|
| -
|
| - if (pickle.ReadInt64(&iter, &info->parent_id) &&
|
| - pickle.ReadString(&iter, &data_path) &&
|
| - pickle.ReadString(&iter, &name) &&
|
| - pickle.ReadInt64(&iter, &internal_time)) {
|
| - info->data_path = storage::StringToFilePath(data_path);
|
| - info->name = storage::StringToFilePath(name).value();
|
| - info->modification_time = base::Time::FromInternalValue(internal_time);
|
| - return true;
|
| - }
|
| - LOG(ERROR) << "Pickle could not be digested!";
|
| - return false;
|
| -}
|
| -
|
| -const base::FilePath::CharType kDirectoryDatabaseName[] =
|
| - FILE_PATH_LITERAL("Paths");
|
| -const char kChildLookupPrefix[] = "CHILD_OF:";
|
| -const char kChildLookupSeparator[] = ":";
|
| -const char kLastFileIdKey[] = "LAST_FILE_ID";
|
| -const char kLastIntegerKey[] = "LAST_INTEGER";
|
| -const int64 kMinimumReportIntervalHours = 1;
|
| -const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
|
| -const char kDatabaseRepairHistogramLabel[] =
|
| - "FileSystem.DirectoryDatabaseRepair";
|
| -
|
| -enum InitStatus {
|
| - INIT_STATUS_OK = 0,
|
| - INIT_STATUS_CORRUPTION,
|
| - INIT_STATUS_IO_ERROR,
|
| - INIT_STATUS_UNKNOWN_ERROR,
|
| - INIT_STATUS_MAX
|
| -};
|
| -
|
| -enum RepairResult {
|
| - DB_REPAIR_SUCCEEDED = 0,
|
| - DB_REPAIR_FAILED,
|
| - DB_REPAIR_MAX
|
| -};
|
| -
|
| -std::string GetChildLookupKey(
|
| - storage::SandboxDirectoryDatabase::FileId parent_id,
|
| - const base::FilePath::StringType& child_name) {
|
| - std::string name;
|
| - name = storage::FilePathToString(base::FilePath(child_name));
|
| - return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
|
| - std::string(kChildLookupSeparator) + name;
|
| -}
|
| -
|
| -std::string GetChildListingKeyPrefix(
|
| - storage::SandboxDirectoryDatabase::FileId parent_id) {
|
| - return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
|
| - std::string(kChildLookupSeparator);
|
| -}
|
| -
|
| -const char* LastFileIdKey() {
|
| - return kLastFileIdKey;
|
| -}
|
| -
|
| -const char* LastIntegerKey() {
|
| - return kLastIntegerKey;
|
| -}
|
| -
|
| -std::string GetFileLookupKey(
|
| - storage::SandboxDirectoryDatabase::FileId file_id) {
|
| - 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 storage::SandboxDirectoryDatabase::FileId FileId;
|
| - typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
|
| -
|
| - DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
|
| - leveldb::DB* db,
|
| - const base::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();
|
| -
|
| - storage::SandboxDirectoryDatabase* dir_db_;
|
| - leveldb::DB* db_;
|
| - base::FilePath path_;
|
| -
|
| - std::set<base::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(
|
| - storage::SandboxDirectoryDatabase* dir_db,
|
| - leveldb::DB* db,
|
| - const base::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() && base::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::File::Info platform_file_info;
|
| - if (!base::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() {
|
| - // TODO(kinuko): Scans all local file system entries to verify each of them
|
| - // has a database entry.
|
| - const base::FilePath kExcludes[] = {
|
| - base::FilePath(kDirectoryDatabaseName),
|
| - base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
|
| - };
|
| -
|
| - // Any path in |pending_directories| is relative to |path_|.
|
| - std::stack<base::FilePath> pending_directories;
|
| - pending_directories.push(base::FilePath());
|
| -
|
| - while (!pending_directories.empty()) {
|
| - base::FilePath dir_path = pending_directories.top();
|
| - pending_directories.pop();
|
| -
|
| - base::FileEnumerator file_enum(
|
| - dir_path.empty() ? path_ : path_.Append(dir_path),
|
| - false /* not recursive */,
|
| - base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
|
| -
|
| - base::FilePath absolute_file_path;
|
| - while (!(absolute_file_path = file_enum.Next()).empty()) {
|
| - base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
|
| -
|
| - base::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 (find_info.IsDirectory()) {
|
| - pending_directories.push(relative_file_path);
|
| - continue;
|
| - }
|
| -
|
| - // Check if the file has a database entry.
|
| - std::set<base::FilePath>::iterator itr =
|
| - files_in_db_.find(relative_file_path);
|
| - if (itr == files_in_db_.end()) {
|
| - if (!base::DeleteFile(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;
|
| -}
|
| -
|
| -// Returns true if the given |data_path| contains no parent references ("..")
|
| -// and does not refer to special system files.
|
| -// This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
|
| -// ensure we're only dealing with valid data paths.
|
| -bool VerifyDataPath(const base::FilePath& data_path) {
|
| - // |data_path| should not contain any ".." and should be a relative path
|
| - // (to the filesystem_data_directory_).
|
| - if (data_path.ReferencesParent() || data_path.IsAbsolute())
|
| - return false;
|
| - // See if it's not pointing to the special system paths.
|
| - const base::FilePath kExcludes[] = {
|
| - base::FilePath(kDirectoryDatabaseName),
|
| - base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
|
| - };
|
| - for (size_t i = 0; i < arraysize(kExcludes); ++i) {
|
| - if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -namespace storage {
|
| -
|
| -SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
|
| -}
|
| -
|
| -SandboxDirectoryDatabase::FileInfo::~FileInfo() {
|
| -}
|
| -
|
| -SandboxDirectoryDatabase::SandboxDirectoryDatabase(
|
| - const base::FilePath& filesystem_data_directory,
|
| - leveldb::Env* env_override)
|
| - : filesystem_data_directory_(filesystem_data_directory),
|
| - env_override_(env_override) {
|
| -}
|
| -
|
| -SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::GetChildWithName(
|
| - FileId parent_id,
|
| - const base::FilePath::StringType& name,
|
| - FileId* child_id) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(child_id);
|
| - std::string child_key = GetChildLookupKey(parent_id, name);
|
| - std::string child_id_string;
|
| - leveldb::Status status =
|
| - db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
|
| - if (status.IsNotFound())
|
| - return false;
|
| - if (status.ok()) {
|
| - if (!base::StringToInt64(child_id_string, child_id)) {
|
| - LOG(ERROR) << "Hit database corruption!";
|
| - return false;
|
| - }
|
| - return true;
|
| - }
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::GetFileWithPath(
|
| - const base::FilePath& path, FileId* file_id) {
|
| - std::vector<base::FilePath::StringType> components;
|
| - VirtualPath::GetComponents(path, &components);
|
| - FileId local_id = 0;
|
| - std::vector<base::FilePath::StringType>::iterator iter;
|
| - for (iter = components.begin(); iter != components.end(); ++iter) {
|
| - base::FilePath::StringType name;
|
| - name = *iter;
|
| - if (name == FILE_PATH_LITERAL("/"))
|
| - continue;
|
| - if (!GetChildWithName(local_id, name, &local_id))
|
| - return false;
|
| - }
|
| - *file_id = local_id;
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::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(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(children);
|
| - std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
|
| -
|
| - scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
|
| - iter->Seek(child_key_prefix);
|
| - children->clear();
|
| - while (iter->Valid() &&
|
| - StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
|
| - std::string child_id_string = iter->value().ToString();
|
| - FileId child_id;
|
| - if (!base::StringToInt64(child_id_string, &child_id)) {
|
| - LOG(ERROR) << "Hit database corruption!";
|
| - return false;
|
| - }
|
| - children->push_back(child_id);
|
| - iter->Next();
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(info);
|
| - std::string file_key = GetFileLookupKey(file_id);
|
| - std::string file_data_string;
|
| - leveldb::Status status =
|
| - db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
|
| - if (status.ok()) {
|
| - bool success = FileInfoFromPickle(
|
| - Pickle(file_data_string.data(), file_data_string.length()), info);
|
| - if (!success)
|
| - return false;
|
| - if (!VerifyDataPath(info->data_path)) {
|
| - LOG(ERROR) << "Resolved data path is invalid: "
|
| - << info->data_path.value();
|
| - return false;
|
| - }
|
| - return true;
|
| - }
|
| - // Special-case the root, for databases that haven't been initialized yet.
|
| - // Without this, a query for the root's file info, made before creating the
|
| - // first file in the database, will fail and confuse callers.
|
| - if (status.IsNotFound() && !file_id) {
|
| - info->name = base::FilePath::StringType();
|
| - info->data_path = base::FilePath();
|
| - info->modification_time = base::Time::Now();
|
| - info->parent_id = 0;
|
| - return true;
|
| - }
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| -}
|
| -
|
| -base::File::Error SandboxDirectoryDatabase::AddFileInfo(
|
| - const FileInfo& info, FileId* file_id) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return base::File::FILE_ERROR_FAILED;
|
| - DCHECK(file_id);
|
| - std::string child_key = GetChildLookupKey(info.parent_id, info.name);
|
| - std::string child_id_string;
|
| - leveldb::Status status =
|
| - db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
|
| - if (status.ok()) {
|
| - LOG(ERROR) << "File exists already!";
|
| - return base::File::FILE_ERROR_EXISTS;
|
| - }
|
| - if (!status.IsNotFound()) {
|
| - HandleError(FROM_HERE, status);
|
| - return base::File::FILE_ERROR_NOT_FOUND;
|
| - }
|
| -
|
| - if (!IsDirectory(info.parent_id)) {
|
| - LOG(ERROR) << "New parent directory is a file!";
|
| - return base::File::FILE_ERROR_NOT_A_DIRECTORY;
|
| - }
|
| -
|
| - // This would be a fine place to limit the number of files in a directory, if
|
| - // we decide to add that restriction.
|
| -
|
| - FileId temp_id;
|
| - if (!GetLastFileId(&temp_id))
|
| - return base::File::FILE_ERROR_FAILED;
|
| - ++temp_id;
|
| -
|
| - leveldb::WriteBatch batch;
|
| - if (!AddFileInfoHelper(info, temp_id, &batch))
|
| - return base::File::FILE_ERROR_FAILED;
|
| -
|
| - batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
|
| - status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return base::File::FILE_ERROR_FAILED;
|
| - }
|
| - *file_id = temp_id;
|
| - return base::File::FILE_OK;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - leveldb::WriteBatch batch;
|
| - if (!RemoveFileInfoHelper(file_id, &batch))
|
| - return false;
|
| - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::UpdateFileInfo(
|
| - FileId file_id, const FileInfo& new_info) {
|
| - // TODO(ericu): We should also check to see that this doesn't create a loop,
|
| - // but perhaps only in a debug build.
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
|
| - FileInfo old_info;
|
| - if (!GetFileInfo(file_id, &old_info))
|
| - return false;
|
| - if (old_info.parent_id != new_info.parent_id &&
|
| - !IsDirectory(new_info.parent_id))
|
| - return false;
|
| - if (old_info.parent_id != new_info.parent_id ||
|
| - old_info.name != new_info.name) {
|
| - // Check for name clashes.
|
| - FileId temp_id;
|
| - if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
|
| - LOG(ERROR) << "Name collision on move.";
|
| - return false;
|
| - }
|
| - }
|
| - leveldb::WriteBatch batch;
|
| - if (!RemoveFileInfoHelper(file_id, &batch) ||
|
| - !AddFileInfoHelper(new_info, file_id, &batch))
|
| - return false;
|
| - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::UpdateModificationTime(
|
| - FileId file_id, const base::Time& modification_time) {
|
| - FileInfo info;
|
| - if (!GetFileInfo(file_id, &info))
|
| - return false;
|
| - info.modification_time = modification_time;
|
| - Pickle pickle;
|
| - if (!PickleFromFileInfo(info, &pickle))
|
| - return false;
|
| - leveldb::Status status = db_->Put(
|
| - leveldb::WriteOptions(),
|
| - GetFileLookupKey(file_id),
|
| - leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
|
| - pickle.size()));
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::OverwritingMoveFile(
|
| - FileId src_file_id, FileId dest_file_id) {
|
| - FileInfo src_file_info;
|
| - FileInfo dest_file_info;
|
| -
|
| - if (!GetFileInfo(src_file_id, &src_file_info))
|
| - return false;
|
| - if (!GetFileInfo(dest_file_id, &dest_file_info))
|
| - return false;
|
| - if (src_file_info.is_directory() || dest_file_info.is_directory())
|
| - return false;
|
| - leveldb::WriteBatch batch;
|
| - // This is the only field that really gets moved over; if you add fields to
|
| - // FileInfo, e.g. ctime, they might need to be copied here.
|
| - dest_file_info.data_path = src_file_info.data_path;
|
| - if (!RemoveFileInfoHelper(src_file_id, &batch))
|
| - return false;
|
| - Pickle pickle;
|
| - if (!PickleFromFileInfo(dest_file_info, &pickle))
|
| - return false;
|
| - batch.Put(
|
| - GetFileLookupKey(dest_file_id),
|
| - leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
|
| - pickle.size()));
|
| - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(next);
|
| - std::string int_string;
|
| - leveldb::Status status =
|
| - db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
|
| - if (status.ok()) {
|
| - int64 temp;
|
| - if (!base::StringToInt64(int_string, &temp)) {
|
| - LOG(ERROR) << "Hit database corruption!";
|
| - return false;
|
| - }
|
| - ++temp;
|
| - status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
|
| - base::Int64ToString(temp));
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - *next = temp;
|
| - return true;
|
| - }
|
| - if (!status.IsNotFound()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - // The database must not yet exist; initialize it.
|
| - if (!StoreDefaultValues())
|
| - return false;
|
| -
|
| - return GetNextInteger(next);
|
| -}
|
| -
|
| -// static
|
| -bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path,
|
| - leveldb::Env* env_override) {
|
| - std::string name = FilePathToString(path.Append(kDirectoryDatabaseName));
|
| - leveldb::Options options;
|
| - if (env_override)
|
| - options.env = env_override;
|
| - leveldb::Status status = leveldb::DestroyDB(name, options);
|
| - if (status.ok())
|
| - return true;
|
| - LOG(WARNING) << "Failed to destroy a database with status " <<
|
| - status.ToString();
|
| - return false;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
|
| - if (db_)
|
| - return true;
|
| -
|
| - std::string path =
|
| - FilePathToString(filesystem_data_directory_.Append(
|
| - kDirectoryDatabaseName));
|
| - leveldb::Options options;
|
| - options.max_open_files = 0; // Use minimum.
|
| - options.create_if_missing = true;
|
| - if (env_override_)
|
| - options.env = env_override_;
|
| - leveldb::DB* db;
|
| - leveldb::Status status = leveldb::DB::Open(options, path, &db);
|
| - ReportInitStatus(status);
|
| - if (status.ok()) {
|
| - db_.reset(db);
|
| - return true;
|
| - }
|
| - HandleError(FROM_HERE, status);
|
| -
|
| - // Corruption due to missing necessary MANIFEST-* file causes IOError instead
|
| - // of Corruption error.
|
| - // Try to repair database even when IOError case.
|
| - if (!status.IsCorruption() && !status.IsIOError())
|
| - return false;
|
| -
|
| - switch (recovery_option) {
|
| - case FAIL_ON_CORRUPTION:
|
| - return false;
|
| - case REPAIR_ON_CORRUPTION:
|
| - LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
|
| - << " Attempting to repair.";
|
| - if (RepairDatabase(path)) {
|
| - UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
|
| - DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
|
| - return true;
|
| - }
|
| - UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
|
| - DB_REPAIR_FAILED, DB_REPAIR_MAX);
|
| - LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
|
| - // fall through
|
| - case DELETE_ON_CORRUPTION:
|
| - LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
|
| - if (!base::DeleteFile(filesystem_data_directory_, true))
|
| - return false;
|
| - if (!base::CreateDirectory(filesystem_data_directory_))
|
| - return false;
|
| - return Init(FAIL_ON_CORRUPTION);
|
| - }
|
| -
|
| - NOTREACHED();
|
| - return false;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
|
| - DCHECK(!db_.get());
|
| - leveldb::Options options;
|
| - options.max_open_files = 0; // Use minimum.
|
| - if (env_override_)
|
| - options.env = env_override_;
|
| - if (!leveldb::RepairDB(db_path, options).ok())
|
| - return false;
|
| - if (!Init(FAIL_ON_CORRUPTION))
|
| - return false;
|
| - if (IsFileSystemConsistent())
|
| - return true;
|
| - db_.reset();
|
| - return false;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
|
| - FileInfo info;
|
| - if (!file_id)
|
| - return true; // The root is a directory.
|
| - if (!GetFileInfo(file_id, &info))
|
| - return false;
|
| - if (!info.is_directory())
|
| - return false;
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
|
| - if (!Init(FAIL_ON_CORRUPTION))
|
| - return false;
|
| - DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
|
| - return helper.IsFileSystemConsistent();
|
| -}
|
| -
|
| -void SandboxDirectoryDatabase::ReportInitStatus(
|
| - const leveldb::Status& status) {
|
| - base::Time now = base::Time::Now();
|
| - const base::TimeDelta minimum_interval =
|
| - base::TimeDelta::FromHours(kMinimumReportIntervalHours);
|
| - if (last_reported_time_ + minimum_interval >= now)
|
| - return;
|
| - last_reported_time_ = now;
|
| -
|
| - if (status.ok()) {
|
| - UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
|
| - INIT_STATUS_OK, INIT_STATUS_MAX);
|
| - } else if (status.IsCorruption()) {
|
| - UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
|
| - INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
|
| - } else if (status.IsIOError()) {
|
| - UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
|
| - INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
|
| - } else {
|
| - UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
|
| - INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
|
| - }
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::StoreDefaultValues() {
|
| - // Verify that this is a totally new database, and initialize it.
|
| - scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
|
| - iter->SeekToFirst();
|
| - if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
|
| - LOG(ERROR) << "File system origin database is corrupt!";
|
| - return false;
|
| - }
|
| - // This is always the first write into the database. If we ever add a
|
| - // version number, it should go in this transaction too.
|
| - FileInfo root;
|
| - root.parent_id = 0;
|
| - root.modification_time = base::Time::Now();
|
| - leveldb::WriteBatch batch;
|
| - if (!AddFileInfoHelper(root, 0, &batch))
|
| - return false;
|
| - batch.Put(LastFileIdKey(), base::Int64ToString(0));
|
| - batch.Put(LastIntegerKey(), base::Int64ToString(-1));
|
| - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (!status.ok()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
|
| - if (!Init(REPAIR_ON_CORRUPTION))
|
| - return false;
|
| - DCHECK(file_id);
|
| - std::string id_string;
|
| - leveldb::Status status =
|
| - db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
|
| - if (status.ok()) {
|
| - if (!base::StringToInt64(id_string, file_id)) {
|
| - LOG(ERROR) << "Hit database corruption!";
|
| - return false;
|
| - }
|
| - return true;
|
| - }
|
| - if (!status.IsNotFound()) {
|
| - HandleError(FROM_HERE, status);
|
| - return false;
|
| - }
|
| - // The database must not yet exist; initialize it.
|
| - if (!StoreDefaultValues())
|
| - return false;
|
| - *file_id = 0;
|
| - return true;
|
| -}
|
| -
|
| -// This does very few safety checks!
|
| -bool SandboxDirectoryDatabase::AddFileInfoHelper(
|
| - const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
|
| - if (!VerifyDataPath(info.data_path)) {
|
| - LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
|
| - return false;
|
| - }
|
| - std::string id_string = GetFileLookupKey(file_id);
|
| - if (!file_id) {
|
| - // The root directory doesn't need to be looked up by path from its parent.
|
| - DCHECK(!info.parent_id);
|
| - DCHECK(info.data_path.empty());
|
| - } else {
|
| - std::string child_key = GetChildLookupKey(info.parent_id, info.name);
|
| - batch->Put(child_key, id_string);
|
| - }
|
| - Pickle pickle;
|
| - if (!PickleFromFileInfo(info, &pickle))
|
| - return false;
|
| - batch->Put(
|
| - id_string,
|
| - leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
|
| - pickle.size()));
|
| - return true;
|
| -}
|
| -
|
| -// This does very few safety checks!
|
| -bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
|
| - FileId file_id, leveldb::WriteBatch* batch) {
|
| - DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
|
| - FileInfo info;
|
| - if (!GetFileInfo(file_id, &info))
|
| - return false;
|
| - if (info.data_path.empty()) { // It's a directory
|
| - std::vector<FileId> children;
|
| - // TODO(ericu): Make a faster is-the-directory-empty check.
|
| - if (!ListChildren(file_id, &children))
|
| - return false;
|
| - if (children.size()) {
|
| - LOG(ERROR) << "Can't remove a directory with children.";
|
| - return false;
|
| - }
|
| - }
|
| - batch->Delete(GetChildLookupKey(info.parent_id, info.name));
|
| - batch->Delete(GetFileLookupKey(file_id));
|
| - return true;
|
| -}
|
| -
|
| -void SandboxDirectoryDatabase::HandleError(
|
| - const tracked_objects::Location& from_here,
|
| - const leveldb::Status& status) {
|
| - LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
|
| - << from_here.ToString() << " with error: " << status.ToString();
|
| - db_.reset();
|
| -}
|
| -
|
| -} // namespace storage
|
|
|