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> { let flags = if matches.get_flag("force") { libc::MNT_FORCE } else { 0 }; if matches.get_flag("all") { let mut mntpts: Vec = 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::("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::("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::("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 { Some(shitbox::Path::Bin) } } fn get_mntpt(spec: &str) -> io::Result { 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")) }