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 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}