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 "storage/browser/fileapi/sandbox_origin_database.h"
6
7 #include <set>
8 #include <utility>
9
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/src/include/leveldb/db.h"
21 #include "third_party/leveldatabase/src/include/leveldb/write_batch.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
OriginToOriginKey(const std::string & origin)47 std::string OriginToOriginKey(const std::string& origin) {
48 std::string key(kOriginKeyPrefix);
49 return key + origin;
50 }
51
LastPathKey()52 const char* LastPathKey() {
53 return kLastPathKey;
54 }
55
56 } // namespace
57
58 namespace storage {
59
SandboxOriginDatabase(const base::FilePath & file_system_directory,leveldb::Env * env_override)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
~SandboxOriginDatabase()67 SandboxOriginDatabase::~SandboxOriginDatabase() {
68 }
69
Init(InitOption init_option,RecoveryOption recovery_option)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
RepairDatabase(const std::string & db_path)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
HandleError(const tracked_objects::Location & from_here,const leveldb::Status & status)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
ReportInitStatus(const leveldb::Status & status)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
HasOriginPath(const std::string & origin)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
GetPathForOrigin(const std::string & origin,base::FilePath * directory)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
RemovePathForOrigin(const std::string & origin)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
ListAllOrigins(std::vector<OriginRecord> * origins)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
DropDatabase()303 void SandboxOriginDatabase::DropDatabase() {
304 db_.reset();
305 }
306
GetDatabasePath() const307 base::FilePath SandboxOriginDatabase::GetDatabasePath() const {
308 return file_system_directory_.Append(kOriginDatabaseName);
309 }
310
RemoveDatabase()311 void SandboxOriginDatabase::RemoveDatabase() {
312 DropDatabase();
313 base::DeleteFile(GetDatabasePath(), true /* recursive */);
314 }
315
GetLastPathNumber(int * number)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 storage
348