proc_mounts/mounts/
info.rs

1use partition_identity::PartitionID;
2use std::{
3    char,
4    ffi::OsString,
5    fmt::{self, Display, Formatter},
6    io::{self, Error, ErrorKind},
7    os::unix::ffi::OsStringExt,
8    path::PathBuf,
9    str::FromStr,
10};
11
12/// A mount entry which contains information regarding how and where a source
13/// is mounted.
14#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
15pub struct MountInfo {
16    /// The source which is mounted.
17    pub source: PathBuf,
18    /// Where the source is mounted.
19    pub dest: PathBuf,
20    /// The type of the mounted file system.
21    pub fstype: String,
22    /// Options specified for this file system.
23    pub options: Vec<String>,
24    /// Defines if the file system should be dumped.
25    pub dump: i32,
26    /// Defines if the file system should be checked, and in what order.
27    pub pass: i32,
28}
29
30impl Display for MountInfo {
31    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
32        write!(
33            fmt,
34            "{} {} {} {} {} {}",
35            self.source.display(),
36            self.dest.display(),
37            self.fstype,
38            if self.options.is_empty() { "defaults".into() } else { self.options.join(",") },
39            self.dump,
40            self.pass
41        )
42    }
43}
44
45impl FromStr for MountInfo {
46    type Err = io::Error;
47
48    fn from_str(line: &str) -> Result<Self, Self::Err> {
49        let mut parts = line.split_whitespace();
50
51        fn map_err(why: &'static str) -> io::Error { Error::new(ErrorKind::InvalidData, why) }
52
53        let source = parts.next().ok_or_else(|| map_err("missing source"))?;
54        let dest = parts.next().ok_or_else(|| map_err("missing dest"))?;
55        let fstype = parts.next().ok_or_else(|| map_err("missing type"))?;
56        let options = parts.next().ok_or_else(|| map_err("missing options"))?;
57
58        let dump = parts.next().map_or(Ok(0), |value| {
59            value.parse::<i32>().map_err(|_| map_err("dump value is not a number"))
60        })?;
61
62        let pass = parts.next().map_or(Ok(0), |value| {
63            value.parse::<i32>().map_err(|_| map_err("pass value is not a number"))
64        })?;
65
66        let path = Self::parse_value(source)?;
67        let path = path.to_str().ok_or_else(|| map_err("non-utf8 paths are unsupported"))?;
68
69        let source = if path.starts_with("/dev/disk/by-") {
70            Self::fetch_from_disk_by_path(path)?
71        } else {
72            PathBuf::from(path)
73        };
74
75        let path = Self::parse_value(dest)?;
76        let path = path.to_str().ok_or_else(|| map_err("non-utf8 paths are unsupported"))?;
77
78        let dest = PathBuf::from(path);
79
80        Ok(MountInfo {
81            source,
82            dest,
83            fstype: fstype.to_owned(),
84            options: options.split(',').map(String::from).collect(),
85            dump,
86            pass,
87        })
88    }
89}
90
91impl MountInfo {
92    /// Attempt to parse a `/proc/mounts`-like line.
93    #[deprecated]
94    pub fn parse_line(line: &str) -> io::Result<MountInfo> { line.parse::<Self>() }
95
96    fn fetch_from_disk_by_path(path: &str) -> io::Result<PathBuf> {
97        PartitionID::from_disk_by_path(path)
98            .map_err(|why| Error::new(ErrorKind::InvalidData, format!("{}: {}", path, why)))?
99            .get_device_path()
100            .ok_or_else(|| {
101                Error::new(ErrorKind::NotFound, format!("device path for {} was not found", path))
102            })
103    }
104
105    fn parse_value(value: &str) -> io::Result<OsString> {
106        let mut ret = Vec::new();
107
108        let mut bytes = value.bytes();
109        while let Some(b) = bytes.next() {
110            match b {
111                b'\\' => {
112                    let mut code = 0;
113                    for _i in 0..3 {
114                        if let Some(b) = bytes.next() {
115                            code *= 8;
116                            code += u32::from_str_radix(&(b as char).to_string(), 8)
117                                .map_err(|err| Error::new(ErrorKind::Other, err))?;
118                        } else {
119                            return Err(Error::new(ErrorKind::Other, "truncated octal code"));
120                        }
121                    }
122                    ret.push(code as u8);
123                }
124                _ => {
125                    ret.push(b);
126                }
127            }
128        }
129
130        Ok(OsString::from_vec(ret))
131    }
132}