procfs_core/
mounts.rs

1use std::{collections::HashMap, io::BufRead};
2
3use super::ProcResult;
4use std::str::FromStr;
5
6#[cfg(feature = "serde1")]
7use serde::{Deserialize, Serialize};
8
9/// A mountpoint entry under `/proc/mounts`
10#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
12#[allow(non_snake_case)]
13pub struct MountEntry {
14    /// Device
15    pub fs_spec: String,
16    /// Mountpoint
17    pub fs_file: String,
18    /// FS type
19    pub fs_vfstype: String,
20    /// Mount options
21    pub fs_mntops: HashMap<String, Option<String>>,
22    /// Dump
23    pub fs_freq: u8,
24    /// Check
25    pub fs_passno: u8,
26}
27
28impl super::FromBufRead for Vec<MountEntry> {
29    fn from_buf_read<R: BufRead>(r: R) -> ProcResult<Self> {
30        let mut vec = Vec::new();
31
32        for line in r.lines() {
33            let line = expect!(line);
34            let mut s = line.split(' '); // not using split_whitespace because we might have empty fields
35
36            let fs_spec = unmangle_octal(expect!(s.next()));
37            let fs_file = unmangle_octal(expect!(s.next()));
38            let fs_vfstype = unmangle_octal(expect!(s.next()));
39            let fs_mntops = unmangle_octal(expect!(s.next()));
40            let fs_mntops: HashMap<String, Option<String>> = fs_mntops
41                .split(',')
42                .map(|s| {
43                    let mut split = s.splitn(2, '=');
44                    let k = split.next().unwrap().to_string(); // can not fail, splitn will always return at least 1 element
45                    let v = split.next().map(|s| s.to_string());
46
47                    (k, v)
48                })
49                .collect();
50            let fs_freq = expect!(u8::from_str(expect!(s.next())));
51            let fs_passno = expect!(u8::from_str(expect!(s.next())));
52
53            let mount_entry = MountEntry {
54                fs_spec,
55                fs_file,
56                fs_vfstype,
57                fs_mntops,
58                fs_freq,
59                fs_passno,
60            };
61
62            vec.push(mount_entry);
63        }
64
65        Ok(vec)
66    }
67}
68
69/// Unmangle spaces ' ', tabs '\t', line breaks '\n', backslashes '\\', and hashes '#'
70///
71/// See https://elixir.bootlin.com/linux/v6.2.8/source/fs/proc_namespace.c#L89
72pub(crate) fn unmangle_octal(input: &str) -> String {
73    let mut input = input.to_string();
74
75    for (octal, c) in [(r"\011", "\t"), (r"\012", "\n"), (r"\134", "\\"), (r"\043", "#")] {
76        input = input.replace(octal, c);
77    }
78
79    input
80}
81
82#[test]
83fn test_unmangle_octal() {
84    let tests = [
85        (r"a\134b\011c\012d\043e", "a\\b\tc\nd#e"), // all escaped chars with abcde in between
86        (r"abcd", r"abcd"),                         // do nothing
87    ];
88
89    for (input, expected) in tests {
90        assert_eq!(unmangle_octal(input), expected);
91    }
92}
93
94#[test]
95fn test_mounts() {
96    use crate::FromBufRead;
97    use std::io::Cursor;
98
99    let s = "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
100sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
101/dev/mapper/ol-root / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0
102Downloads /media/sf_downloads vboxsf rw,nodev,relatime,iocharset=utf8,uid=0,gid=977,dmode=0770,fmode=0770,tag=VBoxAutomounter 0 0";
103
104    let cursor = Cursor::new(s);
105    let mounts = Vec::<MountEntry>::from_buf_read(cursor).unwrap();
106    assert_eq!(mounts.len(), 4);
107
108    // https://github.com/eminence/procfs/issues/333
109    let s = " / tmpfs ro,nosuid,nodev,noexec,relatime,size=0k,nr_inodes=2,uid=1000,gid=1000,inode64 0 0";
110    let mounts = Vec::<MountEntry>::from_buf_read(Cursor::new(s)).unwrap();
111    assert_eq!(mounts.len(), 1);
112    assert_eq!(mounts[0].fs_spec, "");
113    assert_eq!(mounts[0].fs_file, "/");
114    assert_eq!(mounts[0].fs_vfstype, "tmpfs");
115    assert!(mounts[0].fs_mntops.contains_key("ro"));
116}