blob: f8932cb546c6afaba6edf695b9c7c2777e039f4e [file] [log] [blame]
// Copyright 2019 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 "diagnostics/common/disk_utils.h"
#include <fcntl.h>
#include <libudev.h>
#include <memory>
#include <string>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <unordered_map>
#include <utility>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/values.h>
namespace diagnostics {
namespace disk_utils {
using NonRemovableBlockDeviceInfo =
::chromeos::cros_healthd::mojom::NonRemovableBlockDeviceInfo;
using NonRemovableBlockDeviceInfoPtr =
::chromeos::cros_healthd::mojom::NonRemovableBlockDeviceInfoPtr;
namespace {
// Reads the contents of |filename| within |directory| into |out|, trimming
// trailing whitespace. Returns true on success.
bool ReadAndTrimString(const base::FilePath& directory,
const std::string& filename,
std::string* out) {
if (!base::ReadFileToString(directory.Append(filename), out))
return false;
base::TrimWhitespaceASCII(*out, base::TRIM_TRAILING, out);
return true;
}
// Reads a 64-bit decimal-encoded integer value from a text file and returns
// true on success.
bool ReadInt64(const base::FilePath& directory,
const std::string& filename,
int64_t* out) {
std::string buffer;
if (!ReadAndTrimString(directory, filename, &buffer))
return false;
return base::StringToInt64(buffer, out);
}
// Reads a 64-bit hex-encoded unsigned integer value from a text file and
// returns true on success.
bool ReadHexUInt64(const base::FilePath& directory,
const std::string& filename,
uint64_t* out) {
std::string buffer;
if (!ReadAndTrimString(directory, filename, &buffer))
return false;
return base::HexStringToUInt64(buffer, out);
}
// Reads a 32-bit hex-encoded unsigned integer value from a file and returns
// true on success.
bool ReadHexUint32(const base::FilePath& directory,
const std::string& filename,
uint32_t* out) {
std::string buffer;
if (!ReadAndTrimString(directory, filename, &buffer))
return false;
return base::HexStringToUInt(buffer, out);
}
// Look through all the block devices and find the ones that are explicitly
// non-removable.
std::vector<base::FilePath> GetNonRemovableBlockDevices(
const base::FilePath& root) {
std::vector<base::FilePath> res;
const base::FilePath storage_dir_path(root.Append("sys/class/block/"));
base::FileEnumerator storage_dir_it(storage_dir_path, true,
base::FileEnumerator::SHOW_SYM_LINKS |
base::FileEnumerator::FILES |
base::FileEnumerator::DIRECTORIES);
while (true) {
const auto storage_path = storage_dir_it.Next();
if (storage_path.empty())
break;
// Skip Loobpack or dm-verity device
if (base::StartsWith(storage_path.BaseName().value(), "loop",
base::CompareCase::SENSITIVE) ||
base::StartsWith(storage_path.BaseName().value(), "dm-",
base::CompareCase::SENSITIVE)) {
continue;
}
// Only return non-removable devices
int64_t removable = 0;
if (!ReadInt64(storage_path, "removable", &removable) || removable) {
VLOG(1) << "Storage device " << storage_path.value()
<< " does not specify the removable property or is removable.";
continue;
}
res.push_back(storage_path);
}
return res;
}
// Gets the size of the drive in bytes, given the /dev node.
bool GetDriveDeviceSizeInBytes(const base::FilePath& dev_path,
uint64_t* size_in_bytes) {
int fd = open(dev_path.value().c_str(), O_RDONLY, 0);
if (fd < 0) {
LOG(ERROR) << "Could not open " << dev_path.value() << " for ioctl access";
return false;
}
base::ScopedFD scoped_fd(fd);
uint64_t size = 0;
int res = ioctl(fd, BLKGETSIZE64, &size);
if (res != 0) {
LOG(ERROR) << "Unable to run ioctl(" << fd << ", BLKGETSIZE64, &size) => "
<< res;
return false;
}
DCHECK_GE(size, 0);
VLOG(1) << "Found size of " << dev_path.value() << " is "
<< std::to_string(size);
if (size_in_bytes)
*size_in_bytes = size;
return true;
}
// Fill the output with a colon-separated list of subsystems. For example,
// "block:mmc:mmc_host:pci". Similar output is returned by `lsblk -o SUBSYSTEMS`
bool GetUdevDeviceSubsystems(udev_device* input_device,
std::string* subsystem_output) {
// |subsystems| will track the stack of subsystems that this device uses.
std::vector<std::string> subsystems;
for (udev_device* device = input_device; device != nullptr;
device = udev_device_get_parent(device)) {
const char* subsystem = udev_device_get_subsystem(device);
if (subsystem != nullptr) {
subsystems.push_back(subsystem);
}
}
if (subsystems.empty()) {
const char* devnode = udev_device_get_devnode(input_device);
VLOG(1) << "Unable to collect any subsystems for device "
<< (devnode ? devnode : "<unknown>");
return false;
}
if (subsystem_output) {
*subsystem_output = base::JoinString(subsystems, ":");
}
return true;
}
// Return the /dev/... name for |sys_path|, which should be a
// /sys/class/block/... name. This utilizes libudev. Also returns the driver
// |subsystems| for use in determining the "type" of the block device.
bool GatherSysPathRelatedInfo(const base::FilePath& sys_path,
base::FilePath* devnode_path,
std::string* subsystems) {
udev* udev = udev_new();
if (udev == nullptr) {
LOG(ERROR) << "Unable to get udev.";
return false;
}
// Keep track of whether we have succeeded at getting a dev node.
bool found_related_info = false;
udev_device* device =
udev_device_new_from_syspath(udev, sys_path.value().c_str());
if (device != nullptr) {
if (!GetUdevDeviceSubsystems(device, subsystems)) {
VLOG(1) << "Unable to get a disk type from the subsystem chain.";
} else {
if (devnode_path) {
*devnode_path = base::FilePath{udev_device_get_devnode(device)};
}
found_related_info = true;
}
udev_device_unref(device);
} else {
LOG(ERROR) << "Unable to get udev_device for " << sys_path.value();
}
udev_unref(udev);
return found_related_info;
}
bool FetchNonRemovableBlockDeviceInfo(
const base::FilePath& sys_path,
NonRemovableBlockDeviceInfoPtr* output_info) {
NonRemovableBlockDeviceInfo info;
base::FilePath devnode_path;
if (!GatherSysPathRelatedInfo(sys_path, &devnode_path,
/*subsystems=*/&info.type)) {
VLOG(1) << "Unable to get the dev node path for " << sys_path.value();
return false;
}
info.path = devnode_path.value();
if (!GetDriveDeviceSizeInBytes(devnode_path, &info.size)) {
VLOG(1) << "Could not find the device size. (" << devnode_path.value()
<< ")";
return false;
}
const auto device_path = sys_path.Append("device");
// Not all devices in sysfs have a model/name, so ignore failure here.
if (!ReadAndTrimString(device_path, "model", &info.name)) {
ReadAndTrimString(device_path, "name", &info.name);
}
// Not all devices in sysfs have a serial, so ignore the return code.
ReadHexUint32(device_path, "serial", &info.serial);
uint64_t manfid = 0;
if (ReadHexUInt64(device_path, "manfid", &manfid)) {
DCHECK_EQ(manfid & 0xFF, manfid);
info.manufacturer_id = manfid;
}
if (output_info) {
*output_info = info.Clone();
}
return true;
}
} // namespace
std::vector<NonRemovableBlockDeviceInfoPtr> FetchNonRemovableBlockDevicesInfo(
const base::FilePath& root) {
// We'll fill out this |devices| vector with the return value.
std::vector<NonRemovableBlockDeviceInfoPtr> devices{};
for (const base::FilePath& sys_path : GetNonRemovableBlockDevices(root)) {
VLOG(1) << "Processing the node " << sys_path.value();
NonRemovableBlockDeviceInfoPtr info;
if (FetchNonRemovableBlockDeviceInfo(sys_path, &info)) {
DCHECK_NE(info->path, "");
DCHECK_NE(info->size, 0);
DCHECK_NE(info->type, "");
devices.push_back(std::move(info));
}
}
return devices;
}
} // namespace disk_utils
} // namespace diagnostics