| // 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 "storage/browser/fileapi/sandbox_origin_database.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <set> |
| #include <utility> |
| |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "storage/common/fileapi/file_system_util.h" |
| #include "third_party/leveldatabase/env_chromium.h" |
| #include "third_party/leveldatabase/src/include/leveldb/db.h" |
| #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
| |
| namespace { |
| |
| const base::FilePath::CharType kOriginDatabaseName[] = |
| FILE_PATH_LITERAL("Origins"); |
| const char kOriginKeyPrefix[] = "ORIGIN:"; |
| const char kSandboxOriginLastPathKey[] = "LAST_PATH"; |
| const int64_t kSandboxOriginMinimumReportIntervalHours = 1; |
| const char kSandboxOriginInitStatusHistogramLabel[] = |
| "FileSystem.OriginDatabaseInit"; |
| const char kSandboxOriginDatabaseRepairHistogramLabel[] = |
| "FileSystem.OriginDatabaseRepair"; |
| |
| enum class InitSandboxOriginStatus { |
| INIT_STATUS_OK = 0, |
| INIT_STATUS_CORRUPTION, |
| INIT_STATUS_IO_ERROR, |
| INIT_STATUS_UNKNOWN_ERROR, |
| INIT_STATUS_MAX |
| }; |
| |
| enum class SandboxOriginRepairResult { |
| DB_REPAIR_SUCCEEDED = 0, |
| DB_REPAIR_FAILED, |
| DB_REPAIR_MAX |
| }; |
| |
| std::string OriginToOriginKey(const std::string& origin) { |
| std::string key(kOriginKeyPrefix); |
| return key + origin; |
| } |
| |
| const char* LastPathKey() { |
| return kSandboxOriginLastPathKey; |
| } |
| |
| } // namespace |
| |
| namespace storage { |
| |
| SandboxOriginDatabase::SandboxOriginDatabase( |
| const base::FilePath& file_system_directory, |
| leveldb::Env* env_override) |
| : file_system_directory_(file_system_directory), |
| env_override_(env_override) { |
| } |
| |
| SandboxOriginDatabase::~SandboxOriginDatabase() = default; |
| |
| bool SandboxOriginDatabase::Init(InitOption init_option, |
| RecoveryOption recovery_option) { |
| if (db_) |
| return true; |
| |
| base::FilePath db_path = GetDatabasePath(); |
| if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path)) |
| return false; |
| |
| std::string path = FilePathToString(db_path); |
| leveldb_env::Options options; |
| options.max_open_files = 0; // Use minimum. |
| options.create_if_missing = true; |
| if (env_override_) |
| options.env = env_override_; |
| leveldb::Status status = leveldb_env::OpenDB(options, path, &db_); |
| ReportInitStatus(status); |
| if (status.ok()) { |
| return true; |
| } |
| HandleError(FROM_HERE, status); |
| |
| // Corruption due to missing necessary MANIFEST-* file causes IOError instead |
| // of Corruption error. |
| // Try to repair database even when IOError case. |
| if (!status.IsCorruption() && !status.IsIOError()) |
| return false; |
| |
| switch (recovery_option) { |
| case FAIL_ON_CORRUPTION: |
| return false; |
| case REPAIR_ON_CORRUPTION: |
| LOG(WARNING) << "Attempting to repair SandboxOriginDatabase."; |
| |
| if (RepairDatabase(path)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| kSandboxOriginDatabaseRepairHistogramLabel, |
| SandboxOriginRepairResult::DB_REPAIR_SUCCEEDED, |
| SandboxOriginRepairResult::DB_REPAIR_MAX); |
| LOG(WARNING) << "Repairing SandboxOriginDatabase completed."; |
| return true; |
| } |
| UMA_HISTOGRAM_ENUMERATION(kSandboxOriginDatabaseRepairHistogramLabel, |
| SandboxOriginRepairResult::DB_REPAIR_FAILED, |
| SandboxOriginRepairResult::DB_REPAIR_MAX); |
| FALLTHROUGH; |
| case DELETE_ON_CORRUPTION: |
| if (!base::DeleteFile(file_system_directory_, true)) |
| return false; |
| if (!base::CreateDirectory(file_system_directory_)) |
| return false; |
| return Init(init_option, FAIL_ON_CORRUPTION); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) { |
| DCHECK(!db_.get()); |
| leveldb_env::Options options; |
| options.reuse_logs = false; |
| options.max_open_files = 0; // Use minimum. |
| if (env_override_) |
| options.env = env_override_; |
| if (!leveldb::RepairDB(db_path, options).ok() || |
| !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) { |
| LOG(WARNING) << "Failed to repair SandboxOriginDatabase."; |
| return false; |
| } |
| |
| // See if the repaired entries match with what we have on disk. |
| std::set<base::FilePath> directories; |
| base::FileEnumerator file_enum(file_system_directory_, |
| false /* recursive */, |
| base::FileEnumerator::DIRECTORIES); |
| base::FilePath path_each; |
| while (!(path_each = file_enum.Next()).empty()) |
| directories.insert(path_each.BaseName()); |
| auto 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<OriginRecord> origins; |
| if (!ListAllOrigins(&origins)) { |
| DropDatabase(); |
| return false; |
| } |
| |
| // Delete any obsolete entries from the origins database. |
| for (const OriginRecord& record : origins) { |
| auto dir_itr = directories.find(record.path); |
| if (dir_itr == directories.end()) { |
| if (!RemovePathForOrigin(record.origin)) { |
| DropDatabase(); |
| return false; |
| } |
| } else { |
| directories.erase(dir_itr); |
| } |
| } |
| |
| // Delete any directories not listed in the origins database. |
| for (const base::FilePath& dir : directories) { |
| if (!base::DeleteFile(file_system_directory_.Append(dir), |
| true /* recursive */)) { |
| DropDatabase(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void SandboxOriginDatabase::HandleError(const base::Location& from_here, |
| const leveldb::Status& status) { |
| db_.reset(); |
| LOG(ERROR) << "SandboxOriginDatabase failed at: " |
| << from_here.ToString() << " with error: " << status.ToString(); |
| } |
| |
| void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) { |
| base::Time now = base::Time::Now(); |
| base::TimeDelta minimum_interval = |
| base::TimeDelta::FromHours(kSandboxOriginMinimumReportIntervalHours); |
| if (last_reported_time_ + minimum_interval >= now) |
| return; |
| last_reported_time_ = now; |
| |
| if (status.ok()) { |
| UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel, |
| InitSandboxOriginStatus::INIT_STATUS_OK, |
| InitSandboxOriginStatus::INIT_STATUS_MAX); |
| } else if (status.IsCorruption()) { |
| UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel, |
| InitSandboxOriginStatus::INIT_STATUS_CORRUPTION, |
| InitSandboxOriginStatus::INIT_STATUS_MAX); |
| } else if (status.IsIOError()) { |
| UMA_HISTOGRAM_ENUMERATION(kSandboxOriginInitStatusHistogramLabel, |
| InitSandboxOriginStatus::INIT_STATUS_IO_ERROR, |
| InitSandboxOriginStatus::INIT_STATUS_MAX); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| kSandboxOriginInitStatusHistogramLabel, |
| InitSandboxOriginStatus::INIT_STATUS_UNKNOWN_ERROR, |
| InitSandboxOriginStatus::INIT_STATUS_MAX); |
| } |
| } |
| |
| bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) { |
| if (!Init(FAIL_IF_NONEXISTENT, 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 SandboxOriginDatabase::GetPathForOrigin( |
| const std::string& origin, base::FilePath* directory) { |
| if (!Init(CREATE_IF_NONEXISTENT, 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 = base::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 SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) { |
| if (!Init(CREATE_IF_NONEXISTENT, 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 SandboxOriginDatabase::ListAllOrigins( |
| std::vector<OriginRecord>* origins) { |
| DCHECK(origins); |
| if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) { |
| origins->clear(); |
| return false; |
| } |
| std::unique_ptr<leveldb::Iterator> iter( |
| db_->NewIterator(leveldb::ReadOptions())); |
| std::string origin_key_prefix = OriginToOriginKey(std::string()); |
| iter->Seek(origin_key_prefix); |
| origins->clear(); |
| while (iter->Valid() && base::StartsWith(iter->key().ToString(), |
| origin_key_prefix, |
| base::CompareCase::SENSITIVE)) { |
| 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 SandboxOriginDatabase::DropDatabase() { |
| db_.reset(); |
| } |
| |
| void SandboxOriginDatabase::RewriteDatabase() { |
| if (!Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) |
| return; |
| base::FilePath db_path = GetDatabasePath(); |
| std::string path = FilePathToString(db_path); |
| leveldb_env::Options options; |
| options.max_open_files = 0; // Use minimum. |
| options.create_if_missing = true; |
| if (env_override_) |
| options.env = env_override_; |
| // There is a possibility that |db_| is null after this call. This case |
| // will be handled by the |!Init(...)| checks above each method. |
| leveldb_env::RewriteDB(options, path, &db_); |
| } |
| |
| base::FilePath SandboxOriginDatabase::GetDatabasePath() const { |
| return file_system_directory_.Append(kOriginDatabaseName); |
| } |
| |
| void SandboxOriginDatabase::RemoveDatabase() { |
| DropDatabase(); |
| base::DeleteFile(GetDatabasePath(), true /* recursive */); |
| } |
| |
| bool SandboxOriginDatabase::GetLastPathNumber(int* number) { |
| DCHECK(db_); |
| 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. |
| { |
| // Scope the iterator to ensure it is deleted before database is closed. |
| std::unique_ptr<leveldb::Iterator> 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 storage |