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/// An id of a mount
19pub type MountId = u32;
20
21/// A mount point as described in /proc/self/mountinfo
22#[derive(Debug, Clone)]
23pub struct MountInfo {
24    pub id: Option<MountId>,
25    pub parent: Option<MountId>,
26    pub dev: DeviceId,
27    pub root: PathBuf,
28    pub mount_point: PathBuf,
29    pub options: Vec<MountOption>,
30    pub fs: String, // rename into "node" ?
31    pub fs_type: String,
32    /// whether it's a bound mount (usually mirroring part of another device)
33    pub bound: bool,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct MountOption {
38    pub name: String,
39    pub value: Option<String>,
40}
41
42impl MountOption {
43    pub fn new<S: Into<String>>(
44        name: S,
45        value: Option<S>,
46    ) -> Self {
47        MountOption {
48            name: name.into(),
49            value: value.map(|s| s.into()),
50        }
51    }
52}
53
54impl MountInfo {
55    /// return `<name>` when the path is `/dev/mapper/<name>`
56    pub fn dm_name(&self) -> Option<&str> {
57        regex_captures!(r#"^/dev/mapper/([^/]+)$"#, &self.fs).map(|(_, dm_name)| dm_name)
58    }
59    /// return the last token of the fs path
60    pub fn fs_name(&self) -> Option<&str> {
61        regex_find!(r#"[^\\/]+$"#, &self.fs)
62    }
63    /// tell whether the mount looks remote
64    ///
65    /// Heuristics copied from https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c
66    pub fn is_remote(&self) -> bool {
67        self.fs.contains(':')
68            || (self.fs.starts_with("//")
69                && ["cifs", "smb3", "smbfs"].contains(&self.fs_type.as_ref()))
70            || REMOTE_ONLY_FS_TYPES.contains(&self.fs_type.as_ref())
71            || self.fs == "-hosts"
72    }
73    /// return a string like "rw,noatime,compress=zstd:3,space_cache=v2,subvolid=256"
74    /// (as in /proc/mountinfo)
75    pub fn options_string(&self) -> String {
76        let mut s = String::new();
77        let mut first = true;
78        for option in &self.options {
79            if !first {
80                s.push(',');
81            }
82            s.push_str(&option.name);
83            if let Some(value) = &option.value {
84                s.push('=');
85                s.push_str(value);
86            }
87            first = false;
88        }
89        s
90    }
91    /// tell whether the option (eg "compress", "rw", "noatime") is present
92    /// among options
93    pub fn has_option(
94        &self,
95        name: &str,
96    ) -> bool {
97        for option in &self.options {
98            if option.name == name {
99                return true;
100            }
101        }
102        false
103    }
104    /// return the value of the mountoption, or None
105    pub fn option_value(
106        &self,
107        name: &str,
108    ) -> Option<&str> {
109        for option in &self.options {
110            if option.name == name {
111                return option.value.as_deref();
112            }
113        }
114        None
115    }
116}