252 lines
8.6 KiB
Rust
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"))
|
|
}
|