use clap::Parser;
use procutils_common::{
MAX_TERM_WIDTH,
man::ManContent,
procmatch::{MatchOptions, ProcessInfo},
signal::parse_signal,
};
use std::process::ExitCode;
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
&& is_signal_spec(&arg[1..])
{
out.push("--signal".to_string());
out.push(arg[1..].to_string());
found = true;
} else {
out.push(arg);
}
}
out
}
fn is_signal_spec(s: &str) -> bool {
s == "0" || parse_signal(s).is_some()
}
pub const MAN: ManContent = ManContent {
description: Some(include_str!("../man/description.man")),
extra_sections: &[
("EXAMPLES", include_str!("../man/examples.man")),
("NOTES", include_str!("../man/notes.man")),
("DIVERGENCES", include_str!("../man/divergences.man")),
("SEE ALSO", include_str!("../man/see_also.man")),
],
};
const SIGNAL: &str = "Signal";
const MATCHING: &str = "Matching";
const FILTERS: &str = "Filters";
const SELECTION: &str = "Selection";
const OUTPUT: &str = "Output";
#[derive(Parser)]
#[command(name = "pkill", version, about, max_term_width = MAX_TERM_WIDTH)]
pub struct Args {
#[arg(long, default_value = "TERM", help_heading = SIGNAL)]
signal: String,
#[arg(short = 'q', long, value_name = "VALUE", help_heading = SIGNAL)]
queue: Option<i32>,
#[arg(short, long, help_heading = MATCHING)]
full: bool,
#[arg(short, long, help_heading = MATCHING)]
ignore_case: bool,
#[arg(short = 'x', long, help_heading = MATCHING)]
exact: bool,
#[arg(short = 'r', long, value_delimiter = ',', help_heading = FILTERS)]
runstates: Option<Vec<char>>,
#[arg(long, value_name = "NAME[=VALUE]", help_heading = FILTERS)]
env: Option<String>,
#[arg(short = 'O', long, help_heading = FILTERS)]
older: Option<f64>,
#[arg(short = 'p', long = "pid", value_delimiter = ',', help_heading = FILTERS)]
pid: Option<Vec<i32>>,
#[arg(
short = 'F',
long = "pidfile",
value_name = "FILE",
conflicts_with = "pid",
help_heading = FILTERS,
)]
pidfile: Option<std::path::PathBuf>,
#[arg(short = 'P', long, value_delimiter = ',', help_heading = FILTERS)]
parent: Option<Vec<i32>>,
#[arg(short = 'g', long = "pgroup", value_delimiter = ',', help_heading = FILTERS)]
pgroup: Option<Vec<i32>>,
#[arg(short = 'G', long = "group", value_delimiter = ',', help_heading = FILTERS)]
group: Option<Vec<u32>>,
#[arg(short = 's', long, value_delimiter = ',', help_heading = FILTERS)]
session: Option<Vec<i32>>,
#[arg(short = 't', long = "terminal", value_delimiter = ',', help_heading = FILTERS)]
terminal: Option<Vec<String>>,
#[arg(short = 'u', long = "euid", value_delimiter = ',', help_heading = FILTERS)]
euid: Option<Vec<String>>,
#[arg(short = 'U', long = "uid", value_delimiter = ',', help_heading = FILTERS)]
uid: Option<Vec<String>>,
#[arg(short, long, help_heading = SELECTION)]
newest: bool,
#[arg(short, long, help_heading = SELECTION)]
oldest: bool,
#[arg(short, long, help_heading = OUTPUT)]
count: bool,
#[arg(short, long, help_heading = OUTPUT)]
echo: bool,
pattern: Option<String>,
}
fn parse_signum(s: &str) -> Option<i32> {
if s == "0" {
return Some(0);
}
parse_signal(s).map(|sig| sig.as_raw())
}
fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
rc == 0
}
fn send_queued_signal(proc: &ProcessInfo, signum: i32, value: i32) -> bool {
let sigval = libc::sigval {
sival_ptr: value as usize as *mut libc::c_void,
};
let rc =
unsafe { libc::sigqueue(proc.pid as libc::pid_t, signum, sigval) };
rc == 0
}
pub fn run(args: Args) -> ExitCode {
let signum = match parse_signum(&args.signal) {
Some(n) => n,
None => {
eprintln!("pkill: unknown signal: {}", args.signal);
return ExitCode::from(2);
}
};
let pid_filter = match args.pidfile.as_deref() {
Some(path) => match procutils_common::procmatch::read_pidfile(path) {
Ok(pids) => Some(pids),
Err(e) => {
eprintln!("pkill: {e}");
return ExitCode::from(2);
}
},
None => args.pid.clone(),
};
let opts = MatchOptions {
pattern: args.pattern.clone().unwrap_or_default(),
full: args.full,
ignore_case: args.ignore_case,
pid: pid_filter,
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,
env: args.env,
};
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 {
let sent = match args.queue {
Some(value) => send_queued_signal(proc, signum, value),
None => send_signal(proc, signum),
};
if sent {
if args.echo {
println!("{} killed (pid {})", proc.comm, proc.pid);
}
} else {
any_failed = true;
}
}
if any_failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}