shitbox/src/cmd/mountpoint/mod.rs
Nathan Fisher fb389fd309 Simplify subcommand parsing:
- one match statement to return a `Box<dyn Cmd>`
- one array containing all command names
Only two places to register new commands (besides their module), both in
crate::cmd::mod.rs. Also removes `once_cell` crate dependency.

Replace `base64` crate dependency with `data_encoding::BASE64` so that
both base32 and base64 commands use the same crate.
2023-01-06 23:41:02 -05:00

159 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::Cmd;
use clap::{Arg, ArgAction, Command};
use std::{
error::Error,
fs::{self, File},
io::{self, BufRead, BufReader, ErrorKind},
os::linux::fs::MetadataExt,
path::PathBuf,
process,
};
#[derive(Debug)]
pub struct Mountpoint {
name: &'static str,
path: Option<crate::Path>,
}
impl Default for Mountpoint {
fn default() -> Self {
Self {
name: "mountpoint",
path: Some(crate::Path::Bin),
}
}
}
impl Cmd for Mountpoint {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.about("see if a directory or file is a mountpoint")
.author("Nathan Fisher")
.after_long_help(
"EXIT STATUS\n \
0\n \
success: the directory is a mountpoint, or device is a block\
device on --devno\n \
1\n \
failure: incorrect invocation, permissions or system error\
\n 32\n \
failure: the directory is not a mountpoint, or device is not a \
block device on --devno",
)
.args([
Arg::new("fs-devno")
.help(
"Show the major/minor numbers of the device that is \
mounted on the given directory.",
)
.short('d')
.long("fs-devno")
.action(ArgAction::SetTrue),
Arg::new("devno")
.help(
"Show the major/minor numbers of the given blockdevice \
on standard output.",
)
.short('x')
.long("devno")
.conflicts_with("fs-devno")
.action(ArgAction::SetTrue),
Arg::new("quiet")
.help("Be quiet - dont print anything.")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue),
Arg::new("file").num_args(1).required(true),
])
}
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
let Some(matches) = matches else {
return Err(io::Error::new(ErrorKind::Other, "no input").into());
};
let file = matches.get_one::<String>("file").unwrap();
if matches.get_flag("fs-devno") {
match fs_devno(file)? {
Some(maj_min) => println!("{maj_min}"),
None => {
println!("{file} is not a mountpoint");
process::exit(32);
}
}
} else if matches.get_flag("devno") {
let devno = devno(file)?;
println!("{}:{}", devno.0, devno.1);
} else {
let val = is_mountpoint(file)?;
if val {
if !matches.get_flag("quiet") {
println!("{file} is a mountpoint");
}
return Ok(());
} else {
if !matches.get_flag("quiet") {
println!("{file} is not a mountpoint");
}
process::exit(32);
}
}
Ok(())
}
fn path(&self) -> Option<crate::Path> {
self.path
}
}
fn is_mountpoint(path: &str) -> Result<bool, Box<dyn Error>> {
let fd = File::open("/proc/mounts")?;
let reader = BufReader::new(fd);
for line in reader.lines() {
let line = line?;
if let Some(mntpt) = line.split_whitespace().skip(1).next() {
if mntpt == path {
return Ok(true);
}
}
}
Ok(false)
}
fn fs_devno(path: &str) -> Result<Option<String>, Box<dyn Error>> {
let p = PathBuf::from(path);
if !p.exists() {
let msg = format!("mountpoint: {path}: No such file or directory");
return Err(Box::new(io::Error::new(ErrorKind::Other, msg)));
}
let fd = File::open("/proc/self/mountinfo")?;
let reader = BufReader::new(fd);
for line in reader.lines() {
let line = line?;
let mut line = line.split_whitespace().skip(2);
if let Some(maj_min) = line.next() {
if let Some(mntpt) = line.skip(1).next() {
if mntpt == path {
return Ok(Some(String::from(maj_min)));
}
}
}
}
Ok(None)
}
fn devno(path: &str) -> Result<(u32, u32), Box<dyn Error>> {
let meta = fs::metadata(path)?;
let rdev = meta.st_rdev();
if rdev == 0 {
let msg = format!("Mountpoint: Error: {path} is not a character device");
return Err(Box::new(io::Error::new(ErrorKind::Other, msg)));
}
let major = unsafe { libc::major(rdev) };
let minor = unsafe { libc::minor(rdev) };
Ok((major, minor))
}