blob: 0f0926a455df4be4d4f2b6fd83831da696be8f3b [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_origin_database.h"
#include <stdint.h>
#include <memory>
#include <set>
#include <utility>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.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 {
const base::FilePath::CharType kOriginDatabaseName[] =
FILE_PATH_LITERAL("Origins");
const char kOriginKeyPrefix[] = "ORIGIN:";
const char kSandboxOriginLastPathKey[] = "LAST_PATH";
const int64_t kSandboxOriginMinimumReportIntervalHours = 1;
const char kSandboxOriginInitStatusHistogramLabel[] =
"FileSystem.OriginDatabaseInit";
const char kSandboxOriginDatabaseRepairHistogramLabel[] =
"FileSystem.OriginDatabaseRepair";
enum class InitSandboxOriginStatus {
INIT_STATUS_OK = 0,
INIT_STATUS_CORRUPTION,
INIT_STATUS_IO_ERROR,
INIT_STATUS_UNKNOWN_ERROR,
INIT_STATUS_MAX
};
enum class SandboxOriginRepairResult {
DB_REPAIR_SUCCEEDED = 0,
DB_REPAIR_FAILED,
DB_REPAIR_MAX
};
std::string OriginToOriginKey(const std::string& origin) {
std::string key(kOriginKeyPrefix);
return key + origin;
}
const char* LastPathKey() {
return kSandboxOriginLastPathKey;
}
} // namespace
namespace storage {
SandboxOriginDatabase::SandboxOriginDatabase(
const base::FilePath& file_system_directory,
leveldb::Env* env_override)
: file_system_directory_(file_system_directory),
env_override_(env_override) {
}
SandboxOriginDatabase::~SandboxOriginDatabase() = default;
bool SandboxOriginDatabase::Init(InitOption init_option,
RecoveryOption recovery_option) {
if (db_)
return true;
base::FilePath db_path = GetDatabasePath();
if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path))
return false;
std::string path = FilePathToString(db_path);
leveldb_env::Options options;
options.max_open_files = 0; // Use minimum.
options.create_if_missing = true;
if (env_override_)
options.env = env_override_;
leveldb::Status status = leveldb_env::OpenDB(options, path, &db_);
ReportInitStatus(status);
if (status.ok()) {
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) << "Attempting to repair SandboxOriginDatabase.";
if (RepairDatabase(path)) {
UMA_HISTOGRAM_ENUMERATION(
kSandboxOriginDatabaseRepairHistogramLabel,
SandboxOriginRepairResult::DB_REPAIR_SUCCEEDED,
SandboxOriginRepairResult::DB_REPAIR_MAX);
LOG(WARNING) << "Repairing SandboxOriginDatabase completed.";
return true;
}
UMA_HISTOGRAM_ENUMERATION(kSandboxOriginDatabaseRepairHistogramLabel,
SandboxOriginRepairResult::DB_REPAIR_FAILED,
SandboxOriginRepairResult::DB_REPAIR_MAX);
FALLTHROUGH;
case DELETE_ON_CORRUPTION:
if (!base::DeleteFile(file_system_directory_, true))
return false;
if (!base::CreateDirectory(file_system_directory_))
return false;
return Init(init_option, FAIL_ON_CORRUPTION);
}
NOTREACHED();
return false;
}
bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) {
DCHECK(!db_.get());
leveldb_env::Options options;
options.reuse_logs = false;
options.max_open_files = 0; // Use minimum.
if (env_override_)
options.env = env_override_;
if (!leveldb::RepairDB(db_path, options).ok() ||
!Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) {
LOG(WARNING) << "Failed to repair SandboxOriginDatabase.";
return false;
}
// See if the repaired entries match with what we have on disk.
std::set<base::FilePath> directories;
base::FileEnumerator file_enum(file_system_directory_,
false /* recursive */,
base::FileEnumerator::DIRECTORIES);
base::FilePath path_each;
while (!(path_each = file_enum.Next()).empty())
directories.insert(path_each.BaseName());
auto db_dir_itr = directories.find(base::FilePath(kOriginDatabaseName));
// Make sure we have the database file in its directory and therefore we are
// working on the correct path.
DCHECK(db_dir_itr != directories.end());
directories.erase(db_dir_itr);
std::vector<OriginRecord> origins;
if (!ListAllOrigins(&origins)) {
DropDatabase();
return false;
}
// Delete any obsolete entries from the origins database.
for (const OriginRecord& record : origins) {
auto dir_itr = directories.find(record.path);
if (dir_itr == directories.end()) {
if (!RemovePathForOrigin(record.origin)) {
DropDatabase();
return false;
}
} else {
directories.erase(dir_itr);
}
}
// Delete any directories not listed in the origins database.
for (const base::FilePath& dir : directories) {
if (!base::DeleteFile(file_system_directory_.Append(dir),
true /* recursive */)) {
DropDatabase();
return false;
}
}
return true;
}
void SandboxOriginDatabase::HandleError(const base::Location& from_here,
const leveldb::Status& status) {
db_.reset();
LOG(ERROR) << "SandboxOriginDatabase failed at: "
<< from_here.ToString() << " with error: " << status.ToString();
}
void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) {
base::Time now = base::Time::Now();
base::TimeDelta minimum_interval =
base::TimeDelta::FromHours(kSandboxOriginMinimumReportIntervalHours);
if (last_reported_time_ + minimum_interval >= now)
return;
last_reported_time_ = now;
if (status.ok()) {
UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel,
InitSandboxOriginStatus::INIT_STATUS_OK,
InitSandboxOriginStatus::INIT_STATUS_MAX);
} else if (status.IsCorruption()) {
UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel,
InitSandboxOriginStatus::INIT_STATUS_CORRUPTION,
InitSandboxOriginStatus::INIT_STATUS_MAX);
} else if (status.IsIOError()) {
UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel,
InitSandboxOriginStatus::INIT_STATUS_IO_ERROR,
InitSandboxOriginStatus::INIT_STATUS_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION(
kSandboxOriginInitStatusHistogramLabel,
InitSandboxOriginStatus::INIT_STATUS_UNKNOWN_ERROR,
InitSandboxOriginStatus::INIT_STATUS_MAX);
}
}
bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) {
if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
return false;
if (origin.empty())
return false;
std::string path;
leveldb::Status status =
db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path);
if (status.ok())
return true;
if (status.IsNotFound())
return false;
HandleError(FROM_HERE, status);
return false;
}
bool SandboxOriginDatabase::GetPathForOrigin(
const std::string& origin, base::FilePath* directory) {
if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
return false;
DCHECK(directory);
if (origin.empty())
return false;
std::string path_string;
std::string origin_key = OriginToOriginKey(origin);
leveldb::Status status =
db_->Get(leveldb::ReadOptions(), origin_key, &path_string);
if (status.IsNotFound()) {
int last_path_number;
if (!GetLastPathNumber(&last_path_number))
return false;
path_string = base::StringPrintf("%03u", last_path_number + 1);
// store both back as a single transaction
leveldb::WriteBatch batch;
batch.Put(LastPathKey(), path_string);
batch.Put(origin_key, path_string);
status = db_->Write(leveldb::WriteOptions(), &batch);
if (!status.ok()) {
HandleError(FROM_HERE, status);
return false;
}
}
if (status.ok()) {
*directory = StringToFilePath(path_string);
return true;
}
HandleError(FROM_HERE, status);
return false;
}
bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) {
if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
return false;
leveldb::Status status =
db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin));
if (status.ok() || status.IsNotFound())
return true;
HandleError(FROM_HERE, status);
return false;
}
bool SandboxOriginDatabase::ListAllOrigins(
std::vector<OriginRecord>* origins) {
DCHECK(origins);
if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) {
origins->clear();
return false;
}
std::unique_ptr<leveldb::Iterator> iter(
db_->NewIterator(leveldb::ReadOptions()));
std::string origin_key_prefix = OriginToOriginKey(std::string());
iter->Seek(origin_key_prefix);
origins->clear();
while (iter->Valid() && base::StartsWith(iter->key().ToString(),
origin_key_prefix,
base::CompareCase::SENSITIVE)) {
std::string origin =
iter->key().ToString().substr(origin_key_prefix.length());
base::FilePath path = StringToFilePath(iter->value().ToString());
origins->push_back(OriginRecord(origin, path));
iter->Next();
}
return true;
}
void SandboxOriginDatabase::DropDatabase() {
db_.reset();
}
base::FilePath SandboxOriginDatabase::GetDatabasePath() const {
return file_system_directory_.Append(kOriginDatabaseName);
}
void SandboxOriginDatabase::RemoveDatabase() {
DropDatabase();
base::DeleteFile(GetDatabasePath(), true /* recursive */);
}
bool SandboxOriginDatabase::GetLastPathNumber(int* number) {
DCHECK(db_);
DCHECK(number);
std::string number_string;
leveldb::Status status =
db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string);
if (status.ok())
return base::StringToInt(number_string, number);
if (!status.IsNotFound()) {
HandleError(FROM_HERE, status);
return false;
}
// Verify that this is a totally new database, and initialize it.
{
// Scope the iterator to ensure it is deleted before database is closed.
std::unique_ptr<leveldb::Iterator> iter(
db_->NewIterator(leveldb::ReadOptions()));
iter->SeekToFirst();
if (iter->Valid()) { // DB was not empty, but had no last path number!
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, they should go in in a single transaction.
status = db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1"));
if (!status.ok()) {
HandleError(FROM_HERE, status);
return false;
}
*number = -1;
return true;
}
} // namespace storage