1// Copyright (c) 2012 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.
4
5#include "chrome/browser/value_store/leveldb_value_store.h"
6
7#include "base/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 "chrome/browser/value_store/value_store_util.h"
15#include "content/public/browser/browser_thread.h"
16#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
17#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
18
19namespace util = value_store_util;
20using content::BrowserThread;
21
22namespace {
23
24const char kInvalidJson[] = "Invalid JSON";
25
26// Scoped leveldb snapshot which releases the snapshot on destruction.
27class ScopedSnapshot {
28 public:
29  explicit ScopedSnapshot(leveldb::DB* db)
30      : db_(db), snapshot_(db->GetSnapshot()) {}
31
32  ~ScopedSnapshot() {
33    db_->ReleaseSnapshot(snapshot_);
34  }
35
36  const leveldb::Snapshot* get() {
37    return snapshot_;
38  }
39
40 private:
41  leveldb::DB* db_;
42  const leveldb::Snapshot* snapshot_;
43
44  DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot);
45};
46
47}  // namespace
48
49LeveldbValueStore::LeveldbValueStore(const base::FilePath& db_path)
50    : db_path_(db_path) {
51  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
52
53  scoped_ptr<Error> open_error = EnsureDbIsOpen();
54  if (open_error)
55    LOG(WARNING) << open_error->message;
56}
57
58LeveldbValueStore::~LeveldbValueStore() {
59  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
60
61  // Delete the database from disk if it's empty (but only if we managed to
62  // open it!). This is safe on destruction, assuming that we have exclusive
63  // access to the database.
64  if (db_ && IsEmpty())
65    DeleteDbFile();
66}
67
68size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
69  // Let SettingsStorageQuotaEnforcer implement this.
70  NOTREACHED() << "Not implemented";
71  return 0;
72}
73
74size_t LeveldbValueStore::GetBytesInUse(
75    const std::vector<std::string>& keys) {
76  // Let SettingsStorageQuotaEnforcer implement this.
77  NOTREACHED() << "Not implemented";
78  return 0;
79}
80
81size_t LeveldbValueStore::GetBytesInUse() {
82  // Let SettingsStorageQuotaEnforcer implement this.
83  NOTREACHED() << "Not implemented";
84  return 0;
85}
86
87ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
88  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
89
90  scoped_ptr<Error> open_error = EnsureDbIsOpen();
91  if (open_error)
92    return MakeReadResult(open_error.Pass());
93
94  scoped_ptr<Value> setting;
95  scoped_ptr<Error> error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
96  if (error)
97    return MakeReadResult(error.Pass());
98
99  DictionaryValue* settings = new DictionaryValue();
100  if (setting)
101    settings->SetWithoutPathExpansion(key, setting.release());
102  return MakeReadResult(make_scoped_ptr(settings));
103}
104
105ValueStore::ReadResult LeveldbValueStore::Get(
106    const std::vector<std::string>& keys) {
107  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
108
109  scoped_ptr<Error> open_error = EnsureDbIsOpen();
110  if (open_error)
111    return MakeReadResult(open_error.Pass());
112
113  leveldb::ReadOptions options;
114  scoped_ptr<DictionaryValue> settings(new DictionaryValue());
115
116  // All interaction with the db is done on the same thread, so snapshotting
117  // isn't strictly necessary.  This is just defensive.
118  ScopedSnapshot snapshot(db_.get());
119  options.snapshot = snapshot.get();
120  for (std::vector<std::string>::const_iterator it = keys.begin();
121      it != keys.end(); ++it) {
122    scoped_ptr<Value> setting;
123    scoped_ptr<Error> error = ReadFromDb(options, *it, &setting);
124    if (error)
125      return MakeReadResult(error.Pass());
126    if (setting)
127      settings->SetWithoutPathExpansion(*it, setting.release());
128  }
129
130  return MakeReadResult(settings.Pass());
131}
132
133ValueStore::ReadResult LeveldbValueStore::Get() {
134  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
135
136  scoped_ptr<Error> open_error = EnsureDbIsOpen();
137  if (open_error)
138    return MakeReadResult(open_error.Pass());
139
140  base::JSONReader json_reader;
141  leveldb::ReadOptions options = leveldb::ReadOptions();
142  // All interaction with the db is done on the same thread, so snapshotting
143  // isn't strictly necessary.  This is just defensive.
144  scoped_ptr<DictionaryValue> settings(new DictionaryValue());
145
146  ScopedSnapshot snapshot(db_.get());
147  options.snapshot = snapshot.get();
148  scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
149  for (it->SeekToFirst(); it->Valid(); it->Next()) {
150    std::string key = it->key().ToString();
151    Value* value = json_reader.ReadToValue(it->value().ToString());
152    if (!value) {
153      return MakeReadResult(
154          Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key)));
155    }
156    settings->SetWithoutPathExpansion(key, value);
157  }
158
159  if (it->status().IsNotFound()) {
160    NOTREACHED() << "IsNotFound() but iterating over all keys?!";
161    return MakeReadResult(settings.Pass());
162  }
163
164  if (!it->status().ok())
165    return MakeReadResult(ToValueStoreError(it->status(), util::NoKey()));
166
167  return MakeReadResult(settings.Pass());
168}
169
170ValueStore::WriteResult LeveldbValueStore::Set(
171    WriteOptions options, const std::string& key, const Value& value) {
172  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
173
174  scoped_ptr<Error> open_error = EnsureDbIsOpen();
175  if (open_error)
176    return MakeWriteResult(open_error.Pass());
177
178  leveldb::WriteBatch batch;
179  scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
180  scoped_ptr<Error> batch_error =
181      AddToBatch(options, key, value, &batch, changes.get());
182  if (batch_error)
183    return MakeWriteResult(batch_error.Pass());
184
185  scoped_ptr<Error> write_error = WriteToDb(&batch);
186  return write_error ? MakeWriteResult(write_error.Pass())
187                     : MakeWriteResult(changes.Pass());
188}
189
190ValueStore::WriteResult LeveldbValueStore::Set(
191    WriteOptions options, const DictionaryValue& settings) {
192  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
193
194  scoped_ptr<Error> open_error = EnsureDbIsOpen();
195  if (open_error)
196    return MakeWriteResult(open_error.Pass());
197
198  leveldb::WriteBatch batch;
199  scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
200
201  for (DictionaryValue::Iterator it(settings); !it.IsAtEnd(); it.Advance()) {
202    scoped_ptr<Error> batch_error =
203        AddToBatch(options, it.key(), it.value(), &batch, changes.get());
204    if (batch_error)
205      return MakeWriteResult(batch_error.Pass());
206  }
207
208  scoped_ptr<Error> write_error = WriteToDb(&batch);
209  return write_error ? MakeWriteResult(write_error.Pass())
210                     : MakeWriteResult(changes.Pass());
211}
212
213ValueStore::WriteResult LeveldbValueStore::Remove(const std::string& key) {
214  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
215  return Remove(std::vector<std::string>(1, key));
216}
217
218ValueStore::WriteResult LeveldbValueStore::Remove(
219    const std::vector<std::string>& keys) {
220  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
221
222  scoped_ptr<Error> open_error = EnsureDbIsOpen();
223  if (open_error)
224    return MakeWriteResult(open_error.Pass());
225
226  leveldb::WriteBatch batch;
227  scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
228
229  for (std::vector<std::string>::const_iterator it = keys.begin();
230      it != keys.end(); ++it) {
231    scoped_ptr<Value> old_value;
232    scoped_ptr<Error> read_error =
233        ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
234    if (read_error)
235      return MakeWriteResult(read_error.Pass());
236
237    if (old_value) {
238      changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
239      batch.Delete(*it);
240    }
241  }
242
243  leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
244  if (!status.ok() && !status.IsNotFound())
245    return MakeWriteResult(ToValueStoreError(status, util::NoKey()));
246  return MakeWriteResult(changes.Pass());
247}
248
249ValueStore::WriteResult LeveldbValueStore::Clear() {
250  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
251
252  scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
253
254  ReadResult read_result = Get();
255  if (read_result->HasError())
256    return MakeWriteResult(read_result->PassError());
257
258  base::DictionaryValue& whole_db = read_result->settings();
259  while (!whole_db.empty()) {
260    std::string next_key = DictionaryValue::Iterator(whole_db).key();
261    scoped_ptr<base::Value> next_value;
262    whole_db.RemoveWithoutPathExpansion(next_key, &next_value);
263    changes->push_back(
264        ValueStoreChange(next_key, next_value.release(), NULL));
265  }
266
267  DeleteDbFile();
268  return MakeWriteResult(changes.Pass());
269}
270
271scoped_ptr<ValueStore::Error> LeveldbValueStore::EnsureDbIsOpen() {
272  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
273
274  if (db_)
275    return util::NoError();
276
277  leveldb::Options options;
278  options.max_open_files = 0;  // Use minimum.
279  options.create_if_missing = true;
280
281  leveldb::DB* db = NULL;
282  leveldb::Status status =
283      leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
284  if (!status.ok())
285    return ToValueStoreError(status, util::NoKey());
286
287  CHECK(db);
288  db_.reset(db);
289  return util::NoError();
290}
291
292scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
293    leveldb::ReadOptions options,
294    const std::string& key,
295    scoped_ptr<Value>* setting) {
296  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
297  DCHECK(setting);
298
299  std::string value_as_json;
300  leveldb::Status s = db_->Get(options, key, &value_as_json);
301
302  if (s.IsNotFound()) {
303    // Despite there being no value, it was still a success. Check this first
304    // because ok() is false on IsNotFound.
305    return util::NoError();
306  }
307
308  if (!s.ok())
309    return ToValueStoreError(s, util::NewKey(key));
310
311  Value* value = base::JSONReader().ReadToValue(value_as_json);
312  if (!value)
313    return Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key));
314
315  setting->reset(value);
316  return util::NoError();
317}
318
319scoped_ptr<ValueStore::Error> LeveldbValueStore::AddToBatch(
320    ValueStore::WriteOptions options,
321    const std::string& key,
322    const base::Value& value,
323    leveldb::WriteBatch* batch,
324    ValueStoreChangeList* changes) {
325  bool write_new_value = true;
326
327  if (!(options & NO_GENERATE_CHANGES)) {
328    scoped_ptr<Value> old_value;
329    scoped_ptr<Error> read_error =
330        ReadFromDb(leveldb::ReadOptions(), key, &old_value);
331    if (read_error)
332      return read_error.Pass();
333    if (!old_value || !old_value->Equals(&value)) {
334      changes->push_back(
335          ValueStoreChange(key, old_value.release(), value.DeepCopy()));
336    } else {
337      write_new_value = false;
338    }
339  }
340
341  if (write_new_value) {
342    std::string value_as_json;
343    base::JSONWriter::Write(&value, &value_as_json);
344    batch->Put(key, value_as_json);
345  }
346
347  return util::NoError();
348}
349
350scoped_ptr<ValueStore::Error> LeveldbValueStore::WriteToDb(
351    leveldb::WriteBatch* batch) {
352  leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
353  return status.ok() ? util::NoError()
354                     : ToValueStoreError(status, util::NoKey());
355}
356
357bool LeveldbValueStore::IsEmpty() {
358  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
359  scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
360
361  it->SeekToFirst();
362  bool is_empty = !it->Valid();
363  if (!it->status().ok()) {
364    LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString();
365    return false;
366  }
367  return is_empty;
368}
369
370void LeveldbValueStore::DeleteDbFile() {
371  db_.reset();  // release any lock on the directory
372  if (!base::DeleteFile(db_path_, true /* recursive */)) {
373    LOG(WARNING) << "Failed to delete LeveldbValueStore database at " <<
374        db_path_.value();
375  }
376}
377
378scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError(
379    const leveldb::Status& status,
380    scoped_ptr<std::string> key) {
381  CHECK(!status.ok());
382  CHECK(!status.IsNotFound());  // not an error
383
384  std::string message = status.ToString();
385  // The message may contain |db_path_|, which may be considered sensitive
386  // data, and those strings are passed to the extension, so strip it out.
387  ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(), "...");
388
389  return Error::Create(CORRUPTION, message, key.Pass());
390}
391