Finish mountpoint applet

This commit is contained in:
Nathan Fisher 2023-01-05 19:20:12 -05:00
parent 6b6cc314e8
commit 84ede35190
3 changed files with 110 additions and 5 deletions

5
Cargo.lock generated
View File

@ -162,9 +162,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.138" version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -222,6 +222,7 @@ dependencies = [
"clap_mangen", "clap_mangen",
"data-encoding", "data-encoding",
"hostname", "hostname",
"libc",
"once_cell", "once_cell",
"termcolor", "termcolor",
] ]

View File

@ -14,6 +14,7 @@ clap_complete_nushell = "0.1.8"
clap_mangen = "0.2.5" clap_mangen = "0.2.5"
data-encoding = "2.3.3" data-encoding = "2.3.3"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
libc = "0.2.139"
once_cell = "1.16.0" once_cell = "1.16.0"
termcolor = "1.1.3" termcolor = "1.1.3"

View File

@ -1,5 +1,13 @@
use super::Cmd; use super::Cmd;
use clap::{Arg, ArgAction, Command}; 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)] #[derive(Debug)]
pub struct Mountpoint { pub struct Mountpoint {
@ -21,14 +29,31 @@ impl Cmd for Mountpoint {
Command::new(self.name) Command::new(self.name)
.about("see if a directory or file is a mountpoint") .about("see if a directory or file is a mountpoint")
.author("Nathan Fisher") .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([ .args([
Arg::new("fs-devno") Arg::new("fs-devno")
.help("Show the major/minor numbers of the device that is mounted on the given directory.") .help(
"Show the major/minor numbers of the device that is \
mounted on the given directory.",
)
.short('d') .short('d')
.long("fs-devno") .long("fs-devno")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("devno") Arg::new("devno")
.help("Show the major/minor numbers of the given blockdevice on standard output.") .help(
"Show the major/minor numbers of the given blockdevice \
on standard output.",
)
.short('x') .short('x')
.long("devno") .long("devno")
.conflicts_with("fs-devno") .conflicts_with("fs-devno")
@ -38,14 +63,92 @@ impl Cmd for Mountpoint {
.short('q') .short('q')
.long("quiet") .long("quiet")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("file").num_args(1).required(true),
]) ])
} }
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> { fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
unimplemented!(); 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> { fn path(&self) -> Option<crate::Path> {
self.path self.path
} }
} }
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))
}