|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "storage/browser/fileapi/sandbox_directory_database.h" | 
|  |  | 
|  | #include <math.h> | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <memory> | 
|  | #include <set> | 
|  |  | 
|  | #include "base/containers/stack.h" | 
|  | #include "base/files/file_enumerator.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/location.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/pickle.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "storage/browser/fileapi/file_system_usage_cache.h" | 
|  | #include "storage/common/fileapi/file_system_util.h" | 
|  | #include "third_party/leveldatabase/env_chromium.h" | 
|  | #include "third_party/leveldatabase/src/include/leveldb/db.h" | 
|  | #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | void PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info, | 
|  | base::Pickle* pickle) { | 
|  | DCHECK(pickle); | 
|  | std::string data_path; | 
|  | // Round off here to match the behavior of the filesystem on real files. | 
|  | base::Time time = | 
|  | base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT())); | 
|  | std::string name; | 
|  |  | 
|  | data_path = storage::FilePathToString(info.data_path); | 
|  | name = storage::FilePathToString(base::FilePath(info.name)); | 
|  |  | 
|  | pickle->WriteInt64(info.parent_id); | 
|  | pickle->WriteString(data_path); | 
|  | pickle->WriteString(name); | 
|  | pickle->WriteInt64(time.ToInternalValue()); | 
|  | } | 
|  |  | 
|  | bool FileInfoFromPickle(const base::Pickle& pickle, | 
|  | storage::SandboxDirectoryDatabase::FileInfo* info) { | 
|  | base::PickleIterator iter(pickle); | 
|  | std::string data_path; | 
|  | std::string name; | 
|  | int64_t internal_time; | 
|  |  | 
|  | if (iter.ReadInt64(&info->parent_id) && | 
|  | iter.ReadString(&data_path) && | 
|  | iter.ReadString(&name) && | 
|  | iter.ReadInt64(&internal_time)) { | 
|  | info->data_path = storage::StringToFilePath(data_path); | 
|  | info->name = storage::StringToFilePath(name).value(); | 
|  | info->modification_time = base::Time::FromInternalValue(internal_time); | 
|  | return true; | 
|  | } | 
|  | LOG(ERROR) << "base::Pickle could not be digested!"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const base::FilePath::CharType kDirectoryDatabaseName[] = | 
|  | FILE_PATH_LITERAL("Paths"); | 
|  | const char kChildLookupPrefix[] = "CHILD_OF:"; | 
|  | const char kChildLookupSeparator[] = ":"; | 
|  | const char kLastFileIdKey[] = "LAST_FILE_ID"; | 
|  | const char kLastIntegerKey[] = "LAST_INTEGER"; | 
|  | const int64_t kMinimumReportIntervalHours = 1; | 
|  | const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit"; | 
|  | const char kDatabaseRepairHistogramLabel[] = | 
|  | "FileSystem.DirectoryDatabaseRepair"; | 
|  |  | 
|  | // These values are recorded in UMA. Changing existing values will invalidate | 
|  | // results for older Chrome releases. Only add new values. | 
|  | enum InitStatus { | 
|  | INIT_STATUS_OK = 0, | 
|  | INIT_STATUS_CORRUPTION, | 
|  | INIT_STATUS_IO_ERROR, | 
|  | INIT_STATUS_UNKNOWN_ERROR, | 
|  | INIT_STATUS_MAX | 
|  | }; | 
|  |  | 
|  | // These values are recorded in UMA. Changing existing values will invalidate | 
|  | // results for older Chrome releases. Only add new values. | 
|  | enum RepairResult { | 
|  | DB_REPAIR_SUCCEEDED = 0, | 
|  | DB_REPAIR_FAILED, | 
|  | DB_REPAIR_MAX | 
|  | }; | 
|  |  | 
|  | std::string GetChildLookupKey( | 
|  | storage::SandboxDirectoryDatabase::FileId parent_id, | 
|  | const base::FilePath::StringType& child_name) { | 
|  | std::string name; | 
|  | name = storage::FilePathToString(base::FilePath(child_name)); | 
|  | return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + | 
|  | std::string(kChildLookupSeparator) + name; | 
|  | } | 
|  |  | 
|  | std::string GetChildListingKeyPrefix( | 
|  | storage::SandboxDirectoryDatabase::FileId parent_id) { | 
|  | return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + | 
|  | std::string(kChildLookupSeparator); | 
|  | } | 
|  |  | 
|  | const char* LastFileIdKey() { | 
|  | return kLastFileIdKey; | 
|  | } | 
|  |  | 
|  | const char* LastIntegerKey() { | 
|  | return kLastIntegerKey; | 
|  | } | 
|  |  | 
|  | std::string GetFileLookupKey( | 
|  | storage::SandboxDirectoryDatabase::FileId file_id) { | 
|  | return base::Int64ToString(file_id); | 
|  | } | 
|  |  | 
|  | // Assumptions: | 
|  | //  - Any database entry is one of: | 
|  | //    - ("CHILD_OF:|parent_id|:<name>", "|file_id|"), | 
|  | //    - ("LAST_FILE_ID", "|last_file_id|"), | 
|  | //    - ("LAST_INTEGER", "|last_integer|"), | 
|  | //    - ("|file_id|", "pickled FileInfo") | 
|  | //        where FileInfo has |parent_id|, |data_path|, |name| and | 
|  | //        |modification_time|, | 
|  | // Constraints: | 
|  | //  - Each file in the database has unique backing file. | 
|  | //  - Each file in |filesystem_data_directory_| has a database entry. | 
|  | //  - Directory structure is tree, i.e. connected and acyclic. | 
|  | class DatabaseCheckHelper { | 
|  | public: | 
|  | typedef storage::SandboxDirectoryDatabase::FileId FileId; | 
|  | typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo; | 
|  |  | 
|  | DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db, | 
|  | leveldb::DB* db, | 
|  | const base::FilePath& path); | 
|  |  | 
|  | bool IsFileSystemConsistent() { | 
|  | return IsDatabaseEmpty() || | 
|  | (ScanDatabase() && ScanDirectory() && ScanHierarchy()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool IsDatabaseEmpty(); | 
|  | // These 3 methods need to be called in the order.  Each method requires its | 
|  | // previous method finished successfully. They also require the database is | 
|  | // not empty. | 
|  | bool ScanDatabase(); | 
|  | bool ScanDirectory(); | 
|  | bool ScanHierarchy(); | 
|  |  | 
|  | storage::SandboxDirectoryDatabase* dir_db_; | 
|  | leveldb::DB* db_; | 
|  | base::FilePath path_; | 
|  |  | 
|  | std::set<base::FilePath> files_in_db_; | 
|  |  | 
|  | size_t num_directories_in_db_; | 
|  | size_t num_files_in_db_; | 
|  | size_t num_hierarchy_links_in_db_; | 
|  |  | 
|  | FileId last_file_id_; | 
|  | FileId last_integer_; | 
|  | }; | 
|  |  | 
|  | DatabaseCheckHelper::DatabaseCheckHelper( | 
|  | storage::SandboxDirectoryDatabase* dir_db, | 
|  | leveldb::DB* db, | 
|  | const base::FilePath& path) | 
|  | : dir_db_(dir_db), | 
|  | db_(db), | 
|  | path_(path), | 
|  | num_directories_in_db_(0), | 
|  | num_files_in_db_(0), | 
|  | num_hierarchy_links_in_db_(0), | 
|  | last_file_id_(-1), | 
|  | last_integer_(-1) { | 
|  | DCHECK(dir_db_); | 
|  | DCHECK(db_); | 
|  | DCHECK(!path_.empty() && base::DirectoryExists(path_)); | 
|  | } | 
|  |  | 
|  | bool DatabaseCheckHelper::IsDatabaseEmpty() { | 
|  | std::unique_ptr<leveldb::Iterator> itr( | 
|  | db_->NewIterator(leveldb::ReadOptions())); | 
|  | itr->SeekToFirst(); | 
|  | return !itr->Valid(); | 
|  | } | 
|  |  | 
|  | bool DatabaseCheckHelper::ScanDatabase() { | 
|  | // Scans all database entries sequentially to verify each of them has unique | 
|  | // backing file. | 
|  | int64_t max_file_id = -1; | 
|  | std::set<FileId> file_ids; | 
|  |  | 
|  | std::unique_ptr<leveldb::Iterator> itr( | 
|  | db_->NewIterator(leveldb::ReadOptions())); | 
|  | for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { | 
|  | std::string key = itr->key().ToString(); | 
|  | if (base::StartsWith(key, kChildLookupPrefix, | 
|  | base::CompareCase::SENSITIVE)) { | 
|  | // key: "CHILD_OF:<parent_id>:<name>" | 
|  | // value: "<child_id>" | 
|  | ++num_hierarchy_links_in_db_; | 
|  | } else if (key == kLastFileIdKey) { | 
|  | // key: "LAST_FILE_ID" | 
|  | // value: "<last_file_id>" | 
|  | if (last_file_id_ >= 0 || | 
|  | !base::StringToInt64(itr->value().ToString(), &last_file_id_)) | 
|  | return false; | 
|  |  | 
|  | if (last_file_id_ < 0) | 
|  | return false; | 
|  | } else if (key == kLastIntegerKey) { | 
|  | // key: "LAST_INTEGER" | 
|  | // value: "<last_integer>" | 
|  | if (last_integer_ >= 0 || | 
|  | !base::StringToInt64(itr->value().ToString(), &last_integer_)) | 
|  | return false; | 
|  | } else { | 
|  | // key: "<entry_id>" | 
|  | // value: "<pickled FileInfo>" | 
|  | FileInfo file_info; | 
|  | if (!FileInfoFromPickle( | 
|  | base::Pickle(itr->value().data(), itr->value().size()), | 
|  | &file_info)) | 
|  | return false; | 
|  |  | 
|  | FileId file_id = -1; | 
|  | if (!base::StringToInt64(key, &file_id) || file_id < 0) | 
|  | return false; | 
|  |  | 
|  | if (max_file_id < file_id) | 
|  | max_file_id = file_id; | 
|  | if (!file_ids.insert(file_id).second) | 
|  | return false; | 
|  |  | 
|  | if (file_info.is_directory()) { | 
|  | ++num_directories_in_db_; | 
|  | DCHECK(file_info.data_path.empty()); | 
|  | } else { | 
|  | // Ensure any pair of file entry don't share their data_path. | 
|  | if (!files_in_db_.insert(file_info.data_path).second) | 
|  | return false; | 
|  |  | 
|  | // Ensure the backing file exists as a normal file. | 
|  | base::File::Info platform_file_info; | 
|  | if (!base::GetFileInfo( | 
|  | path_.Append(file_info.data_path), &platform_file_info) || | 
|  | platform_file_info.is_directory || | 
|  | platform_file_info.is_symbolic_link) { | 
|  | // leveldb::Iterator iterates a snapshot of the database. | 
|  | // So even after RemoveFileInfo() call, we'll visit hierarchy link | 
|  | // from |parent_id| to |file_id|. | 
|  | if (!dir_db_->RemoveFileInfo(file_id)) | 
|  | return false; | 
|  | --num_hierarchy_links_in_db_; | 
|  | files_in_db_.erase(file_info.data_path); | 
|  | } else { | 
|  | ++num_files_in_db_; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(tzik): Add constraint for |last_integer_| to avoid possible | 
|  | // data path confliction on ObfuscatedFileUtil. | 
|  | return max_file_id <= last_file_id_; | 
|  | } | 
|  |  | 
|  | bool DatabaseCheckHelper::ScanDirectory() { | 
|  | // TODO(kinuko): Scans all local file system entries to verify each of them | 
|  | // has a database entry. | 
|  | const base::FilePath kExcludes[] = { | 
|  | base::FilePath(kDirectoryDatabaseName), | 
|  | base::FilePath(storage::FileSystemUsageCache::kUsageFileName), | 
|  | }; | 
|  |  | 
|  | // Any path in |pending_directories| is relative to |path_|. | 
|  | base::stack<base::FilePath> pending_directories; | 
|  | pending_directories.push(base::FilePath()); | 
|  |  | 
|  | while (!pending_directories.empty()) { | 
|  | base::FilePath dir_path = pending_directories.top(); | 
|  | pending_directories.pop(); | 
|  |  | 
|  | base::FileEnumerator file_enum( | 
|  | dir_path.empty() ? path_ : path_.Append(dir_path), | 
|  | false /* not recursive */, | 
|  | base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); | 
|  |  | 
|  | base::FilePath absolute_file_path; | 
|  | while (!(absolute_file_path = file_enum.Next()).empty()) { | 
|  | base::FileEnumerator::FileInfo find_info = file_enum.GetInfo(); | 
|  |  | 
|  | base::FilePath relative_file_path; | 
|  | if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path)) | 
|  | return false; | 
|  |  | 
|  | if (std::find(kExcludes, kExcludes + arraysize(kExcludes), | 
|  | relative_file_path) != kExcludes + arraysize(kExcludes)) | 
|  | continue; | 
|  |  | 
|  | if (find_info.IsDirectory()) { | 
|  | pending_directories.push(relative_file_path); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Check if the file has a database entry. | 
|  | std::set<base::FilePath>::iterator itr = | 
|  | files_in_db_.find(relative_file_path); | 
|  | if (itr == files_in_db_.end()) { | 
|  | if (!base::DeleteFile(absolute_file_path, false)) | 
|  | return false; | 
|  | } else { | 
|  | files_in_db_.erase(itr); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return files_in_db_.empty(); | 
|  | } | 
|  |  | 
|  | bool DatabaseCheckHelper::ScanHierarchy() { | 
|  | size_t visited_directories = 0; | 
|  | size_t visited_files = 0; | 
|  | size_t visited_links = 0; | 
|  |  | 
|  | base::stack<FileId> directories; | 
|  | directories.push(0); | 
|  |  | 
|  | // Check if the root directory exists as a directory. | 
|  | FileInfo file_info; | 
|  | if (!dir_db_->GetFileInfo(0, &file_info)) | 
|  | return false; | 
|  | if (file_info.parent_id != 0 || | 
|  | !file_info.is_directory()) | 
|  | return false; | 
|  |  | 
|  | while (!directories.empty()) { | 
|  | ++visited_directories; | 
|  | FileId dir_id = directories.top(); | 
|  | directories.pop(); | 
|  |  | 
|  | std::vector<FileId> children; | 
|  | if (!dir_db_->ListChildren(dir_id, &children)) | 
|  | return false; | 
|  | for (std::vector<FileId>::iterator itr = children.begin(); | 
|  | itr != children.end(); | 
|  | ++itr) { | 
|  | // Any directory must not have root directory as child. | 
|  | if (!*itr) | 
|  | return false; | 
|  |  | 
|  | // Check if the child knows the parent as its parent. | 
|  | FileInfo file_info; | 
|  | if (!dir_db_->GetFileInfo(*itr, &file_info)) | 
|  | return false; | 
|  | if (file_info.parent_id != dir_id) | 
|  | return false; | 
|  |  | 
|  | // Check if the parent knows the name of its child correctly. | 
|  | FileId file_id; | 
|  | if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) || | 
|  | file_id != *itr) | 
|  | return false; | 
|  |  | 
|  | if (file_info.is_directory()) | 
|  | directories.push(*itr); | 
|  | else | 
|  | ++visited_files; | 
|  | ++visited_links; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check if we've visited all database entries. | 
|  | return num_directories_in_db_ == visited_directories && | 
|  | num_files_in_db_ == visited_files && | 
|  | num_hierarchy_links_in_db_ == visited_links; | 
|  | } | 
|  |  | 
|  | // Returns true if the given |data_path| contains no parent references ("..") | 
|  | // and does not refer to special system files. | 
|  | // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to | 
|  | // ensure we're only dealing with valid data paths. | 
|  | bool VerifyDataPath(const base::FilePath& data_path) { | 
|  | // |data_path| should not contain any ".." and should be a relative path | 
|  | // (to the filesystem_data_directory_). | 
|  | if (data_path.ReferencesParent() || data_path.IsAbsolute()) | 
|  | return false; | 
|  | // See if it's not pointing to the special system paths. | 
|  | const base::FilePath kExcludes[] = { | 
|  | base::FilePath(kDirectoryDatabaseName), | 
|  | base::FilePath(storage::FileSystemUsageCache::kUsageFileName), | 
|  | }; | 
|  | for (size_t i = 0; i < arraysize(kExcludes); ++i) { | 
|  | if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path)) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace storage { | 
|  |  | 
|  | SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) { | 
|  | } | 
|  |  | 
|  | SandboxDirectoryDatabase::FileInfo::~FileInfo() { | 
|  | } | 
|  |  | 
|  | SandboxDirectoryDatabase::SandboxDirectoryDatabase( | 
|  | const base::FilePath& filesystem_data_directory, | 
|  | leveldb::Env* env_override) | 
|  | : filesystem_data_directory_(filesystem_data_directory), | 
|  | env_override_(env_override) { | 
|  | } | 
|  |  | 
|  | SandboxDirectoryDatabase::~SandboxDirectoryDatabase() { | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::GetChildWithName( | 
|  | FileId parent_id, | 
|  | const base::FilePath::StringType& name, | 
|  | FileId* child_id) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(child_id); | 
|  | std::string child_key = GetChildLookupKey(parent_id, name); | 
|  | std::string child_id_string; | 
|  | leveldb::Status status = | 
|  | db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | 
|  | if (status.IsNotFound()) | 
|  | return false; | 
|  | if (status.ok()) { | 
|  | if (!base::StringToInt64(child_id_string, child_id)) { | 
|  | LOG(ERROR) << "Hit database corruption!"; | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::GetFileWithPath( | 
|  | const base::FilePath& path, FileId* file_id) { | 
|  | std::vector<base::FilePath::StringType> components; | 
|  | VirtualPath::GetComponents(path, &components); | 
|  | FileId local_id = 0; | 
|  | std::vector<base::FilePath::StringType>::iterator iter; | 
|  | for (iter = components.begin(); iter != components.end(); ++iter) { | 
|  | base::FilePath::StringType name; | 
|  | name = *iter; | 
|  | if (name == FILE_PATH_LITERAL("/")) | 
|  | continue; | 
|  | if (!GetChildWithName(local_id, name, &local_id)) | 
|  | return false; | 
|  | } | 
|  | *file_id = local_id; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::ListChildren( | 
|  | FileId parent_id, std::vector<FileId>* children) { | 
|  | // Check to add later: fail if parent is a file, at least in debug builds. | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(children); | 
|  | std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); | 
|  |  | 
|  | std::unique_ptr<leveldb::Iterator> iter( | 
|  | db_->NewIterator(leveldb::ReadOptions())); | 
|  | iter->Seek(child_key_prefix); | 
|  | children->clear(); | 
|  | while (iter->Valid() && base::StartsWith(iter->key().ToString(), | 
|  | child_key_prefix, | 
|  | base::CompareCase::SENSITIVE)) { | 
|  | std::string child_id_string = iter->value().ToString(); | 
|  | FileId child_id; | 
|  | if (!base::StringToInt64(child_id_string, &child_id)) { | 
|  | LOG(ERROR) << "Hit database corruption!"; | 
|  | return false; | 
|  | } | 
|  | children->push_back(child_id); | 
|  | iter->Next(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(info); | 
|  | std::string file_key = GetFileLookupKey(file_id); | 
|  | std::string file_data_string; | 
|  | leveldb::Status status = | 
|  | db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); | 
|  | if (status.ok()) { | 
|  | bool success = FileInfoFromPickle( | 
|  | base::Pickle(file_data_string.data(), file_data_string.length()), info); | 
|  | if (!success) | 
|  | return false; | 
|  | if (!VerifyDataPath(info->data_path)) { | 
|  | LOG(ERROR) << "Resolved data path is invalid: " | 
|  | << info->data_path.value(); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | // Special-case the root, for databases that haven't been initialized yet. | 
|  | // Without this, a query for the root's file info, made before creating the | 
|  | // first file in the database, will fail and confuse callers. | 
|  | if (status.IsNotFound() && !file_id) { | 
|  | info->name = base::FilePath::StringType(); | 
|  | info->data_path = base::FilePath(); | 
|  | info->modification_time = base::Time::Now(); | 
|  | info->parent_id = 0; | 
|  | return true; | 
|  | } | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | base::File::Error SandboxDirectoryDatabase::AddFileInfo( | 
|  | const FileInfo& info, FileId* file_id) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return base::File::FILE_ERROR_FAILED; | 
|  | DCHECK(file_id); | 
|  | std::string child_key = GetChildLookupKey(info.parent_id, info.name); | 
|  | std::string child_id_string; | 
|  | leveldb::Status status = | 
|  | db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | 
|  | if (status.ok()) { | 
|  | LOG(ERROR) << "File exists already!"; | 
|  | return base::File::FILE_ERROR_EXISTS; | 
|  | } | 
|  | if (!status.IsNotFound()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return base::File::FILE_ERROR_NOT_FOUND; | 
|  | } | 
|  |  | 
|  | if (!IsDirectory(info.parent_id)) { | 
|  | LOG(ERROR) << "New parent directory is a file!"; | 
|  | return base::File::FILE_ERROR_NOT_A_DIRECTORY; | 
|  | } | 
|  |  | 
|  | // This would be a fine place to limit the number of files in a directory, if | 
|  | // we decide to add that restriction. | 
|  |  | 
|  | FileId temp_id; | 
|  | if (!GetLastFileId(&temp_id)) | 
|  | return base::File::FILE_ERROR_FAILED; | 
|  | ++temp_id; | 
|  |  | 
|  | leveldb::WriteBatch batch; | 
|  | if (!AddFileInfoHelper(info, temp_id, &batch)) | 
|  | return base::File::FILE_ERROR_FAILED; | 
|  |  | 
|  | batch.Put(LastFileIdKey(), base::Int64ToString(temp_id)); | 
|  | status = db_->Write(leveldb::WriteOptions(), &batch); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return base::File::FILE_ERROR_FAILED; | 
|  | } | 
|  | *file_id = temp_id; | 
|  | return base::File::FILE_OK; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | leveldb::WriteBatch batch; | 
|  | if (!RemoveFileInfoHelper(file_id, &batch)) | 
|  | return false; | 
|  | leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::UpdateFileInfo( | 
|  | FileId file_id, const FileInfo& new_info) { | 
|  | // TODO(ericu): We should also check to see that this doesn't create a loop, | 
|  | // but perhaps only in a debug build. | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB. | 
|  | FileInfo old_info; | 
|  | if (!GetFileInfo(file_id, &old_info)) | 
|  | return false; | 
|  | if (old_info.parent_id != new_info.parent_id && | 
|  | !IsDirectory(new_info.parent_id)) | 
|  | return false; | 
|  | if (old_info.parent_id != new_info.parent_id || | 
|  | old_info.name != new_info.name) { | 
|  | // Check for name clashes. | 
|  | FileId temp_id; | 
|  | if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) { | 
|  | LOG(ERROR) << "Name collision on move."; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | leveldb::WriteBatch batch; | 
|  | if (!RemoveFileInfoHelper(file_id, &batch) || | 
|  | !AddFileInfoHelper(new_info, file_id, &batch)) | 
|  | return false; | 
|  | leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::UpdateModificationTime( | 
|  | FileId file_id, const base::Time& modification_time) { | 
|  | FileInfo info; | 
|  | if (!GetFileInfo(file_id, &info)) | 
|  | return false; | 
|  | info.modification_time = modification_time; | 
|  | base::Pickle pickle; | 
|  | PickleFromFileInfo(info, &pickle); | 
|  | leveldb::Status status = db_->Put( | 
|  | leveldb::WriteOptions(), | 
|  | GetFileLookupKey(file_id), | 
|  | leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | 
|  | pickle.size())); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::OverwritingMoveFile( | 
|  | FileId src_file_id, FileId dest_file_id) { | 
|  | FileInfo src_file_info; | 
|  | FileInfo dest_file_info; | 
|  |  | 
|  | if (!GetFileInfo(src_file_id, &src_file_info)) | 
|  | return false; | 
|  | if (!GetFileInfo(dest_file_id, &dest_file_info)) | 
|  | return false; | 
|  | if (src_file_info.is_directory() || dest_file_info.is_directory()) | 
|  | return false; | 
|  | leveldb::WriteBatch batch; | 
|  | // This is the only field that really gets moved over; if you add fields to | 
|  | // FileInfo, e.g. ctime, they might need to be copied here. | 
|  | dest_file_info.data_path = src_file_info.data_path; | 
|  | if (!RemoveFileInfoHelper(src_file_id, &batch)) | 
|  | return false; | 
|  | base::Pickle pickle; | 
|  | PickleFromFileInfo(dest_file_info, &pickle); | 
|  | batch.Put( | 
|  | GetFileLookupKey(dest_file_id), | 
|  | leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | 
|  | pickle.size())); | 
|  | leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::GetNextInteger(int64_t* next) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(next); | 
|  | std::string int_string; | 
|  | leveldb::Status status = | 
|  | db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); | 
|  | if (status.ok()) { | 
|  | int64_t temp; | 
|  | if (!base::StringToInt64(int_string, &temp)) { | 
|  | LOG(ERROR) << "Hit database corruption!"; | 
|  | return false; | 
|  | } | 
|  | ++temp; | 
|  | status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(), | 
|  | base::Int64ToString(temp)); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | *next = temp; | 
|  | return true; | 
|  | } | 
|  | if (!status.IsNotFound()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | // The database must not yet exist; initialize it. | 
|  | if (!StoreDefaultValues()) | 
|  | return false; | 
|  |  | 
|  | return GetNextInteger(next); | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::DestroyDatabase() { | 
|  | db_.reset(); | 
|  | const std::string path = | 
|  | FilePathToString(filesystem_data_directory_.Append( | 
|  | kDirectoryDatabaseName)); | 
|  | leveldb_env::Options options; | 
|  | if (env_override_) | 
|  | options.env = env_override_; | 
|  | leveldb::Status status = leveldb::DestroyDB(path, options); | 
|  | if (status.ok()) | 
|  | return true; | 
|  | LOG(WARNING) << "Failed to destroy a database with status " << | 
|  | status.ToString(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) { | 
|  | if (db_) | 
|  | return true; | 
|  |  | 
|  | std::string path = | 
|  | FilePathToString(filesystem_data_directory_.Append( | 
|  | kDirectoryDatabaseName)); | 
|  | leveldb_env::Options options; | 
|  | options.max_open_files = 0;  // Use minimum. | 
|  | options.create_if_missing = true; | 
|  | if (env_override_) | 
|  | options.env = env_override_; | 
|  | leveldb::Status status = leveldb_env::OpenDB(options, path, &db_); | 
|  | ReportInitStatus(status); | 
|  | if (status.ok()) { | 
|  | return true; | 
|  | } | 
|  | HandleError(FROM_HERE, status); | 
|  |  | 
|  | // Corruption due to missing necessary MANIFEST-* file causes IOError instead | 
|  | // of Corruption error. | 
|  | // Try to repair database even when IOError case. | 
|  | if (!status.IsCorruption() && !status.IsIOError()) | 
|  | return false; | 
|  |  | 
|  | switch (recovery_option) { | 
|  | case FAIL_ON_CORRUPTION: | 
|  | return false; | 
|  | case REPAIR_ON_CORRUPTION: | 
|  | LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected." | 
|  | << " Attempting to repair."; | 
|  | if (RepairDatabase(path)) { | 
|  | UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | 
|  | DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); | 
|  | return true; | 
|  | } | 
|  | UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | 
|  | DB_REPAIR_FAILED, DB_REPAIR_MAX); | 
|  | LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase."; | 
|  | // fall through | 
|  | case DELETE_ON_CORRUPTION: | 
|  | LOG(WARNING) << "Clearing SandboxDirectoryDatabase."; | 
|  | if (!base::DeleteFile(filesystem_data_directory_, true)) | 
|  | return false; | 
|  | if (!base::CreateDirectory(filesystem_data_directory_)) | 
|  | return false; | 
|  | return Init(FAIL_ON_CORRUPTION); | 
|  | } | 
|  |  | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) { | 
|  | DCHECK(!db_.get()); | 
|  | leveldb_env::Options options; | 
|  | options.reuse_logs = false; | 
|  | options.max_open_files = 0;  // Use minimum. | 
|  | if (env_override_) | 
|  | options.env = env_override_; | 
|  | if (!leveldb::RepairDB(db_path, options).ok()) | 
|  | return false; | 
|  | if (!Init(FAIL_ON_CORRUPTION)) | 
|  | return false; | 
|  | if (IsFileSystemConsistent()) | 
|  | return true; | 
|  | db_.reset(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) { | 
|  | FileInfo info; | 
|  | if (!file_id) | 
|  | return true;  // The root is a directory. | 
|  | if (!GetFileInfo(file_id, &info)) | 
|  | return false; | 
|  | if (!info.is_directory()) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::IsFileSystemConsistent() { | 
|  | if (!Init(FAIL_ON_CORRUPTION)) | 
|  | return false; | 
|  | DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_); | 
|  | return helper.IsFileSystemConsistent(); | 
|  | } | 
|  |  | 
|  | void SandboxDirectoryDatabase::ReportInitStatus( | 
|  | const leveldb::Status& status) { | 
|  | base::Time now = base::Time::Now(); | 
|  | const base::TimeDelta minimum_interval = | 
|  | base::TimeDelta::FromHours(kMinimumReportIntervalHours); | 
|  | if (last_reported_time_ + minimum_interval >= now) | 
|  | return; | 
|  | last_reported_time_ = now; | 
|  |  | 
|  | if (status.ok()) { | 
|  | UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | 
|  | INIT_STATUS_OK, INIT_STATUS_MAX); | 
|  | } else if (status.IsCorruption()) { | 
|  | UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | 
|  | INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); | 
|  | } else if (status.IsIOError()) { | 
|  | UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | 
|  | INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); | 
|  | } else { | 
|  | UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | 
|  | INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::StoreDefaultValues() { | 
|  | // Verify that this is a totally new database, and initialize it. | 
|  | { | 
|  | // Scope the iterator to ensure deleted before database is closed. | 
|  | std::unique_ptr<leveldb::Iterator> iter( | 
|  | db_->NewIterator(leveldb::ReadOptions())); | 
|  | iter->SeekToFirst(); | 
|  | if (iter->Valid()) {  // DB was not empty--we shouldn't have been called. | 
|  | LOG(ERROR) << "File system origin database is corrupt!"; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | // This is always the first write into the database.  If we ever add a | 
|  | // version number, it should go in this transaction too. | 
|  | FileInfo root; | 
|  | root.parent_id = 0; | 
|  | root.modification_time = base::Time::Now(); | 
|  | leveldb::WriteBatch batch; | 
|  | if (!AddFileInfoHelper(root, 0, &batch)) | 
|  | return false; | 
|  | batch.Put(LastFileIdKey(), base::Int64ToString(0)); | 
|  | batch.Put(LastIntegerKey(), base::Int64ToString(-1)); | 
|  | leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 
|  | if (!status.ok()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) { | 
|  | if (!Init(REPAIR_ON_CORRUPTION)) | 
|  | return false; | 
|  | DCHECK(file_id); | 
|  | std::string id_string; | 
|  | leveldb::Status status = | 
|  | db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); | 
|  | if (status.ok()) { | 
|  | if (!base::StringToInt64(id_string, file_id)) { | 
|  | LOG(ERROR) << "Hit database corruption!"; | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | if (!status.IsNotFound()) { | 
|  | HandleError(FROM_HERE, status); | 
|  | return false; | 
|  | } | 
|  | // The database must not yet exist; initialize it. | 
|  | if (!StoreDefaultValues()) | 
|  | return false; | 
|  | *file_id = 0; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This does very few safety checks! | 
|  | bool SandboxDirectoryDatabase::AddFileInfoHelper( | 
|  | const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) { | 
|  | if (!VerifyDataPath(info.data_path)) { | 
|  | LOG(ERROR) << "Invalid data path is given: " << info.data_path.value(); | 
|  | return false; | 
|  | } | 
|  | std::string id_string = GetFileLookupKey(file_id); | 
|  | if (!file_id) { | 
|  | // The root directory doesn't need to be looked up by path from its parent. | 
|  | DCHECK(!info.parent_id); | 
|  | DCHECK(info.data_path.empty()); | 
|  | } else { | 
|  | std::string child_key = GetChildLookupKey(info.parent_id, info.name); | 
|  | batch->Put(child_key, id_string); | 
|  | } | 
|  | base::Pickle pickle; | 
|  | PickleFromFileInfo(info, &pickle); | 
|  | batch->Put( | 
|  | id_string, | 
|  | leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | 
|  | pickle.size())); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This does very few safety checks! | 
|  | bool SandboxDirectoryDatabase::RemoveFileInfoHelper( | 
|  | FileId file_id, leveldb::WriteBatch* batch) { | 
|  | DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB. | 
|  | FileInfo info; | 
|  | if (!GetFileInfo(file_id, &info)) | 
|  | return false; | 
|  | if (info.data_path.empty()) {  // It's a directory | 
|  | std::vector<FileId> children; | 
|  | // TODO(ericu): Make a faster is-the-directory-empty check. | 
|  | if (!ListChildren(file_id, &children)) | 
|  | return false; | 
|  | if (children.size()) { | 
|  | LOG(ERROR) << "Can't remove a directory with children."; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | batch->Delete(GetChildLookupKey(info.parent_id, info.name)); | 
|  | batch->Delete(GetFileLookupKey(file_id)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void SandboxDirectoryDatabase::HandleError(const base::Location& from_here, | 
|  | const leveldb::Status& status) { | 
|  | LOG(ERROR) << "SandboxDirectoryDatabase failed at: " | 
|  | << from_here.ToString() << " with error: " << status.ToString(); | 
|  | db_.reset(); | 
|  | } | 
|  |  | 
|  | }  // namespace storage |