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] = &[
12 "afs",
13 "coda",
14 "auristorfs",
15 "fhgfs",
16 "gpfs",
17 "ibrix",
18 "ocfs2",
19 "vxfs",
20];
21
22pub type MountId = u32;
24
25#[derive(Debug, Clone)]
27pub struct MountInfo {
28 pub id: Option<MountId>,
29 pub parent: Option<MountId>,
30 pub dev: DeviceId,
31 pub root: PathBuf,
32 pub mount_point: PathBuf,
33 pub fs: String, pub fs_type: String,
35 pub bound: bool,
37}
38
39impl MountInfo {
40 pub fn dm_name(&self) -> Option<&str> {
42 regex_captures!(r#"^/dev/mapper/([^/]+)$"#, &self.fs).map(|(_, dm_name)| dm_name)
43 }
44 pub fn fs_name(&self) -> Option<&str> {
46 regex_find!(r#"[^\\/]+$"#, &self.fs)
47 }
48 pub fn is_remote(&self) -> bool {
52 self.fs.contains(':')
53 || (self.fs.starts_with("//")
54 && ["cifs", "smb3", "smbfs"].contains(&self.fs_type.as_ref()))
55 || REMOTE_ONLY_FS_TYPES.contains(&self.fs_type.as_ref())
56 || self.fs == "-hosts"
57 }
58}
59
60#[derive(Debug, Snafu)]
61#[snafu(display("Could not parse {line} as mount info"))]
62pub struct ParseMountInfoError {
63 line: String,
64}
65
66impl FromStr for MountInfo {
67 type Err = ParseMountInfoError;
68 fn from_str(line: &str) -> Result<Self, Self::Err> {
69 (|| {
70 let mut tokens = line.split_whitespace();
72
73 let id = tokens.next()?.parse().ok()?;
74 let parent = tokens.next()?.parse().ok()?;
75
76 let id = Some(id);
79 let parent = Some(parent);
80
81 let dev = tokens.next()?.parse().ok()?;
82 let root = str_to_pathbuf(tokens.next()?);
83 let mount_point = str_to_pathbuf(tokens.next()?);
84 loop {
85 let token = tokens.next()?;
86 if token == "-" {
87 break;
88 }
89 }
90 let fs_type = tokens.next()?.to_string();
91 let fs = tokens.next()?.to_string();
92 Some(Self {
93 id,
94 parent,
95 dev,
96 root,
97 mount_point,
98 fs,
99 fs_type,
100 bound: false, })
102 })()
103 .with_context(|| ParseMountInfoSnafu { line })
104 }
105}
106
107fn str_to_pathbuf(s: &str) -> PathBuf {
112 PathBuf::from(sys::decode_string(s))
113}
114
115pub fn read_mountinfo() -> Result<Vec<MountInfo>, Error> {
117 let mut mounts: Vec<MountInfo> = Vec::new();
118 let path = "/proc/self/mountinfo";
119 let file_content = sys::read_file(path).context(CantReadDirSnafu { path })?;
120 for line in file_content.trim().split('\n') {
121 let mut mount: MountInfo = line
122 .parse()
123 .map_err(|source| Error::ParseMountInfo { source })?;
124 mount.bound = mounts.iter().any(|m| m.dev == mount.dev);
125 mounts.push(mount);
126 }
127 Ok(mounts)
128}
129
130#[cfg(target_os = "linux")]
131#[test]
132fn test_from_str() {
133 let mi = MountInfo::from_str(
134 "47 21 0:41 / /dev/hugepages rw,relatime shared:27 - hugetlbfs hugetlbfs rw,pagesize=2M",
135 )
136 .unwrap();
137 assert_eq!(mi.id, Some(47));
138 assert_eq!(mi.dev, DeviceId::new(0, 41));
139 assert_eq!(mi.root, PathBuf::from("/"));
140 assert_eq!(mi.mount_point, PathBuf::from("/dev/hugepages"));
141
142 let mi = MountInfo::from_str(
143 "106 26 8:17 / /home/dys/dev rw,relatime shared:57 - xfs /dev/sdb1 rw,attr2,inode64,noquota"
144 ).unwrap();
145 assert_eq!(mi.id, Some(106));
146 assert_eq!(mi.dev, DeviceId::new(8, 17));
147 assert_eq!(&mi.fs, "/dev/sdb1");
148 assert_eq!(&mi.fs_type, "xfs");
149}