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_origin_database.h"
      6 
      7 #include <set>
      8 #include <utility>
      9 
     10 #include "base/file_util.h"
     11 #include "base/files/file_enumerator.h"
     12 #include "base/format_macros.h"
     13 #include "base/location.h"
     14 #include "base/logging.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/strings/string_number_conversions.h"
     17 #include "base/strings/string_util.h"
     18 #include "base/strings/stringprintf.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/common/fileapi/file_system_util.h"
     22 
     23 namespace {
     24 
     25 const base::FilePath::CharType kOriginDatabaseName[] =
     26     FILE_PATH_LITERAL("Origins");
     27 const char kOriginKeyPrefix[] = "ORIGIN:";
     28 const char kLastPathKey[] = "LAST_PATH";
     29 const int64 kMinimumReportIntervalHours = 1;
     30 const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit";
     31 const char kDatabaseRepairHistogramLabel[] = "FileSystem.OriginDatabaseRepair";
     32 
     33 enum InitStatus {
     34   INIT_STATUS_OK = 0,
     35   INIT_STATUS_CORRUPTION,
     36   INIT_STATUS_IO_ERROR,
     37   INIT_STATUS_UNKNOWN_ERROR,
     38   INIT_STATUS_MAX
     39 };
     40 
     41 enum RepairResult {
     42   DB_REPAIR_SUCCEEDED = 0,
     43   DB_REPAIR_FAILED,
     44   DB_REPAIR_MAX
     45 };
     46 
     47 std::string OriginToOriginKey(const std::string& origin) {
     48   std::string key(kOriginKeyPrefix);
     49   return key + origin;
     50 }
     51 
     52 const char* LastPathKey() {
     53   return kLastPathKey;
     54 }
     55 
     56 }  // namespace
     57 
     58 namespace fileapi {
     59 
     60 SandboxOriginDatabase::SandboxOriginDatabase(
     61     const base::FilePath& file_system_directory,
     62     leveldb::Env* env_override)
     63     : file_system_directory_(file_system_directory),
     64       env_override_(env_override) {
     65 }
     66 
     67 SandboxOriginDatabase::~SandboxOriginDatabase() {
     68 }
     69 
     70 bool SandboxOriginDatabase::Init(InitOption init_option,
     71                                  RecoveryOption recovery_option) {
     72   if (db_)
     73     return true;
     74 
     75   base::FilePath db_path = GetDatabasePath();
     76   if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path))
     77     return false;
     78 
     79   std::string path = FilePathToString(db_path);
     80   leveldb::Options options;
     81   options.max_open_files = 0;  // Use minimum.
     82   options.create_if_missing = true;
     83   if (env_override_)
     84     options.env = env_override_;
     85   leveldb::DB* db;
     86   leveldb::Status status = leveldb::DB::Open(options, path, &db);
     87   ReportInitStatus(status);
     88   if (status.ok()) {
     89     db_.reset(db);
     90     return true;
     91   }
     92   HandleError(FROM_HERE, status);
     93 
     94   // Corruption due to missing necessary MANIFEST-* file causes IOError instead
     95   // of Corruption error.
     96   // Try to repair database even when IOError case.
     97   if (!status.IsCorruption() && !status.IsIOError())
     98     return false;
     99 
    100   switch (recovery_option) {
    101     case FAIL_ON_CORRUPTION:
    102       return false;
    103     case REPAIR_ON_CORRUPTION:
    104       LOG(WARNING) << "Attempting to repair SandboxOriginDatabase.";
    105 
    106       if (RepairDatabase(path)) {
    107         UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    108                                   DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
    109         LOG(WARNING) << "Repairing SandboxOriginDatabase completed.";
    110         return true;
    111       }
    112       UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
    113                                 DB_REPAIR_FAILED, DB_REPAIR_MAX);
    114       // fall through
    115     case DELETE_ON_CORRUPTION:
    116       if (!base::DeleteFile(file_system_directory_, true))
    117         return false;
    118       if (!base::CreateDirectory(file_system_directory_))
    119         return false;
    120       return Init(init_option, FAIL_ON_CORRUPTION);
    121   }
    122   NOTREACHED();
    123   return false;
    124 }
    125 
    126 bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) {
    127   DCHECK(!db_.get());
    128   leveldb::Options options;
    129   options.max_open_files = 0;  // Use minimum.
    130   if (env_override_)
    131     options.env = env_override_;
    132   if (!leveldb::RepairDB(db_path, options).ok() ||
    133       !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) {
    134     LOG(WARNING) << "Failed to repair SandboxOriginDatabase.";
    135     return false;
    136   }
    137 
    138   // See if the repaired entries match with what we have on disk.
    139   std::set<base::FilePath> directories;
    140   base::FileEnumerator file_enum(file_system_directory_,
    141                                  false /* recursive */,
    142                                  base::FileEnumerator::DIRECTORIES);
    143   base::FilePath path_each;
    144   while (!(path_each = file_enum.Next()).empty())
    145     directories.insert(path_each.BaseName());
    146   std::set<base::FilePath>::iterator db_dir_itr =
    147       directories.find(base::FilePath(kOriginDatabaseName));
    148   // Make sure we have the database file in its directory and therefore we are
    149   // working on the correct path.
    150   DCHECK(db_dir_itr != directories.end());
    151   directories.erase(db_dir_itr);
    152 
    153   std::vector<OriginRecord> origins;
    154   if (!ListAllOrigins(&origins)) {
    155     DropDatabase();
    156     return false;
    157   }
    158 
    159   // Delete any obsolete entries from the origins database.
    160   for (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin();
    161        db_origin_itr != origins.end();
    162        ++db_origin_itr) {
    163     std::set<base::FilePath>::iterator dir_itr =
    164         directories.find(db_origin_itr->path);
    165     if (dir_itr == directories.end()) {
    166       if (!RemovePathForOrigin(db_origin_itr->origin)) {
    167         DropDatabase();
    168         return false;
    169       }
    170     } else {
    171       directories.erase(dir_itr);
    172     }
    173   }
    174 
    175   // Delete any directories not listed in the origins database.
    176   for (std::set<base::FilePath>::iterator dir_itr = directories.begin();
    177        dir_itr != directories.end();
    178        ++dir_itr) {
    179     if (!base::DeleteFile(file_system_directory_.Append(*dir_itr),
    180                            true /* recursive */)) {
    181       DropDatabase();
    182       return false;
    183     }
    184   }
    185 
    186   return true;
    187 }
    188 
    189 void SandboxOriginDatabase::HandleError(
    190     const tracked_objects::Location& from_here,
    191     const leveldb::Status& status) {
    192   db_.reset();
    193   LOG(ERROR) << "SandboxOriginDatabase failed at: "
    194              << from_here.ToString() << " with error: " << status.ToString();
    195 }
    196 
    197 void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) {
    198   base::Time now = base::Time::Now();
    199   base::TimeDelta minimum_interval =
    200       base::TimeDelta::FromHours(kMinimumReportIntervalHours);
    201   if (last_reported_time_ + minimum_interval >= now)
    202     return;
    203   last_reported_time_ = now;
    204 
    205   if (status.ok()) {
    206     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    207                               INIT_STATUS_OK, INIT_STATUS_MAX);
    208   } else if (status.IsCorruption()) {
    209     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    210                               INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
    211   } else if (status.IsIOError()) {
    212     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    213                               INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
    214   } else {
    215     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
    216                               INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
    217   }
    218 }
    219 
    220 bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) {
    221   if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
    222     return false;
    223   if (origin.empty())
    224     return false;
    225   std::string path;
    226   leveldb::Status status =
    227       db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path);
    228   if (status.ok())
    229     return true;
    230   if (status.IsNotFound())
    231     return false;
    232   HandleError(FROM_HERE, status);
    233   return false;
    234 }
    235 
    236 bool SandboxOriginDatabase::GetPathForOrigin(
    237     const std::string& origin, base::FilePath* directory) {
    238   if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
    239     return false;
    240   DCHECK(directory);
    241   if (origin.empty())
    242     return false;
    243   std::string path_string;
    244   std::string origin_key = OriginToOriginKey(origin);
    245   leveldb::Status status =
    246       db_->Get(leveldb::ReadOptions(), origin_key, &path_string);
    247   if (status.IsNotFound()) {
    248     int last_path_number;
    249     if (!GetLastPathNumber(&last_path_number))
    250       return false;
    251     path_string = base::StringPrintf("%03u", last_path_number + 1);
    252     // store both back as a single transaction
    253     leveldb::WriteBatch batch;
    254     batch.Put(LastPathKey(), path_string);
    255     batch.Put(origin_key, path_string);
    256     status = db_->Write(leveldb::WriteOptions(), &batch);
    257     if (!status.ok()) {
    258       HandleError(FROM_HERE, status);
    259       return false;
    260     }
    261   }
    262   if (status.ok()) {
    263     *directory = StringToFilePath(path_string);
    264     return true;
    265   }
    266   HandleError(FROM_HERE, status);
    267   return false;
    268 }
    269 
    270 bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) {
    271   if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
    272     return false;
    273   leveldb::Status status =
    274       db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin));
    275   if (status.ok() || status.IsNotFound())
    276     return true;
    277   HandleError(FROM_HERE, status);
    278   return false;
    279 }
    280 
    281 bool SandboxOriginDatabase::ListAllOrigins(
    282     std::vector<OriginRecord>* origins) {
    283   DCHECK(origins);
    284   if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) {
    285     origins->clear();
    286     return false;
    287   }
    288   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    289   std::string origin_key_prefix = OriginToOriginKey(std::string());
    290   iter->Seek(origin_key_prefix);
    291   origins->clear();
    292   while (iter->Valid() &&
    293       StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) {
    294     std::string origin =
    295       iter->key().ToString().substr(origin_key_prefix.length());
    296     base::FilePath path = StringToFilePath(iter->value().ToString());
    297     origins->push_back(OriginRecord(origin, path));
    298     iter->Next();
    299   }
    300   return true;
    301 }
    302 
    303 void SandboxOriginDatabase::DropDatabase() {
    304   db_.reset();
    305 }
    306 
    307 base::FilePath SandboxOriginDatabase::GetDatabasePath() const {
    308   return file_system_directory_.Append(kOriginDatabaseName);
    309 }
    310 
    311 void SandboxOriginDatabase::RemoveDatabase() {
    312   DropDatabase();
    313   base::DeleteFile(GetDatabasePath(), true /* recursive */);
    314 }
    315 
    316 bool SandboxOriginDatabase::GetLastPathNumber(int* number) {
    317   DCHECK(db_);
    318   DCHECK(number);
    319   std::string number_string;
    320   leveldb::Status status =
    321       db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string);
    322   if (status.ok())
    323     return base::StringToInt(number_string, number);
    324   if (!status.IsNotFound()) {
    325     HandleError(FROM_HERE, status);
    326     return false;
    327   }
    328   // Verify that this is a totally new database, and initialize it.
    329   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
    330   iter->SeekToFirst();
    331   if (iter->Valid()) {  // DB was not empty, but had no last path number!
    332     LOG(ERROR) << "File system origin database is corrupt!";
    333     return false;
    334   }
    335   // This is always the first write into the database.  If we ever add a
    336   // version number, they should go in in a single transaction.
    337   status =
    338       db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1"));
    339   if (!status.ok()) {
    340     HandleError(FROM_HERE, status);
    341     return false;
    342   }
    343   *number = -1;
    344   return true;
    345 }
    346 
    347 }  // namespace fileapi
    348