use clap::Parser;
use procutils_common::{
procmatch::{MatchOptions, ProcessInfo},
signal::parse_signal,
};
use rustix::process::{Pid, Signal};
use std::process::ExitCode;
#[derive(Parser)]
#[command(name = "pkill", version, about)]
pub struct Args {
#[arg(long, default_value = "TERM")]
signal: String,
#[arg(short, long)]
count: bool,
#[arg(short, long)]
echo: bool,
#[arg(short, long)]
full: bool,
#[arg(short, long)]
ignore_case: bool,
#[arg(short, long)]
newest: bool,
#[arg(short, long)]
oldest: bool,
#[arg(short = 'O', long)]
older: Option<f64>,
#[arg(short = 'P', long, value_delimiter = ',')]
parent: Option<Vec<i32>>,
#[arg(short = 'g', long = "pgroup", value_delimiter = ',')]
pgroup: Option<Vec<i32>>,
#[arg(short = 'G', long = "group", value_delimiter = ',')]
group: Option<Vec<u32>>,
#[arg(short = 's', long, value_delimiter = ',')]
session: Option<Vec<i32>>,
#[arg(short = 't', long = "terminal", value_delimiter = ',')]
terminal: Option<Vec<String>>,
#[arg(short = 'u', long = "euid", value_delimiter = ',')]
euid: Option<Vec<String>>,
#[arg(short = 'U', long = "uid", value_delimiter = ',')]
uid: Option<Vec<String>>,
#[arg(short = 'x', long)]
exact: bool,
#[arg(short = 'r', long, value_delimiter = ',')]
runstates: Option<Vec<char>>,
pattern: Option<String>,
}
fn send_signal(proc: &ProcessInfo, signal: Signal) -> bool {
let Some(pid) = Pid::from_raw(proc.pid) else {
return false;
};
rustix::process::kill_process(pid, signal).is_ok()
}
pub fn run(args: Args) -> ExitCode {
let signal = match parse_signal(&args.signal) {
Some(s) => s,
None => {
eprintln!("pkill: unknown signal: {}", args.signal);
return ExitCode::from(2);
}
};
let opts = MatchOptions {
pattern: args.pattern.clone().unwrap_or_default(),
full: args.full,
ignore_case: args.ignore_case,
pid: None,
exact: args.exact,
inverse: false,
newest: args.newest,
oldest: args.oldest,
older: args.older,
parent: args.parent,
pgroup: args.pgroup,
group: args.group,
session: args.session,
terminal: args.terminal,
euid: args.euid,
uid: args.uid,
runstates: args.runstates,
};
if args.pattern.is_none() && !opts.has_filter() {
eprintln!("pkill: pattern is required");
return ExitCode::from(2);
}
let matches = match procutils_common::procmatch::find_matching_processes(
&opts, "pkill",
) {
Ok(m) => m,
Err(code) => return code,
};
if matches.is_empty() {
return ExitCode::from(1);
}
if args.count {
println!("{}", matches.len());
return ExitCode::SUCCESS;
}
let mut any_failed = false;
for proc in &matches {
if send_signal(proc, signal) {
if args.echo {
println!("{} killed (pid {})", proc.comm, proc.pid);
}
} else {
any_failed = true;
}
}
if any_failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}