proc_mounts/mounts/
info.rs1use 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#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
15pub struct MountInfo {
16 pub source: PathBuf,
18 pub dest: PathBuf,
20 pub fstype: String,
22 pub options: Vec<String>,
24 pub dump: i32,
26 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 #[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}