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

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

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

Powered by Google App Engine
This is Rietveld 408576698