1extern crate libc;
16
17use error::*;
18use self::libc::c_int;
19use std::cmp::Ordering;
20use std::convert::{AsRef, From};
21use std::fmt;
22use std::fs::File;
23use std::io::{BufReader, BufRead, Lines};
24use std::iter::Enumerate;
25use std::path::{Path, PathBuf};
26use std::str::FromStr;
27
28const PROC_MOUNTS: &'static str = "/proc/mounts";
29
30#[derive(Clone, Copy, PartialEq, Eq, Debug)]
31pub enum DumpField {
32 Ignore = 0,
33 Backup = 1,
34}
35
36pub type PassField = Option<c_int>;
37
38#[derive(Clone, PartialEq, Eq, Debug)]
39pub enum MntOps {
40 Atime(bool),
41 DirAtime(bool),
42 RelAtime(bool),
43 Dev(bool),
44 Exec(bool),
45 Suid(bool),
46 Write(bool),
47 Extra(String),
48}
49
50impl FromStr for MntOps {
51 type Err = LineError;
52
53 fn from_str(token: &str) -> Result<MntOps, LineError> {
54 Ok(match token {
55 "atime" => MntOps::Atime(true),
56 "noatime" => MntOps::Atime(false),
57 "diratime" => MntOps::DirAtime(true),
58 "nodiratime" => MntOps::DirAtime(false),
59 "relatime" => MntOps::RelAtime(true),
60 "norelatime" => MntOps::RelAtime(false),
61 "dev" => MntOps::Dev(true),
62 "nodev" => MntOps::Dev(false),
63 "exec" => MntOps::Exec(true),
64 "noexec" => MntOps::Exec(false),
65 "suid" => MntOps::Suid(true),
66 "nosuid" => MntOps::Suid(false),
67 "rw" => MntOps::Write(true),
68 "ro" => MntOps::Write(false),
69 extra => MntOps::Extra(extra.to_string()),
71 })
72 }
73}
74
75#[derive(Clone, Debug)]
76pub enum MountParam<'a> {
77 Spec(&'a str),
78 File(&'a Path),
79 VfsType(&'a str),
80 MntOps(&'a MntOps),
81 Freq(&'a DumpField),
82 PassNo(&'a PassField),
83}
84
85#[derive(Clone, PartialEq, Eq)]
86pub struct MountEntry {
87 pub spec: String,
88 pub file: PathBuf,
89 pub vfstype: String,
90 pub mntops: Vec<MntOps>,
91 pub freq: DumpField,
92 pub passno: PassField,
93}
94
95impl MountEntry {
96 pub fn contains(&self, search: &MountParam) -> bool {
97 match search {
98 &MountParam::Spec(spec) => spec == &self.spec,
99 &MountParam::File(file) => file == &self.file,
100 &MountParam::VfsType(vfstype) => vfstype == &self.vfstype,
101 &MountParam::MntOps(mntops) => self.mntops.contains(mntops),
102 &MountParam::Freq(dumpfield) => dumpfield == &self.freq,
103 &MountParam::PassNo(passno) => passno == &self.passno,
104 }
105 }
106}
107
108impl FromStr for MountEntry {
109 type Err = LineError;
110
111 fn from_str(line: &str) -> Result<MountEntry, LineError> {
112 let line = line.trim();
113 let mut tokens = line.split_terminator(|s: char| { s == ' ' || s == '\t' })
114 .filter(|s| { s != &"" } );
115 Ok(MountEntry {
116 spec: try!(tokens.next().ok_or(LineError::MissingSpec)).to_string(),
117 file: {
118 let file = try!(tokens.next().ok_or(LineError::MissingFile));
119 let path = PathBuf::from(file);
120 if path.is_relative() {
121 return Err(LineError::InvalidFilePath(file.into()));
122 }
123 path
124 },
125 vfstype: try!(tokens.next().ok_or(LineError::MissingVfstype)).to_string(),
126 mntops: try!(tokens.next().ok_or(LineError::MissingMntops))
127 .split_terminator(',').map(|x| { FromStr::from_str(x).unwrap() }).collect(),
129 freq: {
130 let freq = try!(tokens.next().ok_or(LineError::MissingFreq));
131 match FromStr::from_str(freq) {
132 Ok(0) => DumpField::Ignore,
133 Ok(1) => DumpField::Backup,
134 _ => return Err(LineError::InvalidFreq(freq.into())),
135 }
136 },
137 passno: {
138 let passno = try!(tokens.next().ok_or(LineError::MissingPassno));
139 match FromStr::from_str(passno) {
140 Ok(0) => None,
141 Ok(f) if f > 0 => Some(f),
142 _ => return Err(LineError::InvalidPassno(passno.into())),
143 }
144 },
145 })
146 }
147}
148
149
150pub fn get_submounts_from<T, U>(root: T, iter: MountIter<U>)
152 -> Result<Vec<MountEntry>, ParseError> where T: AsRef<Path>, U: BufRead {
153 let mut ret = vec!();
154 for mount in iter {
155 match mount {
156 Ok(m) => if m.file.starts_with(&root) {
157 ret.push(m);
158 },
159 Err(e) => return Err(e),
160 }
161 }
162 Ok(ret)
163}
164
165pub fn get_submounts<T>(root: T) -> Result<Vec<MountEntry>, ParseError> where T: AsRef<Path> {
167 get_submounts_from(root, try!(MountIter::new_from_proc()))
168}
169
170pub fn get_mount_from<T, U>(target: T, iter: MountIter<U>)
172 -> Result<Option<MountEntry>, ParseError> where T: AsRef<Path>, U: BufRead {
173 let mut ret = None;
174 for mount in iter {
175 match mount {
176 Ok(m) => if target.as_ref().starts_with(&m.file) {
177 ret = Some(m);
179 },
180 Err(e) => return Err(e),
181 }
182 }
183 Ok(ret)
184}
185
186pub fn get_mount<T>(target: T) -> Result<Option<MountEntry>, ParseError> where T: AsRef<Path> {
188 get_mount_from(target, try!(MountIter::new_from_proc()))
189}
190
191pub fn get_mount_writable<T>(target: T, writable: bool) -> Option<MountEntry> where T: AsRef<Path> {
195 match get_mount(target) {
196 Ok(Some(m)) => {
197 if !writable || m.mntops.contains(&MntOps::Write(writable)) {
198 Some(m)
199 } else {
200 None
201 }
202 }
203 _ => None,
204 }
205}
206
207pub trait VecMountEntry {
208 fn remove_overlaps<T>(self, exclude_files: &Vec<T>) -> Self where T: AsRef<Path>;
209}
210
211impl VecMountEntry for Vec<MountEntry> {
212 fn remove_overlaps<T>(self, exclude_files: &Vec<T>) -> Vec<MountEntry> where T: AsRef<Path> {
214 let mut sorted: Vec<MountEntry> = vec!();
215 let root = Path::new("/");
216 'list: for mount in self.into_iter().rev() {
217 if AsRef::<Path>::as_ref(&mount.file) == root {
219 continue 'list;
220 }
221 let mut has_overlaps = false;
222 'filter: for mount_sorted in sorted.iter() {
223 if exclude_files.iter().skip_while(|x|
224 AsRef::<Path>::as_ref(&mount_sorted.file) != x.as_ref()).next().is_some() {
225 continue 'filter;
226 }
227 if mount.file.starts_with(&mount_sorted.file) {
229 has_overlaps = true;
230 break 'filter;
231 }
232 }
233 if !has_overlaps {
234 sorted.push(mount);
235 }
236 }
237 sorted.reverse();
238 sorted
239 }
240}
241
242
243impl fmt::Debug for MountEntry {
244 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
245 write!(out, "MountEntry {{ spec: {:?}, file: {:?}, vfstype: {:?}, mntops: {:?}, freq: {:?}, passno: {:?} }}",
246 self.spec, self.file.display(), self.vfstype, self.mntops, self.freq, self.passno)
247 }
248}
249
250impl PartialOrd for MountEntry {
251 fn partial_cmp(&self, other: &MountEntry) -> Option<Ordering> {
252 self.file.partial_cmp(&other.file)
253 }
254}
255
256impl Ord for MountEntry {
257 fn cmp(&self, other: &MountEntry) -> Ordering {
258 self.file.cmp(&other.file)
259 }
260}
261
262
263pub struct MountIter<T> {
264 lines: Enumerate<Lines<T>>,
265}
266
267impl<T> MountIter<T> where T: BufRead {
268 pub fn new(mtab: T) -> MountIter<T> {
269 MountIter {
270 lines: mtab.lines().enumerate(),
271 }
272 }
273}
274
275impl MountIter<BufReader<File>> {
276 pub fn new_from_proc() -> Result<MountIter<BufReader<File>>, ParseError> {
277 let file = try!(File::open(PROC_MOUNTS));
278 Ok(MountIter::new(BufReader::new(file)))
279 }
280}
281
282impl<T> Iterator for MountIter<T> where T: BufRead {
283 type Item = Result<MountEntry, ParseError>;
284
285 fn next(&mut self) -> Option<<Self as Iterator>::Item> {
286 match self.lines.next() {
287 Some((nb, line)) => Some(match line {
288 Ok(line) => match <MountEntry as FromStr>::from_str(line.as_ref()) {
289 Ok(m) => Ok(m),
290 Err(e) => Err(ParseError::new(format!("Failed at line {}: {}", nb, e))),
291 },
292 Err(e) => Err(From::from(e)),
293 }),
294 None => None,
295 }
296 }
297}
298
299
300#[cfg(test)]
301mod test {
302 use std::fs::File;
303 use std::io::{BufReader, BufRead, Cursor};
304 use std::path::{Path, PathBuf};
305 use std::str::FromStr;
306 use super::{DumpField, MntOps, MountEntry, MountIter, MountParam, get_mount_from, get_submounts_from};
307
308 #[test]
309 fn test_line_root() {
310 let root_ref = MountEntry {
311 spec: "rootfs".to_string(),
312 file: PathBuf::from("/"),
313 vfstype: "rootfs".to_string(),
314 mntops: vec!(MntOps::Write(true)),
315 freq: DumpField::Ignore,
316 passno: None,
317 };
318 let from_str = <MountEntry as FromStr>::from_str;
319 assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
320 assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
321 assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
322 assert_eq!(from_str("rootfs / rootfs rw, 0 0"), Ok(root_ref.clone()));
323 }
324
325 #[test]
326 fn test_line_mntops() {
327 let root_ref = MountEntry {
328 spec: "rootfs".to_string(),
329 file: PathBuf::from("/"),
330 vfstype: "rootfs".to_string(),
331 mntops: vec!(MntOps::Exec(false), MntOps::Write(true)),
332 freq: DumpField::Ignore,
333 passno: None,
334 };
335 let from_str = <MountEntry as FromStr>::from_str;
336 assert_eq!(from_str("rootfs / rootfs noexec,rw 0 0"), Ok(root_ref.clone()));
337 }
338
339 fn test_file<T>(path: T) -> Result<(), String> where T: AsRef<Path> {
340 let file = match File::open(&path) {
341 Ok(f) => f,
342 Err(e) => return Err(format!("Failed to open {}: {}", path.as_ref().display(), e)),
343 };
344 let mount = BufReader::new(file);
345 for line in mount.lines() {
346 let line = match line {
347 Ok(l) => l,
348 Err(e) => return Err(format!("Failed to read line: {}", e)),
349 };
350 match <MountEntry as FromStr>::from_str(line.as_ref()) {
351 Ok(_) => {},
352 Err(e) => return Err(format!("Error for `{}`: {}", line.trim(), e)),
353 }
354 }
355 Ok(())
356 }
357
358 #[test]
359 fn test_proc_mounts() {
360 assert!(test_file("/proc/mounts").is_ok());
361 }
362
363 #[test]
364 fn test_path() {
365 let from_str = <MountEntry as FromStr>::from_str;
366 assert!(from_str("rootfs ./ rootfs rw 0 0").is_err());
367 assert!(from_str("rootfs foo rootfs rw 0 0").is_err());
368 assert!(from_str("/dev/mapper/swap none swap sw 0 0").is_err());
370 }
371
372 #[test]
373 fn test_proc_mounts_from() {
374 use super::MntOps::*;
375 use super::DumpField::*;
376
377 let buf = Cursor::new(b"\
378 rootfs / rootfs rw 0 0\n\
379 sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0\n\
380 tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0\n\
381 udev /dev devtmpfs rw,relatime,size=10240k,nr_inodes=505357,mode=755 0 0\n\
382 tmpfs /run tmpfs rw,nosuid,relatime,size=809928k,mode=755 0 0\n\
383 /dev/mapper/foo-tmp /var/tmp ext4 rw,relatime,data=ordered 0 0\n\
384 ".as_ref());
385 let mount_vartmp = MountEntry {
387 spec: "/dev/mapper/foo-tmp".to_string(),
388 file: PathBuf::from("/var/tmp"),
389 vfstype: "ext4".to_string(),
390 mntops: vec![Write(true), RelAtime(true), Extra("data=ordered".to_string())],
391 freq: Ignore,
392 passno: None
393 };
394 let mount_root = MountEntry {
395 spec: "rootfs".to_string(),
396 file: PathBuf::from("/"),
397 vfstype: "rootfs".to_string(),
398 mntops: vec![Write(true)],
399 freq: Ignore,
400 passno: None
401 };
402 let mount_sysfs = MountEntry {
403 spec: "sysfs".to_string(),
404 file: PathBuf::from("/sys"),
405 vfstype: "sysfs".to_string(),
406 mntops: vec![Write(true), Suid(false), Dev(false), Exec(false), RelAtime(true)],
407 freq: Ignore,
408 passno: None
409 };
410 let mount_tmp = MountEntry {
411 spec: "tmpfs".to_string(),
412 file: PathBuf::from("/sys/fs/cgroup"),
413 vfstype: "tmpfs".to_string(),
414 mntops: vec![Write(false), Suid(false), Dev(false), Exec(false), Extra("mode=755".to_string())],
415 freq: Ignore,
416 passno: None
417 };
418 let mounts_all = vec!(
419 mount_root.clone(),
420 mount_sysfs.clone(),
421 mount_tmp.clone(),
422 MountEntry {
423 spec: "udev".to_string(),
424 file: PathBuf::from("/dev"),
425 vfstype: "devtmpfs".to_string(),
426 mntops: vec![Write(true), RelAtime(true), Extra("size=10240k".to_string()), Extra("nr_inodes=505357".to_string()), Extra("mode=755".to_string())],
427 freq: Ignore,
428 passno: None
429 },
430 MountEntry {
431 spec: "tmpfs".to_string(),
432 file: PathBuf::from("/run"),
433 vfstype: "tmpfs".to_string(),
434 mntops: vec![Write(true), Suid(false), RelAtime(true), Extra("size=809928k".to_string()), Extra("mode=755".to_string())],
435 freq: Ignore,
436 passno: None
437 },
438 mount_vartmp.clone()
439 );
440 let mounts = MountIter::new(buf.clone());
441 assert_eq!(mounts.map(|x| x.unwrap() ).collect::<Vec<_>>(), mounts_all.clone());
442 let mounts = MountIter::new(buf.clone());
443 assert_eq!(get_submounts_from("/", mounts).ok(), Some(mounts_all.clone()));
444 let mounts = MountIter::new(buf.clone());
445 assert_eq!(get_submounts_from("/var/tmp", mounts).ok(), Some(vec!(mount_vartmp.clone())));
446 let mounts = MountIter::new(buf.clone());
447 assert_eq!(get_mount_from("/var/tmp/bar", mounts).ok(), Some(Some(mount_vartmp.clone())));
448 let mounts = MountIter::new(buf.clone());
449 assert_eq!(get_mount_from("/var/", mounts).ok(), Some(Some(mount_root.clone())));
450
451 let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
453 assert_eq!(mounts.find(|m|
454 m.contains(&MountParam::Spec("rootfs"))
455 ).unwrap(), mount_root.clone());
456 let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
457 assert_eq!(mounts.find(|m|
458 m.contains(&MountParam::File(Path::new("/")))
459 ).unwrap(), mount_root.clone());
460 let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
461 assert_eq!(mounts.find(|m|
462 m.contains(&MountParam::VfsType("tmpfs"))
463 ).unwrap(), mount_tmp.clone());
464 let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
465 let mnt_ops = [MntOps::Write(true), MntOps::Suid(false), MntOps::Dev(false), MntOps::Exec(false)];
466 assert_eq!(mounts.find(|m| {
467 mnt_ops.iter().all( |o| m.contains(&MountParam::MntOps(o)) )
468 }).unwrap(), mount_sysfs.clone());
469
470 let mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());
471 assert_eq!(mounts.filter(|m|
472 m.contains(&MountParam::Freq(&DumpField::Ignore))
473 ).collect::<Vec<_>>(), mounts_all.clone());
474 let mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());
475 assert_eq!(mounts.filter(|m|
476 m.contains(&MountParam::PassNo(&None))
477 ).collect::<Vec<_>>(), mounts_all.clone());
478 }
479}