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