1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 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 as _, Error};
use fidl_fuchsia_device::ControllerMarker;
use fidl_fuchsia_hardware_block::BlockMarker;

#[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: &str) -> Result<Option<BlockDevice>, Error> {
    let controller = fuchsia_component::client::connect_to_protocol_at_path::<ControllerMarker>(
        format!("{class_path}/device_controller").as_str(),
    )?;
    let topo_path = controller
        .get_topological_path()
        .await
        .context("FIDL: get_topological_path()")?
        .map_err(zx::Status::from_raw)
        .context("response: get_topological_path()")?;
    if topo_path.contains("/ramdisk-") {
        // This is probably ram, skip it
        Ok(None)
    } else {
        let block =
            fuchsia_component::client::connect_to_protocol_at_path::<BlockMarker>(class_path)?;
        let info = block
            .get_info()
            .await
            .context("FIDL: get_info()")?
            .map_err(zx::Status::from_raw)
            .context("response: get_info()")?;
        let block_count = info.block_count;
        let block_size = info.block_size;
        let size = block_count.checked_mul(block_size.into()).ok_or_else(|| {
            anyhow!("device size overflow: block_count={} block_size={}", block_count, block_size)
        })?;
        let class_path = class_path.to_owned();
        Ok(Some(BlockDevice { topo_path, class_path, size }))
    }
}

pub async fn get_block_devices() -> Result<Vec<BlockDevice>, Error> {
    const BLOCK_DIR: &str = "/dev/class/block";
    let entries = std::fs::read_dir(BLOCK_DIR)?;
    let futures = entries.map(|entry| async {
        let entry = entry?;
        let path = entry.path();
        let class_path =
            path.to_str().ok_or_else(|| anyhow!("path contains non-UTF8: {}", path.display()))?;
        get_block_device(class_path).await
    });
    let options = futures::future::try_join_all(futures).await?;
    let devices = options.into_iter().filter_map(std::convert::identity).collect();
    Ok(devices)
}