blob: 17eb7745203aebab53087954e86a9a979fc619d0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{anyhow, Context, Error},
fdio,
fidl::endpoints::Proxy,
fidl_fuchsia_device::ControllerProxy,
fidl_fuchsia_hardware_block::BlockProxy,
fuchsia_zircon as zx, fuchsia_zircon_status as zx_status,
std::{fs, path::Path},
};
async fn connect_to_service(path: &str) -> Result<fidl::AsyncChannel, Error> {
let (local, remote) = zx::Channel::create().context("Creating channel")?;
fdio::service_connect(path, remote).context("Connecting to service")?;
let local = fidl::AsyncChannel::from_channel(local).context("Creating AsyncChannel")?;
Ok(local)
}
async fn block_device_get_info(
block_channel: fidl::AsyncChannel,
) -> Result<Option<(String, u64)>, Error> {
// Figure out topological path of the block device, so we can guess if it's a disk or a
// partition.
let (maybe_path, block_channel) = get_topological_path(block_channel).await?;
let topo_path = maybe_path.ok_or(anyhow!("Failed to get topo path for device"))?;
if topo_path.contains("/ramdisk-") {
// This is probably ram, skip it
return Ok(None);
}
let block = BlockProxy::from_channel(block_channel);
let (status, maybe_info) = block.get_info().await?;
if let Some(info) = maybe_info {
let blocks = info.block_count;
let block_size = info.block_size as u64;
return Ok(Some((topo_path, blocks * block_size)));
}
return Err(Error::new(zx_status::Status::from_raw(status)));
}
// There's no nice way to use a service without losing the channel,
// so this function returns the controller.
async fn get_topological_path(
channel: fidl::AsyncChannel,
) -> Result<(Option<String>, fidl::AsyncChannel), Error> {
let controller = ControllerProxy::from_channel(channel);
let topo_resp = controller.get_topological_path().await.context("Getting topological path")?;
Ok((topo_resp.ok(), controller.into_channel().unwrap()))
}
#[derive(Debug, PartialEq, Clone)]
pub struct BlockDevice {
/// Topological path of the block device.
pub topo_path: String,
/// Path to the block device under /dev/class/block.
pub class_path: String,
/// Size of the block device, in bytes.
pub size: u64,
}
impl BlockDevice {
/// Returns true if this block device is a disk.
pub fn is_disk(&self) -> bool {
// partitions have paths like this:
// /dev/sys/platform/pci/00:14.0/xhci/usb-bus/001/001/ifc-000/ums/lun-000/block/part-000/block
// while disks are like this:
// /dev/sys/platform/pci/00:17.0/ahci/sata2/block
!self.topo_path.contains("/block/part-")
}
}
pub async fn get_block_device(class_path: String) -> Result<Option<BlockDevice>, Error> {
let block_channel = connect_to_service(&class_path).await?;
let result = block_device_get_info(block_channel).await.context("Getting block device info")?;
Ok(result.map(|(topo_path, size)| BlockDevice { topo_path, class_path, size }))
}
pub async fn get_block_devices() -> Result<Vec<BlockDevice>, Error> {
let block_dir = Path::new("/dev/class/block");
let mut devices = Vec::new();
for entry in fs::read_dir(block_dir)? {
let name = entry?.path().to_str().unwrap().to_owned();
if let Some(bd) = get_block_device(name.clone()).await? {
devices.push(bd);
} else {
println!("Bad disk: {:?}", name);
}
}
Ok(devices)
}