private_api_file_system.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1// Copyright 2013 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/extensions/file_manager/private_api_file_system.h"
6
7#include <sys/stat.h>
8#include <sys/statvfs.h>
9#include <sys/types.h>
10#include <utime.h>
11
12#include "base/posix/eintr_wrapper.h"
13#include "base/strings/stringprintf.h"
14#include "base/task_runner_util.h"
15#include "base/threading/sequenced_worker_pool.h"
16#include "chrome/browser/chromeos/drive/drive.pb.h"
17#include "chrome/browser/chromeos/drive/drive_integration_service.h"
18#include "chrome/browser/chromeos/drive/file_system_interface.h"
19#include "chrome/browser/chromeos/drive/file_system_util.h"
20#include "chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.h"
21#include "chrome/browser/chromeos/extensions/file_manager/file_manager_event_router.h"
22#include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
23#include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
24#include "chrome/browser/chromeos/fileapi/file_system_backend.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chromeos/disks/disk_mount_manager.h"
27#include "content/public/browser/browser_context.h"
28#include "content/public/browser/child_process_security_policy.h"
29#include "content/public/browser/render_process_host.h"
30#include "content/public/browser/render_view_host.h"
31#include "content/public/browser/storage_partition.h"
32#include "webkit/browser/fileapi/file_system_context.h"
33#include "webkit/browser/fileapi/file_system_file_util.h"
34#include "webkit/browser/fileapi/file_system_operation_context.h"
35#include "webkit/browser/fileapi/file_system_url.h"
36#include "webkit/common/fileapi/file_system_types.h"
37#include "webkit/common/fileapi/file_system_util.h"
38
39using chromeos::disks::DiskMountManager;
40using content::BrowserContext;
41using content::BrowserThread;
42using content::ChildProcessSecurityPolicy;
43using content::WebContents;
44using fileapi::FileSystemURL;
45
46namespace file_manager {
47namespace {
48
49// Error messages.
50const char kFileError[] = "File error %d";
51
52const DiskMountManager::Disk* GetVolumeAsDisk(const std::string& mount_path) {
53  DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
54
55  DiskMountManager::MountPointMap::const_iterator mount_point_it =
56      disk_mount_manager->mount_points().find(mount_path);
57  if (mount_point_it == disk_mount_manager->mount_points().end())
58    return NULL;
59
60  const DiskMountManager::Disk* disk = disk_mount_manager->FindDiskBySourcePath(
61      mount_point_it->second.source_path);
62
63  return (disk && disk->is_hidden()) ? NULL : disk;
64}
65
66base::DictionaryValue* CreateValueFromDisk(
67    Profile* profile,
68    const std::string& extension_id,
69    const DiskMountManager::Disk* volume) {
70  base::DictionaryValue* volume_info = new base::DictionaryValue();
71
72  std::string mount_path;
73  if (!volume->mount_path().empty()) {
74    base::FilePath relative_mount_path;
75    util::ConvertFileToRelativeFileSystemPath(
76        profile, extension_id, base::FilePath(volume->mount_path()),
77        &relative_mount_path);
78    mount_path = relative_mount_path.value();
79  }
80
81  volume_info->SetString("devicePath", volume->device_path());
82  volume_info->SetString("mountPath", mount_path);
83  volume_info->SetString("systemPath", volume->system_path());
84  volume_info->SetString("filePath", volume->file_path());
85  volume_info->SetString("deviceLabel", volume->device_label());
86  volume_info->SetString("driveLabel", volume->drive_label());
87  volume_info->SetString(
88      "deviceType",
89      DiskMountManager::DeviceTypeToString(volume->device_type()));
90  volume_info->SetDouble("totalSize",
91                         static_cast<double>(volume->total_size_in_bytes()));
92  volume_info->SetBoolean("isParent", volume->is_parent());
93  volume_info->SetBoolean("isReadOnly", volume->is_read_only());
94  volume_info->SetBoolean("hasMedia", volume->has_media());
95  volume_info->SetBoolean("isOnBootDevice", volume->on_boot_device());
96
97  return volume_info;
98}
99
100// Sets permissions for the Drive mount point so Files.app can access files
101// in the mount point directory. It's safe to call this function even if
102// Drive is disabled by the setting (i.e. prefs::kDisableDrive is true).
103void SetDriveMountPointPermissions(
104    Profile* profile,
105    const std::string& extension_id,
106    content::RenderViewHost* render_view_host) {
107  if (!render_view_host ||
108      !render_view_host->GetSiteInstance() || !render_view_host->GetProcess()) {
109    return;
110  }
111
112  content::SiteInstance* site_instance = render_view_host->GetSiteInstance();
113  fileapi::ExternalFileSystemBackend* backend =
114      BrowserContext::GetStoragePartition(profile, site_instance)->
115      GetFileSystemContext()->external_backend();
116  if (!backend)
117    return;
118
119  const base::FilePath mount_point = drive::util::GetDriveMountPointPath();
120  // Grant R/W permissions to drive 'folder'. File API layer still
121  // expects this to be satisfied.
122  ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
123      render_view_host->GetProcess()->GetID(), mount_point);
124
125  base::FilePath mount_point_virtual;
126  if (backend->GetVirtualPath(mount_point, &mount_point_virtual))
127    backend->GrantFileAccessToExtension(extension_id, mount_point_virtual);
128}
129
130// Retrieves total and remaining available size on |mount_path|.
131void GetSizeStatsOnBlockingPool(const std::string& mount_path,
132                                uint64* total_size,
133                                uint64* remaining_size) {
134  struct statvfs stat = {};  // Zero-clear
135  if (HANDLE_EINTR(statvfs(mount_path.c_str(), &stat)) == 0) {
136    *total_size =
137        static_cast<uint64>(stat.f_blocks) * stat.f_frsize;
138    *remaining_size =
139        static_cast<uint64>(stat.f_bfree) * stat.f_frsize;
140  }
141}
142
143// Retrieves the maximum file name length of the file system of |path|.
144// Returns 0 if it could not be queried.
145size_t GetFileNameMaxLengthOnBlockingPool(const std::string& path) {
146  struct statvfs stat = {};
147  if (statvfs(path.c_str(), &stat) != 0) {
148    // The filesystem seems not supporting statvfs(). Assume it to be a commonly
149    // used bound 255, and log the failure.
150    LOG(ERROR) << "Cannot statvfs() the name length limit for: " << path;
151    return 255;
152  }
153  return stat.f_namemax;
154}
155
156// Sets last modified date.
157bool SetLastModifiedOnBlockingPool(const base::FilePath& local_path,
158                                   time_t timestamp) {
159  if (local_path.empty())
160    return false;
161
162  struct stat stat_buffer;
163  if (stat(local_path.value().c_str(), &stat_buffer) != 0)
164    return false;
165
166  struct utimbuf times;
167  times.actime = stat_buffer.st_atime;
168  times.modtime = timestamp;
169  return utime(local_path.value().c_str(), &times) == 0;
170}
171
172}  // namespace
173
174RequestFileSystemFunction::RequestFileSystemFunction() {
175}
176
177RequestFileSystemFunction::~RequestFileSystemFunction() {
178}
179
180void RequestFileSystemFunction::DidOpenFileSystem(
181    scoped_refptr<fileapi::FileSystemContext> file_system_context,
182    base::PlatformFileError result,
183    const std::string& name,
184    const GURL& root_path) {
185  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186
187  if (result != base::PLATFORM_FILE_OK) {
188    DidFail(result);
189    return;
190  }
191
192  // RenderViewHost may have gone while the task is posted asynchronously.
193  if (!render_view_host()) {
194    DidFail(base::PLATFORM_FILE_ERROR_FAILED);
195    return;
196  }
197
198  // Set up file permission access.
199  const int child_id = render_view_host()->GetProcess()->GetID();
200  if (!SetupFileSystemAccessPermissions(file_system_context,
201                                        child_id,
202                                        GetExtension())) {
203    DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
204    return;
205  }
206
207  // Set permissions for the Drive mount point immediately when we kick of
208  // first instance of file manager. The actual mount event will be sent to
209  // UI only when we perform proper authentication.
210  //
211  // Note that we call this function even when Drive is disabled by the
212  // setting. Otherwise, we need to call this when the setting is changed at
213  // a later time, which complicates the code.
214  SetDriveMountPointPermissions(profile_, extension_id(), render_view_host());
215
216  DictionaryValue* dict = new DictionaryValue();
217  SetResult(dict);
218  dict->SetString("name", name);
219  dict->SetString("path", root_path.spec());
220  dict->SetInteger("error", drive::FILE_ERROR_OK);
221  SendResponse(true);
222}
223
224void RequestFileSystemFunction::DidFail(
225    base::PlatformFileError error_code) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227
228  error_ = base::StringPrintf(kFileError, static_cast<int>(error_code));
229  SendResponse(false);
230}
231
232bool RequestFileSystemFunction::SetupFileSystemAccessPermissions(
233    scoped_refptr<fileapi::FileSystemContext> file_system_context,
234    int child_id,
235    scoped_refptr<const extensions::Extension> extension) {
236  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
237
238  if (!extension.get())
239    return false;
240
241  // Make sure that only component extension can access the entire
242  // local file system.
243  if (extension_->location() != extensions::Manifest::COMPONENT) {
244    NOTREACHED() << "Private method access by non-component extension "
245                 << extension->id();
246    return false;
247  }
248
249  fileapi::ExternalFileSystemBackend* backend =
250      file_system_context->external_backend();
251  if (!backend)
252    return false;
253
254  // Grant full access to File API from this component extension.
255  backend->GrantFullAccessToExtension(extension_->id());
256
257  // Grant R/W file permissions to the renderer hosting component
258  // extension for all paths exposed by our local file system backend.
259  std::vector<base::FilePath> root_dirs = backend->GetRootDirectories();
260  for (size_t i = 0; i < root_dirs.size(); ++i) {
261    ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
262        child_id, root_dirs[i]);
263  }
264  return true;
265}
266
267bool RequestFileSystemFunction::RunImpl() {
268  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
269
270  if (!dispatcher() || !render_view_host() || !render_view_host()->GetProcess())
271    return false;
272
273  set_log_on_completion(true);
274
275  content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
276  scoped_refptr<fileapi::FileSystemContext> file_system_context =
277      BrowserContext::GetStoragePartition(profile_, site_instance)->
278      GetFileSystemContext();
279
280  const GURL origin_url = source_url_.GetOrigin();
281  file_system_context->OpenFileSystem(
282      origin_url,
283      fileapi::kFileSystemTypeExternal,
284      fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
285      base::Bind(&RequestFileSystemFunction::DidOpenFileSystem,
286                 this,
287                 file_system_context));
288  return true;
289}
290
291FileWatchFunctionBase::FileWatchFunctionBase() {
292}
293
294FileWatchFunctionBase::~FileWatchFunctionBase() {
295}
296
297void FileWatchFunctionBase::Respond(bool success) {
298  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299
300  SetResult(Value::CreateBooleanValue(success));
301  SendResponse(success);
302}
303
304bool FileWatchFunctionBase::RunImpl() {
305  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306
307  if (!render_view_host() || !render_view_host()->GetProcess())
308    return false;
309
310  // First param is url of a file to watch.
311  std::string url;
312  if (!args_->GetString(0, &url) || url.empty())
313    return false;
314
315  content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
316  scoped_refptr<fileapi::FileSystemContext> file_system_context =
317      BrowserContext::GetStoragePartition(profile(), site_instance)->
318      GetFileSystemContext();
319
320  FileSystemURL file_watch_url = file_system_context->CrackURL(GURL(url));
321  base::FilePath local_path = file_watch_url.path();
322  base::FilePath virtual_path = file_watch_url.virtual_path();
323  if (local_path.empty()) {
324    Respond(false);
325    return true;
326  }
327  PerformFileWatchOperation(local_path, virtual_path, extension_id());
328
329  return true;
330}
331
332AddFileWatchFunction::AddFileWatchFunction() {
333}
334
335AddFileWatchFunction::~AddFileWatchFunction() {
336}
337
338void AddFileWatchFunction::PerformFileWatchOperation(
339    const base::FilePath& local_path,
340    const base::FilePath& virtual_path,
341    const std::string& extension_id) {
342  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343
344  FileManagerEventRouter* event_router =
345      FileBrowserPrivateAPI::Get(profile_)->event_router();
346  event_router->AddFileWatch(
347      local_path,
348      virtual_path,
349      extension_id,
350      base::Bind(&AddFileWatchFunction::Respond, this));
351}
352
353RemoveFileWatchFunction::RemoveFileWatchFunction() {
354}
355
356RemoveFileWatchFunction::~RemoveFileWatchFunction() {
357}
358
359void RemoveFileWatchFunction::PerformFileWatchOperation(
360    const base::FilePath& local_path,
361    const base::FilePath& unused,
362    const std::string& extension_id) {
363  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
364
365  FileManagerEventRouter* event_router =
366      FileBrowserPrivateAPI::Get(profile_)->event_router();
367  event_router->RemoveFileWatch(local_path, extension_id);
368  Respond(true);
369}
370
371SetLastModifiedFunction::SetLastModifiedFunction() {
372}
373
374SetLastModifiedFunction::~SetLastModifiedFunction() {
375}
376
377bool SetLastModifiedFunction::RunImpl() {
378  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
379
380  if (args_->GetSize() != 2) {
381    return false;
382  }
383
384  std::string file_url;
385  if (!args_->GetString(0, &file_url))
386    return false;
387
388  std::string timestamp;
389  if (!args_->GetString(1, &timestamp))
390    return false;
391
392  base::FilePath local_path = util::GetLocalPathFromURL(
393      render_view_host(), profile(), GURL(file_url));
394
395  base::PostTaskAndReplyWithResult(
396      BrowserThread::GetBlockingPool(),
397      FROM_HERE,
398      base::Bind(&SetLastModifiedOnBlockingPool,
399                 local_path,
400                 strtoul(timestamp.c_str(), NULL, 0)),
401      base::Bind(&SetLastModifiedFunction::SendResponse,
402                 this));
403  return true;
404}
405
406GetSizeStatsFunction::GetSizeStatsFunction() {
407}
408
409GetSizeStatsFunction::~GetSizeStatsFunction() {
410}
411
412bool GetSizeStatsFunction::RunImpl() {
413  if (args_->GetSize() != 1) {
414    return false;
415  }
416
417  std::string mount_url;
418  if (!args_->GetString(0, &mount_url))
419    return false;
420
421  base::FilePath file_path = util::GetLocalPathFromURL(
422      render_view_host(), profile(), GURL(mount_url));
423  if (file_path.empty())
424    return false;
425
426  if (file_path == drive::util::GetDriveMountPointPath()) {
427    drive::DriveIntegrationService* integration_service =
428        drive::DriveIntegrationServiceFactory::GetForProfile(profile_);
429    // |integration_service| is NULL if Drive is disabled.
430    if (!integration_service) {
431      // If stats couldn't be gotten for drive, result should be left
432      // undefined. See comments in GetDriveAvailableSpaceCallback().
433      SendResponse(true);
434      return true;
435    }
436
437    drive::FileSystemInterface* file_system =
438        integration_service->file_system();
439
440    file_system->GetAvailableSpace(
441        base::Bind(&GetSizeStatsFunction::GetDriveAvailableSpaceCallback,
442                   this));
443
444  } else {
445    uint64* total_size = new uint64(0);
446    uint64* remaining_size = new uint64(0);
447    BrowserThread::PostBlockingPoolTaskAndReply(
448        FROM_HERE,
449        base::Bind(&GetSizeStatsOnBlockingPool,
450                   file_path.value(),
451                   total_size,
452                   remaining_size),
453        base::Bind(&GetSizeStatsFunction::GetSizeStatsCallback,
454                   this,
455                   base::Owned(total_size),
456                   base::Owned(remaining_size)));
457  }
458  return true;
459}
460
461void GetSizeStatsFunction::GetDriveAvailableSpaceCallback(
462    drive::FileError error,
463    int64 bytes_total,
464    int64 bytes_used) {
465  if (error == drive::FILE_ERROR_OK) {
466    const uint64 bytes_total_unsigned = bytes_total;
467    const uint64 bytes_remaining_unsigned = bytes_total - bytes_used;
468    GetSizeStatsCallback(&bytes_total_unsigned,
469                         &bytes_remaining_unsigned);
470  } else {
471    // If stats couldn't be gotten for drive, result should be left undefined.
472    SendResponse(true);
473  }
474}
475
476void GetSizeStatsFunction::GetSizeStatsCallback(
477    const uint64* total_size,
478    const uint64* remaining_size) {
479  base::DictionaryValue* sizes = new base::DictionaryValue();
480  SetResult(sizes);
481
482  sizes->SetDouble("totalSize", static_cast<double>(*total_size));
483  sizes->SetDouble("remainingSize", static_cast<double>(*remaining_size));
484
485  SendResponse(true);
486}
487
488GetVolumeMetadataFunction::GetVolumeMetadataFunction() {
489}
490
491GetVolumeMetadataFunction::~GetVolumeMetadataFunction() {
492}
493
494bool GetVolumeMetadataFunction::RunImpl() {
495  if (args_->GetSize() != 1) {
496    error_ = "Invalid argument count";
497    return false;
498  }
499
500  std::string volume_mount_url;
501  if (!args_->GetString(0, &volume_mount_url)) {
502    NOTREACHED();
503    return false;
504  }
505
506  base::FilePath file_path = util::GetLocalPathFromURL(
507      render_view_host(), profile(), GURL(volume_mount_url));
508  if (file_path.empty()) {
509    error_ = "Invalid mount path.";
510    return false;
511  }
512
513  results_.reset();
514
515  const DiskMountManager::Disk* volume = GetVolumeAsDisk(file_path.value());
516  if (volume) {
517    DictionaryValue* volume_info =
518        CreateValueFromDisk(profile_, extension_->id(), volume);
519    SetResult(volume_info);
520  }
521
522  SendResponse(true);
523  return true;
524}
525
526ValidatePathNameLengthFunction::ValidatePathNameLengthFunction() {
527}
528
529ValidatePathNameLengthFunction::~ValidatePathNameLengthFunction() {
530}
531
532bool ValidatePathNameLengthFunction::RunImpl() {
533  std::string parent_url;
534  if (!args_->GetString(0, &parent_url))
535    return false;
536
537  std::string name;
538  if (!args_->GetString(1, &name))
539    return false;
540
541  content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
542  scoped_refptr<fileapi::FileSystemContext> file_system_context =
543      BrowserContext::GetStoragePartition(profile(), site_instance)->
544      GetFileSystemContext();
545  fileapi::FileSystemURL filesystem_url(
546      file_system_context->CrackURL(GURL(parent_url)));
547  if (!chromeos::FileSystemBackend::CanHandleURL(filesystem_url))
548    return false;
549
550  // No explicit limit on the length of Drive file names.
551  if (filesystem_url.type() == fileapi::kFileSystemTypeDrive) {
552    SetResult(new base::FundamentalValue(true));
553    SendResponse(true);
554    return true;
555  }
556
557  base::PostTaskAndReplyWithResult(
558      BrowserThread::GetBlockingPool(),
559      FROM_HERE,
560      base::Bind(&GetFileNameMaxLengthOnBlockingPool,
561                 filesystem_url.path().AsUTF8Unsafe()),
562      base::Bind(&ValidatePathNameLengthFunction::OnFilePathLimitRetrieved,
563                 this, name.size()));
564  return true;
565}
566
567void ValidatePathNameLengthFunction::OnFilePathLimitRetrieved(
568    size_t current_length,
569    size_t max_length) {
570  SetResult(new base::FundamentalValue(current_length <= max_length));
571  SendResponse(true);
572}
573
574FormatDeviceFunction::FormatDeviceFunction() {
575}
576
577FormatDeviceFunction::~FormatDeviceFunction() {
578}
579
580bool FormatDeviceFunction::RunImpl() {
581  if (args_->GetSize() != 1) {
582    return false;
583  }
584
585  std::string volume_file_url;
586  if (!args_->GetString(0, &volume_file_url)) {
587    NOTREACHED();
588    return false;
589  }
590
591  base::FilePath file_path = util::GetLocalPathFromURL(
592      render_view_host(), profile(), GURL(volume_file_url));
593  if (file_path.empty())
594    return false;
595
596  DiskMountManager::GetInstance()->FormatMountedDevice(file_path.value());
597  SendResponse(true);
598  return true;
599}
600
601}  // namespace file_manager
602