140 lines
4.6 KiB
Rust
140 lines
4.6 KiB
Rust
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 - don’t 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") {
|
||
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<crate::Path> {
|
||
Some(crate::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))
|
||
}
|