procutils-killall 0.2.0

Kill processes by name
Documentation
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")),
    ],
};

/// Lift a leading `-SIG` / `-NUM` / `-NAME` argument out of `argv` and
/// rewrite it as `--signal SIG`, so the rest of the args parse normally.
/// Only the first matching argument is consumed.
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
}

/// Kill processes by name.
#[derive(Parser)]
#[command(name = "killall", version, about, max_term_width = MAX_TERM_WIDTH)]
pub struct Args {
    /// Process names to signal.
    pub name: Vec<String>,

    /// Signal to send (name or number); defaults to TERM.
    #[arg(short = 's', long, default_value = "TERM")]
    pub signal: String,

    /// Match process names case-insensitively.
    #[arg(short = 'I', long)]
    pub ignore_case: bool,

    /// Only kill processes owned by USER (name or numeric UID).
    #[arg(short = 'u', long, value_name = "USER")]
    pub user: Option<String>,

    /// List the supported signal names and exit.
    #[arg(short = 'l', long, conflicts_with_all = ["name", "user", "verbose"])]
    pub list: bool,

    /// Suppress the diagnostic for names that match no process.
    #[arg(short = 'q', long)]
    pub quiet: bool,

    /// Print a line for each process being signalled.
    #[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
}