leveldb_database.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1// Copyright (c) 2013 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 "content/browser/indexed_db/leveldb/leveldb_database.h"
6
7#include <cerrno>
8
9#include "base/basictypes.h"
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/metrics/histogram.h"
13#include "base/strings/string16.h"
14#include "base/strings/string_piece.h"
15#include "base/strings/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/sys_info.h"
18#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
19#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
20#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
21#include "third_party/leveldatabase/env_chromium.h"
22#include "third_party/leveldatabase/env_idb.h"
23#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
24#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
25#include "third_party/leveldatabase/src/include/leveldb/db.h"
26#include "third_party/leveldatabase/src/include/leveldb/env.h"
27#include "third_party/leveldatabase/src/include/leveldb/slice.h"
28
29using base::StringPiece;
30
31namespace content {
32
33static leveldb::Slice MakeSlice(const StringPiece& s) {
34  return leveldb::Slice(s.begin(), s.size());
35}
36
37static StringPiece MakeStringPiece(const leveldb::Slice& s) {
38  return StringPiece(s.data(), s.size());
39}
40
41class ComparatorAdapter : public leveldb::Comparator {
42 public:
43  explicit ComparatorAdapter(const LevelDBComparator* comparator)
44      : comparator_(comparator) {}
45
46  virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const
47      OVERRIDE {
48    return comparator_->Compare(MakeStringPiece(a), MakeStringPiece(b));
49  }
50
51  virtual const char* Name() const OVERRIDE { return comparator_->Name(); }
52
53  // TODO(jsbell): Support the methods below in the future.
54  virtual void FindShortestSeparator(std::string* start,
55                                     const leveldb::Slice& limit) const
56      OVERRIDE {}
57  virtual void FindShortSuccessor(std::string* key) const OVERRIDE {}
58
59 private:
60  const LevelDBComparator* comparator_;
61};
62
63LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
64    : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
65
66LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); }
67
68LevelDBDatabase::LevelDBDatabase() {}
69
70LevelDBDatabase::~LevelDBDatabase() {
71  // db_'s destructor uses comparator_adapter_; order of deletion is important.
72  db_.reset();
73  comparator_adapter_.reset();
74  env_.reset();
75}
76
77static leveldb::Status OpenDB(leveldb::Comparator* comparator,
78                              leveldb::Env* env,
79                              const base::FilePath& path,
80                              leveldb::DB** db) {
81  leveldb::Options options;
82  options.comparator = comparator;
83  options.create_if_missing = true;
84  options.paranoid_checks = true;
85
86  // Marking compression as explicitly off so snappy support can be
87  // compiled in for other leveldb clients without implicitly enabling
88  // it for IndexedDB. http://crbug.com/81384
89  options.compression = leveldb::kNoCompression;
90
91  // For info about the troubles we've run into with this parameter, see:
92  // https://code.google.com/p/chromium/issues/detail?id=227313#c11
93  options.max_open_files = 80;
94  options.env = env;
95
96  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
97  return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db);
98}
99
100bool LevelDBDatabase::Destroy(const base::FilePath& file_name) {
101  leveldb::Options options;
102  options.env = leveldb::IDBEnv();
103  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
104  const leveldb::Status s =
105      leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
106  return s.ok();
107}
108
109static int CheckFreeSpace(const char* const type,
110                          const base::FilePath& file_name) {
111  std::string name =
112      std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
113  int64 free_disk_space_in_k_bytes =
114      base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
115  if (free_disk_space_in_k_bytes < 0) {
116    base::Histogram::FactoryGet(
117        "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
118        1,
119        2 /*boundary*/,
120        2 /*boundary*/ + 1,
121        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/);
122    return -1;
123  }
124  int clamped_disk_space_k_bytes =
125      free_disk_space_in_k_bytes > INT_MAX ? INT_MAX
126                                           : free_disk_space_in_k_bytes;
127  const uint64 histogram_max = static_cast<uint64>(1e9);
128  COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big);
129  base::Histogram::FactoryGet(name,
130                              1,
131                              histogram_max,
132                              11 /*buckets*/,
133                              base::HistogramBase::kUmaTargetedHistogramFlag)
134      ->Add(clamped_disk_space_k_bytes);
135  return clamped_disk_space_k_bytes;
136}
137
138static void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
139                                            const leveldb::Status& s) {
140  leveldb_env::MethodID method;
141  int error = -1;
142  leveldb_env::ErrorParsingResult result =
143      leveldb_env::ParseMethodAndError(s.ToString().c_str(), &method, &error);
144  if (result == leveldb_env::NONE)
145    return;
146  std::string method_histogram_name(histogram_name);
147  method_histogram_name.append(".EnvMethod");
148  base::LinearHistogram::FactoryGet(
149      method_histogram_name,
150      1,
151      leveldb_env::kNumEntries,
152      leveldb_env::kNumEntries + 1,
153      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method);
154
155  std::string error_histogram_name(histogram_name);
156
157  if (result == leveldb_env::METHOD_AND_PFE) {
158    DCHECK(error < 0);
159    error_histogram_name.append(std::string(".PFE.") +
160                                leveldb_env::MethodIDToString(method));
161    base::LinearHistogram::FactoryGet(
162        error_histogram_name,
163        1,
164        -base::PLATFORM_FILE_ERROR_MAX,
165        -base::PLATFORM_FILE_ERROR_MAX + 1,
166        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error);
167  } else if (result == leveldb_env::METHOD_AND_ERRNO) {
168    error_histogram_name.append(std::string(".Errno.") +
169                                leveldb_env::MethodIDToString(method));
170    base::LinearHistogram::FactoryGet(
171        error_histogram_name,
172        1,
173        ERANGE + 1,
174        ERANGE + 2,
175        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
176  }
177}
178
179static void ParseAndHistogramCorruptionDetails(
180    const std::string& histogram_name,
181    const leveldb::Status& status) {
182  DCHECK(!status.IsIOError());
183  DCHECK(!status.ok());
184  const int kOtherError = 0;
185  int error = kOtherError;
186  const std::string& str_error = status.ToString();
187  // Keep in sync with LevelDBCorruptionTypes in histograms.xml.
188  const char* patterns[] = {
189    "missing files",
190    "log record too small",
191    "corrupted internal key",
192    "partial record",
193    "missing start of fragmented record",
194    "error in middle of record",
195    "unknown record type",
196    "truncated record at end",
197    "bad record length",
198    "VersionEdit",
199    "FileReader invoked with unexpected value",
200    "corrupted key",
201    "CURRENT file does not end with newline",
202    "no meta-nextfile entry",
203    "no meta-lognumber entry",
204    "no last-sequence-number entry",
205    "malformed WriteBatch",
206    "bad WriteBatch Put",
207    "bad WriteBatch Delete",
208    "unknown WriteBatch tag",
209    "WriteBatch has wrong count",
210    "bad entry in block",
211    "bad block contents",
212    "bad block handle",
213    "truncated block read",
214    "block checksum mismatch",
215    "checksum mismatch",
216    "corrupted compressed block contents",
217    "bad block type",
218    "bad magic number",
219    "file is too short",
220  };
221  const size_t kNumPatterns = arraysize(patterns);
222  for (size_t i = 0; i < kNumPatterns; ++i) {
223    if (str_error.find(patterns[i]) != std::string::npos) {
224      error = i + 1;
225      break;
226    }
227  }
228  DCHECK(error >= 0);
229  std::string corruption_histogram_name(histogram_name);
230  corruption_histogram_name.append(".Corruption");
231  base::LinearHistogram::FactoryGet(
232      corruption_histogram_name,
233      1,
234      kNumPatterns + 1,
235      kNumPatterns + 2,
236      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
237}
238
239static void HistogramLevelDBError(const std::string& histogram_name,
240                                  const leveldb::Status& s) {
241  if (s.ok()) {
242    NOTREACHED();
243    return;
244  }
245  enum {
246    LEVEL_DB_NOT_FOUND,
247    LEVEL_DB_CORRUPTION,
248    LEVEL_DB_IO_ERROR,
249    LEVEL_DB_OTHER,
250    LEVEL_DB_MAX_ERROR
251  };
252  int leveldb_error = LEVEL_DB_OTHER;
253  if (s.IsNotFound())
254    leveldb_error = LEVEL_DB_NOT_FOUND;
255  else if (s.IsCorruption())
256    leveldb_error = LEVEL_DB_CORRUPTION;
257  else if (s.IsIOError())
258    leveldb_error = LEVEL_DB_IO_ERROR;
259  base::Histogram::FactoryGet(histogram_name,
260                              1,
261                              LEVEL_DB_MAX_ERROR,
262                              LEVEL_DB_MAX_ERROR + 1,
263                              base::HistogramBase::kUmaTargetedHistogramFlag)
264      ->Add(leveldb_error);
265  if (s.IsIOError())
266    ParseAndHistogramIOErrorDetails(histogram_name, s);
267  else
268    ParseAndHistogramCorruptionDetails(histogram_name, s);
269}
270
271scoped_ptr<LevelDBDatabase> LevelDBDatabase::Open(
272    const base::FilePath& file_name,
273    const LevelDBComparator* comparator,
274    bool* is_disk_full) {
275  scoped_ptr<ComparatorAdapter> comparator_adapter(
276      new ComparatorAdapter(comparator));
277
278  leveldb::DB* db;
279  const leveldb::Status s =
280      OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db);
281
282  if (!s.ok()) {
283    HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
284    int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
285    // Disks with <100k of free space almost never succeed in opening a
286    // leveldb database.
287    if (is_disk_full)
288      *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
289
290    LOG(ERROR) << "Failed to open LevelDB database from "
291               << file_name.AsUTF8Unsafe() << "," << s.ToString();
292    return scoped_ptr<LevelDBDatabase>();
293  }
294
295  CheckFreeSpace("Success", file_name);
296
297  scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
298  result->db_ = make_scoped_ptr(db);
299  result->comparator_adapter_ = comparator_adapter.Pass();
300  result->comparator_ = comparator;
301
302  return result.Pass();
303}
304
305scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
306    const LevelDBComparator* comparator) {
307  scoped_ptr<ComparatorAdapter> comparator_adapter(
308      new ComparatorAdapter(comparator));
309  scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
310
311  leveldb::DB* db;
312  const leveldb::Status s = OpenDB(
313      comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db);
314
315  if (!s.ok()) {
316    LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
317    return scoped_ptr<LevelDBDatabase>();
318  }
319
320  scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
321  result->env_ = in_memory_env.Pass();
322  result->db_ = make_scoped_ptr(db);
323  result->comparator_adapter_ = comparator_adapter.Pass();
324  result->comparator_ = comparator;
325
326  return result.Pass();
327}
328
329bool LevelDBDatabase::Put(const StringPiece& key, std::string* value) {
330  leveldb::WriteOptions write_options;
331  write_options.sync = true;
332
333  const leveldb::Status s =
334      db_->Put(write_options, MakeSlice(key), MakeSlice(*value));
335  if (s.ok())
336    return true;
337  LOG(ERROR) << "LevelDB put failed: " << s.ToString();
338  return false;
339}
340
341bool LevelDBDatabase::Remove(const StringPiece& key) {
342  leveldb::WriteOptions write_options;
343  write_options.sync = true;
344
345  const leveldb::Status s = db_->Delete(write_options, MakeSlice(key));
346  if (s.ok())
347    return true;
348  if (s.IsNotFound())
349    return false;
350  LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
351  return false;
352}
353
354bool LevelDBDatabase::Get(const StringPiece& key,
355                          std::string* value,
356                          bool* found,
357                          const LevelDBSnapshot* snapshot) {
358  *found = false;
359  leveldb::ReadOptions read_options;
360  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
361                                         // performance impact is too great.
362  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
363
364  const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value);
365  if (s.ok()) {
366    *found = true;
367    return true;
368  }
369  if (s.IsNotFound())
370    return true;
371  HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
372  LOG(ERROR) << "LevelDB get failed: " << s.ToString();
373  return false;
374}
375
376bool LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) {
377  leveldb::WriteOptions write_options;
378  write_options.sync = true;
379
380  const leveldb::Status s =
381      db_->Write(write_options, write_batch.write_batch_.get());
382  if (s.ok())
383    return true;
384  HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
385  LOG(ERROR) << "LevelDB write failed: " << s.ToString();
386  return false;
387}
388
389namespace {
390class IteratorImpl : public LevelDBIterator {
391 public:
392  virtual ~IteratorImpl() {}
393
394  virtual bool IsValid() const OVERRIDE;
395  virtual void SeekToLast() OVERRIDE;
396  virtual void Seek(const StringPiece& target) OVERRIDE;
397  virtual void Next() OVERRIDE;
398  virtual void Prev() OVERRIDE;
399  virtual StringPiece Key() const OVERRIDE;
400  virtual StringPiece Value() const OVERRIDE;
401
402 private:
403  friend class content::LevelDBDatabase;
404  explicit IteratorImpl(scoped_ptr<leveldb::Iterator> iterator);
405  void CheckStatus();
406
407  scoped_ptr<leveldb::Iterator> iterator_;
408};
409}
410
411IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it)
412    : iterator_(it.Pass()) {}
413
414void IteratorImpl::CheckStatus() {
415  const leveldb::Status s = iterator_->status();
416  if (!s.ok())
417    LOG(ERROR) << "LevelDB iterator error: " << s.ToString();
418}
419
420bool IteratorImpl::IsValid() const { return iterator_->Valid(); }
421
422void IteratorImpl::SeekToLast() {
423  iterator_->SeekToLast();
424  CheckStatus();
425}
426
427void IteratorImpl::Seek(const StringPiece& target) {
428  iterator_->Seek(MakeSlice(target));
429  CheckStatus();
430}
431
432void IteratorImpl::Next() {
433  DCHECK(IsValid());
434  iterator_->Next();
435  CheckStatus();
436}
437
438void IteratorImpl::Prev() {
439  DCHECK(IsValid());
440  iterator_->Prev();
441  CheckStatus();
442}
443
444StringPiece IteratorImpl::Key() const {
445  DCHECK(IsValid());
446  return MakeStringPiece(iterator_->key());
447}
448
449StringPiece IteratorImpl::Value() const {
450  DCHECK(IsValid());
451  return MakeStringPiece(iterator_->value());
452}
453
454scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
455    const LevelDBSnapshot* snapshot) {
456  leveldb::ReadOptions read_options;
457  read_options.verify_checksums = true;  // TODO(jsbell): Disable this if the
458                                         // performance impact is too great.
459  read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
460
461  scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options));
462  return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass()));
463}
464
465const LevelDBComparator* LevelDBDatabase::Comparator() const {
466  return comparator_;
467}
468
469}  // namespace content
470