diff --git a/Cargo.lock b/Cargo.lock index 2d88da5..a8c6f4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linux-raw-sys" @@ -222,6 +222,7 @@ dependencies = [ "clap_mangen", "data-encoding", "hostname", + "libc", "once_cell", "termcolor", ] diff --git a/Cargo.toml b/Cargo.toml index 85e3186..97eae97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ clap_complete_nushell = "0.1.8" clap_mangen = "0.2.5" data-encoding = "2.3.3" hostname = { version = "0.3", features = ["set"] } +libc = "0.2.139" once_cell = "1.16.0" termcolor = "1.1.3" diff --git a/src/cmd/mountpoint/mod.rs b/src/cmd/mountpoint/mod.rs index c20600f..8638cb0 100644 --- a/src/cmd/mountpoint/mod.rs +++ b/src/cmd/mountpoint/mod.rs @@ -1,5 +1,13 @@ 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)] pub struct Mountpoint { @@ -21,14 +29,31 @@ impl Cmd for Mountpoint { Command::new(self.name) .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.") + .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.") + .help( + "Show the major/minor numbers of the given blockdevice \ + on standard output.", + ) .short('x') .long("devno") .conflicts_with("fs-devno") @@ -38,14 +63,92 @@ impl Cmd for Mountpoint { .short('q') .long("quiet") .action(ArgAction::SetTrue), + Arg::new("file").num_args(1).required(true), ]) } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { - unimplemented!(); + let Some(matches) = matches else { + return Err(io::Error::new(ErrorKind::Other, "no input").into()); + }; + let file = matches.get_one::("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 { self.path } } + +fn is_mountpoint(path: &str) -> Result> { + 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, Box> { + 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> { + 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)) +}