lfs_core/
mountinfo.rs

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