shitbox/utilbox/commands/umount/mod.rs
2023-02-06 18:57:28 -05:00

141 lines
5.1 KiB
Rust

use std::io;
use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint};
use mount::MntEntries;
use shitbox::{args, Cmd};
#[derive(Debug, Default)]
pub struct Umount;
impl Cmd for Umount {
fn cli(&self) -> Command {
Command::new("umount")
.about("unmount filesystems")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.args([
Arg::new("all")
.help(
"All of the filesystems described in /proc/self/mountinfo \
are unmounted, except the proc, devfs, devpts, sysfs, rpc_pipefs \
and nfsd filesystems.",
)
.short('a')
.long("all")
.action(ArgAction::SetTrue),
Arg::new("force")
.help("Force an unmount (in case of an unreachable NFS system).")
.short('f')
.long("force")
.action(ArgAction::SetTrue),
Arg::new("types")
.help(
"Indicate that the actions should only be taken on filesystems \
of the specified type.",
)
.long_help(
"Indicate that the actions should only be taken on filesystems \
of the specified type. More than one type may be specified in a \
comma-separated list. The list of filesystem types can be prefixed \
with no to indicate that no action should be taken for all of the \
mentioned types.",
)
.short('t')
.long("types")
.value_delimiter(',')
.value_name("type")
.requires("all")
.num_args(1..),
args::verbose(),
Arg::new("spec")
.value_name("directory|device")
.value_hint(ValueHint::AnyPath)
.num_args(0..)
.required_unless_present("all"),
])
}
fn run(&self, matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let flags = if matches.get_flag("force") {
libc::MNT_FORCE
} else {
0
};
if matches.get_flag("all") {
let mut mntpts: Vec<mount::MntEntry> = MntEntries::new("/proc/mounts")?
.filter(|x| {
x.fstype != "proc"
&& x.fstype != "sysfs"
&& x.fstype != "devfs"
&& x.fstype != "devpts"
&& x.fstype != "rpc_pipefs"
&& x.fstype != "nfsd"
})
.collect();
if let Some(types) = matches.get_many::<String>("types") {
for t in types {
if t.starts_with("no") {
let t = t.strip_prefix("no").unwrap();
mntpts = mntpts
.iter()
.filter(|x| x.fstype != t)
.map(|x| x.clone())
.collect();
} else {
mntpts = mntpts
.iter()
.filter(|x| &x.fstype == t)
.map(|x| x.clone())
.collect();
}
}
}
if let Some(spec) = matches.get_many::<String>("spec") {
for s in spec {
for p in &mntpts {
if &p.dir == s {
mount::umount(s, flags as u32)?;
if matches.get_flag("verbose") {
println!("umount: {s} unmounted");
}
}
}
}
} else {
for p in &mntpts {
mount::umount(&p.dir, flags as u32)?;
if matches.get_flag("verbose") {
println!("umount: {} unmounted", &p.dir);
}
}
}
} else {
if let Some(spec) = matches.get_many::<String>("spec") {
for s in spec {
let mntpt = get_mntpt(s)?;
mount::umount(&mntpt, flags as u32)?;
if matches.get_flag("verbose") {
println!("umount: {mntpt} unmounted");
}
}
}
}
Ok(())
}
fn path(&self) -> Option<shitbox::Path> {
Some(shitbox::Path::Bin)
}
}
fn get_mntpt(spec: &str) -> io::Result<String> {
let entries = MntEntries::new("/proc/mounts")?;
for e in entries {
if e.fsname == spec {
return Ok(e.dir.clone());
} else if e.dir == spec {
return Ok(spec.to_string());
}
}
Err(io::Error::new(io::ErrorKind::Other, "no such mount"))
}