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 "webkit/browser/fileapi/sandbox_directory_database.h"
12 #include "base/file_util.h"
13 #include "base/files/file_enumerator.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 "third_party/leveldatabase/src/include/leveldb/db.h"
20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
21 #include "webkit/browser/fileapi/file_system_usage_cache.h"
22 #include "webkit/common/fileapi/file_system_util.h"
26 bool PickleFromFileInfo(
27 const fileapi::SandboxDirectoryDatabase::FileInfo
& info
,
30 std::string data_path
;
31 // Round off here to match the behavior of the filesystem on real files.
33 base::Time::FromDoubleT(floor(info
.modification_time
.ToDoubleT()));
36 data_path
= fileapi::FilePathToString(info
.data_path
);
37 name
= fileapi::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()))
49 bool FileInfoFromPickle(
51 fileapi::SandboxDirectoryDatabase::FileInfo
* info
) {
52 PickleIterator
iter(pickle
);
53 std::string data_path
;
57 if (pickle
.ReadInt64(&iter
, &info
->parent_id
) &&
58 pickle
.ReadString(&iter
, &data_path
) &&
59 pickle
.ReadString(&iter
, &name
) &&
60 pickle
.ReadInt64(&iter
, &internal_time
)) {
61 info
->data_path
= fileapi::StringToFilePath(data_path
);
62 info
->name
= fileapi::StringToFilePath(name
).value();
63 info
->modification_time
= base::Time::FromInternalValue(internal_time
);
66 LOG(ERROR
) << "Pickle could not be digested!";
70 const base::FilePath::CharType kDirectoryDatabaseName
[] =
71 FILE_PATH_LITERAL("Paths");
72 const char kChildLookupPrefix
[] = "CHILD_OF:";
73 const char kChildLookupSeparator
[] = ":";
74 const char kLastFileIdKey
[] = "LAST_FILE_ID";
75 const char kLastIntegerKey
[] = "LAST_INTEGER";
76 const int64 kMinimumReportIntervalHours
= 1;
77 const char kInitStatusHistogramLabel
[] = "FileSystem.DirectoryDatabaseInit";
78 const char kDatabaseRepairHistogramLabel
[] =
79 "FileSystem.DirectoryDatabaseRepair";
83 INIT_STATUS_CORRUPTION
,
85 INIT_STATUS_UNKNOWN_ERROR
,
90 DB_REPAIR_SUCCEEDED
= 0,
95 std::string
GetChildLookupKey(
96 fileapi::SandboxDirectoryDatabase::FileId parent_id
,
97 const base::FilePath::StringType
& child_name
) {
99 name
= fileapi::FilePathToString(base::FilePath(child_name
));
100 return std::string(kChildLookupPrefix
) + base::Int64ToString(parent_id
) +
101 std::string(kChildLookupSeparator
) + name
;
104 std::string
GetChildListingKeyPrefix(
105 fileapi::SandboxDirectoryDatabase::FileId parent_id
) {
106 return std::string(kChildLookupPrefix
) + base::Int64ToString(parent_id
) +
107 std::string(kChildLookupSeparator
);
110 const char* LastFileIdKey() {
111 return kLastFileIdKey
;
114 const char* LastIntegerKey() {
115 return kLastIntegerKey
;
118 std::string
GetFileLookupKey(
119 fileapi::SandboxDirectoryDatabase::FileId file_id
) {
120 return base::Int64ToString(file_id
);
124 // - Any database entry is one of:
125 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
126 // - ("LAST_FILE_ID", "|last_file_id|"),
127 // - ("LAST_INTEGER", "|last_integer|"),
128 // - ("|file_id|", "pickled FileInfo")
129 // where FileInfo has |parent_id|, |data_path|, |name| and
130 // |modification_time|,
132 // - Each file in the database has unique backing file.
133 // - Each file in |filesystem_data_directory_| has a database entry.
134 // - Directory structure is tree, i.e. connected and acyclic.
135 class DatabaseCheckHelper
{
137 typedef fileapi::SandboxDirectoryDatabase::FileId FileId
;
138 typedef fileapi::SandboxDirectoryDatabase::FileInfo FileInfo
;
140 DatabaseCheckHelper(fileapi::SandboxDirectoryDatabase
* dir_db
,
142 const base::FilePath
& path
);
144 bool IsFileSystemConsistent() {
145 return IsDatabaseEmpty() ||
146 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
150 bool IsDatabaseEmpty();
151 // These 3 methods need to be called in the order. Each method requires its
152 // previous method finished successfully. They also require the database is
155 bool ScanDirectory();
156 bool ScanHierarchy();
158 fileapi::SandboxDirectoryDatabase
* dir_db_
;
160 base::FilePath path_
;
162 std::set
<base::FilePath
> files_in_db_
;
164 size_t num_directories_in_db_
;
165 size_t num_files_in_db_
;
166 size_t num_hierarchy_links_in_db_
;
168 FileId last_file_id_
;
169 FileId last_integer_
;
172 DatabaseCheckHelper::DatabaseCheckHelper(
173 fileapi::SandboxDirectoryDatabase
* dir_db
,
175 const base::FilePath
& path
)
176 : dir_db_(dir_db
), db_(db
), path_(path
),
177 num_directories_in_db_(0),
179 num_hierarchy_links_in_db_(0),
180 last_file_id_(-1), last_integer_(-1) {
183 DCHECK(!path_
.empty() && base::DirectoryExists(path_
));
186 bool DatabaseCheckHelper::IsDatabaseEmpty() {
187 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
189 return !itr
->Valid();
192 bool DatabaseCheckHelper::ScanDatabase() {
193 // Scans all database entries sequentially to verify each of them has unique
195 int64 max_file_id
= -1;
196 std::set
<FileId
> file_ids
;
198 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
199 for (itr
->SeekToFirst(); itr
->Valid(); itr
->Next()) {
200 std::string key
= itr
->key().ToString();
201 if (StartsWithASCII(key
, kChildLookupPrefix
, true)) {
202 // key: "CHILD_OF:<parent_id>:<name>"
203 // value: "<child_id>"
204 ++num_hierarchy_links_in_db_
;
205 } else if (key
== kLastFileIdKey
) {
206 // key: "LAST_FILE_ID"
207 // value: "<last_file_id>"
208 if (last_file_id_
>= 0 ||
209 !base::StringToInt64(itr
->value().ToString(), &last_file_id_
))
212 if (last_file_id_
< 0)
214 } else if (key
== kLastIntegerKey
) {
215 // key: "LAST_INTEGER"
216 // value: "<last_integer>"
217 if (last_integer_
>= 0 ||
218 !base::StringToInt64(itr
->value().ToString(), &last_integer_
))
222 // value: "<pickled FileInfo>"
224 if (!FileInfoFromPickle(
225 Pickle(itr
->value().data(), itr
->value().size()), &file_info
))
229 if (!base::StringToInt64(key
, &file_id
) || file_id
< 0)
232 if (max_file_id
< file_id
)
233 max_file_id
= file_id
;
234 if (!file_ids
.insert(file_id
).second
)
237 if (file_info
.is_directory()) {
238 ++num_directories_in_db_
;
239 DCHECK(file_info
.data_path
.empty());
241 // Ensure any pair of file entry don't share their data_path.
242 if (!files_in_db_
.insert(file_info
.data_path
).second
)
245 // Ensure the backing file exists as a normal file.
246 base::PlatformFileInfo platform_file_info
;
247 if (!file_util::GetFileInfo(
248 path_
.Append(file_info
.data_path
), &platform_file_info
) ||
249 platform_file_info
.is_directory
||
250 platform_file_info
.is_symbolic_link
) {
251 // leveldb::Iterator iterates a snapshot of the database.
252 // So even after RemoveFileInfo() call, we'll visit hierarchy link
253 // from |parent_id| to |file_id|.
254 if (!dir_db_
->RemoveFileInfo(file_id
))
256 --num_hierarchy_links_in_db_
;
257 files_in_db_
.erase(file_info
.data_path
);
265 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
266 // data path confliction on ObfuscatedFileUtil.
267 return max_file_id
<= last_file_id_
;
270 bool DatabaseCheckHelper::ScanDirectory() {
271 // TODO(kinuko): Scans all local file system entries to verify each of them
272 // has a database entry.
273 const base::FilePath kExcludes
[] = {
274 base::FilePath(kDirectoryDatabaseName
),
275 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName
),
278 // Any path in |pending_directories| is relative to |path_|.
279 std::stack
<base::FilePath
> pending_directories
;
280 pending_directories
.push(base::FilePath());
282 while (!pending_directories
.empty()) {
283 base::FilePath dir_path
= pending_directories
.top();
284 pending_directories
.pop();
286 base::FileEnumerator
file_enum(
287 dir_path
.empty() ? path_
: path_
.Append(dir_path
),
288 false /* not recursive */,
289 base::FileEnumerator::DIRECTORIES
| base::FileEnumerator::FILES
);
291 base::FilePath absolute_file_path
;
292 while (!(absolute_file_path
= file_enum
.Next()).empty()) {
293 base::FileEnumerator::FileInfo find_info
= file_enum
.GetInfo();
295 base::FilePath relative_file_path
;
296 if (!path_
.AppendRelativePath(absolute_file_path
, &relative_file_path
))
299 if (std::find(kExcludes
, kExcludes
+ arraysize(kExcludes
),
300 relative_file_path
) != kExcludes
+ arraysize(kExcludes
))
303 if (find_info
.IsDirectory()) {
304 pending_directories
.push(relative_file_path
);
308 // Check if the file has a database entry.
309 std::set
<base::FilePath
>::iterator itr
=
310 files_in_db_
.find(relative_file_path
);
311 if (itr
== files_in_db_
.end()) {
312 if (!base::DeleteFile(absolute_file_path
, false))
315 files_in_db_
.erase(itr
);
320 return files_in_db_
.empty();
323 bool DatabaseCheckHelper::ScanHierarchy() {
324 size_t visited_directories
= 0;
325 size_t visited_files
= 0;
326 size_t visited_links
= 0;
328 std::stack
<FileId
> directories
;
331 // Check if the root directory exists as a directory.
333 if (!dir_db_
->GetFileInfo(0, &file_info
))
335 if (file_info
.parent_id
!= 0 ||
336 !file_info
.is_directory())
339 while (!directories
.empty()) {
340 ++visited_directories
;
341 FileId dir_id
= directories
.top();
344 std::vector
<FileId
> children
;
345 if (!dir_db_
->ListChildren(dir_id
, &children
))
347 for (std::vector
<FileId
>::iterator itr
= children
.begin();
348 itr
!= children
.end();
350 // Any directory must not have root directory as child.
354 // Check if the child knows the parent as its parent.
356 if (!dir_db_
->GetFileInfo(*itr
, &file_info
))
358 if (file_info
.parent_id
!= dir_id
)
361 // Check if the parent knows the name of its child correctly.
363 if (!dir_db_
->GetChildWithName(dir_id
, file_info
.name
, &file_id
) ||
367 if (file_info
.is_directory())
368 directories
.push(*itr
);
375 // Check if we've visited all database entries.
376 return num_directories_in_db_
== visited_directories
&&
377 num_files_in_db_
== visited_files
&&
378 num_hierarchy_links_in_db_
== visited_links
;
381 // Returns true if the given |data_path| contains no parent references ("..")
382 // and does not refer to special system files.
383 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
384 // ensure we're only dealing with valid data paths.
385 bool VerifyDataPath(const base::FilePath
& data_path
) {
386 // |data_path| should not contain any ".." and should be a relative path
387 // (to the filesystem_data_directory_).
388 if (data_path
.ReferencesParent() || data_path
.IsAbsolute())
390 // See if it's not pointing to the special system paths.
391 const base::FilePath kExcludes
[] = {
392 base::FilePath(kDirectoryDatabaseName
),
393 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName
),
395 for (size_t i
= 0; i
< arraysize(kExcludes
); ++i
) {
396 if (data_path
== kExcludes
[i
] || kExcludes
[i
].IsParent(data_path
))
406 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
409 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
412 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
413 const base::FilePath
& filesystem_data_directory
)
414 : filesystem_data_directory_(filesystem_data_directory
) {
417 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
420 bool SandboxDirectoryDatabase::GetChildWithName(
422 const base::FilePath::StringType
& name
,
424 if (!Init(REPAIR_ON_CORRUPTION
))
427 std::string child_key
= GetChildLookupKey(parent_id
, name
);
428 std::string child_id_string
;
429 leveldb::Status status
=
430 db_
->Get(leveldb::ReadOptions(), child_key
, &child_id_string
);
431 if (status
.IsNotFound())
434 if (!base::StringToInt64(child_id_string
, child_id
)) {
435 LOG(ERROR
) << "Hit database corruption!";
440 HandleError(FROM_HERE
, status
);
444 bool SandboxDirectoryDatabase::GetFileWithPath(
445 const base::FilePath
& path
, FileId
* file_id
) {
446 std::vector
<base::FilePath::StringType
> components
;
447 VirtualPath::GetComponents(path
, &components
);
449 std::vector
<base::FilePath::StringType
>::iterator iter
;
450 for (iter
= components
.begin(); iter
!= components
.end(); ++iter
) {
451 base::FilePath::StringType name
;
453 if (name
== FILE_PATH_LITERAL("/"))
455 if (!GetChildWithName(local_id
, name
, &local_id
))
462 bool SandboxDirectoryDatabase::ListChildren(
463 FileId parent_id
, std::vector
<FileId
>* children
) {
464 // Check to add later: fail if parent is a file, at least in debug builds.
465 if (!Init(REPAIR_ON_CORRUPTION
))
468 std::string child_key_prefix
= GetChildListingKeyPrefix(parent_id
);
470 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
471 iter
->Seek(child_key_prefix
);
473 while (iter
->Valid() &&
474 StartsWithASCII(iter
->key().ToString(), child_key_prefix
, true)) {
475 std::string child_id_string
= iter
->value().ToString();
477 if (!base::StringToInt64(child_id_string
, &child_id
)) {
478 LOG(ERROR
) << "Hit database corruption!";
481 children
->push_back(child_id
);
487 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id
, FileInfo
* info
) {
488 if (!Init(REPAIR_ON_CORRUPTION
))
491 std::string file_key
= GetFileLookupKey(file_id
);
492 std::string file_data_string
;
493 leveldb::Status status
=
494 db_
->Get(leveldb::ReadOptions(), file_key
, &file_data_string
);
496 bool success
= FileInfoFromPickle(
497 Pickle(file_data_string
.data(), file_data_string
.length()), info
);
500 if (!VerifyDataPath(info
->data_path
)) {
501 LOG(ERROR
) << "Resolved data path is invalid: "
502 << info
->data_path
.value();
507 // Special-case the root, for databases that haven't been initialized yet.
508 // Without this, a query for the root's file info, made before creating the
509 // first file in the database, will fail and confuse callers.
510 if (status
.IsNotFound() && !file_id
) {
511 info
->name
= base::FilePath::StringType();
512 info
->data_path
= base::FilePath();
513 info
->modification_time
= base::Time::Now();
517 HandleError(FROM_HERE
, status
);
521 base::PlatformFileError
SandboxDirectoryDatabase::AddFileInfo(
522 const FileInfo
& info
, FileId
* file_id
) {
523 if (!Init(REPAIR_ON_CORRUPTION
))
524 return base::PLATFORM_FILE_ERROR_FAILED
;
526 std::string child_key
= GetChildLookupKey(info
.parent_id
, info
.name
);
527 std::string child_id_string
;
528 leveldb::Status status
=
529 db_
->Get(leveldb::ReadOptions(), child_key
, &child_id_string
);
531 LOG(ERROR
) << "File exists already!";
532 return base::PLATFORM_FILE_ERROR_EXISTS
;
534 if (!status
.IsNotFound()) {
535 HandleError(FROM_HERE
, status
);
536 return base::PLATFORM_FILE_ERROR_NOT_FOUND
;
539 if (!VerifyIsDirectory(info
.parent_id
))
540 return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY
;
542 // This would be a fine place to limit the number of files in a directory, if
543 // we decide to add that restriction.
546 if (!GetLastFileId(&temp_id
))
547 return base::PLATFORM_FILE_ERROR_FAILED
;
550 leveldb::WriteBatch batch
;
551 if (!AddFileInfoHelper(info
, temp_id
, &batch
))
552 return base::PLATFORM_FILE_ERROR_FAILED
;
554 batch
.Put(LastFileIdKey(), base::Int64ToString(temp_id
));
555 status
= db_
->Write(leveldb::WriteOptions(), &batch
);
557 HandleError(FROM_HERE
, status
);
558 return base::PLATFORM_FILE_ERROR_FAILED
;
561 return base::PLATFORM_FILE_OK
;
564 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id
) {
565 if (!Init(REPAIR_ON_CORRUPTION
))
567 leveldb::WriteBatch batch
;
568 if (!RemoveFileInfoHelper(file_id
, &batch
))
570 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
572 HandleError(FROM_HERE
, status
);
578 bool SandboxDirectoryDatabase::UpdateFileInfo(
579 FileId file_id
, const FileInfo
& new_info
) {
580 // TODO(ericu): We should also check to see that this doesn't create a loop,
581 // but perhaps only in a debug build.
582 if (!Init(REPAIR_ON_CORRUPTION
))
584 DCHECK(file_id
); // You can't remove the root, ever. Just delete the DB.
586 if (!GetFileInfo(file_id
, &old_info
))
588 if (old_info
.parent_id
!= new_info
.parent_id
&&
589 !VerifyIsDirectory(new_info
.parent_id
))
591 if (old_info
.parent_id
!= new_info
.parent_id
||
592 old_info
.name
!= new_info
.name
) {
593 // Check for name clashes.
595 if (GetChildWithName(new_info
.parent_id
, new_info
.name
, &temp_id
)) {
596 LOG(ERROR
) << "Name collision on move.";
600 leveldb::WriteBatch batch
;
601 if (!RemoveFileInfoHelper(file_id
, &batch
) ||
602 !AddFileInfoHelper(new_info
, file_id
, &batch
))
604 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
606 HandleError(FROM_HERE
, status
);
612 bool SandboxDirectoryDatabase::UpdateModificationTime(
613 FileId file_id
, const base::Time
& modification_time
) {
615 if (!GetFileInfo(file_id
, &info
))
617 info
.modification_time
= modification_time
;
619 if (!PickleFromFileInfo(info
, &pickle
))
621 leveldb::Status status
= db_
->Put(
622 leveldb::WriteOptions(),
623 GetFileLookupKey(file_id
),
624 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
627 HandleError(FROM_HERE
, status
);
633 bool SandboxDirectoryDatabase::OverwritingMoveFile(
634 FileId src_file_id
, FileId dest_file_id
) {
635 FileInfo src_file_info
;
636 FileInfo dest_file_info
;
638 if (!GetFileInfo(src_file_id
, &src_file_info
))
640 if (!GetFileInfo(dest_file_id
, &dest_file_info
))
642 if (src_file_info
.is_directory() || dest_file_info
.is_directory())
644 leveldb::WriteBatch batch
;
645 // This is the only field that really gets moved over; if you add fields to
646 // FileInfo, e.g. ctime, they might need to be copied here.
647 dest_file_info
.data_path
= src_file_info
.data_path
;
648 if (!RemoveFileInfoHelper(src_file_id
, &batch
))
651 if (!PickleFromFileInfo(dest_file_info
, &pickle
))
654 GetFileLookupKey(dest_file_id
),
655 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
657 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
659 HandleError(FROM_HERE
, status
);
665 bool SandboxDirectoryDatabase::GetNextInteger(int64
* next
) {
666 if (!Init(REPAIR_ON_CORRUPTION
))
669 std::string int_string
;
670 leveldb::Status status
=
671 db_
->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string
);
674 if (!base::StringToInt64(int_string
, &temp
)) {
675 LOG(ERROR
) << "Hit database corruption!";
679 status
= db_
->Put(leveldb::WriteOptions(), LastIntegerKey(),
680 base::Int64ToString(temp
));
682 HandleError(FROM_HERE
, status
);
688 if (!status
.IsNotFound()) {
689 HandleError(FROM_HERE
, status
);
692 // The database must not yet exist; initialize it.
693 if (!StoreDefaultValues())
696 return GetNextInteger(next
);
700 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath
& path
) {
701 std::string name
= FilePathToString(path
.Append(kDirectoryDatabaseName
));
702 leveldb::Status status
= leveldb::DestroyDB(name
, leveldb::Options());
705 LOG(WARNING
) << "Failed to destroy a database with status " <<
710 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option
) {
715 FilePathToString(filesystem_data_directory_
.Append(
716 kDirectoryDatabaseName
));
717 leveldb::Options options
;
718 options
.max_open_files
= 0; // Use minimum.
719 options
.create_if_missing
= true;
721 leveldb::Status status
= leveldb::DB::Open(options
, path
, &db
);
722 ReportInitStatus(status
);
727 HandleError(FROM_HERE
, status
);
729 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
730 // of Corruption error.
731 // Try to repair database even when IOError case.
732 if (!status
.IsCorruption() && !status
.IsIOError())
735 switch (recovery_option
) {
736 case FAIL_ON_CORRUPTION
:
738 case REPAIR_ON_CORRUPTION
:
739 LOG(WARNING
) << "Corrupted SandboxDirectoryDatabase detected."
740 << " Attempting to repair.";
741 if (RepairDatabase(path
)) {
742 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel
,
743 DB_REPAIR_SUCCEEDED
, DB_REPAIR_MAX
);
746 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel
,
747 DB_REPAIR_FAILED
, DB_REPAIR_MAX
);
748 LOG(WARNING
) << "Failed to repair SandboxDirectoryDatabase.";
750 case DELETE_ON_CORRUPTION
:
751 LOG(WARNING
) << "Clearing SandboxDirectoryDatabase.";
752 if (!base::DeleteFile(filesystem_data_directory_
, true))
754 if (!file_util::CreateDirectory(filesystem_data_directory_
))
756 return Init(FAIL_ON_CORRUPTION
);
763 bool SandboxDirectoryDatabase::RepairDatabase(const std::string
& db_path
) {
765 leveldb::Options options
;
766 options
.max_open_files
= 0; // Use minimum.
767 if (!leveldb::RepairDB(db_path
, options
).ok())
769 if (!Init(FAIL_ON_CORRUPTION
))
771 if (IsFileSystemConsistent())
777 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
778 if (!Init(FAIL_ON_CORRUPTION
))
780 DatabaseCheckHelper
helper(this, db_
.get(), filesystem_data_directory_
);
781 return helper
.IsFileSystemConsistent();
784 void SandboxDirectoryDatabase::ReportInitStatus(
785 const leveldb::Status
& status
) {
786 base::Time now
= base::Time::Now();
787 const base::TimeDelta minimum_interval
=
788 base::TimeDelta::FromHours(kMinimumReportIntervalHours
);
789 if (last_reported_time_
+ minimum_interval
>= now
)
791 last_reported_time_
= now
;
794 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
795 INIT_STATUS_OK
, INIT_STATUS_MAX
);
796 } else if (status
.IsCorruption()) {
797 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
798 INIT_STATUS_CORRUPTION
, INIT_STATUS_MAX
);
799 } else if (status
.IsIOError()) {
800 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
801 INIT_STATUS_IO_ERROR
, INIT_STATUS_MAX
);
803 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
804 INIT_STATUS_UNKNOWN_ERROR
, INIT_STATUS_MAX
);
808 bool SandboxDirectoryDatabase::StoreDefaultValues() {
809 // Verify that this is a totally new database, and initialize it.
810 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
812 if (iter
->Valid()) { // DB was not empty--we shouldn't have been called.
813 LOG(ERROR
) << "File system origin database is corrupt!";
816 // This is always the first write into the database. If we ever add a
817 // version number, it should go in this transaction too.
820 root
.modification_time
= base::Time::Now();
821 leveldb::WriteBatch batch
;
822 if (!AddFileInfoHelper(root
, 0, &batch
))
824 batch
.Put(LastFileIdKey(), base::Int64ToString(0));
825 batch
.Put(LastIntegerKey(), base::Int64ToString(-1));
826 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
828 HandleError(FROM_HERE
, status
);
834 bool SandboxDirectoryDatabase::GetLastFileId(FileId
* file_id
) {
835 if (!Init(REPAIR_ON_CORRUPTION
))
838 std::string id_string
;
839 leveldb::Status status
=
840 db_
->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string
);
842 if (!base::StringToInt64(id_string
, file_id
)) {
843 LOG(ERROR
) << "Hit database corruption!";
848 if (!status
.IsNotFound()) {
849 HandleError(FROM_HERE
, status
);
852 // The database must not yet exist; initialize it.
853 if (!StoreDefaultValues())
859 bool SandboxDirectoryDatabase::VerifyIsDirectory(FileId file_id
) {
862 return true; // The root is a directory.
863 if (!GetFileInfo(file_id
, &info
))
865 if (!info
.is_directory()) {
866 LOG(ERROR
) << "New parent directory is a file!";
872 // This does very few safety checks!
873 bool SandboxDirectoryDatabase::AddFileInfoHelper(
874 const FileInfo
& info
, FileId file_id
, leveldb::WriteBatch
* batch
) {
875 if (!VerifyDataPath(info
.data_path
)) {
876 LOG(ERROR
) << "Invalid data path is given: " << info
.data_path
.value();
879 std::string id_string
= GetFileLookupKey(file_id
);
881 // The root directory doesn't need to be looked up by path from its parent.
882 DCHECK(!info
.parent_id
);
883 DCHECK(info
.data_path
.empty());
885 std::string child_key
= GetChildLookupKey(info
.parent_id
, info
.name
);
886 batch
->Put(child_key
, id_string
);
889 if (!PickleFromFileInfo(info
, &pickle
))
893 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
898 // This does very few safety checks!
899 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
900 FileId file_id
, leveldb::WriteBatch
* batch
) {
901 DCHECK(file_id
); // You can't remove the root, ever. Just delete the DB.
903 if (!GetFileInfo(file_id
, &info
))
905 if (info
.data_path
.empty()) { // It's a directory
906 std::vector
<FileId
> children
;
907 // TODO(ericu): Make a faster is-the-directory-empty check.
908 if (!ListChildren(file_id
, &children
))
910 if (children
.size()) {
911 LOG(ERROR
) << "Can't remove a directory with children.";
915 batch
->Delete(GetChildLookupKey(info
.parent_id
, info
.name
));
916 batch
->Delete(GetFileLookupKey(file_id
));
920 void SandboxDirectoryDatabase::HandleError(
921 const tracked_objects::Location
& from_here
,
922 const leveldb::Status
& status
) {
923 LOG(ERROR
) << "SandboxDirectoryDatabase failed at: "
924 << from_here
.ToString() << " with error: " << status
.ToString();
928 } // namespace fileapi