1mod block_device;
2mod read_mountinfos;
3
4use {
5 crate::*,
6 block_device::*,
7 lazy_regex::*,
8 std::{
9 ffi::CString,
10 mem,
11 os::unix::ffi::OsStrExt,
12 path::Path,
13 sync::mpsc,
14 thread,
15 time::Duration,
16 },
17};
18
19pub use read_mountinfos::ParseMountInfoError;
20
21pub fn new_disk(name: String) -> Disk {
22 let rotational = sys::read_file_as_bool(format!("/sys/block/{name}/queue/rotational"));
23 let removable = sys::read_file_as_bool(format!("/sys/block/{name}/removable"));
24 let ram = regex_is_match!(r#"^zram\d*$"#, &name);
25 let dm_uuid = sys::read_file(format!("/sys/block/{name}/dm/uuid")).ok();
26 let crypted = dm_uuid
27 .as_ref()
28 .is_some_and(|uuid| uuid.starts_with("CRYPT-"));
29 let lvm = dm_uuid.is_some_and(|uuid| uuid.starts_with("LVM-"));
30 Disk {
31 name,
32 rotational,
33 removable,
34 image: false,
35 read_only: None,
36 ram,
37 lvm,
38 crypted,
39 }
40}
41
42pub fn read_mounts(options: &ReadOptions) -> Result<Vec<Mount>, Error> {
44 let by_label = read_by("label").ok();
45 let by_uuid = read_by("uuid").ok();
46 let by_partuuid = read_by("partuuid").ok();
47
48 let bd_list = BlockDeviceList::read()?;
52 read_mountinfos::read_all_mountinfos()?
53 .drain(..)
54 .map(|info| {
55 let top_bd = bd_list.find_top(info.dev, info.dm_name(), info.fs_name());
56 let fs_label = get_label(&info.fs, by_label.as_deref());
57 let uuid = get_label(&info.fs, by_uuid.as_deref());
58 let part_uuid = get_label(&info.fs, by_partuuid.as_deref());
59 let disk = top_bd.map(|bd| new_disk(bd.name.clone()));
60 let stats = if info.is_remote() && !options.remote_stats {
61 Err(StatsError::Excluded)
62 } else if let Some(timeout) = options.stats_timeout {
63 read_stats_with_timeout(&info.mount_point, timeout)
64 } else {
65 read_stats(&info.mount_point)
66 };
67 Ok(Mount {
68 info,
69 fs_label,
70 disk,
71 stats,
72 uuid,
73 part_uuid,
74 })
75 })
76 .collect()
77}
78
79pub fn read_stats_with_timeout(
80 mount_point: &Path,
81 timeout: Duration,
82) -> Result<Stats, StatsError> {
83 let mount_point = mount_point.to_path_buf();
84 let (tx, rx) = mpsc::channel();
85 thread::spawn(move || {
86 let stats = read_stats(&mount_point);
87 let _ = tx.send(stats);
88 });
89 rx.recv_timeout(timeout).map_err(|_| StatsError::Timeout)?
90}
91pub fn read_stats(mount_point: &Path) -> Result<Stats, StatsError> {
92 let c_mount_point = CString::new(mount_point.as_os_str().as_bytes()).unwrap();
93 unsafe {
94 let mut statvfs = mem::MaybeUninit::<libc::statvfs>::uninit();
95 let code = libc::statvfs(c_mount_point.as_ptr(), statvfs.as_mut_ptr());
96 match code {
97 0 => {
98 let statvfs = statvfs.assume_init();
99
100 let bsize = statvfs.f_bsize;
102 let blocks = statvfs.f_blocks;
103 let bfree = statvfs.f_bfree;
104 let bavail = statvfs.f_bavail;
105 if bsize == 0 || blocks == 0 || bfree > blocks || bavail > blocks {
106 return Err(StatsError::Unconsistent);
108 }
109
110 let bused = blocks - bavail;
112
113 let files = statvfs.f_files;
115 let ffree = statvfs.f_ffree;
116 let favail = statvfs.f_favail;
117 #[allow(clippy::useless_conversion)]
118 let inodes = Inodes::new(files.into(), ffree.into(), favail.into());
119
120 #[allow(clippy::useless_conversion)]
121 Ok(Stats {
122 bsize: bsize.into(),
123 blocks: blocks.into(),
124 bused: bused.into(),
125 bfree: bfree.into(),
126 bavail: bavail.into(),
127 inodes: inodes.into(),
128 })
129 }
130 _ => {
131 Err(StatsError::Unreachable)
134 }
135 }
136 }
137}