lfs_core/linux/
mod.rs

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
42/// Read all the mount points and load basic information on them
43pub 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    // we'll find the disk for a filesystem by taking the longest
49    // disk whose name starts the one of our partition
50    // hence the sorting.
51    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                // blocks info
101                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                    // unconsistent or void data
107                    return Err(StatsError::Unconsistent);
108                }
109
110                // statvfs doesn't provide bused
111                let bused = blocks - bavail;
112
113                // inodes info, will be checked in Inodes::new
114                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                // the filesystem wasn't found, it's a strange one, for example a
132                // docker one, or a disconnected remote one
133                Err(StatsError::Unreachable)
134            }
135        }
136    }
137}