| // 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 <errno.h> |
| #include <inttypes.h> |
| #include <libudev.h> |
| #include <string.h> |
| #include <sys/mount.h> |
| #include <time.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_piece.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/time/time.h> |
| |
| #include "cros-disks/device_ejector.h" |
| #include "cros-disks/disk_monitor.h" |
| #include "cros-disks/exfat_mounter.h" |
| #include "cros-disks/filesystem.h" |
| #include "cros-disks/metrics.h" |
| #include "cros-disks/mount_options.h" |
| #include "cros-disks/mount_point.h" |
| #include "cros-disks/ntfs_mounter.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/quote.h" |
| #include "cros-disks/system_mounter.h" |
| |
| namespace cros_disks { |
| |
| class DiskManager::EjectingMountPoint : public MountPoint { |
| public: |
| EjectingMountPoint(std::unique_ptr<MountPoint> mount_point, |
| DiskManager* disk_manager, |
| const std::string& device_file) |
| : MountPoint(mount_point->path()), |
| mount_point_(std::move(mount_point)), |
| disk_manager_(disk_manager), |
| device_file_(device_file) { |
| DCHECK(mount_point_); |
| DCHECK(disk_manager_); |
| DCHECK(!device_file_.empty()); |
| } |
| |
| ~EjectingMountPoint() override { DestructorUnmount(); } |
| |
| void Release() override { |
| MountPoint::Release(); |
| mount_point_->Release(); |
| } |
| |
| protected: |
| MountErrorType UnmountImpl() override { |
| MountErrorType error = mount_point_->Unmount(); |
| if (error == MOUNT_ERROR_NONE) { |
| bool success = disk_manager_->EjectDevice(device_file_); |
| LOG_IF(ERROR, !success) |
| << "Unable to eject device " << quote(device_file_) |
| << " for mount path " << quote(path()); |
| } |
| return error; |
| } |
| |
| private: |
| const std::unique_ptr<MountPoint> mount_point_; |
| DiskManager* const disk_manager_; |
| const std::string device_file_; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(EjectingMountPoint); |
| }; |
| |
| DiskManager::DiskManager(const std::string& mount_root, |
| Platform* platform, |
| Metrics* metrics, |
| brillo::ProcessReaper* process_reaper, |
| DiskMonitor* disk_monitor, |
| DeviceEjector* device_ejector) |
| : MountManager(mount_root, platform, metrics, process_reaper), |
| disk_monitor_(disk_monitor), |
| device_ejector_(device_ejector), |
| eject_device_on_unmount_(true) {} |
| |
| DiskManager::~DiskManager() { |
| UnmountAll(); |
| } |
| |
| bool DiskManager::Initialize() { |
| RegisterDefaultFilesystems(); |
| return MountManager::Initialize(); |
| } |
| |
| const Filesystem* DiskManager::GetFilesystem( |
| const std::string& filesystem_type) const { |
| std::map<std::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.accepts_user_and_group_id = true; |
| vfat_fs.extra_mount_options = {MountOptions::kOptionDirSync, |
| MountOptions::kOptionFlush, "shortname=mixed", |
| MountOptions::kOptionUtf8}; |
| RegisterFilesystem(vfat_fs); |
| |
| Filesystem exfat_fs("exfat"); |
| exfat_fs.mounter_type = ExFATMounter::kMounterType; |
| exfat_fs.accepts_user_and_group_id = true; |
| exfat_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(exfat_fs); |
| |
| Filesystem ntfs_fs("ntfs"); |
| ntfs_fs.mounter_type = NTFSMounter::kMounterType; |
| ntfs_fs.accepts_user_and_group_id = true; |
| ntfs_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(ntfs_fs); |
| |
| Filesystem hfsplus_fs("hfsplus"); |
| hfsplus_fs.accepts_user_and_group_id = true; |
| hfsplus_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(hfsplus_fs); |
| |
| Filesystem iso9660_fs("iso9660"); |
| iso9660_fs.is_mounted_read_only = true; |
| iso9660_fs.accepts_user_and_group_id = true; |
| iso9660_fs.extra_mount_options = {MountOptions::kOptionUtf8}; |
| RegisterFilesystem(iso9660_fs); |
| |
| Filesystem udf_fs("udf"); |
| udf_fs.is_mounted_read_only = true; |
| udf_fs.accepts_user_and_group_id = true; |
| udf_fs.extra_mount_options = {MountOptions::kOptionUtf8}; |
| RegisterFilesystem(udf_fs); |
| |
| Filesystem ext2_fs("ext2"); |
| ext2_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(ext2_fs); |
| |
| Filesystem ext3_fs("ext3"); |
| ext3_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(ext3_fs); |
| |
| Filesystem ext4_fs("ext4"); |
| ext4_fs.extra_mount_options = {MountOptions::kOptionDirSync}; |
| RegisterFilesystem(ext4_fs); |
| } |
| |
| void DiskManager::RegisterFilesystem(const Filesystem& filesystem) { |
| filesystems_.emplace(filesystem.type, filesystem); |
| } |
| |
| std::unique_ptr<MounterCompat> DiskManager::CreateMounter( |
| const Disk& disk, |
| const Filesystem& filesystem, |
| const std::string& target_path, |
| const std::vector<std::string>& options) const { |
| const std::vector<std::string>& extra_options = |
| filesystem.extra_mount_options; |
| std::vector<std::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()); |
| |
| if (filesystem.type == "vfat") { |
| // FAT32 stores times as local time instead of UTC. By default, the vfat |
| // kernel module will use the kernel's time zone, which is set using |
| // settimeofday(), to interpret time stamps as local time. However, time |
| // zones are complicated and generally a user-space concern in modern Linux. |
| // The man page for {get,set}timeofday comments that the |timezone| fields |
| // of these functions is obsolete. Chrome OS doesn't appear to set these |
| // either. Instead, we pass the time offset explicitly as a mount option so |
| // that the user can see file time stamps as local time. This mirrors what |
| // the user will see in other operating systems. |
| // TODO(amistry): Consider moving this code into a FATMounter. |
| time_t now = base::Time::Now().ToTimeT(); |
| struct tm timestruct; |
| // The time zone might have changed since cros-disks was started. Force a |
| // re-read of the time zone to ensure the local time is what the user |
| // expects. |
| tzset(); |
| localtime_r(&now, ×truct); |
| // tm_gmtoff is a glibc extension. |
| int64_t offset_minutes = static_cast<int64_t>(timestruct.tm_gmtoff) / 60; |
| extended_options.push_back( |
| base::StringPrintf("time_offset=%" PRId64, offset_minutes)); |
| } |
| |
| std::string default_user_id, default_group_id; |
| if (filesystem.accepts_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.WhitelistOption(MountOptions::kOptionNoSymFollow); |
| mount_options.Initialize(extended_options, |
| filesystem.accepts_user_and_group_id, |
| default_user_id, default_group_id); |
| |
| if (filesystem.is_mounted_read_only || disk.is_read_only || |
| disk.IsOpticalDisk()) { |
| mount_options.SetReadOnlyOption(); |
| } |
| |
| if (filesystem.mounter_type.empty()) |
| return std::make_unique<MounterCompat>( |
| std::make_unique<SystemMounter>(filesystem.mount_type, platform()), |
| mount_options); |
| |
| if (filesystem.mounter_type == ExFATMounter::kMounterType) |
| return std::make_unique<ExFATMounter>(filesystem.mount_type, mount_options, |
| platform(), process_reaper()); |
| |
| if (filesystem.mounter_type == NTFSMounter::kMounterType) |
| return std::make_unique<NTFSMounter>(filesystem.mount_type, mount_options, |
| platform(), process_reaper()); |
| |
| LOG(FATAL) << "Invalid mounter type " << quote(filesystem.mounter_type); |
| return nullptr; |
| } |
| |
| bool DiskManager::CanMount(const std::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); |
| } |
| |
| std::unique_ptr<MountPoint> DiskManager::DoMount( |
| const std::string& source_path, |
| const std::string& filesystem_type, |
| const std::vector<std::string>& options, |
| const base::FilePath& mount_path, |
| MountOptions* applied_options, |
| MountErrorType* error) { |
| CHECK(!source_path.empty()) << "Invalid source path argument"; |
| CHECK(!mount_path.empty()) << "Invalid mount path argument"; |
| |
| Disk disk; |
| if (!disk_monitor_->GetDiskByDevicePath(base::FilePath(source_path), &disk)) { |
| LOG(ERROR) << quote(source_path) << " is not a valid device"; |
| *error = MOUNT_ERROR_INVALID_DEVICE_PATH; |
| return nullptr; |
| } |
| |
| if (disk.is_on_boot_device) { |
| LOG(ERROR) << quote(source_path) |
| << " is on boot device and not allowed to mount"; |
| *error = MOUNT_ERROR_INVALID_DEVICE_PATH; |
| return nullptr; |
| } |
| |
| if (disk.device_file.empty()) { |
| LOG(ERROR) << quote(source_path) << " does not have a device file"; |
| *error = MOUNT_ERROR_INVALID_DEVICE_PATH; |
| return nullptr; |
| } |
| |
| std::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) << "Cannot determine the file system type of device " |
| << quote(source_path); |
| *error = MOUNT_ERROR_UNKNOWN_FILESYSTEM; |
| return nullptr; |
| } |
| |
| const Filesystem* filesystem = GetFilesystem(device_filesystem_type); |
| if (filesystem == nullptr) { |
| LOG(ERROR) << "File system type " << quote(device_filesystem_type) |
| << " on device " << quote(source_path) << " is not supported"; |
| *error = MOUNT_ERROR_UNSUPPORTED_FILESYSTEM; |
| return nullptr; |
| } |
| |
| std::unique_ptr<MounterCompat> mounter( |
| CreateMounter(disk, *filesystem, mount_path.value(), options)); |
| CHECK(mounter) << "Failed to create a mounter"; |
| |
| std::unique_ptr<MountPoint> mount_point = mounter->Mount( |
| disk.device_file, mount_path, mounter->mount_options().options(), error); |
| if (*error != MOUNT_ERROR_NONE) { |
| DCHECK(!mount_point); |
| // Try to mount the filesystem read-only if mounting it read-write failed. |
| if (!mounter->mount_options().IsReadOnlyOptionSet()) { |
| LOG(INFO) << "Trying to mount " << quote(source_path) << " read-only"; |
| std::vector<std::string> ro_options = options; |
| ro_options.push_back("ro"); |
| mounter = |
| CreateMounter(disk, *filesystem, mount_path.value(), ro_options); |
| CHECK(mounter) << "Failed to create a 'ro' mounter"; |
| mount_point = mounter->Mount(disk.device_file, mount_path, |
| mounter->mount_options().options(), error); |
| } |
| } |
| |
| if (*error != MOUNT_ERROR_NONE) { |
| DCHECK(!mount_point); |
| return nullptr; |
| } |
| |
| *applied_options = mounter->mount_options(); |
| return MaybeWrapMountPointForEject(std::move(mount_point), disk); |
| } |
| |
| std::string DiskManager::SuggestMountPath( |
| const std::string& source_path) const { |
| Disk disk; |
| disk_monitor_->GetDiskByDevicePath(base::FilePath(source_path), &disk); |
| // If GetDiskByDevicePath fails, disk.GetPresentationName() returns |
| // the fallback presentation name. |
| return mount_root().Append(disk.GetPresentationName()).value(); |
| } |
| |
| bool DiskManager::ShouldReserveMountPathOnError( |
| MountErrorType error_type) const { |
| return error_type == MOUNT_ERROR_UNKNOWN_FILESYSTEM || |
| error_type == MOUNT_ERROR_UNSUPPORTED_FILESYSTEM; |
| } |
| |
| bool DiskManager::EjectDevice(const std::string& device_file) { |
| if (eject_device_on_unmount_) { |
| return device_ejector_->Eject(device_file); |
| } |
| return true; |
| } |
| |
| std::unique_ptr<MountPoint> DiskManager::MaybeWrapMountPointForEject( |
| std::unique_ptr<MountPoint> mount_point, const Disk& disk) { |
| if (!disk.IsOpticalDisk()) { |
| return mount_point; |
| } |
| return std::make_unique<EjectingMountPoint>(std::move(mount_point), this, |
| disk.device_file); |
| } |
| |
| 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 |