extern crate libc;
use error::*;
use self::libc::c_int;
use std::cmp::Ordering;
use std::convert::{AsRef, From};
use std::fmt;
use std::fs::File;
use std::io::{BufReader, BufRead, Lines};
use std::iter::Enumerate;
use std::path::{Path, PathBuf};
use std::str::FromStr;
const PROC_MOUNTS: &'static str = "/proc/mounts";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DumpField {
Ignore = 0,
Backup = 1,
}
pub type PassField = Option<c_int>;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum MntOps {
Atime(bool),
DirAtime(bool),
RelAtime(bool),
Dev(bool),
Exec(bool),
Suid(bool),
Write(bool),
Extra(String),
}
impl FromStr for MntOps {
type Err = LineError;
fn from_str(token: &str) -> Result<MntOps, LineError> {
Ok(match token {
"atime" => MntOps::Atime(true),
"noatime" => MntOps::Atime(false),
"diratime" => MntOps::DirAtime(true),
"nodiratime" => MntOps::DirAtime(false),
"relatime" => MntOps::RelAtime(true),
"norelatime" => MntOps::RelAtime(false),
"dev" => MntOps::Dev(true),
"nodev" => MntOps::Dev(false),
"exec" => MntOps::Exec(true),
"noexec" => MntOps::Exec(false),
"suid" => MntOps::Suid(true),
"nosuid" => MntOps::Suid(false),
"rw" => MntOps::Write(true),
"ro" => MntOps::Write(false),
extra => MntOps::Extra(extra.to_string()),
})
}
}
#[derive(Clone, Debug)]
pub enum MountParam<'a> {
Spec(&'a str),
File(&'a Path),
VfsType(&'a str),
MntOps(&'a MntOps),
Freq(&'a DumpField),
PassNo(&'a PassField),
}
#[derive(Clone, PartialEq, Eq)]
pub struct MountEntry {
pub spec: String,
pub file: PathBuf,
pub vfstype: String,
pub mntops: Vec<MntOps>,
pub freq: DumpField,
pub passno: PassField,
}
impl MountEntry {
pub fn contains(&self, search: &MountParam) -> bool {
match search {
&MountParam::Spec(spec) => spec == &self.spec,
&MountParam::File(file) => file == &self.file,
&MountParam::VfsType(vfstype) => vfstype == &self.vfstype,
&MountParam::MntOps(mntops) => self.mntops.contains(mntops),
&MountParam::Freq(dumpfield) => dumpfield == &self.freq,
&MountParam::PassNo(passno) => passno == &self.passno,
}
}
}
impl FromStr for MountEntry {
type Err = LineError;
fn from_str(line: &str) -> Result<MountEntry, LineError> {
let line = line.trim();
let mut tokens = line.split_terminator(|s: char| { s == ' ' || s == '\t' })
.filter(|s| { s != &"" } );
Ok(MountEntry {
spec: try!(tokens.next().ok_or(LineError::MissingSpec)).to_string(),
file: {
let file = try!(tokens.next().ok_or(LineError::MissingFile));
let path = PathBuf::from(file);
if path.is_relative() {
return Err(LineError::InvalidFilePath(file.into()));
}
path
},
vfstype: try!(tokens.next().ok_or(LineError::MissingVfstype)).to_string(),
mntops: try!(tokens.next().ok_or(LineError::MissingMntops))
.split_terminator(',').map(|x| { FromStr::from_str(x).unwrap() }).collect(),
freq: {
let freq = try!(tokens.next().ok_or(LineError::MissingFreq));
match FromStr::from_str(freq) {
Ok(0) => DumpField::Ignore,
Ok(1) => DumpField::Backup,
_ => return Err(LineError::InvalidFreq(freq.into())),
}
},
passno: {
let passno = try!(tokens.next().ok_or(LineError::MissingPassno));
match FromStr::from_str(passno) {
Ok(0) => None,
Ok(f) if f > 0 => Some(f),
_ => return Err(LineError::InvalidPassno(passno.into())),
}
},
})
}
}
pub fn get_submounts_from<T, U>(root: T, iter: MountIter<U>)
-> Result<Vec<MountEntry>, ParseError> where T: AsRef<Path>, U: BufRead {
let mut ret = vec!();
for mount in iter {
match mount {
Ok(m) => if m.file.starts_with(&root) {
ret.push(m);
},
Err(e) => return Err(e),
}
}
Ok(ret)
}
pub fn get_submounts<T>(root: T) -> Result<Vec<MountEntry>, ParseError> where T: AsRef<Path> {
get_submounts_from(root, try!(MountIter::new_from_proc()))
}
pub fn get_mount_from<T, U>(target: T, iter: MountIter<U>)
-> Result<Option<MountEntry>, ParseError> where T: AsRef<Path>, U: BufRead {
let mut ret = None;
for mount in iter {
match mount {
Ok(m) => if target.as_ref().starts_with(&m.file) {
ret = Some(m);
},
Err(e) => return Err(e),
}
}
Ok(ret)
}
pub fn get_mount<T>(target: T) -> Result<Option<MountEntry>, ParseError> where T: AsRef<Path> {
get_mount_from(target, try!(MountIter::new_from_proc()))
}
pub fn get_mount_writable<T>(target: T, writable: bool) -> Option<MountEntry> where T: AsRef<Path> {
match get_mount(target) {
Ok(Some(m)) => {
if !writable || m.mntops.contains(&MntOps::Write(writable)) {
Some(m)
} else {
None
}
}
_ => None,
}
}
pub trait VecMountEntry {
fn remove_overlaps<T>(self, exclude_files: &Vec<T>) -> Self where T: AsRef<Path>;
}
impl VecMountEntry for Vec<MountEntry> {
fn remove_overlaps<T>(self, exclude_files: &Vec<T>) -> Vec<MountEntry> where T: AsRef<Path> {
let mut sorted: Vec<MountEntry> = vec!();
let root = Path::new("/");
'list: for mount in self.into_iter().rev() {
if AsRef::<Path>::as_ref(&mount.file) == root {
continue 'list;
}
let mut has_overlaps = false;
'filter: for mount_sorted in sorted.iter() {
if exclude_files.iter().skip_while(|x|
AsRef::<Path>::as_ref(&mount_sorted.file) != x.as_ref()).next().is_some() {
continue 'filter;
}
if mount.file.starts_with(&mount_sorted.file) {
has_overlaps = true;
break 'filter;
}
}
if !has_overlaps {
sorted.push(mount);
}
}
sorted.reverse();
sorted
}
}
impl fmt::Debug for MountEntry {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "MountEntry {{ spec: {:?}, file: {:?}, vfstype: {:?}, mntops: {:?}, freq: {:?}, passno: {:?} }}",
self.spec, self.file.display(), self.vfstype, self.mntops, self.freq, self.passno)
}
}
impl PartialOrd for MountEntry {
fn partial_cmp(&self, other: &MountEntry) -> Option<Ordering> {
self.file.partial_cmp(&other.file)
}
}
impl Ord for MountEntry {
fn cmp(&self, other: &MountEntry) -> Ordering {
self.file.cmp(&other.file)
}
}
pub struct MountIter<T> {
lines: Enumerate<Lines<T>>,
}
impl<T> MountIter<T> where T: BufRead {
pub fn new(mtab: T) -> MountIter<T> {
MountIter {
lines: mtab.lines().enumerate(),
}
}
}
impl MountIter<BufReader<File>> {
pub fn new_from_proc() -> Result<MountIter<BufReader<File>>, ParseError> {
let file = try!(File::open(PROC_MOUNTS));
Ok(MountIter::new(BufReader::new(file)))
}
}
impl<T> Iterator for MountIter<T> where T: BufRead {
type Item = Result<MountEntry, ParseError>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
match self.lines.next() {
Some((nb, line)) => Some(match line {
Ok(line) => match <MountEntry as FromStr>::from_str(line.as_ref()) {
Ok(m) => Ok(m),
Err(e) => Err(ParseError::new(format!("Failed at line {}: {}", nb, e))),
},
Err(e) => Err(From::from(e)),
}),
None => None,
}
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use std::io::{BufReader, BufRead, Cursor};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use super::{DumpField, MntOps, MountEntry, MountIter, MountParam, get_mount_from, get_submounts_from};
#[test]
fn test_line_root() {
let root_ref = MountEntry {
spec: "rootfs".to_string(),
file: PathBuf::from("/"),
vfstype: "rootfs".to_string(),
mntops: vec!(MntOps::Write(true)),
freq: DumpField::Ignore,
passno: None,
};
let from_str = <MountEntry as FromStr>::from_str;
assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
assert_eq!(from_str("rootfs / rootfs rw 0 0"), Ok(root_ref.clone()));
assert_eq!(from_str("rootfs / rootfs rw, 0 0"), Ok(root_ref.clone()));
}
#[test]
fn test_line_mntops() {
let root_ref = MountEntry {
spec: "rootfs".to_string(),
file: PathBuf::from("/"),
vfstype: "rootfs".to_string(),
mntops: vec!(MntOps::Exec(false), MntOps::Write(true)),
freq: DumpField::Ignore,
passno: None,
};
let from_str = <MountEntry as FromStr>::from_str;
assert_eq!(from_str("rootfs / rootfs noexec,rw 0 0"), Ok(root_ref.clone()));
}
fn test_file<T>(path: T) -> Result<(), String> where T: AsRef<Path> {
let file = match File::open(&path) {
Ok(f) => f,
Err(e) => return Err(format!("Failed to open {}: {}", path.as_ref().display(), e)),
};
let mount = BufReader::new(file);
for line in mount.lines() {
let line = match line {
Ok(l) => l,
Err(e) => return Err(format!("Failed to read line: {}", e)),
};
match <MountEntry as FromStr>::from_str(line.as_ref()) {
Ok(_) => {},
Err(e) => return Err(format!("Error for `{}`: {}", line.trim(), e)),
}
}
Ok(())
}
#[test]
fn test_proc_mounts() {
assert!(test_file("/proc/mounts").is_ok());
}
#[test]
fn test_path() {
let from_str = <MountEntry as FromStr>::from_str;
assert!(from_str("rootfs ./ rootfs rw 0 0").is_err());
assert!(from_str("rootfs foo rootfs rw 0 0").is_err());
assert!(from_str("/dev/mapper/swap none swap sw 0 0").is_err());
}
#[test]
fn test_proc_mounts_from() {
use super::MntOps::*;
use super::DumpField::*;
let buf = Cursor::new(b"\
rootfs / rootfs rw 0 0\n\
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0\n\
tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0\n\
udev /dev devtmpfs rw,relatime,size=10240k,nr_inodes=505357,mode=755 0 0\n\
tmpfs /run tmpfs rw,nosuid,relatime,size=809928k,mode=755 0 0\n\
/dev/mapper/foo-tmp /var/tmp ext4 rw,relatime,data=ordered 0 0\n\
".as_ref());
let mount_vartmp = MountEntry {
spec: "/dev/mapper/foo-tmp".to_string(),
file: PathBuf::from("/var/tmp"),
vfstype: "ext4".to_string(),
mntops: vec![Write(true), RelAtime(true), Extra("data=ordered".to_string())],
freq: Ignore,
passno: None
};
let mount_root = MountEntry {
spec: "rootfs".to_string(),
file: PathBuf::from("/"),
vfstype: "rootfs".to_string(),
mntops: vec![Write(true)],
freq: Ignore,
passno: None
};
let mount_sysfs = MountEntry {
spec: "sysfs".to_string(),
file: PathBuf::from("/sys"),
vfstype: "sysfs".to_string(),
mntops: vec![Write(true), Suid(false), Dev(false), Exec(false), RelAtime(true)],
freq: Ignore,
passno: None
};
let mount_tmp = MountEntry {
spec: "tmpfs".to_string(),
file: PathBuf::from("/sys/fs/cgroup"),
vfstype: "tmpfs".to_string(),
mntops: vec![Write(false), Suid(false), Dev(false), Exec(false), Extra("mode=755".to_string())],
freq: Ignore,
passno: None
};
let mounts_all = vec!(
mount_root.clone(),
mount_sysfs.clone(),
mount_tmp.clone(),
MountEntry {
spec: "udev".to_string(),
file: PathBuf::from("/dev"),
vfstype: "devtmpfs".to_string(),
mntops: vec![Write(true), RelAtime(true), Extra("size=10240k".to_string()), Extra("nr_inodes=505357".to_string()), Extra("mode=755".to_string())],
freq: Ignore,
passno: None
},
MountEntry {
spec: "tmpfs".to_string(),
file: PathBuf::from("/run"),
vfstype: "tmpfs".to_string(),
mntops: vec![Write(true), Suid(false), RelAtime(true), Extra("size=809928k".to_string()), Extra("mode=755".to_string())],
freq: Ignore,
passno: None
},
mount_vartmp.clone()
);
let mounts = MountIter::new(buf.clone());
assert_eq!(mounts.map(|x| x.unwrap() ).collect::<Vec<_>>(), mounts_all.clone());
let mounts = MountIter::new(buf.clone());
assert_eq!(get_submounts_from("/", mounts).ok(), Some(mounts_all.clone()));
let mounts = MountIter::new(buf.clone());
assert_eq!(get_submounts_from("/var/tmp", mounts).ok(), Some(vec!(mount_vartmp.clone())));
let mounts = MountIter::new(buf.clone());
assert_eq!(get_mount_from("/var/tmp/bar", mounts).ok(), Some(Some(mount_vartmp.clone())));
let mounts = MountIter::new(buf.clone());
assert_eq!(get_mount_from("/var/", mounts).ok(), Some(Some(mount_root.clone())));
let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
assert_eq!(mounts.find(|m|
m.contains(&MountParam::Spec("rootfs"))
).unwrap(), mount_root.clone());
let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
assert_eq!(mounts.find(|m|
m.contains(&MountParam::File(Path::new("/")))
).unwrap(), mount_root.clone());
let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
assert_eq!(mounts.find(|m|
m.contains(&MountParam::VfsType("tmpfs"))
).unwrap(), mount_tmp.clone());
let mut mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());;
let mnt_ops = [MntOps::Write(true), MntOps::Suid(false), MntOps::Dev(false), MntOps::Exec(false)];
assert_eq!(mounts.find(|m| {
mnt_ops.iter().all( |o| m.contains(&MountParam::MntOps(o)) )
}).unwrap(), mount_sysfs.clone());
let mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());
assert_eq!(mounts.filter(|m|
m.contains(&MountParam::Freq(&DumpField::Ignore))
).collect::<Vec<_>>(), mounts_all.clone());
let mounts = MountIter::new(buf.clone()).map(|m| m.ok().unwrap());
assert_eq!(mounts.filter(|m|
m.contains(&MountParam::PassNo(&None))
).collect::<Vec<_>>(), mounts_all.clone());
}
}