Home | History | Annotate | Download | only in drive
      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