| // Copyright (c) 2012 The Chromium OS 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 "cros-disks/disk_manager.h" |
| |
| #include <libudev.h> |
| #include <string.h> |
| #include <sys/mount.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "cros-disks/disk.h" |
| #include "cros-disks/exfat_mounter.h" |
| #include "cros-disks/external_mounter.h" |
| #include "cros-disks/filesystem.h" |
| #include "cros-disks/metrics.h" |
| #include "cros-disks/mount_options.h" |
| #include "cros-disks/ntfs_mounter.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/system_mounter.h" |
| #include "cros-disks/udev_device.h" |
| |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::unique_ptr; |
| using std::vector; |
| |
| namespace cros_disks { |
| |
| namespace { |
| |
| const char kBlockSubsystem[] = "block"; |
| const char kMmcSubsystem[] = "mmc"; |
| const char kScsiSubsystem[] = "scsi"; |
| const char kScsiDevice[] = "scsi_device"; |
| const char kUdevAddAction[] = "add"; |
| const char kUdevChangeAction[] = "change"; |
| const char kUdevRemoveAction[] = "remove"; |
| const char kPropertyDiskEjectRequest[] = "DISK_EJECT_REQUEST"; |
| const char kPropertyDiskMediaChange[] = "DISK_MEDIA_CHANGE"; |
| |
| // An EnumerateBlockDevices callback that appends a Disk object, created from |
| // |dev|, to |disks| if |dev| should not be ignored by cros-disks. Always |
| // returns true to continue the enumeration in EnumerateBlockDevices. |
| bool AppendDiskIfNotIgnored(vector<Disk>* disks, udev_device* dev) { |
| DCHECK(disks); |
| DCHECK(dev); |
| |
| UdevDevice device(dev); |
| if (!device.IsIgnored()) |
| disks->push_back(device.ToDisk()); |
| |
| return true; // Continue the enumeration. |
| } |
| |
| // An EnumerateBlockDevices callback that checks if |dev| matches |path|. If |
| // it's a match, sets |match| to true and |disk| (if not NULL) to a Disk object |
| // created from |dev|, and returns false to stop the enumeration in |
| // EnumerateBlockDevices. Otherwise, sets |match| to false, leaves |disk| |
| // unchanged, and returns true to continue the enumeration in |
| // EnumerateBlockDevices. |
| bool MatchDiskByPath(const string& path, bool* match, Disk* disk, |
| udev_device* dev) { |
| DCHECK(match); |
| DCHECK(dev); |
| |
| const char* sys_path = udev_device_get_syspath(dev); |
| const char* dev_path = udev_device_get_devpath(dev); |
| const char* dev_file = udev_device_get_devnode(dev); |
| *match = (sys_path && path == sys_path) || |
| (dev_path && path == dev_path) || |
| (dev_file && path == dev_file); |
| if (!*match) |
| return true; // Not a match. Continue the enumeration. |
| |
| if (disk) |
| *disk = UdevDevice(dev).ToDisk(); |
| |
| return false; // Match. Stop enumeration. |
| } |
| |
| } // namespace |
| |
| DiskManager::DiskManager(const string& mount_root, Platform* platform, |
| Metrics* metrics, DeviceEjector* device_ejector) |
| : MountManager(mount_root, platform, metrics), |
| device_ejector_(device_ejector), |
| udev_(udev_new()), |
| udev_monitor_fd_(0), |
| eject_device_on_unmount_(true) { |
| CHECK(device_ejector_) << "Invalid device ejector"; |
| CHECK(udev_) << "Failed to initialize udev"; |
| udev_monitor_ = udev_monitor_new_from_netlink(udev_, "udev"); |
| CHECK(udev_monitor_) << "Failed to create a udev monitor"; |
| udev_monitor_filter_add_match_subsystem_devtype(udev_monitor_, |
| kBlockSubsystem, nullptr); |
| udev_monitor_filter_add_match_subsystem_devtype(udev_monitor_, |
| kMmcSubsystem, nullptr); |
| udev_monitor_filter_add_match_subsystem_devtype(udev_monitor_, |
| kScsiSubsystem, kScsiDevice); |
| udev_monitor_enable_receiving(udev_monitor_); |
| udev_monitor_fd_ = udev_monitor_get_fd(udev_monitor_); |
| } |
| |
| DiskManager::~DiskManager() { |
| UnmountAll(); |
| udev_monitor_unref(udev_monitor_); |
| udev_unref(udev_); |
| } |
| |
| bool DiskManager::Initialize() { |
| RegisterDefaultFilesystems(); |
| |
| // Since there are no udev add events for the devices that already exist |
| // when the disk manager starts, emulate udev add events for these devices |
| // to correctly populate |disks_detected_|. |
| EnumerateBlockDevices(base::Bind(&DiskManager::EmulateBlockDeviceEvent, |
| base::Unretained(this), |
| kUdevAddAction)); |
| |
| return MountManager::Initialize(); |
| } |
| |
| bool DiskManager::StopSession() { |
| return UnmountAll(); |
| } |
| |
| bool DiskManager::EmulateBlockDeviceEvent(const char* action, |
| udev_device* dev) { |
| DCHECK(dev); |
| |
| DeviceEventList events; |
| ProcessBlockDeviceEvents(dev, action, &events); |
| |
| return true; // Continue the enumeration. |
| } |
| |
| vector<Disk> DiskManager::EnumerateDisks() const { |
| vector<Disk> disks; |
| EnumerateBlockDevices( |
| base::Bind(&AppendDiskIfNotIgnored, base::Unretained(&disks))); |
| return disks; |
| } |
| |
| void DiskManager::EnumerateBlockDevices( |
| const base::Callback<bool(udev_device* dev)>& callback) const { |
| udev_enumerate *enumerate = udev_enumerate_new(udev_); |
| udev_enumerate_add_match_subsystem(enumerate, kBlockSubsystem); |
| udev_enumerate_scan_devices(enumerate); |
| |
| udev_list_entry *device_list, *device_list_entry; |
| device_list = udev_enumerate_get_list_entry(enumerate); |
| udev_list_entry_foreach(device_list_entry, device_list) { |
| const char *path = udev_list_entry_get_name(device_list_entry); |
| udev_device *dev = udev_device_new_from_syspath(udev_, path); |
| if (dev == nullptr) continue; |
| |
| LOG(INFO) << "Device"; |
| LOG(INFO) << " Node: " << udev_device_get_devnode(dev); |
| LOG(INFO) << " Subsystem: " << udev_device_get_subsystem(dev); |
| LOG(INFO) << " Devtype: " << udev_device_get_devtype(dev); |
| LOG(INFO) << " Devpath: " << udev_device_get_devpath(dev); |
| LOG(INFO) << " Sysname: " << udev_device_get_sysname(dev); |
| LOG(INFO) << " Syspath: " << udev_device_get_syspath(dev); |
| LOG(INFO) << " Properties: "; |
| udev_list_entry *property_list, *property_list_entry; |
| property_list = udev_device_get_properties_list_entry(dev); |
| udev_list_entry_foreach(property_list_entry, property_list) { |
| const char *key = udev_list_entry_get_name(property_list_entry); |
| const char *value = udev_list_entry_get_value(property_list_entry); |
| LOG(INFO) << " " << key << " = " << value; |
| } |
| |
| bool continue_enumeration = callback.Run(dev); |
| udev_device_unref(dev); |
| if (!continue_enumeration) |
| break; |
| } |
| udev_enumerate_unref(enumerate); |
| } |
| |
| void DiskManager::ProcessBlockDeviceEvents( |
| udev_device* dev, const char* action, DeviceEventList* events) { |
| UdevDevice device(dev); |
| if (device.IsIgnored()) |
| return; |
| |
| bool disk_added = false; |
| bool disk_removed = false; |
| bool child_disk_removed = false; |
| if (strcmp(action, kUdevAddAction) == 0) { |
| disk_added = true; |
| } else if (strcmp(action, kUdevRemoveAction) == 0) { |
| disk_removed = true; |
| } else if (strcmp(action, kUdevChangeAction) == 0) { |
| // For removable devices like CD-ROM, an eject request event |
| // is treated as disk removal, while a media change event with |
| // media available is treated as disk insertion. |
| if (device.IsPropertyTrue(kPropertyDiskEjectRequest)) { |
| disk_removed = true; |
| } else if (device.IsPropertyTrue(kPropertyDiskMediaChange)) { |
| if (device.IsMediaAvailable()) { |
| disk_added = true; |
| } else { |
| child_disk_removed = true; |
| } |
| } |
| } |
| |
| string device_path = device.NativePath(); |
| if (disk_added) { |
| if (device.IsAutoMountable()) { |
| if (ContainsKey(disks_detected_, device_path)) { |
| // Disk already exists, so remove it and then add it again. |
| events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path)); |
| } else { |
| disks_detected_[device_path] = set<string>(); |
| |
| // Add the disk as a child of its parent if the parent is already |
| // added to |disks_detected_|. |
| udev_device* parent = udev_device_get_parent(dev); |
| if (parent) { |
| string parent_device_path = UdevDevice(parent).NativePath(); |
| if (ContainsKey(disks_detected_, parent_device_path)) { |
| disks_detected_[parent_device_path].insert(device_path); |
| } |
| } |
| } |
| events->push_back(DeviceEvent(DeviceEvent::kDiskAdded, device_path)); |
| } |
| } else if (disk_removed) { |
| disks_detected_.erase(device_path); |
| events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path)); |
| } else if (child_disk_removed) { |
| bool no_child_disks_found = true; |
| if (ContainsKey(disks_detected_, device_path)) { |
| set<string>& child_disks = disks_detected_[device_path]; |
| no_child_disks_found = child_disks.empty(); |
| for (const auto& child_disk : child_disks) { |
| events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, child_disk)); |
| } |
| } |
| // When the device contains a full-disk partition, there are no child disks. |
| // Remove the device instead. |
| if (no_child_disks_found) |
| events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path)); |
| } |
| } |
| |
| void DiskManager::ProcessMmcOrScsiDeviceEvents( |
| udev_device* dev, const char* action, DeviceEventList* events) { |
| UdevDevice device(dev); |
| if (device.IsMobileBroadbandDevice()) |
| return; |
| |
| string device_path = device.NativePath(); |
| if (strcmp(action, kUdevAddAction) == 0) { |
| if (ContainsKey(devices_detected_, device_path)) { |
| events->push_back(DeviceEvent(DeviceEvent::kDeviceScanned, device_path)); |
| } else { |
| devices_detected_.insert(device_path); |
| events->push_back(DeviceEvent(DeviceEvent::kDeviceAdded, device_path)); |
| } |
| } else if (strcmp(action, kUdevRemoveAction) == 0) { |
| if (ContainsKey(devices_detected_, device_path)) { |
| devices_detected_.erase(device_path); |
| events->push_back(DeviceEvent(DeviceEvent::kDeviceRemoved, device_path)); |
| } |
| } |
| } |
| |
| bool DiskManager::GetDeviceEvents(DeviceEventList* events) { |
| CHECK(events) << "Invalid device event list"; |
| |
| udev_device *dev = udev_monitor_receive_device(udev_monitor_); |
| if (!dev) { |
| LOG(WARNING) << "Ignore device event with no associated udev device."; |
| return false; |
| } |
| |
| LOG(INFO) << "Got Device"; |
| LOG(INFO) << " Syspath: " << udev_device_get_syspath(dev); |
| LOG(INFO) << " Node: " << udev_device_get_devnode(dev); |
| LOG(INFO) << " Subsystem: " << udev_device_get_subsystem(dev); |
| LOG(INFO) << " Devtype: " << udev_device_get_devtype(dev); |
| LOG(INFO) << " Action: " << udev_device_get_action(dev); |
| |
| const char *sys_path = udev_device_get_syspath(dev); |
| const char *subsystem = udev_device_get_subsystem(dev); |
| const char *action = udev_device_get_action(dev); |
| if (!sys_path || !subsystem || !action) { |
| udev_device_unref(dev); |
| return false; |
| } |
| |
| // |udev_monitor_| only monitors block, mmc, and scsi device changes, so |
| // subsystem is either "block", "mmc", or "scsi". |
| if (strcmp(subsystem, kBlockSubsystem) == 0) { |
| ProcessBlockDeviceEvents(dev, action, events); |
| } else { |
| // strcmp(subsystem, kMmcSubsystem) == 0 || |
| // strcmp(subsystem, kScsiSubsystem) == 0 |
| ProcessMmcOrScsiDeviceEvents(dev, action, events); |
| } |
| |
| udev_device_unref(dev); |
| return true; |
| } |
| |
| bool DiskManager::GetDiskByDevicePath(const string& device_path, |
| Disk *disk) const { |
| if (device_path.empty()) |
| return false; |
| |
| bool disk_found = false; |
| EnumerateBlockDevices(base::Bind(&MatchDiskByPath, |
| device_path, |
| base::Unretained(&disk_found), |
| base::Unretained(disk))); |
| return disk_found; |
| } |
| |
| const Filesystem* DiskManager::GetFilesystem( |
| const string& filesystem_type) const { |
| map<string, Filesystem>::const_iterator filesystem_iterator = |
| filesystems_.find(filesystem_type); |
| if (filesystem_iterator == filesystems_.end()) |
| return nullptr; |
| |
| return &filesystem_iterator->second; |
| } |
| |
| void DiskManager::RegisterDefaultFilesystems() { |
| // TODO(benchan): Perhaps these settings can be read from a config file. |
| Filesystem vfat_fs("vfat"); |
| vfat_fs.set_accepts_user_and_group_id(true); |
| vfat_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| vfat_fs.AddExtraMountOption(MountOptions::kOptionFlush); |
| vfat_fs.AddExtraMountOption("shortname=mixed"); |
| vfat_fs.AddExtraMountOption(MountOptions::kOptionUtf8); |
| RegisterFilesystem(vfat_fs); |
| |
| Filesystem exfat_fs("exfat"); |
| exfat_fs.set_mounter_type(ExFATMounter::kMounterType); |
| exfat_fs.set_accepts_user_and_group_id(true); |
| exfat_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(exfat_fs); |
| |
| Filesystem ntfs_fs("ntfs"); |
| ntfs_fs.set_mounter_type(NTFSMounter::kMounterType); |
| ntfs_fs.set_accepts_user_and_group_id(true); |
| ntfs_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(ntfs_fs); |
| |
| Filesystem hfsplus_fs("hfsplus"); |
| hfsplus_fs.set_accepts_user_and_group_id(true); |
| hfsplus_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(hfsplus_fs); |
| |
| Filesystem iso9660_fs("iso9660"); |
| iso9660_fs.set_is_mounted_read_only(true); |
| iso9660_fs.set_accepts_user_and_group_id(true); |
| iso9660_fs.AddExtraMountOption(MountOptions::kOptionUtf8); |
| RegisterFilesystem(iso9660_fs); |
| |
| Filesystem udf_fs("udf"); |
| udf_fs.set_is_mounted_read_only(true); |
| udf_fs.set_accepts_user_and_group_id(true); |
| udf_fs.AddExtraMountOption(MountOptions::kOptionUtf8); |
| RegisterFilesystem(udf_fs); |
| |
| Filesystem ext2_fs("ext2"); |
| ext2_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(ext2_fs); |
| |
| Filesystem ext3_fs("ext3"); |
| ext3_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(ext3_fs); |
| |
| Filesystem ext4_fs("ext4"); |
| ext4_fs.AddExtraMountOption(MountOptions::kOptionDirSync); |
| RegisterFilesystem(ext4_fs); |
| } |
| |
| void DiskManager::RegisterFilesystem(const Filesystem& filesystem) { |
| filesystems_.insert(std::make_pair(filesystem.type(), filesystem)); |
| } |
| |
| Mounter* DiskManager::CreateMounter(const Disk& disk, |
| const Filesystem& filesystem, |
| const string& target_path, |
| const vector<string>& options) const { |
| const vector<string>& extra_options = filesystem.extra_mount_options(); |
| vector<string> extended_options; |
| extended_options.reserve(options.size() + extra_options.size()); |
| extended_options.assign(options.begin(), options.end()); |
| extended_options.insert(extended_options.end(), |
| extra_options.begin(), extra_options.end()); |
| |
| string default_user_id, default_group_id; |
| bool set_user_and_group_id = filesystem.accepts_user_and_group_id(); |
| if (set_user_and_group_id) { |
| default_user_id = base::StringPrintf("%d", platform()->mount_user_id()); |
| default_group_id = base::StringPrintf("%d", platform()->mount_group_id()); |
| } |
| |
| MountOptions mount_options; |
| mount_options.Initialize(extended_options, set_user_and_group_id, |
| default_user_id, default_group_id); |
| |
| if (filesystem.is_mounted_read_only() || |
| disk.is_read_only() || disk.is_optical_disk()) { |
| mount_options.SetReadOnlyOption(); |
| } |
| |
| const string& mounter_type = filesystem.mounter_type(); |
| if (mounter_type == SystemMounter::kMounterType) |
| return new(std::nothrow) SystemMounter(disk.device_file(), target_path, |
| filesystem.mount_type(), |
| mount_options); |
| |
| if (mounter_type == ExternalMounter::kMounterType) |
| return new(std::nothrow) ExternalMounter(disk.device_file(), target_path, |
| filesystem.mount_type(), |
| mount_options); |
| |
| if (mounter_type == ExFATMounter::kMounterType) |
| return new(std::nothrow) ExFATMounter(disk.device_file(), target_path, |
| filesystem.mount_type(), |
| mount_options, platform()); |
| |
| if (mounter_type == NTFSMounter::kMounterType) |
| return new(std::nothrow) NTFSMounter(disk.device_file(), target_path, |
| filesystem.mount_type(), |
| mount_options, platform()); |
| |
| LOG(FATAL) << "Invalid mounter type '" << mounter_type << "'"; |
| return nullptr; |
| } |
| |
| bool DiskManager::CanMount(const string& source_path) const { |
| // The following paths can be mounted: |
| // /sys/... |
| // /devices/... |
| // /dev/... |
| return base::StartsWith(source_path, "/sys/", base::CompareCase::SENSITIVE) || |
| base::StartsWith(source_path, "/devices/", |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(source_path, "/dev/", base::CompareCase::SENSITIVE); |
| } |
| |
| MountErrorType DiskManager::DoMount(const string& source_path, |
| const string& filesystem_type, |
| const vector<string>& options, |
| const string& mount_path, |
| MountOptions* applied_options) { |
| CHECK(!source_path.empty()) << "Invalid source path argument"; |
| CHECK(!mount_path.empty()) << "Invalid mount path argument"; |
| |
| Disk disk; |
| if (!GetDiskByDevicePath(source_path, &disk)) { |
| LOG(ERROR) << "'" << source_path << "' is not a valid device."; |
| return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| } |
| |
| const string& device_file = disk.device_file(); |
| if (device_file.empty()) { |
| LOG(ERROR) << "'" << source_path << "' does not have a device file"; |
| return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| } |
| |
| string device_filesystem_type = filesystem_type.empty() ? |
| disk.filesystem_type() : filesystem_type; |
| metrics()->RecordDeviceMediaType(disk.media_type()); |
| metrics()->RecordFilesystemType(device_filesystem_type); |
| if (device_filesystem_type.empty()) { |
| LOG(ERROR) << "Failed to determine the file system type of device '" |
| << source_path << "'"; |
| return MOUNT_ERROR_UNKNOWN_FILESYSTEM; |
| } |
| |
| const Filesystem* filesystem = GetFilesystem(device_filesystem_type); |
| if (filesystem == nullptr) { |
| LOG(ERROR) << "File system type '" << device_filesystem_type |
| << "' on device '" << source_path << "' is not supported"; |
| return MOUNT_ERROR_UNSUPPORTED_FILESYSTEM; |
| } |
| |
| unique_ptr<Mounter> mounter( |
| CreateMounter(disk, *filesystem, mount_path, options)); |
| CHECK(mounter) << "Failed to create a mounter"; |
| |
| MountErrorType error_type = mounter->Mount(); |
| if (error_type == MOUNT_ERROR_NONE) { |
| ScheduleEjectOnUnmount(mount_path, disk); |
| } |
| *applied_options = mounter->mount_options(); |
| |
| return error_type; |
| } |
| |
| MountErrorType DiskManager::DoUnmount(const string& path, |
| const vector<string>& options) { |
| CHECK(!path.empty()) << "Invalid path argument"; |
| |
| // TODO(benchan): Deprecate "force" unmount options after updating the |
| // cros-disks client on the Chrome side. |
| int unmount_flags; |
| if (!ExtractUnmountOptions(options, &unmount_flags)) { |
| LOG(ERROR) << "Invalid unmount options"; |
| return MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS; |
| } |
| |
| // Temporarily filter out the force unmount flag set by Chrome. |
| // TODO(benchan): Remove this after the force unmount option is deprecated. |
| unmount_flags &= ~MNT_FORCE; |
| |
| // The USB or SD drive on some system may be powered off after the system |
| // goes into the S3 suspend state. To avoid leaving a mount point in a stale |
| // state while its associated physical drive is gone, the cros-disks client |
| // on the Chrome side unmounts all the mount points it manages before the |
| // system goes into suspend. However, an ongoing filesystem access may keep a |
| // mount point busy, which is beyond the control of Chrome or cros-disks. We |
| // used to force umount the mount points but that has become undesirable. For |
| // instance, when force unmounting a mount point backed by a FUSE process, |
| // umount2() reports an error and the mount point is left in a half-broken |
| // state. Lazy unmount is preferred over force unmount under such condition |
| // (although it still doesn't necessarily guarantee a clean unmount if the |
| // filesystem access doesn't finish before the USB or SD drive is powered |
| // off). To better handle this kind of situation, we first try performing a |
| // normal unmount. If that fails with errno == EBUSY, we retry with a lazy |
| // unmount before giving up and reporting an error. |
| bool unmount_failed = (umount2(path.c_str(), unmount_flags) != 0); |
| if (unmount_failed && errno == EBUSY) { |
| LOG(ERROR) << "Failed to unmount '" << path |
| << "' as it is busy; retry with lazy unmount"; |
| unmount_flags |= MNT_DETACH; |
| unmount_failed = (umount2(path.c_str(), unmount_flags) != 0); |
| } |
| if (unmount_failed) { |
| PLOG(ERROR) << "Failed to unmount '" << path << "'"; |
| // TODO(benchan): Extract error from low-level unmount operation. |
| return MOUNT_ERROR_UNKNOWN; |
| } |
| |
| EjectDeviceOfMountPath(path); |
| |
| return MOUNT_ERROR_NONE; |
| } |
| |
| string DiskManager::SuggestMountPath(const string& source_path) const { |
| Disk disk; |
| GetDiskByDevicePath(source_path, &disk); |
| // If GetDiskByDevicePath fails, disk.GetPresentationName() returns |
| // the fallback presentation name. |
| return string(mount_root()) + "/" + disk.GetPresentationName(); |
| } |
| |
| bool DiskManager::ShouldReserveMountPathOnError( |
| MountErrorType error_type) const { |
| return error_type == MOUNT_ERROR_UNKNOWN_FILESYSTEM || |
| error_type == MOUNT_ERROR_UNSUPPORTED_FILESYSTEM; |
| } |
| |
| bool DiskManager::ScheduleEjectOnUnmount(const string& mount_path, |
| const Disk& disk) { |
| if (!disk.is_optical_disk()) |
| return false; |
| |
| devices_to_eject_on_unmount_[mount_path] = disk.device_file(); |
| return true; |
| } |
| |
| bool DiskManager::EjectDeviceOfMountPath(const string& mount_path) { |
| map<string, string>::iterator device_iterator = |
| devices_to_eject_on_unmount_.find(mount_path); |
| if (device_iterator == devices_to_eject_on_unmount_.end()) |
| return false; |
| |
| string device_file = device_iterator->second; |
| devices_to_eject_on_unmount_.erase(device_iterator); |
| |
| if (!eject_device_on_unmount_) |
| return false; |
| |
| LOG(INFO) << "Eject device '" << device_file << "'."; |
| if (!device_ejector_->Eject(device_file)) { |
| LOG(WARNING) << "Failed to eject media from optical device '" |
| << device_file << "'."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DiskManager::UnmountAll() { |
| // UnmountAll() is called when a user session ends. We do not want to eject |
| // devices in that situation and thus set |eject_device_on_unmount_| to |
| // false temporarily to prevent devices from being ejected upon unmount. |
| eject_device_on_unmount_ = false; |
| bool all_unmounted = MountManager::UnmountAll(); |
| eject_device_on_unmount_ = true; |
| return all_unmounted; |
| } |
| |
| } // namespace cros_disks |