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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::{
    cell::RefCell,
    fmt::Debug,
    fs::File,
    io::{self, Read},
    path::{Path, PathBuf},
    str::FromStr,
};
use sysfs_class::{Block, SysClass};

/// Methods that all block devices share, whether they are partitions or disks.
///
/// This trait is required to implement other disk traits.
pub trait BlockDeviceExt {
    /// The sys path of the block device.
    fn sys_block_path(&self) -> PathBuf { sys_block_path(self.get_device_name(), "") }

    /// Checks if the device is a read-only device.
    fn is_read_only(&self) -> bool {
        Block::from_path(&self.sys_block_path())
            .ok()
            .map_or(false, |block| block.ro().ok() == Some(1))
    }

    /// Checks if the device is a removable device.
    ///
    /// # Notes
    /// This is only applicable for disk devices.
    fn is_removable(&self) -> bool {
        Block::from_path(&self.sys_block_path())
            .ok()
            .map_or(false, |block| block.removable().ok() == Some(1))
    }

    /// Checks if the device is a rotational device.
    ///
    /// # Notes
    /// This is only applicable for disk devices.
    fn is_rotational(&self) -> bool {
        Block::from_path(&self.sys_block_path())
            .ok()
            .map_or(false, |block| block.queue_rotational().ok() == Some(1))
    }

    /// The path to the block device, such as `/dev/sda1`, or `/dev/data/root`.
    fn get_device_path(&self) -> &Path;

    /// The mount point of this block device, if it is mounted.
    fn get_mount_point(&self) -> Option<&Path> { None }

    /// The name of the device, such as `sda1`.
    fn get_device_name(&self) -> &str;

    /// The combined total number of sectors on the disk.
    fn get_sectors(&self) -> u64 {
        let size_file = sys_block_path(self.get_device_name(), "/size");
        read_file::<u64>(&size_file).expect("no sector count found")
    }

    /// The size of each logical sector, in bytes.
    fn get_logical_block_size(&self) -> u64 {
        let path = sys_block_path(self.get_device_name(), "/queue/logical_block_size");
        read_file::<u64>(&path).expect("logical block size not found")
    }

    /// The size of each logical sector, in bytes.
    fn get_physical_block_size(&self) -> u64 {
        let path = sys_block_path(self.get_device_name(), "/queue/physical_block_size");
        read_file::<u64>(&path).expect("physical block size not found")
    }
}

fn sys_block_path(name: &str, ppath: &str) -> PathBuf {
    PathBuf::from(["/sys/class/block/", name, ppath].concat())
}

thread_local! {
    static BUFFER: RefCell<String> = String::with_capacity(256).into();
}

fn read_file<T: FromStr>(path: &Path) -> io::Result<T>
where
    <T as FromStr>::Err: Debug,
{
    BUFFER.with(|buffer| {
        let mut buffer = buffer.borrow_mut();
        File::open(path)?.read_to_string(&mut buffer)?;
        let value = buffer.trim().parse::<T>();
        buffer.clear();
        value.map_err(|why| io::Error::new(io::ErrorKind::Other, format!("{:?}", why)))
    })
}