lfs_core/
mountinfo.rs

1use {
2    crate::*,
3    lazy_regex::*,
4    snafu::prelude::*,
5    std::{
6        path::PathBuf,
7        str::FromStr,
8    },
9};
10
11static REMOTE_ONLY_FS_TYPES: &[&str] = &["afs", "coda", "auristorfs", "fhgfs", "gpfs", "ibrix", "ocfs2", "vxfs"];
12
13/// An id of a mount
14pub type MountId = u32;
15
16/// A mount point as described in /proc/self/mountinfo
17#[derive(Debug, Clone)]
18pub struct MountInfo {
19    pub id: MountId,
20    pub parent: MountId,
21    pub dev: DeviceId,
22    pub root: PathBuf,
23    pub mount_point: PathBuf,
24    pub fs: String,
25    pub fs_type: String,
26    /// whether it's a bound mount (usually mirroring part of another device)
27    pub bound: bool,
28}
29
30impl MountInfo {
31    /// return `<name>` when the path is `/dev/mapper/<name>`
32    pub fn dm_name(&self) -> Option<&str> {
33        regex_captures!(r#"^/dev/mapper/([^/]+)$"#, &self.fs)
34            .map(|(_, dm_name)| dm_name)
35    }
36    /// return the last token of the fs path
37    pub fn fs_name(&self) -> Option<&str> {
38        regex_find!(r#"[^\\/]+$"#, &self.fs)
39    }
40    /// tell whether the mount looks remote
41    ///
42    /// Heuristics copied from https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c
43    pub fn is_remote(&self) -> bool {
44        self.fs.contains(':')
45            || (
46                self.fs.starts_with("//")
47                && ["cifs", "smb3", "smbfs"].contains(&self.fs_type.as_ref())
48            )
49            || REMOTE_ONLY_FS_TYPES.contains(&self.fs_type.as_ref())
50            || self.fs == "-hosts"
51    }
52}
53
54#[derive(Debug, Snafu)]
55#[snafu(display("Could not parse {line} as mount info"))]
56pub struct ParseMountInfoError {
57    line: String,
58}
59
60impl FromStr for MountInfo {
61    type Err = ParseMountInfoError;
62    fn from_str(line: &str) -> Result<Self, Self::Err> {
63        (|| {
64            // this parsing is based on `man 5 proc`
65            let mut tokens = line.split_whitespace();
66            let id = tokens.next()?.parse().ok()?;
67            let parent = tokens.next()?.parse().ok()?;
68            let dev = tokens.next()?.parse().ok()?;
69            let root = str_to_pathbuf(tokens.next()?);
70            let mount_point = str_to_pathbuf(tokens.next()?);
71            loop {
72                let token = tokens.next()?;
73                if token == "-" {
74                    break;
75                }
76            };
77            let fs_type = tokens.next()?.to_string();
78            let fs = tokens.next()?.to_string();
79            Some(Self {
80                id,
81                parent,
82                dev,
83                root,
84                mount_point,
85                fs,
86                fs_type,
87                bound: false, // determined by post-treatment
88            })
89        })().with_context(|| ParseMountInfoSnafu { line })
90    }
91}
92
93/// convert a string to a pathbuf, converting ascii-octal encoded
94/// chars.
95/// This is necessary because some chars are encoded. For example
96/// the `/media/dys/USB DISK` is present as `/media/dys/USB\040DISK`
97fn str_to_pathbuf(s: &str) -> PathBuf {
98    PathBuf::from(sys::decode_string(s))
99}
100
101/// read all the mount points
102pub fn read_mountinfo() -> Result<Vec<MountInfo>, Error> {
103    let mut mounts: Vec<MountInfo> = Vec::new();
104    let path = "/proc/self/mountinfo";
105    let file_content = sys::read_file(path)
106        .context(CantReadDirSnafu { path })?;
107    for line in file_content.trim().split('\n') {
108        let mut mount: MountInfo = line.parse()
109            .map_err(|source| Error::ParseMountInfo { source })?;
110        mount.bound = mounts.iter().any(|m| m.dev == mount.dev);
111        mounts.push(mount);
112    }
113    Ok(mounts)
114}
115
116#[test]
117fn test_from_str() {
118    let mi = MountInfo::from_str(
119        "47 21 0:41 / /dev/hugepages rw,relatime shared:27 - hugetlbfs hugetlbfs rw,pagesize=2M"
120    ).unwrap();
121    assert_eq!(mi.id, 47);
122    assert_eq!(mi.dev, DeviceId::new(0, 41));
123    assert_eq!(mi.root, PathBuf::from("/"));
124    assert_eq!(mi.mount_point, PathBuf::from("/dev/hugepages"));
125
126    let mi = MountInfo::from_str(
127        "106 26 8:17 / /home/dys/dev rw,relatime shared:57 - xfs /dev/sdb1 rw,attr2,inode64,noquota"
128    ).unwrap();
129    assert_eq!(mi.id, 106);
130    assert_eq!(mi.dev, DeviceId::new(8, 17));
131    assert_eq!(&mi.fs, "/dev/sdb1");
132    assert_eq!(&mi.fs_type, "xfs");
133}