Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / storage / browser / fileapi / sandbox_directory_database.cc
blob39f1d5386a0272429ffba0f09d2b559d1a19c119
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 // These values are recorded in UMA. Changing existing values will invalidate
81 // results for older Chrome releases. Only add new values.
82 enum InitStatus {
83 INIT_STATUS_OK = 0,
84 INIT_STATUS_CORRUPTION,
85 INIT_STATUS_IO_ERROR,
86 INIT_STATUS_UNKNOWN_ERROR,
87 INIT_STATUS_MAX
90 // These values are recorded in UMA. Changing existing values will invalidate
91 // results for older Chrome releases. Only add new values.
92 enum RepairResult {
93 DB_REPAIR_SUCCEEDED = 0,
94 DB_REPAIR_FAILED,
95 DB_REPAIR_MAX
98 std::string GetChildLookupKey(
99 storage::SandboxDirectoryDatabase::FileId parent_id,
100 const base::FilePath::StringType& child_name) {
101 std::string name;
102 name = storage::FilePathToString(base::FilePath(child_name));
103 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
104 std::string(kChildLookupSeparator) + name;
107 std::string GetChildListingKeyPrefix(
108 storage::SandboxDirectoryDatabase::FileId parent_id) {
109 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
110 std::string(kChildLookupSeparator);
113 const char* LastFileIdKey() {
114 return kLastFileIdKey;
117 const char* LastIntegerKey() {
118 return kLastIntegerKey;
121 std::string GetFileLookupKey(
122 storage::SandboxDirectoryDatabase::FileId file_id) {
123 return base::Int64ToString(file_id);
126 // Assumptions:
127 // - Any database entry is one of:
128 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
129 // - ("LAST_FILE_ID", "|last_file_id|"),
130 // - ("LAST_INTEGER", "|last_integer|"),
131 // - ("|file_id|", "pickled FileInfo")
132 // where FileInfo has |parent_id|, |data_path|, |name| and
133 // |modification_time|,
134 // Constraints:
135 // - Each file in the database has unique backing file.
136 // - Each file in |filesystem_data_directory_| has a database entry.
137 // - Directory structure is tree, i.e. connected and acyclic.
138 class DatabaseCheckHelper {
139 public:
140 typedef storage::SandboxDirectoryDatabase::FileId FileId;
141 typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
143 DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
144 leveldb::DB* db,
145 const base::FilePath& path);
147 bool IsFileSystemConsistent() {
148 return IsDatabaseEmpty() ||
149 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
152 private:
153 bool IsDatabaseEmpty();
154 // These 3 methods need to be called in the order. Each method requires its
155 // previous method finished successfully. They also require the database is
156 // not empty.
157 bool ScanDatabase();
158 bool ScanDirectory();
159 bool ScanHierarchy();
161 storage::SandboxDirectoryDatabase* dir_db_;
162 leveldb::DB* db_;
163 base::FilePath path_;
165 std::set<base::FilePath> files_in_db_;
167 size_t num_directories_in_db_;
168 size_t num_files_in_db_;
169 size_t num_hierarchy_links_in_db_;
171 FileId last_file_id_;
172 FileId last_integer_;
175 DatabaseCheckHelper::DatabaseCheckHelper(
176 storage::SandboxDirectoryDatabase* dir_db,
177 leveldb::DB* db,
178 const base::FilePath& path)
179 : dir_db_(dir_db),
180 db_(db),
181 path_(path),
182 num_directories_in_db_(0),
183 num_files_in_db_(0),
184 num_hierarchy_links_in_db_(0),
185 last_file_id_(-1),
186 last_integer_(-1) {
187 DCHECK(dir_db_);
188 DCHECK(db_);
189 DCHECK(!path_.empty() && base::DirectoryExists(path_));
192 bool DatabaseCheckHelper::IsDatabaseEmpty() {
193 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
194 itr->SeekToFirst();
195 return !itr->Valid();
198 bool DatabaseCheckHelper::ScanDatabase() {
199 // Scans all database entries sequentially to verify each of them has unique
200 // backing file.
201 int64 max_file_id = -1;
202 std::set<FileId> file_ids;
204 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
205 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
206 std::string key = itr->key().ToString();
207 if (base::StartsWith(key, kChildLookupPrefix,
208 base::CompareCase::SENSITIVE)) {
209 // key: "CHILD_OF:<parent_id>:<name>"
210 // value: "<child_id>"
211 ++num_hierarchy_links_in_db_;
212 } else if (key == kLastFileIdKey) {
213 // key: "LAST_FILE_ID"
214 // value: "<last_file_id>"
215 if (last_file_id_ >= 0 ||
216 !base::StringToInt64(itr->value().ToString(), &last_file_id_))
217 return false;
219 if (last_file_id_ < 0)
220 return false;
221 } else if (key == kLastIntegerKey) {
222 // key: "LAST_INTEGER"
223 // value: "<last_integer>"
224 if (last_integer_ >= 0 ||
225 !base::StringToInt64(itr->value().ToString(), &last_integer_))
226 return false;
227 } else {
228 // key: "<entry_id>"
229 // value: "<pickled FileInfo>"
230 FileInfo file_info;
231 if (!FileInfoFromPickle(
232 base::Pickle(itr->value().data(), itr->value().size()),
233 &file_info))
234 return false;
236 FileId file_id = -1;
237 if (!base::StringToInt64(key, &file_id) || file_id < 0)
238 return false;
240 if (max_file_id < file_id)
241 max_file_id = file_id;
242 if (!file_ids.insert(file_id).second)
243 return false;
245 if (file_info.is_directory()) {
246 ++num_directories_in_db_;
247 DCHECK(file_info.data_path.empty());
248 } else {
249 // Ensure any pair of file entry don't share their data_path.
250 if (!files_in_db_.insert(file_info.data_path).second)
251 return false;
253 // Ensure the backing file exists as a normal file.
254 base::File::Info platform_file_info;
255 if (!base::GetFileInfo(
256 path_.Append(file_info.data_path), &platform_file_info) ||
257 platform_file_info.is_directory ||
258 platform_file_info.is_symbolic_link) {
259 // leveldb::Iterator iterates a snapshot of the database.
260 // So even after RemoveFileInfo() call, we'll visit hierarchy link
261 // from |parent_id| to |file_id|.
262 if (!dir_db_->RemoveFileInfo(file_id))
263 return false;
264 --num_hierarchy_links_in_db_;
265 files_in_db_.erase(file_info.data_path);
266 } else {
267 ++num_files_in_db_;
273 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
274 // data path confliction on ObfuscatedFileUtil.
275 return max_file_id <= last_file_id_;
278 bool DatabaseCheckHelper::ScanDirectory() {
279 // TODO(kinuko): Scans all local file system entries to verify each of them
280 // has a database entry.
281 const base::FilePath kExcludes[] = {
282 base::FilePath(kDirectoryDatabaseName),
283 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
286 // Any path in |pending_directories| is relative to |path_|.
287 std::stack<base::FilePath> pending_directories;
288 pending_directories.push(base::FilePath());
290 while (!pending_directories.empty()) {
291 base::FilePath dir_path = pending_directories.top();
292 pending_directories.pop();
294 base::FileEnumerator file_enum(
295 dir_path.empty() ? path_ : path_.Append(dir_path),
296 false /* not recursive */,
297 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
299 base::FilePath absolute_file_path;
300 while (!(absolute_file_path = file_enum.Next()).empty()) {
301 base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
303 base::FilePath relative_file_path;
304 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
305 return false;
307 if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
308 relative_file_path) != kExcludes + arraysize(kExcludes))
309 continue;
311 if (find_info.IsDirectory()) {
312 pending_directories.push(relative_file_path);
313 continue;
316 // Check if the file has a database entry.
317 std::set<base::FilePath>::iterator itr =
318 files_in_db_.find(relative_file_path);
319 if (itr == files_in_db_.end()) {
320 if (!base::DeleteFile(absolute_file_path, false))
321 return false;
322 } else {
323 files_in_db_.erase(itr);
328 return files_in_db_.empty();
331 bool DatabaseCheckHelper::ScanHierarchy() {
332 size_t visited_directories = 0;
333 size_t visited_files = 0;
334 size_t visited_links = 0;
336 std::stack<FileId> directories;
337 directories.push(0);
339 // Check if the root directory exists as a directory.
340 FileInfo file_info;
341 if (!dir_db_->GetFileInfo(0, &file_info))
342 return false;
343 if (file_info.parent_id != 0 ||
344 !file_info.is_directory())
345 return false;
347 while (!directories.empty()) {
348 ++visited_directories;
349 FileId dir_id = directories.top();
350 directories.pop();
352 std::vector<FileId> children;
353 if (!dir_db_->ListChildren(dir_id, &children))
354 return false;
355 for (std::vector<FileId>::iterator itr = children.begin();
356 itr != children.end();
357 ++itr) {
358 // Any directory must not have root directory as child.
359 if (!*itr)
360 return false;
362 // Check if the child knows the parent as its parent.
363 FileInfo file_info;
364 if (!dir_db_->GetFileInfo(*itr, &file_info))
365 return false;
366 if (file_info.parent_id != dir_id)
367 return false;
369 // Check if the parent knows the name of its child correctly.
370 FileId file_id;
371 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
372 file_id != *itr)
373 return false;
375 if (file_info.is_directory())
376 directories.push(*itr);
377 else
378 ++visited_files;
379 ++visited_links;
383 // Check if we've visited all database entries.
384 return num_directories_in_db_ == visited_directories &&
385 num_files_in_db_ == visited_files &&
386 num_hierarchy_links_in_db_ == visited_links;
389 // Returns true if the given |data_path| contains no parent references ("..")
390 // and does not refer to special system files.
391 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
392 // ensure we're only dealing with valid data paths.
393 bool VerifyDataPath(const base::FilePath& data_path) {
394 // |data_path| should not contain any ".." and should be a relative path
395 // (to the filesystem_data_directory_).
396 if (data_path.ReferencesParent() || data_path.IsAbsolute())
397 return false;
398 // See if it's not pointing to the special system paths.
399 const base::FilePath kExcludes[] = {
400 base::FilePath(kDirectoryDatabaseName),
401 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
403 for (size_t i = 0; i < arraysize(kExcludes); ++i) {
404 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
405 return false;
407 return true;
410 } // namespace
412 namespace storage {
414 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
417 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
420 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
421 const base::FilePath& filesystem_data_directory,
422 leveldb::Env* env_override)
423 : filesystem_data_directory_(filesystem_data_directory),
424 env_override_(env_override) {
427 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
430 bool SandboxDirectoryDatabase::GetChildWithName(
431 FileId parent_id,
432 const base::FilePath::StringType& name,
433 FileId* child_id) {
434 if (!Init(REPAIR_ON_CORRUPTION))
435 return false;
436 DCHECK(child_id);
437 std::string child_key = GetChildLookupKey(parent_id, name);
438 std::string child_id_string;
439 leveldb::Status status =
440 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
441 if (status.IsNotFound())
442 return false;
443 if (status.ok()) {
444 if (!base::StringToInt64(child_id_string, child_id)) {
445 LOG(ERROR) << "Hit database corruption!";
446 return false;
448 return true;
450 HandleError(FROM_HERE, status);
451 return false;
454 bool SandboxDirectoryDatabase::GetFileWithPath(
455 const base::FilePath& path, FileId* file_id) {
456 std::vector<base::FilePath::StringType> components;
457 VirtualPath::GetComponents(path, &components);
458 FileId local_id = 0;
459 std::vector<base::FilePath::StringType>::iterator iter;
460 for (iter = components.begin(); iter != components.end(); ++iter) {
461 base::FilePath::StringType name;
462 name = *iter;
463 if (name == FILE_PATH_LITERAL("/"))
464 continue;
465 if (!GetChildWithName(local_id, name, &local_id))
466 return false;
468 *file_id = local_id;
469 return true;
472 bool SandboxDirectoryDatabase::ListChildren(
473 FileId parent_id, std::vector<FileId>* children) {
474 // Check to add later: fail if parent is a file, at least in debug builds.
475 if (!Init(REPAIR_ON_CORRUPTION))
476 return false;
477 DCHECK(children);
478 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
480 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
481 iter->Seek(child_key_prefix);
482 children->clear();
483 while (iter->Valid() && base::StartsWith(iter->key().ToString(),
484 child_key_prefix,
485 base::CompareCase::SENSITIVE)) {
486 std::string child_id_string = iter->value().ToString();
487 FileId child_id;
488 if (!base::StringToInt64(child_id_string, &child_id)) {
489 LOG(ERROR) << "Hit database corruption!";
490 return false;
492 children->push_back(child_id);
493 iter->Next();
495 return true;
498 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
499 if (!Init(REPAIR_ON_CORRUPTION))
500 return false;
501 DCHECK(info);
502 std::string file_key = GetFileLookupKey(file_id);
503 std::string file_data_string;
504 leveldb::Status status =
505 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
506 if (status.ok()) {
507 bool success = FileInfoFromPickle(
508 base::Pickle(file_data_string.data(), file_data_string.length()), info);
509 if (!success)
510 return false;
511 if (!VerifyDataPath(info->data_path)) {
512 LOG(ERROR) << "Resolved data path is invalid: "
513 << info->data_path.value();
514 return false;
516 return true;
518 // Special-case the root, for databases that haven't been initialized yet.
519 // Without this, a query for the root's file info, made before creating the
520 // first file in the database, will fail and confuse callers.
521 if (status.IsNotFound() && !file_id) {
522 info->name = base::FilePath::StringType();
523 info->data_path = base::FilePath();
524 info->modification_time = base::Time::Now();
525 info->parent_id = 0;
526 return true;
528 HandleError(FROM_HERE, status);
529 return false;
532 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
533 const FileInfo& info, FileId* file_id) {
534 if (!Init(REPAIR_ON_CORRUPTION))
535 return base::File::FILE_ERROR_FAILED;
536 DCHECK(file_id);
537 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
538 std::string child_id_string;
539 leveldb::Status status =
540 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
541 if (status.ok()) {
542 LOG(ERROR) << "File exists already!";
543 return base::File::FILE_ERROR_EXISTS;
545 if (!status.IsNotFound()) {
546 HandleError(FROM_HERE, status);
547 return base::File::FILE_ERROR_NOT_FOUND;
550 if (!IsDirectory(info.parent_id)) {
551 LOG(ERROR) << "New parent directory is a file!";
552 return base::File::FILE_ERROR_NOT_A_DIRECTORY;
555 // This would be a fine place to limit the number of files in a directory, if
556 // we decide to add that restriction.
558 FileId temp_id;
559 if (!GetLastFileId(&temp_id))
560 return base::File::FILE_ERROR_FAILED;
561 ++temp_id;
563 leveldb::WriteBatch batch;
564 if (!AddFileInfoHelper(info, temp_id, &batch))
565 return base::File::FILE_ERROR_FAILED;
567 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
568 status = db_->Write(leveldb::WriteOptions(), &batch);
569 if (!status.ok()) {
570 HandleError(FROM_HERE, status);
571 return base::File::FILE_ERROR_FAILED;
573 *file_id = temp_id;
574 return base::File::FILE_OK;
577 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
578 if (!Init(REPAIR_ON_CORRUPTION))
579 return false;
580 leveldb::WriteBatch batch;
581 if (!RemoveFileInfoHelper(file_id, &batch))
582 return false;
583 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
584 if (!status.ok()) {
585 HandleError(FROM_HERE, status);
586 return false;
588 return true;
591 bool SandboxDirectoryDatabase::UpdateFileInfo(
592 FileId file_id, const FileInfo& new_info) {
593 // TODO(ericu): We should also check to see that this doesn't create a loop,
594 // but perhaps only in a debug build.
595 if (!Init(REPAIR_ON_CORRUPTION))
596 return false;
597 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
598 FileInfo old_info;
599 if (!GetFileInfo(file_id, &old_info))
600 return false;
601 if (old_info.parent_id != new_info.parent_id &&
602 !IsDirectory(new_info.parent_id))
603 return false;
604 if (old_info.parent_id != new_info.parent_id ||
605 old_info.name != new_info.name) {
606 // Check for name clashes.
607 FileId temp_id;
608 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
609 LOG(ERROR) << "Name collision on move.";
610 return false;
613 leveldb::WriteBatch batch;
614 if (!RemoveFileInfoHelper(file_id, &batch) ||
615 !AddFileInfoHelper(new_info, file_id, &batch))
616 return false;
617 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
618 if (!status.ok()) {
619 HandleError(FROM_HERE, status);
620 return false;
622 return true;
625 bool SandboxDirectoryDatabase::UpdateModificationTime(
626 FileId file_id, const base::Time& modification_time) {
627 FileInfo info;
628 if (!GetFileInfo(file_id, &info))
629 return false;
630 info.modification_time = modification_time;
631 base::Pickle pickle;
632 if (!PickleFromFileInfo(info, &pickle))
633 return false;
634 leveldb::Status status = db_->Put(
635 leveldb::WriteOptions(),
636 GetFileLookupKey(file_id),
637 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
638 pickle.size()));
639 if (!status.ok()) {
640 HandleError(FROM_HERE, status);
641 return false;
643 return true;
646 bool SandboxDirectoryDatabase::OverwritingMoveFile(
647 FileId src_file_id, FileId dest_file_id) {
648 FileInfo src_file_info;
649 FileInfo dest_file_info;
651 if (!GetFileInfo(src_file_id, &src_file_info))
652 return false;
653 if (!GetFileInfo(dest_file_id, &dest_file_info))
654 return false;
655 if (src_file_info.is_directory() || dest_file_info.is_directory())
656 return false;
657 leveldb::WriteBatch batch;
658 // This is the only field that really gets moved over; if you add fields to
659 // FileInfo, e.g. ctime, they might need to be copied here.
660 dest_file_info.data_path = src_file_info.data_path;
661 if (!RemoveFileInfoHelper(src_file_id, &batch))
662 return false;
663 base::Pickle pickle;
664 if (!PickleFromFileInfo(dest_file_info, &pickle))
665 return false;
666 batch.Put(
667 GetFileLookupKey(dest_file_id),
668 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
669 pickle.size()));
670 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
671 if (!status.ok()) {
672 HandleError(FROM_HERE, status);
673 return false;
675 return true;
678 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
679 if (!Init(REPAIR_ON_CORRUPTION))
680 return false;
681 DCHECK(next);
682 std::string int_string;
683 leveldb::Status status =
684 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
685 if (status.ok()) {
686 int64 temp;
687 if (!base::StringToInt64(int_string, &temp)) {
688 LOG(ERROR) << "Hit database corruption!";
689 return false;
691 ++temp;
692 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
693 base::Int64ToString(temp));
694 if (!status.ok()) {
695 HandleError(FROM_HERE, status);
696 return false;
698 *next = temp;
699 return true;
701 if (!status.IsNotFound()) {
702 HandleError(FROM_HERE, status);
703 return false;
705 // The database must not yet exist; initialize it.
706 if (!StoreDefaultValues())
707 return false;
709 return GetNextInteger(next);
712 bool SandboxDirectoryDatabase::DestroyDatabase() {
713 db_.reset();
714 const std::string path =
715 FilePathToString(filesystem_data_directory_.Append(
716 kDirectoryDatabaseName));
717 leveldb::Options options;
718 if (env_override_)
719 options.env = env_override_;
720 leveldb::Status status = leveldb::DestroyDB(path, options);
721 if (status.ok())
722 return true;
723 LOG(WARNING) << "Failed to destroy a database with status " <<
724 status.ToString();
725 return false;
728 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
729 if (db_)
730 return true;
732 std::string path =
733 FilePathToString(filesystem_data_directory_.Append(
734 kDirectoryDatabaseName));
735 leveldb::Options options;
736 options.max_open_files = 0; // Use minimum.
737 options.create_if_missing = true;
738 options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
739 if (env_override_)
740 options.env = env_override_;
741 leveldb::DB* db;
742 leveldb::Status status = leveldb::DB::Open(options, path, &db);
743 ReportInitStatus(status);
744 if (status.ok()) {
745 db_.reset(db);
746 return true;
748 HandleError(FROM_HERE, status);
750 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
751 // of Corruption error.
752 // Try to repair database even when IOError case.
753 if (!status.IsCorruption() && !status.IsIOError())
754 return false;
756 switch (recovery_option) {
757 case FAIL_ON_CORRUPTION:
758 return false;
759 case REPAIR_ON_CORRUPTION:
760 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
761 << " Attempting to repair.";
762 if (RepairDatabase(path)) {
763 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
764 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
765 return true;
767 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
768 DB_REPAIR_FAILED, DB_REPAIR_MAX);
769 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
770 // fall through
771 case DELETE_ON_CORRUPTION:
772 LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
773 if (!base::DeleteFile(filesystem_data_directory_, true))
774 return false;
775 if (!base::CreateDirectory(filesystem_data_directory_))
776 return false;
777 return Init(FAIL_ON_CORRUPTION);
780 NOTREACHED();
781 return false;
784 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
785 DCHECK(!db_.get());
786 leveldb::Options options;
787 options.max_open_files = 0; // Use minimum.
788 if (env_override_)
789 options.env = env_override_;
790 if (!leveldb::RepairDB(db_path, options).ok())
791 return false;
792 if (!Init(FAIL_ON_CORRUPTION))
793 return false;
794 if (IsFileSystemConsistent())
795 return true;
796 db_.reset();
797 return false;
800 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
801 FileInfo info;
802 if (!file_id)
803 return true; // The root is a directory.
804 if (!GetFileInfo(file_id, &info))
805 return false;
806 if (!info.is_directory())
807 return false;
808 return true;
811 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
812 if (!Init(FAIL_ON_CORRUPTION))
813 return false;
814 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
815 return helper.IsFileSystemConsistent();
818 void SandboxDirectoryDatabase::ReportInitStatus(
819 const leveldb::Status& status) {
820 base::Time now = base::Time::Now();
821 const base::TimeDelta minimum_interval =
822 base::TimeDelta::FromHours(kMinimumReportIntervalHours);
823 if (last_reported_time_ + minimum_interval >= now)
824 return;
825 last_reported_time_ = now;
827 if (status.ok()) {
828 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
829 INIT_STATUS_OK, INIT_STATUS_MAX);
830 } else if (status.IsCorruption()) {
831 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
832 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
833 } else if (status.IsIOError()) {
834 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
835 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
836 } else {
837 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
838 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
842 bool SandboxDirectoryDatabase::StoreDefaultValues() {
843 // Verify that this is a totally new database, and initialize it.
844 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
845 iter->SeekToFirst();
846 if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
847 LOG(ERROR) << "File system origin database is corrupt!";
848 return false;
850 // This is always the first write into the database. If we ever add a
851 // version number, it should go in this transaction too.
852 FileInfo root;
853 root.parent_id = 0;
854 root.modification_time = base::Time::Now();
855 leveldb::WriteBatch batch;
856 if (!AddFileInfoHelper(root, 0, &batch))
857 return false;
858 batch.Put(LastFileIdKey(), base::Int64ToString(0));
859 batch.Put(LastIntegerKey(), base::Int64ToString(-1));
860 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
861 if (!status.ok()) {
862 HandleError(FROM_HERE, status);
863 return false;
865 return true;
868 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
869 if (!Init(REPAIR_ON_CORRUPTION))
870 return false;
871 DCHECK(file_id);
872 std::string id_string;
873 leveldb::Status status =
874 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
875 if (status.ok()) {
876 if (!base::StringToInt64(id_string, file_id)) {
877 LOG(ERROR) << "Hit database corruption!";
878 return false;
880 return true;
882 if (!status.IsNotFound()) {
883 HandleError(FROM_HERE, status);
884 return false;
886 // The database must not yet exist; initialize it.
887 if (!StoreDefaultValues())
888 return false;
889 *file_id = 0;
890 return true;
893 // This does very few safety checks!
894 bool SandboxDirectoryDatabase::AddFileInfoHelper(
895 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
896 if (!VerifyDataPath(info.data_path)) {
897 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
898 return false;
900 std::string id_string = GetFileLookupKey(file_id);
901 if (!file_id) {
902 // The root directory doesn't need to be looked up by path from its parent.
903 DCHECK(!info.parent_id);
904 DCHECK(info.data_path.empty());
905 } else {
906 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
907 batch->Put(child_key, id_string);
909 base::Pickle pickle;
910 if (!PickleFromFileInfo(info, &pickle))
911 return false;
912 batch->Put(
913 id_string,
914 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
915 pickle.size()));
916 return true;
919 // This does very few safety checks!
920 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
921 FileId file_id, leveldb::WriteBatch* batch) {
922 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
923 FileInfo info;
924 if (!GetFileInfo(file_id, &info))
925 return false;
926 if (info.data_path.empty()) { // It's a directory
927 std::vector<FileId> children;
928 // TODO(ericu): Make a faster is-the-directory-empty check.
929 if (!ListChildren(file_id, &children))
930 return false;
931 if (children.size()) {
932 LOG(ERROR) << "Can't remove a directory with children.";
933 return false;
936 batch->Delete(GetChildLookupKey(info.parent_id, info.name));
937 batch->Delete(GetFileLookupKey(file_id));
938 return true;
941 void SandboxDirectoryDatabase::HandleError(
942 const tracked_objects::Location& from_here,
943 const leveldb::Status& status) {
944 LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
945 << from_here.ToString() << " with error: " << status.ToString();
946 db_.reset();
949 } // namespace storage