| Index: chrome/browser/value_store/leveldb_value_store.cc
|
| diff --git a/chrome/browser/value_store/leveldb_value_store.cc b/chrome/browser/value_store/leveldb_value_store.cc
|
| index 1a2dbe6cd55bafe7f26363e2584f6ec240b4a5e2..c092b331bd812effadd43899b134756d5e290d88 100644
|
| --- a/chrome/browser/value_store/leveldb_value_store.cc
|
| +++ b/chrome/browser/value_store/leveldb_value_store.cc
|
| @@ -11,47 +11,17 @@
|
| #include "base/strings/string_util.h"
|
| #include "base/strings/stringprintf.h"
|
| #include "base/strings/sys_string_conversions.h"
|
| +#include "chrome/browser/value_store/value_store_util.h"
|
| #include "content/public/browser/browser_thread.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";
|
| -
|
| -ValueStore::ReadResult ReadFailure(const std::string& action,
|
| - const std::string& reason) {
|
| - CHECK_NE("", reason);
|
| - return ValueStore::MakeReadResult(base::StringPrintf(
|
| - "Failure to %s: %s", action.c_str(), reason.c_str()));
|
| -}
|
| -
|
| -ValueStore::ReadResult ReadFailureForKey(const std::string& action,
|
| - const std::string& key,
|
| - const std::string& reason) {
|
| - CHECK_NE("", reason);
|
| - return ValueStore::MakeReadResult(base::StringPrintf(
|
| - "Failure to %s for key %s: %s",
|
| - action.c_str(), key.c_str(), reason.c_str()));
|
| -}
|
| -
|
| -ValueStore::WriteResult WriteFailure(const std::string& action,
|
| - const std::string& reason) {
|
| - CHECK_NE("", reason);
|
| - return ValueStore::MakeWriteResult(base::StringPrintf(
|
| - "Failure to %s: %s", action.c_str(), reason.c_str()));
|
| -}
|
| -
|
| -ValueStore::WriteResult WriteFailureForKey(const std::string& action,
|
| - const std::string& key,
|
| - const std::string& reason) {
|
| - CHECK_NE("", reason);
|
| - return ValueStore::MakeWriteResult(base::StringPrintf(
|
| - "Failure to %s for key %s: %s",
|
| - action.c_str(), key.c_str(), reason.c_str()));
|
| -}
|
| +const char kInvalidJson[] = "Invalid JSON";
|
|
|
| // Scoped leveldb snapshot which releases the snapshot on destruction.
|
| class ScopedSnapshot {
|
| @@ -80,9 +50,9 @@ LeveldbValueStore::LeveldbValueStore(const base::FilePath& db_path)
|
| : db_path_(db_path) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - LOG(WARNING) << error;
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + LOG(WARNING) << open_error->message;
|
| }
|
|
|
| LeveldbValueStore::~LeveldbValueStore() {
|
| @@ -91,14 +61,8 @@ LeveldbValueStore::~LeveldbValueStore() {
|
| // 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()) {
|
| - // Close |db_| now to release any lock on the directory.
|
| - db_.reset();
|
| - if (!base::DeleteFile(db_path_, true)) {
|
| - LOG(WARNING) << "Failed to delete LeveldbValueStore database " <<
|
| - db_path_.value();
|
| - }
|
| - }
|
| + if (db_ && IsEmpty())
|
| + DeleteDbFile();
|
| }
|
|
|
| size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
|
| @@ -123,28 +87,28 @@ size_t LeveldbValueStore::GetBytesInUse() {
|
| ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeReadResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeReadResult(open_error.Pass());
|
|
|
| scoped_ptr<Value> setting;
|
| - error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
|
| - if (!error.empty())
|
| - return ReadFailureForKey("get", key, error);
|
| + scoped_ptr<Error> error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
|
| + if (error)
|
| + return MakeReadResult(error.Pass());
|
|
|
| DictionaryValue* settings = new DictionaryValue();
|
| - if (setting.get())
|
| + if (setting)
|
| settings->SetWithoutPathExpansion(key, setting.release());
|
| - return MakeReadResult(settings);
|
| + return MakeReadResult(make_scoped_ptr(settings));
|
| }
|
|
|
| ValueStore::ReadResult LeveldbValueStore::Get(
|
| const std::vector<std::string>& keys) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeReadResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeReadResult(open_error.Pass());
|
|
|
| leveldb::ReadOptions options;
|
| scoped_ptr<DictionaryValue> settings(new DictionaryValue());
|
| @@ -156,22 +120,22 @@ ValueStore::ReadResult LeveldbValueStore::Get(
|
| for (std::vector<std::string>::const_iterator it = keys.begin();
|
| it != keys.end(); ++it) {
|
| scoped_ptr<Value> setting;
|
| - error = ReadFromDb(options, *it, &setting);
|
| - if (!error.empty())
|
| - return ReadFailureForKey("get multiple items", *it, error);
|
| - if (setting.get())
|
| + scoped_ptr<Error> error = ReadFromDb(options, *it, &setting);
|
| + if (error)
|
| + return MakeReadResult(error.Pass());
|
| + if (setting)
|
| settings->SetWithoutPathExpansion(*it, setting.release());
|
| }
|
|
|
| - return MakeReadResult(settings.release());
|
| + return MakeReadResult(settings.Pass());
|
| }
|
|
|
| ValueStore::ReadResult LeveldbValueStore::Get() {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeReadResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeReadResult(open_error.Pass());
|
|
|
| base::JSONReader json_reader;
|
| leveldb::ReadOptions options = leveldb::ReadOptions();
|
| @@ -185,68 +149,65 @@ ValueStore::ReadResult LeveldbValueStore::Get() {
|
| for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
| std::string key = it->key().ToString();
|
| Value* value = json_reader.ReadToValue(it->value().ToString());
|
| - if (value == NULL) {
|
| - // TODO(kalman): clear the offending non-JSON value from the database.
|
| - return ReadFailureForKey("get all", key, kInvalidJson);
|
| + 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.release());
|
| + return MakeReadResult(settings.Pass());
|
| }
|
|
|
| if (!it->status().ok())
|
| - return ReadFailure("get all items", it->status().ToString());
|
| + return MakeReadResult(ToValueStoreError(it->status(), util::NoKey()));
|
|
|
| - return MakeReadResult(settings.release());
|
| + return MakeReadResult(settings.Pass());
|
| }
|
|
|
| ValueStore::WriteResult LeveldbValueStore::Set(
|
| WriteOptions options, const std::string& key, const Value& value) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeWriteResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeWriteResult(open_error.Pass());
|
|
|
| leveldb::WriteBatch batch;
|
| scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
|
| - error = AddToBatch(options, key, value, &batch, changes.get());
|
| - if (!error.empty())
|
| - return WriteFailureForKey("find changes to set", key, error);
|
| -
|
| - error = WriteToDb(&batch);
|
| - if (!error.empty())
|
| - return WriteFailureForKey("set", key, error);
|
| - return MakeWriteResult(changes.release());
|
| + 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 DictionaryValue& settings) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeWriteResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeWriteResult(open_error.Pass());
|
|
|
| leveldb::WriteBatch batch;
|
| scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
|
|
|
| for (DictionaryValue::Iterator it(settings); !it.IsAtEnd(); it.Advance()) {
|
| - error = AddToBatch(options, it.key(), it.value(), &batch, changes.get());
|
| - if (!error.empty()) {
|
| - return WriteFailureForKey("find changes to set multiple items",
|
| - it.key(),
|
| - error);
|
| - }
|
| + scoped_ptr<Error> batch_error =
|
| + AddToBatch(options, it.key(), it.value(), &batch, changes.get());
|
| + if (batch_error)
|
| + return MakeWriteResult(batch_error.Pass());
|
| }
|
|
|
| - error = WriteToDb(&batch);
|
| - if (!error.empty())
|
| - return WriteFailure("set multiple items", error);
|
| - return MakeWriteResult(changes.release());
|
| + 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) {
|
| @@ -258,9 +219,9 @@ ValueStore::WriteResult LeveldbValueStore::Remove(
|
| const std::vector<std::string>& keys) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeWriteResult(error);
|
| + scoped_ptr<Error> open_error = EnsureDbIsOpen();
|
| + if (open_error)
|
| + return MakeWriteResult(open_error.Pass());
|
|
|
| leveldb::WriteBatch batch;
|
| scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
|
| @@ -268,14 +229,12 @@ ValueStore::WriteResult LeveldbValueStore::Remove(
|
| for (std::vector<std::string>::const_iterator it = keys.begin();
|
| it != keys.end(); ++it) {
|
| scoped_ptr<Value> old_value;
|
| - error = ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
|
| - if (!error.empty()) {
|
| - return WriteFailureForKey("find changes to remove multiple items",
|
| - *it,
|
| - error);
|
| - }
|
| + scoped_ptr<Error> read_error =
|
| + ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
|
| + if (read_error)
|
| + return MakeWriteResult(read_error.Pass());
|
|
|
| - if (old_value.get()) {
|
| + if (old_value) {
|
| changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
|
| batch.Delete(*it);
|
| }
|
| @@ -283,144 +242,116 @@ ValueStore::WriteResult LeveldbValueStore::Remove(
|
|
|
| leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| if (!status.ok() && !status.IsNotFound())
|
| - return WriteFailure("remove multiple items", status.ToString());
|
| - return MakeWriteResult(changes.release());
|
| + return MakeWriteResult(ToValueStoreError(status, util::NoKey()));
|
| + return MakeWriteResult(changes.Pass());
|
| }
|
|
|
| ValueStore::WriteResult LeveldbValueStore::Clear() {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - std::string error = EnsureDbIsOpen();
|
| - if (!error.empty())
|
| - return ValueStore::MakeWriteResult(error);
|
| -
|
| - leveldb::ReadOptions read_options;
|
| - // All interaction with the db is done on the same thread, so snapshotting
|
| - // isn't strictly necessary. This is just defensive.
|
| - leveldb::WriteBatch batch;
|
| scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
|
|
|
| - ScopedSnapshot snapshot(db_.get());
|
| - read_options.snapshot = snapshot.get();
|
| - scoped_ptr<leveldb::Iterator> it(db_->NewIterator(read_options));
|
| - for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
| - const std::string key = it->key().ToString();
|
| - const std::string old_value_json = it->value().ToString();
|
| - Value* old_value = base::JSONReader().ReadToValue(old_value_json);
|
| - if (!old_value) {
|
| - // TODO: delete the bad JSON.
|
| - return WriteFailureForKey("find changes to clear", key, kInvalidJson);
|
| - }
|
| - changes->push_back(ValueStoreChange(key, old_value, NULL));
|
| - batch.Delete(key);
|
| + 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 = 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));
|
| }
|
|
|
| - if (it->status().IsNotFound())
|
| - NOTREACHED() << "IsNotFound() but clearing?!";
|
| - else if (!it->status().ok())
|
| - return WriteFailure("find changes to clear", it->status().ToString());
|
| -
|
| - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
|
| - if (status.IsNotFound()) {
|
| - NOTREACHED() << "IsNotFound() but clearing?!";
|
| - return MakeWriteResult(changes.release());
|
| - }
|
| - if (!status.ok())
|
| - return WriteFailure("clear", status.ToString());
|
| - return MakeWriteResult(changes.release());
|
| + DeleteDbFile();
|
| + return MakeWriteResult(changes.Pass());
|
| }
|
|
|
| -std::string LeveldbValueStore::EnsureDbIsOpen() {
|
| +scoped_ptr<ValueStore::Error> LeveldbValueStore::EnsureDbIsOpen() {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| - if (db_.get())
|
| - return std::string();
|
| -
|
| -#if defined(OS_POSIX)
|
| - std::string os_path(db_path_.value());
|
| -#elif defined(OS_WIN)
|
| - std::string os_path = base::SysWideToUTF8(db_path_.value());
|
| -#endif
|
| + if (db_)
|
| + return util::NoError();
|
|
|
| leveldb::Options options;
|
| options.max_open_files = 0; // Use minimum.
|
| options.create_if_missing = true;
|
| - leveldb::DB* db;
|
| - leveldb::Status status = leveldb::DB::Open(options, os_path, &db);
|
| - if (!status.ok()) {
|
| - // |os_path| may contain sensitive data, and these strings are passed
|
| - // through to the extension, so strip that out.
|
| - std::string status_string = status.ToString();
|
| - ReplaceSubstringsAfterOffset(&status_string, 0u, os_path, "...");
|
| - return base::StringPrintf("Failed to open database: %s",
|
| - status_string.c_str());
|
| - }
|
|
|
| + 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 std::string();
|
| + return util::NoError();
|
| }
|
|
|
| -std::string LeveldbValueStore::ReadFromDb(
|
| +scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
|
| leveldb::ReadOptions options,
|
| const std::string& key,
|
| scoped_ptr<Value>* setting) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| - DCHECK(setting != NULL);
|
| + 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 std::string();
|
| + // 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 s.ToString();
|
| + return ToValueStoreError(s, util::NewKey(key));
|
|
|
| Value* value = base::JSONReader().ReadToValue(value_as_json);
|
| - if (value == NULL) {
|
| - // TODO(kalman): clear the offending non-JSON value from the database.
|
| - return kInvalidJson;
|
| - }
|
| + if (!value)
|
| + return Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key));
|
|
|
| setting->reset(value);
|
| - return std::string();
|
| + return util::NoError();
|
| }
|
|
|
| -std::string LeveldbValueStore::AddToBatch(
|
| +scoped_ptr<ValueStore::Error> LeveldbValueStore::AddToBatch(
|
| ValueStore::WriteOptions options,
|
| const std::string& key,
|
| const base::Value& value,
|
| leveldb::WriteBatch* batch,
|
| ValueStoreChangeList* changes) {
|
| - scoped_ptr<Value> old_value;
|
| - if (!(options & NO_CHECK_OLD_VALUE)) {
|
| - std::string error = ReadFromDb(leveldb::ReadOptions(), key, &old_value);
|
| - if (!error.empty())
|
| - return error;
|
| - }
|
| + bool write_new_value = true;
|
|
|
| - if (!old_value.get() || !old_value->Equals(&value)) {
|
| - if (!(options & NO_GENERATE_CHANGES)) {
|
| + if (!(options & NO_GENERATE_CHANGES)) {
|
| + scoped_ptr<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;
|
| base::JSONWriter::Write(&value, &value_as_json);
|
| batch->Put(key, value_as_json);
|
| }
|
|
|
| - return std::string();
|
| + return util::NoError();
|
| }
|
|
|
| -std::string LeveldbValueStore::WriteToDb(leveldb::WriteBatch* batch) {
|
| +scoped_ptr<ValueStore::Error> LeveldbValueStore::WriteToDb(
|
| + leveldb::WriteBatch* batch) {
|
| leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
|
| - if (status.IsNotFound()) {
|
| - NOTREACHED() << "IsNotFound() but writing?!";
|
| - return std::string();
|
| - }
|
| - return status.ok() ? std::string() : status.ToString();
|
| + return status.ok() ? util::NoError()
|
| + : ToValueStoreError(status, util::NoKey());
|
| }
|
|
|
| bool LeveldbValueStore::IsEmpty() {
|
| @@ -435,3 +366,25 @@ bool LeveldbValueStore::IsEmpty() {
|
| }
|
| 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());
|
| +}
|
|
|