Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Side by Side Diff: webkit/fileapi/sandbox_directory_database.cc

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

Powered by Google App Engine
This is Rietveld 408576698