use std::str::FromStr;
use std::path::{Path, PathBuf};
use std::io::{self, BufRead};
use std::fs::File;
use std::fmt;
use regex::Regex;
#[derive(Debug, PartialEq)]
pub enum FsType {
Proc,
Overlay,
Tmpfs,
Sysfs,
Btrfs,
Ext2,
Ext3,
Ext4,
Devtmpfs,
Other(String)
}
impl FromStr for FsType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"proc" => Ok(FsType::Proc),
"tmpfs" => Ok(FsType::Tmpfs),
"overlay" => Ok(FsType::Overlay),
"sysfs" => Ok(FsType::Sysfs),
"btrfs" => Ok(FsType::Btrfs),
"ext2" => Ok(FsType::Ext2),
"ext3" => Ok(FsType::Ext3),
"ext4" => Ok(FsType::Ext4),
"devtmpfs" => Ok(FsType::Devtmpfs),
_ => Ok(FsType::Other(s.to_string()))
}
}
}
impl fmt::Display for FsType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fsname = match self {
FsType::Proc => "proc",
FsType::Overlay => "overlay",
FsType::Tmpfs => "tmpfs",
FsType::Sysfs => "sysfs",
FsType::Btrfs => "btrfs",
FsType::Ext2 => "ext2",
FsType::Ext3 => "ext3",
FsType::Ext4 => "ext4",
FsType::Devtmpfs => "devtmpfs",
FsType::Other(ref fsname) => fsname
};
write!(f, "{}", fsname)
}
}
#[derive(Debug)]
pub struct MountPoint {
pub id: Option<u32>,
pub parent_id: Option<u32>,
pub root: Option<PathBuf>,
pub what: String,
pub path: PathBuf,
pub fstype: FsType,
pub options: MountOptions,
}
impl MountPoint {
fn parse_proc_mountinfo_line(line: &String) -> Result<Self, io::Error> {
let re = Regex::new(r"(\d*)\s(\d*)\s(\d*:\d*)\s([\S]*)\s([\S]*)\s([A-Za-z0-9,]*)\s([A-Za-z0-9:\s]*)\s\- ([\S]*)\s([\S]*)(.*)").unwrap();
if !re.is_match(line) {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid format"));
}
let caps = re.captures(line).unwrap();
Ok(MountPoint {
id: Some(caps[1].parse::<u32>().unwrap()),
parent_id: Some(caps[2].parse::<u32>().unwrap()),
root: Some(PathBuf::from(caps[4].to_string())),
path: PathBuf::from(caps[5].to_string()),
options: MountOptions::new(&caps[6].to_string()),
fstype: FsType::from_str(&caps[8]).unwrap(),
what: caps[9].to_string()
})
}
}
#[derive(Debug)]
#[derive(PartialEq)]
pub enum ReadWrite {
ReadOnly,
ReadWrite
}
#[derive(Debug)]
pub struct MountOptions {
pub read_write: ReadWrite,
pub others: Vec<String>
}
impl MountOptions {
pub fn new(options: &str) -> Self {
let mut read_write = ReadWrite::ReadOnly;
let mut others = Vec::new();
for option in options.split(',') {
match option {
"ro" => read_write = ReadWrite::ReadOnly,
"rw" => read_write = ReadWrite::ReadWrite,
&_ => others.push(option.to_owned())
}
}
MountOptions {
read_write,
others
}
}
}
#[derive(Debug)]
pub struct MountInfo {
pub mounting_points: Vec<MountPoint>
}
impl MountInfo {
const MOUNT_INFO_FILE: &'static str = "/proc/self/mountinfo";
const MTAB_FILE: &'static str = "/etc/mtab";
pub fn new() -> Result<Self, io::Error> {
if Path::new(MountInfo::MOUNT_INFO_FILE).exists() {
let mut mtab = File::open("/proc/self/mountinfo")?;
return Ok(MountInfo {
mounting_points: MountInfo::parse_proc_mountinfo(&mut mtab)?
})
}
else if Path::new(MountInfo::MTAB_FILE).exists() {
let mut mtab = File::open(MountInfo::MTAB_FILE)?;
return Ok(MountInfo {
mounting_points: MountInfo::parse_mtab(&mut mtab)?
})
}
else {
return Err(io::Error::new(io::ErrorKind::NotFound, "No mountinfo file found"))
}
}
pub fn contains<P: AsRef<Path>>(&self, mounting_point: P, fstype: FsType) -> bool {
let path = mounting_point.as_ref();
let filtered: Vec<&MountPoint> = self.mounting_points.iter().
filter(|mts|
mts.path == path.to_owned() && mts.fstype == fstype)
.collect();
filtered.len() > 0
}
pub fn is_mounted<P: AsRef<Path>>(&self, path: P) -> bool {
let filtered: Vec<&MountPoint> = self.mounting_points.iter().
filter(|mts|
mts.path == path.as_ref().to_path_buf())
.collect();
filtered.len() > 0
}
fn parse_proc_mountinfo(file: &mut dyn std::io::Read) -> Result<Vec<MountPoint>, std::io::Error> {
let mut result = Vec::new();
let reader = io::BufReader::new(file);
for line in reader.lines() {
let mpoint = MountPoint::parse_proc_mountinfo_line(&line?)?;
result.push(mpoint);
}
Ok(result)
}
fn parse_mtab(file: &mut dyn std::io::Read) -> Result<Vec<MountPoint>, std::io::Error> {
let mut results: Vec<MountPoint> = vec![];
let reader = io::BufReader::new(file);
for line in reader.lines() {
let l = line?;
let parts: Vec<&str> = l.split_whitespace().collect();
if !parts.is_empty() {
results.push(MountPoint {
what: parts[0].to_string(),
path: PathBuf::from(parts[1]),
fstype: FsType::from_str(parts[2]).unwrap(),
options: MountOptions::new(parts[3]),
id: None,
parent_id: None,
root: None,
})
}
}
Ok(results)
}
}
#[cfg(test)]
mod test {
use super::*;
struct FakeFile {
s: String,
read: bool
}
impl io::Read for FakeFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.read {
return Ok(0);
}
let mut i = 0;
while i < self.s.len() {
buf[i] = self.s.as_bytes()[i];
i += 1;
}
self.read = true;
Ok(buf.len())
}
}
#[test]
fn test_load_mount_points() {
let mut file = FakeFile { s: "tmpfs /tmp tmpfs rw,seclabel,nosuid,nodev,size=8026512k,nr_inodes=1048576,inode64 0 0".to_owned(), read: false };
let munt_points = MountInfo::parse_mtab(&mut file).unwrap();
assert_eq!(munt_points.len(), 1);
assert_eq!(munt_points[0].what, "tmpfs".to_owned());
assert_eq!(munt_points[0].path, PathBuf::from("/tmp"));
assert_eq!(munt_points[0].fstype, FsType::Tmpfs);
}
#[test]
fn test_contains() {
let mut file = FakeFile { s: "tmpfs /tmp tmpfs rw,seclabel,nosuid,nodev,size=8026512k,nr_inodes=1048576,inode64 0 0".to_owned(), read: false };
let mtab = MountInfo { mounting_points: MountInfo::parse_mtab(&mut file).unwrap() };
assert_eq!(mtab.contains("/tmp", FsType::Tmpfs), true);
}
#[test]
fn test_is_mounted() {
let mut file = FakeFile { s: "tmpfs /tmp tmpfs rw,seclabel,nosuid,nodev,size=8026512k,nr_inodes=1048576,inode64 0 0".to_owned(), read: false };
let mtab = MountInfo { mounting_points: MountInfo::parse_mtab(&mut file).unwrap() };
assert_eq!(mtab.is_mounted("/tmp"), true);
}
#[test]
fn test_mount_options() {
let options = MountOptions::new("rw,seclabel,nosuid,nodev,size=8026512k,nr_inodes=1048576,inode64");
assert_eq!(options.read_write, ReadWrite::ReadWrite);
assert_ne!(options.others.len(), 0);
let more_options = MountOptions::new("ro,seclabel,nosuid,nodev,size=8026512k,nr_inodes=1048576,inode64");
assert_eq!(more_options.read_write, ReadWrite::ReadOnly);
assert_ne!(more_options.others.len(), 0);
}
}