blob: 0c9207af5686754813b3831d9712a34199b6dcb6 [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 "webkit/fileapi/file_system_origin_database.h"
#include <set>
#include "base/file_util.h"
#include "base/format_macros.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/stringprintf.h"
#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_util.h"
namespace {
const FilePath::CharType kOriginDatabaseName[] = FILE_PATH_LITERAL("Origins");
const char kOriginKeyPrefix[] = "ORIGIN:";
const char kLastPathKey[] = "LAST_PATH";
const int64 kMinimumReportIntervalHours = 1;
const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit";
enum InitStatus {
INIT_STATUS_OK = 0,
INIT_STATUS_CORRUPTION,
INIT_STATUS_IO_ERROR,
INIT_STATUS_UNKNOWN_ERROR,
INIT_STATUS_MAX
};
std::string OriginToOriginKey(const std::string& origin) {
std::string key(kOriginKeyPrefix);
return key + origin;
}
const char* LastPathKey() {
return kLastPathKey;
}
} // namespace
namespace fileapi {
FileSystemOriginDatabase::OriginRecord::OriginRecord() {
}
FileSystemOriginDatabase::OriginRecord::OriginRecord(
const std::string& origin_in, const FilePath& path_in)
: origin(origin_in), path(path_in) {
}
FileSystemOriginDatabase::OriginRecord::~OriginRecord() {
}
FileSystemOriginDatabase::FileSystemOriginDatabase(
const FilePath& file_system_directory)
: file_system_directory_(file_system_directory) {
}
FileSystemOriginDatabase::~FileSystemOriginDatabase() {
}
bool FileSystemOriginDatabase::Init(RecoveryOption recovery_option) {
if (db_.get())
return true;
std::string path =
FilePathToString(file_system_directory_.Append(kOriginDatabaseName));
leveldb::Options options;
options.create_if_missing = true;
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);
if (!status.IsCorruption())
return false;
switch (recovery_option) {
case FAIL_ON_CORRUPTION:
return false;
case REPAIR_ON_CORRUPTION:
LOG(WARNING) << "Attempting to repair FileSystemOriginDatabase.";
if (RepairDatabase(path)) {
LOG(WARNING) << "Repairing FileSystemOriginDatabase completed.";
return true;
}
// fall through
case DELETE_ON_CORRUPTION:
if (!file_util::Delete(file_system_directory_, true))
return false;
if (!file_util::CreateDirectory(file_system_directory_))
return false;
return Init(FAIL_ON_CORRUPTION);
}
NOTREACHED();
return false;
}
bool FileSystemOriginDatabase::RepairDatabase(const std::string& db_path) {
DCHECK(!db_.get());
if (!leveldb::RepairDB(db_path, leveldb::Options()).ok() ||
!Init(FAIL_ON_CORRUPTION)) {
LOG(WARNING) << "Failed to repair FileSystemOriginDatabase.";
return false;
}
// See if the repaired entries match with what we have on disk.
std::set<FilePath> directories;
file_util::FileEnumerator file_enum(file_system_directory_,
false /* recursive */,
file_util::FileEnumerator::DIRECTORIES);
FilePath path_each;
while (!(path_each = file_enum.Next()).empty())
directories.insert(path_each.BaseName());
std::set<FilePath>::iterator db_dir_itr =
directories.find(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 (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin();
db_origin_itr != origins.end();
++db_origin_itr) {
std::set<FilePath>::iterator dir_itr =
directories.find(db_origin_itr->path);
if (dir_itr == directories.end()) {
if (!RemovePathForOrigin(db_origin_itr->origin)) {
DropDatabase();
return false;
}
} else {
directories.erase(dir_itr);
}
}
// Delete any directories not listed in the origins database.
for (std::set<FilePath>::iterator dir_itr = directories.begin();
dir_itr != directories.end();
++dir_itr) {
if (!file_util::Delete(file_system_directory_.Append(*dir_itr),
true /* recursive */)) {
DropDatabase();
return false;
}
}
return true;
}
void FileSystemOriginDatabase::HandleError(
const tracked_objects::Location& from_here,
const leveldb::Status& status) {
db_.reset();
LOG(ERROR) << "FileSystemOriginDatabase failed at: "
<< from_here.ToString() << " with error: " << status.ToString();
}
void FileSystemOriginDatabase::ReportInitStatus(const leveldb::Status& status) {
base::Time now = base::Time::Now();
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 FileSystemOriginDatabase::HasOriginPath(const std::string& origin) {
if (!Init(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 FileSystemOriginDatabase::GetPathForOrigin(
const std::string& origin, FilePath* directory) {
if (!Init(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 = 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 FileSystemOriginDatabase::RemovePathForOrigin(const std::string& origin) {
if (!Init(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 FileSystemOriginDatabase::ListAllOrigins(
std::vector<OriginRecord>* origins) {
if (!Init(REPAIR_ON_CORRUPTION))
return false;
DCHECK(origins);
scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
std::string origin_key_prefix = OriginToOriginKey("");
iter->Seek(origin_key_prefix);
origins->clear();
while (iter->Valid() &&
StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) {
std::string origin =
iter->key().ToString().substr(origin_key_prefix.length());
FilePath path = StringToFilePath(iter->value().ToString());
origins->push_back(OriginRecord(origin, path));
iter->Next();
}
return true;
}
void FileSystemOriginDatabase::DropDatabase() {
db_.reset();
}
bool FileSystemOriginDatabase::GetLastPathNumber(int* number) {
if (!Init(REPAIR_ON_CORRUPTION))
return false;
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.
scoped_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 fileapi