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.
5 #include "chrome/browser/chromeos/drive/file_cache.h"
9 #include "base/callback_helpers.h"
10 #include "base/file_util.h"
11 #include "base/files/file_enumerator.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/mime_sniffer.h"
25 #include "net/base/mime_util.h"
26 #include "net/base/net_util.h"
27 #include "third_party/cros_system_api/constants/cryptohome.h"
29 using content::BrowserThread;
35 // Returns ID extracted from the path.
36 std::string GetIdFromPath(const base::FilePath& path) {
37 return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
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),
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));
55 FileCache::~FileCache() {
56 // Must be on the sequenced worker pool, as |metadata_| must be deleted on
57 // the sequenced worker pool.
58 AssertOnSequencedWorkerPool();
61 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const {
62 return cache_file_directory_.Append(
63 base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id)));
66 void FileCache::AssertOnSequencedWorkerPool() {
67 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
70 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
71 return cache_file_directory_.IsParent(path);
74 bool FileCache::GetCacheEntry(const std::string& id, FileCacheEntry* entry) {
76 AssertOnSequencedWorkerPool();
77 return storage_->GetCacheEntry(id, entry);
80 scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
81 AssertOnSequencedWorkerPool();
82 return storage_->GetCacheEntryIterator();
85 bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
86 AssertOnSequencedWorkerPool();
88 // Do nothing and return if we have enough space.
89 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
92 // Otherwise, try to free up the disk space.
93 DVLOG(1) << "Freeing up disk space for " << num_bytes;
95 // Remove all entries unless specially marked.
96 scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
97 storage_->GetCacheEntryIterator();
98 for (; !it->IsAtEnd(); it->Advance()) {
99 const FileCacheEntry& entry = it->GetValue();
100 if (!entry.is_pinned() &&
102 !mounted_files_.count(it->GetID()))
103 storage_->RemoveCacheEntry(it->GetID());
105 DCHECK(!it->HasError());
107 // Remove all files which have no corresponding cache entries.
108 base::FileEnumerator enumerator(cache_file_directory_,
109 false, // not recursive
110 base::FileEnumerator::FILES);
111 FileCacheEntry entry;
112 for (base::FilePath current = enumerator.Next(); !current.empty();
113 current = enumerator.Next()) {
114 std::string id = GetIdFromPath(current);
115 if (!storage_->GetCacheEntry(id, &entry))
116 base::DeleteFile(current, false /* recursive */);
119 // Check the disk space again.
120 return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
123 FileError FileCache::GetFile(const std::string& id,
124 base::FilePath* cache_file_path) {
125 AssertOnSequencedWorkerPool();
126 DCHECK(cache_file_path);
128 FileCacheEntry cache_entry;
129 if (!storage_->GetCacheEntry(id, &cache_entry) ||
130 !cache_entry.is_present())
131 return FILE_ERROR_NOT_FOUND;
133 *cache_file_path = GetCacheFilePath(id);
134 return FILE_ERROR_OK;
137 FileError FileCache::Store(const std::string& id,
138 const std::string& md5,
139 const base::FilePath& source_path,
140 FileOperationType file_operation_type) {
141 AssertOnSequencedWorkerPool();
144 if (file_operation_type == FILE_OPERATION_COPY) {
145 if (!base::GetFileSize(source_path, &file_size)) {
146 LOG(WARNING) << "Couldn't get file size for: " << source_path.value();
147 return FILE_ERROR_FAILED;
150 if (!FreeDiskSpaceIfNeededFor(file_size))
151 return FILE_ERROR_NO_LOCAL_SPACE;
153 // If file is mounted, return error.
154 if (mounted_files_.count(id))
155 return FILE_ERROR_IN_USE;
157 base::FilePath dest_path = GetCacheFilePath(id);
158 bool success = false;
159 switch (file_operation_type) {
160 case FILE_OPERATION_MOVE:
161 success = base::Move(source_path, dest_path);
163 case FILE_OPERATION_COPY:
164 success = base::CopyFile(source_path, dest_path);
171 LOG(ERROR) << "Failed to store: "
172 << "source_path = " << source_path.value() << ", "
173 << "dest_path = " << dest_path.value() << ", "
174 << "file_operation_type = " << file_operation_type;
175 return FILE_ERROR_FAILED;
178 // Now that file operations have completed, update metadata.
179 FileCacheEntry cache_entry;
180 storage_->GetCacheEntry(id, &cache_entry);
181 cache_entry.set_md5(md5);
182 cache_entry.set_is_present(true);
184 cache_entry.set_is_dirty(true);
185 return storage_->PutCacheEntry(id, cache_entry) ?
186 FILE_ERROR_OK : FILE_ERROR_FAILED;
189 FileError FileCache::Pin(const std::string& id) {
190 AssertOnSequencedWorkerPool();
192 FileCacheEntry cache_entry;
193 storage_->GetCacheEntry(id, &cache_entry);
194 cache_entry.set_is_pinned(true);
195 return storage_->PutCacheEntry(id, cache_entry) ?
196 FILE_ERROR_OK : FILE_ERROR_FAILED;
199 FileError FileCache::Unpin(const std::string& id) {
200 AssertOnSequencedWorkerPool();
202 // Unpinning a file means its entry must exist in cache.
203 FileCacheEntry cache_entry;
204 if (!storage_->GetCacheEntry(id, &cache_entry))
205 return FILE_ERROR_NOT_FOUND;
207 // Now that file operations have completed, update metadata.
208 if (cache_entry.is_present()) {
209 cache_entry.set_is_pinned(false);
210 if (!storage_->PutCacheEntry(id, cache_entry))
211 return FILE_ERROR_FAILED;
213 // Remove the existing entry if we are unpinning a non-present file.
214 if (!storage_->RemoveCacheEntry(id))
215 return FILE_ERROR_FAILED;
218 // Now it's a chance to free up space if needed.
219 FreeDiskSpaceIfNeededFor(0);
221 return FILE_ERROR_OK;
224 FileError FileCache::MarkAsMounted(const std::string& id,
225 base::FilePath* cache_file_path) {
226 AssertOnSequencedWorkerPool();
227 DCHECK(cache_file_path);
229 // Get cache entry associated with the id and md5
230 FileCacheEntry cache_entry;
231 if (!storage_->GetCacheEntry(id, &cache_entry))
232 return FILE_ERROR_NOT_FOUND;
234 if (mounted_files_.count(id))
235 return FILE_ERROR_INVALID_OPERATION;
237 // Ensure the file is readable to cros_disks. See crbug.com/236994.
238 base::FilePath path = GetCacheFilePath(id);
239 if (!base::SetPosixFilePermissions(
241 base::FILE_PERMISSION_READ_BY_USER |
242 base::FILE_PERMISSION_WRITE_BY_USER |
243 base::FILE_PERMISSION_READ_BY_GROUP |
244 base::FILE_PERMISSION_READ_BY_OTHERS))
245 return FILE_ERROR_FAILED;
247 mounted_files_.insert(id);
249 *cache_file_path = path;
250 return FILE_ERROR_OK;
253 FileError FileCache::OpenForWrite(
254 const std::string& id,
255 scoped_ptr<base::ScopedClosureRunner>* file_closer) {
256 AssertOnSequencedWorkerPool();
258 // Marking a file dirty means its entry and actual file blob must exist in
260 FileCacheEntry cache_entry;
261 if (!storage_->GetCacheEntry(id, &cache_entry) ||
262 !cache_entry.is_present()) {
263 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id;
264 return FILE_ERROR_NOT_FOUND;
267 cache_entry.set_is_dirty(true);
268 cache_entry.clear_md5();
269 if (!storage_->PutCacheEntry(id, cache_entry))
270 return FILE_ERROR_FAILED;
272 write_opened_files_[id]++;
273 file_closer->reset(new base::ScopedClosureRunner(
274 base::Bind(&google_apis::RunTaskOnThread,
275 blocking_task_runner_,
276 base::Bind(&FileCache::CloseForWrite,
277 weak_ptr_factory_.GetWeakPtr(),
279 return FILE_ERROR_OK;
282 bool FileCache::IsOpenedForWrite(const std::string& id) {
283 AssertOnSequencedWorkerPool();
284 return write_opened_files_.count(id);
287 FileError FileCache::UpdateMd5(const std::string& id) {
288 AssertOnSequencedWorkerPool();
290 if (IsOpenedForWrite(id))
291 return FILE_ERROR_IN_USE;
293 FileCacheEntry cache_entry;
294 if (!storage_->GetCacheEntry(id, &cache_entry) ||
295 !cache_entry.is_present())
296 return FILE_ERROR_NOT_FOUND;
298 const std::string& md5 = util::GetMd5Digest(GetCacheFilePath(id));
300 return FILE_ERROR_NOT_FOUND;
302 cache_entry.set_md5(md5);
303 return storage_->PutCacheEntry(id, cache_entry) ?
304 FILE_ERROR_OK : FILE_ERROR_FAILED;
307 FileError FileCache::ClearDirty(const std::string& id) {
308 AssertOnSequencedWorkerPool();
310 if (IsOpenedForWrite(id))
311 return FILE_ERROR_IN_USE;
313 // Clearing a dirty file means its entry and actual file blob must exist in
315 FileCacheEntry cache_entry;
316 if (!storage_->GetCacheEntry(id, &cache_entry) ||
317 !cache_entry.is_present()) {
318 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
320 return FILE_ERROR_NOT_FOUND;
323 // If a file is not dirty (it should have been marked dirty via OpenForWrite),
324 // clearing its dirty state is an invalid operation.
325 if (!cache_entry.is_dirty()) {
326 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id;
327 return FILE_ERROR_INVALID_OPERATION;
330 cache_entry.set_is_dirty(false);
331 return storage_->PutCacheEntry(id, cache_entry) ?
332 FILE_ERROR_OK : FILE_ERROR_FAILED;
335 FileError FileCache::Remove(const std::string& id) {
336 AssertOnSequencedWorkerPool();
338 FileCacheEntry cache_entry;
340 // If entry doesn't exist, nothing to do.
341 if (!storage_->GetCacheEntry(id, &cache_entry))
342 return FILE_ERROR_OK;
344 // Cannot delete a mounted file.
345 if (mounted_files_.count(id))
346 return FILE_ERROR_IN_USE;
349 base::FilePath path = GetCacheFilePath(id);
350 if (!base::DeleteFile(path, false /* recursive */))
351 return FILE_ERROR_FAILED;
353 // Now that all file operations have completed, remove from metadata.
354 return storage_->RemoveCacheEntry(id) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
357 bool FileCache::ClearAll() {
358 AssertOnSequencedWorkerPool();
360 // Remove entries on the metadata.
361 scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
362 storage_->GetCacheEntryIterator();
363 for (; !it->IsAtEnd(); it->Advance()) {
364 if (!storage_->RemoveCacheEntry(it->GetID()))
372 base::FileEnumerator enumerator(cache_file_directory_,
373 false, // not recursive
374 base::FileEnumerator::FILES);
375 for (base::FilePath file = enumerator.Next(); !file.empty();
376 file = enumerator.Next())
377 base::DeleteFile(file, false /* recursive */);
382 bool FileCache::Initialize() {
383 AssertOnSequencedWorkerPool();
385 // Older versions do not clear MD5 when marking entries dirty.
386 // Clear MD5 of all dirty entries to deal with old data.
387 scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
388 storage_->GetCacheEntryIterator();
389 for (; !it->IsAtEnd(); it->Advance()) {
390 if (it->GetValue().is_dirty()) {
391 FileCacheEntry new_entry(it->GetValue());
392 new_entry.clear_md5();
393 if (!storage_->PutCacheEntry(it->GetID(), new_entry))
398 if (!RenameCacheFilesToNewFormat())
403 void FileCache::Destroy() {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
406 // Destroy myself on the blocking pool.
407 // Note that base::DeletePointer<> cannot be used as the destructor of this
409 blocking_task_runner_->PostTask(
411 base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
414 void FileCache::DestroyOnBlockingPool() {
415 AssertOnSequencedWorkerPool();
419 bool FileCache::RecoverFilesFromCacheDirectory(
420 const base::FilePath& dest_directory,
421 const ResourceMetadataStorage::RecoveredCacheInfoMap&
422 recovered_cache_info) {
425 base::FileEnumerator enumerator(cache_file_directory_,
426 false, // not recursive
427 base::FileEnumerator::FILES);
428 for (base::FilePath current = enumerator.Next(); !current.empty();
429 current = enumerator.Next()) {
430 const std::string& id = GetIdFromPath(current);
431 FileCacheEntry entry;
432 if (storage_->GetCacheEntry(id, &entry)) {
433 // This file is managed by FileCache, no need to recover it.
437 // If a cache entry which is non-dirty and has matching MD5 is found in
438 // |recovered_cache_entries|, it means the current file is already uploaded
439 // to the server. Just delete it instead of recovering it.
440 ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it =
441 recovered_cache_info.find(id);
442 if (it != recovered_cache_info.end()) {
443 // Due to the DB corruption, cache info might be recovered from old
444 // revision. Perform MD5 check even when is_dirty is false just in case.
445 if (!it->second.is_dirty &&
446 it->second.md5 == util::GetMd5Digest(current)) {
447 base::DeleteFile(current, false /* recursive */);
452 // Read file contents to sniff mime type.
453 std::vector<char> content(net::kMaxBytesToSniff);
454 const int read_result =
455 base::ReadFile(current, &content[0], content.size());
456 if (read_result < 0) {
457 LOG(WARNING) << "Cannot read: " << current.value();
460 if (read_result == 0) // Skip empty files.
463 // Use recovered file name if available, otherwise decide file name with
464 // sniffed mime type.
465 base::FilePath dest_base_name(FILE_PATH_LITERAL("file"));
466 std::string mime_type;
467 if (it != recovered_cache_info.end() && !it->second.title.empty()) {
468 // We can use a file name recovered from the trashed DB.
469 dest_base_name = base::FilePath::FromUTF8Unsafe(it->second.title);
470 } else if (net::SniffMimeType(&content[0], read_result,
471 net::FilePathToFileURL(current),
472 std::string(), &mime_type) ||
473 net::SniffMimeTypeFromLocalData(&content[0], read_result,
475 // Change base name for common mime types.
476 if (net::MatchesMimeType("image/*", mime_type)) {
477 dest_base_name = base::FilePath(FILE_PATH_LITERAL("image"));
478 } else if (net::MatchesMimeType("video/*", mime_type)) {
479 dest_base_name = base::FilePath(FILE_PATH_LITERAL("video"));
480 } else if (net::MatchesMimeType("audio/*", mime_type)) {
481 dest_base_name = base::FilePath(FILE_PATH_LITERAL("audio"));
484 // Estimate extension from mime type.
485 std::vector<base::FilePath::StringType> extensions;
486 base::FilePath::StringType extension;
487 if (net::GetPreferredExtensionForMimeType(mime_type, &extension))
488 extensions.push_back(extension);
490 net::GetExtensionsForMimeType(mime_type, &extensions);
492 // Add extension if possible.
493 if (!extensions.empty())
494 dest_base_name = dest_base_name.AddExtension(extensions[0]);
497 // Add file number to the file name and move.
498 const base::FilePath& dest_path = dest_directory.Append(dest_base_name)
499 .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number++));
500 if (!base::CreateDirectory(dest_directory) ||
501 !base::Move(current, dest_path)) {
502 LOG(WARNING) << "Failed to move: " << current.value()
503 << " to " << dest_path.value();
507 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
512 FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
513 AssertOnSequencedWorkerPool();
514 DCHECK(IsUnderFileCacheDirectory(file_path));
516 std::string id = GetIdFromPath(file_path);
518 // Get cache entry associated with the id and md5
519 FileCacheEntry cache_entry;
520 if (!storage_->GetCacheEntry(id, &cache_entry))
521 return FILE_ERROR_NOT_FOUND;
523 std::set<std::string>::iterator it = mounted_files_.find(id);
524 if (it == mounted_files_.end())
525 return FILE_ERROR_INVALID_OPERATION;
527 mounted_files_.erase(it);
528 return FILE_ERROR_OK;
531 bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
532 const base::FilePath& path) {
533 int64 free_space = 0;
534 if (free_disk_space_getter_)
535 free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
537 free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
539 // Subtract this as if this portion does not exist.
540 free_space -= cryptohome::kMinFreeSpaceInBytes;
541 return (free_space >= num_bytes);
544 bool FileCache::RenameCacheFilesToNewFormat() {
545 base::FileEnumerator enumerator(cache_file_directory_,
546 false, // not recursive
547 base::FileEnumerator::FILES);
548 for (base::FilePath current = enumerator.Next(); !current.empty();
549 current = enumerator.Next()) {
550 base::FilePath new_path = current.RemoveExtension();
551 if (!new_path.Extension().empty()) {
552 // Delete files with multiple extensions.
553 if (!base::DeleteFile(current, false /* recursive */))
557 const std::string& id = GetIdFromPath(new_path);
558 new_path = GetCacheFilePath(util::CanonicalizeResourceId(id));
559 if (new_path != current && !base::Move(current, new_path))
565 void FileCache::CloseForWrite(const std::string& id) {
566 AssertOnSequencedWorkerPool();
568 std::map<std::string, int>::iterator it = write_opened_files_.find(id);
569 if (it == write_opened_files_.end())
572 DCHECK_LT(0, it->second);
575 write_opened_files_.erase(it);
578 } // namespace internal