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.
5 #include "chrome/browser/chromeos/extensions/file_manager/private_api_file_system.h"
7 #include <sys/statvfs.h>
11 #include "base/path_service.h"
12 #include "base/posix/eintr_wrapper.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/task_runner_util.h"
16 #include "base/threading/sequenced_worker_pool.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chromeos/drive/drive.pb.h"
19 #include "chrome/browser/chromeos/drive/file_system_interface.h"
20 #include "chrome/browser/chromeos/drive/file_system_util.h"
21 #include "chrome/browser/chromeos/extensions/file_manager/event_router.h"
22 #include "chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.h"
23 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
24 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
25 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/common/extensions/api/file_browser_private.h"
29 #include "chromeos/disks/disk_mount_manager.h"
30 #include "content/public/browser/browser_context.h"
31 #include "content/public/browser/child_process_security_policy.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/render_view_host.h"
34 #include "content/public/browser/storage_partition.h"
35 #include "webkit/browser/fileapi/file_system_context.h"
36 #include "webkit/browser/fileapi/file_system_file_util.h"
37 #include "webkit/browser/fileapi/file_system_operation_context.h"
38 #include "webkit/browser/fileapi/file_system_operation_runner.h"
39 #include "webkit/browser/fileapi/file_system_url.h"
40 #include "webkit/common/fileapi/file_system_info.h"
41 #include "webkit/common/fileapi/file_system_types.h"
42 #include "webkit/common/fileapi/file_system_util.h"
44 using chromeos::disks::DiskMountManager;
45 using content::BrowserContext;
46 using content::BrowserThread;
47 using content::ChildProcessSecurityPolicy;
48 using content::WebContents;
49 using fileapi::FileSystemURL;
51 namespace extensions {
55 const char kFileError[] = "File error %d";
57 const DiskMountManager::Disk* GetVolumeAsDisk(const std::string& mount_path) {
58 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
60 DiskMountManager::MountPointMap::const_iterator mount_point_it =
61 disk_mount_manager->mount_points().find(mount_path);
62 if (mount_point_it == disk_mount_manager->mount_points().end())
65 const DiskMountManager::Disk* disk = disk_mount_manager->FindDiskBySourcePath(
66 mount_point_it->second.source_path);
68 return (disk && disk->is_hidden()) ? NULL : disk;
71 base::DictionaryValue* CreateValueFromDisk(
73 const std::string& extension_id,
74 const DiskMountManager::Disk* volume) {
75 base::DictionaryValue* volume_info = new base::DictionaryValue();
77 std::string mount_path;
78 if (!volume->mount_path().empty()) {
79 base::FilePath relative_mount_path;
80 file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
81 profile, extension_id, base::FilePath(volume->mount_path()),
82 &relative_mount_path);
83 mount_path = relative_mount_path.value();
86 volume_info->SetString("devicePath", volume->device_path());
87 volume_info->SetString("mountPath", mount_path);
88 volume_info->SetString("systemPath", volume->system_path());
89 volume_info->SetString("filePath", volume->file_path());
90 volume_info->SetString("deviceLabel", volume->device_label());
91 volume_info->SetString("driveLabel", volume->drive_label());
92 volume_info->SetString(
94 DiskMountManager::DeviceTypeToString(volume->device_type()));
95 volume_info->SetDouble("totalSize",
96 static_cast<double>(volume->total_size_in_bytes()));
97 volume_info->SetBoolean("isParent", volume->is_parent());
98 volume_info->SetBoolean("isReadOnly", volume->is_read_only());
99 volume_info->SetBoolean("hasMedia", volume->has_media());
100 volume_info->SetBoolean("isOnBootDevice", volume->on_boot_device());
105 base::DictionaryValue* CreateDownloadsVolumeMetadata() {
106 base::DictionaryValue* volume_info = new base::DictionaryValue;
107 volume_info->SetString("mountPath", "Downloads");
108 volume_info->SetBoolean("isReadOnly", false);
112 // Sets permissions for the Drive mount point so Files.app can access files
113 // in the mount point directory. It's safe to call this function even if
114 // Drive is disabled by the setting (i.e. prefs::kDisableDrive is true).
115 void SetDriveMountPointPermissions(
117 const std::string& extension_id,
118 content::RenderViewHost* render_view_host) {
119 if (!render_view_host ||
120 !render_view_host->GetSiteInstance() || !render_view_host->GetProcess()) {
124 fileapi::ExternalFileSystemBackend* backend =
125 file_manager::util::GetFileSystemContextForRenderViewHost(
126 profile, render_view_host)->external_backend();
130 const base::FilePath mount_point = drive::util::GetDriveMountPointPath();
131 // Grant R/W permissions to drive 'folder'. File API layer still
132 // expects this to be satisfied.
133 ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
134 render_view_host->GetProcess()->GetID(), mount_point);
136 base::FilePath mount_point_virtual;
137 if (backend->GetVirtualPath(mount_point, &mount_point_virtual))
138 backend->GrantFileAccessToExtension(extension_id, mount_point_virtual);
141 // Retrieves total and remaining available size on |mount_path|.
142 void GetSizeStatsOnBlockingPool(const std::string& mount_path,
144 uint64* remaining_size) {
145 struct statvfs stat = {}; // Zero-clear
146 if (HANDLE_EINTR(statvfs(mount_path.c_str(), &stat)) == 0) {
148 static_cast<uint64>(stat.f_blocks) * stat.f_frsize;
150 static_cast<uint64>(stat.f_bavail) * stat.f_frsize;
154 // Retrieves the maximum file name length of the file system of |path|.
155 // Returns 0 if it could not be queried.
156 size_t GetFileNameMaxLengthOnBlockingPool(const std::string& path) {
157 struct statvfs stat = {};
158 if (HANDLE_EINTR(statvfs(path.c_str(), &stat)) != 0) {
159 // The filesystem seems not supporting statvfs(). Assume it to be a commonly
160 // used bound 255, and log the failure.
161 LOG(ERROR) << "Cannot statvfs() the name length limit for: " << path;
164 return stat.f_namemax;
167 // Returns EventRouter for the |profile_id| if available.
168 file_manager::EventRouter* GetEventRouterByProfileId(void* profile_id) {
169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171 // |profile_id| needs to be checked with ProfileManager::IsValidProfile
173 Profile* profile = reinterpret_cast<Profile*>(profile_id);
174 if (!g_browser_process->profile_manager()->IsValidProfile(profile))
177 return file_manager::FileBrowserPrivateAPI::Get(profile)->event_router();
180 // Notifies the copy progress to extensions via event router.
181 void NotifyCopyProgress(
183 fileapi::FileSystemOperationRunner::OperationID operation_id,
184 fileapi::FileSystemOperation::CopyProgressType type,
185 const FileSystemURL& source_url,
186 const FileSystemURL& destination_url,
188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 file_manager::EventRouter* event_router =
191 GetEventRouterByProfileId(profile_id);
193 event_router->OnCopyProgress(
195 source_url.ToGURL(), destination_url.ToGURL(), size);
199 // Callback invoked periodically on progress update of Copy().
202 fileapi::FileSystemOperationRunner::OperationID* operation_id,
203 fileapi::FileSystemOperation::CopyProgressType type,
204 const FileSystemURL& source_url,
205 const FileSystemURL& destination_url,
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
209 BrowserThread::PostTask(
210 BrowserThread::UI, FROM_HERE,
211 base::Bind(&NotifyCopyProgress,
212 profile_id, *operation_id, type,
213 source_url, destination_url, size));
216 // Notifies the copy completion to extensions via event router.
217 void NotifyCopyCompletion(
219 fileapi::FileSystemOperationRunner::OperationID operation_id,
220 const FileSystemURL& source_url,
221 const FileSystemURL& destination_url,
222 base::PlatformFileError error) {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225 file_manager::EventRouter* event_router =
226 GetEventRouterByProfileId(profile_id);
228 event_router->OnCopyCompleted(
230 source_url.ToGURL(), destination_url.ToGURL(), error);
233 // Callback invoked upon completion of Copy() (regardless of succeeded or
235 void OnCopyCompleted(
237 fileapi::FileSystemOperationRunner::OperationID* operation_id,
238 const FileSystemURL& source_url,
239 const FileSystemURL& destination_url,
240 base::PlatformFileError error) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
243 BrowserThread::PostTask(
244 BrowserThread::UI, FROM_HERE,
245 base::Bind(&NotifyCopyCompletion,
246 profile_id, *operation_id,
247 source_url, destination_url, error));
250 // Starts the copy operation via FileSystemOperationRunner.
251 fileapi::FileSystemOperationRunner::OperationID StartCopyOnIOThread(
253 scoped_refptr<fileapi::FileSystemContext> file_system_context,
254 const FileSystemURL& source_url,
255 const FileSystemURL& destination_url) {
256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
258 // Note: |operation_id| is owned by the callback for
259 // FileSystemOperationRunner::Copy(). It is always called in the next message
260 // loop or later, so at least during this invocation it should alive.
261 fileapi::FileSystemOperationRunner::OperationID* operation_id =
262 new fileapi::FileSystemOperationRunner::OperationID;
263 *operation_id = file_system_context->operation_runner()->Copy(
264 source_url, destination_url,
265 fileapi::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
266 base::Bind(&OnCopyProgress,
267 profile_id, base::Unretained(operation_id)),
268 base::Bind(&OnCopyCompleted,
269 profile_id, base::Owned(operation_id),
270 source_url, destination_url));
271 return *operation_id;
274 void OnCopyCancelled(base::PlatformFileError error) {
275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
277 // We just ignore the status if the copy is actually cancelled or not,
278 // because failing cancellation means the operation is not running now.
279 DLOG_IF(WARNING, error != base::PLATFORM_FILE_OK)
280 << "Failed to cancel copy: " << error;
283 // Cancels the running copy operation identified by |operation_id|.
284 void CancelCopyOnIOThread(
285 scoped_refptr<fileapi::FileSystemContext> file_system_context,
286 fileapi::FileSystemOperationRunner::OperationID operation_id) {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
289 file_system_context->operation_runner()->Cancel(
290 operation_id, base::Bind(&OnCopyCancelled));
295 void FileBrowserPrivateRequestFileSystemFunction::DidFail(
296 base::PlatformFileError error_code) {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299 error_ = base::StringPrintf(kFileError, static_cast<int>(error_code));
303 bool FileBrowserPrivateRequestFileSystemFunction::
304 SetupFileSystemAccessPermissions(
305 scoped_refptr<fileapi::FileSystemContext> file_system_context,
307 scoped_refptr<const extensions::Extension> extension) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310 if (!extension.get())
313 // Make sure that only component extension can access the entire
314 // local file system.
315 if (extension_->location() != extensions::Manifest::COMPONENT) {
316 NOTREACHED() << "Private method access by non-component extension "
321 fileapi::ExternalFileSystemBackend* backend =
322 file_system_context->external_backend();
326 // Grant full access to File API from this component extension.
327 backend->GrantFullAccessToExtension(extension_->id());
329 // Grant R/W file permissions to the renderer hosting component
330 // extension for all paths exposed by our local file system backend.
331 std::vector<base::FilePath> root_dirs = backend->GetRootDirectories();
332 for (size_t i = 0; i < root_dirs.size(); ++i) {
333 ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
334 child_id, root_dirs[i]);
339 bool FileBrowserPrivateRequestFileSystemFunction::RunImpl() {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
341 using extensions::api::file_browser_private::RequestFileSystem::Params;
342 const scoped_ptr<Params> params(Params::Create(*args_));
343 EXTENSION_FUNCTION_VALIDATE(params);
345 // TODO(satorux): Handle the file system ID. crbug.com/284963.
346 DCHECK_EQ("compatible", params->file_system_id);
348 if (!dispatcher() || !render_view_host() || !render_view_host()->GetProcess())
351 set_log_on_completion(true);
353 scoped_refptr<fileapi::FileSystemContext> file_system_context =
354 file_manager::util::GetFileSystemContextForRenderViewHost(
355 GetProfile(), render_view_host());
357 // Set up file permission access.
358 const int child_id = render_view_host()->GetProcess()->GetID();
359 if (!SetupFileSystemAccessPermissions(file_system_context,
362 DidFail(base::PLATFORM_FILE_ERROR_SECURITY);
366 // Set permissions for the Drive mount point immediately when we kick of
367 // first instance of file manager. The actual mount event will be sent to
368 // UI only when we perform proper authentication.
370 // Note that we call this function even when Drive is disabled by the
371 // setting. Otherwise, we need to call this when the setting is changed at
372 // a later time, which complicates the code.
373 SetDriveMountPointPermissions(
374 GetProfile(), extension_id(), render_view_host());
376 fileapi::FileSystemInfo info =
377 fileapi::GetFileSystemInfoForChromeOS(source_url_.GetOrigin());
379 DictionaryValue* dict = new DictionaryValue();
381 dict->SetString("name", info.name);
382 dict->SetString("root_url", info.root_url.spec());
383 dict->SetInteger("error", drive::FILE_ERROR_OK);
388 void FileWatchFunctionBase::Respond(bool success) {
389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
391 SetResult(Value::CreateBooleanValue(success));
392 SendResponse(success);
395 bool FileWatchFunctionBase::RunImpl() {
396 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
398 if (!render_view_host() || !render_view_host()->GetProcess())
401 // First param is url of a file to watch.
403 if (!args_->GetString(0, &url) || url.empty())
406 scoped_refptr<fileapi::FileSystemContext> file_system_context =
407 file_manager::util::GetFileSystemContextForRenderViewHost(
408 GetProfile(), render_view_host());
410 FileSystemURL file_watch_url = file_system_context->CrackURL(GURL(url));
411 base::FilePath local_path = file_watch_url.path();
412 base::FilePath virtual_path = file_watch_url.virtual_path();
413 if (local_path.empty()) {
417 PerformFileWatchOperation(local_path, virtual_path, extension_id());
422 void FileBrowserPrivateAddFileWatchFunction::PerformFileWatchOperation(
423 const base::FilePath& local_path,
424 const base::FilePath& virtual_path,
425 const std::string& extension_id) {
426 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
428 file_manager::EventRouter* event_router =
429 file_manager::FileBrowserPrivateAPI::Get(GetProfile())->event_router();
430 event_router->AddFileWatch(
434 base::Bind(&FileBrowserPrivateAddFileWatchFunction::Respond, this));
437 void FileBrowserPrivateRemoveFileWatchFunction::PerformFileWatchOperation(
438 const base::FilePath& local_path,
439 const base::FilePath& unused,
440 const std::string& extension_id) {
441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
443 file_manager::EventRouter* event_router =
444 file_manager::FileBrowserPrivateAPI::Get(GetProfile())->event_router();
445 event_router->RemoveFileWatch(local_path, extension_id);
449 bool FileBrowserPrivateGetSizeStatsFunction::RunImpl() {
450 using extensions::api::file_browser_private::GetSizeStats::Params;
451 const scoped_ptr<Params> params(Params::Create(*args_));
452 EXTENSION_FUNCTION_VALIDATE(params);
454 base::FilePath file_path = file_manager::util::GetLocalPathFromURL(
455 render_view_host(), GetProfile(), GURL(params->mount_path));
456 if (file_path.empty())
459 if (file_path == drive::util::GetDriveMountPointPath()) {
460 drive::FileSystemInterface* file_system =
461 drive::util::GetFileSystemByProfile(GetProfile());
463 // |file_system| is NULL if Drive is disabled.
464 // If stats couldn't be gotten for drive, result should be left
465 // undefined. See comments in GetDriveAvailableSpaceCallback().
470 file_system->GetAvailableSpace(
471 base::Bind(&FileBrowserPrivateGetSizeStatsFunction::
472 GetDriveAvailableSpaceCallback,
475 uint64* total_size = new uint64(0);
476 uint64* remaining_size = new uint64(0);
477 BrowserThread::PostBlockingPoolTaskAndReply(
479 base::Bind(&GetSizeStatsOnBlockingPool,
483 base::Bind(&FileBrowserPrivateGetSizeStatsFunction::
484 GetSizeStatsCallback,
486 base::Owned(total_size),
487 base::Owned(remaining_size)));
492 void FileBrowserPrivateGetSizeStatsFunction::GetDriveAvailableSpaceCallback(
493 drive::FileError error,
496 if (error == drive::FILE_ERROR_OK) {
497 const uint64 bytes_total_unsigned = bytes_total;
498 const uint64 bytes_remaining_unsigned = bytes_total - bytes_used;
499 GetSizeStatsCallback(&bytes_total_unsigned,
500 &bytes_remaining_unsigned);
502 // If stats couldn't be gotten for drive, result should be left undefined.
507 void FileBrowserPrivateGetSizeStatsFunction::GetSizeStatsCallback(
508 const uint64* total_size,
509 const uint64* remaining_size) {
510 base::DictionaryValue* sizes = new base::DictionaryValue();
513 sizes->SetDouble("totalSize", static_cast<double>(*total_size));
514 sizes->SetDouble("remainingSize", static_cast<double>(*remaining_size));
519 bool FileBrowserPrivateValidatePathNameLengthFunction::RunImpl() {
520 using extensions::api::file_browser_private::ValidatePathNameLength::Params;
521 const scoped_ptr<Params> params(Params::Create(*args_));
522 EXTENSION_FUNCTION_VALIDATE(params);
524 scoped_refptr<fileapi::FileSystemContext> file_system_context =
525 file_manager::util::GetFileSystemContextForRenderViewHost(
526 GetProfile(), render_view_host());
528 fileapi::FileSystemURL filesystem_url(
529 file_system_context->CrackURL(GURL(params->parent_directory_url)));
530 if (!chromeos::FileSystemBackend::CanHandleURL(filesystem_url))
533 // No explicit limit on the length of Drive file names.
534 if (filesystem_url.type() == fileapi::kFileSystemTypeDrive) {
535 SetResult(new base::FundamentalValue(true));
540 base::PostTaskAndReplyWithResult(
541 BrowserThread::GetBlockingPool(),
543 base::Bind(&GetFileNameMaxLengthOnBlockingPool,
544 filesystem_url.path().AsUTF8Unsafe()),
545 base::Bind(&FileBrowserPrivateValidatePathNameLengthFunction::
546 OnFilePathLimitRetrieved,
547 this, params->name.size()));
551 void FileBrowserPrivateValidatePathNameLengthFunction::OnFilePathLimitRetrieved(
552 size_t current_length,
554 SetResult(new base::FundamentalValue(current_length <= max_length));
558 bool FileBrowserPrivateFormatDeviceFunction::RunImpl() {
559 using extensions::api::file_browser_private::FormatDevice::Params;
560 const scoped_ptr<Params> params(Params::Create(*args_));
561 EXTENSION_FUNCTION_VALIDATE(params);
563 base::FilePath file_path = file_manager::util::GetLocalPathFromURL(
564 render_view_host(), GetProfile(), GURL(params->mount_path));
565 if (file_path.empty())
568 DiskMountManager::GetInstance()->FormatMountedDevice(file_path.value());
573 bool FileBrowserPrivateStartCopyFunction::RunImpl() {
574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
576 using extensions::api::file_browser_private::StartCopy::Params;
577 const scoped_ptr<Params> params(Params::Create(*args_));
578 EXTENSION_FUNCTION_VALIDATE(params);
580 if (params->source_url.empty() || params->parent.empty() ||
581 params->new_name.empty()) {
582 error_ = base::IntToString(fileapi::PlatformFileErrorToWebFileError(
583 base::PLATFORM_FILE_ERROR_INVALID_URL));
587 scoped_refptr<fileapi::FileSystemContext> file_system_context =
588 file_manager::util::GetFileSystemContextForRenderViewHost(
589 GetProfile(), render_view_host());
591 fileapi::FileSystemURL source_url(
592 file_system_context->CrackURL(GURL(params->source_url)));
593 fileapi::FileSystemURL destination_url(file_system_context->CrackURL(
594 GURL(params->parent + "/" + params->new_name)));
596 if (!source_url.is_valid() || !destination_url.is_valid()) {
597 error_ = base::IntToString(fileapi::PlatformFileErrorToWebFileError(
598 base::PLATFORM_FILE_ERROR_INVALID_URL));
602 return BrowserThread::PostTaskAndReplyWithResult(
605 base::Bind(&StartCopyOnIOThread,
610 base::Bind(&FileBrowserPrivateStartCopyFunction::RunAfterStartCopy,
614 void FileBrowserPrivateStartCopyFunction::RunAfterStartCopy(
616 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
618 SetResult(Value::CreateIntegerValue(operation_id));
622 bool FileBrowserPrivateCancelCopyFunction::RunImpl() {
623 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
625 using extensions::api::file_browser_private::CancelCopy::Params;
626 const scoped_ptr<Params> params(Params::Create(*args_));
627 EXTENSION_FUNCTION_VALIDATE(params);
629 scoped_refptr<fileapi::FileSystemContext> file_system_context =
630 file_manager::util::GetFileSystemContextForRenderViewHost(
631 GetProfile(), render_view_host());
633 // We don't much take care about the result of cancellation.
634 BrowserThread::PostTask(
637 base::Bind(&CancelCopyOnIOThread, file_system_context, params->copy_id));
642 } // namespace extensions