root/extensions/browser/value_store/leveldb_value_store.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. snapshot_
  2. get
  3. GetBytesInUse
  4. GetBytesInUse
  5. GetBytesInUse
  6. Get
  7. Get
  8. Get
  9. Set
  10. Set
  11. Remove
  12. Remove
  13. Clear
  14. Restore
  15. RestoreKey
  16. WriteToDbForTest
  17. EnsureDbIsOpen
  18. ReadFromDb
  19. AddToBatch
  20. WriteToDb
  21. IsEmpty
  22. DeleteDbFile
  23. ToValueStoreError

// 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";

// 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;
    base::JSONWriter::Write(&value, &value_as_json);
    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());
}

/* [<][>][^][v][top][bottom][index][help] */