summaryrefslogtreecommitdiffstats
path: root/extensions/browser/value_store/lazy_leveldb.cc
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/browser/value_store/lazy_leveldb.cc')
-rw-r--r--extensions/browser/value_store/lazy_leveldb.cc271
1 files changed, 271 insertions, 0 deletions
diff --git a/extensions/browser/value_store/lazy_leveldb.cc b/extensions/browser/value_store/lazy_leveldb.cc
new file mode 100644
index 0000000..da37f08
--- /dev/null
+++ b/extensions/browser/value_store/lazy_leveldb.cc
@@ -0,0 +1,271 @@
+// Copyright 2016 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/lazy_leveldb.h"
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+using base::StringPiece;
+using content::BrowserThread;
+
+namespace {
+
+const char kInvalidJson[] = "Invalid JSON";
+const char kRestoredDuringOpen[] = "Database corruption repaired during open";
+
+// UMA values used when recovering from a corrupted leveldb.
+// Do not change/delete these values as you will break reporting for older
+// copies of Chrome. Only add new values to the end.
+enum LevelDBDatabaseCorruptionRecoveryValue {
+ LEVELDB_DB_RESTORE_DELETE_SUCCESS = 0,
+ LEVELDB_DB_RESTORE_DELETE_FAILURE,
+ LEVELDB_DB_RESTORE_REPAIR_SUCCESS,
+ LEVELDB_DB_RESTORE_MAX
+};
+
+// UMA values used when recovering from a corrupted leveldb.
+// Do not change/delete these values as you will break reporting for older
+// copies of Chrome. Only add new values to the end.
+enum LevelDBValueCorruptionRecoveryValue {
+ LEVELDB_VALUE_RESTORE_DELETE_SUCCESS,
+ LEVELDB_VALUE_RESTORE_DELETE_FAILURE,
+ LEVELDB_VALUE_RESTORE_MAX
+};
+
+ValueStore::StatusCode LevelDbToValueStoreStatusCode(
+ const leveldb::Status& status) {
+ if (status.ok())
+ return ValueStore::OK;
+ if (status.IsCorruption())
+ return ValueStore::CORRUPTION;
+ return ValueStore::OTHER_ERROR;
+}
+
+} // namespace
+
+LazyLevelDb::LazyLevelDb(const std::string& uma_client_name,
+ const base::FilePath& path)
+ : db_path_(path) {
+ open_options_.create_if_missing = true;
+ open_options_.paranoid_checks = true;
+ open_options_.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+
+ read_options_.verify_checksums = true;
+
+ // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is
+ // not a constant.
+ open_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Open." + uma_client_name, 1,
+ leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ db_restore_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Database.Restore." + uma_client_name, 1,
+ LEVELDB_DB_RESTORE_MAX, LEVELDB_DB_RESTORE_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ value_restore_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Value.Restore." + uma_client_name, 1,
+ LEVELDB_VALUE_RESTORE_MAX, LEVELDB_VALUE_RESTORE_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+LazyLevelDb::~LazyLevelDb() {
+ if (db_ && !BrowserThread::CurrentlyOn(BrowserThread::FILE))
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, db_.release());
+}
+
+ValueStore::Status LazyLevelDb::Read(const std::string& key,
+ scoped_ptr<base::Value>* value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ DCHECK(value);
+
+ std::string value_as_json;
+ leveldb::Status s = db_->Get(read_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 ValueStore::Status();
+ }
+
+ if (!s.ok())
+ return ToValueStoreError(s);
+
+ scoped_ptr<base::Value> val = base::JSONReader().ReadToValue(value_as_json);
+ if (!val)
+ return ValueStore::Status(ValueStore::CORRUPTION, FixCorruption(&key),
+ kInvalidJson);
+
+ *value = std::move(val);
+ return ValueStore::Status();
+}
+
+leveldb::Status LazyLevelDb::Delete(const std::string& key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ DCHECK(db_.get());
+
+ leveldb::WriteBatch batch;
+ batch.Delete(key);
+
+ return db_->Write(leveldb::WriteOptions(), &batch);
+}
+
+ValueStore::BackingStoreRestoreStatus LazyLevelDb::LogRestoreStatus(
+ ValueStore::BackingStoreRestoreStatus restore_status) const {
+ switch (restore_status) {
+ case ValueStore::RESTORE_NONE:
+ NOTREACHED();
+ break;
+ case ValueStore::DB_RESTORE_DELETE_SUCCESS:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_SUCCESS);
+ break;
+ case ValueStore::DB_RESTORE_DELETE_FAILURE:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_FAILURE);
+ break;
+ case ValueStore::DB_RESTORE_REPAIR_SUCCESS:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_REPAIR_SUCCESS);
+ break;
+ case ValueStore::VALUE_RESTORE_DELETE_SUCCESS:
+ value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_SUCCESS);
+ break;
+ case ValueStore::VALUE_RESTORE_DELETE_FAILURE:
+ value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_FAILURE);
+ break;
+ }
+ return restore_status;
+}
+
+ValueStore::BackingStoreRestoreStatus LazyLevelDb::FixCorruption(
+ const std::string* key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ leveldb::Status s;
+ if (key && db_) {
+ s = Delete(*key);
+ // Deleting involves writing to the log, so it's possible to have a
+ // perfectly OK database but still have a delete fail.
+ if (s.ok())
+ return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_SUCCESS);
+ else if (s.IsIOError())
+ return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_FAILURE);
+ // Any other kind of failure triggers a db repair.
+ }
+
+ // Make sure database is closed.
+ db_.reset();
+
+ // First try the less lossy repair.
+ ValueStore::BackingStoreRestoreStatus restore_status =
+ ValueStore::RESTORE_NONE;
+
+ leveldb::Options repair_options;
+ repair_options.create_if_missing = true;
+ repair_options.paranoid_checks = true;
+
+ // RepairDB can drop an unbounded number of leveldb tables (key/value sets).
+ s = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), repair_options);
+
+ leveldb::DB* db = nullptr;
+ if (s.ok()) {
+ restore_status = ValueStore::DB_RESTORE_REPAIR_SUCCESS;
+ s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ }
+
+ if (!s.ok()) {
+ if (DeleteDbFile()) {
+ restore_status = ValueStore::DB_RESTORE_DELETE_SUCCESS;
+ s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ } else {
+ restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE;
+ }
+ }
+
+ if (s.ok())
+ db_.reset(db);
+ else
+ db_unrecoverable_ = true;
+
+ if (s.ok() && key) {
+ s = Delete(*key);
+ if (s.ok()) {
+ restore_status = ValueStore::VALUE_RESTORE_DELETE_SUCCESS;
+ } else if (s.IsIOError()) {
+ restore_status = ValueStore::VALUE_RESTORE_DELETE_FAILURE;
+ } else {
+ db_.reset(db);
+ if (!DeleteDbFile())
+ db_unrecoverable_ = true;
+ restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE;
+ }
+ }
+
+ // Only log for the final and most extreme form of database restoration.
+ LogRestoreStatus(restore_status);
+
+ return restore_status;
+}
+
+ValueStore::Status LazyLevelDb::EnsureDbIsOpen() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ if (db_)
+ return ValueStore::Status();
+
+ if (db_unrecoverable_) {
+ return ValueStore::Status(ValueStore::CORRUPTION,
+ ValueStore::DB_RESTORE_DELETE_FAILURE,
+ "Database corrupted");
+ }
+
+ leveldb::DB* db = nullptr;
+ leveldb::Status ldb_status =
+ leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(ldb_status));
+ ValueStore::Status status = ToValueStoreError(ldb_status);
+ if (ldb_status.ok()) {
+ db_.reset(db);
+ } else if (ldb_status.IsCorruption()) {
+ status.restore_status = FixCorruption(nullptr);
+ if (status.restore_status != ValueStore::DB_RESTORE_DELETE_FAILURE) {
+ status.code = ValueStore::OK;
+ status.message = kRestoredDuringOpen;
+ }
+ }
+
+ return status;
+}
+
+ValueStore::Status LazyLevelDb::ToValueStoreError(
+ const leveldb::Status& status) {
+ 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.
+ base::ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(),
+ "...");
+
+ return ValueStore::Status(LevelDbToValueStoreStatusCode(status), message);
+}
+
+bool LazyLevelDb::DeleteDbFile() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ db_.reset(); // release any lock on the directory
+ if (!base::DeleteFile(db_path_, true /* recursive */)) {
+ LOG(WARNING) << "Failed to delete leveldb database at " << db_path_.value();
+ return false;
+ }
+ return true;
+}
+
+scoped_ptr<leveldb::Iterator> LazyLevelDb::CreateIterator(
+ const leveldb::ReadOptions& read_options) {
+ if (!EnsureDbIsOpen().ok())
+ return nullptr;
+ return make_scoped_ptr(db_->NewIterator(read_options));
+}