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