Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / file_cache.cc
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/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"
28
29 using content::BrowserThread;
30
31 namespace drive {
32 namespace internal {
33 namespace {
34
35 // Returns ID extracted from the path.
36 std::string GetIdFromPath(const base::FilePath& path) {
37   return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
38 }
39
40 }  // namespace
41
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),
48       storage_(storage),
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));
53 }
54
55 FileCache::~FileCache() {
56   // Must be on the sequenced worker pool, as |metadata_| must be deleted on
57   // the sequenced worker pool.
58   AssertOnSequencedWorkerPool();
59 }
60
61 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const {
62   return cache_file_directory_.Append(
63       base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id)));
64 }
65
66 void FileCache::AssertOnSequencedWorkerPool() {
67   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
68 }
69
70 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
71   return cache_file_directory_.IsParent(path);
72 }
73
74 bool FileCache::GetCacheEntry(const std::string& id, FileCacheEntry* entry) {
75   DCHECK(entry);
76   AssertOnSequencedWorkerPool();
77   return storage_->GetCacheEntry(id, entry);
78 }
79
80 scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
81   AssertOnSequencedWorkerPool();
82   return storage_->GetCacheEntryIterator();
83 }
84
85 bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
86   AssertOnSequencedWorkerPool();
87
88   // Do nothing and return if we have enough space.
89   if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
90     return true;
91
92   // Otherwise, try to free up the disk space.
93   DVLOG(1) << "Freeing up disk space for " << num_bytes;
94
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() &&
101         !entry.is_dirty() &&
102         !mounted_files_.count(it->GetID()))
103       storage_->RemoveCacheEntry(it->GetID());
104   }
105   DCHECK(!it->HasError());
106
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 */);
117   }
118
119   // Check the disk space again.
120   return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
121 }
122
123 FileError FileCache::GetFile(const std::string& id,
124                              base::FilePath* cache_file_path) {
125   AssertOnSequencedWorkerPool();
126   DCHECK(cache_file_path);
127
128   FileCacheEntry cache_entry;
129   if (!storage_->GetCacheEntry(id, &cache_entry) ||
130       !cache_entry.is_present())
131     return FILE_ERROR_NOT_FOUND;
132
133   *cache_file_path = GetCacheFilePath(id);
134   return FILE_ERROR_OK;
135 }
136
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();
142
143   int64 file_size = 0;
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;
148     }
149   }
150   if (!FreeDiskSpaceIfNeededFor(file_size))
151     return FILE_ERROR_NO_LOCAL_SPACE;
152
153   // If file is mounted, return error.
154   if (mounted_files_.count(id))
155     return FILE_ERROR_IN_USE;
156
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);
162       break;
163     case FILE_OPERATION_COPY:
164       success = base::CopyFile(source_path, dest_path);
165       break;
166     default:
167       NOTREACHED();
168   }
169
170   if (!success) {
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;
176   }
177
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);
183   if (md5.empty())
184     cache_entry.set_is_dirty(true);
185   return storage_->PutCacheEntry(id, cache_entry) ?
186       FILE_ERROR_OK : FILE_ERROR_FAILED;
187 }
188
189 FileError FileCache::Pin(const std::string& id) {
190   AssertOnSequencedWorkerPool();
191
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;
197 }
198
199 FileError FileCache::Unpin(const std::string& id) {
200   AssertOnSequencedWorkerPool();
201
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;
206
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;
212   } else {
213     // Remove the existing entry if we are unpinning a non-present file.
214     if  (!storage_->RemoveCacheEntry(id))
215       return FILE_ERROR_FAILED;
216   }
217
218   // Now it's a chance to free up space if needed.
219   FreeDiskSpaceIfNeededFor(0);
220
221   return FILE_ERROR_OK;
222 }
223
224 FileError FileCache::MarkAsMounted(const std::string& id,
225                                    base::FilePath* cache_file_path) {
226   AssertOnSequencedWorkerPool();
227   DCHECK(cache_file_path);
228
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;
233
234   if (mounted_files_.count(id))
235     return FILE_ERROR_INVALID_OPERATION;
236
237   // Ensure the file is readable to cros_disks. See crbug.com/236994.
238   base::FilePath path = GetCacheFilePath(id);
239   if (!base::SetPosixFilePermissions(
240           path,
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;
246
247   mounted_files_.insert(id);
248
249   *cache_file_path = path;
250   return FILE_ERROR_OK;
251 }
252
253 FileError FileCache::OpenForWrite(
254     const std::string& id,
255     scoped_ptr<base::ScopedClosureRunner>* file_closer) {
256   AssertOnSequencedWorkerPool();
257
258   // Marking a file dirty means its entry and actual file blob must exist in
259   // cache.
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;
265   }
266
267   cache_entry.set_is_dirty(true);
268   cache_entry.clear_md5();
269   if (!storage_->PutCacheEntry(id, cache_entry))
270     return FILE_ERROR_FAILED;
271
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(),
278                             id))));
279   return FILE_ERROR_OK;
280 }
281
282 bool FileCache::IsOpenedForWrite(const std::string& id) {
283   AssertOnSequencedWorkerPool();
284   return write_opened_files_.count(id);
285 }
286
287 FileError FileCache::UpdateMd5(const std::string& id) {
288   AssertOnSequencedWorkerPool();
289
290   if (IsOpenedForWrite(id))
291     return FILE_ERROR_IN_USE;
292
293   FileCacheEntry cache_entry;
294   if (!storage_->GetCacheEntry(id, &cache_entry) ||
295       !cache_entry.is_present())
296     return FILE_ERROR_NOT_FOUND;
297
298   const std::string& md5 = util::GetMd5Digest(GetCacheFilePath(id));
299   if (md5.empty())
300     return FILE_ERROR_NOT_FOUND;
301
302   cache_entry.set_md5(md5);
303   return storage_->PutCacheEntry(id, cache_entry) ?
304       FILE_ERROR_OK : FILE_ERROR_FAILED;
305 }
306
307 FileError FileCache::ClearDirty(const std::string& id) {
308   AssertOnSequencedWorkerPool();
309
310   if (IsOpenedForWrite(id))
311     return FILE_ERROR_IN_USE;
312
313   // Clearing a dirty file means its entry and actual file blob must exist in
314   // cache.
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: "
319                  << id;
320     return FILE_ERROR_NOT_FOUND;
321   }
322
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;
328   }
329
330   cache_entry.set_is_dirty(false);
331   return storage_->PutCacheEntry(id, cache_entry) ?
332       FILE_ERROR_OK : FILE_ERROR_FAILED;
333 }
334
335 FileError FileCache::Remove(const std::string& id) {
336   AssertOnSequencedWorkerPool();
337
338   FileCacheEntry cache_entry;
339
340   // If entry doesn't exist, nothing to do.
341   if (!storage_->GetCacheEntry(id, &cache_entry))
342     return FILE_ERROR_OK;
343
344   // Cannot delete a mounted file.
345   if (mounted_files_.count(id))
346     return FILE_ERROR_IN_USE;
347
348   // Delete the file.
349   base::FilePath path = GetCacheFilePath(id);
350   if (!base::DeleteFile(path, false /* recursive */))
351     return FILE_ERROR_FAILED;
352
353   // Now that all file operations have completed, remove from metadata.
354   return storage_->RemoveCacheEntry(id) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
355 }
356
357 bool FileCache::ClearAll() {
358   AssertOnSequencedWorkerPool();
359
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()))
365       return false;
366   }
367
368   if (it->HasError())
369     return false;
370
371   // Remove files.
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 */);
378
379   return true;
380 }
381
382 bool FileCache::Initialize() {
383   AssertOnSequencedWorkerPool();
384
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))
394         return false;
395     }
396   }
397
398   if (!RenameCacheFilesToNewFormat())
399     return false;
400   return true;
401 }
402
403 void FileCache::Destroy() {
404   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
405
406   // Destroy myself on the blocking pool.
407   // Note that base::DeletePointer<> cannot be used as the destructor of this
408   // class is private.
409   blocking_task_runner_->PostTask(
410       FROM_HERE,
411       base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
412 }
413
414 void FileCache::DestroyOnBlockingPool() {
415   AssertOnSequencedWorkerPool();
416   delete this;
417 }
418
419 bool FileCache::RecoverFilesFromCacheDirectory(
420     const base::FilePath& dest_directory,
421     const ResourceMetadataStorage::RecoveredCacheInfoMap&
422         recovered_cache_info) {
423   int file_number = 1;
424
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.
434       continue;
435     }
436
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 */);
448         continue;
449       }
450     }
451
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();
458       return false;
459     }
460     if (read_result == 0)  // Skip empty files.
461       continue;
462
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,
474                                                &mime_type)) {
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"));
482       }
483
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);
489       else
490         net::GetExtensionsForMimeType(mime_type, &extensions);
491
492       // Add extension if possible.
493       if (!extensions.empty())
494         dest_base_name = dest_base_name.AddExtension(extensions[0]);
495     }
496
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();
504       return false;
505     }
506   }
507   UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
508                        file_number - 1);
509   return true;
510 }
511
512 FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
513   AssertOnSequencedWorkerPool();
514   DCHECK(IsUnderFileCacheDirectory(file_path));
515
516   std::string id = GetIdFromPath(file_path);
517
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;
522
523   std::set<std::string>::iterator it = mounted_files_.find(id);
524   if (it == mounted_files_.end())
525     return FILE_ERROR_INVALID_OPERATION;
526
527   mounted_files_.erase(it);
528   return FILE_ERROR_OK;
529 }
530
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();
536   else
537     free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
538
539   // Subtract this as if this portion does not exist.
540   free_space -= cryptohome::kMinFreeSpaceInBytes;
541   return (free_space >= num_bytes);
542 }
543
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 */))
554         return false;
555       continue;
556     }
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))
560       return false;
561   }
562   return true;
563 }
564
565 void FileCache::CloseForWrite(const std::string& id) {
566   AssertOnSequencedWorkerPool();
567
568   std::map<std::string, int>::iterator it = write_opened_files_.find(id);
569   if (it == write_opened_files_.end())
570     return;
571
572   DCHECK_LT(0, it->second);
573   --it->second;
574   if (it->second == 0)
575     write_opened_files_.erase(it);
576 }
577
578 }  // namespace internal
579 }  // namespace drive