shitbox/mount/src/mntent.rs

252 lines
8.6 KiB
Rust

#![allow(clippy::must_use_candidate)]
//! A Rust reimplementation of libc's mntent, for parsing /etc/fstab or
//! /proc/mounts
use super::{MntEntries, MntFlags};
use blkid::{
cache::Cache,
tag::{PartitionTag, SuperblockTag, TagType},
};
use std::{
error::Error,
fmt::{self, Write},
fs::File,
io::{self, BufRead, BufReader},
path::PathBuf,
str::FromStr,
};
/// The information for a mount broken out into a struct
#[derive(Clone)]
pub struct MntEntry {
pub fsname: String,
pub dir: String,
pub fstype: String,
pub opts: String,
pub freq: i32,
pub passno: i32,
}
impl MntEntry {
/// Mount the mount specified by this entry
pub fn mount(&self) -> Result<(), Box<dyn Error>> {
let dev = self.device()?;
let dev = dev.to_string_lossy();
let (flags, opts) = self
.parse_opts()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
super::mount(&dev, &self.dir, &self.fstype, flags, Some(&opts))?;
Ok(())
}
pub fn is_mounted(&self) -> bool {
if let Ok(entries) = MntEntries::new("/proc/mounts") {
for e in entries {
if self.dir == e.dir && self.fstype == e.fstype {
return true;
}
}
}
false
}
pub fn is_swapped(&self) -> bool {
if self.fstype != "swap" {
return false;
}
let dev = self.device().map(|x| x.to_str().map(ToString::to_string));
let Ok(Some(dev)) = dev else { return false };
let Ok(fd) = File::open("/proc/swaps") else {
return false;
};
let reader = BufReader::new(fd);
for line in reader.lines() {
let Ok(line) = line else {
return false;
};
let swap = line.split_whitespace().next();
if let Some(swap) = swap {
if swap == dev {
return true;
}
}
}
false
}
pub fn parse_opts(&self) -> Result<(u32, String), fmt::Error> {
let mut res = (0, String::new());
let splitopts = self.opts.split(',');
for opt in splitopts {
match opt.parse::<MntFlags>() {
Ok(f) => res.0 |= f,
Err(_) => match opt {
"async" => res.0 &= !MntFlags::Synchronous,
"atime" => res.0 &= !MntFlags::NoAtime,
"dev" => res.0 &= !MntFlags::NoDev,
"diratime" => res.0 &= !MntFlags::NoDirAtime,
"exec" => res.0 &= !MntFlags::NoExec,
"group" => res.0 &= MntFlags::NoSuid | MntFlags::NoDev,
"noiversion" => res.0 &= !MntFlags::IVersion,
"mand" => res.0 &= !MntFlags::NoRemoteLock,
"norelatime" => res.0 &= !MntFlags::RelAtime,
"nostrictatime" => res.0 &= !MntFlags::StrictAtime,
"nolazytime" => res.0 &= !MntFlags::LazyTime,
"loud" => res.0 &= !MntFlags::Silent,
"owner" => res.0 |= MntFlags::NoSuid | MntFlags::NoDev,
"rw" => res.0 &= !MntFlags::ReadOnly,
"user" => {
res.0 &= !MntFlags::NoUser;
res.0 |= MntFlags::NoExec | MntFlags::NoSuid | MntFlags::NoDev;
}
_ => {
if res.1.is_empty() {
write!(res.1, "{opt}")?;
} else {
write!(res.1, ",{opt}")?;
}
}
},
}
}
Ok(res)
}
/// Get the canonical path to the device pointed to by this entry
pub fn device(&self) -> io::Result<PathBuf> {
match self.fsname.as_str() {
s if s.starts_with("UUID=") => dev_from_uuid(s),
s if s.starts_with("LABEL=") => dev_from_label(s),
s if s.starts_with("PARTUUID=") => dev_from_partuuid(s),
s if s.starts_with("PARTLABEL=") => dev_from_partlabel(s),
s => PathBuf::from(s).canonicalize(),
}
}
}
impl FromStr for MntEntry {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = match s.split_once('#') {
Some((lhs, _)) => lhs,
None => s,
};
let mut split = s.split_whitespace();
let Some(fsname) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
let Some(dir) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
let Some(fstype) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
let Some(opts) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
let Some(freq) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
let Some(passno) = split.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "missing field"));
};
if split.next().is_some() {
return Err(io::Error::new(io::ErrorKind::Other, "extra field"));
}
Ok(Self {
fsname: fsname.to_string(),
dir: dir.to_string(),
fstype: fstype.to_string(),
opts: opts.to_string(),
freq: freq
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?,
passno: passno
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?,
})
}
}
fn dev_from_uuid(s: &str) -> io::Result<PathBuf> {
let uuid = s.strip_prefix("UUID=").ok_or(io::Error::new(
io::ErrorKind::Other,
"this should never happen",
))?;
let cache = Cache::new().map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
cache
.probe_all()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let devs = cache.devs();
for dev in devs {
let tags = dev.tags();
for tag in tags {
if let TagType::Superblock(SuperblockTag::Uuid) = tag.typ() {
if uuid.to_lowercase().as_str().trim() == tag.value().trim() {
return dev.name().canonicalize();
}
}
}
}
Err(io::Error::new(io::ErrorKind::Other, "device not found"))
}
fn dev_from_label(s: &str) -> io::Result<PathBuf> {
let label = s.strip_prefix("LABEL=").ok_or(io::Error::new(
io::ErrorKind::Other,
"this should never happen",
))?;
let cache = Cache::new().map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
let devs = cache.devs();
for dev in devs {
let tags = dev.tags();
for tag in tags {
if let TagType::Superblock(SuperblockTag::Label) = tag.typ() {
if label == tag.value() {
return dev.name().canonicalize();
}
}
}
}
Err(io::Error::new(io::ErrorKind::Other, "device not found"))
}
fn dev_from_partuuid(s: &str) -> io::Result<PathBuf> {
let partlabel = s.strip_prefix("PARTUUID=").ok_or(io::Error::new(
io::ErrorKind::Other,
"this should never happen",
))?;
let cache = Cache::new().map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
let devs = cache.devs();
for dev in devs {
let tags = dev.tags();
for tag in tags {
if let TagType::Partition(PartitionTag::PartEntryUuid) = tag.typ() {
if partlabel == tag.value() {
return dev.name().canonicalize();
}
}
}
}
Err(io::Error::new(io::ErrorKind::Other, "device not found"))
}
fn dev_from_partlabel(s: &str) -> io::Result<PathBuf> {
let partlabel = s.strip_prefix("PARTLABEL=").ok_or(io::Error::new(
io::ErrorKind::Other,
"this should never happen",
))?;
let cache = Cache::new().map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
let devs = cache.devs();
for dev in devs {
let tags = dev.tags();
for tag in tags {
if let TagType::Partition(PartitionTag::PartEntryName) = tag.typ() {
if partlabel == tag.value() {
return dev.name().canonicalize();
}
}
}
}
Err(io::Error::new(io::ErrorKind::Other, "device not found"))
}