use clap::Parser;
use procutils_common::{
MAX_TERM_WIDTH,
man::ManContent,
procmatch::{MatchOptions, ProcessInfo, find_matching_processes},
signal::{all_signals, parse_signum_any},
};
use std::process::ExitCode;
pub const MAN: ManContent = ManContent {
description: Some(include_str!("../man/description.man")),
extra_sections: &[
("EXAMPLES", include_str!("../man/examples.man")),
("DIVERGENCES", include_str!("../man/divergences.man")),
("SEE ALSO", include_str!("../man/see_also.man")),
],
};
pub fn preprocess_argv(argv: Vec<String>) -> Vec<String> {
let mut out = Vec::with_capacity(argv.len() + 1);
let mut iter = argv.into_iter();
if let Some(arg0) = iter.next() {
out.push(arg0);
}
let mut found = false;
for arg in iter {
if !found
&& arg.starts_with('-')
&& !arg.starts_with("--")
&& arg.len() > 1
&& parse_signum_any(&arg[1..]).is_some()
{
out.push("--signal".to_string());
out.push(arg[1..].to_string());
found = true;
} else {
out.push(arg);
}
}
out
}
#[derive(Parser)]
#[command(name = "killall", version, about, max_term_width = MAX_TERM_WIDTH)]
pub struct Args {
pub name: Vec<String>,
#[arg(short = 's', long, default_value = "TERM")]
pub signal: String,
#[arg(short = 'I', long)]
pub ignore_case: bool,
#[arg(short = 'u', long, value_name = "USER")]
pub user: Option<String>,
#[arg(short = 'l', long, conflicts_with_all = ["name", "user", "verbose"])]
pub list: bool,
#[arg(short = 'q', long)]
pub quiet: bool,
#[arg(short = 'v', long)]
pub verbose: bool,
}
pub fn run(args: Args) -> ExitCode {
if args.list {
for (_, name) in all_signals() {
println!("{name}");
}
return ExitCode::SUCCESS;
}
if args.name.is_empty() {
eprintln!("killall: at least one process name is required");
return ExitCode::from(2);
}
let signum = match parse_signum_any(&args.signal) {
Some(n) => n,
None => {
eprintln!("killall: unknown signal: {}", args.signal);
return ExitCode::from(2);
}
};
let mut any_missing = false;
let mut any_kill_failed = false;
for name in &args.name {
let opts = MatchOptions {
pattern: regex::escape(name),
full: false,
ignore_case: args.ignore_case,
exact: true,
inverse: false,
newest: false,
oldest: false,
older: None,
pid: None,
parent: None,
pgroup: None,
group: None,
session: None,
terminal: None,
euid: None,
uid: args.user.clone().map(|u| vec![u]),
runstates: None,
env: None,
};
let matches = match find_matching_processes(&opts, "killall") {
Ok(m) => m,
Err(code) => return code,
};
if matches.is_empty() {
if !args.quiet {
eprintln!("killall: {name}: no process found");
}
any_missing = true;
continue;
}
for proc in &matches {
if send_signal(proc, signum) {
if args.verbose {
println!(
"Killed {comm}({pid}) with signal {sig}",
comm = proc.comm,
pid = proc.pid,
sig = args.signal,
);
}
} else {
any_kill_failed = true;
}
}
}
if any_missing || any_kill_failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
rc == 0
}