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