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 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 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 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 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 110 const char* LastFileIdKey() { 111 return kLastFileIdKey; 112 } 113 114 const char* LastIntegerKey() { 115 return kLastIntegerKey; 116 } 117 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 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 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 186 bool DatabaseCheckHelper::IsDatabaseEmpty() { 187 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); 188 itr->SeekToFirst(); 189 return !itr->Valid(); 190 } 191 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 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 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. 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 406 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) { 407 } 408 409 SandboxDirectoryDatabase::FileInfo::~FileInfo() { 410 } 411 412 SandboxDirectoryDatabase::SandboxDirectoryDatabase( 413 const base::FilePath& filesystem_data_directory) 414 : filesystem_data_directory_(filesystem_data_directory) { 415 } 416 417 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() { 418 } 419 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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! 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! 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 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