2023-01-04 14:26:44 -05:00
|
|
|
|
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
|
|
|
|
|
2023-01-04 14:26:44 -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"
|
|
|
|
|
)
|
2023-01-04 14:26:44 -05:00
|
|
|
|
.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.",
|
|
|
|
|
)
|
2023-01-04 14:26:44 -05:00
|
|
|
|
.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.",
|
|
|
|
|
)
|
2023-01-04 14:26:44 -05:00
|
|
|
|
.short('x')
|
|
|
|
|
.long("devno")
|
2023-01-04 15:14:16 -05:00
|
|
|
|
.conflicts_with("fs-devno")
|
2023-01-04 14:26:44 -05:00
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
|
Arg::new("quiet")
|
|
|
|
|
.help("Be quiet - don’t 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),
|
2023-01-04 14:26:44 -05:00
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(())
|
2023-01-04 14:26:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
}
|