• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
GetIdFromPath(const base::FilePath & path)34 std::string GetIdFromPath(const base::FilePath& path) {
35   return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
36 }
37 
38 }  // namespace
39 
FileCache(ResourceMetadataStorage * storage,const base::FilePath & cache_file_directory,base::SequencedTaskRunner * blocking_task_runner,FreeDiskSpaceGetterInterface * free_disk_space_getter)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 
~FileCache()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 
GetCacheFilePath(const std::string & id) const59 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const {
60   return cache_file_directory_.Append(
61       base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id)));
62 }
63 
AssertOnSequencedWorkerPool()64 void FileCache::AssertOnSequencedWorkerPool() {
65   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
66 }
67 
IsUnderFileCacheDirectory(const base::FilePath & path) const68 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
69   return cache_file_directory_.IsParent(path);
70 }
71 
GetCacheEntry(const std::string & id,FileCacheEntry * entry)72 bool FileCache::GetCacheEntry(const std::string& id, FileCacheEntry* entry) {
73   DCHECK(entry);
74   AssertOnSequencedWorkerPool();
75   return storage_->GetCacheEntry(id, entry);
76 }
77 
GetIterator()78 scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
79   AssertOnSequencedWorkerPool();
80   return storage_->GetCacheEntryIterator();
81 }
82 
FreeDiskSpaceIfNeededFor(int64 num_bytes)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 
GetFile(const std::string & id,base::FilePath * cache_file_path)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 
Store(const std::string & id,const std::string & md5,const base::FilePath & source_path,FileOperationType file_operation_type)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 
Pin(const std::string & id)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 
Unpin(const std::string & id)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 
MarkAsMounted(const std::string & id,base::FilePath * cache_file_path)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 
MarkDirty(const std::string & id)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 
ClearDirty(const std::string & id,const std::string & md5)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 
Remove(const std::string & id)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 
ClearAll()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 
Initialize()344 bool FileCache::Initialize() {
345   AssertOnSequencedWorkerPool();
346 
347   if (!RenameCacheFilesToNewFormat())
348     return false;
349   return true;
350 }
351 
Destroy()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 
DestroyOnBlockingPool()366 void FileCache::DestroyOnBlockingPool() {
367   AssertOnSequencedWorkerPool();
368   delete this;
369 }
370 
RecoverFilesFromCacheDirectory(const base::FilePath & dest_directory,const ResourceMetadataStorage::RecoveredCacheInfoMap & recovered_cache_info)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 
MarkAsUnmounted(const base::FilePath & file_path)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 
HasEnoughSpaceFor(int64 num_bytes,const base::FilePath & path)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 
RenameCacheFilesToNewFormat()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