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