uu_kill/
kill.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5
6// spell-checker:ignore (ToDO) signalname pids killpg
7
8use clap::{Arg, ArgAction, Command};
9use nix::sys::signal::{self, Signal};
10use nix::unistd::Pid;
11use std::io::Error;
12use uucore::display::Quotable;
13use uucore::error::{FromIo, UResult, USimpleError};
14use uucore::translate;
15
16use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value};
17use uucore::{format_usage, show};
18
19// When the -l option is selected, the program displays the type of signal related to a certain
20// value or string. In case of a value, the program should control the lower 8 bits, but there is
21// a particular case in which if the value is in range [128, 159], it is translated to a signal
22const OFFSET: usize = 128;
23
24pub mod options {
25    pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
26    pub static LIST: &str = "list";
27    pub static TABLE: &str = "table";
28    pub static SIGNAL: &str = "signal";
29}
30
31#[derive(Clone, Copy)]
32pub enum Mode {
33    Kill,
34    Table,
35    List,
36}
37
38#[uucore::main]
39pub fn uumain(args: impl uucore::Args) -> UResult<()> {
40    let mut args = args.collect_ignore();
41    let obs_signal = handle_obsolete(&mut args);
42
43    let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
44
45    let mode = if matches.get_flag(options::TABLE) {
46        Mode::Table
47    } else if matches.get_flag(options::LIST) {
48        Mode::List
49    } else {
50        Mode::Kill
51    };
52
53    let pids_or_signals: Vec<String> = matches
54        .get_many::<String>(options::PIDS_OR_SIGNALS)
55        .map(|v| v.map(ToString::to_string).collect())
56        .unwrap_or_default();
57
58    match mode {
59        Mode::Kill => {
60            let sig = if let Some(signal) = obs_signal {
61                signal
62            } else if let Some(signal) = matches.get_one::<String>(options::SIGNAL) {
63                parse_signal_value(signal)?
64            } else {
65                15_usize //SIGTERM
66            };
67
68            let sig_name = signal_name_by_value(sig);
69            // Signal does not support converting from EXIT
70            // Instead, nix::signal::kill expects Option::None to properly handle EXIT
71            let sig: Option<Signal> = if sig_name.is_some_and(|name| name == "EXIT") {
72                None
73            } else {
74                let sig = (sig as i32)
75                    .try_into()
76                    .map_err(|e| Error::from_raw_os_error(e as i32))?;
77                Some(sig)
78            };
79
80            let pids = parse_pids(&pids_or_signals)?;
81            if pids.is_empty() {
82                Err(USimpleError::new(1, translate!("kill-error-no-process-id")))
83            } else {
84                kill(sig, &pids);
85                Ok(())
86            }
87        }
88        Mode::Table => {
89            table();
90            Ok(())
91        }
92        Mode::List => {
93            list(&pids_or_signals);
94            Ok(())
95        }
96    }
97}
98
99pub fn uu_app() -> Command {
100    Command::new(uucore::util_name())
101        .version(uucore::crate_version!())
102        .help_template(uucore::localized_help_template(uucore::util_name()))
103        .about(translate!("kill-about"))
104        .override_usage(format_usage(&translate!("kill-usage")))
105        .infer_long_args(true)
106        .allow_negative_numbers(true)
107        .arg(
108            Arg::new(options::LIST)
109                .short('l')
110                .long(options::LIST)
111                .help(translate!("kill-help-list"))
112                .conflicts_with(options::TABLE)
113                .action(ArgAction::SetTrue),
114        )
115        .arg(
116            Arg::new(options::TABLE)
117                .short('t')
118                .short_alias('L')
119                .long(options::TABLE)
120                .help(translate!("kill-help-table"))
121                .action(ArgAction::SetTrue),
122        )
123        .arg(
124            Arg::new(options::SIGNAL)
125                .short('s')
126                .short_alias('n') // For bash compatibility, like in GNU coreutils
127                .long(options::SIGNAL)
128                .value_name("signal")
129                .help(translate!("kill-help-signal"))
130                .conflicts_with_all([options::LIST, options::TABLE]),
131        )
132        .arg(
133            Arg::new(options::PIDS_OR_SIGNALS)
134                .hide(true)
135                .action(ArgAction::Append),
136        )
137}
138
139fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
140    // Sanity check
141    if args.len() > 2 {
142        // Old signal can only be in the first argument position
143        let slice = args[1].as_str();
144        if let Some(signal) = slice.strip_prefix('-') {
145            // With '-', a signal name must start with an uppercase char
146            if signal.chars().next().is_some_and(|c| c.is_lowercase()) {
147                return None;
148            }
149            // Check if it is a valid signal
150            let opt_signal = signal_by_name_or_value(signal);
151            if opt_signal.is_some() {
152                // remove the signal before return
153                args.remove(1);
154                return opt_signal;
155            }
156        }
157    }
158    None
159}
160
161fn table() {
162    for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
163        println!("{idx: >#2} {signal}");
164    }
165}
166
167fn print_signal(signal_name_or_value: &str) -> UResult<()> {
168    // Closure used to track the last 8 bits of the signal value
169    // when the -l option is passed only the lower 8 bits are important
170    // or the value is in range [128, 159]
171    // Example: kill -l 143 => TERM because 143 = 15 + 128
172    // Example: kill -l 2304 => EXIT
173    let lower_8_bits = |x: usize| x & 0xff;
174    let option_num_parse = signal_name_or_value.parse::<usize>().ok();
175
176    for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
177        if signal.eq_ignore_ascii_case(signal_name_or_value)
178            || format!("SIG{signal}").eq_ignore_ascii_case(signal_name_or_value)
179        {
180            println!("{value}");
181            return Ok(());
182        } else if signal_name_or_value == value.to_string()
183            || option_num_parse.is_some_and(|signal_value| lower_8_bits(signal_value) == value)
184            || option_num_parse.is_some_and(|signal_value| signal_value == value + OFFSET)
185        {
186            println!("{signal}");
187            return Ok(());
188        }
189    }
190    Err(USimpleError::new(
191        1,
192        translate!("kill-error-invalid-signal", "signal" => signal_name_or_value.quote()),
193    ))
194}
195
196fn print_signals() {
197    for signal in ALL_SIGNALS {
198        println!("{signal}");
199    }
200}
201
202fn list(signals: &Vec<String>) {
203    if signals.is_empty() {
204        print_signals();
205    } else {
206        for signal in signals {
207            if let Err(e) = print_signal(signal) {
208                uucore::show!(e);
209            }
210        }
211    }
212}
213
214fn parse_signal_value(signal_name: &str) -> UResult<usize> {
215    let optional_signal_value = signal_by_name_or_value(signal_name);
216    match optional_signal_value {
217        Some(x) => Ok(x),
218        None => Err(USimpleError::new(
219            1,
220            translate!("kill-error-invalid-signal", "signal" => signal_name.quote()),
221        )),
222    }
223}
224
225fn parse_pids(pids: &[String]) -> UResult<Vec<i32>> {
226    pids.iter()
227        .map(|x| {
228            x.parse::<i32>().map_err(|e| {
229                USimpleError::new(
230                    1,
231                    translate!("kill-error-parse-argument", "argument" => x.quote(), "error" => e),
232                )
233            })
234        })
235        .collect()
236}
237
238fn kill(sig: Option<Signal>, pids: &[i32]) {
239    for &pid in pids {
240        if let Err(e) = signal::kill(Pid::from_raw(pid), sig) {
241            show!(
242                Error::from_raw_os_error(e as i32)
243                    .map_err_context(|| { translate!("kill-error-sending-signal", "pid" => pid) })
244            );
245        }
246    }
247}