Skip to main content

linuxutils_misc/
kill.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use rustix::process::{self, Pid, Signal};
7use std::process::ExitCode;
8
9#[derive(Parser)]
10#[command(
11    name = "kill",
12    about = "Terminate a process",
13    override_usage = "kill [-s <signal>|-<signal>] <pid>...\n       \
14                      kill -l [<number>]\n       \
15                      kill -L"
16)]
17pub struct Args {
18    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
19    args: Vec<String>,
20}
21
22const SIGNALS: &[(i32, &str)] = &[
23    (1, "HUP"),
24    (2, "INT"),
25    (3, "QUIT"),
26    (4, "ILL"),
27    (5, "TRAP"),
28    (6, "ABRT"),
29    (7, "BUS"),
30    (8, "FPE"),
31    (9, "KILL"),
32    (10, "USR1"),
33    (11, "SEGV"),
34    (12, "USR2"),
35    (13, "PIPE"),
36    (14, "ALRM"),
37    (15, "TERM"),
38    (16, "STKFLT"),
39    (17, "CHLD"),
40    (18, "CONT"),
41    (19, "STOP"),
42    (20, "TSTP"),
43    (21, "TTIN"),
44    (22, "TTOU"),
45    (23, "URG"),
46    (24, "XCPU"),
47    (25, "XFSZ"),
48    (26, "VTALRM"),
49    (27, "PROF"),
50    (28, "WINCH"),
51    (29, "IO"),
52    (30, "PWR"),
53    (31, "SYS"),
54];
55
56const SIGNAL_ALIASES: &[(&str, i32)] = &[("IOT", 6), ("CLD", 17), ("POLL", 29)];
57
58const RTMIN: i32 = 34;
59const RTMAX: i32 = 64;
60
61fn signal_name(num: i32) -> Option<String> {
62    for &(n, name) in SIGNALS {
63        if n == num {
64            return Some(name.to_string());
65        }
66    }
67    if (RTMIN..=RTMAX).contains(&num) {
68        return Some(rt_signal_name(num));
69    }
70    None
71}
72
73fn rt_signal_name(num: i32) -> String {
74    if num == RTMIN {
75        return "RTMIN".to_string();
76    }
77    if num == RTMAX {
78        return "RTMAX".to_string();
79    }
80    let mid = (RTMIN + RTMAX) / 2;
81    if num <= mid {
82        format!("RTMIN+{}", num - RTMIN)
83    } else {
84        format!("RTMAX-{}", RTMAX - num)
85    }
86}
87
88fn parse_signal_str(s: &str) -> Result<i32, String> {
89    if let Ok(n) = s.parse::<i32>() {
90        if n == 0 || (1..=31).contains(&n) || (RTMIN..=RTMAX).contains(&n) {
91            return Ok(n);
92        }
93        return Err(format!("unknown signal: {s}"));
94    }
95
96    let name = s.strip_prefix("SIG").unwrap_or(s);
97    let upper = name.to_ascii_uppercase();
98
99    for &(num, sname) in SIGNALS {
100        if sname == upper {
101            return Ok(num);
102        }
103    }
104    for &(alias, num) in SIGNAL_ALIASES {
105        if alias == upper {
106            return Ok(num);
107        }
108    }
109
110    if upper == "RTMIN" {
111        return Ok(RTMIN);
112    }
113    if upper == "RTMAX" {
114        return Ok(RTMAX);
115    }
116    if let Some(offset) = upper.strip_prefix("RTMIN+")
117        && let Ok(n) = offset.parse::<i32>()
118    {
119        let sig = RTMIN + n;
120        if sig <= RTMAX {
121            return Ok(sig);
122        }
123    }
124    if let Some(offset) = upper.strip_prefix("RTMAX-")
125        && let Ok(n) = offset.parse::<i32>()
126    {
127        let sig = RTMAX - n;
128        if sig >= RTMIN {
129            return Ok(sig);
130        }
131    }
132
133    Err(format!("unknown signal: {s}"))
134}
135
136fn signal_to_rustix(num: i32) -> Option<Signal> {
137    Some(match num {
138        1 => Signal::HUP,
139        2 => Signal::INT,
140        3 => Signal::QUIT,
141        4 => Signal::ILL,
142        5 => Signal::TRAP,
143        6 => Signal::ABORT,
144        7 => Signal::BUS,
145        8 => Signal::FPE,
146        9 => Signal::KILL,
147        10 => Signal::USR1,
148        11 => Signal::SEGV,
149        12 => Signal::USR2,
150        13 => Signal::PIPE,
151        14 => Signal::ALARM,
152        15 => Signal::TERM,
153        16 => Signal::STKFLT,
154        17 => Signal::CHILD,
155        18 => Signal::CONT,
156        19 => Signal::STOP,
157        20 => Signal::TSTP,
158        21 => Signal::TTIN,
159        22 => Signal::TTOU,
160        23 => Signal::URG,
161        24 => Signal::XCPU,
162        25 => Signal::XFSZ,
163        26 => Signal::VTALARM,
164        27 => Signal::PROF,
165        28 => Signal::WINCH,
166        29 => Signal::IO,
167        30 => Signal::POWER,
168        31 => Signal::SYS,
169        // SAFETY: we've bounds-checked n to be a valid RT signal number
170        34..=64 => unsafe { Signal::from_raw_unchecked(num) },
171        _ => return None,
172    })
173}
174
175enum Action {
176    Kill { signal: i32, pids: Vec<i32> },
177    List(Option<String>),
178    Table,
179}
180
181fn parse_action(args: &[String]) -> Result<Action, String> {
182    let mut signal: Option<i32> = None;
183    let mut pids: Vec<i32> = Vec::new();
184    let mut i = 0;
185
186    while i < args.len() {
187        let arg = &args[i];
188
189        if arg == "-l" || arg == "--list" {
190            let list_arg = args.get(i + 1).cloned();
191            return Ok(Action::List(list_arg));
192        }
193
194        if arg == "-L" || arg == "--table" {
195            return Ok(Action::Table);
196        }
197
198        if arg == "-s" || arg == "--signal" {
199            i += 1;
200            let sig_str =
201                args.get(i).ok_or("option '-s' requires an argument")?;
202            signal = Some(parse_signal_str(sig_str)?);
203            i += 1;
204            continue;
205        }
206
207        if arg == "--" {
208            i += 1;
209            break;
210        }
211
212        if signal.is_none()
213            && let Some(sig_str) = arg.strip_prefix('-')
214            && !sig_str.is_empty()
215            && let Ok(sig) = parse_signal_str(sig_str)
216        {
217            signal = Some(sig);
218            i += 1;
219            continue;
220        }
221
222        match arg.parse::<i32>() {
223            Ok(pid) => pids.push(pid),
224            Err(_) => return Err(format!("failed to parse pid: '{arg}'")),
225        }
226        i += 1;
227    }
228
229    while i < args.len() {
230        match args[i].parse::<i32>() {
231            Ok(pid) => pids.push(pid),
232            Err(_) => {
233                return Err(format!("failed to parse pid: '{}'", args[i]));
234            }
235        }
236        i += 1;
237    }
238
239    if pids.is_empty() {
240        return Err("no process ID specified".to_string());
241    }
242
243    Ok(Action::Kill {
244        signal: signal.unwrap_or(15),
245        pids,
246    })
247}
248
249fn send_signal(pid: i32, sig: i32) -> Result<(), rustix::io::Errno> {
250    if sig == 0 {
251        if pid == 0 {
252            return process::test_kill_current_process_group();
253        }
254        let p = Pid::from_raw(pid).ok_or(rustix::io::Errno::INVAL)?;
255        return process::test_kill_process(p);
256    }
257
258    let signal = signal_to_rustix(sig).ok_or(rustix::io::Errno::INVAL)?;
259
260    if pid == 0 {
261        process::kill_current_process_group(signal)
262    } else {
263        let p = Pid::from_raw(pid).ok_or(rustix::io::Errno::INVAL)?;
264        if pid < 0 {
265            process::kill_process_group(p, signal)
266        } else {
267            process::kill_process(p, signal)
268        }
269    }
270}
271
272fn list_signals(arg: Option<String>) -> ExitCode {
273    match arg {
274        None => {
275            let names: Vec<String> = SIGNALS
276                .iter()
277                .map(|(_, name)| name.to_string())
278                .chain((RTMIN..=RTMAX).map(rt_signal_name))
279                .collect();
280            println!("{}", names.join(" "));
281            ExitCode::SUCCESS
282        }
283        Some(s) if s.starts_with("0x") || s.starts_with("0X") => {
284            match u64::from_str_radix(&s[2..], 16) {
285                Ok(mask) => {
286                    for bit in 1..=RTMAX {
287                        if mask & (1u64 << (bit - 1)) != 0
288                            && let Some(name) = signal_name(bit)
289                        {
290                            println!("{name}");
291                        }
292                    }
293                    ExitCode::SUCCESS
294                }
295                Err(_) => {
296                    eprintln!("kill: invalid signal mask: {s}");
297                    ExitCode::FAILURE
298                }
299            }
300        }
301        Some(s) => match s.parse::<i32>() {
302            Ok(mut num) => {
303                if num > 128 {
304                    num -= 128;
305                }
306                match signal_name(num) {
307                    Some(name) => {
308                        println!("{name}");
309                        ExitCode::SUCCESS
310                    }
311                    None => {
312                        eprintln!("kill: unknown signal: {num}");
313                        ExitCode::FAILURE
314                    }
315                }
316            }
317            Err(_) => match parse_signal_str(&s) {
318                Ok(num) => {
319                    println!("{num}");
320                    ExitCode::SUCCESS
321                }
322                Err(_) => {
323                    eprintln!("kill: unknown signal: {s}");
324                    ExitCode::FAILURE
325                }
326            },
327        },
328    }
329}
330
331fn table_signals() -> ExitCode {
332    let entries: Vec<(i32, String)> = SIGNALS
333        .iter()
334        .map(|&(num, name)| (num, name.to_string()))
335        .chain((RTMIN..=RTMAX).map(|n| (n, rt_signal_name(n))))
336        .collect();
337
338    let cols = 7;
339    for (i, (num, name)) in entries.iter().enumerate() {
340        if i > 0 && i % cols == 0 {
341            println!();
342        }
343        print!("{num:2} {name:<10}");
344    }
345    println!();
346    ExitCode::SUCCESS
347}
348
349pub fn run(args: Args) -> ExitCode {
350    let action = match parse_action(&args.args) {
351        Ok(a) => a,
352        Err(e) => {
353            eprintln!("kill: {e}");
354            return ExitCode::FAILURE;
355        }
356    };
357
358    match action {
359        Action::List(arg) => list_signals(arg),
360        Action::Table => table_signals(),
361        Action::Kill { signal, pids } => {
362            let mut failures = 0;
363            let total = pids.len();
364            for pid in pids {
365                if let Err(e) = send_signal(pid, signal) {
366                    eprintln!(
367                        "kill: sending signal to {pid} failed: {}",
368                        std::io::Error::from(e)
369                    );
370                    failures += 1;
371                }
372            }
373            if failures == 0 {
374                ExitCode::SUCCESS
375            } else if failures < total {
376                ExitCode::from(64)
377            } else {
378                ExitCode::FAILURE
379            }
380        }
381    }
382}