use crate::{bitflags::BitFlags, mode::Bit}; 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 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 { if let Some(p) = which(command, &path) { println!("{p}"); } else { println!("which: no {} in ({})", 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(); let myuid = unsafe { libc::geteuid() }; let mygroups = crate::pw::get_gids(); // we own the file and it has u+x if myuid == meta.uid() && mode.contains(Bit::UExec) { return Some(format!("{}", file.display())); // file has ug+x } else if mode.contains(Bit::UExec | Bit::GExec) { if let Ok(groups) = mygroups { // one of our groups owns the file if groups.contains(&meta.gid()) { return Some(format!("{}", file.display())); } } // the file has uga+x } else if mode & 0o111 != 0 { return Some(format!("{}", file.display())); } } } } } None }