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 "storage/browser/fileapi/sandbox_directory_database.h"
      6 
      7 #include <math.h>
      8 #include <algorithm>
      9 #include <set>
     10 #include <stack>
     11 
     12 #include "base/files/file_enumerator.h"
     13 #include "base/files/file_util.h"
     14 #include "base/location.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/pickle.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/string_util.h"
     19 #include "storage/browser/fileapi/file_system_usage_cache.h"
     20 #include "storage/common/fileapi/file_system_util.h"
     21 #include "third_party/leveldatabase/src/include/leveldb/db.h"
     22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
     23 
     24 namespace {
     25 
     26 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
     27                         Pickle* pickle) {
     28   DCHECK(pickle);
     29   std::string data_path;
     30   // Round off here to match the behavior of the filesystem on real files.
     31   base::Time time =
     32       base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
     33   std::string name;
     34 
     35   data_path = storage::FilePathToString(info.data_path);
     36   name = storage::FilePathToString(base::FilePath(info.name));
     37 
     38   if (pickle->WriteInt64(info.parent_id) &&
     39       pickle->WriteString(data_path) &&
     40       pickle->WriteString(name) &&
     41       pickle->WriteInt64(time.ToInternalValue()))
     42     return true;
     43 
     44   NOTREACHED();
     45   return false;
     46 }
     47 
     48 bool FileInfoFromPickle(const Pickle& pickle,
     49                         storage::SandboxDirectoryDatabase::FileInfo* info) {
     50   PickleIterator iter(pickle);
     51   std::string data_path;
     52   std::string name;
     53   int64 internal_time;
     54 
     55   if (pickle.ReadInt64(&iter, &info->parent_id) &&
     56       pickle.ReadString(&iter, &data_path) &&
     57       pickle.ReadString(&iter, &name) &&
     58       pickle.ReadInt64(&iter, &internal_time)) {
     59     info->data_path = storage::StringToFilePath(data_path);
     60     info->name = storage::StringToFilePath(name).value();
     61     info->modification_time = base::Time::FromInternalValue(internal_time);
     62     return true;
     63   }
     64   LOG(ERROR) << "Pickle could not be digested!";
     65   return false;
     66 }
     67 
     68 const base::FilePath::CharType kDirectoryDatabaseName[] =
     69     FILE_PATH_LITERAL("Paths");
     70 const char kChildLookupPrefix[] = "CHILD_OF:";
     71 const char kChildLookupSeparator[] = ":";
     72 const char kLastFileIdKey[] = "LAST_FILE_ID";
     73 const char kLastIntegerKey[] = "LAST_INTEGER";
     74 const int64 kMinimumReportIntervalHours = 1;
     75 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
     76 const char kDatabaseRepairHistogramLabel[] =
     77     "FileSystem.DirectoryDatabaseRepair";
     78 
     79 enum InitStatus {
     80   INIT_STATUS_OK = 0,
     81   INIT_STATUS_CORRUPTION,
     82   INIT_STATUS_IO_ERROR,
     83   INIT_STATUS_UNKNOWN_ERROR,
     84   INIT_STATUS_MAX
     85 };
     86 
     87 enum RepairResult {
     88   DB_REPAIR_SUCCEEDED = 0,
     89   DB_REPAIR_FAILED,
     90   DB_REPAIR_MAX
     91 };
     92 
     93 std::string GetChildLookupKey(
     94     storage::SandboxDirectoryDatabase::FileId parent_id,
     95     const base::FilePath::StringType& child_name) {
     96   std::string name;
     97   name = storage::FilePathToString(base::FilePath(child_name));
     98   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
     99       std::string(kChildLookupSeparator) + name;
    100 }
    101 
    102 std::string GetChildListingKeyPrefix(
    103     storage::SandboxDirectoryDatabase::FileId parent_id) {
    104   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
    105       std::string(kChildLookupSeparator);
    106 }
    107 
    108 const char* LastFileIdKey() {
    109   return kLastFileIdKey;
    110 }
    111 
    112 const char* LastIntegerKey() {
    113   return kLastIntegerKey;
    114 }
    115 
    116 std::string GetFileLookupKey(
    117     storage::SandboxDirectoryDatabase::FileId file_id) {
    118   return base::Int64ToString(file_id);
    119 }
    120 
    121 // Assumptions:
    122 //  - Any database entry is one of:
    123 //    - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
    124 //    - ("LAST_FILE_ID", "|last_file_id|"),
    125 //    - ("LAST_INTEGER", "|last_integer|"),
    126 //    - ("|file_id|", "pickled FileInfo")
    127 //        where FileInfo has |parent_id|, |data_path|, |name| and
    128 //        |modification_time|,
    129 // Constraints:
    130 //  - Each file in the database has unique backing file.
    131 //  - Each file in |filesystem_data_directory_| has a database entry.
    132 //  - Directory structure is tree, i.e. connected and acyclic.
    133 class DatabaseCheckHelper {
    134  public:
    135   typedef storage::SandboxDirectoryDatabase::FileId FileId;
    136   typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
    137 
    138   DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
    139                       leveldb::DB* db,
    140                       const base::FilePath& path);
    141 
    142   bool IsFileSystemConsistent() {
    143     return IsDatabaseEmpty() ||
    144         (ScanDatabase() && ScanDirectory() && ScanHierarchy());
    145   }
    146 
    147  private:
    148   bool IsDatabaseEmpty();
    149   // These 3 methods need to be called in the order.  Each method requires its
    150   // previous method finished successfully. They also require the database is
    151   // not empty.
    152   bool ScanDatabase();
    153   bool ScanDirectory();
    154   bool ScanHierarchy();
    155 
    156   storage::SandboxDirectoryDatabase* dir_db_;
    157   leveldb::DB* db_;
    158   base::FilePath path_;
    159 
    160   std::set<base::FilePath> files_in_db_;
    161 
    162   size_t num_directories_in_db_;
    163   size_t num_files_in_db_;
    164   size_t num_hierarchy_links_in_db_;
    165 
    166   FileId last_file_id_;
    167   FileId last_integer_;
    168 };
    169 
    170 DatabaseCheckHelper::DatabaseCheckHelper(
    171     storage::SandboxDirectoryDatabase* dir_db,
    172     leveldb::DB* db,
    173     const base::FilePath& path)
    174     : dir_db_(dir_db),
    175       db_(db),
    176       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),
    181       last_integer_(-1) {
    182   DCHECK(dir_db_);
    183   DCHECK(db_);
    184   DCHECK(!path_.empty() && base::DirectoryExists(path_));
    185 }
    186 
    187 bool DatabaseCheckHelper::IsDatabaseEmpty() {
    188   scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
    189   itr->SeekToFirst();
    190   return !itr->Valid();
    191 }
    192 
    193 bool DatabaseCheckHelper::ScanDatabase() {
    194   // Scans all database entries sequentially to verify each of them has unique
    195   // backing file.
    196   int64 max_file_id = -1;
    197   std::set<FileId> file_ids;
    198 
    199   scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
    200   for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
    201     std::string key = itr->key().ToString();
    202     if (StartsWithASCII(key, kChildLookupPrefix, true)) {
    203       // key: "CHILD_OF:<parent_id>:<name>"
    204       // value: "<child_id>"
    205       ++num_hierarchy_links_in_db_;
    206     } else if (key == kLastFileIdKey) {
    207       // key: "LAST_FILE_ID"
    208       // value: "<last_file_id>"
    209       if (last_file_id_ >= 0 ||
    210           !base::StringToInt64(itr->value().ToString(), &last_file_id_))
    211         return false;
    212 
    213       if (last_file_id_ < 0)
    214         return false;
    215     } else if (key == kLastIntegerKey) {
    216       // key: "LAST_INTEGER"
    217       // value: "<last_integer>"
    218       if (last_integer_ >= 0 ||
    219           !base::StringToInt64(itr->value().ToString(), &last_integer_))
    220         return false;
    221     } else {
    222       // key: "<entry_id>"
    223       // value: "<pickled FileInfo>"
    224       FileInfo file_info;
    225       if (!FileInfoFromPickle(
    226               Pickle(itr->value().data(), itr->value().size()), &file_info))
    227         return false;
    228 
    229       FileId file_id = -1;
    230       if (!base::StringToInt64(key, &file_id) || file_id < 0)
    231         return false;
    232 
    233       if (max_file_id < file_id)
    234         max_file_id = file_id;
    235       if (!file_ids.insert(file_id).second)
    236         return false;
    237 
    238       if (file_info.is_directory()) {
    239         ++num_directories_in_db_;
    240         DCHECK(file_info.data_path.empty());
    241       } else {
    242         // Ensure any pair of file entry don't share their data_path.
    243         if (!files_in_db_.insert(file_info.data_path).second)
    244           return false;
    245 
    246         // Ensure the backing file exists as a normal file.
    247         base::File::Info platform_file_info;
    248         if (!base::GetFileInfo(
    249                 path_.Append(file_info.data_path), &platform_file_info) ||
    250             platform_file_info.is_directory ||
    251             platform_file_info.is_symbolic_link) {
    252           // leveldb::Iterator iterates a snapshot of the database.
    253           // So even after RemoveFileInfo() call, we'll visit hierarchy link
    254           // from |parent_id| to |file_id|.
    255           if (!dir_db_->RemoveFileInfo(file_id))
    256             return false;
    257           --num_hierarchy_links_in_db_;
    258           files_in_db_.erase(file_info.data_path);
    259         } else {
    260           ++num_files_in_db_;
    261         }
    262       }
    263     }
    264   }
    265 
    266   // TODO(tzik): Add constraint for |last_integer_| to avoid possible
    267   // data path confliction on ObfuscatedFileUtil.
    268   return max_file_id <= last_file_id_;
    269 }
    270 
    271 bool DatabaseCheckHelper::ScanDirectory() {
    272   // TODO(kinuko): Scans all local file system entries to verify each of them
    273   // has a database entry.
    274   const base::FilePath kExcludes[] = {
    275       base::FilePath(kDirectoryDatabaseName),
    276       base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
    277   };
    278 
    279   // Any path in |pending_directories| is relative to |path_|.
    280   std::stack<base::FilePath> pending_directories;
    281   pending_directories.push(base::FilePath());
    282 
    283   while (!pending_directories.empty()) {
    284     base::FilePath dir_path = pending_directories.top();
    285     pending_directories.pop();
    286 
    287     base::FileEnumerator file_enum(
    288         dir_path.empty() ? path_ : path_.Append(dir_path),
    289         false /* not recursive */,
    290         base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
    291 
    292     base::FilePath absolute_file_path;
    293     while (!(absolute_file_path = file_enum.Next()).empty()) {
    294       base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
    295 
    296       base::FilePath relative_file_path;
    297       if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
    298         return false;
    299 
    300       if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
    301                     relative_file_path) != kExcludes + arraysize(kExcludes))
    302         continue;
    303 
    304       if (find_info.IsDirectory()) {
    305         pending_directories.push(relative_file_path);
    306         continue;
    307       }
    308 
    309       // Check if the file has a database entry.
    310       std::set<base::FilePath>::iterator itr =
    311           files_in_db_.find(relative_file_path);
    312       if (itr == files_in_db_.end()) {
    313         if (!base::DeleteFile(absolute_file_path, false))
    314           return false;
    315       } else {
    316         files_in_db_.erase(itr);
    317       }
    318     }
    319   }
    320 
    321   return files_in_db_.empty();
    322 }
    323 
    324 bool DatabaseCheckHelper::ScanHierarchy() {
    325   size_t visited_directories = 0;
    326   size_t visited_files = 0;
    327   size_t visited_links = 0;
    328 
    329   std::stack<FileId> directories;
    330   directories.push(0);
    331 
    332   // Check if the root directory exists as a directory.
    333   FileInfo file_info;
    334   if (!dir_db_->GetFileInfo(0, &file_info))
    335     return false;
    336   if (file_info.parent_id != 0 ||
    337       !file_info.is_directory())
    338     return false;
    339 
    340   while (!directories.empty()) {
    341     ++visited_directories;
    342     FileId dir_id = directories.top();
    343     directories.pop();
    344 
    345     std::vector<FileId> children;
    346     if (!dir_db_->ListChildren(dir_id, &children))
    347       return false;
    348     for (std::vector<FileId>::iterator itr = children.begin();
    349          itr != children.end();
    350          ++itr) {
    351       // Any directory must not have root directory as child.
    352       if (!*itr)
    353         return false;
    354 
    355       // Check if the child knows the parent as its parent.
    356       FileInfo file_info;
    357       if (!dir_db_->GetFileInfo(*itr, &file_info))
    358         return false;
    359       if (file_info.parent_id != dir_id)
    360         return false;
    361 
    362       // Check if the parent knows the name of its child correctly.
    363       FileId file_id;
    364       if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
    365           file_id != *itr)
    366         return false;
    367 
    368       if (file_info.is_directory())
    369         directories.push(*itr);
    370       else
    371         ++visited_files;
    372       ++visited_links;
    373     }
    374   }
    375 
    376   // Check if we've visited all database entries.
    377   return num_directories_in_db_ == visited_directories &&
    378       num_files_in_db_ == visited_files &&
    379       num_hierarchy_links_in_db_ == visited_links;
    380 }
    381 
    382 // Returns true if the given |data_path| contains no parent references ("..")
    383 // and does not refer to special system files.
    384 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
    385 // ensure we're only dealing with valid data paths.
    386 bool VerifyDataPath(const base::FilePath& data_path) {
    387   // |data_path| should not contain any ".." and should be a relative path
    388   // (to the filesystem_data_directory_).
    389   if (data_path.ReferencesParent() || data_path.IsAbsolute())
    390     return false;
    391   // See if it's not pointing to the special system paths.
    392   const base::FilePath kExcludes[] = {
    393       base::FilePath(kDirectoryDatabaseName),
    394       base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
    395   };
    396   for (size_t i = 0; i < arraysize(kExcludes); ++i) {
    397     if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
    398       return false;
    399   }
    400   return true;
    401 }
    402 
    403 }  // namespace
    404 
    405 namespace storage {
    406 
    407 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
    408 }
    409 
    410 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
    411 }
    412 
    413 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
    414     const base::FilePath& filesystem_data_directory,
    415     leveldb::Env* env_override)
    416     : filesystem_data_directory_(filesystem_data_directory),
    417       env_override_(env_override) {
    418 }
    419 
    420 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
    421 }
    422 
    423 bool SandboxDirectoryDatabase::GetChildWithName(
    424     FileId parent_id,
    425     const base::FilePath::StringType& name,
    426     FileId* child_id) {
    427   if (!Init(REPAIR_ON_CORRUPTION))
    428     return false;
    429   DCHECK(child_id);
    430   std::string child_key = GetChildLookupKey(parent_id, name);
    431   std::string child_id_string;
    432   leveldb::Status status =
    433       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
    434   if (status.IsNotFound())
    435     return false;
    436   if (status.ok()) {
    437     if (!base::StringToInt64(child_id_string, child_id)) {
    438       LOG(ERROR) << "Hit database corruption!";
    439       return false;
    440     }
    441     return true;
    442   }
    443   HandleError(FROM_HERE, status);
    444   return false;
    445 }
    446 
    447 bool SandboxDirectoryDatabase::GetFileWithPath(
    448     const base::FilePath& path, FileId* file_id) {
    449   std::vector<base::FilePath::StringType> components;
    450   VirtualPath::GetComponents(path, &components);
    451   FileId local_id = 0;
    452   std::vector<base::FilePath::StringType>::iterator iter;
    453   for (iter = components.begin(); iter != components.end(); ++iter) {
    454     base::FilePath::StringType name;
    455     name = *iter;
    456     if (name == FILE_PATH_LITERAL("/"))
    457       continue;
    458     if (!GetChildWithName(local_id, name, &local_id))
    459       return false;
    460   }
    461   *file_id = local_id;
    462   return true;
    463 }
    464 
    465 bool SandboxDirectoryDatabase::ListChildren(
    466     FileId parent_id, std::vector<FileId>* children) {
    467   // Check to add later: fail if parent is a file, at least in debug builds.
    468   if (!Init(REPAIR_ON_CORRUPTION))
    469     return false;
    470   DCHECK(children);
    471   std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
    472 
    473   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    474   iter->Seek(child_key_prefix);
    475   children->clear();
    476   while (iter->Valid() &&
    477       StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
    478     std::string child_id_string = iter->value().ToString();
    479     FileId child_id;
    480     if (!base::StringToInt64(child_id_string, &child_id)) {
    481       LOG(ERROR) << "Hit database corruption!";
    482       return false;
    483     }
    484     children->push_back(child_id);
    485     iter->Next();
    486   }
    487   return true;
    488 }
    489 
    490 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
    491   if (!Init(REPAIR_ON_CORRUPTION))
    492     return false;
    493   DCHECK(info);
    494   std::string file_key = GetFileLookupKey(file_id);
    495   std::string file_data_string;
    496   leveldb::Status status =
    497       db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
    498   if (status.ok()) {
    499     bool success = FileInfoFromPickle(
    500         Pickle(file_data_string.data(), file_data_string.length()), info);
    501     if (!success)
    502       return false;
    503     if (!VerifyDataPath(info->data_path)) {
    504       LOG(ERROR) << "Resolved data path is invalid: "
    505                  << info->data_path.value();
    506       return false;
    507     }
    508     return true;
    509   }
    510   // Special-case the root, for databases that haven't been initialized yet.
    511   // Without this, a query for the root's file info, made before creating the
    512   // first file in the database, will fail and confuse callers.
    513   if (status.IsNotFound() && !file_id) {
    514     info->name = base::FilePath::StringType();
    515     info->data_path = base::FilePath();
    516     info->modification_time = base::Time::Now();
    517     info->parent_id = 0;
    518     return true;
    519   }
    520   HandleError(FROM_HERE, status);
    521   return false;
    522 }
    523 
    524 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
    525     const FileInfo& info, FileId* file_id) {
    526   if (!Init(REPAIR_ON_CORRUPTION))
    527     return base::File::FILE_ERROR_FAILED;
    528   DCHECK(file_id);
    529   std::string child_key = GetChildLookupKey(info.parent_id, info.name);
    530   std::string child_id_string;
    531   leveldb::Status status =
    532       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
    533   if (status.ok()) {
    534     LOG(ERROR) << "File exists already!";
    535     return base::File::FILE_ERROR_EXISTS;
    536   }
    537   if (!status.IsNotFound()) {
    538     HandleError(FROM_HERE, status);
    539     return base::File::FILE_ERROR_NOT_FOUND;
    540   }
    541 
    542   if (!IsDirectory(info.parent_id)) {
    543     LOG(ERROR) << "New parent directory is a file!";
    544     return base::File::FILE_ERROR_NOT_A_DIRECTORY;
    545   }
    546 
    547   // This would be a fine place to limit the number of files in a directory, if
    548   // we decide to add that restriction.
    549 
    550   FileId temp_id;
    551   if (!GetLastFileId(&temp_id))
    552     return base::File::FILE_ERROR_FAILED;
    553   ++temp_id;
    554 
    555   leveldb::WriteBatch batch;
    556   if (!AddFileInfoHelper(info, temp_id, &batch))
    557     return base::File::FILE_ERROR_FAILED;
    558 
    559   batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
    560   status = db_->Write(leveldb::WriteOptions(), &batch);
    561   if (!status.ok()) {
    562     HandleError(FROM_HERE, status);
    563     return base::File::FILE_ERROR_FAILED;
    564   }
    565   *file_id = temp_id;
    566   return base::File::FILE_OK;
    567 }
    568 
    569 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
    570   if (!Init(REPAIR_ON_CORRUPTION))
    571     return false;
    572   leveldb::WriteBatch batch;
    573   if (!RemoveFileInfoHelper(file_id, &batch))
    574     return false;
    575   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    576   if (!status.ok()) {
    577     HandleError(FROM_HERE, status);
    578     return false;
    579   }
    580   return true;
    581 }
    582 
    583 bool SandboxDirectoryDatabase::UpdateFileInfo(
    584     FileId file_id, const FileInfo& new_info) {
    585   // TODO(ericu): We should also check to see that this doesn't create a loop,
    586   // but perhaps only in a debug build.
    587   if (!Init(REPAIR_ON_CORRUPTION))
    588     return false;
    589   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
    590   FileInfo old_info;
    591   if (!GetFileInfo(file_id, &old_info))
    592     return false;
    593   if (old_info.parent_id != new_info.parent_id &&
    594       !IsDirectory(new_info.parent_id))
    595     return false;
    596   if (old_info.parent_id != new_info.parent_id ||
    597       old_info.name != new_info.name) {
    598     // Check for name clashes.
    599     FileId temp_id;
    600     if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
    601       LOG(ERROR) << "Name collision on move.";
    602       return false;
    603     }
    604   }
    605   leveldb::WriteBatch batch;
    606   if (!RemoveFileInfoHelper(file_id, &batch) ||
    607       !AddFileInfoHelper(new_info, file_id, &batch))
    608     return false;
    609   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    610   if (!status.ok()) {
    611     HandleError(FROM_HERE, status);
    612     return false;
    613   }
    614   return true;
    615 }
    616 
    617 bool SandboxDirectoryDatabase::UpdateModificationTime(
    618     FileId file_id, const base::Time& modification_time) {
    619   FileInfo info;
    620   if (!GetFileInfo(file_id, &info))
    621     return false;
    622   info.modification_time = modification_time;
    623   Pickle pickle;
    624   if (!PickleFromFileInfo(info, &pickle))
    625     return false;
    626   leveldb::Status status = db_->Put(
    627       leveldb::WriteOptions(),
    628       GetFileLookupKey(file_id),
    629       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    630                      pickle.size()));
    631   if (!status.ok()) {
    632     HandleError(FROM_HERE, status);
    633     return false;
    634   }
    635   return true;
    636 }
    637 
    638 bool SandboxDirectoryDatabase::OverwritingMoveFile(
    639     FileId src_file_id, FileId dest_file_id) {
    640   FileInfo src_file_info;
    641   FileInfo dest_file_info;
    642 
    643   if (!GetFileInfo(src_file_id, &src_file_info))
    644     return false;
    645   if (!GetFileInfo(dest_file_id, &dest_file_info))
    646     return false;
    647   if (src_file_info.is_directory() || dest_file_info.is_directory())
    648     return false;
    649   leveldb::WriteBatch batch;
    650   // This is the only field that really gets moved over; if you add fields to
    651   // FileInfo, e.g. ctime, they might need to be copied here.
    652   dest_file_info.data_path = src_file_info.data_path;
    653   if (!RemoveFileInfoHelper(src_file_id, &batch))
    654     return false;
    655   Pickle pickle;
    656   if (!PickleFromFileInfo(dest_file_info, &pickle))
    657     return false;
    658   batch.Put(
    659       GetFileLookupKey(dest_file_id),
    660       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    661                      pickle.size()));
    662   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    663   if (!status.ok()) {
    664     HandleError(FROM_HERE, status);
    665     return false;
    666   }
    667   return true;
    668 }
    669 
    670 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
    671   if (!Init(REPAIR_ON_CORRUPTION))
    672     return false;
    673   DCHECK(next);
    674   std::string int_string;
    675   leveldb::Status status =
    676       db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
    677   if (status.ok()) {
    678     int64 temp;
    679     if (!base::StringToInt64(int_string, &temp)) {
    680       LOG(ERROR) << "Hit database corruption!";
    681       return false;
    682     }
    683     ++temp;
    684     status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
    685         base::Int64ToString(temp));
    686     if (!status.ok()) {
    687       HandleError(FROM_HERE, status);
    688       return false;
    689     }
    690     *next = temp;
    691     return true;
    692   }
    693   if (!status.IsNotFound()) {
    694     HandleError(FROM_HERE, status);
    695     return false;
    696   }
    697   // The database must not yet exist; initialize it.
    698   if (!StoreDefaultValues())
    699     return false;
    700 
    701   return GetNextInteger(next);
    702 }
    703 
    704 bool SandboxDirectoryDatabase::DestroyDatabase() {
    705   db_.reset();
    706   const std::string path =
    707       FilePathToString(filesystem_data_directory_.Append(
    708           kDirectoryDatabaseName));
    709   leveldb::Options options;
    710   if (env_override_)
    711     options.env = env_override_;
    712   leveldb::Status status = leveldb::DestroyDB(path, options);
    713   if (status.ok())
    714     return true;
    715   LOG(WARNING) << "Failed to destroy a database with status " <<
    716       status.ToString();
    717   return false;
    718 }
    719 
    720 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
    721   if (db_)
    722     return true;
    723 
    724   std::string path =
    725       FilePathToString(filesystem_data_directory_.Append(
    726           kDirectoryDatabaseName));
    727   leveldb::Options options;
    728   options.max_open_files = 0;  // Use minimum.
    729   options.create_if_missing = true;
    730   if (env_override_)
    731     options.env = env_override_;
    732   leveldb::DB* db;
    733   leveldb::Status status = leveldb::DB::Open(options, path, &db);
    734   ReportInitStatus(status);
    735   if (status.ok()) {
    736     db_.reset(db);
    737     return true;
    738   }
    739   HandleError(FROM_HERE, status);
    740 
    741   // Corruption due to missing necessary MANIFEST-* file causes IOError instead
    742   // of Corruption error.
    743   // Try to repair database even when IOError case.
    744   if (!status.IsCorruption() && !status.IsIOError())
    745     return false;
    746 
    747   switch (recovery_option) {
    748     case FAIL_ON_CORRUPTION:
    749       return false;
    750     case REPAIR_ON_CORRUPTION:
    751       LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
    752                    << " Attempting to repair.";
    753       if (RepairDatabase(path)) {
    754         UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    755                                   DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
    756         return true;
    757       }
    758       UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    759                                 DB_REPAIR_FAILED, DB_REPAIR_MAX);
    760       LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
    761       // fall through
    762     case DELETE_ON_CORRUPTION:
    763       LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
    764       if (!base::DeleteFile(filesystem_data_directory_, true))
    765         return false;
    766       if (!base::CreateDirectory(filesystem_data_directory_))
    767         return false;
    768       return Init(FAIL_ON_CORRUPTION);
    769   }
    770 
    771   NOTREACHED();
    772   return false;
    773 }
    774 
    775 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
    776   DCHECK(!db_.get());
    777   leveldb::Options options;
    778   options.max_open_files = 0;  // Use minimum.
    779   if (env_override_)
    780     options.env = env_override_;
    781   if (!leveldb::RepairDB(db_path, options).ok())
    782     return false;
    783   if (!Init(FAIL_ON_CORRUPTION))
    784     return false;
    785   if (IsFileSystemConsistent())
    786     return true;
    787   db_.reset();
    788   return false;
    789 }
    790 
    791 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
    792   FileInfo info;
    793   if (!file_id)
    794     return true;  // The root is a directory.
    795   if (!GetFileInfo(file_id, &info))
    796     return false;
    797   if (!info.is_directory())
    798     return false;
    799   return true;
    800 }
    801 
    802 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
    803   if (!Init(FAIL_ON_CORRUPTION))
    804     return false;
    805   DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
    806   return helper.IsFileSystemConsistent();
    807 }
    808 
    809 void SandboxDirectoryDatabase::ReportInitStatus(
    810     const leveldb::Status& status) {
    811   base::Time now = base::Time::Now();
    812   const base::TimeDelta minimum_interval =
    813       base::TimeDelta::FromHours(kMinimumReportIntervalHours);
    814   if (last_reported_time_ + minimum_interval >= now)
    815     return;
    816   last_reported_time_ = now;
    817 
    818   if (status.ok()) {
    819     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    820                               INIT_STATUS_OK, INIT_STATUS_MAX);
    821   } else if (status.IsCorruption()) {
    822     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    823                               INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
    824   } else if (status.IsIOError()) {
    825     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    826                               INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
    827   } else {
    828     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    829                               INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
    830   }
    831 }
    832 
    833 bool SandboxDirectoryDatabase::StoreDefaultValues() {
    834   // Verify that this is a totally new database, and initialize it.
    835   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    836   iter->SeekToFirst();
    837   if (iter->Valid()) {  // DB was not empty--we shouldn't have been called.
    838     LOG(ERROR) << "File system origin database is corrupt!";
    839     return false;
    840   }
    841   // This is always the first write into the database.  If we ever add a
    842   // version number, it should go in this transaction too.
    843   FileInfo root;
    844   root.parent_id = 0;
    845   root.modification_time = base::Time::Now();
    846   leveldb::WriteBatch batch;
    847   if (!AddFileInfoHelper(root, 0, &batch))
    848     return false;
    849   batch.Put(LastFileIdKey(), base::Int64ToString(0));
    850   batch.Put(LastIntegerKey(), base::Int64ToString(-1));
    851   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
    852   if (!status.ok()) {
    853     HandleError(FROM_HERE, status);
    854     return false;
    855   }
    856   return true;
    857 }
    858 
    859 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
    860   if (!Init(REPAIR_ON_CORRUPTION))
    861     return false;
    862   DCHECK(file_id);
    863   std::string id_string;
    864   leveldb::Status status =
    865       db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
    866   if (status.ok()) {
    867     if (!base::StringToInt64(id_string, file_id)) {
    868       LOG(ERROR) << "Hit database corruption!";
    869       return false;
    870     }
    871     return true;
    872   }
    873   if (!status.IsNotFound()) {
    874     HandleError(FROM_HERE, status);
    875     return false;
    876   }
    877   // The database must not yet exist; initialize it.
    878   if (!StoreDefaultValues())
    879     return false;
    880   *file_id = 0;
    881   return true;
    882 }
    883 
    884 // This does very few safety checks!
    885 bool SandboxDirectoryDatabase::AddFileInfoHelper(
    886     const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
    887   if (!VerifyDataPath(info.data_path)) {
    888     LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
    889     return false;
    890   }
    891   std::string id_string = GetFileLookupKey(file_id);
    892   if (!file_id) {
    893     // The root directory doesn't need to be looked up by path from its parent.
    894     DCHECK(!info.parent_id);
    895     DCHECK(info.data_path.empty());
    896   } else {
    897     std::string child_key = GetChildLookupKey(info.parent_id, info.name);
    898     batch->Put(child_key, id_string);
    899   }
    900   Pickle pickle;
    901   if (!PickleFromFileInfo(info, &pickle))
    902     return false;
    903   batch->Put(
    904       id_string,
    905       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
    906                      pickle.size()));
    907   return true;
    908 }
    909 
    910 // This does very few safety checks!
    911 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
    912     FileId file_id, leveldb::WriteBatch* batch) {
    913   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
    914   FileInfo info;
    915   if (!GetFileInfo(file_id, &info))
    916     return false;
    917   if (info.data_path.empty()) {  // It's a directory
    918     std::vector<FileId> children;
    919     // TODO(ericu): Make a faster is-the-directory-empty check.
    920     if (!ListChildren(file_id, &children))
    921       return false;
    922     if (children.size()) {
    923       LOG(ERROR) << "Can't remove a directory with children.";
    924       return false;
    925     }
    926   }
    927   batch->Delete(GetChildLookupKey(info.parent_id, info.name));
    928   batch->Delete(GetFileLookupKey(file_id));
    929   return true;
    930 }
    931 
    932 void SandboxDirectoryDatabase::HandleError(
    933     const tracked_objects::Location& from_here,
    934     const leveldb::Status& status) {
    935   LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
    936              << from_here.ToString() << " with error: " << status.ToString();
    937   db_.reset();
    938 }
    939 
    940 }  // namespace storage
    941