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