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
13pub type MountId = u32;
15
16#[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 pub bound: bool,
28}
29
30impl MountInfo {
31 pub fn dm_name(&self) -> Option<&str> {
33 regex_captures!(r#"^/dev/mapper/([^/]+)$"#, &self.fs)
34 .map(|(_, dm_name)| dm_name)
35 }
36 pub fn fs_name(&self) -> Option<&str> {
38 regex_find!(r#"[^\\/]+$"#, &self.fs)
39 }
40 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 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, })
89 })().with_context(|| ParseMountInfoSnafu { line })
90 }
91}
92
93fn str_to_pathbuf(s: &str) -> PathBuf {
98 PathBuf::from(sys::decode_string(s))
99}
100
101pub 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}