diff --git a/src/cmd/fold/mod.rs b/src/cmd/fold/mod.rs index df5b2db..32f22aa 100644 --- a/src/cmd/fold/mod.rs +++ b/src/cmd/fold/mod.rs @@ -1,6 +1,9 @@ use super::Cmd; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; -use std::{fs::File, io::{self, BufRead, BufReader}}; +use std::{ + fs::File, + io::{self, BufRead, BufReader}, +}; use textwrap::{fill, wrap_algorithms::WrapAlgorithm}; #[derive(Debug)] diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 3747d44..c245191 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -32,6 +32,7 @@ pub mod shitbox; pub mod sleep; mod sync; pub mod r#true; +pub mod which; pub mod whoami; pub mod yes; @@ -39,7 +40,7 @@ pub use { self::hostname::Hostname, base32::Base32, base64::Base64, basename::Basename, bootstrap::Bootstrap, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, head::Head, mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, - shitbox::Shitbox, sleep::Sleep, whoami::Whoami, yes::Yes, + shitbox::Shitbox, sleep::Sleep, which::Which, whoami::Whoami, yes::Yes, }; pub trait Cmd: fmt::Debug + Sync { @@ -68,13 +69,14 @@ pub fn get(name: &str) -> Option> { "shitbox" => Some(Box::new(Shitbox::default())), "sleep" => Some(Box::new(Sleep::default())), "true" => Some(Box::new(True::default())), + "which" => Some(Box::new(Which::default())), "whoami" => Some(Box::new(Whoami::default())), "yes" => Some(Box::new(Yes::default())), _ => None, } } -pub static COMMANDS: [&'static str; 20] = [ +pub static COMMANDS: [&'static str; 21] = [ "base32", "base64", "basename", @@ -93,6 +95,7 @@ pub static COMMANDS: [&'static str; 20] = [ "sleep", "shitbox", "true", + "which", "whoami", "yes", ]; diff --git a/src/cmd/which/mod.rs b/src/cmd/which/mod.rs new file mode 100644 index 0000000..354289d --- /dev/null +++ b/src/cmd/which/mod.rs @@ -0,0 +1,73 @@ +use super::Cmd; +use clap::{Arg, Command}; +use std::{env, fs::File, io, os::unix::prelude::MetadataExt, path::PathBuf, process}; + +#[derive(Debug, Default)] +pub struct Which; + +impl Cmd for Which { + fn name(&self) -> &str { + "which" + } + + fn cli(&self) -> clap::Command { + Command::new("which") + .about("Write the full path of COMMAND(s) to standard output") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .arg(Arg::new("COMMAND").num_args(1..)) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))); + }; + let rawpath = if let Ok(p) = env::var("PATH") { + p + } else { + "/usr/bin:/bin".to_string() + }; + let path: Vec<_> = rawpath.split(':').collect(); + let mut failures = 0; + if let Some(commands) = matches.get_many::("COMMAND") { + for command in commands { + match which(command, &path) { + Some(p) => println!("{p}"), + None => { + println!("{}: no {} in ({})", self.name(), command, &rawpath); + failures += 1; + } + } + } + } else { + let _res = self.cli().print_help(); + process::exit(255); + } + if failures > 0 { + process::exit(failures); + } else { + Ok(()) + } + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} + +fn which(command: &str, path: &[&str]) -> Option { + for p in path { + let file = [p, command].iter().collect::(); + if file.exists() { + if let Ok(exe) = File::open(&file) { + if let Ok(meta) = exe.metadata() { + let mode = meta.mode(); + if mode & 0o111 != 0 { + return Some(format!("{}", file.display())); + } + } + } + } + } + None +} diff --git a/src/cmd/whoami/mod.rs b/src/cmd/whoami/mod.rs index 2071e5a..207ce6e 100644 --- a/src/cmd/whoami/mod.rs +++ b/src/cmd/whoami/mod.rs @@ -1,9 +1,9 @@ use std::ffi::CStr; use { + super::Cmd, clap::Command, libc::{geteuid, getpwuid}, - super::Cmd, }; #[derive(Debug, Default)]