Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "extensions/browser/value_store/leveldb_value_store.h" | 5 #include "extensions/browser/value_store/leveldb_value_store.h" |
| 6 | 6 |
| 7 #include "base/files/file_util.h" | 7 #include "base/files/file_util.h" |
| 8 #include "base/json/json_reader.h" | 8 #include "base/json/json_reader.h" |
| 9 #include "base/json/json_writer.h" | 9 #include "base/json/json_writer.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
| 23 | 23 |
| 24 namespace util = value_store_util; | 24 namespace util = value_store_util; |
| 25 using content::BrowserThread; | 25 using content::BrowserThread; |
| 26 | 26 |
| 27 namespace { | 27 namespace { |
| 28 | 28 |
| 29 const char kInvalidJson[] = "Invalid JSON"; | 29 const char kInvalidJson[] = "Invalid JSON"; |
| 30 const char kCannotSerialize[] = "Cannot serialize value to JSON"; | 30 const char kCannotSerialize[] = "Cannot serialize value to JSON"; |
| 31 | 31 |
| 32 // UMA values used when recovering from a corrupted leveldb. | |
| 33 // Do not change/delete these values as you will break reporting for older | |
| 34 // copies of Chrome. Only add new values to the end. | |
| 35 enum LevelDBCorruptionRecoveryValue { | |
| 36 LEVELDB_RESTORE_DELETE_SUCCESS = 0, | |
| 37 LEVELDB_RESTORE_DELETE_FAILURE, | |
| 38 LEVELDB_RESTORE_REPAIR_SUCCESS, | |
| 39 LEVELDB_RESTORE_MAX | |
| 40 }; | |
| 41 | |
| 32 // Scoped leveldb snapshot which releases the snapshot on destruction. | 42 // Scoped leveldb snapshot which releases the snapshot on destruction. |
| 33 class ScopedSnapshot { | 43 class ScopedSnapshot { |
| 34 public: | 44 public: |
| 35 explicit ScopedSnapshot(leveldb::DB* db) | 45 explicit ScopedSnapshot(leveldb::DB* db) |
| 36 : db_(db), snapshot_(db->GetSnapshot()) {} | 46 : db_(db), snapshot_(db->GetSnapshot()) {} |
| 37 | 47 |
| 38 ~ScopedSnapshot() { | 48 ~ScopedSnapshot() { |
| 39 db_->ReleaseSnapshot(snapshot_); | 49 db_->ReleaseSnapshot(snapshot_); |
| 40 } | 50 } |
| 41 | 51 |
| 42 const leveldb::Snapshot* get() { | 52 const leveldb::Snapshot* get() { |
| 43 return snapshot_; | 53 return snapshot_; |
| 44 } | 54 } |
| 45 | 55 |
| 46 private: | 56 private: |
| 47 leveldb::DB* db_; | 57 leveldb::DB* db_; |
| 48 const leveldb::Snapshot* snapshot_; | 58 const leveldb::Snapshot* snapshot_; |
| 49 | 59 |
| 50 DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot); | 60 DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot); |
| 51 }; | 61 }; |
| 52 | 62 |
| 53 } // namespace | 63 } // namespace |
| 54 | 64 |
| 55 LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name, | 65 LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name, |
| 56 const base::FilePath& db_path) | 66 const base::FilePath& db_path) |
| 57 : db_path_(db_path), open_histogram_(nullptr) { | 67 : db_path_(db_path), open_histogram_(nullptr), restore_histogram_(nullptr) { |
| 58 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 68 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 59 | 69 |
| 60 // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is | 70 // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is |
| 61 // not a constant. | 71 // not a constant. |
| 62 open_histogram_ = base::LinearHistogram::FactoryGet( | 72 open_histogram_ = base::LinearHistogram::FactoryGet( |
| 63 "Extensions.Database.Open." + uma_client_name, 1, | 73 "Extensions.Database.Open." + uma_client_name, 1, |
| 64 leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, | 74 leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, |
| 65 base::Histogram::kUmaTargetedHistogramFlag); | 75 base::Histogram::kUmaTargetedHistogramFlag); |
| 76 restore_histogram_ = base::LinearHistogram::FactoryGet( | |
|
Devlin
2015/11/06 19:54:46
This is histogram black magic to me, so I'll trust
cmumford
2015/11/09 23:04:21
I manually hack-ed in a call to "restore_histogram
| |
| 77 "Extensions.Database.Restore." + uma_client_name, 1, LEVELDB_RESTORE_MAX, | |
| 78 LEVELDB_RESTORE_MAX + 1, base::Histogram::kUmaTargetedHistogramFlag); | |
| 66 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( | 79 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| 67 this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get()); | 80 this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get()); |
| 68 } | 81 } |
| 69 | 82 |
| 70 LeveldbValueStore::~LeveldbValueStore() { | 83 LeveldbValueStore::~LeveldbValueStore() { |
| 71 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 84 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 72 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( | 85 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| 73 this); | 86 this); |
| 74 | 87 |
| 75 // Delete the database from disk if it's empty (but only if we managed to | 88 // Delete the database from disk if it's empty (but only if we managed to |
| (...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 280 ValueStoreChange(next_key, next_value.release(), NULL)); | 293 ValueStoreChange(next_key, next_value.release(), NULL)); |
| 281 } | 294 } |
| 282 | 295 |
| 283 DeleteDbFile(); | 296 DeleteDbFile(); |
| 284 return MakeWriteResult(changes.Pass()); | 297 return MakeWriteResult(changes.Pass()); |
| 285 } | 298 } |
| 286 | 299 |
| 287 bool LeveldbValueStore::Restore() { | 300 bool LeveldbValueStore::Restore() { |
| 288 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 301 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 289 | 302 |
| 290 ReadResult result = Get(); | 303 // Possible to have a corrupted open database, so first close it. |
| 291 std::string previous_key; | 304 db_.reset(); |
| 292 while (result->IsCorrupted()) { | |
| 293 // If we don't have a specific corrupted key, or we've tried and failed to | |
| 294 // clear this specific key, or we fail to restore the key, then wipe the | |
| 295 // whole database. | |
| 296 if (!result->error().key.get() || *result->error().key == previous_key || | |
| 297 !RestoreKey(*result->error().key)) { | |
| 298 DeleteDbFile(); | |
| 299 result = Get(); | |
| 300 break; | |
| 301 } | |
| 302 | 305 |
| 303 // Otherwise, re-Get() the database to check if there is still any | 306 leveldb::Options options; |
| 304 // corruption. | 307 options.create_if_missing = true; |
| 305 previous_key = *result->error().key; | 308 |
| 306 result = Get(); | 309 // Repair can drop an unbounded number of leveldb tables (key/value sets) |
| 310 leveldb::Status status = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), options); | |
| 311 if (status.ok()) { | |
| 312 restore_histogram_->Add(LEVELDB_RESTORE_REPAIR_SUCCESS); | |
| 313 return true; | |
| 307 } | 314 } |
| 308 | 315 |
| 309 // If we still have an error, it means we've tried deleting the database file, | 316 if (DeleteDbFile()) { |
| 310 // and failed. There's nothing more we can do. | 317 restore_histogram_->Add(LEVELDB_RESTORE_DELETE_SUCCESS); |
| 311 return !result->IsCorrupted(); | 318 return true; |
| 319 } | |
| 320 | |
| 321 restore_histogram_->Add(LEVELDB_RESTORE_DELETE_FAILURE); | |
| 322 return false; | |
| 312 } | 323 } |
| 313 | 324 |
| 314 bool LeveldbValueStore::RestoreKey(const std::string& key) { | 325 bool LeveldbValueStore::RestoreKey(const std::string& key) { |
| 315 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 326 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 316 | 327 |
| 317 ReadResult result = Get(key); | 328 ReadResult result = Get(key); |
| 318 if (result->IsCorrupted()) { | 329 if (result->IsCorrupted()) { |
| 319 leveldb::WriteBatch batch; | 330 leveldb::WriteBatch batch; |
| 320 batch.Delete(key); | 331 batch.Delete(key); |
| 321 scoped_ptr<ValueStore::Error> error = WriteToDb(&batch); | 332 scoped_ptr<ValueStore::Error> error = WriteToDb(&batch); |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 374 leveldb::Options options; | 385 leveldb::Options options; |
| 375 options.max_open_files = 0; // Use minimum. | 386 options.max_open_files = 0; // Use minimum. |
| 376 options.create_if_missing = true; | 387 options.create_if_missing = true; |
| 377 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; | 388 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; |
| 378 | 389 |
| 379 leveldb::DB* db = NULL; | 390 leveldb::DB* db = NULL; |
| 380 leveldb::Status status = | 391 leveldb::Status status = |
| 381 leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db); | 392 leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db); |
| 382 if (open_histogram_) | 393 if (open_histogram_) |
| 383 open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status)); | 394 open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status)); |
| 395 if (status.IsCorruption()) { | |
| 396 // Returning a corruption error should result in Restore() being called. | |
| 397 // However, since once a leveldb becomes corrupt it's unusable without | |
| 398 // some kind of repair or delete, so do that right now. | |
| 399 Restore(); | |
| 400 } | |
| 384 if (!status.ok()) | 401 if (!status.ok()) |
| 385 return ToValueStoreError(status, util::NoKey()); | 402 return ToValueStoreError(status, util::NoKey()); |
| 386 | 403 |
| 387 CHECK(db); | 404 CHECK(db); |
| 388 db_.reset(db); | 405 db_.reset(db); |
| 389 return util::NoError(); | 406 return util::NoError(); |
| 390 } | 407 } |
| 391 | 408 |
| 392 scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb( | 409 scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb( |
| 393 leveldb::ReadOptions options, | 410 leveldb::ReadOptions options, |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 461 | 478 |
| 462 it->SeekToFirst(); | 479 it->SeekToFirst(); |
| 463 bool is_empty = !it->Valid(); | 480 bool is_empty = !it->Valid(); |
| 464 if (!it->status().ok()) { | 481 if (!it->status().ok()) { |
| 465 LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString(); | 482 LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString(); |
| 466 return false; | 483 return false; |
| 467 } | 484 } |
| 468 return is_empty; | 485 return is_empty; |
| 469 } | 486 } |
| 470 | 487 |
| 471 void LeveldbValueStore::DeleteDbFile() { | 488 bool LeveldbValueStore::DeleteDbFile() { |
| 472 db_.reset(); // release any lock on the directory | 489 db_.reset(); // release any lock on the directory |
| 473 if (!base::DeleteFile(db_path_, true /* recursive */)) { | 490 if (!base::DeleteFile(db_path_, true /* recursive */)) { |
| 474 LOG(WARNING) << "Failed to delete LeveldbValueStore database at " << | 491 LOG(WARNING) << "Failed to delete LeveldbValueStore database at " << |
| 475 db_path_.value(); | 492 db_path_.value(); |
| 493 return false; | |
| 476 } | 494 } |
| 495 return true; | |
| 477 } | 496 } |
| 478 | 497 |
| 479 scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError( | 498 scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError( |
| 480 const leveldb::Status& status, | 499 const leveldb::Status& status, |
| 481 scoped_ptr<std::string> key) { | 500 scoped_ptr<std::string> key) { |
| 482 CHECK(!status.ok()); | 501 CHECK(!status.ok()); |
| 483 CHECK(!status.IsNotFound()); // not an error | 502 CHECK(!status.IsNotFound()); // not an error |
| 484 | 503 |
| 485 std::string message = status.ToString(); | 504 std::string message = status.ToString(); |
| 486 // The message may contain |db_path_|, which may be considered sensitive | 505 // The message may contain |db_path_|, which may be considered sensitive |
| 487 // data, and those strings are passed to the extension, so strip it out. | 506 // data, and those strings are passed to the extension, so strip it out. |
| 488 base::ReplaceSubstringsAfterOffset( | 507 base::ReplaceSubstringsAfterOffset( |
| 489 &message, 0u, db_path_.AsUTF8Unsafe(), "..."); | 508 &message, 0u, db_path_.AsUTF8Unsafe(), "..."); |
| 490 | 509 |
| 491 return Error::Create(CORRUPTION, message, key.Pass()); | 510 return Error::Create(CORRUPTION, message, key.Pass()); |
| 492 } | 511 } |
| OLD | NEW |