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 FileCacheEntry cache_entry
;
154 storage_
->GetCacheEntry(id
, &cache_entry
);
156 // If file is dirty or mounted, return error.
157 if (cache_entry
.is_dirty() || mounted_files_
.count(id
))
158 return FILE_ERROR_IN_USE
;
160 base::FilePath dest_path
= GetCacheFilePath(id
);
161 bool success
= false;
162 switch (file_operation_type
) {
163 case FILE_OPERATION_MOVE
:
164 success
= base::Move(source_path
, dest_path
);
166 case FILE_OPERATION_COPY
:
167 success
= base::CopyFile(source_path
, dest_path
);
174 LOG(ERROR
) << "Failed to store: "
175 << "source_path = " << source_path
.value() << ", "
176 << "dest_path = " << dest_path
.value() << ", "
177 << "file_operation_type = " << file_operation_type
;
178 return FILE_ERROR_FAILED
;
181 // Now that file operations have completed, update metadata.
182 cache_entry
.set_md5(md5
);
183 cache_entry
.set_is_present(true);
184 cache_entry
.set_is_dirty(false);
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 if (!cache_entry
.is_dirty()) {
268 cache_entry
.set_is_dirty(true);
269 if (!storage_
->PutCacheEntry(id
, cache_entry
))
270 return FILE_ERROR_FAILED
;
273 write_opened_files_
[id
]++;
274 file_closer
->reset(new base::ScopedClosureRunner(
275 base::Bind(&google_apis::RunTaskOnThread
,
276 blocking_task_runner_
,
277 base::Bind(&FileCache::CloseForWrite
,
278 weak_ptr_factory_
.GetWeakPtr(),
280 return FILE_ERROR_OK
;
283 bool FileCache::IsOpenedForWrite(const std::string
& id
) {
284 AssertOnSequencedWorkerPool();
285 return write_opened_files_
.count(id
);
288 FileError
FileCache::ClearDirty(const std::string
& id
, const std::string
& md5
) {
289 AssertOnSequencedWorkerPool();
291 // Clearing a dirty file means its entry and actual file blob must exist in
293 FileCacheEntry cache_entry
;
294 if (!storage_
->GetCacheEntry(id
, &cache_entry
) ||
295 !cache_entry
.is_present()) {
296 LOG(WARNING
) << "Can't clear dirty state of a file that wasn't cached: "
298 return FILE_ERROR_NOT_FOUND
;
301 // If a file is not dirty (it should have been marked dirty via OpenForWrite),
302 // clearing its dirty state is an invalid operation.
303 if (!cache_entry
.is_dirty()) {
304 LOG(WARNING
) << "Can't clear dirty state of a non-dirty file: " << id
;
305 return FILE_ERROR_INVALID_OPERATION
;
308 cache_entry
.set_md5(md5
);
309 cache_entry
.set_is_dirty(false);
310 return storage_
->PutCacheEntry(id
, cache_entry
) ?
311 FILE_ERROR_OK
: FILE_ERROR_FAILED
;
314 FileError
FileCache::Remove(const std::string
& id
) {
315 AssertOnSequencedWorkerPool();
317 FileCacheEntry cache_entry
;
319 // If entry doesn't exist, nothing to do.
320 if (!storage_
->GetCacheEntry(id
, &cache_entry
))
321 return FILE_ERROR_OK
;
323 // Cannot delete a mounted file.
324 if (mounted_files_
.count(id
))
325 return FILE_ERROR_IN_USE
;
328 base::FilePath path
= GetCacheFilePath(id
);
329 if (!base::DeleteFile(path
, false /* recursive */))
330 return FILE_ERROR_FAILED
;
332 // Now that all file operations have completed, remove from metadata.
333 return storage_
->RemoveCacheEntry(id
) ? FILE_ERROR_OK
: FILE_ERROR_FAILED
;
336 bool FileCache::ClearAll() {
337 AssertOnSequencedWorkerPool();
339 // Remove entries on the metadata.
340 scoped_ptr
<ResourceMetadataStorage::CacheEntryIterator
> it
=
341 storage_
->GetCacheEntryIterator();
342 for (; !it
->IsAtEnd(); it
->Advance()) {
343 if (!storage_
->RemoveCacheEntry(it
->GetID()))
351 base::FileEnumerator
enumerator(cache_file_directory_
,
352 false, // not recursive
353 base::FileEnumerator::FILES
);
354 for (base::FilePath file
= enumerator
.Next(); !file
.empty();
355 file
= enumerator
.Next())
356 base::DeleteFile(file
, false /* recursive */);
361 bool FileCache::Initialize() {
362 AssertOnSequencedWorkerPool();
364 if (!RenameCacheFilesToNewFormat())
369 void FileCache::Destroy() {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
372 // Destroy myself on the blocking pool.
373 // Note that base::DeletePointer<> cannot be used as the destructor of this
375 blocking_task_runner_
->PostTask(
377 base::Bind(&FileCache::DestroyOnBlockingPool
, base::Unretained(this)));
380 void FileCache::DestroyOnBlockingPool() {
381 AssertOnSequencedWorkerPool();
385 bool FileCache::RecoverFilesFromCacheDirectory(
386 const base::FilePath
& dest_directory
,
387 const ResourceMetadataStorage::RecoveredCacheInfoMap
&
388 recovered_cache_info
) {
391 base::FileEnumerator
enumerator(cache_file_directory_
,
392 false, // not recursive
393 base::FileEnumerator::FILES
);
394 for (base::FilePath current
= enumerator
.Next(); !current
.empty();
395 current
= enumerator
.Next()) {
396 const std::string
& id
= GetIdFromPath(current
);
397 FileCacheEntry entry
;
398 if (storage_
->GetCacheEntry(id
, &entry
)) {
399 // This file is managed by FileCache, no need to recover it.
403 // If a cache entry which is non-dirty and has matching MD5 is found in
404 // |recovered_cache_entries|, it means the current file is already uploaded
405 // to the server. Just delete it instead of recovering it.
406 ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it
=
407 recovered_cache_info
.find(id
);
408 if (it
!= recovered_cache_info
.end()) {
409 // Due to the DB corruption, cache info might be recovered from old
410 // revision. Perform MD5 check even when is_dirty is false just in case.
411 if (!it
->second
.is_dirty
&&
412 it
->second
.md5
== util::GetMd5Digest(current
)) {
413 base::DeleteFile(current
, false /* recursive */);
418 // Read file contents to sniff mime type.
419 std::vector
<char> content(net::kMaxBytesToSniff
);
420 const int read_result
=
421 base::ReadFile(current
, &content
[0], content
.size());
422 if (read_result
< 0) {
423 LOG(WARNING
) << "Cannot read: " << current
.value();
426 if (read_result
== 0) // Skip empty files.
429 // Use recovered file name if available, otherwise decide file name with
430 // sniffed mime type.
431 base::FilePath
dest_base_name(FILE_PATH_LITERAL("file"));
432 std::string mime_type
;
433 if (it
!= recovered_cache_info
.end() && !it
->second
.title
.empty()) {
434 // We can use a file name recovered from the trashed DB.
435 dest_base_name
= base::FilePath::FromUTF8Unsafe(it
->second
.title
);
436 } else if (net::SniffMimeType(&content
[0], read_result
,
437 net::FilePathToFileURL(current
),
438 std::string(), &mime_type
) ||
439 net::SniffMimeTypeFromLocalData(&content
[0], read_result
,
441 // Change base name for common mime types.
442 if (net::MatchesMimeType("image/*", mime_type
)) {
443 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("image"));
444 } else if (net::MatchesMimeType("video/*", mime_type
)) {
445 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("video"));
446 } else if (net::MatchesMimeType("audio/*", mime_type
)) {
447 dest_base_name
= base::FilePath(FILE_PATH_LITERAL("audio"));
450 // Estimate extension from mime type.
451 std::vector
<base::FilePath::StringType
> extensions
;
452 base::FilePath::StringType extension
;
453 if (net::GetPreferredExtensionForMimeType(mime_type
, &extension
))
454 extensions
.push_back(extension
);
456 net::GetExtensionsForMimeType(mime_type
, &extensions
);
458 // Add extension if possible.
459 if (!extensions
.empty())
460 dest_base_name
= dest_base_name
.AddExtension(extensions
[0]);
463 // Add file number to the file name and move.
464 const base::FilePath
& dest_path
= dest_directory
.Append(dest_base_name
)
465 .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number
++));
466 if (!base::CreateDirectory(dest_directory
) ||
467 !base::Move(current
, dest_path
)) {
468 LOG(WARNING
) << "Failed to move: " << current
.value()
469 << " to " << dest_path
.value();
473 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
478 FileError
FileCache::MarkAsUnmounted(const base::FilePath
& file_path
) {
479 AssertOnSequencedWorkerPool();
480 DCHECK(IsUnderFileCacheDirectory(file_path
));
482 std::string id
= GetIdFromPath(file_path
);
484 // Get cache entry associated with the id and md5
485 FileCacheEntry cache_entry
;
486 if (!storage_
->GetCacheEntry(id
, &cache_entry
))
487 return FILE_ERROR_NOT_FOUND
;
489 std::set
<std::string
>::iterator it
= mounted_files_
.find(id
);
490 if (it
== mounted_files_
.end())
491 return FILE_ERROR_INVALID_OPERATION
;
493 mounted_files_
.erase(it
);
494 return FILE_ERROR_OK
;
497 bool FileCache::HasEnoughSpaceFor(int64 num_bytes
,
498 const base::FilePath
& path
) {
499 int64 free_space
= 0;
500 if (free_disk_space_getter_
)
501 free_space
= free_disk_space_getter_
->AmountOfFreeDiskSpace();
503 free_space
= base::SysInfo::AmountOfFreeDiskSpace(path
);
505 // Subtract this as if this portion does not exist.
506 free_space
-= cryptohome::kMinFreeSpaceInBytes
;
507 return (free_space
>= num_bytes
);
510 bool FileCache::RenameCacheFilesToNewFormat() {
511 base::FileEnumerator
enumerator(cache_file_directory_
,
512 false, // not recursive
513 base::FileEnumerator::FILES
);
514 for (base::FilePath current
= enumerator
.Next(); !current
.empty();
515 current
= enumerator
.Next()) {
516 base::FilePath new_path
= current
.RemoveExtension();
517 if (!new_path
.Extension().empty()) {
518 // Delete files with multiple extensions.
519 if (!base::DeleteFile(current
, false /* recursive */))
523 const std::string
& id
= GetIdFromPath(new_path
);
524 new_path
= GetCacheFilePath(util::CanonicalizeResourceId(id
));
525 if (new_path
!= current
&& !base::Move(current
, new_path
))
531 void FileCache::CloseForWrite(const std::string
& id
) {
532 AssertOnSequencedWorkerPool();
534 std::map
<std::string
, int>::iterator it
= write_opened_files_
.find(id
);
535 if (it
== write_opened_files_
.end())
538 DCHECK_LT(0, it
->second
);
541 write_opened_files_
.erase(it
);
544 } // namespace internal