use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::path::PathBuf;
use std::str::{self, FromStr};
use std::io::{Error, ErrorKind};
use libc::pid_t;
use nom::{Err, IResult, Needed};
use nom::ErrorKind::Tag;
use parsers::{map_result, parse_isize, parse_usize};
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Mountinfo {
pub mount_id: isize,
pub parent_id: isize,
pub major: usize,
pub minor: usize,
pub root: PathBuf,
pub mount_point: PathBuf,
pub mount_options: Vec<MountOption>,
pub opt_fields: Vec<OptionalField>,
pub fs_type: (String, Option<String>),
pub mount_src: Option<String>,
pub super_opts: Vec<String>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum OptionalField {
Shared(usize),
Master(usize),
PropagateFrom(usize),
Unbindable,
Private
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum MountOption {
Noatime,
Nodev,
Nodiratime,
Noexec,
Nosuid,
Relatime,
Ro,
Rw,
Other(String),
}
named!(space, tag!(" "));
named!(hypen, tag!("-"));
named!(colon, tag!(":"));
named!(dot, tag!("."));
named!(parse_string_field<String>,
map_res!(map_res!(is_not!(" "), str::from_utf8), FromStr::from_str));
fn mount_options(opts: String) -> Vec<MountOption> {
opts.split(",").map(|o|
match o {
"noatime" => MountOption::Noatime,
"nodev" => MountOption::Nodev,
"nodiratime" => MountOption::Nodiratime,
"noexec" => MountOption::Noexec,
"nosuid" => MountOption::Nosuid,
"relatime" => MountOption::Relatime,
"ro" => MountOption::Ro,
"rw" => MountOption::Rw,
x => MountOption::Other(x.into()),
}
).collect()
}
named!(parse_mnt_options<Vec<MountOption> >,
do_parse!(token: parse_string_field >>
(mount_options(token))
)
);
fn opt_fields(fs: &str) -> Result<Vec<OptionalField>> {
let mut v = Vec::new();
for i in fs.split_terminator(' ') {
let t: Vec<&str> = i.split(':').collect();
if t.len() > 2 {
return Err(Error::new(ErrorKind::InvalidInput, "too many colons"));
}
match (t.get(0), t.get(1)) {
(Some(&"shared"), Some(x)) if usize::from_str(x).is_ok() =>
v.push(OptionalField::Shared(usize::from_str(x).unwrap())),
(Some(&"master"), Some(x)) if usize::from_str(x).is_ok() =>
v.push(OptionalField::Master(usize::from_str(x).unwrap())),
(Some(&"propagate_from"), Some(x)) if usize::from_str(x).is_ok() =>
v.push(OptionalField::PropagateFrom(usize::from_str(x).unwrap())),
(Some(&"unbindable"), None) =>
v.push(OptionalField::Unbindable),
(_, _) => return Err(Error::new(ErrorKind::InvalidInput, "invalid optional value")),
};
}
if v.len() == 0 {
v.push(OptionalField::Private);
}
Ok(v)
}
fn parse_opt_fields(input: &[u8]) -> IResult<&[u8], Vec<OptionalField>> {
let mut hypen = None;
for idx in 0..input.len() {
if '-' as u8 == input[idx] {
hypen = Some(idx);
break
}
}
if hypen.is_none() {
return IResult::Incomplete(Needed::Unknown);
}
let term = hypen.unwrap();
let fs = str::from_utf8(&input[0..term]);
match fs {
Err(_) => IResult::Error(Err::Position(Tag, input)),
Ok(f) => match opt_fields(f) {
Err(_) => IResult::Error(Err::Position(Tag, input)),
Ok(r) => IResult::Done(&input[term..], r),
}
}
}
named!(parse_fs_type<(String, Option<String>)>,
do_parse!(k: map_res!(map_res!(take_until_either!(" ."), str::from_utf8), FromStr::from_str) >>
v: opt!(do_parse!(dot >> s: parse_string_field >> (s))) >>
(k, v)
)
);
named!(parse_mount_src<Option<String> >,
do_parse!(src: parse_string_field >>
(if src == "none" { None } else { Some(src) })
)
);
named!(parse_options<Vec<String> >,
do_parse!(token: parse_string_field >>
(token.split(",").map(|s| s.into()).collect())
)
);
named!(parse_mountinfo_entry<Mountinfo>,
do_parse!(mount_id: parse_isize >> space >>
parent_id: parse_isize >> space >>
major: parse_usize >> colon >>
minor: parse_usize >> space >>
root: parse_string_field >> space >>
mount_point: parse_string_field >> space >>
mount_options: parse_mnt_options >> space >>
opt_fields: parse_opt_fields >> hypen >> space >>
fs_type: parse_fs_type >> space >>
mount_src: parse_mount_src >> space >>
super_opts: parse_options >>
( Mountinfo {
mount_id: mount_id,
parent_id: parent_id,
major: major,
minor: minor,
root: root.into(),
mount_point: mount_point.into(),
mount_options: mount_options,
opt_fields: opt_fields,
fs_type: fs_type,
mount_src: mount_src,
super_opts: super_opts,
} )));
fn mountinfo_file(file: &mut File) -> Result<Vec<Mountinfo>> {
let mut r = Vec::new();
for line in BufReader::new(file).lines() {
let mi = try!(map_result(parse_mountinfo_entry(try!(line).as_bytes())));
r.push(mi);
}
Ok(r)
}
pub fn mountinfo(pid: pid_t) -> Result<Vec<Mountinfo>> {
mountinfo_file(&mut try!(File::open(&format!("/proc/{}/mountinfo", pid))))
}
pub fn mountinfo_self() -> Result<Vec<Mountinfo>> {
mountinfo_file(&mut try!(File::open("/proc/self/mountinfo")))
}
pub fn mountinfo_task(process_id: pid_t, thread_id: pid_t) -> Result<Vec<Mountinfo>> {
mountinfo_file(&mut try!(File::open(&format!("/proc/{}/task/{}/mountinfo", process_id, thread_id))))
}
#[cfg(test)]
pub mod tests {
use super::{Mountinfo, MountOption, OptionalField, mountinfo, mountinfo_self, parse_mountinfo_entry};
#[test]
fn test_parse_mountinfo_entry() {
let entry =
b"19 23 0:4 / /proc rw,nosuid,foo shared:13 master:20 - proc.sys proc rw,nosuid";
let got_mi = parse_mountinfo_entry(entry).unwrap().1;
let want_mi = Mountinfo {
mount_id: 19,
parent_id: 23,
major: 0,
minor: 4,
root: "/".into(),
mount_point: "/proc".into(),
mount_options: vec![
MountOption::Rw,
MountOption::Nosuid,
MountOption::Other("foo".to_string())
],
opt_fields: vec![
OptionalField::Shared(13),
OptionalField::Master(20)
],
fs_type: ("proc".to_string(), Some("sys".to_string())),
mount_src: Some("proc".to_string()),
super_opts: vec!["rw","nosuid"].iter().map(|&s| s.into()).collect(),
};
assert_eq!(got_mi, want_mi);
}
#[test]
fn test_parse_mountinfo_error() {
let entry = b"10 - 0:4 / /sys rw master -";
parse_mountinfo_entry(entry).unwrap_err();
}
#[test]
fn test_mountinfo() {
mountinfo_self().unwrap();
mountinfo(1).unwrap();
}
}