Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / storage / browser / fileapi / sandbox_directory_database.cc
blob454386371b6828344d5e8e382d5b32761a2517aa
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.
5 #include "storage/browser/fileapi/sandbox_directory_database.h"
7 #include <math.h>
8 #include <algorithm>
9 #include <set>
10 #include <stack>
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram.h"
16 #include "base/pickle.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "storage/browser/fileapi/file_system_usage_cache.h"
20 #include "storage/common/fileapi/file_system_util.h"
21 #include "third_party/leveldatabase/env_chromium.h"
22 #include "third_party/leveldatabase/src/include/leveldb/db.h"
23 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
25 namespace {
27 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
28 base::Pickle* pickle) {
29 DCHECK(pickle);
30 std::string data_path;
31 // Round off here to match the behavior of the filesystem on real files.
32 base::Time time =
33 base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
34 std::string name;
36 data_path = storage::FilePathToString(info.data_path);
37 name = storage::FilePathToString(base::FilePath(info.name));
39 if (pickle->WriteInt64(info.parent_id) &&
40 pickle->WriteString(data_path) &&
41 pickle->WriteString(name) &&
42 pickle->WriteInt64(time.ToInternalValue()))
43 return true;
45 NOTREACHED();
46 return false;
49 bool FileInfoFromPickle(const base::Pickle& pickle,
50 storage::SandboxDirectoryDatabase::FileInfo* info) {
51 base::PickleIterator iter(pickle);
52 std::string data_path;
53 std::string name;
54 int64 internal_time;
56 if (iter.ReadInt64(&info->parent_id) &&
57 iter.ReadString(&data_path) &&
58 iter.ReadString(&name) &&
59 iter.ReadInt64(&internal_time)) {
60 info->data_path = storage::StringToFilePath(data_path);
61 info->name = storage::StringToFilePath(name).value();
62 info->modification_time = base::Time::FromInternalValue(internal_time);
63 return true;
65 LOG(ERROR) << "base::Pickle could not be digested!";
66 return false;
69 const base::FilePath::CharType kDirectoryDatabaseName[] =
70 FILE_PATH_LITERAL("Paths");
71 const char kChildLookupPrefix[] = "CHILD_OF:";
72 const char kChildLookupSeparator[] = ":";
73 const char kLastFileIdKey[] = "LAST_FILE_ID";
74 const char kLastIntegerKey[] = "LAST_INTEGER";
75 const int64 kMinimumReportIntervalHours = 1;
76 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
77 const char kDatabaseRepairHistogramLabel[] =
78 "FileSystem.DirectoryDatabaseRepair";
80 enum InitStatus {
81 INIT_STATUS_OK = 0,
82 INIT_STATUS_CORRUPTION,
83 INIT_STATUS_IO_ERROR,
84 INIT_STATUS_UNKNOWN_ERROR,
85 INIT_STATUS_MAX
88 enum RepairResult {
89 DB_REPAIR_SUCCEEDED = 0,
90 DB_REPAIR_FAILED,
91 DB_REPAIR_MAX
94 std::string GetChildLookupKey(
95 storage::SandboxDirectoryDatabase::FileId parent_id,
96 const base::FilePath::StringType& child_name) {
97 std::string name;
98 name = storage::FilePathToString(base::FilePath(child_name));
99 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
100 std::string(kChildLookupSeparator) + name;
103 std::string GetChildListingKeyPrefix(
104 storage::SandboxDirectoryDatabase::FileId parent_id) {
105 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
106 std::string(kChildLookupSeparator);
109 const char* LastFileIdKey() {
110 return kLastFileIdKey;
113 const char* LastIntegerKey() {
114 return kLastIntegerKey;
117 std::string GetFileLookupKey(
118 storage::SandboxDirectoryDatabase::FileId file_id) {
119 return base::Int64ToString(file_id);
122 // Assumptions:
123 // - Any database entry is one of:
124 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
125 // - ("LAST_FILE_ID", "|last_file_id|"),
126 // - ("LAST_INTEGER", "|last_integer|"),
127 // - ("|file_id|", "pickled FileInfo")
128 // where FileInfo has |parent_id|, |data_path|, |name| and
129 // |modification_time|,
130 // Constraints:
131 // - Each file in the database has unique backing file.
132 // - Each file in |filesystem_data_directory_| has a database entry.
133 // - Directory structure is tree, i.e. connected and acyclic.
134 class DatabaseCheckHelper {
135 public:
136 typedef storage::SandboxDirectoryDatabase::FileId FileId;
137 typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
139 DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
140 leveldb::DB* db,
141 const base::FilePath& path);
143 bool IsFileSystemConsistent() {
144 return IsDatabaseEmpty() ||
145 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
148 private:
149 bool IsDatabaseEmpty();
150 // These 3 methods need to be called in the order. Each method requires its
151 // previous method finished successfully. They also require the database is
152 // not empty.
153 bool ScanDatabase();
154 bool ScanDirectory();
155 bool ScanHierarchy();
157 storage::SandboxDirectoryDatabase* dir_db_;
158 leveldb::DB* db_;
159 base::FilePath path_;
161 std::set<base::FilePath> files_in_db_;
163 size_t num_directories_in_db_;
164 size_t num_files_in_db_;
165 size_t num_hierarchy_links_in_db_;
167 FileId last_file_id_;
168 FileId last_integer_;
171 DatabaseCheckHelper::DatabaseCheckHelper(
172 storage::SandboxDirectoryDatabase* dir_db,
173 leveldb::DB* db,
174 const base::FilePath& path)
175 : dir_db_(dir_db),
176 db_(db),
177 path_(path),
178 num_directories_in_db_(0),
179 num_files_in_db_(0),
180 num_hierarchy_links_in_db_(0),
181 last_file_id_(-1),
182 last_integer_(-1) {
183 DCHECK(dir_db_);
184 DCHECK(db_);
185 DCHECK(!path_.empty() && base::DirectoryExists(path_));
188 bool DatabaseCheckHelper::IsDatabaseEmpty() {
189 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
190 itr->SeekToFirst();
191 return !itr->Valid();
194 bool DatabaseCheckHelper::ScanDatabase() {
195 // Scans all database entries sequentially to verify each of them has unique
196 // backing file.
197 int64 max_file_id = -1;
198 std::set<FileId> file_ids;
200 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
201 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
202 std::string key = itr->key().ToString();
203 if (StartsWithASCII(key, kChildLookupPrefix, true)) {
204 // key: "CHILD_OF:<parent_id>:<name>"
205 // value: "<child_id>"
206 ++num_hierarchy_links_in_db_;
207 } else if (key == kLastFileIdKey) {
208 // key: "LAST_FILE_ID"
209 // value: "<last_file_id>"
210 if (last_file_id_ >= 0 ||
211 !base::StringToInt64(itr->value().ToString(), &last_file_id_))
212 return false;
214 if (last_file_id_ < 0)
215 return false;
216 } else if (key == kLastIntegerKey) {
217 // key: "LAST_INTEGER"
218 // value: "<last_integer>"
219 if (last_integer_ >= 0 ||
220 !base::StringToInt64(itr->value().ToString(), &last_integer_))
221 return false;
222 } else {
223 // key: "<entry_id>"
224 // value: "<pickled FileInfo>"
225 FileInfo file_info;
226 if (!FileInfoFromPickle(
227 base::Pickle(itr->value().data(), itr->value().size()),
228 &file_info))
229 return false;
231 FileId file_id = -1;
232 if (!base::StringToInt64(key, &file_id) || file_id < 0)
233 return false;
235 if (max_file_id < file_id)
236 max_file_id = file_id;
237 if (!file_ids.insert(file_id).second)
238 return false;
240 if (file_info.is_directory()) {
241 ++num_directories_in_db_;
242 DCHECK(file_info.data_path.empty());
243 } else {
244 // Ensure any pair of file entry don't share their data_path.
245 if (!files_in_db_.insert(file_info.data_path).second)
246 return false;
248 // Ensure the backing file exists as a normal file.
249 base::File::Info platform_file_info;
250 if (!base::GetFileInfo(
251 path_.Append(file_info.data_path), &platform_file_info) ||
252 platform_file_info.is_directory ||
253 platform_file_info.is_symbolic_link) {
254 // leveldb::Iterator iterates a snapshot of the database.
255 // So even after RemoveFileInfo() call, we'll visit hierarchy link
256 // from |parent_id| to |file_id|.
257 if (!dir_db_->RemoveFileInfo(file_id))
258 return false;
259 --num_hierarchy_links_in_db_;
260 files_in_db_.erase(file_info.data_path);
261 } else {
262 ++num_files_in_db_;
268 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
269 // data path confliction on ObfuscatedFileUtil.
270 return max_file_id <= last_file_id_;
273 bool DatabaseCheckHelper::ScanDirectory() {
274 // TODO(kinuko): Scans all local file system entries to verify each of them
275 // has a database entry.
276 const base::FilePath kExcludes[] = {
277 base::FilePath(kDirectoryDatabaseName),
278 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
281 // Any path in |pending_directories| is relative to |path_|.
282 std::stack<base::FilePath> pending_directories;
283 pending_directories.push(base::FilePath());
285 while (!pending_directories.empty()) {
286 base::FilePath dir_path = pending_directories.top();
287 pending_directories.pop();
289 base::FileEnumerator file_enum(
290 dir_path.empty() ? path_ : path_.Append(dir_path),
291 false /* not recursive */,
292 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
294 base::FilePath absolute_file_path;
295 while (!(absolute_file_path = file_enum.Next()).empty()) {
296 base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
298 base::FilePath relative_file_path;
299 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
300 return false;
302 if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
303 relative_file_path) != kExcludes + arraysize(kExcludes))
304 continue;
306 if (find_info.IsDirectory()) {
307 pending_directories.push(relative_file_path);
308 continue;
311 // Check if the file has a database entry.
312 std::set<base::FilePath>::iterator itr =
313 files_in_db_.find(relative_file_path);
314 if (itr == files_in_db_.end()) {
315 if (!base::DeleteFile(absolute_file_path, false))
316 return false;
317 } else {
318 files_in_db_.erase(itr);
323 return files_in_db_.empty();
326 bool DatabaseCheckHelper::ScanHierarchy() {
327 size_t visited_directories = 0;
328 size_t visited_files = 0;
329 size_t visited_links = 0;
331 std::stack<FileId> directories;
332 directories.push(0);
334 // Check if the root directory exists as a directory.
335 FileInfo file_info;
336 if (!dir_db_->GetFileInfo(0, &file_info))
337 return false;
338 if (file_info.parent_id != 0 ||
339 !file_info.is_directory())
340 return false;
342 while (!directories.empty()) {
343 ++visited_directories;
344 FileId dir_id = directories.top();
345 directories.pop();
347 std::vector<FileId> children;
348 if (!dir_db_->ListChildren(dir_id, &children))
349 return false;
350 for (std::vector<FileId>::iterator itr = children.begin();
351 itr != children.end();
352 ++itr) {
353 // Any directory must not have root directory as child.
354 if (!*itr)
355 return false;
357 // Check if the child knows the parent as its parent.
358 FileInfo file_info;
359 if (!dir_db_->GetFileInfo(*itr, &file_info))
360 return false;
361 if (file_info.parent_id != dir_id)
362 return false;
364 // Check if the parent knows the name of its child correctly.
365 FileId file_id;
366 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
367 file_id != *itr)
368 return false;
370 if (file_info.is_directory())
371 directories.push(*itr);
372 else
373 ++visited_files;
374 ++visited_links;
378 // Check if we've visited all database entries.
379 return num_directories_in_db_ == visited_directories &&
380 num_files_in_db_ == visited_files &&
381 num_hierarchy_links_in_db_ == visited_links;
384 // Returns true if the given |data_path| contains no parent references ("..")
385 // and does not refer to special system files.
386 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
387 // ensure we're only dealing with valid data paths.
388 bool VerifyDataPath(const base::FilePath& data_path) {
389 // |data_path| should not contain any ".." and should be a relative path
390 // (to the filesystem_data_directory_).
391 if (data_path.ReferencesParent() || data_path.IsAbsolute())
392 return false;
393 // See if it's not pointing to the special system paths.
394 const base::FilePath kExcludes[] = {
395 base::FilePath(kDirectoryDatabaseName),
396 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
398 for (size_t i = 0; i < arraysize(kExcludes); ++i) {
399 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
400 return false;
402 return true;
405 } // namespace
407 namespace storage {
409 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
412 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
415 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
416 const base::FilePath& filesystem_data_directory,
417 leveldb::Env* env_override)
418 : filesystem_data_directory_(filesystem_data_directory),
419 env_override_(env_override) {
422 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
425 bool SandboxDirectoryDatabase::GetChildWithName(
426 FileId parent_id,
427 const base::FilePath::StringType& name,
428 FileId* child_id) {
429 if (!Init(REPAIR_ON_CORRUPTION))
430 return false;
431 DCHECK(child_id);
432 std::string child_key = GetChildLookupKey(parent_id, name);
433 std::string child_id_string;
434 leveldb::Status status =
435 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
436 if (status.IsNotFound())
437 return false;
438 if (status.ok()) {
439 if (!base::StringToInt64(child_id_string, child_id)) {
440 LOG(ERROR) << "Hit database corruption!";
441 return false;
443 return true;
445 HandleError(FROM_HERE, status);
446 return false;
449 bool SandboxDirectoryDatabase::GetFileWithPath(
450 const base::FilePath& path, FileId* file_id) {
451 std::vector<base::FilePath::StringType> components;
452 VirtualPath::GetComponents(path, &components);
453 FileId local_id = 0;
454 std::vector<base::FilePath::StringType>::iterator iter;
455 for (iter = components.begin(); iter != components.end(); ++iter) {
456 base::FilePath::StringType name;
457 name = *iter;
458 if (name == FILE_PATH_LITERAL("/"))
459 continue;
460 if (!GetChildWithName(local_id, name, &local_id))
461 return false;
463 *file_id = local_id;
464 return true;
467 bool SandboxDirectoryDatabase::ListChildren(
468 FileId parent_id, std::vector<FileId>* children) {
469 // Check to add later: fail if parent is a file, at least in debug builds.
470 if (!Init(REPAIR_ON_CORRUPTION))
471 return false;
472 DCHECK(children);
473 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
475 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
476 iter->Seek(child_key_prefix);
477 children->clear();
478 while (iter->Valid() &&
479 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
480 std::string child_id_string = iter->value().ToString();
481 FileId child_id;
482 if (!base::StringToInt64(child_id_string, &child_id)) {
483 LOG(ERROR) << "Hit database corruption!";
484 return false;
486 children->push_back(child_id);
487 iter->Next();
489 return true;
492 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
493 if (!Init(REPAIR_ON_CORRUPTION))
494 return false;
495 DCHECK(info);
496 std::string file_key = GetFileLookupKey(file_id);
497 std::string file_data_string;
498 leveldb::Status status =
499 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
500 if (status.ok()) {
501 bool success = FileInfoFromPickle(
502 base::Pickle(file_data_string.data(), file_data_string.length()), info);
503 if (!success)
504 return false;
505 if (!VerifyDataPath(info->data_path)) {
506 LOG(ERROR) << "Resolved data path is invalid: "
507 << info->data_path.value();
508 return false;
510 return true;
512 // Special-case the root, for databases that haven't been initialized yet.
513 // Without this, a query for the root's file info, made before creating the
514 // first file in the database, will fail and confuse callers.
515 if (status.IsNotFound() && !file_id) {
516 info->name = base::FilePath::StringType();
517 info->data_path = base::FilePath();
518 info->modification_time = base::Time::Now();
519 info->parent_id = 0;
520 return true;
522 HandleError(FROM_HERE, status);
523 return false;
526 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
527 const FileInfo& info, FileId* file_id) {
528 if (!Init(REPAIR_ON_CORRUPTION))
529 return base::File::FILE_ERROR_FAILED;
530 DCHECK(file_id);
531 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
532 std::string child_id_string;
533 leveldb::Status status =
534 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
535 if (status.ok()) {
536 LOG(ERROR) << "File exists already!";
537 return base::File::FILE_ERROR_EXISTS;
539 if (!status.IsNotFound()) {
540 HandleError(FROM_HERE, status);
541 return base::File::FILE_ERROR_NOT_FOUND;
544 if (!IsDirectory(info.parent_id)) {
545 LOG(ERROR) << "New parent directory is a file!";
546 return base::File::FILE_ERROR_NOT_A_DIRECTORY;
549 // This would be a fine place to limit the number of files in a directory, if
550 // we decide to add that restriction.
552 FileId temp_id;
553 if (!GetLastFileId(&temp_id))
554 return base::File::FILE_ERROR_FAILED;
555 ++temp_id;
557 leveldb::WriteBatch batch;
558 if (!AddFileInfoHelper(info, temp_id, &batch))
559 return base::File::FILE_ERROR_FAILED;
561 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
562 status = db_->Write(leveldb::WriteOptions(), &batch);
563 if (!status.ok()) {
564 HandleError(FROM_HERE, status);
565 return base::File::FILE_ERROR_FAILED;
567 *file_id = temp_id;
568 return base::File::FILE_OK;
571 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
572 if (!Init(REPAIR_ON_CORRUPTION))
573 return false;
574 leveldb::WriteBatch batch;
575 if (!RemoveFileInfoHelper(file_id, &batch))
576 return false;
577 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
578 if (!status.ok()) {
579 HandleError(FROM_HERE, status);
580 return false;
582 return true;
585 bool SandboxDirectoryDatabase::UpdateFileInfo(
586 FileId file_id, const FileInfo& new_info) {
587 // TODO(ericu): We should also check to see that this doesn't create a loop,
588 // but perhaps only in a debug build.
589 if (!Init(REPAIR_ON_CORRUPTION))
590 return false;
591 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
592 FileInfo old_info;
593 if (!GetFileInfo(file_id, &old_info))
594 return false;
595 if (old_info.parent_id != new_info.parent_id &&
596 !IsDirectory(new_info.parent_id))
597 return false;
598 if (old_info.parent_id != new_info.parent_id ||
599 old_info.name != new_info.name) {
600 // Check for name clashes.
601 FileId temp_id;
602 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
603 LOG(ERROR) << "Name collision on move.";
604 return false;
607 leveldb::WriteBatch batch;
608 if (!RemoveFileInfoHelper(file_id, &batch) ||
609 !AddFileInfoHelper(new_info, file_id, &batch))
610 return false;
611 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
612 if (!status.ok()) {
613 HandleError(FROM_HERE, status);
614 return false;
616 return true;
619 bool SandboxDirectoryDatabase::UpdateModificationTime(
620 FileId file_id, const base::Time& modification_time) {
621 FileInfo info;
622 if (!GetFileInfo(file_id, &info))
623 return false;
624 info.modification_time = modification_time;
625 base::Pickle pickle;
626 if (!PickleFromFileInfo(info, &pickle))
627 return false;
628 leveldb::Status status = db_->Put(
629 leveldb::WriteOptions(),
630 GetFileLookupKey(file_id),
631 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
632 pickle.size()));
633 if (!status.ok()) {
634 HandleError(FROM_HERE, status);
635 return false;
637 return true;
640 bool SandboxDirectoryDatabase::OverwritingMoveFile(
641 FileId src_file_id, FileId dest_file_id) {
642 FileInfo src_file_info;
643 FileInfo dest_file_info;
645 if (!GetFileInfo(src_file_id, &src_file_info))
646 return false;
647 if (!GetFileInfo(dest_file_id, &dest_file_info))
648 return false;
649 if (src_file_info.is_directory() || dest_file_info.is_directory())
650 return false;
651 leveldb::WriteBatch batch;
652 // This is the only field that really gets moved over; if you add fields to
653 // FileInfo, e.g. ctime, they might need to be copied here.
654 dest_file_info.data_path = src_file_info.data_path;
655 if (!RemoveFileInfoHelper(src_file_id, &batch))
656 return false;
657 base::Pickle pickle;
658 if (!PickleFromFileInfo(dest_file_info, &pickle))
659 return false;
660 batch.Put(
661 GetFileLookupKey(dest_file_id),
662 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
663 pickle.size()));
664 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
665 if (!status.ok()) {
666 HandleError(FROM_HERE, status);
667 return false;
669 return true;
672 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
673 if (!Init(REPAIR_ON_CORRUPTION))
674 return false;
675 DCHECK(next);
676 std::string int_string;
677 leveldb::Status status =
678 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
679 if (status.ok()) {
680 int64 temp;
681 if (!base::StringToInt64(int_string, &temp)) {
682 LOG(ERROR) << "Hit database corruption!";
683 return false;
685 ++temp;
686 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
687 base::Int64ToString(temp));
688 if (!status.ok()) {
689 HandleError(FROM_HERE, status);
690 return false;
692 *next = temp;
693 return true;
695 if (!status.IsNotFound()) {
696 HandleError(FROM_HERE, status);
697 return false;
699 // The database must not yet exist; initialize it.
700 if (!StoreDefaultValues())
701 return false;
703 return GetNextInteger(next);
706 bool SandboxDirectoryDatabase::DestroyDatabase() {
707 db_.reset();
708 const std::string path =
709 FilePathToString(filesystem_data_directory_.Append(
710 kDirectoryDatabaseName));
711 leveldb::Options options;
712 if (env_override_)
713 options.env = env_override_;
714 leveldb::Status status = leveldb::DestroyDB(path, options);
715 if (status.ok())
716 return true;
717 LOG(WARNING) << "Failed to destroy a database with status " <<
718 status.ToString();
719 return false;
722 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
723 if (db_)
724 return true;
726 std::string path =
727 FilePathToString(filesystem_data_directory_.Append(
728 kDirectoryDatabaseName));
729 leveldb::Options options;
730 options.max_open_files = 0; // Use minimum.
731 options.create_if_missing = true;
732 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
733 if (env_override_)
734 options.env = env_override_;
735 leveldb::DB* db;
736 leveldb::Status status = leveldb::DB::Open(options, path, &db);
737 ReportInitStatus(status);
738 if (status.ok()) {
739 db_.reset(db);
740 return true;
742 HandleError(FROM_HERE, status);
744 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
745 // of Corruption error.
746 // Try to repair database even when IOError case.
747 if (!status.IsCorruption() && !status.IsIOError())
748 return false;
750 switch (recovery_option) {
751 case FAIL_ON_CORRUPTION:
752 return false;
753 case REPAIR_ON_CORRUPTION:
754 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
755 << " Attempting to repair.";
756 if (RepairDatabase(path)) {
757 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
758 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
759 return true;
761 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
762 DB_REPAIR_FAILED, DB_REPAIR_MAX);
763 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
764 // fall through
765 case DELETE_ON_CORRUPTION:
766 LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
767 if (!base::DeleteFile(filesystem_data_directory_, true))
768 return false;
769 if (!base::CreateDirectory(filesystem_data_directory_))
770 return false;
771 return Init(FAIL_ON_CORRUPTION);
774 NOTREACHED();
775 return false;
778 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
779 DCHECK(!db_.get());
780 leveldb::Options options;
781 options.max_open_files = 0; // Use minimum.
782 if (env_override_)
783 options.env = env_override_;
784 if (!leveldb::RepairDB(db_path, options).ok())
785 return false;
786 if (!Init(FAIL_ON_CORRUPTION))
787 return false;
788 if (IsFileSystemConsistent())
789 return true;
790 db_.reset();
791 return false;
794 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
795 FileInfo info;
796 if (!file_id)
797 return true; // The root is a directory.
798 if (!GetFileInfo(file_id, &info))
799 return false;
800 if (!info.is_directory())
801 return false;
802 return true;
805 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
806 if (!Init(FAIL_ON_CORRUPTION))
807 return false;
808 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
809 return helper.IsFileSystemConsistent();
812 void SandboxDirectoryDatabase::ReportInitStatus(
813 const leveldb::Status& status) {
814 base::Time now = base::Time::Now();
815 const base::TimeDelta minimum_interval =
816 base::TimeDelta::FromHours(kMinimumReportIntervalHours);
817 if (last_reported_time_ + minimum_interval >= now)
818 return;
819 last_reported_time_ = now;
821 if (status.ok()) {
822 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
823 INIT_STATUS_OK, INIT_STATUS_MAX);
824 } else if (status.IsCorruption()) {
825 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
826 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
827 } else if (status.IsIOError()) {
828 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
829 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
830 } else {
831 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
832 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
836 bool SandboxDirectoryDatabase::StoreDefaultValues() {
837 // Verify that this is a totally new database, and initialize it.
838 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
839 iter->SeekToFirst();
840 if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
841 LOG(ERROR) << "File system origin database is corrupt!";
842 return false;
844 // This is always the first write into the database. If we ever add a
845 // version number, it should go in this transaction too.
846 FileInfo root;
847 root.parent_id = 0;
848 root.modification_time = base::Time::Now();
849 leveldb::WriteBatch batch;
850 if (!AddFileInfoHelper(root, 0, &batch))
851 return false;
852 batch.Put(LastFileIdKey(), base::Int64ToString(0));
853 batch.Put(LastIntegerKey(), base::Int64ToString(-1));
854 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
855 if (!status.ok()) {
856 HandleError(FROM_HERE, status);
857 return false;
859 return true;
862 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
863 if (!Init(REPAIR_ON_CORRUPTION))
864 return false;
865 DCHECK(file_id);
866 std::string id_string;
867 leveldb::Status status =
868 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
869 if (status.ok()) {
870 if (!base::StringToInt64(id_string, file_id)) {
871 LOG(ERROR) << "Hit database corruption!";
872 return false;
874 return true;
876 if (!status.IsNotFound()) {
877 HandleError(FROM_HERE, status);
878 return false;
880 // The database must not yet exist; initialize it.
881 if (!StoreDefaultValues())
882 return false;
883 *file_id = 0;
884 return true;
887 // This does very few safety checks!
888 bool SandboxDirectoryDatabase::AddFileInfoHelper(
889 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
890 if (!VerifyDataPath(info.data_path)) {
891 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
892 return false;
894 std::string id_string = GetFileLookupKey(file_id);
895 if (!file_id) {
896 // The root directory doesn't need to be looked up by path from its parent.
897 DCHECK(!info.parent_id);
898 DCHECK(info.data_path.empty());
899 } else {
900 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
901 batch->Put(child_key, id_string);
903 base::Pickle pickle;
904 if (!PickleFromFileInfo(info, &pickle))
905 return false;
906 batch->Put(
907 id_string,
908 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
909 pickle.size()));
910 return true;
913 // This does very few safety checks!
914 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
915 FileId file_id, leveldb::WriteBatch* batch) {
916 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
917 FileInfo info;
918 if (!GetFileInfo(file_id, &info))
919 return false;
920 if (info.data_path.empty()) { // It's a directory
921 std::vector<FileId> children;
922 // TODO(ericu): Make a faster is-the-directory-empty check.
923 if (!ListChildren(file_id, &children))
924 return false;
925 if (children.size()) {
926 LOG(ERROR) << "Can't remove a directory with children.";
927 return false;
930 batch->Delete(GetChildLookupKey(info.parent_id, info.name));
931 batch->Delete(GetFileLookupKey(file_id));
932 return true;
935 void SandboxDirectoryDatabase::HandleError(
936 const tracked_objects::Location& from_here,
937 const leveldb::Status& status) {
938 LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
939 << from_here.ToString() << " with error: " << status.ToString();
940 db_.reset();
943 } // namespace storage