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-04-17 23:27:57 -04:00
|
|
|
|
#[derive(Debug)]
|
2023-01-13 01:08:32 -05:00
|
|
|
|
pub struct Mountpoint;
|
2023-01-04 14:26:44 -05:00
|
|
|
|
|
|
|
|
|
impl Cmd for Mountpoint {
|
|
|
|
|
fn cli(&self) -> clap::Command {
|
2023-01-13 01:08:32 -05:00
|
|
|
|
Command::new("mountpoint")
|
2023-01-04 14:26:44 -05:00
|
|
|
|
.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 \
|
2023-01-06 23:41:02 -05:00
|
|
|
|
block device on --devno",
|
2023-01-05 19:20:12 -05:00
|
|
|
|
)
|
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
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-04 08:54:27 -05:00
|
|
|
|
fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
2023-01-05 19:20:12 -05:00
|
|
|
|
let file = matches.get_one::<String>("file").unwrap();
|
|
|
|
|
if matches.get_flag("fs-devno") {
|
2023-01-13 01:08:32 -05:00
|
|
|
|
if let Some(maj_min) = fs_devno(file)? {
|
|
|
|
|
println!("{maj_min}");
|
|
|
|
|
} else {
|
|
|
|
|
println!("{file} is not a mountpoint");
|
|
|
|
|
process::exit(32);
|
2023-01-05 19:20:12 -05:00
|
|
|
|
}
|
|
|
|
|
} 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(());
|
2023-01-13 01:08:32 -05:00
|
|
|
|
} else if !matches.get_flag("quiet") {
|
|
|
|
|
println!("{file} is not a mountpoint");
|
2023-01-05 19:20:12 -05:00
|
|
|
|
}
|
2023-01-13 01:08:32 -05:00
|
|
|
|
process::exit(32);
|
2023-01-05 19:20:12 -05:00
|
|
|
|
}
|
|
|
|
|
Ok(())
|
2023-01-04 14:26:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-05 23:50:59 -05:00
|
|
|
|
fn path(&self) -> Option<shitbox::Path> {
|
|
|
|
|
Some(shitbox::Path::Bin)
|
2023-01-04 14:26:44 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
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?;
|
2023-01-13 01:08:32 -05:00
|
|
|
|
if let Some(mntpt) = line.split_whitespace().nth(1) {
|
2023-01-05 19:20:12 -05:00
|
|
|
|
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() {
|
2023-01-13 01:08:32 -05:00
|
|
|
|
if let Some(mntpt) = line.nth(1) {
|
2023-01-05 19:20:12 -05:00
|
|
|
|
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))
|
|
|
|
|
}
|