shitbox/src/cmd/mountpoint/mod.rs

155 lines
4.9 KiB
Rust
Raw Normal View History

use super::Cmd;
use clap::{Arg, ArgAction, Command};
2023-01-05 19:20:12 -05:00
use std::{
error::Error,
fs::{self, File},
io::{self, BufRead, BufReader, ErrorKind},
os::linux::fs::MetadataExt,
path::PathBuf,
process,
};
2022-12-20 12:05:21 -05:00
#[derive(Debug)]
pub struct Mountpoint {
name: &'static str,
path: Option<crate::Path>,
}
pub const MOUNTPOINT: Mountpoint = Mountpoint {
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")
2023-01-05 19:20:12 -05:00
.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")
2023-01-05 19:20:12 -05:00
.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")
2023-01-05 19:20:12 -05:00
.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),
2023-01-05 19:20:12 -05:00
Arg::new("file").num_args(1).required(true),
])
}
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
2023-01-05 19:20:12 -05:00
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
}
}
2023-01-05 19:20:12 -05:00
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))
}