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/fileapi/file_system_directory_database.h"
12 #include "base/file_util.h"
13 #include "base/location.h"
14 #include "base/metrics/histogram.h"
15 #include "base/pickle.h"
16 #include "base/string_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "third_party/leveldatabase/src/include/leveldb/db.h"
19 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
20 #include "webkit/fileapi/file_system_usage_cache.h"
21 #include "webkit/fileapi/file_system_util.h"
25 bool PickleFromFileInfo(
26 const fileapi::FileSystemDirectoryDatabase::FileInfo
& info
,
29 std::string data_path
;
30 // Round off here to match the behavior of the filesystem on real files.
32 base::Time::FromDoubleT(floor(info
.modification_time
.ToDoubleT()));
35 data_path
= fileapi::FilePathToString(info
.data_path
);
36 name
= fileapi::FilePathToString(base::FilePath(info
.name
));
38 if (pickle
->WriteInt64(info
.parent_id
) &&
39 pickle
->WriteString(data_path
) &&
40 pickle
->WriteString(name
) &&
41 pickle
->WriteInt64(time
.ToInternalValue()))
48 bool FileInfoFromPickle(
50 fileapi::FileSystemDirectoryDatabase::FileInfo
* info
) {
51 PickleIterator
iter(pickle
);
52 std::string data_path
;
56 if (pickle
.ReadInt64(&iter
, &info
->parent_id
) &&
57 pickle
.ReadString(&iter
, &data_path
) &&
58 pickle
.ReadString(&iter
, &name
) &&
59 pickle
.ReadInt64(&iter
, &internal_time
)) {
60 info
->data_path
= fileapi::StringToFilePath(data_path
);
61 info
->name
= fileapi::StringToFilePath(name
).value();
62 info
->modification_time
= base::Time::FromInternalValue(internal_time
);
65 LOG(ERROR
) << "Pickle could not be digested!";
69 const base::FilePath::CharType kDirectoryDatabaseName
[] = FILE_PATH_LITERAL("Paths");
70 const char kChildLookupPrefix
[] = "CHILD_OF:";
71 const char kChildLookupSeparator
[] = ":";
72 const char kLastFileIdKey
[] = "LAST_FILE_ID";
73 const char kLastIntegerKey
[] = "LAST_INTEGER";
74 const int64 kMinimumReportIntervalHours
= 1;
75 const char kInitStatusHistogramLabel
[] = "FileSystem.DirectoryDatabaseInit";
79 INIT_STATUS_CORRUPTION
,
81 INIT_STATUS_UNKNOWN_ERROR
,
85 std::string
GetChildLookupKey(
86 fileapi::FileSystemDirectoryDatabase::FileId parent_id
,
87 const base::FilePath::StringType
& child_name
) {
89 name
= fileapi::FilePathToString(base::FilePath(child_name
));
90 return std::string(kChildLookupPrefix
) + base::Int64ToString(parent_id
) +
91 std::string(kChildLookupSeparator
) + name
;
94 std::string
GetChildListingKeyPrefix(
95 fileapi::FileSystemDirectoryDatabase::FileId parent_id
) {
96 return std::string(kChildLookupPrefix
) + base::Int64ToString(parent_id
) +
97 std::string(kChildLookupSeparator
);
100 const char* LastFileIdKey() {
101 return kLastFileIdKey
;
104 const char* LastIntegerKey() {
105 return kLastIntegerKey
;
108 std::string
GetFileLookupKey(
109 fileapi::FileSystemDirectoryDatabase::FileId file_id
) {
110 return base::Int64ToString(file_id
);
114 // - Any database entry is one of:
115 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
116 // - ("LAST_FILE_ID", "|last_file_id|"),
117 // - ("LAST_INTEGER", "|last_integer|"),
118 // - ("|file_id|", "pickled FileInfo")
119 // where FileInfo has |parent_id|, |data_path|, |name| and
120 // |modification_time|,
122 // - Each file in the database has unique backing file.
123 // - Each file in |filesystem_data_directory_| has a database entry.
124 // - Directory structure is tree, i.e. connected and acyclic.
125 class DatabaseCheckHelper
{
127 typedef fileapi::FileSystemDirectoryDatabase::FileId FileId
;
128 typedef fileapi::FileSystemDirectoryDatabase::FileInfo FileInfo
;
130 DatabaseCheckHelper(fileapi::FileSystemDirectoryDatabase
* dir_db
,
132 const base::FilePath
& path
);
134 bool IsFileSystemConsistent() {
135 return IsDatabaseEmpty() ||
136 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
140 bool IsDatabaseEmpty();
141 // These 3 methods need to be called in the order. Each method requires its
142 // previous method finished successfully. They also require the database is
145 bool ScanDirectory();
146 bool ScanHierarchy();
148 fileapi::FileSystemDirectoryDatabase
* dir_db_
;
150 base::FilePath path_
;
152 std::set
<base::FilePath
> files_in_db_
;
154 size_t num_directories_in_db_
;
155 size_t num_files_in_db_
;
156 size_t num_hierarchy_links_in_db_
;
158 FileId last_file_id_
;
159 FileId last_integer_
;
162 DatabaseCheckHelper::DatabaseCheckHelper(
163 fileapi::FileSystemDirectoryDatabase
* dir_db
,
165 const base::FilePath
& path
)
166 : dir_db_(dir_db
), db_(db
), path_(path
),
167 num_directories_in_db_(0),
169 num_hierarchy_links_in_db_(0),
170 last_file_id_(-1), last_integer_(-1) {
173 DCHECK(!path_
.empty() && file_util::DirectoryExists(path_
));
176 bool DatabaseCheckHelper::IsDatabaseEmpty() {
177 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
179 return !itr
->Valid();
182 bool DatabaseCheckHelper::ScanDatabase() {
183 // Scans all database entries sequentially to verify each of them has unique
185 int64 max_file_id
= -1;
186 std::set
<FileId
> file_ids
;
188 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
189 for (itr
->SeekToFirst(); itr
->Valid(); itr
->Next()) {
190 std::string key
= itr
->key().ToString();
191 if (StartsWithASCII(key
, kChildLookupPrefix
, true)) {
192 // key: "CHILD_OF:<parent_id>:<name>"
193 // value: "<child_id>"
194 ++num_hierarchy_links_in_db_
;
195 } else if (key
== kLastFileIdKey
) {
196 // key: "LAST_FILE_ID"
197 // value: "<last_file_id>"
198 if (last_file_id_
>= 0 ||
199 !base::StringToInt64(itr
->value().ToString(), &last_file_id_
))
202 if (last_file_id_
< 0)
204 } else if (key
== kLastIntegerKey
) {
205 // key: "LAST_INTEGER"
206 // value: "<last_integer>"
207 if (last_integer_
>= 0 ||
208 !base::StringToInt64(itr
->value().ToString(), &last_integer_
))
212 // value: "<pickled FileInfo>"
214 if (!FileInfoFromPickle(
215 Pickle(itr
->value().data(), itr
->value().size()), &file_info
))
219 if (!base::StringToInt64(key
, &file_id
) || file_id
< 0)
222 if (max_file_id
< file_id
)
223 max_file_id
= file_id
;
224 if (!file_ids
.insert(file_id
).second
)
227 if (file_info
.is_directory()) {
228 ++num_directories_in_db_
;
229 DCHECK(file_info
.data_path
.empty());
231 // Ensure any pair of file entry don't share their data_path.
232 if (!files_in_db_
.insert(file_info
.data_path
).second
)
235 // Ensure the backing file exists as a normal file.
236 base::PlatformFileInfo platform_file_info
;
237 if (!file_util::GetFileInfo(
238 path_
.Append(file_info
.data_path
), &platform_file_info
) ||
239 platform_file_info
.is_directory
||
240 platform_file_info
.is_symbolic_link
) {
241 // leveldb::Iterator iterates a snapshot of the database.
242 // So even after RemoveFileInfo() call, we'll visit hierarchy link
243 // from |parent_id| to |file_id|.
244 if (!dir_db_
->RemoveFileInfo(file_id
))
246 --num_hierarchy_links_in_db_
;
247 files_in_db_
.erase(file_info
.data_path
);
255 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
256 // data path confliction on ObfuscatedFileUtil.
257 return max_file_id
<= last_file_id_
;
260 bool DatabaseCheckHelper::ScanDirectory() {
261 // TODO(kinuko): Scans all local file system entries to verify each of them
262 // has a database entry.
263 const base::FilePath kExcludes
[] = {
264 base::FilePath(kDirectoryDatabaseName
),
265 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName
),
268 // Any path in |pending_directories| is relative to |path_|.
269 std::stack
<base::FilePath
> pending_directories
;
270 pending_directories
.push(base::FilePath());
272 while (!pending_directories
.empty()) {
273 base::FilePath dir_path
= pending_directories
.top();
274 pending_directories
.pop();
276 file_util::FileEnumerator
file_enum(
277 dir_path
.empty() ? path_
: path_
.Append(dir_path
),
278 false /* not recursive */,
279 file_util::FileEnumerator::DIRECTORIES
|
280 file_util::FileEnumerator::FILES
);
282 base::FilePath absolute_file_path
;
283 while (!(absolute_file_path
= file_enum
.Next()).empty()) {
284 file_util::FileEnumerator::FindInfo find_info
;
285 file_enum
.GetFindInfo(&find_info
);
287 base::FilePath relative_file_path
;
288 if (!path_
.AppendRelativePath(absolute_file_path
, &relative_file_path
))
291 if (std::find(kExcludes
, kExcludes
+ arraysize(kExcludes
),
292 relative_file_path
) != kExcludes
+ arraysize(kExcludes
))
295 if (file_util::FileEnumerator::IsDirectory(find_info
)) {
296 pending_directories
.push(relative_file_path
);
300 // Check if the file has a database entry.
301 std::set
<base::FilePath
>::iterator itr
= files_in_db_
.find(relative_file_path
);
302 if (itr
== files_in_db_
.end()) {
303 if (!file_util::Delete(absolute_file_path
, false))
306 files_in_db_
.erase(itr
);
311 return files_in_db_
.empty();
314 bool DatabaseCheckHelper::ScanHierarchy() {
315 size_t visited_directories
= 0;
316 size_t visited_files
= 0;
317 size_t visited_links
= 0;
319 std::stack
<FileId
> directories
;
322 // Check if the root directory exists as a directory.
324 if (!dir_db_
->GetFileInfo(0, &file_info
))
326 if (file_info
.parent_id
!= 0 ||
327 !file_info
.is_directory())
330 while (!directories
.empty()) {
331 ++visited_directories
;
332 FileId dir_id
= directories
.top();
335 std::vector
<FileId
> children
;
336 if (!dir_db_
->ListChildren(dir_id
, &children
))
338 for (std::vector
<FileId
>::iterator itr
= children
.begin();
339 itr
!= children
.end();
341 // Any directory must not have root directory as child.
345 // Check if the child knows the parent as its parent.
347 if (!dir_db_
->GetFileInfo(*itr
, &file_info
))
349 if (file_info
.parent_id
!= dir_id
)
352 // Check if the parent knows the name of its child correctly.
354 if (!dir_db_
->GetChildWithName(dir_id
, file_info
.name
, &file_id
) ||
358 if (file_info
.is_directory())
359 directories
.push(*itr
);
366 // Check if we've visited all database entries.
367 return num_directories_in_db_
== visited_directories
&&
368 num_files_in_db_
== visited_files
&&
369 num_hierarchy_links_in_db_
== visited_links
;
372 // Returns true if the given |data_path| contains no parent references ("..")
373 // and does not refer to special system files.
374 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
375 // ensure we're only dealing with valid data paths.
376 bool VerifyDataPath(const base::FilePath
& data_path
) {
377 // |data_path| should not contain any ".." and should be a relative path
378 // (to the filesystem_data_directory_).
379 if (data_path
.ReferencesParent() || data_path
.IsAbsolute())
381 // See if it's not pointing to the special system paths.
382 const base::FilePath kExcludes
[] = {
383 base::FilePath(kDirectoryDatabaseName
),
384 base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName
),
386 for (size_t i
= 0; i
< arraysize(kExcludes
); ++i
) {
387 if (data_path
== kExcludes
[i
] || kExcludes
[i
].IsParent(data_path
))
397 FileSystemDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
400 FileSystemDirectoryDatabase::FileInfo::~FileInfo() {
403 FileSystemDirectoryDatabase::FileSystemDirectoryDatabase(
404 const base::FilePath
& filesystem_data_directory
)
405 : filesystem_data_directory_(filesystem_data_directory
) {
408 FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() {
411 bool FileSystemDirectoryDatabase::GetChildWithName(
412 FileId parent_id
, const base::FilePath::StringType
& name
, FileId
* child_id
) {
413 if (!Init(REPAIR_ON_CORRUPTION
))
416 std::string child_key
= GetChildLookupKey(parent_id
, name
);
417 std::string child_id_string
;
418 leveldb::Status status
=
419 db_
->Get(leveldb::ReadOptions(), child_key
, &child_id_string
);
420 if (status
.IsNotFound())
423 if (!base::StringToInt64(child_id_string
, child_id
)) {
424 LOG(ERROR
) << "Hit database corruption!";
429 HandleError(FROM_HERE
, status
);
433 bool FileSystemDirectoryDatabase::GetFileWithPath(
434 const base::FilePath
& path
, FileId
* file_id
) {
435 std::vector
<base::FilePath::StringType
> components
;
436 VirtualPath::GetComponents(path
, &components
);
438 std::vector
<base::FilePath::StringType
>::iterator iter
;
439 for (iter
= components
.begin(); iter
!= components
.end(); ++iter
) {
440 base::FilePath::StringType name
;
442 if (name
== FILE_PATH_LITERAL("/"))
444 if (!GetChildWithName(local_id
, name
, &local_id
))
451 bool FileSystemDirectoryDatabase::ListChildren(
452 FileId parent_id
, std::vector
<FileId
>* children
) {
453 // Check to add later: fail if parent is a file, at least in debug builds.
454 if (!Init(REPAIR_ON_CORRUPTION
))
457 std::string child_key_prefix
= GetChildListingKeyPrefix(parent_id
);
459 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
460 iter
->Seek(child_key_prefix
);
462 while (iter
->Valid() &&
463 StartsWithASCII(iter
->key().ToString(), child_key_prefix
, true)) {
464 std::string child_id_string
= iter
->value().ToString();
466 if (!base::StringToInt64(child_id_string
, &child_id
)) {
467 LOG(ERROR
) << "Hit database corruption!";
470 children
->push_back(child_id
);
476 bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id
, FileInfo
* info
) {
477 if (!Init(REPAIR_ON_CORRUPTION
))
480 std::string file_key
= GetFileLookupKey(file_id
);
481 std::string file_data_string
;
482 leveldb::Status status
=
483 db_
->Get(leveldb::ReadOptions(), file_key
, &file_data_string
);
485 bool success
= FileInfoFromPickle(
486 Pickle(file_data_string
.data(), file_data_string
.length()), info
);
489 if (!VerifyDataPath(info
->data_path
)) {
490 LOG(ERROR
) << "Resolved data path is invalid: "
491 << info
->data_path
.value();
496 // Special-case the root, for databases that haven't been initialized yet.
497 // Without this, a query for the root's file info, made before creating the
498 // first file in the database, will fail and confuse callers.
499 if (status
.IsNotFound() && !file_id
) {
500 info
->name
= base::FilePath::StringType();
501 info
->data_path
= base::FilePath();
502 info
->modification_time
= base::Time::Now();
506 HandleError(FROM_HERE
, status
);
510 bool FileSystemDirectoryDatabase::AddFileInfo(
511 const FileInfo
& info
, FileId
* file_id
) {
512 if (!Init(REPAIR_ON_CORRUPTION
))
515 std::string child_key
= GetChildLookupKey(info
.parent_id
, info
.name
);
516 std::string child_id_string
;
517 leveldb::Status status
=
518 db_
->Get(leveldb::ReadOptions(), child_key
, &child_id_string
);
520 LOG(ERROR
) << "File exists already!";
523 if (!status
.IsNotFound()) {
524 HandleError(FROM_HERE
, status
);
528 if (!VerifyIsDirectory(info
.parent_id
))
531 // This would be a fine place to limit the number of files in a directory, if
532 // we decide to add that restriction.
535 if (!GetLastFileId(&temp_id
))
539 leveldb::WriteBatch batch
;
540 if (!AddFileInfoHelper(info
, temp_id
, &batch
))
543 batch
.Put(LastFileIdKey(), base::Int64ToString(temp_id
));
544 status
= db_
->Write(leveldb::WriteOptions(), &batch
);
546 HandleError(FROM_HERE
, status
);
553 bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id
) {
554 if (!Init(REPAIR_ON_CORRUPTION
))
556 leveldb::WriteBatch batch
;
557 if (!RemoveFileInfoHelper(file_id
, &batch
))
559 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
561 HandleError(FROM_HERE
, status
);
567 bool FileSystemDirectoryDatabase::UpdateFileInfo(
568 FileId file_id
, const FileInfo
& new_info
) {
569 // TODO(ericu): We should also check to see that this doesn't create a loop,
570 // but perhaps only in a debug build.
571 if (!Init(REPAIR_ON_CORRUPTION
))
573 DCHECK(file_id
); // You can't remove the root, ever. Just delete the DB.
575 if (!GetFileInfo(file_id
, &old_info
))
577 if (old_info
.parent_id
!= new_info
.parent_id
&&
578 !VerifyIsDirectory(new_info
.parent_id
))
580 if (old_info
.parent_id
!= new_info
.parent_id
||
581 old_info
.name
!= new_info
.name
) {
582 // Check for name clashes.
584 if (GetChildWithName(new_info
.parent_id
, new_info
.name
, &temp_id
)) {
585 LOG(ERROR
) << "Name collision on move.";
589 leveldb::WriteBatch batch
;
590 if (!RemoveFileInfoHelper(file_id
, &batch
) ||
591 !AddFileInfoHelper(new_info
, file_id
, &batch
))
593 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
595 HandleError(FROM_HERE
, status
);
601 bool FileSystemDirectoryDatabase::UpdateModificationTime(
602 FileId file_id
, const base::Time
& modification_time
) {
604 if (!GetFileInfo(file_id
, &info
))
606 info
.modification_time
= modification_time
;
608 if (!PickleFromFileInfo(info
, &pickle
))
610 leveldb::Status status
= db_
->Put(
611 leveldb::WriteOptions(),
612 GetFileLookupKey(file_id
),
613 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
616 HandleError(FROM_HERE
, status
);
622 bool FileSystemDirectoryDatabase::OverwritingMoveFile(
623 FileId src_file_id
, FileId dest_file_id
) {
624 FileInfo src_file_info
;
625 FileInfo dest_file_info
;
627 if (!GetFileInfo(src_file_id
, &src_file_info
))
629 if (!GetFileInfo(dest_file_id
, &dest_file_info
))
631 if (src_file_info
.is_directory() || dest_file_info
.is_directory())
633 leveldb::WriteBatch batch
;
634 // This is the only field that really gets moved over; if you add fields to
635 // FileInfo, e.g. ctime, they might need to be copied here.
636 dest_file_info
.data_path
= src_file_info
.data_path
;
637 if (!RemoveFileInfoHelper(src_file_id
, &batch
))
640 if (!PickleFromFileInfo(dest_file_info
, &pickle
))
643 GetFileLookupKey(dest_file_id
),
644 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
646 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
648 HandleError(FROM_HERE
, status
);
654 bool FileSystemDirectoryDatabase::GetNextInteger(int64
* next
) {
655 if (!Init(REPAIR_ON_CORRUPTION
))
658 std::string int_string
;
659 leveldb::Status status
=
660 db_
->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string
);
663 if (!base::StringToInt64(int_string
, &temp
)) {
664 LOG(ERROR
) << "Hit database corruption!";
668 status
= db_
->Put(leveldb::WriteOptions(), LastIntegerKey(),
669 base::Int64ToString(temp
));
671 HandleError(FROM_HERE
, status
);
677 if (!status
.IsNotFound()) {
678 HandleError(FROM_HERE
, status
);
681 // The database must not yet exist; initialize it.
682 if (!StoreDefaultValues())
685 return GetNextInteger(next
);
689 bool FileSystemDirectoryDatabase::DestroyDatabase(const base::FilePath
& path
) {
690 std::string name
= FilePathToString(path
.Append(kDirectoryDatabaseName
));
691 leveldb::Status status
= leveldb::DestroyDB(name
, leveldb::Options());
694 LOG(WARNING
) << "Failed to destroy a database with status " <<
699 bool FileSystemDirectoryDatabase::Init(RecoveryOption recovery_option
) {
704 FilePathToString(filesystem_data_directory_
.Append(
705 kDirectoryDatabaseName
));
706 leveldb::Options options
;
707 options
.create_if_missing
= true;
709 leveldb::Status status
= leveldb::DB::Open(options
, path
, &db
);
710 ReportInitStatus(status
);
715 HandleError(FROM_HERE
, status
);
717 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
718 // of Corruption error.
719 // Try to repair database even when IOError case.
720 if (!status
.IsCorruption() && !status
.IsIOError())
723 switch (recovery_option
) {
724 case FAIL_ON_CORRUPTION
:
726 case REPAIR_ON_CORRUPTION
:
727 LOG(WARNING
) << "Corrupted FileSystemDirectoryDatabase detected."
728 << " Attempting to repair.";
729 if (RepairDatabase(path
))
731 LOG(WARNING
) << "Failed to repair FileSystemDirectoryDatabase.";
733 case DELETE_ON_CORRUPTION
:
734 LOG(WARNING
) << "Clearing FileSystemDirectoryDatabase.";
735 if (!file_util::Delete(filesystem_data_directory_
, true))
737 if (!file_util::CreateDirectory(filesystem_data_directory_
))
739 return Init(FAIL_ON_CORRUPTION
);
746 bool FileSystemDirectoryDatabase::RepairDatabase(const std::string
& db_path
) {
748 if (!leveldb::RepairDB(db_path
, leveldb::Options()).ok())
750 if (!Init(FAIL_ON_CORRUPTION
))
752 if (IsFileSystemConsistent())
758 bool FileSystemDirectoryDatabase::IsFileSystemConsistent() {
759 if (!Init(FAIL_ON_CORRUPTION
))
761 DatabaseCheckHelper
helper(this, db_
.get(), filesystem_data_directory_
);
762 return helper
.IsFileSystemConsistent();
765 void FileSystemDirectoryDatabase::ReportInitStatus(
766 const leveldb::Status
& status
) {
767 base::Time now
= base::Time::Now();
768 const base::TimeDelta minimum_interval
=
769 base::TimeDelta::FromHours(kMinimumReportIntervalHours
);
770 if (last_reported_time_
+ minimum_interval
>= now
)
772 last_reported_time_
= now
;
775 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
776 INIT_STATUS_OK
, INIT_STATUS_MAX
);
777 } else if (status
.IsCorruption()) {
778 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
779 INIT_STATUS_CORRUPTION
, INIT_STATUS_MAX
);
780 } else if (status
.IsIOError()) {
781 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
782 INIT_STATUS_IO_ERROR
, INIT_STATUS_MAX
);
784 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel
,
785 INIT_STATUS_UNKNOWN_ERROR
, INIT_STATUS_MAX
);
789 bool FileSystemDirectoryDatabase::StoreDefaultValues() {
790 // Verify that this is a totally new database, and initialize it.
791 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
793 if (iter
->Valid()) { // DB was not empty--we shouldn't have been called.
794 LOG(ERROR
) << "File system origin database is corrupt!";
797 // This is always the first write into the database. If we ever add a
798 // version number, it should go in this transaction too.
801 root
.modification_time
= base::Time::Now();
802 leveldb::WriteBatch batch
;
803 if (!AddFileInfoHelper(root
, 0, &batch
))
805 batch
.Put(LastFileIdKey(), base::Int64ToString(0));
806 batch
.Put(LastIntegerKey(), base::Int64ToString(-1));
807 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
809 HandleError(FROM_HERE
, status
);
815 bool FileSystemDirectoryDatabase::GetLastFileId(FileId
* file_id
) {
816 if (!Init(REPAIR_ON_CORRUPTION
))
819 std::string id_string
;
820 leveldb::Status status
=
821 db_
->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string
);
823 if (!base::StringToInt64(id_string
, file_id
)) {
824 LOG(ERROR
) << "Hit database corruption!";
829 if (!status
.IsNotFound()) {
830 HandleError(FROM_HERE
, status
);
833 // The database must not yet exist; initialize it.
834 if (!StoreDefaultValues())
840 bool FileSystemDirectoryDatabase::VerifyIsDirectory(FileId file_id
) {
843 return true; // The root is a directory.
844 if (!GetFileInfo(file_id
, &info
))
846 if (!info
.is_directory()) {
847 LOG(ERROR
) << "New parent directory is a file!";
853 // This does very few safety checks!
854 bool FileSystemDirectoryDatabase::AddFileInfoHelper(
855 const FileInfo
& info
, FileId file_id
, leveldb::WriteBatch
* batch
) {
856 if (!VerifyDataPath(info
.data_path
)) {
857 LOG(ERROR
) << "Invalid data path is given: " << info
.data_path
.value();
860 std::string id_string
= GetFileLookupKey(file_id
);
862 // The root directory doesn't need to be looked up by path from its parent.
863 DCHECK(!info
.parent_id
);
864 DCHECK(info
.data_path
.empty());
866 std::string child_key
= GetChildLookupKey(info
.parent_id
, info
.name
);
867 batch
->Put(child_key
, id_string
);
870 if (!PickleFromFileInfo(info
, &pickle
))
874 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
879 // This does very few safety checks!
880 bool FileSystemDirectoryDatabase::RemoveFileInfoHelper(
881 FileId file_id
, leveldb::WriteBatch
* batch
) {
882 DCHECK(file_id
); // You can't remove the root, ever. Just delete the DB.
884 if (!GetFileInfo(file_id
, &info
))
886 if (info
.data_path
.empty()) { // It's a directory
887 std::vector
<FileId
> children
;
888 // TODO(ericu): Make a faster is-the-directory-empty check.
889 if (!ListChildren(file_id
, &children
))
891 if (children
.size()) {
892 LOG(ERROR
) << "Can't remove a directory with children.";
896 batch
->Delete(GetChildLookupKey(info
.parent_id
, info
.name
));
897 batch
->Delete(GetFileLookupKey(file_id
));
901 void FileSystemDirectoryDatabase::HandleError(
902 const tracked_objects::Location
& from_here
,
903 const leveldb::Status
& status
) {
904 LOG(ERROR
) << "FileSystemDirectoryDatabase failed at: "
905 << from_here
.ToString() << " with error: " << status
.ToString();
909 } // namespace fileapi