use super::Cmd; use bitflags::BitFlags; use clap::{Arg, Command}; use mode::Bit; use std::{env, fs::File, 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: &clap::ArgMatches) -> Result<(), Box> { 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 {command} in ({})", &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(shitbox::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 = 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 }