1use clap::Parser;
2use procutils_common::{
3 MAX_TERM_WIDTH,
4 man::ManContent,
5 procmatch::{MatchOptions, 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 ("NOTES", include_str!("../man/notes.man")),
15 ("DIVERGENCES", include_str!("../man/divergences.man")),
16 ("SEE ALSO", include_str!("../man/see_also.man")),
17 ],
18};
19
20pub fn preprocess_argv(argv: Vec<String>) -> Vec<String> {
25 let mut out = Vec::with_capacity(argv.len() + 1);
26 let mut iter = argv.into_iter();
27 if let Some(arg0) = iter.next() {
28 out.push(arg0);
29 }
30 let mut found = false;
31 for arg in iter {
32 if !found
33 && arg.starts_with('-')
34 && !arg.starts_with("--")
35 && arg.len() > 1
36 && parse_signum_any(&arg[1..]).is_some()
37 {
38 out.push("--signal".to_string());
39 out.push(arg[1..].to_string());
40 found = true;
41 } else {
42 out.push(arg);
43 }
44 }
45 out
46}
47
48#[derive(Parser)]
55#[command(
56 name = "skill",
57 version,
58 about,
59 disable_help_flag = true,
60 max_term_width = MAX_TERM_WIDTH,
61 override_usage = "skill [signal] [options] <expression>"
62)]
63pub struct Args {
64 #[arg(long, action = clap::ArgAction::HelpLong)]
66 help: Option<bool>,
67
68 #[arg(long)]
70 signal: Option<String>,
71
72 #[arg(short = 'l', long)]
74 list: bool,
75
76 #[arg(short = 'L', long)]
78 table: bool,
79
80 #[arg(short = 'n', long = "no-action")]
82 no_action: bool,
83
84 #[arg(short, long)]
86 verbose: bool,
87
88 #[arg(short = 't', long, value_name = "TTY")]
90 tty: Vec<String>,
91
92 #[arg(short = 'u', long, value_name = "USER")]
94 user: Vec<String>,
95
96 #[arg(short = 'p', long, value_name = "PID")]
98 pid: Vec<i32>,
99
100 #[arg(short = 'c', long, value_name = "COMMAND")]
102 command: Vec<String>,
103
104 expr: Vec<String>,
106}
107
108pub fn run(args: Args) -> ExitCode {
109 if args.list {
110 let names: Vec<String> = all_signals().map(|(_, n)| n).collect();
111 println!("{}", names.join(" "));
112 return ExitCode::SUCCESS;
113 }
114
115 if args.table {
116 let mut line = String::new();
117 for (i, (num, name)) in all_signals().enumerate() {
118 line.push_str(&format!("{num:>2} {name}"));
119 if (i + 1) % 8 == 0 {
120 println!("{line}");
121 line.clear();
122 } else {
123 line.push('\t');
124 }
125 }
126 if !line.is_empty() {
127 println!("{}", line.trim_end());
128 }
129 return ExitCode::SUCCESS;
130 }
131
132 let signum: i32 = match args.signal.as_deref() {
134 Some(s) => match parse_signum_any(s) {
135 Some(n) => n,
136 None => {
137 eprintln!("skill: unknown signal: {s}");
138 return ExitCode::from(2);
139 }
140 },
141 None => libc::SIGTERM,
142 };
143
144 let mut pid_filter: Vec<i32> = args.pid.clone();
146 for s in &args.expr {
147 match s.parse::<i32>() {
148 Ok(p) => pid_filter.push(p),
149 Err(_) => {
150 eprintln!("skill: bare expression argument must be a PID: {s}");
151 return ExitCode::from(2);
152 }
153 }
154 }
155
156 let no_filter = pid_filter.is_empty()
157 && args.tty.is_empty()
158 && args.user.is_empty()
159 && args.command.is_empty();
160 if no_filter {
161 eprintln!("skill: no expression given");
162 eprintln!("Usage: skill [signal] [options] <expression>");
163 return ExitCode::from(2);
164 }
165
166 let pattern = if args.command.is_empty() {
169 String::new()
170 } else {
171 let alts: Vec<String> =
172 args.command.iter().map(|c| regex::escape(c)).collect();
173 format!("^({})$", alts.join("|"))
174 };
175
176 let opts = MatchOptions {
177 pattern,
178 full: false,
179 ignore_case: false,
180 exact: false,
181 inverse: false,
182 newest: false,
183 oldest: false,
184 older: None,
185 pid: if pid_filter.is_empty() {
186 None
187 } else {
188 Some(pid_filter)
189 },
190 parent: None,
191 pgroup: None,
192 group: None,
193 session: None,
194 terminal: if args.tty.is_empty() {
195 None
196 } else {
197 Some(args.tty.clone())
198 },
199 euid: if args.user.is_empty() {
200 None
201 } else {
202 Some(args.user.clone())
203 },
204 uid: None,
205 runstates: None,
206 env: None,
207 };
208
209 let matches = match find_matching_processes(&opts, "skill") {
210 Ok(m) => m,
211 Err(code) => return code,
212 };
213
214 if matches.is_empty() {
215 return ExitCode::from(1);
216 }
217
218 if args.no_action {
219 for p in &matches {
220 println!("{}", p.pid);
221 }
222 return ExitCode::SUCCESS;
223 }
224
225 let mut any_failed = false;
226 for p in &matches {
227 let rc = unsafe { libc::kill(p.pid as libc::pid_t, signum) };
228 if rc == 0 {
229 if args.verbose {
230 println!("{}: {}", p.pid, p.comm);
231 }
232 } else {
233 let errno = std::io::Error::last_os_error();
234 eprintln!("skill: ({}) - {errno}", p.pid);
235 any_failed = true;
236 }
237 }
238
239 if any_failed {
240 ExitCode::FAILURE
241 } else {
242 ExitCode::SUCCESS
243 }
244}