// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/file_manager/volume_manager.h" #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_service.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chromeos/drive/drive_integration_service.h" #include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/snapshot_manager.h" #include "chrome/browser/chromeos/file_manager/volume_manager_factory.h" #include "chrome/browser/chromeos/file_manager/volume_manager_observer.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/pref_names.h" #include "chromeos/chromeos_switches.h" #include "chromeos/disks/disk_mount_manager.h" #include "components/drive/file_system_core_util.h" #include "components/drive/file_system_interface.h" #include "components/storage_monitor/storage_monitor.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "device/media_transfer_protocol/media_transfer_protocol_manager.h" #include "storage/browser/fileapi/external_mount_points.h" namespace file_manager { namespace { const uint32_t kAccessCapabilityReadWrite = 0; const uint32_t kFilesystemTypeGenericHierarchical = 2; const char kFileManagerMTPMountNamePrefix[] = "fileman-mtp-"; const char kMtpVolumeIdPrefix [] = "mtp:"; const char kRootPath[] = "/"; // Registers |path| as the "Downloads" folder to the FileSystem API backend. // If another folder is already mounted. It revokes and overrides the old one. bool RegisterDownloadsMountPoint(Profile* profile, const base::FilePath& path) { // Although we show only profile's own "Downloads" folder in Files.app, // in the backend we need to mount all profile's download directory globally. // Otherwise, Files.app cannot support cross-profile file copies, etc. // For this reason, we need to register to the global GetSystemInstance(). const std::string mount_point_name = file_manager::util::GetDownloadsMountPointName(profile); storage::ExternalMountPoints* const mount_points = storage::ExternalMountPoints::GetSystemInstance(); // In some tests we want to override existing Downloads mount point, so we // first revoke the existing mount point (if any). mount_points->RevokeFileSystem(mount_point_name); return mount_points->RegisterFileSystem(mount_point_name, storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(), path); } // Finds the path register as the "Downloads" folder to FileSystem API backend. // Returns false if it is not registered. bool FindDownloadsMountPointPath(Profile* profile, base::FilePath* path) { const std::string mount_point_name = util::GetDownloadsMountPointName(profile); storage::ExternalMountPoints* const mount_points = storage::ExternalMountPoints::GetSystemInstance(); return mount_points->GetRegisteredPath(mount_point_name, path); } VolumeType MountTypeToVolumeType(chromeos::MountType type) { switch (type) { case chromeos::MOUNT_TYPE_INVALID: // We don't expect this value, but list here, so that when any value // is added to the enum definition but this is not edited, the compiler // warns it. break; case chromeos::MOUNT_TYPE_DEVICE: return VOLUME_TYPE_REMOVABLE_DISK_PARTITION; case chromeos::MOUNT_TYPE_ARCHIVE: return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE; } NOTREACHED(); return VOLUME_TYPE_DOWNLOADS_DIRECTORY; } // Returns a string representation of the given volume type. std::string VolumeTypeToString(VolumeType type) { switch (type) { case VOLUME_TYPE_GOOGLE_DRIVE: return "drive"; case VOLUME_TYPE_DOWNLOADS_DIRECTORY: return "downloads"; case VOLUME_TYPE_REMOVABLE_DISK_PARTITION: return "removable"; case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE: return "archive"; case VOLUME_TYPE_PROVIDED: return "provided"; case VOLUME_TYPE_MTP: return "mtp"; case VOLUME_TYPE_TESTING: return "testing"; case NUM_VOLUME_TYPE: break; } NOTREACHED(); return ""; } // Generates a unique volume ID for the given volume info. std::string GenerateVolumeId(const Volume& volume) { // For the same volume type, base names are unique, as mount points are // flat for the same volume type. return (VolumeTypeToString(volume.type()) + ":" + volume.mount_path().BaseName().AsUTF8Unsafe()); } std::string GetMountPointNameForMediaStorage( const storage_monitor::StorageInfo& info) { std::string name(kFileManagerMTPMountNamePrefix); name += info.device_id(); return name; } } // namespace Volume::Volume() : source_(SOURCE_FILE), type_(VOLUME_TYPE_GOOGLE_DRIVE), device_type_(chromeos::DEVICE_TYPE_UNKNOWN), mount_condition_(chromeos::disks::MOUNT_CONDITION_NONE), mount_context_(MOUNT_CONTEXT_UNKNOWN), is_parent_(false), is_read_only_(false), has_media_(false), configurable_(false), watchable_(false) { } Volume::~Volume() { } // static Volume* Volume::CreateForDrive(Profile* profile) { const base::FilePath& drive_path = drive::util::GetDriveMountPointPath(profile); Volume* const volume = new Volume; volume->type_ = VOLUME_TYPE_GOOGLE_DRIVE; volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN; volume->source_path_ = drive_path; volume->source_ = SOURCE_NETWORK; volume->mount_path_ = drive_path; volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE; volume->volume_id_ = GenerateVolumeId(*volume); volume->watchable_ = true; return volume; } // static Volume* Volume::CreateForDownloads(const base::FilePath& downloads_path) { Volume* const volume = new Volume; volume->type_ = VOLUME_TYPE_DOWNLOADS_DIRECTORY; volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN; // Keep source_path empty. volume->source_ = SOURCE_SYSTEM; volume->mount_path_ = downloads_path; volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE; volume->volume_id_ = GenerateVolumeId(*volume); volume->watchable_ = true; return volume; } // static Volume* Volume::CreateForRemovable( const chromeos::disks::DiskMountManager::MountPointInfo& mount_point, const chromeos::disks::DiskMountManager::Disk* disk) { Volume* const volume = new Volume; volume->type_ = MountTypeToVolumeType(mount_point.mount_type); volume->source_path_ = base::FilePath(mount_point.source_path); volume->source_ = mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE ? SOURCE_FILE : SOURCE_DEVICE; volume->mount_path_ = base::FilePath(mount_point.mount_path); volume->mount_condition_ = mount_point.mount_condition; volume->volume_label_ = volume->mount_path().BaseName().AsUTF8Unsafe(); if (disk) { volume->device_type_ = disk->device_type(); volume->system_path_prefix_ = base::FilePath(disk->system_path_prefix()); volume->is_parent_ = disk->is_parent(); volume->is_read_only_ = disk->is_read_only(); volume->has_media_ = disk->has_media(); } else { volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN; volume->is_read_only_ = (mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE); } volume->volume_id_ = GenerateVolumeId(*volume); volume->watchable_ = true; return volume; } // static Volume* Volume::CreateForProvidedFileSystem( const chromeos::file_system_provider::ProvidedFileSystemInfo& file_system_info, MountContext mount_context) { Volume* const volume = new Volume; volume->file_system_id_ = file_system_info.file_system_id(); volume->extension_id_ = file_system_info.extension_id(); switch (file_system_info.source()) { case extensions::SOURCE_FILE: volume->source_ = SOURCE_FILE; break; case extensions::SOURCE_DEVICE: volume->source_ = SOURCE_DEVICE; break; case extensions::SOURCE_NETWORK: volume->source_ = SOURCE_NETWORK; break; } volume->volume_label_ = file_system_info.display_name(); volume->type_ = VOLUME_TYPE_PROVIDED; volume->mount_path_ = file_system_info.mount_path(); volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE; volume->mount_context_ = mount_context; volume->is_parent_ = true; volume->is_read_only_ = !file_system_info.writable(); volume->configurable_ = file_system_info.configurable(); volume->watchable_ = file_system_info.watchable(); volume->volume_id_ = GenerateVolumeId(*volume); return volume; } // static Volume* Volume::CreateForMTP(const base::FilePath& mount_path, const std::string& label, bool read_only) { Volume* const volume = new Volume; volume->type_ = VOLUME_TYPE_MTP; volume->mount_path_ = mount_path; volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE; volume->is_parent_ = true; volume->is_read_only_ = read_only; volume->volume_id_ = kMtpVolumeIdPrefix + label; volume->volume_label_ = label; volume->source_path_ = mount_path; volume->source_ = SOURCE_DEVICE; volume->device_type_ = chromeos::DEVICE_TYPE_MOBILE; return volume; } // static Volume* Volume::CreateForTesting(const base::FilePath& path, VolumeType volume_type, chromeos::DeviceType device_type, bool read_only) { Volume* const volume = new Volume; volume->type_ = volume_type; volume->device_type_ = device_type; // Keep source_path empty. volume->source_ = SOURCE_DEVICE; volume->mount_path_ = path; volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE; volume->is_read_only_ = read_only; volume->volume_id_ = GenerateVolumeId(*volume); return volume; } // static Volume* Volume::CreateForTesting(const base::FilePath& device_path, const base::FilePath& mount_path) { Volume* const volume = new Volume; volume->system_path_prefix_ = device_path; volume->mount_path_ = mount_path; return volume; } VolumeManager::VolumeManager( Profile* profile, drive::DriveIntegrationService* drive_integration_service, chromeos::PowerManagerClient* power_manager_client, chromeos::disks::DiskMountManager* disk_mount_manager, chromeos::file_system_provider::Service* file_system_provider_service, GetMtpStorageInfoCallback get_mtp_storage_info_callback) : profile_(profile), drive_integration_service_(drive_integration_service), disk_mount_manager_(disk_mount_manager), file_system_provider_service_(file_system_provider_service), get_mtp_storage_info_callback_(get_mtp_storage_info_callback), snapshot_manager_(new SnapshotManager(profile_)), weak_ptr_factory_(this) { DCHECK(disk_mount_manager); } VolumeManager::~VolumeManager() { } VolumeManager* VolumeManager::Get(content::BrowserContext* context) { return VolumeManagerFactory::Get(context); } void VolumeManager::Initialize() { // If in Sign in profile, then skip mounting and listening for mount events. if (chromeos::ProfileHelper::IsSigninProfile(profile_)) return; // Register 'Downloads' folder for the profile to the file system. const base::FilePath downloads = file_manager::util::GetDownloadsFolderForProfile(profile_); const bool success = RegisterDownloadsMountPoint(profile_, downloads); DCHECK(success); DoMountEvent(chromeos::MOUNT_ERROR_NONE, make_linked_ptr(Volume::CreateForDownloads(downloads))); // Subscribe to DriveIntegrationService. if (drive_integration_service_) { drive_integration_service_->AddObserver(this); if (drive_integration_service_->IsMounted()) { DoMountEvent(chromeos::MOUNT_ERROR_NONE, make_linked_ptr(Volume::CreateForDrive(profile_))); } } // Subscribe to DiskMountManager. disk_mount_manager_->AddObserver(this); disk_mount_manager_->EnsureMountInfoRefreshed( base::Bind(&VolumeManager::OnDiskMountManagerRefreshed, weak_ptr_factory_.GetWeakPtr()), false /* force */); // Subscribe to FileSystemProviderService and register currently mounted // volumes for the profile. if (file_system_provider_service_) { using chromeos::file_system_provider::ProvidedFileSystemInfo; file_system_provider_service_->AddObserver(this); std::vector file_system_info_list = file_system_provider_service_->GetProvidedFileSystemInfoList(); for (size_t i = 0; i < file_system_info_list.size(); ++i) { linked_ptr volume(Volume::CreateForProvidedFileSystem( file_system_info_list[i], MOUNT_CONTEXT_AUTO)); DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume); } } // Subscribe to Profile Preference change. pref_change_registrar_.Init(profile_->GetPrefs()); pref_change_registrar_.Add( prefs::kExternalStorageDisabled, base::Bind(&VolumeManager::OnExternalStorageDisabledChanged, weak_ptr_factory_.GetWeakPtr())); // Subscribe to storage monitor for MTP notifications. if (storage_monitor::StorageMonitor::GetInstance()) { storage_monitor::StorageMonitor::GetInstance()->EnsureInitialized( base::Bind(&VolumeManager::OnStorageMonitorInitialized, weak_ptr_factory_.GetWeakPtr())); } } void VolumeManager::Shutdown() { weak_ptr_factory_.InvalidateWeakPtrs(); snapshot_manager_.reset(); pref_change_registrar_.RemoveAll(); disk_mount_manager_->RemoveObserver(this); if (storage_monitor::StorageMonitor::GetInstance()) storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this); if (drive_integration_service_) drive_integration_service_->RemoveObserver(this); if (file_system_provider_service_) file_system_provider_service_->RemoveObserver(this); } void VolumeManager::AddObserver(VolumeManagerObserver* observer) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(observer); observers_.AddObserver(observer); } void VolumeManager::RemoveObserver(VolumeManagerObserver* observer) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(observer); observers_.RemoveObserver(observer); } std::vector> VolumeManager::GetVolumeList() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); std::vector> result; for (const auto& pair : mounted_volumes_) { result.push_back(pair.second->AsWeakPtr()); } return result; } base::WeakPtr VolumeManager::FindVolumeById( const std::string& volume_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); const auto it = mounted_volumes_.find(volume_id); if (it != mounted_volumes_.end()) return it->second->AsWeakPtr(); return base::WeakPtr(); } bool VolumeManager::RegisterDownloadsDirectoryForTesting( const base::FilePath& path) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); base::FilePath old_path; if (FindDownloadsMountPointPath(profile_, &old_path)) { DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, make_linked_ptr(Volume::CreateForDownloads(old_path))); } bool success = RegisterDownloadsMountPoint(profile_, path); DoMountEvent( success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH, make_linked_ptr(Volume::CreateForDownloads(path))); return success; } void VolumeManager::AddVolumeForTesting(const base::FilePath& path, VolumeType volume_type, chromeos::DeviceType device_type, bool read_only) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DoMountEvent(chromeos::MOUNT_ERROR_NONE, make_linked_ptr(Volume::CreateForTesting( path, volume_type, device_type, read_only))); } void VolumeManager::AddVolumeForTesting(const linked_ptr& volume) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume); } void VolumeManager::OnFileSystemMounted() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Raise mount event. // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed // or network is unreachable. These two errors will be handled later. linked_ptr volume(Volume::CreateForDrive(profile_)); DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume); } void VolumeManager::OnFileSystemBeingUnmounted() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); linked_ptr volume(Volume::CreateForDrive(profile_)); DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, volume); } void VolumeManager::OnDiskEvent( chromeos::disks::DiskMountManager::DiskEvent event, const chromeos::disks::DiskMountManager::Disk* disk) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Disregard hidden devices. if (disk->is_hidden()) return; switch (event) { case chromeos::disks::DiskMountManager::DISK_ADDED: case chromeos::disks::DiskMountManager::DISK_CHANGED: { if (disk->device_path().empty()) { DVLOG(1) << "Empty system path for " << disk->device_path(); return; } bool mounting = false; if (disk->mount_path().empty() && disk->has_media() && !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { // If disk is not mounted yet and it has media and there is no policy // forbidding external storage, give it a try. // Initiate disk mount operation. MountPath auto-detects the filesystem // format if the second argument is empty. The third argument (mount // label) is not used in a disk mount operation. disk_mount_manager_->MountPath( disk->device_path(), std::string(), std::string(), chromeos::MOUNT_TYPE_DEVICE); mounting = true; } // Notify to observers. FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDiskAdded(*disk, mounting)); return; } case chromeos::disks::DiskMountManager::DISK_REMOVED: // If the disk is already mounted, unmount it. if (!disk->mount_path().empty()) { disk_mount_manager_->UnmountPath( disk->mount_path(), chromeos::UNMOUNT_OPTIONS_LAZY, chromeos::disks::DiskMountManager::UnmountPathCallback()); } // Notify to observers. FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDiskRemoved(*disk)); return; } NOTREACHED(); } void VolumeManager::OnDeviceEvent( chromeos::disks::DiskMountManager::DeviceEvent event, const std::string& device_path) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DVLOG(1) << "OnDeviceEvent: " << event << ", " << device_path; switch (event) { case chromeos::disks::DiskMountManager::DEVICE_ADDED: FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDeviceAdded(device_path)); return; case chromeos::disks::DiskMountManager::DEVICE_REMOVED: { FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnDeviceRemoved(device_path)); return; } case chromeos::disks::DiskMountManager::DEVICE_SCANNED: DVLOG(1) << "Ignore SCANNED event: " << device_path; return; } NOTREACHED(); } void VolumeManager::OnMountEvent( chromeos::disks::DiskMountManager::MountEvent event, chromeos::MountError error_code, const chromeos::disks::DiskMountManager::MountPointInfo& mount_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_NE(chromeos::MOUNT_TYPE_INVALID, mount_info.mount_type); if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { // If the file is not mounted now, tell it to drive file system so that // it can handle file caching correctly. // Note that drive file system knows if the file is managed by drive file // system or not, so here we report all paths. if ((event == chromeos::disks::DiskMountManager::MOUNTING && error_code != chromeos::MOUNT_ERROR_NONE) || (event == chromeos::disks::DiskMountManager::UNMOUNTING && error_code == chromeos::MOUNT_ERROR_NONE)) { drive::FileSystemInterface* const file_system = drive::util::GetFileSystemByProfile(profile_); if (file_system) { file_system->MarkCacheFileAsUnmounted( base::FilePath(mount_info.source_path), base::Bind(&drive::util::EmptyFileOperationCallback)); } } } // Notify a mounting/unmounting event to observers. const chromeos::disks::DiskMountManager::Disk* const disk = disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path); linked_ptr volume(Volume::CreateForRemovable(mount_info, disk)); switch (event) { case chromeos::disks::DiskMountManager::MOUNTING: { DoMountEvent(error_code, volume); return; } case chromeos::disks::DiskMountManager::UNMOUNTING: DoUnmountEvent(error_code, volume); return; } NOTREACHED(); } void VolumeManager::OnFormatEvent( chromeos::disks::DiskMountManager::FormatEvent event, chromeos::FormatError error_code, const std::string& device_path) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code << ", " << device_path; switch (event) { case chromeos::disks::DiskMountManager::FORMAT_STARTED: FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE)); return; case chromeos::disks::DiskMountManager::FORMAT_COMPLETED: if (error_code == chromeos::FORMAT_ERROR_NONE) { // If format is completed successfully, try to mount the device. // MountPath auto-detects filesystem format if second argument is // empty. The third argument (mount label) is not used in a disk mount // operation. disk_mount_manager_->MountPath( device_path, std::string(), std::string(), chromeos::MOUNT_TYPE_DEVICE); } FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE)); return; } NOTREACHED(); } void VolumeManager::OnProvidedFileSystemMount( const chromeos::file_system_provider::ProvidedFileSystemInfo& file_system_info, chromeos::file_system_provider::MountContext context, base::File::Error error) { MountContext volume_context = MOUNT_CONTEXT_UNKNOWN; switch (context) { case chromeos::file_system_provider::MOUNT_CONTEXT_USER: volume_context = MOUNT_CONTEXT_USER; break; case chromeos::file_system_provider::MOUNT_CONTEXT_RESTORE: volume_context = MOUNT_CONTEXT_AUTO; break; } linked_ptr volume( Volume::CreateForProvidedFileSystem(file_system_info, volume_context)); // TODO(mtomasz): Introduce own type, and avoid using MountError internally, // since it is related to cros disks only. chromeos::MountError mount_error; switch (error) { case base::File::FILE_OK: mount_error = chromeos::MOUNT_ERROR_NONE; break; case base::File::FILE_ERROR_EXISTS: mount_error = chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED; break; default: mount_error = chromeos::MOUNT_ERROR_UNKNOWN; break; } DoMountEvent(mount_error, volume); } void VolumeManager::OnProvidedFileSystemUnmount( const chromeos::file_system_provider::ProvidedFileSystemInfo& file_system_info, base::File::Error error) { // TODO(mtomasz): Introduce own type, and avoid using MountError internally, // since it is related to cros disks only. const chromeos::MountError mount_error = error == base::File::FILE_OK ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_UNKNOWN; linked_ptr volume(Volume::CreateForProvidedFileSystem( file_system_info, MOUNT_CONTEXT_UNKNOWN)); DoUnmountEvent(mount_error, volume); } void VolumeManager::OnExternalStorageDisabledChanged() { // If the policy just got disabled we have to unmount every device currently // mounted. The opposite is fine - we can let the user re-plug her device to // make it available. if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { // We do not iterate on mount_points directly, because mount_points can // be changed by UnmountPath(). // TODO(hidehiko): Is it necessary to unmount mounted archives, too, here? while (!disk_mount_manager_->mount_points().empty()) { std::string mount_path = disk_mount_manager_->mount_points().begin()->second.mount_path; disk_mount_manager_->UnmountPath( mount_path, chromeos::UNMOUNT_OPTIONS_NONE, chromeos::disks::DiskMountManager::UnmountPathCallback()); } } } void VolumeManager::OnRemovableStorageAttached( const storage_monitor::StorageInfo& info) { if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id())) return; if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) return; const base::FilePath path = base::FilePath::FromUTF8Unsafe(info.location()); const std::string fsid = GetMountPointNameForMediaStorage(info); const std::string base_name = base::UTF16ToUTF8(info.model_name()); // Assign a fresh volume ID based on the volume name. std::string label = base_name; for (int i = 2; mounted_volumes_.count(kMtpVolumeIdPrefix + label); ++i) label = base_name + base::StringPrintf(" (%d)", i); bool result = storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( fsid, storage::kFileSystemTypeDeviceMediaAsFileStorage, storage::FileSystemMountOption(), path); DCHECK(result); // Resolve mtp storage name and get MtpStorageInfo. std::string storage_name; base::RemoveChars(info.location(), kRootPath, &storage_name); DCHECK(!storage_name.empty()); const MtpStorageInfo* mtp_storage_info; if (get_mtp_storage_info_callback_.is_null()) { mtp_storage_info = storage_monitor::StorageMonitor::GetInstance() ->media_transfer_protocol_manager() ->GetStorageInfo(storage_name); } else { mtp_storage_info = get_mtp_storage_info_callback_.Run(storage_name); } DCHECK(mtp_storage_info); // Mtp write is enabled only when the device is writable and supports generic // hierarchical file system. const bool read_only = base::CommandLine::ForCurrentProcess()->HasSwitch( chromeos::switches::kDisableMtpWriteSupport) || mtp_storage_info->access_capability() != kAccessCapabilityReadWrite || mtp_storage_info->filesystem_type() != kFilesystemTypeGenericHierarchical; content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&MTPDeviceMapService::RegisterMTPFileSystem, base::Unretained(MTPDeviceMapService::GetInstance()), info.location(), fsid, read_only)); linked_ptr volume(Volume::CreateForMTP(path, label, read_only)); DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume); } void VolumeManager::OnRemovableStorageDetached( const storage_monitor::StorageInfo& info) { if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id())) return; for (const auto mounted_volume : mounted_volumes_) { if (mounted_volume.second->source_path().value() == info.location()) { DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, mounted_volume.second); const std::string fsid = GetMountPointNameForMediaStorage(info); storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(fsid); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind( &MTPDeviceMapService::RevokeMTPFileSystem, base::Unretained(MTPDeviceMapService::GetInstance()), fsid)); return; } } } void VolumeManager::OnDiskMountManagerRefreshed(bool success) { if (!success) { LOG(ERROR) << "Failed to refresh disk mount manager"; return; } std::vector> archives; const chromeos::disks::DiskMountManager::MountPointMap& mount_points = disk_mount_manager_->mount_points(); for (chromeos::disks::DiskMountManager::MountPointMap::const_iterator it = mount_points.begin(); it != mount_points.end(); ++it) { if (it->second.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { // Archives are mounted after other types of volume. See below. archives.push_back( make_linked_ptr(Volume::CreateForRemovable(it->second, NULL))); continue; } DoMountEvent(chromeos::MOUNT_ERROR_NONE, make_linked_ptr(Volume::CreateForRemovable( it->second, disk_mount_manager_->FindDiskBySourcePath( it->second.source_path)))); } // We mount archives only if they are opened from currently mounted volumes. // To check the condition correctly in DoMountEvent, we care about the order. std::vector done(archives.size(), false); for (size_t i = 0; i < archives.size(); ++i) { if (done[i]) continue; std::vector> chain; done[i] = true; chain.push_back(archives[i]); // If archives[i]'s source_path is in another archive, mount it first. for (size_t parent = i + 1; parent < archives.size(); ++parent) { if (!done[parent] && archives[parent]->mount_path().IsParent( chain.back()->source_path())) { done[parent] = true; chain.push_back(archives[parent]); parent = i + 1; // Search archives[parent]'s parent from the beginning. } } // Mount from the tail of chain. for (size_t i = chain.size(); i > 0; --i) { DoMountEvent(chromeos::MOUNT_ERROR_NONE, chain[i - 1]); } } } void VolumeManager::OnStorageMonitorInitialized() { std::vector storages = storage_monitor::StorageMonitor::GetInstance()->GetAllAvailableStorages(); for (size_t i = 0; i < storages.size(); ++i) OnRemovableStorageAttached(storages[i]); storage_monitor::StorageMonitor::GetInstance()->AddObserver(this); } void VolumeManager::DoMountEvent(chromeos::MountError error_code, const linked_ptr& volume) { // Archive files are mounted globally in system. We however don't want to show // archives from profile-specific folders (Drive/Downloads) of other users in // multi-profile session. To this end, we filter out archives not on the // volumes already mounted on this VolumeManager instance. if (volume->type() == VOLUME_TYPE_MOUNTED_ARCHIVE_FILE) { // Source may be in Drive cache folder under the current profile directory. bool from_current_profile = profile_->GetPath().IsParent(volume->source_path()); for (const auto& mounted_volume : mounted_volumes_) { if (mounted_volume.second->mount_path().IsParent(volume->source_path())) { from_current_profile = true; break; } } if (!from_current_profile) return; } // Filter out removable disks if forbidden by policy for this profile. if (volume->type() == VOLUME_TYPE_REMOVABLE_DISK_PARTITION && profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { return; } if (error_code == chromeos::MOUNT_ERROR_NONE || volume->mount_condition()) { mounted_volumes_[volume->volume_id()] = volume; UMA_HISTOGRAM_ENUMERATION("FileBrowser.VolumeType", volume->type(), NUM_VOLUME_TYPE); } FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnVolumeMounted(error_code, *volume)); } void VolumeManager::DoUnmountEvent(chromeos::MountError error_code, const linked_ptr& volume) { if (mounted_volumes_.find(volume->volume_id()) == mounted_volumes_.end()) return; if (error_code == chromeos::MOUNT_ERROR_NONE) mounted_volumes_.erase(volume->volume_id()); FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnVolumeUnmounted(error_code, *volume.get())); } } // namespace file_manager