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"
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"
27 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo
& info
,
28 base::Pickle
* pickle
) {
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
= 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()))
49 bool FileInfoFromPickle(const base::Pickle
& pickle
,
50 storage::SandboxDirectoryDatabase::FileInfo
* info
) {
51 base::PickleIterator
iter(pickle
);
52 std::string data_path
;
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
);
65 LOG(ERROR
) << "base::Pickle could not be digested!";
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.
84 INIT_STATUS_CORRUPTION
,
86 INIT_STATUS_UNKNOWN_ERROR
,
90 // These values are recorded in UMA. Changing existing values will invalidate
91 // results for older Chrome releases. Only add new values.
93 DB_REPAIR_SUCCEEDED
= 0,
98 std::string
GetChildLookupKey(
99 storage::SandboxDirectoryDatabase::FileId parent_id
,
100 const base::FilePath::StringType
& child_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
);
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|,
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
{
140 typedef storage::SandboxDirectoryDatabase::FileId FileId
;
141 typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo
;
143 DatabaseCheckHelper(storage::SandboxDirectoryDatabase
* dir_db
,
145 const base::FilePath
& path
);
147 bool IsFileSystemConsistent() {
148 return IsDatabaseEmpty() ||
149 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
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
158 bool ScanDirectory();
159 bool ScanHierarchy();
161 storage::SandboxDirectoryDatabase
* dir_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
,
178 const base::FilePath
& path
)
182 num_directories_in_db_(0),
184 num_hierarchy_links_in_db_(0),
189 DCHECK(!path_
.empty() && base::DirectoryExists(path_
));
192 bool DatabaseCheckHelper::IsDatabaseEmpty() {
193 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
195 return !itr
->Valid();
198 bool DatabaseCheckHelper::ScanDatabase() {
199 // Scans all database entries sequentially to verify each of them has unique
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_
))
219 if (last_file_id_
< 0)
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_
))
229 // value: "<pickled FileInfo>"
231 if (!FileInfoFromPickle(
232 base::Pickle(itr
->value().data(), itr
->value().size()),
237 if (!base::StringToInt64(key
, &file_id
) || file_id
< 0)
240 if (max_file_id
< file_id
)
241 max_file_id
= file_id
;
242 if (!file_ids
.insert(file_id
).second
)
245 if (file_info
.is_directory()) {
246 ++num_directories_in_db_
;
247 DCHECK(file_info
.data_path
.empty());
249 // Ensure any pair of file entry don't share their data_path.
250 if (!files_in_db_
.insert(file_info
.data_path
).second
)
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
))
264 --num_hierarchy_links_in_db_
;
265 files_in_db_
.erase(file_info
.data_path
);
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
))
307 if (std::find(kExcludes
, kExcludes
+ arraysize(kExcludes
),
308 relative_file_path
) != kExcludes
+ arraysize(kExcludes
))
311 if (find_info
.IsDirectory()) {
312 pending_directories
.push(relative_file_path
);
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))
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
;
339 // Check if the root directory exists as a directory.
341 if (!dir_db_
->GetFileInfo(0, &file_info
))
343 if (file_info
.parent_id
!= 0 ||
344 !file_info
.is_directory())
347 while (!directories
.empty()) {
348 ++visited_directories
;
349 FileId dir_id
= directories
.top();
352 std::vector
<FileId
> children
;
353 if (!dir_db_
->ListChildren(dir_id
, &children
))
355 for (std::vector
<FileId
>::iterator itr
= children
.begin();
356 itr
!= children
.end();
358 // Any directory must not have root directory as child.
362 // Check if the child knows the parent as its parent.
364 if (!dir_db_
->GetFileInfo(*itr
, &file_info
))
366 if (file_info
.parent_id
!= dir_id
)
369 // Check if the parent knows the name of its child correctly.
371 if (!dir_db_
->GetChildWithName(dir_id
, file_info
.name
, &file_id
) ||
375 if (file_info
.is_directory())
376 directories
.push(*itr
);
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())
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
))
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(
432 const base::FilePath::StringType
& name
,
434 if (!Init(REPAIR_ON_CORRUPTION
))
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())
444 if (!base::StringToInt64(child_id_string
, child_id
)) {
445 LOG(ERROR
) << "Hit database corruption!";
450 HandleError(FROM_HERE
, status
);
454 bool SandboxDirectoryDatabase::GetFileWithPath(
455 const base::FilePath
& path
, FileId
* file_id
) {
456 std::vector
<base::FilePath::StringType
> components
;
457 VirtualPath::GetComponents(path
, &components
);
459 std::vector
<base::FilePath::StringType
>::iterator iter
;
460 for (iter
= components
.begin(); iter
!= components
.end(); ++iter
) {
461 base::FilePath::StringType name
;
463 if (name
== FILE_PATH_LITERAL("/"))
465 if (!GetChildWithName(local_id
, name
, &local_id
))
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
))
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
);
483 while (iter
->Valid() && base::StartsWith(iter
->key().ToString(),
485 base::CompareCase::SENSITIVE
)) {
486 std::string child_id_string
= iter
->value().ToString();
488 if (!base::StringToInt64(child_id_string
, &child_id
)) {
489 LOG(ERROR
) << "Hit database corruption!";
492 children
->push_back(child_id
);
498 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id
, FileInfo
* info
) {
499 if (!Init(REPAIR_ON_CORRUPTION
))
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
);
507 bool success
= FileInfoFromPickle(
508 base::Pickle(file_data_string
.data(), file_data_string
.length()), info
);
511 if (!VerifyDataPath(info
->data_path
)) {
512 LOG(ERROR
) << "Resolved data path is invalid: "
513 << info
->data_path
.value();
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();
528 HandleError(FROM_HERE
, status
);
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
;
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
);
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.
559 if (!GetLastFileId(&temp_id
))
560 return base::File::FILE_ERROR_FAILED
;
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
);
570 HandleError(FROM_HERE
, status
);
571 return base::File::FILE_ERROR_FAILED
;
574 return base::File::FILE_OK
;
577 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id
) {
578 if (!Init(REPAIR_ON_CORRUPTION
))
580 leveldb::WriteBatch batch
;
581 if (!RemoveFileInfoHelper(file_id
, &batch
))
583 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
585 HandleError(FROM_HERE
, status
);
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
))
597 DCHECK(file_id
); // You can't remove the root, ever. Just delete the DB.
599 if (!GetFileInfo(file_id
, &old_info
))
601 if (old_info
.parent_id
!= new_info
.parent_id
&&
602 !IsDirectory(new_info
.parent_id
))
604 if (old_info
.parent_id
!= new_info
.parent_id
||
605 old_info
.name
!= new_info
.name
) {
606 // Check for name clashes.
608 if (GetChildWithName(new_info
.parent_id
, new_info
.name
, &temp_id
)) {
609 LOG(ERROR
) << "Name collision on move.";
613 leveldb::WriteBatch batch
;
614 if (!RemoveFileInfoHelper(file_id
, &batch
) ||
615 !AddFileInfoHelper(new_info
, file_id
, &batch
))
617 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
619 HandleError(FROM_HERE
, status
);
625 bool SandboxDirectoryDatabase::UpdateModificationTime(
626 FileId file_id
, const base::Time
& modification_time
) {
628 if (!GetFileInfo(file_id
, &info
))
630 info
.modification_time
= modification_time
;
632 if (!PickleFromFileInfo(info
, &pickle
))
634 leveldb::Status status
= db_
->Put(
635 leveldb::WriteOptions(),
636 GetFileLookupKey(file_id
),
637 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
640 HandleError(FROM_HERE
, status
);
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
))
653 if (!GetFileInfo(dest_file_id
, &dest_file_info
))
655 if (src_file_info
.is_directory() || dest_file_info
.is_directory())
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
))
664 if (!PickleFromFileInfo(dest_file_info
, &pickle
))
667 GetFileLookupKey(dest_file_id
),
668 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
670 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
672 HandleError(FROM_HERE
, status
);
678 bool SandboxDirectoryDatabase::GetNextInteger(int64
* next
) {
679 if (!Init(REPAIR_ON_CORRUPTION
))
682 std::string int_string
;
683 leveldb::Status status
=
684 db_
->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string
);
687 if (!base::StringToInt64(int_string
, &temp
)) {
688 LOG(ERROR
) << "Hit database corruption!";
692 status
= db_
->Put(leveldb::WriteOptions(), LastIntegerKey(),
693 base::Int64ToString(temp
));
695 HandleError(FROM_HERE
, status
);
701 if (!status
.IsNotFound()) {
702 HandleError(FROM_HERE
, status
);
705 // The database must not yet exist; initialize it.
706 if (!StoreDefaultValues())
709 return GetNextInteger(next
);
712 bool SandboxDirectoryDatabase::DestroyDatabase() {
714 const std::string path
=
715 FilePathToString(filesystem_data_directory_
.Append(
716 kDirectoryDatabaseName
));
717 leveldb::Options options
;
719 options
.env
= env_override_
;
720 leveldb::Status status
= leveldb::DestroyDB(path
, options
);
723 LOG(WARNING
) << "Failed to destroy a database with status " <<
728 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option
) {
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
;
740 options
.env
= env_override_
;
742 leveldb::Status status
= leveldb::DB::Open(options
, path
, &db
);
743 ReportInitStatus(status
);
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())
756 switch (recovery_option
) {
757 case FAIL_ON_CORRUPTION
:
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
);
767 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel
,
768 DB_REPAIR_FAILED
, DB_REPAIR_MAX
);
769 LOG(WARNING
) << "Failed to repair SandboxDirectoryDatabase.";
771 case DELETE_ON_CORRUPTION
:
772 LOG(WARNING
) << "Clearing SandboxDirectoryDatabase.";
773 if (!base::DeleteFile(filesystem_data_directory_
, true))
775 if (!base::CreateDirectory(filesystem_data_directory_
))
777 return Init(FAIL_ON_CORRUPTION
);
784 bool SandboxDirectoryDatabase::RepairDatabase(const std::string
& db_path
) {
786 leveldb::Options options
;
787 options
.max_open_files
= 0; // Use minimum.
789 options
.env
= env_override_
;
790 if (!leveldb::RepairDB(db_path
, options
).ok())
792 if (!Init(FAIL_ON_CORRUPTION
))
794 if (IsFileSystemConsistent())
800 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id
) {
803 return true; // The root is a directory.
804 if (!GetFileInfo(file_id
, &info
))
806 if (!info
.is_directory())
811 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
812 if (!Init(FAIL_ON_CORRUPTION
))
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
)
825 last_reported_time_
= now
;
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
);
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()));
846 if (iter
->Valid()) { // DB was not empty--we shouldn't have been called.
847 LOG(ERROR
) << "File system origin database is corrupt!";
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.
854 root
.modification_time
= base::Time::Now();
855 leveldb::WriteBatch batch
;
856 if (!AddFileInfoHelper(root
, 0, &batch
))
858 batch
.Put(LastFileIdKey(), base::Int64ToString(0));
859 batch
.Put(LastIntegerKey(), base::Int64ToString(-1));
860 leveldb::Status status
= db_
->Write(leveldb::WriteOptions(), &batch
);
862 HandleError(FROM_HERE
, status
);
868 bool SandboxDirectoryDatabase::GetLastFileId(FileId
* file_id
) {
869 if (!Init(REPAIR_ON_CORRUPTION
))
872 std::string id_string
;
873 leveldb::Status status
=
874 db_
->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string
);
876 if (!base::StringToInt64(id_string
, file_id
)) {
877 LOG(ERROR
) << "Hit database corruption!";
882 if (!status
.IsNotFound()) {
883 HandleError(FROM_HERE
, status
);
886 // The database must not yet exist; initialize it.
887 if (!StoreDefaultValues())
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();
900 std::string id_string
= GetFileLookupKey(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());
906 std::string child_key
= GetChildLookupKey(info
.parent_id
, info
.name
);
907 batch
->Put(child_key
, id_string
);
910 if (!PickleFromFileInfo(info
, &pickle
))
914 leveldb::Slice(reinterpret_cast<const char *>(pickle
.data()),
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.
924 if (!GetFileInfo(file_id
, &info
))
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
))
931 if (children
.size()) {
932 LOG(ERROR
) << "Can't remove a directory with children.";
936 batch
->Delete(GetChildLookupKey(info
.parent_id
, info
.name
));
937 batch
->Delete(GetFileLookupKey(file_id
));
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();
949 } // namespace storage