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::File::Info 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     leveldb::Env* env_override)
    415     : filesystem_data_directory_(filesystem_data_directory),
    416       env_override_(env_override) {
    417 }
    418 
    419 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
    420 }
    421 
    422 bool SandboxDirectoryDatabase::GetChildWithName(
    423     FileId parent_id,
    424     const base::FilePath::StringType& name,
    425     FileId* child_id) {
    426   if (!Init(REPAIR_ON_CORRUPTION))
    427     return false;
    428   DCHECK(child_id);
    429   std::string child_key = GetChildLookupKey(parent_id, name);
    430   std::string child_id_string;
    431   leveldb::Status status =
    432       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
    433   if (status.IsNotFound())
    434     return false;
    435   if (status.ok()) {
    436     if (!base::StringToInt64(child_id_string, child_id)) {
    437       LOG(ERROR) << "Hit database corruption!";
    438       return false;
    439     }
    440     return true;
    441   }
    442   HandleError(FROM_HERE, status);
    443   return false;
    444 }
    445 
    446 bool SandboxDirectoryDatabase::GetFileWithPath(
    447     const base::FilePath& path, FileId* file_id) {
    448   std::vector<base::FilePath::StringType> components;
    449   VirtualPath::GetComponents(path, &components);
    450   FileId local_id = 0;
    451   std::vector<base::FilePath::StringType>::iterator iter;
    452   for (iter = components.begin(); iter != components.end(); ++iter) {
    453     base::FilePath::StringType name;
    454     name = *iter;
    455     if (name == FILE_PATH_LITERAL("/"))
    456       continue;
    457     if (!GetChildWithName(local_id, name, &local_id))
    458       return false;
    459   }
    460   *file_id = local_id;
    461   return true;
    462 }
    463 
    464 bool SandboxDirectoryDatabase::ListChildren(
    465     FileId parent_id, std::vector<FileId>* children) {
    466   // Check to add later: fail if parent is a file, at least in debug builds.
    467   if (!Init(REPAIR_ON_CORRUPTION))
    468     return false;
    469   DCHECK(children);
    470   std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
    471 
    472   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    473   iter->Seek(child_key_prefix);
    474   children->clear();
    475   while (iter->Valid() &&
    476       StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
    477     std::string child_id_string = iter->value().ToString();
    478     FileId child_id;
    479     if (!base::StringToInt64(child_id_string, &child_id)) {
    480       LOG(ERROR) << "Hit database corruption!";
    481       return false;
    482     }
    483     children->push_back(child_id);
    484     iter->Next();
    485   }
    486   return true;
    487 }
    488 
    489 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
    490   if (!Init(REPAIR_ON_CORRUPTION))
    491     return false;
    492   DCHECK(info);
    493   std::string file_key = GetFileLookupKey(file_id);
    494   std::string file_data_string;
    495   leveldb::Status status =
    496       db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
    497   if (status.ok()) {
    498     bool success = FileInfoFromPickle(
    499         Pickle(file_data_string.data(), file_data_string.length()), info);
    500     if (!success)
    501       return false;
    502     if (!VerifyDataPath(info->data_path)) {
    503       LOG(ERROR) << "Resolved data path is invalid: "
    504                  << info->data_path.value();
    505       return false;
    506     }
    507     return true;
    508   }
    509   // Special-case the root, for databases that haven't been initialized yet.
    510   // Without this, a query for the root's file info, made before creating the
    511   // first file in the database, will fail and confuse callers.
    512   if (status.IsNotFound() && !file_id) {
    513     info->name = base::FilePath::StringType();
    514     info->data_path = base::FilePath();
    515     info->modification_time = base::Time::Now();
    516     info->parent_id = 0;
    517     return true;
    518   }
    519   HandleError(FROM_HERE, status);
    520   return false;
    521 }
    522 
    523 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
    524     const FileInfo& info, FileId* file_id) {
    525   if (!Init(REPAIR_ON_CORRUPTION))
    526     return base::File::FILE_ERROR_FAILED;
    527   DCHECK(file_id);
    528   std::string child_key = GetChildLookupKey(info.parent_id, info.name);
    529   std::string child_id_string;
    530   leveldb::Status status =
    531       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
    532   if (status.ok()) {
    533     LOG(ERROR) << "File exists already!";
    534     return base::File::FILE_ERROR_EXISTS;
    535   }
    536   if (!status.IsNotFound()) {
    537     HandleError(FROM_HERE, status);
    538     return base::File::FILE_ERROR_NOT_FOUND;
    539   }
    540 
    541   if (!IsDirectory(info.parent_id)) {
    542     LOG(ERROR) << "New parent directory is a file!";
    543     return base::File::FILE_ERROR_NOT_A_DIRECTORY;
    544   }
    545 
    546   // This would be a fine place to limit the number of files in a directory, if
    547   // we decide to add that restriction.
    548 
    549   FileId temp_id;
    550   if (!GetLastFileId(&temp_id))
    551     return base::File::FILE_ERROR_FAILED;
    552   ++temp_id;
    553 
    554   leveldb::WriteBatch batch;
    555   if (!AddFileInfoHelper(info, temp_id, &batch))
    556     return base::File::FILE_ERROR_FAILED;
    557 
    558   batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
    559   status = db_->Write(leveldb::WriteOptions(), &batch);
    560   if (!status.ok()) {
    561     HandleError(FROM_HERE, status);
    562     return base::File::FILE_ERROR_FAILED;
    563   }
    564   *file_id = temp_id;
    565   return base::File::FILE_OK;
    566 }
    567 
    568 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
    569   if (!Init(REPAIR_ON_CORRUPTION))
    570     return false;
    571   leveldb::WriteBatch batch;
    572   if (!RemoveFileInfoHelper(file_id, &batch))
    573     return false;
    574   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    575   if (!status.ok()) {
    576     HandleError(FROM_HERE, status);
    577     return false;
    578   }
    579   return true;
    580 }
    581 
    582 bool SandboxDirectoryDatabase::UpdateFileInfo(
    583     FileId file_id, const FileInfo& new_info) {
    584   // TODO(ericu): We should also check to see that this doesn't create a loop,
    585   // but perhaps only in a debug build.
    586   if (!Init(REPAIR_ON_CORRUPTION))
    587     return false;
    588   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
    589   FileInfo old_info;
    590   if (!GetFileInfo(file_id, &old_info))
    591     return false;
    592   if (old_info.parent_id != new_info.parent_id &&
    593       !IsDirectory(new_info.parent_id))
    594     return false;
    595   if (old_info.parent_id != new_info.parent_id ||
    596       old_info.name != new_info.name) {
    597     // Check for name clashes.
    598     FileId temp_id;
    599     if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
    600       LOG(ERROR) << "Name collision on move.";
    601       return false;
    602     }
    603   }
    604   leveldb::WriteBatch batch;
    605   if (!RemoveFileInfoHelper(file_id, &batch) ||
    606       !AddFileInfoHelper(new_info, file_id, &batch))
    607     return false;
    608   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    609   if (!status.ok()) {
    610     HandleError(FROM_HERE, status);
    611     return false;
    612   }
    613   return true;
    614 }
    615 
    616 bool SandboxDirectoryDatabase::UpdateModificationTime(
    617     FileId file_id, const base::Time& modification_time) {
    618   FileInfo info;
    619   if (!GetFileInfo(file_id, &info))
    620     return false;
    621   info.modification_time = modification_time;
    622   Pickle pickle;
    623   if (!PickleFromFileInfo(info, &pickle))
    624     return false;
    625   leveldb::Status status = db_->Put(
    626       leveldb::WriteOptions(),
    627       GetFileLookupKey(file_id),
    628       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    629                      pickle.size()));
    630   if (!status.ok()) {
    631     HandleError(FROM_HERE, status);
    632     return false;
    633   }
    634   return true;
    635 }
    636 
    637 bool SandboxDirectoryDatabase::OverwritingMoveFile(
    638     FileId src_file_id, FileId dest_file_id) {
    639   FileInfo src_file_info;
    640   FileInfo dest_file_info;
    641 
    642   if (!GetFileInfo(src_file_id, &src_file_info))
    643     return false;
    644   if (!GetFileInfo(dest_file_id, &dest_file_info))
    645     return false;
    646   if (src_file_info.is_directory() || dest_file_info.is_directory())
    647     return false;
    648   leveldb::WriteBatch batch;
    649   // This is the only field that really gets moved over; if you add fields to
    650   // FileInfo, e.g. ctime, they might need to be copied here.
    651   dest_file_info.data_path = src_file_info.data_path;
    652   if (!RemoveFileInfoHelper(src_file_id, &batch))
    653     return false;
    654   Pickle pickle;
    655   if (!PickleFromFileInfo(dest_file_info, &pickle))
    656     return false;
    657   batch.Put(
    658       GetFileLookupKey(dest_file_id),
    659       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    660                      pickle.size()));
    661   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    662   if (!status.ok()) {
    663     HandleError(FROM_HERE, status);
    664     return false;
    665   }
    666   return true;
    667 }
    668 
    669 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
    670   if (!Init(REPAIR_ON_CORRUPTION))
    671     return false;
    672   DCHECK(next);
    673   std::string int_string;
    674   leveldb::Status status =
    675       db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
    676   if (status.ok()) {
    677     int64 temp;
    678     if (!base::StringToInt64(int_string, &temp)) {
    679       LOG(ERROR) << "Hit database corruption!";
    680       return false;
    681     }
    682     ++temp;
    683     status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
    684         base::Int64ToString(temp));
    685     if (!status.ok()) {
    686       HandleError(FROM_HERE, status);
    687       return false;
    688     }
    689     *next = temp;
    690     return true;
    691   }
    692   if (!status.IsNotFound()) {
    693     HandleError(FROM_HERE, status);
    694     return false;
    695   }
    696   // The database must not yet exist; initialize it.
    697   if (!StoreDefaultValues())
    698     return false;
    699 
    700   return GetNextInteger(next);
    701 }
    702 
    703 // static
    704 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path,
    705                                                leveldb::Env* env_override) {
    706   std::string name  = FilePathToString(path.Append(kDirectoryDatabaseName));
    707   leveldb::Options options;
    708   if (env_override)
    709     options.env = env_override;
    710   leveldb::Status status = leveldb::DestroyDB(name, options);
    711   if (status.ok())
    712     return true;
    713   LOG(WARNING) << "Failed to destroy a database with status " <<
    714       status.ToString();
    715   return false;
    716 }
    717 
    718 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
    719   if (db_)
    720     return true;
    721 
    722   std::string path =
    723       FilePathToString(filesystem_data_directory_.Append(
    724           kDirectoryDatabaseName));
    725   leveldb::Options options;
    726   options.max_open_files = 0;  // Use minimum.
    727   options.create_if_missing = true;
    728   if (env_override_)
    729     options.env = env_override_;
    730   leveldb::DB* db;
    731   leveldb::Status status = leveldb::DB::Open(options, path, &db);
    732   ReportInitStatus(status);
    733   if (status.ok()) {
    734     db_.reset(db);
    735     return true;
    736   }
    737   HandleError(FROM_HERE, status);
    738 
    739   // Corruption due to missing necessary MANIFEST-* file causes IOError instead
    740   // of Corruption error.
    741   // Try to repair database even when IOError case.
    742   if (!status.IsCorruption() && !status.IsIOError())
    743     return false;
    744 
    745   switch (recovery_option) {
    746     case FAIL_ON_CORRUPTION:
    747       return false;
    748     case REPAIR_ON_CORRUPTION:
    749       LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
    750                    << " Attempting to repair.";
    751       if (RepairDatabase(path)) {
    752         UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    753                                   DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
    754         return true;
    755       }
    756       UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    757                                 DB_REPAIR_FAILED, DB_REPAIR_MAX);
    758       LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
    759       // fall through
    760     case DELETE_ON_CORRUPTION:
    761       LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
    762       if (!base::DeleteFile(filesystem_data_directory_, true))
    763         return false;
    764       if (!base::CreateDirectory(filesystem_data_directory_))
    765         return false;
    766       return Init(FAIL_ON_CORRUPTION);
    767   }
    768 
    769   NOTREACHED();
    770   return false;
    771 }
    772 
    773 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
    774   DCHECK(!db_.get());
    775   leveldb::Options options;
    776   options.max_open_files = 0;  // Use minimum.
    777   if (env_override_)
    778     options.env = env_override_;
    779   if (!leveldb::RepairDB(db_path, options).ok())
    780     return false;
    781   if (!Init(FAIL_ON_CORRUPTION))
    782     return false;
    783   if (IsFileSystemConsistent())
    784     return true;
    785   db_.reset();
    786   return false;
    787 }
    788 
    789 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
    790   FileInfo info;
    791   if (!file_id)
    792     return true;  // The root is a directory.
    793   if (!GetFileInfo(file_id, &info))
    794     return false;
    795   if (!info.is_directory())
    796     return false;
    797   return true;
    798 }
    799 
    800 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
    801   if (!Init(FAIL_ON_CORRUPTION))
    802     return false;
    803   DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
    804   return helper.IsFileSystemConsistent();
    805 }
    806 
    807 void SandboxDirectoryDatabase::ReportInitStatus(
    808     const leveldb::Status& status) {
    809   base::Time now = base::Time::Now();
    810   const base::TimeDelta minimum_interval =
    811       base::TimeDelta::FromHours(kMinimumReportIntervalHours);
    812   if (last_reported_time_ + minimum_interval >= now)
    813     return;
    814   last_reported_time_ = now;
    815 
    816   if (status.ok()) {
    817     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    818                               INIT_STATUS_OK, INIT_STATUS_MAX);
    819   } else if (status.IsCorruption()) {
    820     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    821                               INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
    822   } else if (status.IsIOError()) {
    823     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    824                               INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
    825   } else {
    826     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    827                               INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
    828   }
    829 }
    830 
    831 bool SandboxDirectoryDatabase::StoreDefaultValues() {
    832   // Verify that this is a totally new database, and initialize it.
    833   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    834   iter->SeekToFirst();
    835   if (iter->Valid()) {  // DB was not empty--we shouldn't have been called.
    836     LOG(ERROR) << "File system origin database is corrupt!";
    837     return false;
    838   }
    839   // This is always the first write into the database.  If we ever add a
    840   // version number, it should go in this transaction too.
    841   FileInfo root;
    842   root.parent_id = 0;
    843   root.modification_time = base::Time::Now();
    844   leveldb::WriteBatch batch;
    845   if (!AddFileInfoHelper(root, 0, &batch))
    846     return false;
    847   batch.Put(LastFileIdKey(), base::Int64ToString(0));
    848   batch.Put(LastIntegerKey(), base::Int64ToString(-1));
    849   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    850   if (!status.ok()) {
    851     HandleError(FROM_HERE, status);
    852     return false;
    853   }
    854   return true;
    855 }
    856 
    857 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
    858   if (!Init(REPAIR_ON_CORRUPTION))
    859     return false;
    860   DCHECK(file_id);
    861   std::string id_string;
    862   leveldb::Status status =
    863       db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
    864   if (status.ok()) {
    865     if (!base::StringToInt64(id_string, file_id)) {
    866       LOG(ERROR) << "Hit database corruption!";
    867       return false;
    868     }
    869     return true;
    870   }
    871   if (!status.IsNotFound()) {
    872     HandleError(FROM_HERE, status);
    873     return false;
    874   }
    875   // The database must not yet exist; initialize it.
    876   if (!StoreDefaultValues())
    877     return false;
    878   *file_id = 0;
    879   return true;
    880 }
    881 
    882 // This does very few safety checks!
    883 bool SandboxDirectoryDatabase::AddFileInfoHelper(
    884     const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
    885   if (!VerifyDataPath(info.data_path)) {
    886     LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
    887     return false;
    888   }
    889   std::string id_string = GetFileLookupKey(file_id);
    890   if (!file_id) {
    891     // The root directory doesn't need to be looked up by path from its parent.
    892     DCHECK(!info.parent_id);
    893     DCHECK(info.data_path.empty());
    894   } else {
    895     std::string child_key = GetChildLookupKey(info.parent_id, info.name);
    896     batch->Put(child_key, id_string);
    897   }
    898   Pickle pickle;
    899   if (!PickleFromFileInfo(info, &pickle))
    900     return false;
    901   batch->Put(
    902       id_string,
    903       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    904                      pickle.size()));
    905   return true;
    906 }
    907 
    908 // This does very few safety checks!
    909 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
    910     FileId file_id, leveldb::WriteBatch* batch) {
    911   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
    912   FileInfo info;
    913   if (!GetFileInfo(file_id, &info))
    914     return false;
    915   if (info.data_path.empty()) {  // It's a directory
    916     std::vector<FileId> children;
    917     // TODO(ericu): Make a faster is-the-directory-empty check.
    918     if (!ListChildren(file_id, &children))
    919       return false;
    920     if (children.size()) {
    921       LOG(ERROR) << "Can't remove a directory with children.";
    922       return false;
    923     }
    924   }
    925   batch->Delete(GetChildLookupKey(info.parent_id, info.name));
    926   batch->Delete(GetFileLookupKey(file_id));
    927   return true;
    928 }
    929 
    930 void SandboxDirectoryDatabase::HandleError(
    931     const tracked_objects::Location& from_here,
    932     const leveldb::Status& status) {
    933   LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
    934              << from_here.ToString() << " with error: " << status.ToString();
    935   db_.reset();
    936 }
    937 
    938 }  // namespace fileapi
    939