// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/fileapi/file_system_origin_database.h" #include #include "base/file_util.h" #include "base/format_macros.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/string_number_conversions.h" #include "base/stringprintf.h" #include "base/string_util.h" #include "third_party/leveldatabase/src/include/leveldb/db.h" #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" #include "webkit/fileapi/file_system_util.h" namespace { const base::FilePath::CharType kOriginDatabaseName[] = FILE_PATH_LITERAL("Origins"); const char kOriginKeyPrefix[] = "ORIGIN:"; const char kLastPathKey[] = "LAST_PATH"; const int64 kMinimumReportIntervalHours = 1; const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit"; enum InitStatus { INIT_STATUS_OK = 0, INIT_STATUS_CORRUPTION, INIT_STATUS_IO_ERROR, INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX }; std::string OriginToOriginKey(const std::string& origin) { std::string key(kOriginKeyPrefix); return key + origin; } const char* LastPathKey() { return kLastPathKey; } } // namespace namespace fileapi { FileSystemOriginDatabase::OriginRecord::OriginRecord() { } FileSystemOriginDatabase::OriginRecord::OriginRecord( const std::string& origin_in, const base::FilePath& path_in) : origin(origin_in), path(path_in) { } FileSystemOriginDatabase::OriginRecord::~OriginRecord() { } FileSystemOriginDatabase::FileSystemOriginDatabase( const base::FilePath& file_system_directory) : file_system_directory_(file_system_directory) { } FileSystemOriginDatabase::~FileSystemOriginDatabase() { } bool FileSystemOriginDatabase::Init(RecoveryOption recovery_option) { if (db_.get()) return true; std::string path = FilePathToString(file_system_directory_.Append(kOriginDatabaseName)); leveldb::Options options; options.create_if_missing = true; leveldb::DB* db; leveldb::Status status = leveldb::DB::Open(options, path, &db); ReportInitStatus(status); if (status.ok()) { db_.reset(db); return true; } HandleError(FROM_HERE, status); if (!status.IsCorruption()) return false; switch (recovery_option) { case FAIL_ON_CORRUPTION: return false; case REPAIR_ON_CORRUPTION: LOG(WARNING) << "Attempting to repair FileSystemOriginDatabase."; if (RepairDatabase(path)) { LOG(WARNING) << "Repairing FileSystemOriginDatabase completed."; return true; } // fall through case DELETE_ON_CORRUPTION: if (!file_util::Delete(file_system_directory_, true)) return false; if (!file_util::CreateDirectory(file_system_directory_)) return false; return Init(FAIL_ON_CORRUPTION); } NOTREACHED(); return false; } bool FileSystemOriginDatabase::RepairDatabase(const std::string& db_path) { DCHECK(!db_.get()); if (!leveldb::RepairDB(db_path, leveldb::Options()).ok() || !Init(FAIL_ON_CORRUPTION)) { LOG(WARNING) << "Failed to repair FileSystemOriginDatabase."; return false; } // See if the repaired entries match with what we have on disk. std::set directories; file_util::FileEnumerator file_enum(file_system_directory_, false /* recursive */, file_util::FileEnumerator::DIRECTORIES); base::FilePath path_each; while (!(path_each = file_enum.Next()).empty()) directories.insert(path_each.BaseName()); std::set::iterator db_dir_itr = directories.find(base::FilePath(kOriginDatabaseName)); // Make sure we have the database file in its directory and therefore we are // working on the correct path. DCHECK(db_dir_itr != directories.end()); directories.erase(db_dir_itr); std::vector origins; if (!ListAllOrigins(&origins)) { DropDatabase(); return false; } // Delete any obsolete entries from the origins database. for (std::vector::iterator db_origin_itr = origins.begin(); db_origin_itr != origins.end(); ++db_origin_itr) { std::set::iterator dir_itr = directories.find(db_origin_itr->path); if (dir_itr == directories.end()) { if (!RemovePathForOrigin(db_origin_itr->origin)) { DropDatabase(); return false; } } else { directories.erase(dir_itr); } } // Delete any directories not listed in the origins database. for (std::set::iterator dir_itr = directories.begin(); dir_itr != directories.end(); ++dir_itr) { if (!file_util::Delete(file_system_directory_.Append(*dir_itr), true /* recursive */)) { DropDatabase(); return false; } } return true; } void FileSystemOriginDatabase::HandleError( const tracked_objects::Location& from_here, const leveldb::Status& status) { db_.reset(); LOG(ERROR) << "FileSystemOriginDatabase failed at: " << from_here.ToString() << " with error: " << status.ToString(); } void FileSystemOriginDatabase::ReportInitStatus(const leveldb::Status& status) { base::Time now = base::Time::Now(); base::TimeDelta minimum_interval = base::TimeDelta::FromHours(kMinimumReportIntervalHours); if (last_reported_time_ + minimum_interval >= now) return; last_reported_time_ = now; if (status.ok()) { UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, INIT_STATUS_OK, INIT_STATUS_MAX); } else if (status.IsCorruption()) { UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); } else if (status.IsIOError()) { UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); } else { UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); } } bool FileSystemOriginDatabase::HasOriginPath(const std::string& origin) { if (!Init(REPAIR_ON_CORRUPTION)) return false; if (origin.empty()) return false; std::string path; leveldb::Status status = db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path); if (status.ok()) return true; if (status.IsNotFound()) return false; HandleError(FROM_HERE, status); return false; } bool FileSystemOriginDatabase::GetPathForOrigin( const std::string& origin, base::FilePath* directory) { if (!Init(REPAIR_ON_CORRUPTION)) return false; DCHECK(directory); if (origin.empty()) return false; std::string path_string; std::string origin_key = OriginToOriginKey(origin); leveldb::Status status = db_->Get(leveldb::ReadOptions(), origin_key, &path_string); if (status.IsNotFound()) { int last_path_number; if (!GetLastPathNumber(&last_path_number)) return false; path_string = StringPrintf("%03u", last_path_number + 1); // store both back as a single transaction leveldb::WriteBatch batch; batch.Put(LastPathKey(), path_string); batch.Put(origin_key, path_string); status = db_->Write(leveldb::WriteOptions(), &batch); if (!status.ok()) { HandleError(FROM_HERE, status); return false; } } if (status.ok()) { *directory = StringToFilePath(path_string); return true; } HandleError(FROM_HERE, status); return false; } bool FileSystemOriginDatabase::RemovePathForOrigin(const std::string& origin) { if (!Init(REPAIR_ON_CORRUPTION)) return false; leveldb::Status status = db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin)); if (status.ok() || status.IsNotFound()) return true; HandleError(FROM_HERE, status); return false; } bool FileSystemOriginDatabase::ListAllOrigins( std::vector* origins) { if (!Init(REPAIR_ON_CORRUPTION)) return false; DCHECK(origins); scoped_ptr iter(db_->NewIterator(leveldb::ReadOptions())); std::string origin_key_prefix = OriginToOriginKey(""); iter->Seek(origin_key_prefix); origins->clear(); while (iter->Valid() && StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) { std::string origin = iter->key().ToString().substr(origin_key_prefix.length()); base::FilePath path = StringToFilePath(iter->value().ToString()); origins->push_back(OriginRecord(origin, path)); iter->Next(); } return true; } void FileSystemOriginDatabase::DropDatabase() { db_.reset(); } bool FileSystemOriginDatabase::GetLastPathNumber(int* number) { if (!Init(REPAIR_ON_CORRUPTION)) return false; DCHECK(number); std::string number_string; leveldb::Status status = db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string); if (status.ok()) return base::StringToInt(number_string, number); if (!status.IsNotFound()) { HandleError(FROM_HERE, status); return false; } // Verify that this is a totally new database, and initialize it. scoped_ptr iter(db_->NewIterator(leveldb::ReadOptions())); iter->SeekToFirst(); if (iter->Valid()) { // DB was not empty, but had no last path number! LOG(ERROR) << "File system origin database is corrupt!"; return false; } // This is always the first write into the database. If we ever add a // version number, they should go in in a single transaction. status = db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1")); if (!status.ok()) { HandleError(FROM_HERE, status); return false; } *number = -1; return true; } } // namespace fileapi