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_directory_database.h"
6
7 #include <math.h>
8 #include <algorithm>
9 #include <set>
10 #include <stack>
11
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram.h"
16 #include "base/pickle.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "storage/browser/fileapi/file_system_usage_cache.h"
20 #include "storage/common/fileapi/file_system_util.h"
21 #include "third_party/leveldatabase/src/include/leveldb/db.h"
22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
23
24 namespace {
25
PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo & info,Pickle * pickle)26 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
27 Pickle* pickle) {
28 DCHECK(pickle);
29 std::string data_path;
30 // Round off here to match the behavior of the filesystem on real files.
31 base::Time time =
32 base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
33 std::string name;
34
35 data_path = storage::FilePathToString(info.data_path);
36 name = storage::FilePathToString(base::FilePath(info.name));
37
38 if (pickle->WriteInt64(info.parent_id) &&
39 pickle->WriteString(data_path) &&
40 pickle->WriteString(name) &&
41 pickle->WriteInt64(time.ToInternalValue()))
42 return true;
43
44 NOTREACHED();
45 return false;
46 }
47
FileInfoFromPickle(const Pickle & pickle,storage::SandboxDirectoryDatabase::FileInfo * info)48 bool FileInfoFromPickle(const Pickle& pickle,
49 storage::SandboxDirectoryDatabase::FileInfo* info) {
50 PickleIterator iter(pickle);
51 std::string data_path;
52 std::string name;
53 int64 internal_time;
54
55 if (pickle.ReadInt64(&iter, &info->parent_id) &&
56 pickle.ReadString(&iter, &data_path) &&
57 pickle.ReadString(&iter, &name) &&
58 pickle.ReadInt64(&iter, &internal_time)) {
59 info->data_path = storage::StringToFilePath(data_path);
60 info->name = storage::StringToFilePath(name).value();
61 info->modification_time = base::Time::FromInternalValue(internal_time);
62 return true;
63 }
64 LOG(ERROR) << "Pickle could not be digested!";
65 return false;
66 }
67
68 const base::FilePath::CharType kDirectoryDatabaseName[] =
69 FILE_PATH_LITERAL("Paths");
70 const char kChildLookupPrefix[] = "CHILD_OF:";
71 const char kChildLookupSeparator[] = ":";
72 const char kLastFileIdKey[] = "LAST_FILE_ID";
73 const char kLastIntegerKey[] = "LAST_INTEGER";
74 const int64 kMinimumReportIntervalHours = 1;
75 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
76 const char kDatabaseRepairHistogramLabel[] =
77 "FileSystem.DirectoryDatabaseRepair";
78
79 enum InitStatus {
80 INIT_STATUS_OK = 0,
81 INIT_STATUS_CORRUPTION,
82 INIT_STATUS_IO_ERROR,
83 INIT_STATUS_UNKNOWN_ERROR,
84 INIT_STATUS_MAX
85 };
86
87 enum RepairResult {
88 DB_REPAIR_SUCCEEDED = 0,
89 DB_REPAIR_FAILED,
90 DB_REPAIR_MAX
91 };
92
GetChildLookupKey(storage::SandboxDirectoryDatabase::FileId parent_id,const base::FilePath::StringType & child_name)93 std::string GetChildLookupKey(
94 storage::SandboxDirectoryDatabase::FileId parent_id,
95 const base::FilePath::StringType& child_name) {
96 std::string name;
97 name = storage::FilePathToString(base::FilePath(child_name));
98 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
99 std::string(kChildLookupSeparator) + name;
100 }
101
GetChildListingKeyPrefix(storage::SandboxDirectoryDatabase::FileId parent_id)102 std::string GetChildListingKeyPrefix(
103 storage::SandboxDirectoryDatabase::FileId parent_id) {
104 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
105 std::string(kChildLookupSeparator);
106 }
107
LastFileIdKey()108 const char* LastFileIdKey() {
109 return kLastFileIdKey;
110 }
111
LastIntegerKey()112 const char* LastIntegerKey() {
113 return kLastIntegerKey;
114 }
115
GetFileLookupKey(storage::SandboxDirectoryDatabase::FileId file_id)116 std::string GetFileLookupKey(
117 storage::SandboxDirectoryDatabase::FileId file_id) {
118 return base::Int64ToString(file_id);
119 }
120
121 // Assumptions:
122 // - Any database entry is one of:
123 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
124 // - ("LAST_FILE_ID", "|last_file_id|"),
125 // - ("LAST_INTEGER", "|last_integer|"),
126 // - ("|file_id|", "pickled FileInfo")
127 // where FileInfo has |parent_id|, |data_path|, |name| and
128 // |modification_time|,
129 // Constraints:
130 // - Each file in the database has unique backing file.
131 // - Each file in |filesystem_data_directory_| has a database entry.
132 // - Directory structure is tree, i.e. connected and acyclic.
133 class DatabaseCheckHelper {
134 public:
135 typedef storage::SandboxDirectoryDatabase::FileId FileId;
136 typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
137
138 DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
139 leveldb::DB* db,
140 const base::FilePath& path);
141
IsFileSystemConsistent()142 bool IsFileSystemConsistent() {
143 return IsDatabaseEmpty() ||
144 (ScanDatabase() && ScanDirectory() && ScanHierarchy());
145 }
146
147 private:
148 bool IsDatabaseEmpty();
149 // These 3 methods need to be called in the order. Each method requires its
150 // previous method finished successfully. They also require the database is
151 // not empty.
152 bool ScanDatabase();
153 bool ScanDirectory();
154 bool ScanHierarchy();
155
156 storage::SandboxDirectoryDatabase* dir_db_;
157 leveldb::DB* db_;
158 base::FilePath path_;
159
160 std::set<base::FilePath> files_in_db_;
161
162 size_t num_directories_in_db_;
163 size_t num_files_in_db_;
164 size_t num_hierarchy_links_in_db_;
165
166 FileId last_file_id_;
167 FileId last_integer_;
168 };
169
DatabaseCheckHelper(storage::SandboxDirectoryDatabase * dir_db,leveldb::DB * db,const base::FilePath & path)170 DatabaseCheckHelper::DatabaseCheckHelper(
171 storage::SandboxDirectoryDatabase* dir_db,
172 leveldb::DB* db,
173 const base::FilePath& path)
174 : dir_db_(dir_db),
175 db_(db),
176 path_(path),
177 num_directories_in_db_(0),
178 num_files_in_db_(0),
179 num_hierarchy_links_in_db_(0),
180 last_file_id_(-1),
181 last_integer_(-1) {
182 DCHECK(dir_db_);
183 DCHECK(db_);
184 DCHECK(!path_.empty() && base::DirectoryExists(path_));
185 }
186
IsDatabaseEmpty()187 bool DatabaseCheckHelper::IsDatabaseEmpty() {
188 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
189 itr->SeekToFirst();
190 return !itr->Valid();
191 }
192
ScanDatabase()193 bool DatabaseCheckHelper::ScanDatabase() {
194 // Scans all database entries sequentially to verify each of them has unique
195 // backing file.
196 int64 max_file_id = -1;
197 std::set<FileId> file_ids;
198
199 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
200 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
201 std::string key = itr->key().ToString();
202 if (StartsWithASCII(key, kChildLookupPrefix, true)) {
203 // key: "CHILD_OF:<parent_id>:<name>"
204 // value: "<child_id>"
205 ++num_hierarchy_links_in_db_;
206 } else if (key == kLastFileIdKey) {
207 // key: "LAST_FILE_ID"
208 // value: "<last_file_id>"
209 if (last_file_id_ >= 0 ||
210 !base::StringToInt64(itr->value().ToString(), &last_file_id_))
211 return false;
212
213 if (last_file_id_ < 0)
214 return false;
215 } else if (key == kLastIntegerKey) {
216 // key: "LAST_INTEGER"
217 // value: "<last_integer>"
218 if (last_integer_ >= 0 ||
219 !base::StringToInt64(itr->value().ToString(), &last_integer_))
220 return false;
221 } else {
222 // key: "<entry_id>"
223 // value: "<pickled FileInfo>"
224 FileInfo file_info;
225 if (!FileInfoFromPickle(
226 Pickle(itr->value().data(), itr->value().size()), &file_info))
227 return false;
228
229 FileId file_id = -1;
230 if (!base::StringToInt64(key, &file_id) || file_id < 0)
231 return false;
232
233 if (max_file_id < file_id)
234 max_file_id = file_id;
235 if (!file_ids.insert(file_id).second)
236 return false;
237
238 if (file_info.is_directory()) {
239 ++num_directories_in_db_;
240 DCHECK(file_info.data_path.empty());
241 } else {
242 // Ensure any pair of file entry don't share their data_path.
243 if (!files_in_db_.insert(file_info.data_path).second)
244 return false;
245
246 // Ensure the backing file exists as a normal file.
247 base::File::Info platform_file_info;
248 if (!base::GetFileInfo(
249 path_.Append(file_info.data_path), &platform_file_info) ||
250 platform_file_info.is_directory ||
251 platform_file_info.is_symbolic_link) {
252 // leveldb::Iterator iterates a snapshot of the database.
253 // So even after RemoveFileInfo() call, we'll visit hierarchy link
254 // from |parent_id| to |file_id|.
255 if (!dir_db_->RemoveFileInfo(file_id))
256 return false;
257 --num_hierarchy_links_in_db_;
258 files_in_db_.erase(file_info.data_path);
259 } else {
260 ++num_files_in_db_;
261 }
262 }
263 }
264 }
265
266 // TODO(tzik): Add constraint for |last_integer_| to avoid possible
267 // data path confliction on ObfuscatedFileUtil.
268 return max_file_id <= last_file_id_;
269 }
270
ScanDirectory()271 bool DatabaseCheckHelper::ScanDirectory() {
272 // TODO(kinuko): Scans all local file system entries to verify each of them
273 // has a database entry.
274 const base::FilePath kExcludes[] = {
275 base::FilePath(kDirectoryDatabaseName),
276 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
277 };
278
279 // Any path in |pending_directories| is relative to |path_|.
280 std::stack<base::FilePath> pending_directories;
281 pending_directories.push(base::FilePath());
282
283 while (!pending_directories.empty()) {
284 base::FilePath dir_path = pending_directories.top();
285 pending_directories.pop();
286
287 base::FileEnumerator file_enum(
288 dir_path.empty() ? path_ : path_.Append(dir_path),
289 false /* not recursive */,
290 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
291
292 base::FilePath absolute_file_path;
293 while (!(absolute_file_path = file_enum.Next()).empty()) {
294 base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
295
296 base::FilePath relative_file_path;
297 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
298 return false;
299
300 if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
301 relative_file_path) != kExcludes + arraysize(kExcludes))
302 continue;
303
304 if (find_info.IsDirectory()) {
305 pending_directories.push(relative_file_path);
306 continue;
307 }
308
309 // Check if the file has a database entry.
310 std::set<base::FilePath>::iterator itr =
311 files_in_db_.find(relative_file_path);
312 if (itr == files_in_db_.end()) {
313 if (!base::DeleteFile(absolute_file_path, false))
314 return false;
315 } else {
316 files_in_db_.erase(itr);
317 }
318 }
319 }
320
321 return files_in_db_.empty();
322 }
323
ScanHierarchy()324 bool DatabaseCheckHelper::ScanHierarchy() {
325 size_t visited_directories = 0;
326 size_t visited_files = 0;
327 size_t visited_links = 0;
328
329 std::stack<FileId> directories;
330 directories.push(0);
331
332 // Check if the root directory exists as a directory.
333 FileInfo file_info;
334 if (!dir_db_->GetFileInfo(0, &file_info))
335 return false;
336 if (file_info.parent_id != 0 ||
337 !file_info.is_directory())
338 return false;
339
340 while (!directories.empty()) {
341 ++visited_directories;
342 FileId dir_id = directories.top();
343 directories.pop();
344
345 std::vector<FileId> children;
346 if (!dir_db_->ListChildren(dir_id, &children))
347 return false;
348 for (std::vector<FileId>::iterator itr = children.begin();
349 itr != children.end();
350 ++itr) {
351 // Any directory must not have root directory as child.
352 if (!*itr)
353 return false;
354
355 // Check if the child knows the parent as its parent.
356 FileInfo file_info;
357 if (!dir_db_->GetFileInfo(*itr, &file_info))
358 return false;
359 if (file_info.parent_id != dir_id)
360 return false;
361
362 // Check if the parent knows the name of its child correctly.
363 FileId file_id;
364 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
365 file_id != *itr)
366 return false;
367
368 if (file_info.is_directory())
369 directories.push(*itr);
370 else
371 ++visited_files;
372 ++visited_links;
373 }
374 }
375
376 // Check if we've visited all database entries.
377 return num_directories_in_db_ == visited_directories &&
378 num_files_in_db_ == visited_files &&
379 num_hierarchy_links_in_db_ == visited_links;
380 }
381
382 // Returns true if the given |data_path| contains no parent references ("..")
383 // and does not refer to special system files.
384 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
385 // ensure we're only dealing with valid data paths.
VerifyDataPath(const base::FilePath & data_path)386 bool VerifyDataPath(const base::FilePath& data_path) {
387 // |data_path| should not contain any ".." and should be a relative path
388 // (to the filesystem_data_directory_).
389 if (data_path.ReferencesParent() || data_path.IsAbsolute())
390 return false;
391 // See if it's not pointing to the special system paths.
392 const base::FilePath kExcludes[] = {
393 base::FilePath(kDirectoryDatabaseName),
394 base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
395 };
396 for (size_t i = 0; i < arraysize(kExcludes); ++i) {
397 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
398 return false;
399 }
400 return true;
401 }
402
403 } // namespace
404
405 namespace storage {
406
FileInfo()407 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
408 }
409
~FileInfo()410 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
411 }
412
SandboxDirectoryDatabase(const base::FilePath & filesystem_data_directory,leveldb::Env * env_override)413 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
414 const base::FilePath& filesystem_data_directory,
415 leveldb::Env* env_override)
416 : filesystem_data_directory_(filesystem_data_directory),
417 env_override_(env_override) {
418 }
419
~SandboxDirectoryDatabase()420 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
421 }
422
GetChildWithName(FileId parent_id,const base::FilePath::StringType & name,FileId * child_id)423 bool SandboxDirectoryDatabase::GetChildWithName(
424 FileId parent_id,
425 const base::FilePath::StringType& name,
426 FileId* child_id) {
427 if (!Init(REPAIR_ON_CORRUPTION))
428 return false;
429 DCHECK(child_id);
430 std::string child_key = GetChildLookupKey(parent_id, name);
431 std::string child_id_string;
432 leveldb::Status status =
433 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
434 if (status.IsNotFound())
435 return false;
436 if (status.ok()) {
437 if (!base::StringToInt64(child_id_string, child_id)) {
438 LOG(ERROR) << "Hit database corruption!";
439 return false;
440 }
441 return true;
442 }
443 HandleError(FROM_HERE, status);
444 return false;
445 }
446
GetFileWithPath(const base::FilePath & path,FileId * file_id)447 bool SandboxDirectoryDatabase::GetFileWithPath(
448 const base::FilePath& path, FileId* file_id) {
449 std::vector<base::FilePath::StringType> components;
450 VirtualPath::GetComponents(path, &components);
451 FileId local_id = 0;
452 std::vector<base::FilePath::StringType>::iterator iter;
453 for (iter = components.begin(); iter != components.end(); ++iter) {
454 base::FilePath::StringType name;
455 name = *iter;
456 if (name == FILE_PATH_LITERAL("/"))
457 continue;
458 if (!GetChildWithName(local_id, name, &local_id))
459 return false;
460 }
461 *file_id = local_id;
462 return true;
463 }
464
ListChildren(FileId parent_id,std::vector<FileId> * children)465 bool SandboxDirectoryDatabase::ListChildren(
466 FileId parent_id, std::vector<FileId>* children) {
467 // Check to add later: fail if parent is a file, at least in debug builds.
468 if (!Init(REPAIR_ON_CORRUPTION))
469 return false;
470 DCHECK(children);
471 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
472
473 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
474 iter->Seek(child_key_prefix);
475 children->clear();
476 while (iter->Valid() &&
477 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
478 std::string child_id_string = iter->value().ToString();
479 FileId child_id;
480 if (!base::StringToInt64(child_id_string, &child_id)) {
481 LOG(ERROR) << "Hit database corruption!";
482 return false;
483 }
484 children->push_back(child_id);
485 iter->Next();
486 }
487 return true;
488 }
489
GetFileInfo(FileId file_id,FileInfo * info)490 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
491 if (!Init(REPAIR_ON_CORRUPTION))
492 return false;
493 DCHECK(info);
494 std::string file_key = GetFileLookupKey(file_id);
495 std::string file_data_string;
496 leveldb::Status status =
497 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
498 if (status.ok()) {
499 bool success = FileInfoFromPickle(
500 Pickle(file_data_string.data(), file_data_string.length()), info);
501 if (!success)
502 return false;
503 if (!VerifyDataPath(info->data_path)) {
504 LOG(ERROR) << "Resolved data path is invalid: "
505 << info->data_path.value();
506 return false;
507 }
508 return true;
509 }
510 // Special-case the root, for databases that haven't been initialized yet.
511 // Without this, a query for the root's file info, made before creating the
512 // first file in the database, will fail and confuse callers.
513 if (status.IsNotFound() && !file_id) {
514 info->name = base::FilePath::StringType();
515 info->data_path = base::FilePath();
516 info->modification_time = base::Time::Now();
517 info->parent_id = 0;
518 return true;
519 }
520 HandleError(FROM_HERE, status);
521 return false;
522 }
523
AddFileInfo(const FileInfo & info,FileId * file_id)524 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
525 const FileInfo& info, FileId* file_id) {
526 if (!Init(REPAIR_ON_CORRUPTION))
527 return base::File::FILE_ERROR_FAILED;
528 DCHECK(file_id);
529 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
530 std::string child_id_string;
531 leveldb::Status status =
532 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
533 if (status.ok()) {
534 LOG(ERROR) << "File exists already!";
535 return base::File::FILE_ERROR_EXISTS;
536 }
537 if (!status.IsNotFound()) {
538 HandleError(FROM_HERE, status);
539 return base::File::FILE_ERROR_NOT_FOUND;
540 }
541
542 if (!IsDirectory(info.parent_id)) {
543 LOG(ERROR) << "New parent directory is a file!";
544 return base::File::FILE_ERROR_NOT_A_DIRECTORY;
545 }
546
547 // This would be a fine place to limit the number of files in a directory, if
548 // we decide to add that restriction.
549
550 FileId temp_id;
551 if (!GetLastFileId(&temp_id))
552 return base::File::FILE_ERROR_FAILED;
553 ++temp_id;
554
555 leveldb::WriteBatch batch;
556 if (!AddFileInfoHelper(info, temp_id, &batch))
557 return base::File::FILE_ERROR_FAILED;
558
559 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
560 status = db_->Write(leveldb::WriteOptions(), &batch);
561 if (!status.ok()) {
562 HandleError(FROM_HERE, status);
563 return base::File::FILE_ERROR_FAILED;
564 }
565 *file_id = temp_id;
566 return base::File::FILE_OK;
567 }
568
RemoveFileInfo(FileId file_id)569 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
570 if (!Init(REPAIR_ON_CORRUPTION))
571 return false;
572 leveldb::WriteBatch batch;
573 if (!RemoveFileInfoHelper(file_id, &batch))
574 return false;
575 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
576 if (!status.ok()) {
577 HandleError(FROM_HERE, status);
578 return false;
579 }
580 return true;
581 }
582
UpdateFileInfo(FileId file_id,const FileInfo & new_info)583 bool SandboxDirectoryDatabase::UpdateFileInfo(
584 FileId file_id, const FileInfo& new_info) {
585 // TODO(ericu): We should also check to see that this doesn't create a loop,
586 // but perhaps only in a debug build.
587 if (!Init(REPAIR_ON_CORRUPTION))
588 return false;
589 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
590 FileInfo old_info;
591 if (!GetFileInfo(file_id, &old_info))
592 return false;
593 if (old_info.parent_id != new_info.parent_id &&
594 !IsDirectory(new_info.parent_id))
595 return false;
596 if (old_info.parent_id != new_info.parent_id ||
597 old_info.name != new_info.name) {
598 // Check for name clashes.
599 FileId temp_id;
600 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
601 LOG(ERROR) << "Name collision on move.";
602 return false;
603 }
604 }
605 leveldb::WriteBatch batch;
606 if (!RemoveFileInfoHelper(file_id, &batch) ||
607 !AddFileInfoHelper(new_info, file_id, &batch))
608 return false;
609 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
610 if (!status.ok()) {
611 HandleError(FROM_HERE, status);
612 return false;
613 }
614 return true;
615 }
616
UpdateModificationTime(FileId file_id,const base::Time & modification_time)617 bool SandboxDirectoryDatabase::UpdateModificationTime(
618 FileId file_id, const base::Time& modification_time) {
619 FileInfo info;
620 if (!GetFileInfo(file_id, &info))
621 return false;
622 info.modification_time = modification_time;
623 Pickle pickle;
624 if (!PickleFromFileInfo(info, &pickle))
625 return false;
626 leveldb::Status status = db_->Put(
627 leveldb::WriteOptions(),
628 GetFileLookupKey(file_id),
629 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
630 pickle.size()));
631 if (!status.ok()) {
632 HandleError(FROM_HERE, status);
633 return false;
634 }
635 return true;
636 }
637
OverwritingMoveFile(FileId src_file_id,FileId dest_file_id)638 bool SandboxDirectoryDatabase::OverwritingMoveFile(
639 FileId src_file_id, FileId dest_file_id) {
640 FileInfo src_file_info;
641 FileInfo dest_file_info;
642
643 if (!GetFileInfo(src_file_id, &src_file_info))
644 return false;
645 if (!GetFileInfo(dest_file_id, &dest_file_info))
646 return false;
647 if (src_file_info.is_directory() || dest_file_info.is_directory())
648 return false;
649 leveldb::WriteBatch batch;
650 // This is the only field that really gets moved over; if you add fields to
651 // FileInfo, e.g. ctime, they might need to be copied here.
652 dest_file_info.data_path = src_file_info.data_path;
653 if (!RemoveFileInfoHelper(src_file_id, &batch))
654 return false;
655 Pickle pickle;
656 if (!PickleFromFileInfo(dest_file_info, &pickle))
657 return false;
658 batch.Put(
659 GetFileLookupKey(dest_file_id),
660 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
661 pickle.size()));
662 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
663 if (!status.ok()) {
664 HandleError(FROM_HERE, status);
665 return false;
666 }
667 return true;
668 }
669
GetNextInteger(int64 * next)670 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
671 if (!Init(REPAIR_ON_CORRUPTION))
672 return false;
673 DCHECK(next);
674 std::string int_string;
675 leveldb::Status status =
676 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
677 if (status.ok()) {
678 int64 temp;
679 if (!base::StringToInt64(int_string, &temp)) {
680 LOG(ERROR) << "Hit database corruption!";
681 return false;
682 }
683 ++temp;
684 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
685 base::Int64ToString(temp));
686 if (!status.ok()) {
687 HandleError(FROM_HERE, status);
688 return false;
689 }
690 *next = temp;
691 return true;
692 }
693 if (!status.IsNotFound()) {
694 HandleError(FROM_HERE, status);
695 return false;
696 }
697 // The database must not yet exist; initialize it.
698 if (!StoreDefaultValues())
699 return false;
700
701 return GetNextInteger(next);
702 }
703
DestroyDatabase()704 bool SandboxDirectoryDatabase::DestroyDatabase() {
705 db_.reset();
706 const std::string path =
707 FilePathToString(filesystem_data_directory_.Append(
708 kDirectoryDatabaseName));
709 leveldb::Options options;
710 if (env_override_)
711 options.env = env_override_;
712 leveldb::Status status = leveldb::DestroyDB(path, options);
713 if (status.ok())
714 return true;
715 LOG(WARNING) << "Failed to destroy a database with status " <<
716 status.ToString();
717 return false;
718 }
719
Init(RecoveryOption recovery_option)720 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
721 if (db_)
722 return true;
723
724 std::string path =
725 FilePathToString(filesystem_data_directory_.Append(
726 kDirectoryDatabaseName));
727 leveldb::Options options;
728 options.max_open_files = 0; // Use minimum.
729 options.create_if_missing = true;
730 if (env_override_)
731 options.env = env_override_;
732 leveldb::DB* db;
733 leveldb::Status status = leveldb::DB::Open(options, path, &db);
734 ReportInitStatus(status);
735 if (status.ok()) {
736 db_.reset(db);
737 return true;
738 }
739 HandleError(FROM_HERE, status);
740
741 // Corruption due to missing necessary MANIFEST-* file causes IOError instead
742 // of Corruption error.
743 // Try to repair database even when IOError case.
744 if (!status.IsCorruption() && !status.IsIOError())
745 return false;
746
747 switch (recovery_option) {
748 case FAIL_ON_CORRUPTION:
749 return false;
750 case REPAIR_ON_CORRUPTION:
751 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
752 << " Attempting to repair.";
753 if (RepairDatabase(path)) {
754 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
755 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
756 return true;
757 }
758 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
759 DB_REPAIR_FAILED, DB_REPAIR_MAX);
760 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
761 // fall through
762 case DELETE_ON_CORRUPTION:
763 LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
764 if (!base::DeleteFile(filesystem_data_directory_, true))
765 return false;
766 if (!base::CreateDirectory(filesystem_data_directory_))
767 return false;
768 return Init(FAIL_ON_CORRUPTION);
769 }
770
771 NOTREACHED();
772 return false;
773 }
774
RepairDatabase(const std::string & db_path)775 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
776 DCHECK(!db_.get());
777 leveldb::Options options;
778 options.max_open_files = 0; // Use minimum.
779 if (env_override_)
780 options.env = env_override_;
781 if (!leveldb::RepairDB(db_path, options).ok())
782 return false;
783 if (!Init(FAIL_ON_CORRUPTION))
784 return false;
785 if (IsFileSystemConsistent())
786 return true;
787 db_.reset();
788 return false;
789 }
790
IsDirectory(FileId file_id)791 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
792 FileInfo info;
793 if (!file_id)
794 return true; // The root is a directory.
795 if (!GetFileInfo(file_id, &info))
796 return false;
797 if (!info.is_directory())
798 return false;
799 return true;
800 }
801
IsFileSystemConsistent()802 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
803 if (!Init(FAIL_ON_CORRUPTION))
804 return false;
805 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
806 return helper.IsFileSystemConsistent();
807 }
808
ReportInitStatus(const leveldb::Status & status)809 void SandboxDirectoryDatabase::ReportInitStatus(
810 const leveldb::Status& status) {
811 base::Time now = base::Time::Now();
812 const base::TimeDelta minimum_interval =
813 base::TimeDelta::FromHours(kMinimumReportIntervalHours);
814 if (last_reported_time_ + minimum_interval >= now)
815 return;
816 last_reported_time_ = now;
817
818 if (status.ok()) {
819 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
820 INIT_STATUS_OK, INIT_STATUS_MAX);
821 } else if (status.IsCorruption()) {
822 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
823 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
824 } else if (status.IsIOError()) {
825 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
826 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
827 } else {
828 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
829 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
830 }
831 }
832
StoreDefaultValues()833 bool SandboxDirectoryDatabase::StoreDefaultValues() {
834 // Verify that this is a totally new database, and initialize it.
835 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
836 iter->SeekToFirst();
837 if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
838 LOG(ERROR) << "File system origin database is corrupt!";
839 return false;
840 }
841 // This is always the first write into the database. If we ever add a
842 // version number, it should go in this transaction too.
843 FileInfo root;
844 root.parent_id = 0;
845 root.modification_time = base::Time::Now();
846 leveldb::WriteBatch batch;
847 if (!AddFileInfoHelper(root, 0, &batch))
848 return false;
849 batch.Put(LastFileIdKey(), base::Int64ToString(0));
850 batch.Put(LastIntegerKey(), base::Int64ToString(-1));
851 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
852 if (!status.ok()) {
853 HandleError(FROM_HERE, status);
854 return false;
855 }
856 return true;
857 }
858
GetLastFileId(FileId * file_id)859 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
860 if (!Init(REPAIR_ON_CORRUPTION))
861 return false;
862 DCHECK(file_id);
863 std::string id_string;
864 leveldb::Status status =
865 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
866 if (status.ok()) {
867 if (!base::StringToInt64(id_string, file_id)) {
868 LOG(ERROR) << "Hit database corruption!";
869 return false;
870 }
871 return true;
872 }
873 if (!status.IsNotFound()) {
874 HandleError(FROM_HERE, status);
875 return false;
876 }
877 // The database must not yet exist; initialize it.
878 if (!StoreDefaultValues())
879 return false;
880 *file_id = 0;
881 return true;
882 }
883
884 // This does very few safety checks!
AddFileInfoHelper(const FileInfo & info,FileId file_id,leveldb::WriteBatch * batch)885 bool SandboxDirectoryDatabase::AddFileInfoHelper(
886 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
887 if (!VerifyDataPath(info.data_path)) {
888 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
889 return false;
890 }
891 std::string id_string = GetFileLookupKey(file_id);
892 if (!file_id) {
893 // The root directory doesn't need to be looked up by path from its parent.
894 DCHECK(!info.parent_id);
895 DCHECK(info.data_path.empty());
896 } else {
897 std::string child_key = GetChildLookupKey(info.parent_id, info.name);
898 batch->Put(child_key, id_string);
899 }
900 Pickle pickle;
901 if (!PickleFromFileInfo(info, &pickle))
902 return false;
903 batch->Put(
904 id_string,
905 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
906 pickle.size()));
907 return true;
908 }
909
910 // This does very few safety checks!
RemoveFileInfoHelper(FileId file_id,leveldb::WriteBatch * batch)911 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
912 FileId file_id, leveldb::WriteBatch* batch) {
913 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
914 FileInfo info;
915 if (!GetFileInfo(file_id, &info))
916 return false;
917 if (info.data_path.empty()) { // It's a directory
918 std::vector<FileId> children;
919 // TODO(ericu): Make a faster is-the-directory-empty check.
920 if (!ListChildren(file_id, &children))
921 return false;
922 if (children.size()) {
923 LOG(ERROR) << "Can't remove a directory with children.";
924 return false;
925 }
926 }
927 batch->Delete(GetChildLookupKey(info.parent_id, info.name));
928 batch->Delete(GetFileLookupKey(file_id));
929 return true;
930 }
931
HandleError(const tracked_objects::Location & from_here,const leveldb::Status & status)932 void SandboxDirectoryDatabase::HandleError(
933 const tracked_objects::Location& from_here,
934 const leveldb::Status& status) {
935 LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
936 << from_here.ToString() << " with error: " << status.ToString();
937 db_.reset();
938 }
939
940 } // namespace storage
941