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