blob: 8e70eaae5c3d00c2a887a65068fe400b22b859c5 [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/udev_device.h"
#include <fcntl.h>
#include <linux/limits.h>
#include <stdlib.h>
#include <sys/statvfs.h>
#include <utility>
#include <base/bind.h>
#include <base/hash/sha1.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_piece.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <brillo/udev/udev_device.h>
#include <rootdev/rootdev.h>
#include "cros-disks/mount_info.h"
#include "cros-disks/usb_device_info.h"
namespace cros_disks {
namespace {
const char kNullDeviceFile[] = "/dev/null";
const char kAttributeBusNum[] = "busnum";
const char kAttributeDevNum[] = "devnum";
const char kAttributeIdProduct[] = "idProduct";
const char kAttributeIdVendor[] = "idVendor";
const char kAttributePartition[] = "partition";
const char kAttributeRange[] = "range";
const char kAttributeReadOnly[] = "ro";
const char kAttributeRemovable[] = "removable";
const char kAttributeSize[] = "size";
const char kPropertyBlkIdFilesystemType[] = "TYPE";
const char kPropertyBlkIdFilesystemLabel[] = "LABEL";
const char kPropertyBlkIdFilesystemUUID[] = "UUID";
const char kPropertyCDROM[] = "ID_CDROM";
const char kPropertyCDROMDVD[] = "ID_CDROM_DVD";
const char kPropertyCDROMMedia[] = "ID_CDROM_MEDIA";
const char kPropertyCDROMMediaTrackCountData[] =
"ID_CDROM_MEDIA_TRACK_COUNT_DATA";
const char kPropertyDeviceType[] = "DEVTYPE";
const char kPropertyDeviceTypeUSBDevice[] = "usb_device";
const char kPropertyFilesystemUsage[] = "ID_FS_USAGE";
const char kPropertyMistSupportedDevice[] = "MIST_SUPPORTED_DEVICE";
const char kPropertyMmcType[] = "MMC_TYPE";
const char kPropertyMmcTypeSd[] = "SD";
const char kPropertyModel[] = "ID_MODEL";
const char kPropertyPartitionEntryType[] = "ID_PART_ENTRY_TYPE";
const char kPropertyPartitionSize[] = "UDISKS_PARTITION_SIZE";
const char kPropertyPresentationHide[] = "UDISKS_PRESENTATION_HIDE";
const char kPropertyRotationRate[] = "ID_ATA_ROTATION_RATE_RPM";
const char kPropertySerial[] = "ID_SERIAL";
const char kSubsystemUsb[] = "usb";
const char kSubsystemMmc[] = "mmc";
const char kSubsystemNvme[] = "nvme";
const char kSubsystemScsi[] = "scsi";
const char kVirtualDevicePathPrefix[] = "/sys/devices/virtual/";
const char kLoopDevicePathPrefix[] = "/sys/devices/virtual/block/loop";
const char kUSBDeviceInfoFile[] = "/usr/share/cros-disks/usb-device-info";
const char kUSBIdentifierDatabase[] = "/usr/share/misc/usb.ids";
const char* const kPartitionTypesToHide[] = {
"c12a7328-f81f-11d2-ba4b-00a0c93ec93b", // EFI system partition
"fe3a2a5d-4f32-41a7-b725-accc3285a309", // Chrome OS kernel
"3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec", // Chrome OS root filesystem
"cab6e88e-abf3-4102-a07a-d4bb9be3c1d3", // Chrome OS firmware
"2e0a753d-9e48-43b0-8337-b15192cb1b5e", // Chrome OS reserved
};
} // namespace
UdevDevice::UdevDevice(std::unique_ptr<brillo::UdevDevice> dev)
: dev_(std::move(dev)), blkid_cache_(nullptr) {
CHECK(dev_) << "Invalid udev device";
}
UdevDevice::~UdevDevice() {
if (blkid_cache_) {
// It needs to call blkid_put_cache to deallocate the blkid cache.
blkid_put_cache(blkid_cache_);
}
}
// static
std::string UdevDevice::EnsureUTF8String(const std::string& str) {
return base::IsStringUTF8(str) ? str : "";
}
// static
bool UdevDevice::IsValueBooleanTrue(const char* value) {
return value && strcmp(value, "1") == 0;
}
std::string UdevDevice::GetAttribute(const char* key) const {
const char* value = dev_->GetSysAttributeValue(key);
return (value) ? value : "";
}
bool UdevDevice::IsAttributeTrue(const char* key) const {
const char* value = dev_->GetSysAttributeValue(key);
return IsValueBooleanTrue(value);
}
bool UdevDevice::HasAttribute(const char* key) const {
const char* value = dev_->GetSysAttributeValue(key);
return value != nullptr;
}
std::string UdevDevice::GetProperty(const char* key) const {
const char* value = dev_->GetPropertyValue(key);
return (value) ? value : "";
}
bool UdevDevice::IsPropertyTrue(const char* key) const {
const char* value = dev_->GetPropertyValue(key);
return IsValueBooleanTrue(value);
}
bool UdevDevice::HasProperty(const char* key) const {
const char* value = dev_->GetPropertyValue(key);
return value != nullptr;
}
std::string UdevDevice::GetPropertyFromBlkId(const char* key) {
std::string value;
const char* dev_file = dev_->GetDeviceNode();
if (dev_file) {
// No cache file is used as it should always query information from
// the device, i.e. setting cache file to /dev/null.
if (blkid_cache_ || blkid_get_cache(&blkid_cache_, kNullDeviceFile) == 0) {
blkid_dev dev = blkid_get_dev(blkid_cache_, dev_file, BLKID_DEV_NORMAL);
if (dev) {
char* tag_value = blkid_get_tag_value(blkid_cache_, key, dev_file);
if (tag_value) {
value = tag_value;
free(tag_value);
}
}
}
}
return value;
}
void UdevDevice::GetSizeInfo(uint64_t* total_size,
uint64_t* remaining_size) const {
static const int kSectorSize = 512;
uint64_t total = 0, remaining = 0;
// If the device is mounted, obtain the total and remaining size in bytes
// using statvfs.
std::vector<std::string> mount_paths = GetMountPaths();
if (!mount_paths.empty()) {
struct statvfs stat;
if (statvfs(mount_paths[0].c_str(), &stat) == 0) {
total = stat.f_blocks * stat.f_frsize;
remaining = stat.f_bfree * stat.f_frsize;
}
}
// If the UDISKS_PARTITION_SIZE property is set, use it as the total size
// instead. If the UDISKS_PARTITION_SIZE property is not set but sysfs
// provides a size value, which is the actual size in bytes divided by 512,
// use that as the total size instead.
const std::string partition_size = GetProperty(kPropertyPartitionSize);
int64_t size = 0;
if (!partition_size.empty()) {
base::StringToInt64(partition_size, &size);
total = size;
} else {
const std::string size_attr = GetAttribute(kAttributeSize);
if (!size_attr.empty()) {
base::StringToInt64(size_attr, &size);
total = size * kSectorSize;
}
}
if (total_size)
*total_size = total;
if (remaining_size)
*remaining_size = remaining;
}
size_t UdevDevice::GetPartitionCount() const {
size_t partition_count = 0;
const char* dev_file = dev_->GetDeviceNode();
if (dev_file) {
blkid_probe probe = blkid_new_probe_from_filename(dev_file);
if (probe) {
blkid_partlist partitions = blkid_probe_get_partitions(probe);
if (partitions) {
partition_count = blkid_partlist_numof_partitions(partitions);
}
blkid_free_probe(probe);
}
}
return partition_count;
}
DeviceMediaType UdevDevice::GetDeviceMediaType() const {
if (IsPropertyTrue(kPropertyCDROMDVD))
return DEVICE_MEDIA_DVD;
if (IsPropertyTrue(kPropertyCDROM))
return DEVICE_MEDIA_OPTICAL_DISC;
if (IsOnSdDevice())
return DEVICE_MEDIA_SD;
std::string vendor_id, product_id;
if (GetVendorAndProductId(&vendor_id, &product_id)) {
USBDeviceInfo info;
info.RetrieveFromFile(kUSBDeviceInfoFile);
return info.GetDeviceMediaType(vendor_id, product_id);
}
return DEVICE_MEDIA_UNKNOWN;
}
bool UdevDevice::EnumerateParentDevices(EnumerateCallback callback) const {
if (callback.Run(*dev_)) {
return true;
}
for (auto parent = dev_->GetParent(); parent; parent = parent->GetParent()) {
if (callback.Run(*parent)) {
return true;
}
}
return false;
}
bool UdevDevice::GetVendorAndProductId(std::string* vendor_id,
std::string* product_id) const {
// Search up the parent device tree to obtain the vendor and product ID
// of the first device with a device type "usb_device". Then look up the
// media type based on the vendor and product ID from a USB device info file.
return EnumerateParentDevices(base::BindRepeating(
[](std::string* vendor_id, std::string* product_id,
const brillo::UdevDevice& device) {
const char* device_type = device.GetPropertyValue(kPropertyDeviceType);
if (device_type &&
strcmp(device_type, kPropertyDeviceTypeUSBDevice) == 0) {
const char* vendor_id_attr =
device.GetSysAttributeValue(kAttributeIdVendor);
const char* product_id_attr =
device.GetSysAttributeValue(kAttributeIdProduct);
if (vendor_id_attr && product_id_attr) {
*vendor_id = vendor_id_attr;
*product_id = product_id_attr;
return true;
}
}
return false;
},
vendor_id, product_id));
}
void UdevDevice::GetBusAndDeviceNumber(int* bus_number,
int* device_number) const {
*bus_number = 0;
*device_number = 0;
EnumerateParentDevices(base::BindRepeating(
[](int* bus_number, int* device_number,
const brillo::UdevDevice& device) {
const char* bus_number_attr =
device.GetSysAttributeValue(kAttributeBusNum);
const char* device_number_attr =
device.GetSysAttributeValue(kAttributeDevNum);
if (bus_number_attr && device_number_attr) {
base::StringToInt(bus_number_attr, bus_number);
base::StringToInt(device_number_attr, device_number);
return true;
}
return false;
},
bus_number, device_number));
}
bool UdevDevice::IsMediaAvailable() const {
bool is_media_available = true;
if (IsAttributeTrue(kAttributeRemovable)) {
if (IsPropertyTrue(kPropertyCDROM)) {
is_media_available = IsPropertyTrue(kPropertyCDROMMedia);
} else {
const char* dev_file = dev_->GetDeviceNode();
if (dev_file) {
int fd = open(dev_file, O_RDONLY);
if (fd < 0) {
is_media_available = false;
} else {
close(fd);
}
}
}
}
return is_media_available;
}
bool UdevDevice::IsMobileBroadbandDevice() const {
// Check if a parent device, which belongs to the "usb" subsystem and has a
// device type "usb_device", has a property "MIST_SUPPORTED_DEVICE=1". If so,
// it is a mobile broadband device supported by mist.
std::unique_ptr<brillo::UdevDevice> parent =
dev_->GetParentWithSubsystemDeviceType(kSubsystemUsb,
kPropertyDeviceTypeUSBDevice);
if (!parent)
return false;
return UdevDevice(std::move(parent))
.IsPropertyTrue(kPropertyMistSupportedDevice);
}
bool UdevDevice::IsAutoMountable() const {
// TODO(benchan): Find a reliable way to detect if a device is a removable
// storage as the removable attribute in sysfs does not always tell the truth.
return !IsOnBootDevice() && !IsVirtual();
}
bool UdevDevice::IsHidden() {
if (IsPropertyTrue(kPropertyPresentationHide))
return true;
// Hide an optical disc without any data track.
// udev/cdrom_id only sets ID_CDROM_MEDIA_TRACK_COUNT_DATA when there is at
// least one data track.
if (IsPropertyTrue(kPropertyCDROM) &&
!HasProperty(kPropertyCDROMMediaTrackCountData)) {
return true;
}
// Hide a mobile broadband device, which may initially expose itself as a USB
// mass storage device and later be switched to a modem by mist.
if (IsMobileBroadbandDevice())
return true;
// Hide a device that is neither marked as a partition nor a filesystem,
// unless it has no valid partitions (e.g. the device is unformatted or
// corrupted). An unformatted or corrupted device is visible in the file
// the file browser so that we can provide a way to format it.
if (!HasAttribute(kAttributePartition) &&
!HasProperty(kPropertyFilesystemUsage) && (GetPartitionCount() > 0))
return true;
// Hide special partitions based on partition type.
std::string partition_type = GetProperty(kPropertyPartitionEntryType);
if (!partition_type.empty()) {
for (const char* partition_type_to_hide : kPartitionTypesToHide) {
if (partition_type == partition_type_to_hide)
return true;
}
}
return false;
}
bool UdevDevice::IsIgnored() const {
return IsVirtual() && !IsLoopDevice();
}
bool UdevDevice::IsOnBootDevice() const {
// Obtain the boot device path, e.g. /dev/sda
char boot_device_path[PATH_MAX];
if (rootdev(boot_device_path, sizeof(boot_device_path), true, true)) {
LOG(ERROR) << "Could not determine root device";
// Assume it is on the boot device when there is any uncertainty.
// This is to prevent a device, which is potentially on the boot device,
// from being auto mounted and exposed to users.
// TODO(benchan): Find a way to eliminate the uncertainty.
return true;
}
// Compare the device file path of the current device and all its parents
// with the boot device path. Any match indicates that the current device
// is on the boot device.
return EnumerateParentDevices(base::BindRepeating(
[](const char* boot_device_path, const brillo::UdevDevice& device) {
const char* dev_file = device.GetDeviceNode();
return (dev_file && strncmp(boot_device_path, dev_file, PATH_MAX) == 0);
},
boot_device_path));
}
bool UdevDevice::IsOnSdDevice() const {
return EnumerateParentDevices(
base::BindRepeating([](const brillo::UdevDevice& device) {
const char* mmc_type = device.GetPropertyValue(kPropertyMmcType);
return (mmc_type && strcmp(mmc_type, kPropertyMmcTypeSd) == 0);
}));
}
bool UdevDevice::IsOnRemovableDevice() const {
return EnumerateParentDevices(
base::BindRepeating([](const brillo::UdevDevice& device) {
const char* value = device.GetSysAttributeValue(kAttributeRemovable);
return (value && IsValueBooleanTrue(value));
}));
}
bool UdevDevice::IsVirtual() const {
const char* sys_path = dev_->GetSysPath();
if (sys_path) {
return base::StartsWith(sys_path, kVirtualDevicePathPrefix,
base::CompareCase::SENSITIVE);
}
// To be safe, mark it as virtual device if sys path cannot be determined.
return true;
}
bool UdevDevice::IsLoopDevice() const {
const char* sys_path = dev_->GetSysPath();
if (sys_path) {
return base::StartsWith(sys_path, kLoopDevicePathPrefix,
base::CompareCase::SENSITIVE);
}
return false;
}
std::string UdevDevice::NativePath() const {
const char* sys_path = dev_->GetSysPath();
return sys_path ? sys_path : "";
}
std::string UdevDevice::StorageDevicePath() const {
std::string path;
EnumerateParentDevices(base::BindRepeating(
[](std::string* path, const brillo::UdevDevice& device) {
base::StringPiece subsystem(device.GetSubsystem());
if (subsystem == kSubsystemMmc || subsystem == kSubsystemNvme ||
subsystem == kSubsystemScsi) {
*path = device.GetSysPath();
return true;
}
return false;
},
&path));
return path;
}
std::vector<std::string> UdevDevice::GetMountPaths() const {
const char* device_path = dev_->GetDeviceNode();
if (device_path) {
return GetMountPaths(device_path);
}
return std::vector<std::string>();
}
std::vector<std::string> UdevDevice::GetMountPaths(
const std::string& device_path) {
MountInfo mount_info;
if (mount_info.RetrieveFromCurrentProcess()) {
return mount_info.GetMountPaths(device_path);
}
return std::vector<std::string>();
}
Disk UdevDevice::ToDisk() {
Disk disk;
disk.is_auto_mountable = IsAutoMountable();
disk.is_read_only = IsAttributeTrue(kAttributeReadOnly);
disk.is_drive = HasAttribute(kAttributeRange);
disk.is_rotational = HasProperty(kPropertyRotationRate);
disk.is_hidden = IsHidden();
disk.is_media_available = IsMediaAvailable();
disk.is_on_boot_device = IsOnBootDevice();
disk.is_on_removable_device = IsOnRemovableDevice();
disk.is_virtual = IsVirtual();
disk.media_type = GetDeviceMediaType();
disk.filesystem_type = GetPropertyFromBlkId(kPropertyBlkIdFilesystemType);
disk.native_path = NativePath();
disk.storage_device_path = StorageDevicePath();
// Drive model and filesystem label may not be UTF-8 encoded, so we
// need to ensure that they are either set to a valid UTF-8 string or
// an empty string before later passed to a DBus message iterator.
disk.drive_model = EnsureUTF8String(GetProperty(kPropertyModel));
disk.label =
EnsureUTF8String(GetPropertyFromBlkId(kPropertyBlkIdFilesystemLabel));
if (GetVendorAndProductId(&disk.vendor_id, &disk.product_id)) {
USBDeviceInfo info;
info.GetVendorAndProductName(kUSBIdentifierDatabase, disk.vendor_id,
disk.product_id, &disk.vendor_name,
&disk.product_name);
}
GetBusAndDeviceNumber(&disk.bus_number, &disk.device_number);
// TODO(benchan): Add a proper unit test when fixing crbug.com/221380.
std::string uuid_hash = base::SHA1HashString(
disk.vendor_id + disk.product_id + GetProperty(kPropertySerial) +
GetPropertyFromBlkId(kPropertyBlkIdFilesystemUUID));
disk.uuid = base::HexEncode(uuid_hash.data(), uuid_hash.size());
const char* dev_file = dev_->GetDeviceNode();
if (dev_file)
disk.device_file = dev_file;
disk.mount_paths = GetMountPaths();
GetSizeInfo(&disk.device_capacity, &disk.bytes_remaining);
return disk;
}
} // namespace cros_disks