Skip to main content

procutils_killall/
lib.rs

1use clap::Parser;
2use procutils_common::{
3    MAX_TERM_WIDTH,
4    man::ManContent,
5    procmatch::{MatchOptions, ProcessInfo, find_matching_processes},
6    signal::{all_signals, parse_signum_any},
7};
8use std::process::ExitCode;
9
10pub const MAN: ManContent = ManContent {
11    description: Some(include_str!("../man/description.man")),
12    extra_sections: &[
13        ("EXAMPLES", include_str!("../man/examples.man")),
14        ("DIVERGENCES", include_str!("../man/divergences.man")),
15        ("SEE ALSO", include_str!("../man/see_also.man")),
16    ],
17};
18
19/// Lift a leading `-SIG` / `-NUM` / `-NAME` argument out of `argv` and
20/// rewrite it as `--signal SIG`, so the rest of the args parse normally.
21/// Only the first matching argument is consumed.
22pub fn preprocess_argv(argv: Vec<String>) -> Vec<String> {
23    let mut out = Vec::with_capacity(argv.len() + 1);
24    let mut iter = argv.into_iter();
25    if let Some(arg0) = iter.next() {
26        out.push(arg0);
27    }
28    let mut found = false;
29    for arg in iter {
30        if !found
31            && arg.starts_with('-')
32            && !arg.starts_with("--")
33            && arg.len() > 1
34            && parse_signum_any(&arg[1..]).is_some()
35        {
36            out.push("--signal".to_string());
37            out.push(arg[1..].to_string());
38            found = true;
39        } else {
40            out.push(arg);
41        }
42    }
43    out
44}
45
46/// Kill processes by name.
47#[derive(Parser)]
48#[command(name = "killall", version, about, max_term_width = MAX_TERM_WIDTH)]
49pub struct Args {
50    /// Process names to signal.
51    pub name: Vec<String>,
52
53    /// Signal to send (name or number); defaults to TERM.
54    #[arg(short = 's', long, default_value = "TERM")]
55    pub signal: String,
56
57    /// Match process names case-insensitively.
58    #[arg(short = 'I', long)]
59    pub ignore_case: bool,
60
61    /// Only kill processes owned by USER (name or numeric UID).
62    #[arg(short = 'u', long, value_name = "USER")]
63    pub user: Option<String>,
64
65    /// List the supported signal names and exit.
66    #[arg(short = 'l', long, conflicts_with_all = ["name", "user", "verbose"])]
67    pub list: bool,
68
69    /// Suppress the diagnostic for names that match no process.
70    #[arg(short = 'q', long)]
71    pub quiet: bool,
72
73    /// Print a line for each process being signalled.
74    #[arg(short = 'v', long)]
75    pub verbose: bool,
76}
77
78pub fn run(args: Args) -> ExitCode {
79    if args.list {
80        for (_, name) in all_signals() {
81            println!("{name}");
82        }
83        return ExitCode::SUCCESS;
84    }
85
86    if args.name.is_empty() {
87        eprintln!("killall: at least one process name is required");
88        return ExitCode::from(2);
89    }
90
91    let signum = match parse_signum_any(&args.signal) {
92        Some(n) => n,
93        None => {
94            eprintln!("killall: unknown signal: {}", args.signal);
95            return ExitCode::from(2);
96        }
97    };
98
99    let mut any_missing = false;
100    let mut any_kill_failed = false;
101
102    for name in &args.name {
103        let opts = MatchOptions {
104            pattern: regex::escape(name),
105            full: false,
106            ignore_case: args.ignore_case,
107            exact: true,
108            inverse: false,
109            newest: false,
110            oldest: false,
111            older: None,
112            pid: None,
113            parent: None,
114            pgroup: None,
115            group: None,
116            session: None,
117            terminal: None,
118            euid: None,
119            uid: args.user.clone().map(|u| vec![u]),
120            runstates: None,
121            env: None,
122        };
123
124        let matches = match find_matching_processes(&opts, "killall") {
125            Ok(m) => m,
126            Err(code) => return code,
127        };
128
129        if matches.is_empty() {
130            if !args.quiet {
131                eprintln!("killall: {name}: no process found");
132            }
133            any_missing = true;
134            continue;
135        }
136
137        for proc in &matches {
138            if send_signal(proc, signum) {
139                if args.verbose {
140                    println!(
141                        "Killed {comm}({pid}) with signal {sig}",
142                        comm = proc.comm,
143                        pid = proc.pid,
144                        sig = args.signal,
145                    );
146                }
147            } else {
148                any_kill_failed = true;
149            }
150        }
151    }
152
153    if any_missing || any_kill_failed {
154        ExitCode::FAILURE
155    } else {
156        ExitCode::SUCCESS
157    }
158}
159
160fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
161    let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
162    rc == 0
163}