net cleanup: Remove unnecessary namespace prefixes.
[chromium-blink-merge.git] / storage / browser / fileapi / sandbox_directory_database.cc
blob976a3fa288be546a21203e0f1cfad997704d636e
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 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 Pickle& pickle,
50 storage::SandboxDirectoryDatabase::FileInfo* info) {
51 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) << "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 Pickle(itr->value().data(), itr->value().size()), &file_info))
228 return false;
230 FileId file_id = -1;
231 if (!base::StringToInt64(key, &file_id) || file_id < 0)
232 return false;
234 if (max_file_id < file_id)
235 max_file_id = file_id;
236 if (!file_ids.insert(file_id).second)
237 return false;
239 if (file_info.is_directory()) {
240 ++num_directories_in_db_;
241 DCHECK(file_info.data_path.empty());
242 } else {
243 // Ensure any pair of file entry don't share their data_path.
244 if (!files_in_db_.insert(file_info.data_path).second)
245 return false;
247 // Ensure the backing file exists as a normal file.
248 base::File::Info platform_file_info;
249 if (!base::GetFileInfo(
250 path_.Append(file_info.data_path), &platform_file_info) ||
251 platform_file_info.is_directory ||
252 platform_file_info.is_symbolic_link) {
253 // leveldb::Iterator iterates a snapshot of the database.
254 // So even after RemoveFileInfo() call, we'll visit hierarchy link
255 // from |parent_id| to |file_id|.
256 if (!dir_db_->RemoveFileInfo(file_id))
257 return false;
258 --num_hierarchy_links_in_db_;
259 files_in_db_.erase(file_info.data_path);
260 } else {
261 ++num_files_in_db_;
267 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
268 // data path confliction on ObfuscatedFileUtil.
269 return max_file_id <= last_file_id_;
272 bool DatabaseCheckHelper::ScanDirectory() {
273 // TODO(kinuko): Scans all local file system entries to verify each of them
274 // has a database entry.
275 const base::FilePath kExcludes[] = {
276 base::FilePath(kDirectoryDatabaseName),
277 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
280 // Any path in |pending_directories| is relative to |path_|.
281 std::stack<base::FilePath> pending_directories;
282 pending_directories.push(base::FilePath());
284 while (!pending_directories.empty()) {
285 base::FilePath dir_path = pending_directories.top();
286 pending_directories.pop();
288 base::FileEnumerator file_enum(
289 dir_path.empty() ? path_ : path_.Append(dir_path),
290 false /* not recursive */,
291 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
293 base::FilePath absolute_file_path;
294 while (!(absolute_file_path = file_enum.Next()).empty()) {
295 base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
297 base::FilePath relative_file_path;
298 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
299 return false;
301 if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
302 relative_file_path) != kExcludes + arraysize(kExcludes))
303 continue;
305 if (find_info.IsDirectory()) {
306 pending_directories.push(relative_file_path);
307 continue;
310 // Check if the file has a database entry.
311 std::set<base::FilePath>::iterator itr =
312 files_in_db_.find(relative_file_path);
313 if (itr == files_in_db_.end()) {
314 if (!base::DeleteFile(absolute_file_path, false))
315 return false;
316 } else {
317 files_in_db_.erase(itr);
322 return files_in_db_.empty();
325 bool DatabaseCheckHelper::ScanHierarchy() {
326 size_t visited_directories = 0;
327 size_t visited_files = 0;
328 size_t visited_links = 0;
330 std::stack<FileId> directories;
331 directories.push(0);
333 // Check if the root directory exists as a directory.
334 FileInfo file_info;
335 if (!dir_db_->GetFileInfo(0, &file_info))
336 return false;
337 if (file_info.parent_id != 0 ||
338 !file_info.is_directory())
339 return false;
341 while (!directories.empty()) {
342 ++visited_directories;
343 FileId dir_id = directories.top();
344 directories.pop();
346 std::vector<FileId> children;
347 if (!dir_db_->ListChildren(dir_id, &children))
348 return false;
349 for (std::vector<FileId>::iterator itr = children.begin();
350 itr != children.end();
351 ++itr) {
352 // Any directory must not have root directory as child.
353 if (!*itr)
354 return false;
356 // Check if the child knows the parent as its parent.
357 FileInfo file_info;
358 if (!dir_db_->GetFileInfo(*itr, &file_info))
359 return false;
360 if (file_info.parent_id != dir_id)
361 return false;
363 // Check if the parent knows the name of its child correctly.
364 FileId file_id;
365 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
366 file_id != *itr)
367 return false;
369 if (file_info.is_directory())
370 directories.push(*itr);
371 else
372 ++visited_files;
373 ++visited_links;
377 // Check if we've visited all database entries.
378 return num_directories_in_db_ == visited_directories &&
379 num_files_in_db_ == visited_files &&
380 num_hierarchy_links_in_db_ == visited_links;
383 // Returns true if the given |data_path| contains no parent references ("..")
384 // and does not refer to special system files.
385 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
386 // ensure we're only dealing with valid data paths.
387 bool VerifyDataPath(const base::FilePath& data_path) {
388 // |data_path| should not contain any ".." and should be a relative path
389 // (to the filesystem_data_directory_).
390 if (data_path.ReferencesParent() || data_path.IsAbsolute())
391 return false;
392 // See if it's not pointing to the special system paths.
393 const base::FilePath kExcludes[] = {
394 base::FilePath(kDirectoryDatabaseName),
395 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
397 for (size_t i = 0; i < arraysize(kExcludes); ++i) {
398 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
399 return false;
401 return true;
404 } // namespace
406 namespace storage {
408 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
411 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
414 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
415 const base::FilePath& filesystem_data_directory,
416 leveldb::Env* env_override)
417 : filesystem_data_directory_(filesystem_data_directory),
418 env_override_(env_override) {
421 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
424 bool SandboxDirectoryDatabase::GetChildWithName(
425 FileId parent_id,
426 const base::FilePath::StringType& name,
427 FileId* child_id) {
428 if (!Init(REPAIR_ON_CORRUPTION))
429 return false;
430 DCHECK(child_id);
431 std::string child_key = GetChildLookupKey(parent_id, name);
432 std::string child_id_string;
433 leveldb::Status status =
434 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
435 if (status.IsNotFound())
436 return false;
437 if (status.ok()) {
438 if (!base::StringToInt64(child_id_string, child_id)) {
439 LOG(ERROR) << "Hit database corruption!";
440 return false;
442 return true;
444 HandleError(FROM_HERE, status);
445 return false;
448 bool SandboxDirectoryDatabase::GetFileWithPath(
449 const base::FilePath& path, FileId* file_id) {
450 std::vector<base::FilePath::StringType> components;
451 VirtualPath::GetComponents(path, &components);
452 FileId local_id = 0;
453 std::vector<base::FilePath::StringType>::iterator iter;
454 for (iter = components.begin(); iter != components.end(); ++iter) {
455 base::FilePath::StringType name;
456 name = *iter;
457 if (name == FILE_PATH_LITERAL("/"))
458 continue;
459 if (!GetChildWithName(local_id, name, &local_id))
460 return false;
462 *file_id = local_id;
463 return true;
466 bool SandboxDirectoryDatabase::ListChildren(
467 FileId parent_id, std::vector<FileId>* children) {
468 // Check to add later: fail if parent is a file, at least in debug builds.
469 if (!Init(REPAIR_ON_CORRUPTION))
470 return false;
471 DCHECK(children);
472 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
474 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
475 iter->Seek(child_key_prefix);
476 children->clear();
477 while (iter->Valid() &&
478 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
479 std::string child_id_string = iter->value().ToString();
480 FileId child_id;
481 if (!base::StringToInt64(child_id_string, &child_id)) {
482 LOG(ERROR) << "Hit database corruption!";
483 return false;
485 children->push_back(child_id);
486 iter->Next();
488 return true;
491 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
492 if (!Init(REPAIR_ON_CORRUPTION))
493 return false;
494 DCHECK(info);
495 std::string file_key = GetFileLookupKey(file_id);
496 std::string file_data_string;
497 leveldb::Status status =
498 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
499 if (status.ok()) {
500 bool success = FileInfoFromPickle(
501 Pickle(file_data_string.data(), file_data_string.length()), info);
502 if (!success)
503 return false;
504 if (!VerifyDataPath(info->data_path)) {
505 LOG(ERROR) << "Resolved data path is invalid: "
506 << info->data_path.value();
507 return false;
509 return true;
511 // Special-case the root, for databases that haven't been initialized yet.
512 // Without this, a query for the root's file info, made before creating the
513 // first file in the database, will fail and confuse callers.
514 if (status.IsNotFound() && !file_id) {
515 info->name = base::FilePath::StringType();
516 info->data_path = base::FilePath();
517 info->modification_time = base::Time::Now();
518 info->parent_id = 0;
519 return true;
521 HandleError(FROM_HERE, status);
522 return false;
525 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
526 const FileInfo& info, FileId* file_id) {
527 if (!Init(REPAIR_ON_CORRUPTION))
528 return base::File::FILE_ERROR_FAILED;
529 DCHECK(file_id);
530 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
531 std::string child_id_string;
532 leveldb::Status status =
533 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
534 if (status.ok()) {
535 LOG(ERROR) << "File exists already!";
536 return base::File::FILE_ERROR_EXISTS;
538 if (!status.IsNotFound()) {
539 HandleError(FROM_HERE, status);
540 return base::File::FILE_ERROR_NOT_FOUND;
543 if (!IsDirectory(info.parent_id)) {
544 LOG(ERROR) << "New parent directory is a file!";
545 return base::File::FILE_ERROR_NOT_A_DIRECTORY;
548 // This would be a fine place to limit the number of files in a directory, if
549 // we decide to add that restriction.
551 FileId temp_id;
552 if (!GetLastFileId(&temp_id))
553 return base::File::FILE_ERROR_FAILED;
554 ++temp_id;
556 leveldb::WriteBatch batch;
557 if (!AddFileInfoHelper(info, temp_id, &batch))
558 return base::File::FILE_ERROR_FAILED;
560 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
561 status = db_->Write(leveldb::WriteOptions(), &batch);
562 if (!status.ok()) {
563 HandleError(FROM_HERE, status);
564 return base::File::FILE_ERROR_FAILED;
566 *file_id = temp_id;
567 return base::File::FILE_OK;
570 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
571 if (!Init(REPAIR_ON_CORRUPTION))
572 return false;
573 leveldb::WriteBatch batch;
574 if (!RemoveFileInfoHelper(file_id, &batch))
575 return false;
576 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
577 if (!status.ok()) {
578 HandleError(FROM_HERE, status);
579 return false;
581 return true;
584 bool SandboxDirectoryDatabase::UpdateFileInfo(
585 FileId file_id, const FileInfo& new_info) {
586 // TODO(ericu): We should also check to see that this doesn't create a loop,
587 // but perhaps only in a debug build.
588 if (!Init(REPAIR_ON_CORRUPTION))
589 return false;
590 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
591 FileInfo old_info;
592 if (!GetFileInfo(file_id, &old_info))
593 return false;
594 if (old_info.parent_id != new_info.parent_id &&
595 !IsDirectory(new_info.parent_id))
596 return false;
597 if (old_info.parent_id != new_info.parent_id ||
598 old_info.name != new_info.name) {
599 // Check for name clashes.
600 FileId temp_id;
601 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
602 LOG(ERROR) << "Name collision on move.";
603 return false;
606 leveldb::WriteBatch batch;
607 if (!RemoveFileInfoHelper(file_id, &batch) ||
608 !AddFileInfoHelper(new_info, file_id, &batch))
609 return false;
610 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
611 if (!status.ok()) {
612 HandleError(FROM_HERE, status);
613 return false;
615 return true;
618 bool SandboxDirectoryDatabase::UpdateModificationTime(
619 FileId file_id, const base::Time& modification_time) {
620 FileInfo info;
621 if (!GetFileInfo(file_id, &info))
622 return false;
623 info.modification_time = modification_time;
624 Pickle pickle;
625 if (!PickleFromFileInfo(info, &pickle))
626 return false;
627 leveldb::Status status = db_->Put(
628 leveldb::WriteOptions(),
629 GetFileLookupKey(file_id),
630 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
631 pickle.size()));
632 if (!status.ok()) {
633 HandleError(FROM_HERE, status);
634 return false;
636 return true;
639 bool SandboxDirectoryDatabase::OverwritingMoveFile(
640 FileId src_file_id, FileId dest_file_id) {
641 FileInfo src_file_info;
642 FileInfo dest_file_info;
644 if (!GetFileInfo(src_file_id, &src_file_info))
645 return false;
646 if (!GetFileInfo(dest_file_id, &dest_file_info))
647 return false;
648 if (src_file_info.is_directory() || dest_file_info.is_directory())
649 return false;
650 leveldb::WriteBatch batch;
651 // This is the only field that really gets moved over; if you add fields to
652 // FileInfo, e.g. ctime, they might need to be copied here.
653 dest_file_info.data_path = src_file_info.data_path;
654 if (!RemoveFileInfoHelper(src_file_id, &batch))
655 return false;
656 Pickle pickle;
657 if (!PickleFromFileInfo(dest_file_info, &pickle))
658 return false;
659 batch.Put(
660 GetFileLookupKey(dest_file_id),
661 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
662 pickle.size()));
663 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
664 if (!status.ok()) {
665 HandleError(FROM_HERE, status);
666 return false;
668 return true;
671 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
672 if (!Init(REPAIR_ON_CORRUPTION))
673 return false;
674 DCHECK(next);
675 std::string int_string;
676 leveldb::Status status =
677 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
678 if (status.ok()) {
679 int64 temp;
680 if (!base::StringToInt64(int_string, &temp)) {
681 LOG(ERROR) << "Hit database corruption!";
682 return false;
684 ++temp;
685 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
686 base::Int64ToString(temp));
687 if (!status.ok()) {
688 HandleError(FROM_HERE, status);
689 return false;
691 *next = temp;
692 return true;
694 if (!status.IsNotFound()) {
695 HandleError(FROM_HERE, status);
696 return false;
698 // The database must not yet exist; initialize it.
699 if (!StoreDefaultValues())
700 return false;
702 return GetNextInteger(next);
705 bool SandboxDirectoryDatabase::DestroyDatabase() {
706 db_.reset();
707 const std::string path =
708 FilePathToString(filesystem_data_directory_.Append(
709 kDirectoryDatabaseName));
710 leveldb::Options options;
711 if (env_override_)
712 options.env = env_override_;
713 leveldb::Status status = leveldb::DestroyDB(path, options);
714 if (status.ok())
715 return true;
716 LOG(WARNING) << "Failed to destroy a database with status " <<
717 status.ToString();
718 return false;
721 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
722 if (db_)
723 return true;
725 std::string path =
726 FilePathToString(filesystem_data_directory_.Append(
727 kDirectoryDatabaseName));
728 leveldb::Options options;
729 options.max_open_files = 0; // Use minimum.
730 options.create_if_missing = true;
731 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
732 if (env_override_)
733 options.env = env_override_;
734 leveldb::DB* db;
735 leveldb::Status status = leveldb::DB::Open(options, path, &db);
736 ReportInitStatus(status);
737 if (status.ok()) {
738 db_.reset(db);
739 return true;
741 HandleError(FROM_HERE, status);
743 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
744 // of Corruption error.
745 // Try to repair database even when IOError case.
746 if (!status.IsCorruption() && !status.IsIOError())
747 return false;
749 switch (recovery_option) {
750 case FAIL_ON_CORRUPTION:
751 return false;
752 case REPAIR_ON_CORRUPTION:
753 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
754 << " Attempting to repair.";
755 if (RepairDatabase(path)) {
756 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
757 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
758 return true;
760 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
761 DB_REPAIR_FAILED, DB_REPAIR_MAX);
762 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
763 // fall through
764 case DELETE_ON_CORRUPTION:
765 LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
766 if (!base::DeleteFile(filesystem_data_directory_, true))
767 return false;
768 if (!base::CreateDirectory(filesystem_data_directory_))
769 return false;
770 return Init(FAIL_ON_CORRUPTION);
773 NOTREACHED();
774 return false;
777 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
778 DCHECK(!db_.get());
779 leveldb::Options options;
780 options.max_open_files = 0; // Use minimum.
781 if (env_override_)
782 options.env = env_override_;
783 if (!leveldb::RepairDB(db_path, options).ok())
784 return false;
785 if (!Init(FAIL_ON_CORRUPTION))
786 return false;
787 if (IsFileSystemConsistent())
788 return true;
789 db_.reset();
790 return false;
793 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
794 FileInfo info;
795 if (!file_id)
796 return true; // The root is a directory.
797 if (!GetFileInfo(file_id, &info))
798 return false;
799 if (!info.is_directory())
800 return false;
801 return true;
804 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
805 if (!Init(FAIL_ON_CORRUPTION))
806 return false;
807 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
808 return helper.IsFileSystemConsistent();
811 void SandboxDirectoryDatabase::ReportInitStatus(
812 const leveldb::Status& status) {
813 base::Time now = base::Time::Now();
814 const base::TimeDelta minimum_interval =
815 base::TimeDelta::FromHours(kMinimumReportIntervalHours);
816 if (last_reported_time_ + minimum_interval >= now)
817 return;
818 last_reported_time_ = now;
820 if (status.ok()) {
821 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
822 INIT_STATUS_OK, INIT_STATUS_MAX);
823 } else if (status.IsCorruption()) {
824 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
825 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
826 } else if (status.IsIOError()) {
827 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
828 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
829 } else {
830 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
831 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
835 bool SandboxDirectoryDatabase::StoreDefaultValues() {
836 // Verify that this is a totally new database, and initialize it.
837 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
838 iter->SeekToFirst();
839 if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
840 LOG(ERROR) << "File system origin database is corrupt!";
841 return false;
843 // This is always the first write into the database. If we ever add a
844 // version number, it should go in this transaction too.
845 FileInfo root;
846 root.parent_id = 0;
847 root.modification_time = base::Time::Now();
848 leveldb::WriteBatch batch;
849 if (!AddFileInfoHelper(root, 0, &batch))
850 return false;
851 batch.Put(LastFileIdKey(), base::Int64ToString(0));
852 batch.Put(LastIntegerKey(), base::Int64ToString(-1));
853 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
854 if (!status.ok()) {
855 HandleError(FROM_HERE, status);
856 return false;
858 return true;
861 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
862 if (!Init(REPAIR_ON_CORRUPTION))
863 return false;
864 DCHECK(file_id);
865 std::string id_string;
866 leveldb::Status status =
867 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
868 if (status.ok()) {
869 if (!base::StringToInt64(id_string, file_id)) {
870 LOG(ERROR) << "Hit database corruption!";
871 return false;
873 return true;
875 if (!status.IsNotFound()) {
876 HandleError(FROM_HERE, status);
877 return false;
879 // The database must not yet exist; initialize it.
880 if (!StoreDefaultValues())
881 return false;
882 *file_id = 0;
883 return true;
886 // This does very few safety checks!
887 bool SandboxDirectoryDatabase::AddFileInfoHelper(
888 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
889 if (!VerifyDataPath(info.data_path)) {
890 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
891 return false;
893 std::string id_string = GetFileLookupKey(file_id);
894 if (!file_id) {
895 // The root directory doesn't need to be looked up by path from its parent.
896 DCHECK(!info.parent_id);
897 DCHECK(info.data_path.empty());
898 } else {
899 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
900 batch->Put(child_key, id_string);
902 Pickle pickle;
903 if (!PickleFromFileInfo(info, &pickle))
904 return false;
905 batch->Put(
906 id_string,
907 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
908 pickle.size()));
909 return true;
912 // This does very few safety checks!
913 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
914 FileId file_id, leveldb::WriteBatch* batch) {
915 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
916 FileInfo info;
917 if (!GetFileInfo(file_id, &info))
918 return false;
919 if (info.data_path.empty()) { // It's a directory
920 std::vector<FileId> children;
921 // TODO(ericu): Make a faster is-the-directory-empty check.
922 if (!ListChildren(file_id, &children))
923 return false;
924 if (children.size()) {
925 LOG(ERROR) << "Can't remove a directory with children.";
926 return false;
929 batch->Delete(GetChildLookupKey(info.parent_id, info.name));
930 batch->Delete(GetFileLookupKey(file_id));
931 return true;
934 void SandboxDirectoryDatabase::HandleError(
935 const tracked_objects::Location& from_here,
936 const leveldb::Status& status) {
937 LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
938 << from_here.ToString() << " with error: " << status.ToString();
939 db_.reset();
942 } // namespace storage