blob: 6d7fdc9a34f12fa8eaca6add8d29b5c47c3483bf [file] [log] [blame]
// Copyright 2014 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 "extensions/browser/value_store/leveldb_value_store.h"
#include "base/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/value_store/value_store_util.h"
#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace util = value_store_util;
using content::BrowserThread;
namespace {
const char kInvalidJson[] = "Invalid JSON";
const char kCannotSerialize[] = "Cannot serialize value to JSON";
// Scoped leveldb snapshot which releases the snapshot on destruction.
class ScopedSnapshot {
public:
explicit ScopedSnapshot(leveldb::DB* db)
: db_(db), snapshot_(db->GetSnapshot()) {}
~ScopedSnapshot() {
db_->ReleaseSnapshot(snapshot_);
}
const leveldb::Snapshot* get() {
return snapshot_;
}
private:
leveldb::DB* db_;
const leveldb::Snapshot* snapshot_;
DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot);
};
} // namespace
LeveldbValueStore::LeveldbValueStore(const base::FilePath& db_path)
: db_path_(db_path) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
LOG(WARNING) << open_error->message;
}
LeveldbValueStore::~LeveldbValueStore() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
// Delete the database from disk if it's empty (but only if we managed to
// open it!). This is safe on destruction, assuming that we have exclusive
// access to the database.
if (db_ && IsEmpty())
DeleteDbFile();
}
size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
// Let SettingsStorageQuotaEnforcer implement this.
NOTREACHED() << "Not implemented";
return 0;
}
size_t LeveldbValueStore::GetBytesInUse(
const std::vector<std::string>& keys) {
// Let SettingsStorageQuotaEnforcer implement this.
NOTREACHED() << "Not implemented";
return 0;
}
size_t LeveldbValueStore::GetBytesInUse() {
// Let SettingsStorageQuotaEnforcer implement this.
NOTREACHED() << "Not implemented";
return 0;
}
ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeReadResult(open_error.Pass());
scoped_ptr<base::Value> setting;
scoped_ptr<Error> error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
if (error)
return MakeReadResult(error.Pass());
base::DictionaryValue* settings = new base::DictionaryValue();
if (setting)
settings->SetWithoutPathExpansion(key, setting.release());
return MakeReadResult(make_scoped_ptr(settings));
}
ValueStore::ReadResult LeveldbValueStore::Get(
const std::vector<std::string>& keys) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeReadResult(open_error.Pass());
leveldb::ReadOptions options;
scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
// All interaction with the db is done on the same thread, so snapshotting
// isn't strictly necessary. This is just defensive.
ScopedSnapshot snapshot(db_.get());
options.snapshot = snapshot.get();
for (std::vector<std::string>::const_iterator it = keys.begin();
it != keys.end(); ++it) {
scoped_ptr<base::Value> setting;
scoped_ptr<Error> error = ReadFromDb(options, *it, &setting);
if (error)
return MakeReadResult(error.Pass());
if (setting)
settings->SetWithoutPathExpansion(*it, setting.release());
}
return MakeReadResult(settings.Pass());
}
ValueStore::ReadResult LeveldbValueStore::Get() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeReadResult(open_error.Pass());
base::JSONReader json_reader;
leveldb::ReadOptions options = leveldb::ReadOptions();
// All interaction with the db is done on the same thread, so snapshotting
// isn't strictly necessary. This is just defensive.
scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
ScopedSnapshot snapshot(db_.get());
options.snapshot = snapshot.get();
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
std::string key = it->key().ToString();
base::Value* value = json_reader.ReadToValue(it->value().ToString());
if (!value) {
return MakeReadResult(
Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key)));
}
settings->SetWithoutPathExpansion(key, value);
}
if (it->status().IsNotFound()) {
NOTREACHED() << "IsNotFound() but iterating over all keys?!";
return MakeReadResult(settings.Pass());
}
if (!it->status().ok())
return MakeReadResult(ToValueStoreError(it->status(), util::NoKey()));
return MakeReadResult(settings.Pass());
}
ValueStore::WriteResult LeveldbValueStore::Set(
WriteOptions options, const std::string& key, const base::Value& value) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeWriteResult(open_error.Pass());
leveldb::WriteBatch batch;
scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
scoped_ptr<Error> batch_error =
AddToBatch(options, key, value, &batch, changes.get());
if (batch_error)
return MakeWriteResult(batch_error.Pass());
scoped_ptr<Error> write_error = WriteToDb(&batch);
return write_error ? MakeWriteResult(write_error.Pass())
: MakeWriteResult(changes.Pass());
}
ValueStore::WriteResult LeveldbValueStore::Set(
WriteOptions options, const base::DictionaryValue& settings) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeWriteResult(open_error.Pass());
leveldb::WriteBatch batch;
scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
for (base::DictionaryValue::Iterator it(settings);
!it.IsAtEnd(); it.Advance()) {
scoped_ptr<Error> batch_error =
AddToBatch(options, it.key(), it.value(), &batch, changes.get());
if (batch_error)
return MakeWriteResult(batch_error.Pass());
}
scoped_ptr<Error> write_error = WriteToDb(&batch);
return write_error ? MakeWriteResult(write_error.Pass())
: MakeWriteResult(changes.Pass());
}
ValueStore::WriteResult LeveldbValueStore::Remove(const std::string& key) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
return Remove(std::vector<std::string>(1, key));
}
ValueStore::WriteResult LeveldbValueStore::Remove(
const std::vector<std::string>& keys) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<Error> open_error = EnsureDbIsOpen();
if (open_error)
return MakeWriteResult(open_error.Pass());
leveldb::WriteBatch batch;
scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
for (std::vector<std::string>::const_iterator it = keys.begin();
it != keys.end(); ++it) {
scoped_ptr<base::Value> old_value;
scoped_ptr<Error> read_error =
ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
if (read_error)
return MakeWriteResult(read_error.Pass());
if (old_value) {
changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
batch.Delete(*it);
}
}
leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
if (!status.ok() && !status.IsNotFound())
return MakeWriteResult(ToValueStoreError(status, util::NoKey()));
return MakeWriteResult(changes.Pass());
}
ValueStore::WriteResult LeveldbValueStore::Clear() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
ReadResult read_result = Get();
if (read_result->HasError())
return MakeWriteResult(read_result->PassError());
base::DictionaryValue& whole_db = read_result->settings();
while (!whole_db.empty()) {
std::string next_key = base::DictionaryValue::Iterator(whole_db).key();
scoped_ptr<base::Value> next_value;
whole_db.RemoveWithoutPathExpansion(next_key, &next_value);
changes->push_back(
ValueStoreChange(next_key, next_value.release(), NULL));
}
DeleteDbFile();
return MakeWriteResult(changes.Pass());
}
bool LeveldbValueStore::Restore() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
ReadResult result = Get();
std::string previous_key;
while (result->IsCorrupted()) {
// If we don't have a specific corrupted key, or we've tried and failed to
// clear this specific key, or we fail to restore the key, then wipe the
// whole database.
if (!result->error().key.get() || *result->error().key == previous_key ||
!RestoreKey(*result->error().key)) {
DeleteDbFile();
result = Get();
break;
}
// Otherwise, re-Get() the database to check if there is still any
// corruption.
previous_key = *result->error().key;
result = Get();
}
// If we still have an error, it means we've tried deleting the database file,
// and failed. There's nothing more we can do.
return !result->IsCorrupted();
}
bool LeveldbValueStore::RestoreKey(const std::string& key) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
ReadResult result = Get(key);
if (result->IsCorrupted()) {
leveldb::WriteBatch batch;
batch.Delete(key);
scoped_ptr<ValueStore::Error> error = WriteToDb(&batch);
// If we can't delete the key, the restore failed.
if (error.get())
return false;
result = Get(key);
}
// The restore succeeded if there is no corruption error.
return !result->IsCorrupted();
}
bool LeveldbValueStore::WriteToDbForTest(leveldb::WriteBatch* batch) {
return !WriteToDb(batch).get();
}
scoped_ptr<ValueStore::Error> LeveldbValueStore::EnsureDbIsOpen() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (db_)
return util::NoError();
leveldb::Options options;
options.max_open_files = 0; // Use minimum.
options.create_if_missing = true;
leveldb::DB* db = NULL;
leveldb::Status status =
leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
if (!status.ok())
return ToValueStoreError(status, util::NoKey());
CHECK(db);
db_.reset(db);
return util::NoError();
}
scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
leveldb::ReadOptions options,
const std::string& key,
scoped_ptr<base::Value>* setting) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
DCHECK(setting);
std::string value_as_json;
leveldb::Status s = db_->Get(options, key, &value_as_json);
if (s.IsNotFound()) {
// Despite there being no value, it was still a success. Check this first
// because ok() is false on IsNotFound.
return util::NoError();
}
if (!s.ok())
return ToValueStoreError(s, util::NewKey(key));
base::Value* value = base::JSONReader().ReadToValue(value_as_json);
if (!value)
return Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key));
setting->reset(value);
return util::NoError();
}
scoped_ptr<ValueStore::Error> LeveldbValueStore::AddToBatch(
ValueStore::WriteOptions options,
const std::string& key,
const base::Value& value,
leveldb::WriteBatch* batch,
ValueStoreChangeList* changes) {
bool write_new_value = true;
if (!(options & NO_GENERATE_CHANGES)) {
scoped_ptr<base::Value> old_value;
scoped_ptr<Error> read_error =
ReadFromDb(leveldb::ReadOptions(), key, &old_value);
if (read_error)
return read_error.Pass();
if (!old_value || !old_value->Equals(&value)) {
changes->push_back(
ValueStoreChange(key, old_value.release(), value.DeepCopy()));
} else {
write_new_value = false;
}
}
if (write_new_value) {
std::string value_as_json;
if (!base::JSONWriter::Write(&value, &value_as_json))
return Error::Create(OTHER_ERROR, kCannotSerialize, util::NewKey(key));
batch->Put(key, value_as_json);
}
return util::NoError();
}
scoped_ptr<ValueStore::Error> LeveldbValueStore::WriteToDb(
leveldb::WriteBatch* batch) {
leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
return status.ok() ? util::NoError()
: ToValueStoreError(status, util::NoKey());
}
bool LeveldbValueStore::IsEmpty() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
it->SeekToFirst();
bool is_empty = !it->Valid();
if (!it->status().ok()) {
LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString();
return false;
}
return is_empty;
}
void LeveldbValueStore::DeleteDbFile() {
db_.reset(); // release any lock on the directory
if (!base::DeleteFile(db_path_, true /* recursive */)) {
LOG(WARNING) << "Failed to delete LeveldbValueStore database at " <<
db_path_.value();
}
}
scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError(
const leveldb::Status& status,
scoped_ptr<std::string> key) {
CHECK(!status.ok());
CHECK(!status.IsNotFound()); // not an error
std::string message = status.ToString();
// The message may contain |db_path_|, which may be considered sensitive
// data, and those strings are passed to the extension, so strip it out.
ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(), "...");
return Error::Create(CORRUPTION, message, key.Pass());
}