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"
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"
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";
36 INIT_STATUS_CORRUPTION
,
38 INIT_STATUS_UNKNOWN_ERROR
,
43 DB_REPAIR_SUCCEEDED
= 0,
48 std::string
OriginToOriginKey(const std::string
& origin
) {
49 std::string
key(kOriginKeyPrefix
);
53 const char* LastPathKey() {
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
) {
76 base::FilePath db_path
= GetDatabasePath();
77 if (init_option
== FAIL_IF_NONEXISTENT
&& !base::PathExists(db_path
))
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
;
86 options
.env
= env_override_
;
88 leveldb::Status status
= leveldb::DB::Open(options
, path
, &db
);
89 ReportInitStatus(status
);
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())
102 switch (recovery_option
) {
103 case FAIL_ON_CORRUPTION
:
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.";
114 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel
,
115 DB_REPAIR_FAILED
, DB_REPAIR_MAX
);
117 case DELETE_ON_CORRUPTION
:
118 if (!base::DeleteFile(file_system_directory_
, true))
120 if (!base::CreateDirectory(file_system_directory_
))
122 return Init(init_option
, FAIL_ON_CORRUPTION
);
128 bool SandboxOriginDatabase::RepairDatabase(const std::string
& db_path
) {
130 leveldb::Options options
;
131 options
.max_open_files
= 0; // Use minimum.
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.";
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
)) {
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();
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
)) {
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();
181 if (!base::DeleteFile(file_system_directory_
.Append(*dir_itr
),
182 true /* recursive */)) {
191 void SandboxOriginDatabase::HandleError(
192 const tracked_objects::Location
& from_here
,
193 const leveldb::Status
& status
) {
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
)
205 last_reported_time_
= now
;
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
);
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
))
228 leveldb::Status status
=
229 db_
->Get(leveldb::ReadOptions(), OriginToOriginKey(origin
), &path
);
232 if (status
.IsNotFound())
234 HandleError(FROM_HERE
, status
);
238 bool SandboxOriginDatabase::GetPathForOrigin(
239 const std::string
& origin
, base::FilePath
* directory
) {
240 if (!Init(CREATE_IF_NONEXISTENT
, REPAIR_ON_CORRUPTION
))
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
))
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
);
260 HandleError(FROM_HERE
, status
);
265 *directory
= StringToFilePath(path_string
);
268 HandleError(FROM_HERE
, status
);
272 bool SandboxOriginDatabase::RemovePathForOrigin(const std::string
& origin
) {
273 if (!Init(CREATE_IF_NONEXISTENT
, REPAIR_ON_CORRUPTION
))
275 leveldb::Status status
=
276 db_
->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin
));
277 if (status
.ok() || status
.IsNotFound())
279 HandleError(FROM_HERE
, status
);
283 bool SandboxOriginDatabase::ListAllOrigins(
284 std::vector
<OriginRecord
>* origins
) {
286 if (!Init(CREATE_IF_NONEXISTENT
, REPAIR_ON_CORRUPTION
)) {
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
);
294 while (iter
->Valid() &&
295 StartsWithASCII(iter
->key().ToString(), origin_key_prefix
, true)) {
297 iter
->key().ToString().substr(origin_key_prefix
.length());
298 base::FilePath path
= StringToFilePath(iter
->value().ToString());
299 origins
->push_back(OriginRecord(origin
, path
));
305 void SandboxOriginDatabase::DropDatabase() {
309 base::FilePath
SandboxOriginDatabase::GetDatabasePath() const {
310 return file_system_directory_
.Append(kOriginDatabaseName
);
313 void SandboxOriginDatabase::RemoveDatabase() {
315 base::DeleteFile(GetDatabasePath(), true /* recursive */);
318 bool SandboxOriginDatabase::GetLastPathNumber(int* number
) {
321 std::string number_string
;
322 leveldb::Status status
=
323 db_
->Get(leveldb::ReadOptions(), LastPathKey(), &number_string
);
325 return base::StringToInt(number_string
, number
);
326 if (!status
.IsNotFound()) {
327 HandleError(FROM_HERE
, status
);
330 // Verify that this is a totally new database, and initialize it.
331 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
333 if (iter
->Valid()) { // DB was not empty, but had no last path number!
334 LOG(ERROR
) << "File system origin database is corrupt!";
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.
340 db_
->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1"));
342 HandleError(FROM_HERE
, status
);
349 } // namespace storage