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.
4
5#include "chrome/browser/prefs/leveldb_pref_store.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/files/file_util.h"
10#include "base/json/json_string_value_serializer.h"
11#include "base/location.h"
12#include "base/metrics/sparse_histogram.h"
13#include "base/sequenced_task_runner.h"
14#include "base/task_runner_util.h"
15#include "base/threading/thread_restrictions.h"
16#include "base/time/time.h"
17#include "base/values.h"
18#include "third_party/leveldatabase/env_chromium.h"
19#include "third_party/leveldatabase/src/include/leveldb/db.h"
20#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
21
22namespace {
23
24enum ErrorMasks {
25  OPENED = 1 << 0,
26  DESTROYED = 1 << 1,
27  REPAIRED = 1 << 2,
28  DESTROY_FAILED = 1 << 3,
29  REPAIR_FAILED = 1 << 4,
30  IO_ERROR = 1 << 5,
31  DATA_LOST = 1 << 6,
32  ITER_NOT_OK = 1 << 7,
33  FILE_NOT_SPECIFIED = 1 << 8,
34};
35
36PersistentPrefStore::PrefReadError IntToPrefReadError(int error) {
37  DCHECK(error);
38  if (error == FILE_NOT_SPECIFIED)
39    return PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED;
40  if (error == OPENED)
41    return PersistentPrefStore::PREF_READ_ERROR_NONE;
42  if (error & IO_ERROR)
43    return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO;
44  if (error & OPENED)
45    return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION;
46  return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY;
47}
48
49} // namespace
50
51struct LevelDBPrefStore::ReadingResults {
52  ReadingResults() : no_dir(true), error(0) {}
53  bool no_dir;
54  scoped_ptr<leveldb::DB> db;
55  scoped_ptr<PrefValueMap> value_map;
56  int error;
57};
58
59// An instance of this class is created on the UI thread but is used
60// exclusively on the FILE thread.
61class LevelDBPrefStore::FileThreadSerializer {
62 public:
63  explicit FileThreadSerializer(scoped_ptr<leveldb::DB> db) : db_(db.Pass()) {}
64  void WriteToDatabase(
65      std::map<std::string, std::string>* keys_to_set,
66      std::set<std::string>* keys_to_delete) {
67    DCHECK(keys_to_set->size() > 0 || keys_to_delete->size() > 0);
68    leveldb::WriteBatch batch;
69    for (std::map<std::string, std::string>::iterator iter =
70             keys_to_set->begin();
71         iter != keys_to_set->end();
72         iter++) {
73      batch.Put(iter->first, iter->second);
74    }
75
76    for (std::set<std::string>::iterator iter = keys_to_delete->begin();
77         iter != keys_to_delete->end();
78         iter++) {
79      batch.Delete(*iter);
80    }
81
82    leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
83
84    // DCHECK is fine; the corresponding error is ignored in JsonPrefStore.
85    // There's also no API available to surface the error back up to the caller.
86    // TODO(dgrogan): UMA?
87    DCHECK(status.ok()) << status.ToString();
88  }
89
90 private:
91  scoped_ptr<leveldb::DB> db_;
92  DISALLOW_COPY_AND_ASSIGN(FileThreadSerializer);
93};
94
95bool MoveDirectoryAside(const base::FilePath& path) {
96  base::FilePath bad_path = path.AppendASCII(".bad");
97  if (!base::Move(path, bad_path)) {
98    base::DeleteFile(bad_path, true);
99    return false;
100  }
101  return true;
102}
103
104/* static */
105void LevelDBPrefStore::OpenDB(const base::FilePath& path,
106                              ReadingResults* reading_results) {
107  DCHECK_EQ(0, reading_results->error);
108  leveldb::Options options;
109  options.create_if_missing = true;
110  leveldb::DB* db;
111  while (1) {
112    leveldb::Status status =
113        leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db);
114    if (status.ok()) {
115      reading_results->db.reset(db);
116      reading_results->error |= OPENED;
117      break;
118    }
119    if (leveldb_env::IsIOError(status)) {
120      reading_results->error |= IO_ERROR;
121      break;
122    }
123    if (reading_results->error & DESTROYED)
124      break;
125
126    DCHECK(!(reading_results->error & REPAIR_FAILED));
127    if (!(reading_results->error & REPAIRED)) {
128      status = leveldb::RepairDB(path.AsUTF8Unsafe(), options);
129      if (status.ok()) {
130        reading_results->error |= REPAIRED;
131        continue;
132      }
133      reading_results->error |= REPAIR_FAILED;
134    }
135    if (!MoveDirectoryAside(path)) {
136      status = leveldb::DestroyDB(path.AsUTF8Unsafe(), options);
137      if (!status.ok()) {
138        reading_results->error |= DESTROY_FAILED;
139        break;
140      }
141    }
142    reading_results->error |= DESTROYED;
143  }
144  DCHECK(reading_results->error);
145  DCHECK(!((reading_results->error & OPENED) &&
146           (reading_results->error & DESTROY_FAILED)));
147  DCHECK(reading_results->error != (REPAIR_FAILED | OPENED));
148}
149
150/* static */
151scoped_ptr<LevelDBPrefStore::ReadingResults> LevelDBPrefStore::DoReading(
152    const base::FilePath& path) {
153  base::ThreadRestrictions::AssertIOAllowed();
154
155  scoped_ptr<ReadingResults> reading_results(new ReadingResults);
156
157  reading_results->no_dir = !base::PathExists(path.DirName());
158  OpenDB(path, reading_results.get());
159  if (!reading_results->db) {
160    DCHECK(!(reading_results->error & OPENED));
161    return reading_results.Pass();
162  }
163
164  DCHECK(reading_results->error & OPENED);
165  reading_results->value_map.reset(new PrefValueMap);
166  scoped_ptr<leveldb::Iterator> it(
167      reading_results->db->NewIterator(leveldb::ReadOptions()));
168  // TODO(dgrogan): Is it really necessary to check it->status() each iteration?
169  for (it->SeekToFirst(); it->Valid() && it->status().ok(); it->Next()) {
170    const std::string value_string = it->value().ToString();
171    JSONStringValueSerializer deserializer(value_string);
172    std::string error_message;
173    int error_code;
174    base::Value* json_value =
175        deserializer.Deserialize(&error_code, &error_message);
176    if (json_value) {
177      reading_results->value_map->SetValue(it->key().ToString(), json_value);
178    } else {
179      DLOG(ERROR) << "Invalid json for key " << it->key().ToString()
180                  << ": " << error_message;
181      reading_results->error |= DATA_LOST;
182    }
183  }
184
185  if (!it->status().ok())
186    reading_results->error |= ITER_NOT_OK;
187
188  return reading_results.Pass();
189}
190
191LevelDBPrefStore::LevelDBPrefStore(
192    const base::FilePath& filename,
193    base::SequencedTaskRunner* sequenced_task_runner)
194    : path_(filename),
195      sequenced_task_runner_(sequenced_task_runner),
196      original_task_runner_(base::MessageLoopProxy::current()),
197      read_only_(false),
198      initialized_(false),
199      read_error_(PREF_READ_ERROR_NONE),
200      weak_ptr_factory_(this) {}
201
202LevelDBPrefStore::~LevelDBPrefStore() {
203  CommitPendingWrite();
204  sequenced_task_runner_->DeleteSoon(FROM_HERE, serializer_.release());
205}
206
207bool LevelDBPrefStore::GetValue(const std::string& key,
208                                const base::Value** result) const {
209  DCHECK(initialized_);
210  const base::Value* tmp = NULL;
211  if (!prefs_.GetValue(key, &tmp)) {
212    return false;
213  }
214
215  if (result)
216    *result = tmp;
217  return true;
218}
219
220// Callers of GetMutableValue have to also call ReportValueChanged.
221bool LevelDBPrefStore::GetMutableValue(const std::string& key,
222                                       base::Value** result) {
223  DCHECK(initialized_);
224  return prefs_.GetValue(key, result);
225}
226
227void LevelDBPrefStore::AddObserver(PrefStore::Observer* observer) {
228  observers_.AddObserver(observer);
229}
230
231void LevelDBPrefStore::RemoveObserver(PrefStore::Observer* observer) {
232  observers_.RemoveObserver(observer);
233}
234
235bool LevelDBPrefStore::HasObservers() const {
236  return observers_.might_have_observers();
237}
238
239bool LevelDBPrefStore::IsInitializationComplete() const { return initialized_; }
240
241void LevelDBPrefStore::PersistFromUIThread() {
242  if (read_only_)
243    return;
244  DCHECK(serializer_);
245
246  scoped_ptr<std::set<std::string> > keys_to_delete(new std::set<std::string>);
247  keys_to_delete->swap(keys_to_delete_);
248
249  scoped_ptr<std::map<std::string, std::string> > keys_to_set(
250      new std::map<std::string, std::string>);
251  keys_to_set->swap(keys_to_set_);
252
253  sequenced_task_runner_->PostTask(
254      FROM_HERE,
255      base::Bind(&LevelDBPrefStore::FileThreadSerializer::WriteToDatabase,
256                 base::Unretained(serializer_.get()),
257                 base::Owned(keys_to_set.release()),
258                 base::Owned(keys_to_delete.release())));
259}
260
261void LevelDBPrefStore::ScheduleWrite() {
262  if (!timer_.IsRunning()) {
263    timer_.Start(FROM_HERE,
264                 base::TimeDelta::FromSeconds(10),
265                 this,
266                 &LevelDBPrefStore::PersistFromUIThread);
267  }
268}
269
270void LevelDBPrefStore::SetValue(const std::string& key, base::Value* value) {
271  SetValueInternal(key, value, true /*notify*/);
272}
273
274void LevelDBPrefStore::SetValueSilently(const std::string& key,
275                                        base::Value* value) {
276  SetValueInternal(key, value, false /*notify*/);
277}
278
279static std::string Serialize(base::Value* value) {
280  std::string value_string;
281  JSONStringValueSerializer serializer(&value_string);
282  bool serialized_ok = serializer.Serialize(*value);
283  DCHECK(serialized_ok);
284  return value_string;
285}
286
287void LevelDBPrefStore::SetValueInternal(const std::string& key,
288                                        base::Value* value,
289                                        bool notify) {
290  DCHECK(initialized_);
291  DCHECK(value);
292  scoped_ptr<base::Value> new_value(value);
293  base::Value* old_value = NULL;
294  prefs_.GetValue(key, &old_value);
295  if (!old_value || !value->Equals(old_value)) {
296    std::string value_string = Serialize(value);
297    prefs_.SetValue(key, new_value.release());
298    MarkForInsertion(key, value_string);
299    if (notify)
300      NotifyObservers(key);
301  }
302}
303
304void LevelDBPrefStore::RemoveValue(const std::string& key) {
305  DCHECK(initialized_);
306  if (prefs_.RemoveValue(key)) {
307    MarkForDeletion(key);
308    NotifyObservers(key);
309  }
310}
311
312bool LevelDBPrefStore::ReadOnly() const { return read_only_; }
313
314PersistentPrefStore::PrefReadError LevelDBPrefStore::GetReadError() const {
315  return read_error_;
316}
317
318PersistentPrefStore::PrefReadError LevelDBPrefStore::ReadPrefs() {
319  DCHECK(!initialized_);
320  scoped_ptr<ReadingResults> reading_results;
321  if (path_.empty()) {
322    reading_results.reset(new ReadingResults);
323    reading_results->error = FILE_NOT_SPECIFIED;
324  } else {
325    reading_results = DoReading(path_);
326  }
327
328  PrefReadError error = IntToPrefReadError(reading_results->error);
329  OnStorageRead(reading_results.Pass());
330  return error;
331}
332
333void LevelDBPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
334  DCHECK_EQ(false, initialized_);
335  error_delegate_.reset(error_delegate);
336  if (path_.empty()) {
337    scoped_ptr<ReadingResults> reading_results(new ReadingResults);
338    reading_results->error = FILE_NOT_SPECIFIED;
339    OnStorageRead(reading_results.Pass());
340    return;
341  }
342  PostTaskAndReplyWithResult(sequenced_task_runner_.get(),
343                             FROM_HERE,
344                             base::Bind(&LevelDBPrefStore::DoReading, path_),
345                             base::Bind(&LevelDBPrefStore::OnStorageRead,
346                                        weak_ptr_factory_.GetWeakPtr()));
347}
348
349void LevelDBPrefStore::CommitPendingWrite() {
350  if (timer_.IsRunning()) {
351    timer_.Stop();
352    PersistFromUIThread();
353  }
354}
355
356void LevelDBPrefStore::MarkForDeletion(const std::string& key) {
357  if (read_only_)
358    return;
359  keys_to_delete_.insert(key);
360  // Need to erase in case there's a set operation in the same batch that would
361  // clobber this delete.
362  keys_to_set_.erase(key);
363  ScheduleWrite();
364}
365
366void LevelDBPrefStore::MarkForInsertion(const std::string& key,
367                                        const std::string& value) {
368  if (read_only_)
369    return;
370  keys_to_set_[key] = value;
371  // Need to erase in case there's a delete operation in the same batch that
372  // would clobber this set.
373  keys_to_delete_.erase(key);
374  ScheduleWrite();
375}
376
377void LevelDBPrefStore::ReportValueChanged(const std::string& key) {
378  base::Value* new_value = NULL;
379  bool contains_value = prefs_.GetValue(key, &new_value);
380  DCHECK(contains_value);
381  std::string value_string = Serialize(new_value);
382  MarkForInsertion(key, value_string);
383  NotifyObservers(key);
384}
385
386void LevelDBPrefStore::NotifyObservers(const std::string& key) {
387  FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key));
388}
389
390void LevelDBPrefStore::OnStorageRead(
391    scoped_ptr<LevelDBPrefStore::ReadingResults> reading_results) {
392  UMA_HISTOGRAM_SPARSE_SLOWLY("LevelDBPrefStore.ReadErrors",
393                              reading_results->error);
394  read_error_ = IntToPrefReadError(reading_results->error);
395
396  if (reading_results->no_dir) {
397    FOR_EACH_OBSERVER(
398        PrefStore::Observer, observers_, OnInitializationCompleted(false));
399    return;
400  }
401
402  initialized_ = true;
403
404  if (reading_results->db) {
405    DCHECK(reading_results->value_map);
406    serializer_.reset(new FileThreadSerializer(reading_results->db.Pass()));
407    prefs_.Swap(reading_results->value_map.get());
408  } else {
409    read_only_ = true;
410  }
411
412  // TODO(dgrogan): Call pref_filter_->FilterOnLoad
413
414  if (error_delegate_.get() && read_error_ != PREF_READ_ERROR_NONE)
415    error_delegate_->OnError(read_error_);
416
417  FOR_EACH_OBSERVER(
418      PrefStore::Observer, observers_, OnInitializationCompleted(true));
419}
420