Home | History | Annotate | Download | only in fileapi
      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.
      4 
      5 #include "webkit/browser/fileapi/sandbox_directory_database.h"
      6 
      7 #include <math.h>
      8 #include <algorithm>
      9 #include <set>
     10 #include <stack>
     11 
     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"
     23 
     24 namespace {
     25 
     26 bool PickleFromFileInfo(
     27     const fileapi::SandboxDirectoryDatabase::FileInfo& info,
     28     Pickle* pickle) {
     29   DCHECK(pickle);
     30   std::string data_path;
     31   // Round off here to match the behavior of the filesystem on real files.
     32   base::Time time =
     33       base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
     34   std::string name;
     35 
     36   data_path = fileapi::FilePathToString(info.data_path);
     37   name = fileapi::FilePathToString(base::FilePath(info.name));
     38 
     39   if (pickle->WriteInt64(info.parent_id) &&
     40       pickle->WriteString(data_path) &&
     41       pickle->WriteString(name) &&
     42       pickle->WriteInt64(time.ToInternalValue()))
     43     return true;
     44 
     45   NOTREACHED();
     46   return false;
     47 }
     48 
     49 bool FileInfoFromPickle(
     50     const Pickle& pickle,
     51     fileapi::SandboxDirectoryDatabase::FileInfo* info) {
     52   PickleIterator iter(pickle);
     53   std::string data_path;
     54   std::string name;
     55   int64 internal_time;
     56 
     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);
     64     return true;
     65   }
     66   LOG(ERROR) << "Pickle could not be digested!";
     67   return false;
     68 }
     69 
     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";
     80 
     81 enum InitStatus {
     82   INIT_STATUS_OK = 0,
     83   INIT_STATUS_CORRUPTION,
     84   INIT_STATUS_IO_ERROR,
     85   INIT_STATUS_UNKNOWN_ERROR,
     86   INIT_STATUS_MAX
     87 };
     88 
     89 enum RepairResult {
     90   DB_REPAIR_SUCCEEDED = 0,
     91   DB_REPAIR_FAILED,
     92   DB_REPAIR_MAX
     93 };
     94 
     95 std::string GetChildLookupKey(
     96     fileapi::SandboxDirectoryDatabase::FileId parent_id,
     97     const base::FilePath::StringType& child_name) {
     98   std::string name;
     99   name = fileapi::FilePathToString(base::FilePath(child_name));
    100   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
    101       std::string(kChildLookupSeparator) + name;
    102 }
    103 
    104 std::string GetChildListingKeyPrefix(
    105     fileapi::SandboxDirectoryDatabase::FileId parent_id) {
    106   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
    107       std::string(kChildLookupSeparator);
    108 }
    109 
    110 const char* LastFileIdKey() {
    111   return kLastFileIdKey;
    112 }
    113 
    114 const char* LastIntegerKey() {
    115   return kLastIntegerKey;
    116 }
    117 
    118 std::string GetFileLookupKey(
    119     fileapi::SandboxDirectoryDatabase::FileId file_id) {
    120   return base::Int64ToString(file_id);
    121 }
    122 
    123 // Assumptions:
    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|,
    131 // Constraints:
    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 {
    136  public:
    137   typedef fileapi::SandboxDirectoryDatabase::FileId FileId;
    138   typedef fileapi::SandboxDirectoryDatabase::FileInfo FileInfo;
    139 
    140   DatabaseCheckHelper(fileapi::SandboxDirectoryDatabase* dir_db,
    141                       leveldb::DB* db,
    142                       const base::FilePath& path);
    143 
    144   bool IsFileSystemConsistent() {
    145     return IsDatabaseEmpty() ||
    146         (ScanDatabase() && ScanDirectory() && ScanHierarchy());
    147   }
    148 
    149  private:
    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
    153   // not empty.
    154   bool ScanDatabase();
    155   bool ScanDirectory();
    156   bool ScanHierarchy();
    157 
    158   fileapi::SandboxDirectoryDatabase* dir_db_;
    159   leveldb::DB* db_;
    160   base::FilePath path_;
    161 
    162   std::set<base::FilePath> files_in_db_;
    163 
    164   size_t num_directories_in_db_;
    165   size_t num_files_in_db_;
    166   size_t num_hierarchy_links_in_db_;
    167 
    168   FileId last_file_id_;
    169   FileId last_integer_;
    170 };
    171 
    172 DatabaseCheckHelper::DatabaseCheckHelper(
    173     fileapi::SandboxDirectoryDatabase* dir_db,
    174     leveldb::DB* db,
    175     const base::FilePath& path)
    176     : dir_db_(dir_db), db_(db), path_(path),
    177       num_directories_in_db_(0),
    178       num_files_in_db_(0),
    179       num_hierarchy_links_in_db_(0),
    180       last_file_id_(-1), last_integer_(-1) {
    181   DCHECK(dir_db_);
    182   DCHECK(db_);
    183   DCHECK(!path_.empty() && base::DirectoryExists(path_));
    184 }
    185 
    186 bool DatabaseCheckHelper::IsDatabaseEmpty() {
    187   scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
    188   itr->SeekToFirst();
    189   return !itr->Valid();
    190 }
    191 
    192 bool DatabaseCheckHelper::ScanDatabase() {
    193   // Scans all database entries sequentially to verify each of them has unique
    194   // backing file.
    195   int64 max_file_id = -1;
    196   std::set<FileId> file_ids;
    197 
    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_))
    210         return false;
    211 
    212       if (last_file_id_ < 0)
    213         return false;
    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_))
    219         return false;
    220     } else {
    221       // key: "<entry_id>"
    222       // value: "<pickled FileInfo>"
    223       FileInfo file_info;
    224       if (!FileInfoFromPickle(
    225               Pickle(itr->value().data(), itr->value().size()), &file_info))
    226         return false;
    227 
    228       FileId file_id = -1;
    229       if (!base::StringToInt64(key, &file_id) || file_id < 0)
    230         return false;
    231 
    232       if (max_file_id < file_id)
    233         max_file_id = file_id;
    234       if (!file_ids.insert(file_id).second)
    235         return false;
    236 
    237       if (file_info.is_directory()) {
    238         ++num_directories_in_db_;
    239         DCHECK(file_info.data_path.empty());
    240       } else {
    241         // Ensure any pair of file entry don't share their data_path.
    242         if (!files_in_db_.insert(file_info.data_path).second)
    243           return false;
    244 
    245         // Ensure the backing file exists as a normal file.
    246         base::PlatformFileInfo platform_file_info;
    247         if (!base::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))
    255             return false;
    256           --num_hierarchy_links_in_db_;
    257           files_in_db_.erase(file_info.data_path);
    258         } else {
    259           ++num_files_in_db_;
    260         }
    261       }
    262     }
    263   }
    264 
    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_;
    268 }
    269 
    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),
    276   };
    277 
    278   // Any path in |pending_directories| is relative to |path_|.
    279   std::stack<base::FilePath> pending_directories;
    280   pending_directories.push(base::FilePath());
    281 
    282   while (!pending_directories.empty()) {
    283     base::FilePath dir_path = pending_directories.top();
    284     pending_directories.pop();
    285 
    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);
    290 
    291     base::FilePath absolute_file_path;
    292     while (!(absolute_file_path = file_enum.Next()).empty()) {
    293       base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
    294 
    295       base::FilePath relative_file_path;
    296       if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
    297         return false;
    298 
    299       if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
    300                     relative_file_path) != kExcludes + arraysize(kExcludes))
    301         continue;
    302 
    303       if (find_info.IsDirectory()) {
    304         pending_directories.push(relative_file_path);
    305         continue;
    306       }
    307 
    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))
    313           return false;
    314       } else {
    315         files_in_db_.erase(itr);
    316       }
    317     }
    318   }
    319 
    320   return files_in_db_.empty();
    321 }
    322 
    323 bool DatabaseCheckHelper::ScanHierarchy() {
    324   size_t visited_directories = 0;
    325   size_t visited_files = 0;
    326   size_t visited_links = 0;
    327 
    328   std::stack<FileId> directories;
    329   directories.push(0);
    330 
    331   // Check if the root directory exists as a directory.
    332   FileInfo file_info;
    333   if (!dir_db_->GetFileInfo(0, &file_info))
    334     return false;
    335   if (file_info.parent_id != 0 ||
    336       !file_info.is_directory())
    337     return false;
    338 
    339   while (!directories.empty()) {
    340     ++visited_directories;
    341     FileId dir_id = directories.top();
    342     directories.pop();
    343 
    344     std::vector<FileId> children;
    345     if (!dir_db_->ListChildren(dir_id, &children))
    346       return false;
    347     for (std::vector<FileId>::iterator itr = children.begin();
    348          itr != children.end();
    349          ++itr) {
    350       // Any directory must not have root directory as child.
    351       if (!*itr)
    352         return false;
    353 
    354       // Check if the child knows the parent as its parent.
    355       FileInfo file_info;
    356       if (!dir_db_->GetFileInfo(*itr, &file_info))
    357         return false;
    358       if (file_info.parent_id != dir_id)
    359         return false;
    360 
    361       // Check if the parent knows the name of its child correctly.
    362       FileId file_id;
    363       if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
    364           file_id != *itr)
    365         return false;
    366 
    367       if (file_info.is_directory())
    368         directories.push(*itr);
    369       else
    370         ++visited_files;
    371       ++visited_links;
    372     }
    373   }
    374 
    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;
    379 }
    380 
    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())
    389     return false;
    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),
    394   };
    395   for (size_t i = 0; i < arraysize(kExcludes); ++i) {
    396     if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
    397       return false;
    398   }
    399   return true;
    400 }
    401 
    402 }  // namespace
    403 
    404 namespace fileapi {
    405 
    406 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
    407 }
    408 
    409 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
    410 }
    411 
    412 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
    413     const base::FilePath& filesystem_data_directory)
    414     : filesystem_data_directory_(filesystem_data_directory) {
    415 }
    416 
    417 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
    418 }
    419 
    420 bool SandboxDirectoryDatabase::GetChildWithName(
    421     FileId parent_id,
    422     const base::FilePath::StringType& name,
    423     FileId* child_id) {
    424   if (!Init(REPAIR_ON_CORRUPTION))
    425     return false;
    426   DCHECK(child_id);
    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())
    432     return false;
    433   if (status.ok()) {
    434     if (!base::StringToInt64(child_id_string, child_id)) {
    435       LOG(ERROR) << "Hit database corruption!";
    436       return false;
    437     }
    438     return true;
    439   }
    440   HandleError(FROM_HERE, status);
    441   return false;
    442 }
    443 
    444 bool SandboxDirectoryDatabase::GetFileWithPath(
    445     const base::FilePath& path, FileId* file_id) {
    446   std::vector<base::FilePath::StringType> components;
    447   VirtualPath::GetComponents(path, &components);
    448   FileId local_id = 0;
    449   std::vector<base::FilePath::StringType>::iterator iter;
    450   for (iter = components.begin(); iter != components.end(); ++iter) {
    451     base::FilePath::StringType name;
    452     name = *iter;
    453     if (name == FILE_PATH_LITERAL("/"))
    454       continue;
    455     if (!GetChildWithName(local_id, name, &local_id))
    456       return false;
    457   }
    458   *file_id = local_id;
    459   return true;
    460 }
    461 
    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))
    466     return false;
    467   DCHECK(children);
    468   std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
    469 
    470   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    471   iter->Seek(child_key_prefix);
    472   children->clear();
    473   while (iter->Valid() &&
    474       StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
    475     std::string child_id_string = iter->value().ToString();
    476     FileId child_id;
    477     if (!base::StringToInt64(child_id_string, &child_id)) {
    478       LOG(ERROR) << "Hit database corruption!";
    479       return false;
    480     }
    481     children->push_back(child_id);
    482     iter->Next();
    483   }
    484   return true;
    485 }
    486 
    487 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
    488   if (!Init(REPAIR_ON_CORRUPTION))
    489     return false;
    490   DCHECK(info);
    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);
    495   if (status.ok()) {
    496     bool success = FileInfoFromPickle(
    497         Pickle(file_data_string.data(), file_data_string.length()), info);
    498     if (!success)
    499       return false;
    500     if (!VerifyDataPath(info->data_path)) {
    501       LOG(ERROR) << "Resolved data path is invalid: "
    502                  << info->data_path.value();
    503       return false;
    504     }
    505     return true;
    506   }
    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();
    514     info->parent_id = 0;
    515     return true;
    516   }
    517   HandleError(FROM_HERE, status);
    518   return false;
    519 }
    520 
    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;
    525   DCHECK(file_id);
    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);
    530   if (status.ok()) {
    531     LOG(ERROR) << "File exists already!";
    532     return base::PLATFORM_FILE_ERROR_EXISTS;
    533   }
    534   if (!status.IsNotFound()) {
    535     HandleError(FROM_HERE, status);
    536     return base::PLATFORM_FILE_ERROR_NOT_FOUND;
    537   }
    538 
    539   if (!IsDirectory(info.parent_id)) {
    540     LOG(ERROR) << "New parent directory is a file!";
    541     return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
    542   }
    543 
    544   // This would be a fine place to limit the number of files in a directory, if
    545   // we decide to add that restriction.
    546 
    547   FileId temp_id;
    548   if (!GetLastFileId(&temp_id))
    549     return base::PLATFORM_FILE_ERROR_FAILED;
    550   ++temp_id;
    551 
    552   leveldb::WriteBatch batch;
    553   if (!AddFileInfoHelper(info, temp_id, &batch))
    554     return base::PLATFORM_FILE_ERROR_FAILED;
    555 
    556   batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
    557   status = db_->Write(leveldb::WriteOptions(), &batch);
    558   if (!status.ok()) {
    559     HandleError(FROM_HERE, status);
    560     return base::PLATFORM_FILE_ERROR_FAILED;
    561   }
    562   *file_id = temp_id;
    563   return base::PLATFORM_FILE_OK;
    564 }
    565 
    566 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
    567   if (!Init(REPAIR_ON_CORRUPTION))
    568     return false;
    569   leveldb::WriteBatch batch;
    570   if (!RemoveFileInfoHelper(file_id, &batch))
    571     return false;
    572   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    573   if (!status.ok()) {
    574     HandleError(FROM_HERE, status);
    575     return false;
    576   }
    577   return true;
    578 }
    579 
    580 bool SandboxDirectoryDatabase::UpdateFileInfo(
    581     FileId file_id, const FileInfo& new_info) {
    582   // TODO(ericu): We should also check to see that this doesn't create a loop,
    583   // but perhaps only in a debug build.
    584   if (!Init(REPAIR_ON_CORRUPTION))
    585     return false;
    586   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
    587   FileInfo old_info;
    588   if (!GetFileInfo(file_id, &old_info))
    589     return false;
    590   if (old_info.parent_id != new_info.parent_id &&
    591       !IsDirectory(new_info.parent_id))
    592     return false;
    593   if (old_info.parent_id != new_info.parent_id ||
    594       old_info.name != new_info.name) {
    595     // Check for name clashes.
    596     FileId temp_id;
    597     if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
    598       LOG(ERROR) << "Name collision on move.";
    599       return false;
    600     }
    601   }
    602   leveldb::WriteBatch batch;
    603   if (!RemoveFileInfoHelper(file_id, &batch) ||
    604       !AddFileInfoHelper(new_info, file_id, &batch))
    605     return false;
    606   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    607   if (!status.ok()) {
    608     HandleError(FROM_HERE, status);
    609     return false;
    610   }
    611   return true;
    612 }
    613 
    614 bool SandboxDirectoryDatabase::UpdateModificationTime(
    615     FileId file_id, const base::Time& modification_time) {
    616   FileInfo info;
    617   if (!GetFileInfo(file_id, &info))
    618     return false;
    619   info.modification_time = modification_time;
    620   Pickle pickle;
    621   if (!PickleFromFileInfo(info, &pickle))
    622     return false;
    623   leveldb::Status status = db_->Put(
    624       leveldb::WriteOptions(),
    625       GetFileLookupKey(file_id),
    626       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    627                      pickle.size()));
    628   if (!status.ok()) {
    629     HandleError(FROM_HERE, status);
    630     return false;
    631   }
    632   return true;
    633 }
    634 
    635 bool SandboxDirectoryDatabase::OverwritingMoveFile(
    636     FileId src_file_id, FileId dest_file_id) {
    637   FileInfo src_file_info;
    638   FileInfo dest_file_info;
    639 
    640   if (!GetFileInfo(src_file_id, &src_file_info))
    641     return false;
    642   if (!GetFileInfo(dest_file_id, &dest_file_info))
    643     return false;
    644   if (src_file_info.is_directory() || dest_file_info.is_directory())
    645     return false;
    646   leveldb::WriteBatch batch;
    647   // This is the only field that really gets moved over; if you add fields to
    648   // FileInfo, e.g. ctime, they might need to be copied here.
    649   dest_file_info.data_path = src_file_info.data_path;
    650   if (!RemoveFileInfoHelper(src_file_id, &batch))
    651     return false;
    652   Pickle pickle;
    653   if (!PickleFromFileInfo(dest_file_info, &pickle))
    654     return false;
    655   batch.Put(
    656       GetFileLookupKey(dest_file_id),
    657       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    658                      pickle.size()));
    659   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    660   if (!status.ok()) {
    661     HandleError(FROM_HERE, status);
    662     return false;
    663   }
    664   return true;
    665 }
    666 
    667 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
    668   if (!Init(REPAIR_ON_CORRUPTION))
    669     return false;
    670   DCHECK(next);
    671   std::string int_string;
    672   leveldb::Status status =
    673       db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
    674   if (status.ok()) {
    675     int64 temp;
    676     if (!base::StringToInt64(int_string, &temp)) {
    677       LOG(ERROR) << "Hit database corruption!";
    678       return false;
    679     }
    680     ++temp;
    681     status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
    682         base::Int64ToString(temp));
    683     if (!status.ok()) {
    684       HandleError(FROM_HERE, status);
    685       return false;
    686     }
    687     *next = temp;
    688     return true;
    689   }
    690   if (!status.IsNotFound()) {
    691     HandleError(FROM_HERE, status);
    692     return false;
    693   }
    694   // The database must not yet exist; initialize it.
    695   if (!StoreDefaultValues())
    696     return false;
    697 
    698   return GetNextInteger(next);
    699 }
    700 
    701 // static
    702 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path) {
    703   std::string name  = FilePathToString(path.Append(kDirectoryDatabaseName));
    704   leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options());
    705   if (status.ok())
    706     return true;
    707   LOG(WARNING) << "Failed to destroy a database with status " <<
    708       status.ToString();
    709   return false;
    710 }
    711 
    712 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
    713   if (db_)
    714     return true;
    715 
    716   std::string path =
    717       FilePathToString(filesystem_data_directory_.Append(
    718           kDirectoryDatabaseName));
    719   leveldb::Options options;
    720   options.max_open_files = 0;  // Use minimum.
    721   options.create_if_missing = true;
    722   leveldb::DB* db;
    723   leveldb::Status status = leveldb::DB::Open(options, path, &db);
    724   ReportInitStatus(status);
    725   if (status.ok()) {
    726     db_.reset(db);
    727     return true;
    728   }
    729   HandleError(FROM_HERE, status);
    730 
    731   // Corruption due to missing necessary MANIFEST-* file causes IOError instead
    732   // of Corruption error.
    733   // Try to repair database even when IOError case.
    734   if (!status.IsCorruption() && !status.IsIOError())
    735     return false;
    736 
    737   switch (recovery_option) {
    738     case FAIL_ON_CORRUPTION:
    739       return false;
    740     case REPAIR_ON_CORRUPTION:
    741       LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
    742                    << " Attempting to repair.";
    743       if (RepairDatabase(path)) {
    744         UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    745                                   DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
    746         return true;
    747       }
    748       UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    749                                 DB_REPAIR_FAILED, DB_REPAIR_MAX);
    750       LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
    751       // fall through
    752     case DELETE_ON_CORRUPTION:
    753       LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
    754       if (!base::DeleteFile(filesystem_data_directory_, true))
    755         return false;
    756       if (!base::CreateDirectory(filesystem_data_directory_))
    757         return false;
    758       return Init(FAIL_ON_CORRUPTION);
    759   }
    760 
    761   NOTREACHED();
    762   return false;
    763 }
    764 
    765 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
    766   DCHECK(!db_.get());
    767   leveldb::Options options;
    768   options.max_open_files = 0;  // Use minimum.
    769   if (!leveldb::RepairDB(db_path, options).ok())
    770     return false;
    771   if (!Init(FAIL_ON_CORRUPTION))
    772     return false;
    773   if (IsFileSystemConsistent())
    774     return true;
    775   db_.reset();
    776   return false;
    777 }
    778 
    779 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
    780   FileInfo info;
    781   if (!file_id)
    782     return true;  // The root is a directory.
    783   if (!GetFileInfo(file_id, &info))
    784     return false;
    785   if (!info.is_directory())
    786     return false;
    787   return true;
    788 }
    789 
    790 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
    791   if (!Init(FAIL_ON_CORRUPTION))
    792     return false;
    793   DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
    794   return helper.IsFileSystemConsistent();
    795 }
    796 
    797 void SandboxDirectoryDatabase::ReportInitStatus(
    798     const leveldb::Status& status) {
    799   base::Time now = base::Time::Now();
    800   const base::TimeDelta minimum_interval =
    801       base::TimeDelta::FromHours(kMinimumReportIntervalHours);
    802   if (last_reported_time_ + minimum_interval >= now)
    803     return;
    804   last_reported_time_ = now;
    805 
    806   if (status.ok()) {
    807     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    808                               INIT_STATUS_OK, INIT_STATUS_MAX);
    809   } else if (status.IsCorruption()) {
    810     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    811                               INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
    812   } else if (status.IsIOError()) {
    813     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    814                               INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
    815   } else {
    816     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    817                               INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
    818   }
    819 }
    820 
    821 bool SandboxDirectoryDatabase::StoreDefaultValues() {
    822   // Verify that this is a totally new database, and initialize it.
    823   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    824   iter->SeekToFirst();
    825   if (iter->Valid()) {  // DB was not empty--we shouldn't have been called.
    826     LOG(ERROR) << "File system origin database is corrupt!";
    827     return false;
    828   }
    829   // This is always the first write into the database.  If we ever add a
    830   // version number, it should go in this transaction too.
    831   FileInfo root;
    832   root.parent_id = 0;
    833   root.modification_time = base::Time::Now();
    834   leveldb::WriteBatch batch;
    835   if (!AddFileInfoHelper(root, 0, &batch))
    836     return false;
    837   batch.Put(LastFileIdKey(), base::Int64ToString(0));
    838   batch.Put(LastIntegerKey(), base::Int64ToString(-1));
    839   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    840   if (!status.ok()) {
    841     HandleError(FROM_HERE, status);
    842     return false;
    843   }
    844   return true;
    845 }
    846 
    847 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
    848   if (!Init(REPAIR_ON_CORRUPTION))
    849     return false;
    850   DCHECK(file_id);
    851   std::string id_string;
    852   leveldb::Status status =
    853       db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
    854   if (status.ok()) {
    855     if (!base::StringToInt64(id_string, file_id)) {
    856       LOG(ERROR) << "Hit database corruption!";
    857       return false;
    858     }
    859     return true;
    860   }
    861   if (!status.IsNotFound()) {
    862     HandleError(FROM_HERE, status);
    863     return false;
    864   }
    865   // The database must not yet exist; initialize it.
    866   if (!StoreDefaultValues())
    867     return false;
    868   *file_id = 0;
    869   return true;
    870 }
    871 
    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();
    877     return false;
    878   }
    879   std::string id_string = GetFileLookupKey(file_id);
    880   if (!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());
    884   } else {
    885     std::string child_key = GetChildLookupKey(info.parent_id, info.name);
    886     batch->Put(child_key, id_string);
    887   }
    888   Pickle pickle;
    889   if (!PickleFromFileInfo(info, &pickle))
    890     return false;
    891   batch->Put(
    892       id_string,
    893       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    894                      pickle.size()));
    895   return true;
    896 }
    897 
    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.
    902   FileInfo info;
    903   if (!GetFileInfo(file_id, &info))
    904     return false;
    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))
    909       return false;
    910     if (children.size()) {
    911       LOG(ERROR) << "Can't remove a directory with children.";
    912       return false;
    913     }
    914   }
    915   batch->Delete(GetChildLookupKey(info.parent_id, info.name));
    916   batch->Delete(GetFileLookupKey(file_id));
    917   return true;
    918 }
    919 
    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();
    925   db_.reset();
    926 }
    927 
    928 }  // namespace fileapi
    929