blob: 91c4914c7f22d22063e00f32fd909a6d4e3a21b3 [file] [log] [blame]
// 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 "storage/browser/fileapi/sandbox_directory_database.h"
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <set>
#include <stack>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "storage/browser/fileapi/file_system_usage_cache.h"
#include "storage/common/fileapi/file_system_util.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace {
bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
base::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 base::Pickle& pickle,
storage::SandboxDirectoryDatabase::FileInfo* info) {
base::PickleIterator iter(pickle);
std::string data_path;
std::string name;
int64_t internal_time;
if (iter.ReadInt64(&info->parent_id) &&
iter.ReadString(&data_path) &&
iter.ReadString(&name) &&
iter.ReadInt64(&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) << "base::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_t kMinimumReportIntervalHours = 1;
const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
const char kDatabaseRepairHistogramLabel[] =
"FileSystem.DirectoryDatabaseRepair";
// These values are recorded in UMA. Changing existing values will invalidate
// results for older Chrome releases. Only add new values.
enum InitStatus {
INIT_STATUS_OK = 0,
INIT_STATUS_CORRUPTION,
INIT_STATUS_IO_ERROR,
INIT_STATUS_UNKNOWN_ERROR,
INIT_STATUS_MAX
};
// These values are recorded in UMA. Changing existing values will invalidate
// results for older Chrome releases. Only add new values.
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() {
std::unique_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_t max_file_id = -1;
std::set<FileId> file_ids;
std::unique_ptr<leveldb::Iterator> itr(
db_->NewIterator(leveldb::ReadOptions()));
for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
std::string key = itr->key().ToString();
if (base::StartsWith(key, kChildLookupPrefix,
base::CompareCase::SENSITIVE)) {
// 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(
base::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);
std::unique_ptr<leveldb::Iterator> iter(
db_->NewIterator(leveldb::ReadOptions()));
iter->Seek(child_key_prefix);
children->clear();
while (iter->Valid() && base::StartsWith(iter->key().ToString(),
child_key_prefix,
base::CompareCase::SENSITIVE)) {
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(
base::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;
base::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;
base::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_t* 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_t 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);
}
bool SandboxDirectoryDatabase::DestroyDatabase() {
db_.reset();
const std::string path =
FilePathToString(filesystem_data_directory_.Append(
kDirectoryDatabaseName));
leveldb::Options options;
if (env_override_)
options.env = env_override_;
leveldb::Status status = leveldb::DestroyDB(path, 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;
options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
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.
{
// Scope the iterator to ensure deleted before database is closed.
std::unique_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);
}
base::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