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/file_manager/volume_manager.h"
7 #include "base/basictypes.h"
9 #include "base/callback.h"
10 #include "base/command_line.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/prefs/pref_service.h"
15 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
16 #include "chrome/browser/chromeos/drive/file_errors.h"
17 #include "chrome/browser/chromeos/drive/file_system_interface.h"
18 #include "chrome/browser/chromeos/drive/file_system_util.h"
19 #include "chrome/browser/chromeos/file_manager/mounted_disk_monitor.h"
20 #include "chrome/browser/chromeos/file_manager/path_util.h"
21 #include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
22 #include "chrome/browser/chromeos/file_manager/volume_manager_observer.h"
23 #include "chrome/browser/chromeos/profiles/profile_helper.h"
24 #include "chrome/browser/local_discovery/storage/privet_filesystem_constants.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/pref_names.h"
28 #include "chromeos/dbus/cros_disks_client.h"
29 #include "chromeos/disks/disk_mount_manager.h"
30 #include "content/public/browser/browser_context.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "webkit/browser/fileapi/external_mount_points.h"
34 namespace file_manager {
37 // Registers |path| as the "Downloads" folder to the FileSystem API backend.
38 // If another folder is already mounted. It revokes and overrides the old one.
39 bool RegisterDownloadsMountPoint(Profile* profile, const base::FilePath& path) {
40 // Although we show only profile's own "Downloads" folder in Files.app,
41 // in the backend we need to mount all profile's download directory globally.
42 // Otherwise, Files.app cannot support cross-profile file copies, etc.
43 // For this reason, we need to register to the global GetSystemInstance().
44 const std::string mount_point_name =
45 file_manager::util::GetDownloadsMountPointName(profile);
46 fileapi::ExternalMountPoints* const mount_points =
47 fileapi::ExternalMountPoints::GetSystemInstance();
49 // In some tests we want to override existing Downloads mount point, so we
50 // first revoke the existing mount point (if any).
51 mount_points->RevokeFileSystem(mount_point_name);
52 return mount_points->RegisterFileSystem(
53 mount_point_name, fileapi::kFileSystemTypeNativeLocal,
54 fileapi::FileSystemMountOption(), path);
57 // Finds the path register as the "Downloads" folder to FileSystem API backend.
58 // Returns false if it is not registered.
59 bool FindDownloadsMountPointPath(Profile* profile, base::FilePath* path) {
60 const std::string mount_point_name =
61 util::GetDownloadsMountPointName(profile);
62 fileapi::ExternalMountPoints* const mount_points =
63 fileapi::ExternalMountPoints::GetSystemInstance();
65 return mount_points->GetRegisteredPath(mount_point_name, path);
68 // Called on completion of MarkCacheFileAsUnmounted.
69 void OnMarkCacheFileAsUnmounted(drive::FileError error) {
73 VolumeType MountTypeToVolumeType(
74 chromeos::MountType type) {
76 case chromeos::MOUNT_TYPE_INVALID:
77 // We don't expect this value, but list here, so that when any value
78 // is added to the enum definition but this is not edited, the compiler
81 case chromeos::MOUNT_TYPE_DEVICE:
82 return VOLUME_TYPE_REMOVABLE_DISK_PARTITION;
83 case chromeos::MOUNT_TYPE_ARCHIVE:
84 return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE;
88 return VOLUME_TYPE_DOWNLOADS_DIRECTORY;
91 // Returns a string representation of the given volume type.
92 std::string VolumeTypeToString(VolumeType type) {
94 case VOLUME_TYPE_GOOGLE_DRIVE:
96 case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
98 case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
100 case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
102 case VOLUME_TYPE_CLOUD_DEVICE:
103 return "cloud_device";
109 // Generates a unique volume ID for the given volume info.
110 std::string GenerateVolumeId(const VolumeInfo& volume_info) {
111 // For the same volume type, base names are unique, as mount points are
112 // flat for the same volume type.
113 return (VolumeTypeToString(volume_info.type) + ":" +
114 volume_info.mount_path.BaseName().AsUTF8Unsafe());
117 // Returns the VolumeInfo for Drive file system.
118 VolumeInfo CreateDriveVolumeInfo(Profile* profile) {
119 const base::FilePath& drive_path =
120 drive::util::GetDriveMountPointPath(profile);
122 VolumeInfo volume_info;
123 volume_info.type = VOLUME_TYPE_GOOGLE_DRIVE;
124 volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
125 volume_info.source_path = drive_path;
126 volume_info.mount_path = drive_path;
127 volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
128 volume_info.is_parent = false;
129 volume_info.is_read_only = false;
130 volume_info.volume_id = GenerateVolumeId(volume_info);
134 VolumeInfo CreateDownloadsVolumeInfo(
135 const base::FilePath& downloads_path) {
136 VolumeInfo volume_info;
137 volume_info.type = VOLUME_TYPE_DOWNLOADS_DIRECTORY;
138 volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
139 // Keep source_path empty.
140 volume_info.mount_path = downloads_path;
141 volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
142 volume_info.is_parent = false;
143 volume_info.is_read_only = false;
144 volume_info.volume_id = GenerateVolumeId(volume_info);
148 VolumeInfo CreateVolumeInfoFromMountPointInfo(
149 const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
150 const chromeos::disks::DiskMountManager::Disk* disk) {
151 VolumeInfo volume_info;
152 volume_info.type = MountTypeToVolumeType(mount_point.mount_type);
153 volume_info.source_path = base::FilePath(mount_point.source_path);
154 volume_info.mount_path = base::FilePath(mount_point.mount_path);
155 volume_info.mount_condition = mount_point.mount_condition;
157 volume_info.device_type = disk->device_type();
158 volume_info.system_path_prefix =
159 base::FilePath(disk->system_path_prefix());
160 volume_info.drive_label = disk->drive_label();
161 volume_info.is_parent = disk->is_parent();
162 volume_info.is_read_only = disk->is_read_only();
164 volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
165 volume_info.is_parent = false;
166 volume_info.is_read_only =
167 (mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE);
169 volume_info.volume_id = GenerateVolumeId(volume_info);
174 VolumeInfo CreatePrivetVolumeInfo(
175 const local_discovery::PrivetVolumeLister::VolumeInfo& privet_volume_info) {
176 VolumeInfo volume_info;
177 volume_info.type = VOLUME_TYPE_CLOUD_DEVICE;
178 volume_info.mount_path = privet_volume_info.volume_path;
179 volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
180 volume_info.is_parent = true;
181 volume_info.is_read_only = true;
182 volume_info.volume_id = GenerateVolumeId(volume_info);
188 VolumeInfo::VolumeInfo() {
191 VolumeInfo::~VolumeInfo() {
194 VolumeManager::VolumeManager(
196 drive::DriveIntegrationService* drive_integration_service,
197 chromeos::PowerManagerClient* power_manager_client,
198 chromeos::disks::DiskMountManager* disk_mount_manager)
200 drive_integration_service_(drive_integration_service),
201 disk_mount_manager_(disk_mount_manager),
202 mounted_disk_monitor_(
203 new MountedDiskMonitor(power_manager_client, disk_mount_manager)) {
204 DCHECK(disk_mount_manager);
207 VolumeManager::~VolumeManager() {
210 VolumeManager* VolumeManager::Get(content::BrowserContext* context) {
211 return VolumeManagerFactory::Get(context);
214 void VolumeManager::Initialize() {
215 const bool kNotRemounting = false;
217 // Path to mount user folders have changed several times. We need to migrate
218 // the old preferences on paths to the new format when needed. For the detail,
219 // see the comments in file_manager::util::MigratePathFromOldFormat,
220 // Note: Preferences related to downloads are handled in download_prefs.cc.
221 // TODO(kinaba): Remove this after several rounds of releases.
222 const base::FilePath old_path =
223 profile_->GetPrefs()->GetFilePath(prefs::kSelectFileLastDirectory);
224 base::FilePath new_path;
225 if (!old_path.empty() &&
226 file_manager::util::MigratePathFromOldFormat(profile_,
227 old_path, &new_path)) {
228 profile_->GetPrefs()->SetFilePath(prefs::kSelectFileLastDirectory,
232 // Register 'Downloads' folder for the profile to the file system.
233 if (!chromeos::ProfileHelper::IsSigninProfile(profile_)) {
234 const base::FilePath downloads =
235 file_manager::util::GetDownloadsFolderForProfile(profile_);
236 const bool success = RegisterDownloadsMountPoint(profile_, downloads);
239 DoMountEvent(chromeos::MOUNT_ERROR_NONE,
240 CreateDownloadsVolumeInfo(downloads),
244 // Subscribe to DriveIntegrationService.
245 if (drive_integration_service_) {
246 drive_integration_service_->AddObserver(this);
247 if (drive_integration_service_->IsMounted()) {
248 DoMountEvent(chromeos::MOUNT_ERROR_NONE,
249 CreateDriveVolumeInfo(profile_),
254 // Subscribe to DiskMountManager.
255 disk_mount_manager_->AddObserver(this);
257 std::vector<VolumeInfo> archives;
259 const chromeos::disks::DiskMountManager::MountPointMap& mount_points =
260 disk_mount_manager_->mount_points();
261 for (chromeos::disks::DiskMountManager::MountPointMap::const_iterator it =
262 mount_points.begin();
263 it != mount_points.end();
265 if (it->second.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
266 // Archives are mounted after other type of volumes. See below.
267 archives.push_back(CreateVolumeInfoFromMountPointInfo(it->second, NULL));
271 chromeos::MOUNT_ERROR_NONE,
272 CreateVolumeInfoFromMountPointInfo(
274 disk_mount_manager_->FindDiskBySourcePath(it->second.source_path)),
278 // We mount archives only if they are opened from currently mounted volumes.
279 // To check the condition correctly in DoMountEvent, we care the order.
280 std::vector<bool> done(archives.size(), false);
281 for (size_t i = 0; i < archives.size(); ++i) {
283 std::vector<VolumeInfo> chain;
285 chain.push_back(archives[i]);
287 // If archives[i]'s source_path is in another archive, mount it first.
288 for (size_t parent = 0; parent < archives.size(); ++parent) {
290 archives[parent].mount_path.IsParent(chain.back().source_path)) {
292 chain.push_back(archives[parent]);
293 parent = 0; // Search archives[parent]'s parent from the beginning.
297 // Mount from the tail of chain.
298 for (size_t i = chain.size(); i > 0; --i)
299 DoMountEvent(chromeos::MOUNT_ERROR_NONE, chain[i - 1], kNotRemounting);
303 disk_mount_manager_->RequestMountInfoRefresh();
305 // Subscribe to Profile Preference change.
306 pref_change_registrar_.Init(profile_->GetPrefs());
307 pref_change_registrar_.Add(
308 prefs::kExternalStorageDisabled,
309 base::Bind(&VolumeManager::OnExternalStorageDisabledChanged,
310 base::Unretained(this)));
312 if (CommandLine::ForCurrentProcess()->HasSwitch(
313 switches::kEnablePrivetStorage)) {
314 privet_volume_lister_.reset(new local_discovery::PrivetVolumeLister(
315 base::Bind(&VolumeManager::OnPrivetVolumesAvailable,
316 base::Unretained(this))));
317 privet_volume_lister_->Start();
321 void VolumeManager::Shutdown() {
322 pref_change_registrar_.RemoveAll();
323 disk_mount_manager_->RemoveObserver(this);
325 if (drive_integration_service_)
326 drive_integration_service_->RemoveObserver(this);
329 void VolumeManager::AddObserver(VolumeManagerObserver* observer) {
330 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
332 observers_.AddObserver(observer);
335 void VolumeManager::RemoveObserver(VolumeManagerObserver* observer) {
336 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
338 observers_.RemoveObserver(observer);
341 std::vector<VolumeInfo> VolumeManager::GetVolumeInfoList() const {
342 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
344 std::vector<VolumeInfo> result;
345 for (std::map<std::string, VolumeInfo>::const_iterator iter =
346 mounted_volumes_.begin();
347 iter != mounted_volumes_.end();
349 result.push_back(iter->second);
354 bool VolumeManager::FindVolumeInfoById(const std::string& volume_id,
355 VolumeInfo* result) const {
356 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
359 std::map<std::string, VolumeInfo>::const_iterator iter =
360 mounted_volumes_.find(volume_id);
361 if (iter == mounted_volumes_.end())
363 *result = iter->second;
367 bool VolumeManager::RegisterDownloadsDirectoryForTesting(
368 const base::FilePath& path) {
369 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
371 base::FilePath old_path;
372 if (FindDownloadsMountPointPath(profile_, &old_path)) {
373 DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
374 CreateDownloadsVolumeInfo(old_path));
377 bool success = RegisterDownloadsMountPoint(profile_, path);
379 success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
380 CreateDownloadsVolumeInfo(path),
381 false /* is_remounting */);
385 void VolumeManager::OnFileSystemMounted() {
386 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
388 // Raise mount event.
389 // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed
390 // or network is unreachable. These two errors will be handled later.
391 VolumeInfo volume_info = CreateDriveVolumeInfo(profile_);
392 DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info,
393 false /* is_remounting */);
396 void VolumeManager::OnFileSystemBeingUnmounted() {
397 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
399 VolumeInfo volume_info = CreateDriveVolumeInfo(profile_);
400 DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, volume_info);
403 void VolumeManager::OnDiskEvent(
404 chromeos::disks::DiskMountManager::DiskEvent event,
405 const chromeos::disks::DiskMountManager::Disk* disk) {
406 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
408 // Disregard hidden devices.
409 if (disk->is_hidden())
413 case chromeos::disks::DiskMountManager::DISK_ADDED: {
414 if (disk->device_path().empty()) {
415 DVLOG(1) << "Empty system path for " << disk->device_path();
419 bool mounting = false;
420 if (disk->mount_path().empty() && disk->has_media() &&
421 !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
422 // If disk is not mounted yet and it has media and there is no policy
423 // forbidding external storage, give it a try.
424 // Initiate disk mount operation. MountPath auto-detects the filesystem
425 // format if the second argument is empty. The third argument (mount
426 // label) is not used in a disk mount operation.
427 disk_mount_manager_->MountPath(
428 disk->device_path(), std::string(), std::string(),
429 chromeos::MOUNT_TYPE_DEVICE);
433 // Notify to observers.
434 FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
435 OnDiskAdded(*disk, mounting));
439 case chromeos::disks::DiskMountManager::DISK_REMOVED:
440 // If the disk is already mounted, unmount it.
441 if (!disk->mount_path().empty()) {
442 disk_mount_manager_->UnmountPath(
444 chromeos::UNMOUNT_OPTIONS_LAZY,
445 chromeos::disks::DiskMountManager::UnmountPathCallback());
448 // Notify to observers.
449 FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
450 OnDiskRemoved(*disk));
453 case chromeos::disks::DiskMountManager::DISK_CHANGED:
454 DVLOG(1) << "Ignore CHANGED event.";
460 void VolumeManager::OnDeviceEvent(
461 chromeos::disks::DiskMountManager::DeviceEvent event,
462 const std::string& device_path) {
463 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
464 DVLOG(1) << "OnDeviceEvent: " << event << ", " << device_path;
467 case chromeos::disks::DiskMountManager::DEVICE_ADDED:
468 FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
469 OnDeviceAdded(device_path));
471 case chromeos::disks::DiskMountManager::DEVICE_REMOVED:
472 FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
473 OnDeviceRemoved(device_path));
475 case chromeos::disks::DiskMountManager::DEVICE_SCANNED:
476 DVLOG(1) << "Ignore SCANNED event: " << device_path;
482 void VolumeManager::OnMountEvent(
483 chromeos::disks::DiskMountManager::MountEvent event,
484 chromeos::MountError error_code,
485 const chromeos::disks::DiskMountManager::MountPointInfo& mount_info) {
486 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
487 DCHECK_NE(chromeos::MOUNT_TYPE_INVALID, mount_info.mount_type);
489 if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
490 // If the file is not mounted now, tell it to drive file system so that
491 // it can handle file caching correctly.
492 // Note that drive file system knows if the file is managed by drive file
493 // system or not, so here we report all paths.
494 if ((event == chromeos::disks::DiskMountManager::MOUNTING &&
495 error_code != chromeos::MOUNT_ERROR_NONE) ||
496 (event == chromeos::disks::DiskMountManager::UNMOUNTING &&
497 error_code == chromeos::MOUNT_ERROR_NONE)) {
498 drive::FileSystemInterface* file_system =
499 drive::util::GetFileSystemByProfile(profile_);
501 file_system->MarkCacheFileAsUnmounted(
502 base::FilePath(mount_info.source_path),
503 base::Bind(&OnMarkCacheFileAsUnmounted));
508 // Notify a mounting/unmounting event to observers.
509 const chromeos::disks::DiskMountManager::Disk* disk =
510 disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path);
511 VolumeInfo volume_info =
512 CreateVolumeInfoFromMountPointInfo(mount_info, disk);
514 case chromeos::disks::DiskMountManager::MOUNTING: {
516 disk && mounted_disk_monitor_->DiskIsRemounting(*disk);
517 DoMountEvent(error_code, volume_info, is_remounting);
520 case chromeos::disks::DiskMountManager::UNMOUNTING:
521 DoUnmountEvent(error_code, volume_info);
527 void VolumeManager::OnFormatEvent(
528 chromeos::disks::DiskMountManager::FormatEvent event,
529 chromeos::FormatError error_code,
530 const std::string& device_path) {
531 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
532 DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code
533 << ", " << device_path;
536 case chromeos::disks::DiskMountManager::FORMAT_STARTED:
538 VolumeManagerObserver, observers_,
539 OnFormatStarted(device_path,
540 error_code == chromeos::FORMAT_ERROR_NONE));
542 case chromeos::disks::DiskMountManager::FORMAT_COMPLETED:
543 if (error_code == chromeos::FORMAT_ERROR_NONE) {
544 // If format is completed successfully, try to mount the device.
545 // MountPath auto-detects filesystem format if second argument is
546 // empty. The third argument (mount label) is not used in a disk mount
548 disk_mount_manager_->MountPath(
549 device_path, std::string(), std::string(),
550 chromeos::MOUNT_TYPE_DEVICE);
554 VolumeManagerObserver, observers_,
555 OnFormatCompleted(device_path,
556 error_code == chromeos::FORMAT_ERROR_NONE));
563 void VolumeManager::OnExternalStorageDisabledChanged() {
564 // If the policy just got disabled we have to unmount every device currently
565 // mounted. The opposite is fine - we can let the user re-plug her device to
566 // make it available.
567 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
568 // We do not iterate on mount_points directly, because mount_points can
569 // be changed by UnmountPath().
570 // TODO(hidehiko): Is it necessary to unmount mounted archives, too, here?
571 while (!disk_mount_manager_->mount_points().empty()) {
572 std::string mount_path =
573 disk_mount_manager_->mount_points().begin()->second.mount_path;
574 disk_mount_manager_->UnmountPath(
576 chromeos::UNMOUNT_OPTIONS_NONE,
577 chromeos::disks::DiskMountManager::UnmountPathCallback());
582 void VolumeManager::OnPrivetVolumesAvailable(
583 const local_discovery::PrivetVolumeLister::VolumeList& volumes) {
584 for (local_discovery::PrivetVolumeLister::VolumeList::const_iterator i =
585 volumes.begin(); i != volumes.end(); i++) {
586 VolumeInfo volume_info = CreatePrivetVolumeInfo(*i);
587 DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info, false);
591 void VolumeManager::DoMountEvent(chromeos::MountError error_code,
592 const VolumeInfo& volume_info,
593 bool is_remounting) {
594 // Archive files are mounted globally in system. We however don't want to show
595 // archives from profile-specific folders (Drive/Downloads) of other users in
596 // multi-profile session. To this end, we filter out archives not on the
597 // volumes already mounted on this VolumeManager instance.
598 if (volume_info.type == VOLUME_TYPE_MOUNTED_ARCHIVE_FILE) {
599 // Source may be in Drive cache folder under the current profile directory.
600 bool from_current_profile =
601 profile_->GetPath().IsParent(volume_info.source_path);
602 for (std::map<std::string, VolumeInfo>::const_iterator iter =
603 mounted_volumes_.begin();
604 !from_current_profile && iter != mounted_volumes_.end();
606 if (iter->second.mount_path.IsParent(volume_info.source_path))
607 from_current_profile = true;
609 if (!from_current_profile)
613 if (error_code == chromeos::MOUNT_ERROR_NONE || volume_info.mount_condition)
614 mounted_volumes_[volume_info.volume_id] = volume_info;
616 FOR_EACH_OBSERVER(VolumeManagerObserver,
618 OnVolumeMounted(error_code, volume_info, is_remounting));
621 void VolumeManager::DoUnmountEvent(chromeos::MountError error_code,
622 const VolumeInfo& volume_info) {
623 if (mounted_volumes_.find(volume_info.volume_id) == mounted_volumes_.end())
625 if (error_code == chromeos::MOUNT_ERROR_NONE)
626 mounted_volumes_.erase(volume_info.volume_id);
628 FOR_EACH_OBSERVER(VolumeManagerObserver,
630 OnVolumeUnmounted(error_code, volume_info));
633 } // namespace file_manager