| OLD | NEW |
| (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/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(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 // static | |
| 705 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path, | |
| 706 leveldb::Env* env_override) { | |
| 707 std::string name = FilePathToString(path.Append(kDirectoryDatabaseName)); | |
| 708 leveldb::Options options; | |
| 709 if (env_override) | |
| 710 options.env = env_override; | |
| 711 leveldb::Status status = leveldb::DestroyDB(name, options); | |
| 712 if (status.ok()) | |
| 713 return true; | |
| 714 LOG(WARNING) << "Failed to destroy a database with status " << | |
| 715 status.ToString(); | |
| 716 return false; | |
| 717 } | |
| 718 | |
| 719 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) { | |
| 720 if (db_) | |
| 721 return true; | |
| 722 | |
| 723 std::string path = | |
| 724 FilePathToString(filesystem_data_directory_.Append( | |
| 725 kDirectoryDatabaseName)); | |
| 726 leveldb::Options options; | |
| 727 options.max_open_files = 0; // Use minimum. | |
| 728 options.create_if_missing = true; | |
| 729 if (env_override_) | |
| 730 options.env = env_override_; | |
| 731 leveldb::DB* db; | |
| 732 leveldb::Status status = leveldb::DB::Open(options, path, &db); | |
| 733 ReportInitStatus(status); | |
| 734 if (status.ok()) { | |
| 735 db_.reset(db); | |
| 736 return true; | |
| 737 } | |
| 738 HandleError(FROM_HERE, status); | |
| 739 | |
| 740 // Corruption due to missing necessary MANIFEST-* file causes IOError instead | |
| 741 // of Corruption error. | |
| 742 // Try to repair database even when IOError case. | |
| 743 if (!status.IsCorruption() && !status.IsIOError()) | |
| 744 return false; | |
| 745 | |
| 746 switch (recovery_option) { | |
| 747 case FAIL_ON_CORRUPTION: | |
| 748 return false; | |
| 749 case REPAIR_ON_CORRUPTION: | |
| 750 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected." | |
| 751 << " Attempting to repair."; | |
| 752 if (RepairDatabase(path)) { | |
| 753 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | |
| 754 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); | |
| 755 return true; | |
| 756 } | |
| 757 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | |
| 758 DB_REPAIR_FAILED, DB_REPAIR_MAX); | |
| 759 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase."; | |
| 760 // fall through | |
| 761 case DELETE_ON_CORRUPTION: | |
| 762 LOG(WARNING) << "Clearing SandboxDirectoryDatabase."; | |
| 763 if (!base::DeleteFile(filesystem_data_directory_, true)) | |
| 764 return false; | |
| 765 if (!base::CreateDirectory(filesystem_data_directory_)) | |
| 766 return false; | |
| 767 return Init(FAIL_ON_CORRUPTION); | |
| 768 } | |
| 769 | |
| 770 NOTREACHED(); | |
| 771 return false; | |
| 772 } | |
| 773 | |
| 774 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) { | |
| 775 DCHECK(!db_.get()); | |
| 776 leveldb::Options options; | |
| 777 options.max_open_files = 0; // Use minimum. | |
| 778 if (env_override_) | |
| 779 options.env = env_override_; | |
| 780 if (!leveldb::RepairDB(db_path, options).ok()) | |
| 781 return false; | |
| 782 if (!Init(FAIL_ON_CORRUPTION)) | |
| 783 return false; | |
| 784 if (IsFileSystemConsistent()) | |
| 785 return true; | |
| 786 db_.reset(); | |
| 787 return false; | |
| 788 } | |
| 789 | |
| 790 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) { | |
| 791 FileInfo info; | |
| 792 if (!file_id) | |
| 793 return true; // The root is a directory. | |
| 794 if (!GetFileInfo(file_id, &info)) | |
| 795 return false; | |
| 796 if (!info.is_directory()) | |
| 797 return false; | |
| 798 return true; | |
| 799 } | |
| 800 | |
| 801 bool SandboxDirectoryDatabase::IsFileSystemConsistent() { | |
| 802 if (!Init(FAIL_ON_CORRUPTION)) | |
| 803 return false; | |
| 804 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_); | |
| 805 return helper.IsFileSystemConsistent(); | |
| 806 } | |
| 807 | |
| 808 void SandboxDirectoryDatabase::ReportInitStatus( | |
| 809 const leveldb::Status& status) { | |
| 810 base::Time now = base::Time::Now(); | |
| 811 const base::TimeDelta minimum_interval = | |
| 812 base::TimeDelta::FromHours(kMinimumReportIntervalHours); | |
| 813 if (last_reported_time_ + minimum_interval >= now) | |
| 814 return; | |
| 815 last_reported_time_ = now; | |
| 816 | |
| 817 if (status.ok()) { | |
| 818 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
| 819 INIT_STATUS_OK, INIT_STATUS_MAX); | |
| 820 } else if (status.IsCorruption()) { | |
| 821 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
| 822 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); | |
| 823 } else if (status.IsIOError()) { | |
| 824 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
| 825 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); | |
| 826 } else { | |
| 827 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
| 828 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); | |
| 829 } | |
| 830 } | |
| 831 | |
| 832 bool SandboxDirectoryDatabase::StoreDefaultValues() { | |
| 833 // Verify that this is a totally new database, and initialize it. | |
| 834 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | |
| 835 iter->SeekToFirst(); | |
| 836 if (iter->Valid()) { // DB was not empty--we shouldn't have been called. | |
| 837 LOG(ERROR) << "File system origin database is corrupt!"; | |
| 838 return false; | |
| 839 } | |
| 840 // This is always the first write into the database. If we ever add a | |
| 841 // version number, it should go in this transaction too. | |
| 842 FileInfo root; | |
| 843 root.parent_id = 0; | |
| 844 root.modification_time = base::Time::Now(); | |
| 845 leveldb::WriteBatch batch; | |
| 846 if (!AddFileInfoHelper(root, 0, &batch)) | |
| 847 return false; | |
| 848 batch.Put(LastFileIdKey(), base::Int64ToString(0)); | |
| 849 batch.Put(LastIntegerKey(), base::Int64ToString(-1)); | |
| 850 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
| 851 if (!status.ok()) { | |
| 852 HandleError(FROM_HERE, status); | |
| 853 return false; | |
| 854 } | |
| 855 return true; | |
| 856 } | |
| 857 | |
| 858 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) { | |
| 859 if (!Init(REPAIR_ON_CORRUPTION)) | |
| 860 return false; | |
| 861 DCHECK(file_id); | |
| 862 std::string id_string; | |
| 863 leveldb::Status status = | |
| 864 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); | |
| 865 if (status.ok()) { | |
| 866 if (!base::StringToInt64(id_string, file_id)) { | |
| 867 LOG(ERROR) << "Hit database corruption!"; | |
| 868 return false; | |
| 869 } | |
| 870 return true; | |
| 871 } | |
| 872 if (!status.IsNotFound()) { | |
| 873 HandleError(FROM_HERE, status); | |
| 874 return false; | |
| 875 } | |
| 876 // The database must not yet exist; initialize it. | |
| 877 if (!StoreDefaultValues()) | |
| 878 return false; | |
| 879 *file_id = 0; | |
| 880 return true; | |
| 881 } | |
| 882 | |
| 883 // This does very few safety checks! | |
| 884 bool SandboxDirectoryDatabase::AddFileInfoHelper( | |
| 885 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) { | |
| 886 if (!VerifyDataPath(info.data_path)) { | |
| 887 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value(); | |
| 888 return false; | |
| 889 } | |
| 890 std::string id_string = GetFileLookupKey(file_id); | |
| 891 if (!file_id) { | |
| 892 // The root directory doesn't need to be looked up by path from its parent. | |
| 893 DCHECK(!info.parent_id); | |
| 894 DCHECK(info.data_path.empty()); | |
| 895 } else { | |
| 896 std::string child_key = GetChildLookupKey(info.parent_id, info.name); | |
| 897 batch->Put(child_key, id_string); | |
| 898 } | |
| 899 Pickle pickle; | |
| 900 if (!PickleFromFileInfo(info, &pickle)) | |
| 901 return false; | |
| 902 batch->Put( | |
| 903 id_string, | |
| 904 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | |
| 905 pickle.size())); | |
| 906 return true; | |
| 907 } | |
| 908 | |
| 909 // This does very few safety checks! | |
| 910 bool SandboxDirectoryDatabase::RemoveFileInfoHelper( | |
| 911 FileId file_id, leveldb::WriteBatch* batch) { | |
| 912 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. | |
| 913 FileInfo info; | |
| 914 if (!GetFileInfo(file_id, &info)) | |
| 915 return false; | |
| 916 if (info.data_path.empty()) { // It's a directory | |
| 917 std::vector<FileId> children; | |
| 918 // TODO(ericu): Make a faster is-the-directory-empty check. | |
| 919 if (!ListChildren(file_id, &children)) | |
| 920 return false; | |
| 921 if (children.size()) { | |
| 922 LOG(ERROR) << "Can't remove a directory with children."; | |
| 923 return false; | |
| 924 } | |
| 925 } | |
| 926 batch->Delete(GetChildLookupKey(info.parent_id, info.name)); | |
| 927 batch->Delete(GetFileLookupKey(file_id)); | |
| 928 return true; | |
| 929 } | |
| 930 | |
| 931 void SandboxDirectoryDatabase::HandleError( | |
| 932 const tracked_objects::Location& from_here, | |
| 933 const leveldb::Status& status) { | |
| 934 LOG(ERROR) << "SandboxDirectoryDatabase failed at: " | |
| 935 << from_here.ToString() << " with error: " << status.ToString(); | |
| 936 db_.reset(); | |
| 937 } | |
| 938 | |
| 939 } // namespace storage | |
| OLD | NEW |