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

137 lines
4.5 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, Default)]
pub struct Mountpoint;
impl Cmd for Mountpoint {
fn cli(&self) -> clap::Command {
Command::new("mountpoint")
.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: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let file = matches.get_one::<String>("file").unwrap();
if matches.get_flag("fs-devno") {
if let Some(maj_min) = fs_devno(file)? {
println!("{maj_min}");
} else {
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<shitbox::Path> {
Some(shitbox::Path::Bin)
}
}
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().nth(1) {
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.nth(1) {
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))
}