use clap::{Arg, Command};
use color_eyre::Result;
use color_eyre::eyre::eyre;
use nix::sys::signal::Signal;
use regex::Regex;
pub struct PkillOptions {
pub pattern: String,
pub signal: Signal,
pub full_command: bool,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<PkillOptions> {
let args = matches.get_many::<String>("args")
.map(|vals| vals.map(|s| s.as_str()).collect::<Vec<_>>())
.unwrap_or_default();
let mut signal_str = "TERM";
let mut pattern = None;
if let Some(s_signal) = matches.get_one::<String>("signal") {
signal_str = s_signal;
}
for arg in args {
if arg.starts_with('-') && arg.len() > 1 {
let signal_part = &arg[1..];
if crate::utils::is_signal_name(signal_part) || signal_part.chars().all(|c| c.is_ascii_digit()) {
signal_str = signal_part;
} else {
return Err(eyre!("pkill: invalid signal: {}", arg));
}
} else {
if pattern.is_some() {
return Err(eyre!("pkill: multiple patterns specified"));
}
pattern = Some(arg.to_string());
}
}
let pattern = pattern.ok_or_else(|| eyre!("pkill: missing pattern"))?;
let signal = crate::utils::parse_signal(signal_str)?;
let full_command = matches.get_flag("full");
Ok(PkillOptions { pattern, signal, full_command })
}
pub fn command() -> Command {
Command::new("pkill")
.about("Look up or signal processes based on name")
.arg(Arg::new("signal")
.short('s')
.help("Signal to send (name or number)")
.value_name("SIGNAL"))
.arg(Arg::new("full")
.short('f')
.help("Match against full command line instead of just process name")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("args")
.help("Signal specifications and pattern (signals start with -)")
.num_args(1..))
}
fn find_and_kill_processes(pattern: &str, signal: Signal, full_command: bool) -> Result<()> {
let regex = Regex::new(pattern)
.map_err(|e| eyre!("pkill: invalid pattern '{}': {}", pattern, e))?;
let mut found_any = false;
for pid_result in crate::utils::iterate_processes()? {
let pid = pid_result?;
let match_string = if full_command {
crate::utils::get_process_cmdline(pid)
} else {
crate::utils::get_process_name(pid)
};
if let Some(match_string) = match_string {
if regex.is_match(&match_string) {
crate::utils::kill_process(pid as i32, signal, "pkill")?;
found_any = true;
}
}
}
if !found_any {
std::process::exit(1);
}
Ok(())
}
pub fn run(options: PkillOptions) -> Result<()> {
find_and_kill_processes(&options.pattern, options.signal, options.full_command)
}