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 "chrome/browser/chromeos/drive/file_cache.h" 6 7 #include <vector> 8 9 #include "base/callback_helpers.h" 10 #include "base/files/file_enumerator.h" 11 #include "base/files/file_util.h" 12 #include "base/logging.h" 13 #include "base/metrics/histogram.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/sys_info.h" 17 #include "chrome/browser/chromeos/drive/drive.pb.h" 18 #include "chrome/browser/chromeos/drive/file_system_util.h" 19 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h" 20 #include "chrome/browser/drive/drive_api_util.h" 21 #include "chromeos/chromeos_constants.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "google_apis/drive/task_util.h" 24 #include "net/base/filename_util.h" 25 #include "net/base/mime_sniffer.h" 26 #include "net/base/mime_util.h" 27 #include "third_party/cros_system_api/constants/cryptohome.h" 28 29 using content::BrowserThread; 30 31 namespace drive { 32 namespace internal { 33 namespace { 34 35 // Returns ID extracted from the path. 36 std::string GetIdFromPath(const base::FilePath& path) { 37 return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe()); 38 } 39 40 } // namespace 41 42 FileCache::FileCache(ResourceMetadataStorage* storage, 43 const base::FilePath& cache_file_directory, 44 base::SequencedTaskRunner* blocking_task_runner, 45 FreeDiskSpaceGetterInterface* free_disk_space_getter) 46 : cache_file_directory_(cache_file_directory), 47 blocking_task_runner_(blocking_task_runner), 48 storage_(storage), 49 free_disk_space_getter_(free_disk_space_getter), 50 weak_ptr_factory_(this) { 51 DCHECK(blocking_task_runner_.get()); 52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 53 } 54 55 FileCache::~FileCache() { 56 // Must be on the sequenced worker pool, as |metadata_| must be deleted on 57 // the sequenced worker pool. 58 AssertOnSequencedWorkerPool(); 59 } 60 61 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const { 62 return cache_file_directory_.Append( 63 base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id))); 64 } 65 66 void FileCache::AssertOnSequencedWorkerPool() { 67 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 68 } 69 70 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const { 71 return cache_file_directory_.IsParent(path); 72 } 73 74 bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) { 75 AssertOnSequencedWorkerPool(); 76 77 // Do nothing and return if we have enough space. 78 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_)) 79 return true; 80 81 // Otherwise, try to free up the disk space. 82 DVLOG(1) << "Freeing up disk space for " << num_bytes; 83 84 // Remove all entries unless specially marked. 85 scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator(); 86 for (; !it->IsAtEnd(); it->Advance()) { 87 if (it->GetValue().file_specific_info().has_cache_state() && 88 !it->GetValue().file_specific_info().cache_state().is_pinned() && 89 !it->GetValue().file_specific_info().cache_state().is_dirty() && 90 !mounted_files_.count(it->GetID())) { 91 ResourceEntry entry(it->GetValue()); 92 entry.mutable_file_specific_info()->clear_cache_state(); 93 storage_->PutEntry(entry); 94 } 95 } 96 if (it->HasError()) 97 return false; 98 99 // Remove all files which have no corresponding cache entries. 100 base::FileEnumerator enumerator(cache_file_directory_, 101 false, // not recursive 102 base::FileEnumerator::FILES); 103 ResourceEntry entry; 104 for (base::FilePath current = enumerator.Next(); !current.empty(); 105 current = enumerator.Next()) { 106 std::string id = GetIdFromPath(current); 107 FileError error = storage_->GetEntry(id, &entry); 108 if (error == FILE_ERROR_NOT_FOUND || 109 (error == FILE_ERROR_OK && 110 !entry.file_specific_info().cache_state().is_present())) 111 base::DeleteFile(current, false /* recursive */); 112 else if (error != FILE_ERROR_OK) 113 return false; 114 } 115 116 // Check the disk space again. 117 return HasEnoughSpaceFor(num_bytes, cache_file_directory_); 118 } 119 120 FileError FileCache::GetFile(const std::string& id, 121 base::FilePath* cache_file_path) { 122 AssertOnSequencedWorkerPool(); 123 DCHECK(cache_file_path); 124 125 ResourceEntry entry; 126 FileError error = storage_->GetEntry(id, &entry); 127 if (error != FILE_ERROR_OK) 128 return error; 129 if (!entry.file_specific_info().cache_state().is_present()) 130 return FILE_ERROR_NOT_FOUND; 131 132 *cache_file_path = GetCacheFilePath(id); 133 return FILE_ERROR_OK; 134 } 135 136 FileError FileCache::Store(const std::string& id, 137 const std::string& md5, 138 const base::FilePath& source_path, 139 FileOperationType file_operation_type) { 140 AssertOnSequencedWorkerPool(); 141 142 ResourceEntry entry; 143 FileError error = storage_->GetEntry(id, &entry); 144 if (error != FILE_ERROR_OK) 145 return error; 146 147 int64 file_size = 0; 148 if (file_operation_type == FILE_OPERATION_COPY) { 149 if (!base::GetFileSize(source_path, &file_size)) { 150 LOG(WARNING) << "Couldn't get file size for: " << source_path.value(); 151 return FILE_ERROR_FAILED; 152 } 153 } 154 if (!FreeDiskSpaceIfNeededFor(file_size)) 155 return FILE_ERROR_NO_LOCAL_SPACE; 156 157 // If file is mounted, return error. 158 if (mounted_files_.count(id)) 159 return FILE_ERROR_IN_USE; 160 161 base::FilePath dest_path = GetCacheFilePath(id); 162 bool success = false; 163 switch (file_operation_type) { 164 case FILE_OPERATION_MOVE: 165 success = base::Move(source_path, dest_path); 166 break; 167 case FILE_OPERATION_COPY: 168 success = base::CopyFile(source_path, dest_path); 169 break; 170 default: 171 NOTREACHED(); 172 } 173 174 if (!success) { 175 LOG(ERROR) << "Failed to store: " 176 << "source_path = " << source_path.value() << ", " 177 << "dest_path = " << dest_path.value() << ", " 178 << "file_operation_type = " << file_operation_type; 179 return FILE_ERROR_FAILED; 180 } 181 182 // Now that file operations have completed, update metadata. 183 FileCacheEntry* cache_state = 184 entry.mutable_file_specific_info()->mutable_cache_state(); 185 cache_state->set_md5(md5); 186 cache_state->set_is_present(true); 187 if (md5.empty()) 188 cache_state->set_is_dirty(true); 189 return storage_->PutEntry(entry); 190 } 191 192 FileError FileCache::Pin(const std::string& id) { 193 AssertOnSequencedWorkerPool(); 194 195 ResourceEntry entry; 196 FileError error = storage_->GetEntry(id, &entry); 197 if (error != FILE_ERROR_OK) 198 return error; 199 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned( 200 true); 201 return storage_->PutEntry(entry); 202 } 203 204 FileError FileCache::Unpin(const std::string& id) { 205 AssertOnSequencedWorkerPool(); 206 207 // Unpinning a file means its entry must exist in cache. 208 ResourceEntry entry; 209 FileError error = storage_->GetEntry(id, &entry); 210 if (error != FILE_ERROR_OK) 211 return error; 212 213 // Now that file operations have completed, update metadata. 214 if (entry.file_specific_info().cache_state().is_present()) { 215 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned( 216 false); 217 } else { 218 // Remove the existing entry if we are unpinning a non-present file. 219 entry.mutable_file_specific_info()->clear_cache_state(); 220 } 221 error = storage_->PutEntry(entry); 222 if (error != FILE_ERROR_OK) 223 return error; 224 225 // Now it's a chance to free up space if needed. 226 FreeDiskSpaceIfNeededFor(0); 227 228 return FILE_ERROR_OK; 229 } 230 231 FileError FileCache::MarkAsMounted(const std::string& id, 232 base::FilePath* cache_file_path) { 233 AssertOnSequencedWorkerPool(); 234 DCHECK(cache_file_path); 235 236 // Get cache entry associated with the id and md5 237 ResourceEntry entry; 238 FileError error = storage_->GetEntry(id, &entry); 239 if (error != FILE_ERROR_OK) 240 return error; 241 if (!entry.file_specific_info().cache_state().is_present()) 242 return FILE_ERROR_NOT_FOUND; 243 244 if (mounted_files_.count(id)) 245 return FILE_ERROR_INVALID_OPERATION; 246 247 // Ensure the file is readable to cros_disks. See crbug.com/236994. 248 base::FilePath path = GetCacheFilePath(id); 249 if (!base::SetPosixFilePermissions( 250 path, 251 base::FILE_PERMISSION_READ_BY_USER | 252 base::FILE_PERMISSION_WRITE_BY_USER | 253 base::FILE_PERMISSION_READ_BY_GROUP | 254 base::FILE_PERMISSION_READ_BY_OTHERS)) 255 return FILE_ERROR_FAILED; 256 257 mounted_files_.insert(id); 258 259 *cache_file_path = path; 260 return FILE_ERROR_OK; 261 } 262 263 FileError FileCache::OpenForWrite( 264 const std::string& id, 265 scoped_ptr<base::ScopedClosureRunner>* file_closer) { 266 AssertOnSequencedWorkerPool(); 267 268 // Marking a file dirty means its entry and actual file blob must exist in 269 // cache. 270 ResourceEntry entry; 271 FileError error = storage_->GetEntry(id, &entry); 272 if (error != FILE_ERROR_OK) 273 return error; 274 if (!entry.file_specific_info().cache_state().is_present()) { 275 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id; 276 return FILE_ERROR_NOT_FOUND; 277 } 278 279 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true); 280 entry.mutable_file_specific_info()->mutable_cache_state()->clear_md5(); 281 error = storage_->PutEntry(entry); 282 if (error != FILE_ERROR_OK) 283 return error; 284 285 write_opened_files_[id]++; 286 file_closer->reset(new base::ScopedClosureRunner( 287 base::Bind(&google_apis::RunTaskWithTaskRunner, 288 blocking_task_runner_, 289 base::Bind(&FileCache::CloseForWrite, 290 weak_ptr_factory_.GetWeakPtr(), 291 id)))); 292 return FILE_ERROR_OK; 293 } 294 295 bool FileCache::IsOpenedForWrite(const std::string& id) { 296 AssertOnSequencedWorkerPool(); 297 return write_opened_files_.count(id); 298 } 299 300 FileError FileCache::UpdateMd5(const std::string& id) { 301 AssertOnSequencedWorkerPool(); 302 303 if (IsOpenedForWrite(id)) 304 return FILE_ERROR_IN_USE; 305 306 ResourceEntry entry; 307 FileError error = storage_->GetEntry(id, &entry); 308 if (error != FILE_ERROR_OK) 309 return error; 310 if (!entry.file_specific_info().cache_state().is_present()) 311 return FILE_ERROR_NOT_FOUND; 312 313 const std::string& md5 = util::GetMd5Digest(GetCacheFilePath(id)); 314 if (md5.empty()) 315 return FILE_ERROR_NOT_FOUND; 316 317 entry.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5); 318 return storage_->PutEntry(entry); 319 } 320 321 FileError FileCache::ClearDirty(const std::string& id) { 322 AssertOnSequencedWorkerPool(); 323 324 if (IsOpenedForWrite(id)) 325 return FILE_ERROR_IN_USE; 326 327 // Clearing a dirty file means its entry and actual file blob must exist in 328 // cache. 329 ResourceEntry entry; 330 FileError error = storage_->GetEntry(id, &entry); 331 if (error != FILE_ERROR_OK) 332 return error; 333 if (!entry.file_specific_info().cache_state().is_present()) { 334 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: " 335 << id; 336 return FILE_ERROR_NOT_FOUND; 337 } 338 339 // If a file is not dirty (it should have been marked dirty via OpenForWrite), 340 // clearing its dirty state is an invalid operation. 341 if (!entry.file_specific_info().cache_state().is_dirty()) { 342 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id; 343 return FILE_ERROR_INVALID_OPERATION; 344 } 345 346 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty( 347 false); 348 return storage_->PutEntry(entry); 349 } 350 351 FileError FileCache::Remove(const std::string& id) { 352 AssertOnSequencedWorkerPool(); 353 354 ResourceEntry entry; 355 356 // If entry doesn't exist, nothing to do. 357 FileError error = storage_->GetEntry(id, &entry); 358 if (error == FILE_ERROR_NOT_FOUND) 359 return FILE_ERROR_OK; 360 if (error != FILE_ERROR_OK) 361 return error; 362 if (!entry.file_specific_info().has_cache_state()) 363 return FILE_ERROR_OK; 364 365 // Cannot delete a mounted file. 366 if (mounted_files_.count(id)) 367 return FILE_ERROR_IN_USE; 368 369 // Delete the file. 370 base::FilePath path = GetCacheFilePath(id); 371 if (!base::DeleteFile(path, false /* recursive */)) 372 return FILE_ERROR_FAILED; 373 374 // Now that all file operations have completed, remove from metadata. 375 entry.mutable_file_specific_info()->clear_cache_state(); 376 return storage_->PutEntry(entry); 377 } 378 379 bool FileCache::ClearAll() { 380 AssertOnSequencedWorkerPool(); 381 382 // Remove files. 383 base::FileEnumerator enumerator(cache_file_directory_, 384 false, // not recursive 385 base::FileEnumerator::FILES); 386 for (base::FilePath file = enumerator.Next(); !file.empty(); 387 file = enumerator.Next()) 388 base::DeleteFile(file, false /* recursive */); 389 390 return true; 391 } 392 393 bool FileCache::Initialize() { 394 AssertOnSequencedWorkerPool(); 395 396 // Older versions do not clear MD5 when marking entries dirty. 397 // Clear MD5 of all dirty entries to deal with old data. 398 scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator(); 399 for (; !it->IsAtEnd(); it->Advance()) { 400 if (it->GetValue().file_specific_info().cache_state().is_dirty()) { 401 ResourceEntry new_entry(it->GetValue()); 402 new_entry.mutable_file_specific_info()->mutable_cache_state()-> 403 clear_md5(); 404 if (storage_->PutEntry(new_entry) != FILE_ERROR_OK) 405 return false; 406 } 407 } 408 if (it->HasError()) 409 return false; 410 411 if (!RenameCacheFilesToNewFormat()) 412 return false; 413 return true; 414 } 415 416 void FileCache::Destroy() { 417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 418 419 // Destroy myself on the blocking pool. 420 // Note that base::DeletePointer<> cannot be used as the destructor of this 421 // class is private. 422 blocking_task_runner_->PostTask( 423 FROM_HERE, 424 base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this))); 425 } 426 427 void FileCache::DestroyOnBlockingPool() { 428 AssertOnSequencedWorkerPool(); 429 delete this; 430 } 431 432 bool FileCache::RecoverFilesFromCacheDirectory( 433 const base::FilePath& dest_directory, 434 const ResourceMetadataStorage::RecoveredCacheInfoMap& 435 recovered_cache_info) { 436 int file_number = 1; 437 438 base::FileEnumerator enumerator(cache_file_directory_, 439 false, // not recursive 440 base::FileEnumerator::FILES); 441 for (base::FilePath current = enumerator.Next(); !current.empty(); 442 current = enumerator.Next()) { 443 const std::string& id = GetIdFromPath(current); 444 ResourceEntry entry; 445 FileError error = storage_->GetEntry(id, &entry); 446 if (error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND) 447 return false; 448 if (error == FILE_ERROR_OK && 449 entry.file_specific_info().cache_state().is_present()) { 450 // This file is managed by FileCache, no need to recover it. 451 continue; 452 } 453 454 // If a cache entry which is non-dirty and has matching MD5 is found in 455 // |recovered_cache_entries|, it means the current file is already uploaded 456 // to the server. Just delete it instead of recovering it. 457 ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it = 458 recovered_cache_info.find(id); 459 if (it != recovered_cache_info.end()) { 460 // Due to the DB corruption, cache info might be recovered from old 461 // revision. Perform MD5 check even when is_dirty is false just in case. 462 if (!it->second.is_dirty && 463 it->second.md5 == util::GetMd5Digest(current)) { 464 base::DeleteFile(current, false /* recursive */); 465 continue; 466 } 467 } 468 469 // Read file contents to sniff mime type. 470 std::vector<char> content(net::kMaxBytesToSniff); 471 const int read_result = 472 base::ReadFile(current, &content[0], content.size()); 473 if (read_result < 0) { 474 LOG(WARNING) << "Cannot read: " << current.value(); 475 return false; 476 } 477 if (read_result == 0) // Skip empty files. 478 continue; 479 480 // Use recovered file name if available, otherwise decide file name with 481 // sniffed mime type. 482 base::FilePath dest_base_name(FILE_PATH_LITERAL("file")); 483 std::string mime_type; 484 if (it != recovered_cache_info.end() && !it->second.title.empty()) { 485 // We can use a file name recovered from the trashed DB. 486 dest_base_name = base::FilePath::FromUTF8Unsafe(it->second.title); 487 } else if (net::SniffMimeType(&content[0], read_result, 488 net::FilePathToFileURL(current), 489 std::string(), &mime_type) || 490 net::SniffMimeTypeFromLocalData(&content[0], read_result, 491 &mime_type)) { 492 // Change base name for common mime types. 493 if (net::MatchesMimeType("image/*", mime_type)) { 494 dest_base_name = base::FilePath(FILE_PATH_LITERAL("image")); 495 } else if (net::MatchesMimeType("video/*", mime_type)) { 496 dest_base_name = base::FilePath(FILE_PATH_LITERAL("video")); 497 } else if (net::MatchesMimeType("audio/*", mime_type)) { 498 dest_base_name = base::FilePath(FILE_PATH_LITERAL("audio")); 499 } 500 501 // Estimate extension from mime type. 502 std::vector<base::FilePath::StringType> extensions; 503 base::FilePath::StringType extension; 504 if (net::GetPreferredExtensionForMimeType(mime_type, &extension)) 505 extensions.push_back(extension); 506 else 507 net::GetExtensionsForMimeType(mime_type, &extensions); 508 509 // Add extension if possible. 510 if (!extensions.empty()) 511 dest_base_name = dest_base_name.AddExtension(extensions[0]); 512 } 513 514 // Add file number to the file name and move. 515 const base::FilePath& dest_path = dest_directory.Append(dest_base_name) 516 .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number++)); 517 if (!base::CreateDirectory(dest_directory) || 518 !base::Move(current, dest_path)) { 519 LOG(WARNING) << "Failed to move: " << current.value() 520 << " to " << dest_path.value(); 521 return false; 522 } 523 } 524 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption", 525 file_number - 1); 526 return true; 527 } 528 529 FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) { 530 AssertOnSequencedWorkerPool(); 531 DCHECK(IsUnderFileCacheDirectory(file_path)); 532 533 std::string id = GetIdFromPath(file_path); 534 535 // Get the entry associated with the id. 536 ResourceEntry entry; 537 FileError error = storage_->GetEntry(id, &entry); 538 if (error != FILE_ERROR_OK) 539 return error; 540 541 std::set<std::string>::iterator it = mounted_files_.find(id); 542 if (it == mounted_files_.end()) 543 return FILE_ERROR_INVALID_OPERATION; 544 545 mounted_files_.erase(it); 546 return FILE_ERROR_OK; 547 } 548 549 bool FileCache::HasEnoughSpaceFor(int64 num_bytes, 550 const base::FilePath& path) { 551 int64 free_space = 0; 552 if (free_disk_space_getter_) 553 free_space = free_disk_space_getter_->AmountOfFreeDiskSpace(); 554 else 555 free_space = base::SysInfo::AmountOfFreeDiskSpace(path); 556 557 // Subtract this as if this portion does not exist. 558 free_space -= cryptohome::kMinFreeSpaceInBytes; 559 return (free_space >= num_bytes); 560 } 561 562 bool FileCache::RenameCacheFilesToNewFormat() { 563 base::FileEnumerator enumerator(cache_file_directory_, 564 false, // not recursive 565 base::FileEnumerator::FILES); 566 for (base::FilePath current = enumerator.Next(); !current.empty(); 567 current = enumerator.Next()) { 568 base::FilePath new_path = current.RemoveExtension(); 569 if (!new_path.Extension().empty()) { 570 // Delete files with multiple extensions. 571 if (!base::DeleteFile(current, false /* recursive */)) 572 return false; 573 continue; 574 } 575 const std::string& id = GetIdFromPath(new_path); 576 new_path = GetCacheFilePath(util::CanonicalizeResourceId(id)); 577 if (new_path != current && !base::Move(current, new_path)) 578 return false; 579 } 580 return true; 581 } 582 583 void FileCache::CloseForWrite(const std::string& id) { 584 AssertOnSequencedWorkerPool(); 585 586 std::map<std::string, int>::iterator it = write_opened_files_.find(id); 587 if (it == write_opened_files_.end()) 588 return; 589 590 DCHECK_LT(0, it->second); 591 --it->second; 592 if (it->second == 0) 593 write_opened_files_.erase(it); 594 595 // Update last modified date. 596 ResourceEntry entry; 597 FileError error = storage_->GetEntry(id, &entry); 598 if (error != FILE_ERROR_OK) { 599 LOG(ERROR) << "Failed to get entry: " << id << ", " 600 << FileErrorToString(error); 601 return; 602 } 603 entry.mutable_file_info()->set_last_modified( 604 base::Time::Now().ToInternalValue()); 605 error = storage_->PutEntry(entry); 606 if (error != FILE_ERROR_OK) { 607 LOG(ERROR) << "Failed to put entry: " << id << ", " 608 << FileErrorToString(error); 609 } 610 } 611 612 } // namespace internal 613 } // namespace drive 614