blob: 2748cdb63e6982b97aec1ebd9574634835b62083 [file] [log] [blame]
// Copyright (c) 2011 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 <base/logging.h>
#include <base/memory/scoped_ptr.h>
#include <base/string_util.h>
#include "cros-disks/disk.h"
#include "cros-disks/external-mounter.h"
#include "cros-disks/filesystem.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::vector;
namespace cros_disks {
static const char kBlockSubsystem[] = "block";
static const char kScsiSubsystem[] = "scsi";
static const char kScsiDevice[] = "scsi_device";
static const char kUdevAddAction[] = "add";
static const char kUdevChangeAction[] = "change";
static const char kUdevRemoveAction[] = "remove";
DiskManager::DiskManager(const string& mount_root, Platform* platform)
: MountManager(mount_root, platform),
udev_(udev_new()),
udev_monitor_fd_(0),
blkid_cache_(NULL) {
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, NULL);
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() {
udev_monitor_unref(udev_monitor_);
udev_unref(udev_);
}
bool DiskManager::Initialize() {
RegisterDefaultFilesystems();
return MountManager::Initialize();
}
vector<Disk> DiskManager::EnumerateDisks() const {
vector<Disk> disks;
struct udev_enumerate *enumerate = udev_enumerate_new(udev_);
udev_enumerate_add_match_subsystem(enumerate, kBlockSubsystem);
udev_enumerate_scan_devices(enumerate);
struct 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 == NULL) 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: ";
struct 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;
}
disks.push_back(UdevDevice(dev).ToDisk());
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
return disks;
}
DeviceEvent::EventType DiskManager::ProcessBlockDeviceEvent(
const UdevDevice& device, const char *action) {
DeviceEvent::EventType event_type = DeviceEvent::kIgnored;
string device_path = device.NativePath();
bool disk_added = false;
bool 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("DISK_EJECT_REQUEST")) {
disk_removed = true;
} else if (device.IsPropertyTrue("DISK_MEDIA_CHANGE")) {
if (device.IsMediaAvailable()) {
disk_added = true;
}
}
}
if (disk_added) {
set<string>::const_iterator disk_iter =
disks_detected_.find(device_path);
if (disk_iter != disks_detected_.end()) {
// Disk already exists, so remove it and then add it again.
event_type = DeviceEvent::kDiskAddedAfterRemoved;
} else {
disks_detected_.insert(device_path);
event_type = DeviceEvent::kDiskAdded;
}
} else if (disk_removed) {
disks_detected_.erase(device_path);
event_type = DeviceEvent::kDiskRemoved;
}
return event_type;
}
DeviceEvent::EventType DiskManager::ProcessScsiDeviceEvent(
const UdevDevice& device, const char *action) {
DeviceEvent::EventType event_type = DeviceEvent::kIgnored;
string device_path = device.NativePath();
set<string>::const_iterator device_iter;
if (strcmp(action, kUdevAddAction) == 0) {
device_iter = devices_detected_.find(device_path);
if (device_iter != devices_detected_.end()) {
event_type = DeviceEvent::kDeviceScanned;
} else {
devices_detected_.insert(device_path);
event_type = DeviceEvent::kDeviceAdded;
}
} else if (strcmp(action, kUdevRemoveAction) == 0) {
device_iter = devices_detected_.find(device_path);
if (device_iter != devices_detected_.end()) {
devices_detected_.erase(device_iter);
event_type = DeviceEvent::kDeviceRemoved;
}
}
return event_type;
}
bool DiskManager::GetDeviceEvent(DeviceEvent* event) {
struct udev_device *dev = udev_monitor_receive_device(udev_monitor_);
CHECK(dev) << "Unknown udev device";
CHECK(event) << "Invalid device event object";
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;
}
event->device_path = sys_path;
event->event_type = DeviceEvent::kIgnored;
// Ignore change events from boot devices, virtual devices,
// or Chrome OS specific partitions, which should not be sent
// to the Chrome browser.
UdevDevice udev(dev);
if (udev.IsAutoMountable()) {
// udev_monitor_ only monitors block or scsi device changes, so
// subsystem is either "block" or "scsi".
if (strcmp(subsystem, kBlockSubsystem) == 0) {
event->event_type = ProcessBlockDeviceEvent(udev, action);
} else { // strcmp(subsystem, kScsiSubsystem) == 0
event->event_type = ProcessScsiDeviceEvent(udev, action);
}
}
udev_device_unref(dev);
return true;
}
bool DiskManager::GetDiskByDevicePath(const string& device_path,
Disk *disk) const {
if (device_path.empty())
return false;
bool is_sys_path = StartsWithASCII(device_path, "/sys/", true);
bool is_dev_path = StartsWithASCII(device_path, "/devices/", true);
bool is_dev_file = StartsWithASCII(device_path, "/dev/", true);
bool disk_found = false;
struct udev_enumerate *enumerate = udev_enumerate_new(udev_);
udev_enumerate_add_match_subsystem(enumerate, kBlockSubsystem);
udev_enumerate_scan_devices(enumerate);
struct 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 *sys_path = udev_list_entry_get_name(device_list_entry);
udev_device *dev = udev_device_new_from_syspath(udev_, sys_path);
if (dev == NULL) continue;
const char *dev_path = udev_device_get_devpath(dev);
const char *dev_file = udev_device_get_devnode(dev);
if ((is_sys_path && device_path == sys_path) ||
(is_dev_path && device_path == dev_path) ||
(is_dev_file && dev_file && device_path == dev_file)) {
disk_found = true;
if (disk)
*disk = UdevDevice(dev).ToDisk();
}
udev_device_unref(dev);
if (disk_found)
break;
}
udev_enumerate_unref(enumerate);
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 NULL;
if (!platform_->experimental_features_enabled() &&
filesystem_iterator->second.is_experimental())
return NULL;
return &filesystem_iterator->second;
}
string DiskManager::GetFilesystemTypeOfDevice(const string& device_path) {
string filesystem_type;
if (blkid_cache_ != NULL || blkid_get_cache(&blkid_cache_, NULL) == 0) {
blkid_dev dev =
blkid_get_dev(blkid_cache_, device_path.c_str(), BLKID_DEV_NORMAL);
if (dev) {
const char *type = blkid_get_tag_value(blkid_cache_, "TYPE",
device_path.c_str());
if (type) {
filesystem_type = type;
}
}
}
return filesystem_type;
}
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("shortname=mixed");
vfat_fs.AddExtraMountOption("utf8");
RegisterFilesystem(vfat_fs);
Filesystem ntfs_fs("ntfs");
ntfs_fs.set_mounter_type(NTFSMounter::kMounterType);
ntfs_fs.set_is_experimental(true);
ntfs_fs.set_is_mounted_read_only(true);
ntfs_fs.set_accepts_user_and_group_id(true);
RegisterFilesystem(ntfs_fs);
Filesystem hfsplus_fs("hfsplus");
hfsplus_fs.set_accepts_user_and_group_id(true);
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("utf8");
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("utf8");
RegisterFilesystem(udf_fs);
Filesystem ext2_fs("ext2");
RegisterFilesystem(ext2_fs);
Filesystem ext3_fs("ext3");
RegisterFilesystem(ext3_fs);
Filesystem ext4_fs("ext4");
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 == 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 NULL;
}
bool DiskManager::CanMount(const string& source_path) const {
// The following paths can be mounted:
// /sys/...
// /devices/...
// /dev/...
return StartsWithASCII(source_path, "/sys/", true) ||
StartsWithASCII(source_path, "/devices/", true) ||
StartsWithASCII(source_path, "/dev/", true);
}
MountErrorType DiskManager::DoMount(const string& source_path,
const string& filesystem_type,
const vector<string>& options,
const string& mount_path) {
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 kMountErrorInvalidDevicePath;
}
const string& device_file = disk.device_file();
if (device_file.empty()) {
LOG(ERROR) << "'" << source_path << "' does not have a device file";
return kMountErrorInvalidDevicePath;
}
// If no explicit filesystem type is given, try to determine it via blkid.
string device_filesystem_type = filesystem_type.empty() ?
GetFilesystemTypeOfDevice(device_file) : filesystem_type;
if (device_filesystem_type.empty()) {
LOG(ERROR) << "Failed to determine the file system type of device '"
<< source_path << "'";
return kMountErrorUnknownFilesystem;
}
const Filesystem* filesystem = GetFilesystem(device_filesystem_type);
if (filesystem == NULL) {
LOG(ERROR) << "File system type '" << device_filesystem_type
<< "' on device '" << source_path << "' is not supported";
return kMountErrorUnsupportedFilesystem;
}
scoped_ptr<Mounter> mounter(CreateMounter(disk, *filesystem, mount_path,
options));
CHECK(mounter.get() != NULL) << "Failed to create a mounter";
return mounter->Mount();
}
MountErrorType DiskManager::DoUnmount(const string& path,
const vector<string>& options) {
CHECK(!path.empty()) << "Invalid path argument";
int unmount_flags;
if (!ExtractUnmountOptions(options, &unmount_flags)) {
LOG(ERROR) << "Invalid unmount options";
return kMountErrorInvalidUnmountOptions;
}
if (umount2(path.c_str(), unmount_flags) != 0) {
PLOG(ERROR) << "Failed to unmount '" << path << "'";
// TODO(benchan): Extract error from low-level unmount operation.
return kMountErrorUnknown;
}
return kMountErrorNone;
}
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();
}
} // namespace cros_disks