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 "components/drive/file_cache.h"
10 #include "base/bind_helpers.h"
11 #include "base/callback_helpers.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/metrics/histogram.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/sys_info.h"
20 #include "components/drive/drive.pb.h"
21 #include "components/drive/drive_api_util.h"
22 #include "components/drive/file_system_core_util.h"
23 #include "components/drive/resource_metadata_storage.h"
24 #include "google_apis/drive/task_util.h"
25 #include "net/base/filename_util.h"
26 #include "net/base/mime_sniffer.h"
27 #include "net/base/mime_util.h"
28 #if defined(OS_CHROMEOS)
29 #include "third_party/cros_system_api/constants/cryptohome.h"
36 // Returns ID extracted from the path.
37 std::string
GetIdFromPath(const base::FilePath
& path
) {
38 return util::UnescapeCacheFileName(path
.BaseName().AsUTF8Unsafe());
43 FileCache::FileCache(ResourceMetadataStorage
* storage
,
44 const base::FilePath
& cache_file_directory
,
45 base::SequencedTaskRunner
* blocking_task_runner
,
46 FreeDiskSpaceGetterInterface
* free_disk_space_getter
)
47 : cache_file_directory_(cache_file_directory
),
48 blocking_task_runner_(blocking_task_runner
),
50 free_disk_space_getter_(free_disk_space_getter
),
51 weak_ptr_factory_(this) {
52 DCHECK(blocking_task_runner_
.get());
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::FreeDiskSpaceIfNeededFor(int64 num_bytes
) {
75 AssertOnSequencedWorkerPool();
77 // Do nothing and return if we have enough space.
78 if (HasEnoughSpaceFor(num_bytes
, cache_file_directory_
))
81 // Otherwise, try to free up the disk space.
82 DVLOG(1) << "Freeing up disk space for " << num_bytes
;
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
);
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
);
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
)
116 // Check the disk space again.
117 return HasEnoughSpaceFor(num_bytes
, cache_file_directory_
);
120 FileError
FileCache::GetFile(const std::string
& id
,
121 base::FilePath
* cache_file_path
) {
122 AssertOnSequencedWorkerPool();
123 DCHECK(cache_file_path
);
126 FileError error
= storage_
->GetEntry(id
, &entry
);
127 if (error
!= FILE_ERROR_OK
)
129 if (!entry
.file_specific_info().cache_state().is_present())
130 return FILE_ERROR_NOT_FOUND
;
132 *cache_file_path
= GetCacheFilePath(id
);
133 return FILE_ERROR_OK
;
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();
143 FileError error
= storage_
->GetEntry(id
, &entry
);
144 if (error
!= FILE_ERROR_OK
)
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
;
154 if (!FreeDiskSpaceIfNeededFor(file_size
))
155 return FILE_ERROR_NO_LOCAL_SPACE
;
157 // If file is mounted, return error.
158 if (mounted_files_
.count(id
))
159 return FILE_ERROR_IN_USE
;
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
);
167 case FILE_OPERATION_COPY
:
168 success
= base::CopyFile(source_path
, dest_path
);
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
;
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);
188 cache_state
->set_is_dirty(true);
189 return storage_
->PutEntry(entry
);
192 FileError
FileCache::Pin(const std::string
& id
) {
193 AssertOnSequencedWorkerPool();
196 FileError error
= storage_
->GetEntry(id
, &entry
);
197 if (error
!= FILE_ERROR_OK
)
199 entry
.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned(
201 return storage_
->PutEntry(entry
);
204 FileError
FileCache::Unpin(const std::string
& id
) {
205 AssertOnSequencedWorkerPool();
207 // Unpinning a file means its entry must exist in cache.
209 FileError error
= storage_
->GetEntry(id
, &entry
);
210 if (error
!= FILE_ERROR_OK
)
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(
218 // Remove the existing entry if we are unpinning a non-present file.
219 entry
.mutable_file_specific_info()->clear_cache_state();
221 error
= storage_
->PutEntry(entry
);
222 if (error
!= FILE_ERROR_OK
)
225 // Now it's a chance to free up space if needed.
226 FreeDiskSpaceIfNeededFor(0);
228 return FILE_ERROR_OK
;
231 FileError
FileCache::MarkAsMounted(const std::string
& id
,
232 base::FilePath
* cache_file_path
) {
233 AssertOnSequencedWorkerPool();
234 DCHECK(cache_file_path
);
236 // Get cache entry associated with the id and md5
238 FileError error
= storage_
->GetEntry(id
, &entry
);
239 if (error
!= FILE_ERROR_OK
)
241 if (!entry
.file_specific_info().cache_state().is_present())
242 return FILE_ERROR_NOT_FOUND
;
244 if (mounted_files_
.count(id
))
245 return FILE_ERROR_INVALID_OPERATION
;
247 base::FilePath path
= GetCacheFilePath(id
);
249 #if defined(OS_CHROMEOS)
250 // Ensure the file is readable to cros_disks. See crbug.com/236994.
251 if (!base::SetPosixFilePermissions(
253 base::FILE_PERMISSION_READ_BY_USER
|
254 base::FILE_PERMISSION_WRITE_BY_USER
|
255 base::FILE_PERMISSION_READ_BY_GROUP
|
256 base::FILE_PERMISSION_READ_BY_OTHERS
))
257 return FILE_ERROR_FAILED
;
260 mounted_files_
.insert(id
);
262 *cache_file_path
= path
;
263 return FILE_ERROR_OK
;
266 FileError
FileCache::OpenForWrite(
267 const std::string
& id
,
268 scoped_ptr
<base::ScopedClosureRunner
>* file_closer
) {
269 AssertOnSequencedWorkerPool();
271 // Marking a file dirty means its entry and actual file blob must exist in
274 FileError error
= storage_
->GetEntry(id
, &entry
);
275 if (error
!= FILE_ERROR_OK
)
277 if (!entry
.file_specific_info().cache_state().is_present()) {
278 LOG(WARNING
) << "Can't mark dirty a file that wasn't cached: " << id
;
279 return FILE_ERROR_NOT_FOUND
;
282 entry
.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true);
283 entry
.mutable_file_specific_info()->mutable_cache_state()->clear_md5();
284 error
= storage_
->PutEntry(entry
);
285 if (error
!= FILE_ERROR_OK
)
288 write_opened_files_
[id
]++;
289 file_closer
->reset(new base::ScopedClosureRunner(
290 base::Bind(&google_apis::RunTaskWithTaskRunner
,
291 blocking_task_runner_
,
292 base::Bind(&FileCache::CloseForWrite
,
293 weak_ptr_factory_
.GetWeakPtr(),
295 return FILE_ERROR_OK
;
298 bool FileCache::IsOpenedForWrite(const std::string
& id
) {
299 AssertOnSequencedWorkerPool();
300 return write_opened_files_
.count(id
) != 0;
303 FileError
FileCache::UpdateMd5(const std::string
& id
) {
304 AssertOnSequencedWorkerPool();
306 if (IsOpenedForWrite(id
))
307 return FILE_ERROR_IN_USE
;
310 FileError error
= storage_
->GetEntry(id
, &entry
);
311 if (error
!= FILE_ERROR_OK
)
313 if (!entry
.file_specific_info().cache_state().is_present())
314 return FILE_ERROR_NOT_FOUND
;
316 const std::string
& md5
=
317 util::GetMd5Digest(GetCacheFilePath(id
), &in_shutdown_
);
318 if (in_shutdown_
.IsSet())
319 return FILE_ERROR_ABORT
;
321 return FILE_ERROR_NOT_FOUND
;
323 entry
.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5
);
324 return storage_
->PutEntry(entry
);
327 FileError
FileCache::ClearDirty(const std::string
& id
) {
328 AssertOnSequencedWorkerPool();
330 if (IsOpenedForWrite(id
))
331 return FILE_ERROR_IN_USE
;
333 // Clearing a dirty file means its entry and actual file blob must exist in
336 FileError error
= storage_
->GetEntry(id
, &entry
);
337 if (error
!= FILE_ERROR_OK
)
339 if (!entry
.file_specific_info().cache_state().is_present()) {
340 LOG(WARNING
) << "Can't clear dirty state of a file that wasn't cached: "
342 return FILE_ERROR_NOT_FOUND
;
345 // If a file is not dirty (it should have been marked dirty via OpenForWrite),
346 // clearing its dirty state is an invalid operation.
347 if (!entry
.file_specific_info().cache_state().is_dirty()) {
348 LOG(WARNING
) << "Can't clear dirty state of a non-dirty file: " << id
;
349 return FILE_ERROR_INVALID_OPERATION
;
352 entry
.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(
354 return storage_
->PutEntry(entry
);
357 FileError
FileCache::Remove(const std::string
& id
) {
358 AssertOnSequencedWorkerPool();
362 // If entry doesn't exist, nothing to do.
363 FileError error
= storage_
->GetEntry(id
, &entry
);
364 if (error
== FILE_ERROR_NOT_FOUND
)
365 return FILE_ERROR_OK
;
366 if (error
!= FILE_ERROR_OK
)
368 if (!entry
.file_specific_info().has_cache_state())
369 return FILE_ERROR_OK
;
371 // Cannot delete a mounted file.
372 if (mounted_files_
.count(id
))
373 return FILE_ERROR_IN_USE
;
376 base::FilePath path
= GetCacheFilePath(id
);
377 if (!base::DeleteFile(path
, false /* recursive */))
378 return FILE_ERROR_FAILED
;
380 // Now that all file operations have completed, remove from metadata.
381 entry
.mutable_file_specific_info()->clear_cache_state();
382 return storage_
->PutEntry(entry
);
385 bool FileCache::ClearAll() {
386 AssertOnSequencedWorkerPool();
389 base::FileEnumerator
enumerator(cache_file_directory_
,
390 false, // not recursive
391 base::FileEnumerator::FILES
);
392 for (base::FilePath file
= enumerator
.Next(); !file
.empty();
393 file
= enumerator
.Next())
394 base::DeleteFile(file
, false /* recursive */);
399 bool FileCache::Initialize() {
400 AssertOnSequencedWorkerPool();
402 // Older versions do not clear MD5 when marking entries dirty.
403 // Clear MD5 of all dirty entries to deal with old data.
404 scoped_ptr
<ResourceMetadataStorage::Iterator
> it
= storage_
->GetIterator();
405 for (; !it
->IsAtEnd(); it
->Advance()) {
406 if (it
->GetValue().file_specific_info().cache_state().is_dirty()) {
407 ResourceEntry
new_entry(it
->GetValue());
408 new_entry
.mutable_file_specific_info()->mutable_cache_state()->
410 if (storage_
->PutEntry(new_entry
) != FILE_ERROR_OK
)
417 if (!RenameCacheFilesToNewFormat())
422 void FileCache::Destroy() {
423 DCHECK(thread_checker_
.CalledOnValidThread());
427 // Destroy myself on the blocking pool.
428 // Note that base::DeletePointer<> cannot be used as the destructor of this
430 blocking_task_runner_
->PostTask(
432 base::Bind(&FileCache::DestroyOnBlockingPool
, base::Unretained(this)));
435 void FileCache::DestroyOnBlockingPool() {
436 AssertOnSequencedWorkerPool();
440 bool FileCache::RecoverFilesFromCacheDirectory(
441 const base::FilePath
& dest_directory
,
442 const ResourceMetadataStorage::RecoveredCacheInfoMap
&
443 recovered_cache_info
) {
446 base::FileEnumerator
enumerator(cache_file_directory_
,
447 false, // not recursive
448 base::FileEnumerator::FILES
);
449 for (base::FilePath current
= enumerator
.Next(); !current
.empty();
450 current
= enumerator
.Next()) {
451 const std::string
& id
= GetIdFromPath(current
);
453 FileError error
= storage_
->GetEntry(id
, &entry
);
454 if (error
!= FILE_ERROR_OK
&& error
!= FILE_ERROR_NOT_FOUND
)
456 if (error
== FILE_ERROR_OK
&&
457 entry
.file_specific_info().cache_state().is_present()) {
458 // This file is managed by FileCache, no need to recover it.
462 // If a cache entry which is non-dirty and has matching MD5 is found in
463 // |recovered_cache_entries|, it means the current file is already uploaded
464 // to the server. Just delete it instead of recovering it.
465 ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it
=
466 recovered_cache_info
.find(id
);
467 if (it
!= recovered_cache_info
.end()) {
468 // Due to the DB corruption, cache info might be recovered from old
469 // revision. Perform MD5 check even when is_dirty is false just in case.
470 if (!it
->second
.is_dirty
&&
471 it
->second
.md5
== util::GetMd5Digest(current
, &in_shutdown_
)) {
472 base::DeleteFile(current
, false /* recursive */);
477 // Read file contents to sniff mime type.
478 std::vector
<char> content(net::kMaxBytesToSniff
);
479 const int read_result
=
480 base::ReadFile(current
, &content
[0], content
.size());
481 if (read_result
< 0) {
482 LOG(WARNING
) << "Cannot read: " << current
.value();
485 if (read_result
== 0) // Skip empty files.
488 // Use recovered file name if available, otherwise decide file name with
489 // sniffed mime type.
490 base::FilePath
dest_base_name(FILE_PATH_LITERAL("file"));
491 std::string mime_type
;
492 if (it
!= recovered_cache_info
.end() && !it
->second
.title
.empty()) {
493 // We can use a file name recovered from the trashed DB.
494 dest_base_name
= base::FilePath::FromUTF8Unsafe(it
->second
.title
);
495 } else if (net::SniffMimeType(&content
[0], read_result
,
496 net::FilePathToFileURL(current
),
497 std::string(), &mime_type
) ||
498 net::SniffMimeTypeFromLocalData(&content
[0], read_result
,
500 // Change base name for common mime types.
501 if (net::MatchesMimeType("image/*", mime_type
)) {
502 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("image"));
503 } else if (net::MatchesMimeType("video/*", mime_type
)) {
504 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("video"));
505 } else if (net::MatchesMimeType("audio/*", mime_type
)) {
506 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("audio"));
509 // Estimate extension from mime type.
510 std::vector
<base::FilePath::StringType
> extensions
;
511 base::FilePath::StringType extension
;
512 if (net::GetPreferredExtensionForMimeType(mime_type
, &extension
))
513 extensions
.push_back(extension
);
515 net::GetExtensionsForMimeType(mime_type
, &extensions
);
517 // Add extension if possible.
518 if (!extensions
.empty())
519 dest_base_name
= dest_base_name
.AddExtension(extensions
[0]);
522 // Add file number to the file name and move.
523 const base::FilePath
& dest_path
= dest_directory
.Append(dest_base_name
)
524 .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number
++));
525 if (!base::CreateDirectory(dest_directory
) ||
526 !base::Move(current
, dest_path
)) {
527 LOG(WARNING
) << "Failed to move: " << current
.value()
528 << " to " << dest_path
.value();
532 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
537 FileError
FileCache::MarkAsUnmounted(const base::FilePath
& file_path
) {
538 AssertOnSequencedWorkerPool();
539 DCHECK(IsUnderFileCacheDirectory(file_path
));
541 std::string id
= GetIdFromPath(file_path
);
543 // Get the entry associated with the id.
545 FileError error
= storage_
->GetEntry(id
, &entry
);
546 if (error
!= FILE_ERROR_OK
)
549 std::set
<std::string
>::iterator it
= mounted_files_
.find(id
);
550 if (it
== mounted_files_
.end())
551 return FILE_ERROR_INVALID_OPERATION
;
553 mounted_files_
.erase(it
);
554 return FILE_ERROR_OK
;
557 bool FileCache::HasEnoughSpaceFor(int64 num_bytes
,
558 const base::FilePath
& path
) {
559 int64 free_space
= 0;
560 if (free_disk_space_getter_
)
561 free_space
= free_disk_space_getter_
->AmountOfFreeDiskSpace();
563 free_space
= base::SysInfo::AmountOfFreeDiskSpace(path
);
565 // Subtract this as if this portion does not exist.
566 #if defined(OS_CHROMEOS)
567 const int64 kMinFreeBytes
= cryptohome::kMinFreeSpaceInBytes
;
569 const int64 kMinFreeBytes
= 512ull * 1024ull * 1024ull; // 512MB
571 free_space
-= kMinFreeBytes
;
572 return (free_space
>= num_bytes
);
575 bool FileCache::RenameCacheFilesToNewFormat() {
576 base::FileEnumerator
enumerator(cache_file_directory_
,
577 false, // not recursive
578 base::FileEnumerator::FILES
);
579 for (base::FilePath current
= enumerator
.Next(); !current
.empty();
580 current
= enumerator
.Next()) {
581 base::FilePath new_path
= current
.RemoveExtension();
582 if (!new_path
.Extension().empty()) {
583 // Delete files with multiple extensions.
584 if (!base::DeleteFile(current
, false /* recursive */))
588 const std::string
& id
= GetIdFromPath(new_path
);
589 new_path
= GetCacheFilePath(util::CanonicalizeResourceId(id
));
590 if (new_path
!= current
&& !base::Move(current
, new_path
))
596 void FileCache::CloseForWrite(const std::string
& id
) {
597 AssertOnSequencedWorkerPool();
599 std::map
<std::string
, int>::iterator it
= write_opened_files_
.find(id
);
600 if (it
== write_opened_files_
.end())
603 DCHECK_LT(0, it
->second
);
606 write_opened_files_
.erase(it
);
608 // Update last modified date.
610 FileError error
= storage_
->GetEntry(id
, &entry
);
611 if (error
!= FILE_ERROR_OK
) {
612 LOG(ERROR
) << "Failed to get entry: " << id
<< ", "
613 << FileErrorToString(error
);
616 entry
.mutable_file_info()->set_last_modified(
617 base::Time::Now().ToInternalValue());
618 error
= storage_
->PutEntry(entry
);
619 if (error
!= FILE_ERROR_OK
) {
620 LOG(ERROR
) << "Failed to put entry: " << id
<< ", "
621 << FileErrorToString(error
);
625 } // namespace internal