lfs_core/
mountinfo.rs

1use {
2    crate::*,
3    lazy_regex::*,
4    std::path::PathBuf,
5};
6
7#[cfg(unix)]
8static REMOTE_ONLY_FS_TYPES: &[&str] = &[
9    "afs",
10    "coda",
11    "auristorfs",
12    "fhgfs",
13    "gpfs",
14    "ibrix",
15    "ocfs2",
16    "vxfs",
17];
18
19/// options that may be present in the options vec but that we
20///  don't want to see in the `options_string()` returned value
21static OPTIONS_NOT_IN_OPTIONS_STRING: &[&str] = &[
22    "removable", // parsed on mac but not found in /proc/mountinfo
23];
24
25/// An id of a mount
26pub type MountId = u32;
27
28/// A mount point as described in /proc/self/mountinfo
29#[derive(Debug, Clone)]
30pub struct MountInfo {
31    pub id: Option<MountId>,
32    pub parent: Option<MountId>,
33    pub dev: DeviceId,
34    pub root: PathBuf,
35    pub mount_point: PathBuf,
36    pub options: Vec<MountOption>,
37    pub fs: String, // rename into "node" ?
38    pub fs_type: String,
39    /// whether it's a bound mount (usually mirroring part of another device)
40    pub bound: bool,
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct MountOption {
45    pub name: String,
46    pub value: Option<String>,
47}
48
49impl MountOption {
50    pub fn new<S: Into<String>>(
51        name: S,
52        value: Option<S>,
53    ) -> Self {
54        MountOption {
55            name: name.into(),
56            value: value.map(|s| s.into()),
57        }
58    }
59}
60
61impl MountInfo {
62    /// return `<name>` when the path is `/dev/mapper/<name>`
63    pub fn dm_name(&self) -> Option<&str> {
64        regex_captures!(r#"^/dev/mapper/([^/]+)$"#, &self.fs).map(|(_, dm_name)| dm_name)
65    }
66    /// return the last token of the fs path
67    pub fn fs_name(&self) -> Option<&str> {
68        regex_find!(r#"[^\\/]+$"#, &self.fs)
69    }
70    /// tell whether the mount looks remote
71    ///
72    /// Heuristics copied from <https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c>
73    #[cfg(unix)]
74    pub fn is_remote(&self) -> bool {
75        self.fs.contains(':')
76            || (self.fs.starts_with("//")
77                && ["cifs", "smb3", "smbfs"].contains(&self.fs_type.as_ref()))
78            || REMOTE_ONLY_FS_TYPES.contains(&self.fs_type.as_ref())
79            || self.fs == "-hosts"
80    }
81    /// return a string like "rw,noatime,compress=zstd:3,space_cache=v2,subvolid=256"
82    /// (as in /proc/mountinfo)
83    ///
84    /// Some options may be skipped as they're less relevant (but you may still find them
85    /// in the options vec)
86    pub fn options_string(&self) -> String {
87        let mut s = String::new();
88        let mut first = true;
89        for option in &self.options {
90            if OPTIONS_NOT_IN_OPTIONS_STRING
91                .iter()
92                .any(|s| s == &option.name)
93            {
94                continue;
95            }
96            if !first {
97                s.push(',');
98            }
99            s.push_str(&option.name);
100            if let Some(value) = &option.value {
101                s.push('=');
102                s.push_str(value);
103            }
104            first = false;
105        }
106        s
107    }
108    /// tell whether the option (eg "compress", "rw", "noatime") is present
109    /// among options
110    pub fn has_option(
111        &self,
112        name: &str,
113    ) -> bool {
114        for option in &self.options {
115            if option.name == name {
116                return true;
117            }
118        }
119        false
120    }
121    /// return the value of the mountoption, or None
122    pub fn option_value(
123        &self,
124        name: &str,
125    ) -> Option<&str> {
126        for option in &self.options {
127            if option.name == name {
128                return option.value.as_deref();
129            }
130        }
131        None
132    }
133}