Add ICU message format support
[chromium-blink-merge.git] / extensions / browser / value_store / leveldb_value_store.cc
blob39a1346df7d2c2ca41d92585254cf4401e397436
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/browser/value_store/leveldb_value_store.h"
7 #include "base/files/file_util.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "extensions/browser/value_store/value_store_util.h"
16 #include "third_party/leveldatabase/env_chromium.h"
17 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
18 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
20 namespace util = value_store_util;
21 using content::BrowserThread;
23 namespace {
25 const char kInvalidJson[] = "Invalid JSON";
26 const char kCannotSerialize[] = "Cannot serialize value to JSON";
28 // Scoped leveldb snapshot which releases the snapshot on destruction.
29 class ScopedSnapshot {
30 public:
31 explicit ScopedSnapshot(leveldb::DB* db)
32 : db_(db), snapshot_(db->GetSnapshot()) {}
34 ~ScopedSnapshot() {
35 db_->ReleaseSnapshot(snapshot_);
38 const leveldb::Snapshot* get() {
39 return snapshot_;
42 private:
43 leveldb::DB* db_;
44 const leveldb::Snapshot* snapshot_;
46 DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot);
49 } // namespace
51 LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name,
52 const base::FilePath& db_path)
53 : db_path_(db_path), open_histogram_(nullptr) {
54 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
56 // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is
57 // not a constant.
58 open_histogram_ = base::LinearHistogram::FactoryGet(
59 "Extensions.Database.Open." + uma_client_name, 1,
60 leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
61 base::Histogram::kUmaTargetedHistogramFlag);
64 LeveldbValueStore::~LeveldbValueStore() {
65 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
67 // Delete the database from disk if it's empty (but only if we managed to
68 // open it!). This is safe on destruction, assuming that we have exclusive
69 // access to the database.
70 if (db_ && IsEmpty())
71 DeleteDbFile();
74 size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
75 // Let SettingsStorageQuotaEnforcer implement this.
76 NOTREACHED() << "Not implemented";
77 return 0;
80 size_t LeveldbValueStore::GetBytesInUse(
81 const std::vector<std::string>& keys) {
82 // Let SettingsStorageQuotaEnforcer implement this.
83 NOTREACHED() << "Not implemented";
84 return 0;
87 size_t LeveldbValueStore::GetBytesInUse() {
88 // Let SettingsStorageQuotaEnforcer implement this.
89 NOTREACHED() << "Not implemented";
90 return 0;
93 ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
94 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
96 scoped_ptr<Error> open_error = EnsureDbIsOpen();
97 if (open_error)
98 return MakeReadResult(open_error.Pass());
100 scoped_ptr<base::Value> setting;
101 scoped_ptr<Error> error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
102 if (error)
103 return MakeReadResult(error.Pass());
105 base::DictionaryValue* settings = new base::DictionaryValue();
106 if (setting)
107 settings->SetWithoutPathExpansion(key, setting.release());
108 return MakeReadResult(make_scoped_ptr(settings));
111 ValueStore::ReadResult LeveldbValueStore::Get(
112 const std::vector<std::string>& keys) {
113 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
115 scoped_ptr<Error> open_error = EnsureDbIsOpen();
116 if (open_error)
117 return MakeReadResult(open_error.Pass());
119 leveldb::ReadOptions options;
120 scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
122 // All interaction with the db is done on the same thread, so snapshotting
123 // isn't strictly necessary. This is just defensive.
124 ScopedSnapshot snapshot(db_.get());
125 options.snapshot = snapshot.get();
126 for (std::vector<std::string>::const_iterator it = keys.begin();
127 it != keys.end(); ++it) {
128 scoped_ptr<base::Value> setting;
129 scoped_ptr<Error> error = ReadFromDb(options, *it, &setting);
130 if (error)
131 return MakeReadResult(error.Pass());
132 if (setting)
133 settings->SetWithoutPathExpansion(*it, setting.release());
136 return MakeReadResult(settings.Pass());
139 ValueStore::ReadResult LeveldbValueStore::Get() {
140 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
142 scoped_ptr<Error> open_error = EnsureDbIsOpen();
143 if (open_error)
144 return MakeReadResult(open_error.Pass());
146 base::JSONReader json_reader;
147 leveldb::ReadOptions options = leveldb::ReadOptions();
148 // All interaction with the db is done on the same thread, so snapshotting
149 // isn't strictly necessary. This is just defensive.
150 scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
152 ScopedSnapshot snapshot(db_.get());
153 options.snapshot = snapshot.get();
154 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
155 for (it->SeekToFirst(); it->Valid(); it->Next()) {
156 std::string key = it->key().ToString();
157 scoped_ptr<base::Value> value =
158 json_reader.ReadToValue(it->value().ToString());
159 if (!value) {
160 return MakeReadResult(
161 Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key)));
163 settings->SetWithoutPathExpansion(key, value.Pass());
166 if (it->status().IsNotFound()) {
167 NOTREACHED() << "IsNotFound() but iterating over all keys?!";
168 return MakeReadResult(settings.Pass());
171 if (!it->status().ok())
172 return MakeReadResult(ToValueStoreError(it->status(), util::NoKey()));
174 return MakeReadResult(settings.Pass());
177 ValueStore::WriteResult LeveldbValueStore::Set(
178 WriteOptions options, const std::string& key, const base::Value& value) {
179 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
181 scoped_ptr<Error> open_error = EnsureDbIsOpen();
182 if (open_error)
183 return MakeWriteResult(open_error.Pass());
185 leveldb::WriteBatch batch;
186 scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
187 scoped_ptr<Error> batch_error =
188 AddToBatch(options, key, value, &batch, changes.get());
189 if (batch_error)
190 return MakeWriteResult(batch_error.Pass());
192 scoped_ptr<Error> write_error = WriteToDb(&batch);
193 return write_error ? MakeWriteResult(write_error.Pass())
194 : MakeWriteResult(changes.Pass());
197 ValueStore::WriteResult LeveldbValueStore::Set(
198 WriteOptions options, const base::DictionaryValue& settings) {
199 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
201 scoped_ptr<Error> open_error = EnsureDbIsOpen();
202 if (open_error)
203 return MakeWriteResult(open_error.Pass());
205 leveldb::WriteBatch batch;
206 scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
208 for (base::DictionaryValue::Iterator it(settings);
209 !it.IsAtEnd(); it.Advance()) {
210 scoped_ptr<Error> batch_error =
211 AddToBatch(options, it.key(), it.value(), &batch, changes.get());
212 if (batch_error)
213 return MakeWriteResult(batch_error.Pass());
216 scoped_ptr<Error> write_error = WriteToDb(&batch);
217 return write_error ? MakeWriteResult(write_error.Pass())
218 : MakeWriteResult(changes.Pass());
221 ValueStore::WriteResult LeveldbValueStore::Remove(const std::string& key) {
222 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
223 return Remove(std::vector<std::string>(1, key));
226 ValueStore::WriteResult LeveldbValueStore::Remove(
227 const std::vector<std::string>& keys) {
228 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
230 scoped_ptr<Error> open_error = EnsureDbIsOpen();
231 if (open_error)
232 return MakeWriteResult(open_error.Pass());
234 leveldb::WriteBatch batch;
235 scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
237 for (std::vector<std::string>::const_iterator it = keys.begin();
238 it != keys.end(); ++it) {
239 scoped_ptr<base::Value> old_value;
240 scoped_ptr<Error> read_error =
241 ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
242 if (read_error)
243 return MakeWriteResult(read_error.Pass());
245 if (old_value) {
246 changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
247 batch.Delete(*it);
251 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
252 if (!status.ok() && !status.IsNotFound())
253 return MakeWriteResult(ToValueStoreError(status, util::NoKey()));
254 return MakeWriteResult(changes.Pass());
257 ValueStore::WriteResult LeveldbValueStore::Clear() {
258 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
260 scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
262 ReadResult read_result = Get();
263 if (read_result->HasError())
264 return MakeWriteResult(read_result->PassError());
266 base::DictionaryValue& whole_db = read_result->settings();
267 while (!whole_db.empty()) {
268 std::string next_key = base::DictionaryValue::Iterator(whole_db).key();
269 scoped_ptr<base::Value> next_value;
270 whole_db.RemoveWithoutPathExpansion(next_key, &next_value);
271 changes->push_back(
272 ValueStoreChange(next_key, next_value.release(), NULL));
275 DeleteDbFile();
276 return MakeWriteResult(changes.Pass());
279 bool LeveldbValueStore::Restore() {
280 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
282 ReadResult result = Get();
283 std::string previous_key;
284 while (result->IsCorrupted()) {
285 // If we don't have a specific corrupted key, or we've tried and failed to
286 // clear this specific key, or we fail to restore the key, then wipe the
287 // whole database.
288 if (!result->error().key.get() || *result->error().key == previous_key ||
289 !RestoreKey(*result->error().key)) {
290 DeleteDbFile();
291 result = Get();
292 break;
295 // Otherwise, re-Get() the database to check if there is still any
296 // corruption.
297 previous_key = *result->error().key;
298 result = Get();
301 // If we still have an error, it means we've tried deleting the database file,
302 // and failed. There's nothing more we can do.
303 return !result->IsCorrupted();
306 bool LeveldbValueStore::RestoreKey(const std::string& key) {
307 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
309 ReadResult result = Get(key);
310 if (result->IsCorrupted()) {
311 leveldb::WriteBatch batch;
312 batch.Delete(key);
313 scoped_ptr<ValueStore::Error> error = WriteToDb(&batch);
314 // If we can't delete the key, the restore failed.
315 if (error.get())
316 return false;
317 result = Get(key);
320 // The restore succeeded if there is no corruption error.
321 return !result->IsCorrupted();
324 bool LeveldbValueStore::WriteToDbForTest(leveldb::WriteBatch* batch) {
325 return !WriteToDb(batch).get();
328 scoped_ptr<ValueStore::Error> LeveldbValueStore::EnsureDbIsOpen() {
329 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
331 if (db_)
332 return util::NoError();
334 leveldb::Options options;
335 options.max_open_files = 0; // Use minimum.
336 options.create_if_missing = true;
337 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
339 leveldb::DB* db = NULL;
340 leveldb::Status status =
341 leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
342 if (open_histogram_)
343 open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status));
344 if (!status.ok())
345 return ToValueStoreError(status, util::NoKey());
347 CHECK(db);
348 db_.reset(db);
349 return util::NoError();
352 scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
353 leveldb::ReadOptions options,
354 const std::string& key,
355 scoped_ptr<base::Value>* setting) {
356 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
357 DCHECK(setting);
359 std::string value_as_json;
360 leveldb::Status s = db_->Get(options, key, &value_as_json);
362 if (s.IsNotFound()) {
363 // Despite there being no value, it was still a success. Check this first
364 // because ok() is false on IsNotFound.
365 return util::NoError();
368 if (!s.ok())
369 return ToValueStoreError(s, util::NewKey(key));
371 scoped_ptr<base::Value> value = base::JSONReader().ReadToValue(value_as_json);
372 if (!value)
373 return Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key));
375 *setting = value.Pass();
376 return util::NoError();
379 scoped_ptr<ValueStore::Error> LeveldbValueStore::AddToBatch(
380 ValueStore::WriteOptions options,
381 const std::string& key,
382 const base::Value& value,
383 leveldb::WriteBatch* batch,
384 ValueStoreChangeList* changes) {
385 bool write_new_value = true;
387 if (!(options & NO_GENERATE_CHANGES)) {
388 scoped_ptr<base::Value> old_value;
389 scoped_ptr<Error> read_error =
390 ReadFromDb(leveldb::ReadOptions(), key, &old_value);
391 if (read_error)
392 return read_error.Pass();
393 if (!old_value || !old_value->Equals(&value)) {
394 changes->push_back(
395 ValueStoreChange(key, old_value.release(), value.DeepCopy()));
396 } else {
397 write_new_value = false;
401 if (write_new_value) {
402 std::string value_as_json;
403 if (!base::JSONWriter::Write(value, &value_as_json))
404 return Error::Create(OTHER_ERROR, kCannotSerialize, util::NewKey(key));
405 batch->Put(key, value_as_json);
408 return util::NoError();
411 scoped_ptr<ValueStore::Error> LeveldbValueStore::WriteToDb(
412 leveldb::WriteBatch* batch) {
413 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
414 return status.ok() ? util::NoError()
415 : ToValueStoreError(status, util::NoKey());
418 bool LeveldbValueStore::IsEmpty() {
419 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
420 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
422 it->SeekToFirst();
423 bool is_empty = !it->Valid();
424 if (!it->status().ok()) {
425 LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString();
426 return false;
428 return is_empty;
431 void LeveldbValueStore::DeleteDbFile() {
432 db_.reset(); // release any lock on the directory
433 if (!base::DeleteFile(db_path_, true /* recursive */)) {
434 LOG(WARNING) << "Failed to delete LeveldbValueStore database at " <<
435 db_path_.value();
439 scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError(
440 const leveldb::Status& status,
441 scoped_ptr<std::string> key) {
442 CHECK(!status.ok());
443 CHECK(!status.IsNotFound()); // not an error
445 std::string message = status.ToString();
446 // The message may contain |db_path_|, which may be considered sensitive
447 // data, and those strings are passed to the extension, so strip it out.
448 base::ReplaceSubstringsAfterOffset(
449 &message, 0u, db_path_.AsUTF8Unsafe(), "...");
451 return Error::Create(CORRUPTION, message, key.Pass());