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