1use clap::Parser;
2use procutils_common::{
3 MAX_TERM_WIDTH,
4 man::ManContent,
5 procmatch::{MatchOptions, ProcessInfo},
6 signal::parse_signum_any,
7};
8use std::process::ExitCode;
9
10pub fn preprocess_argv(argv: Vec<String>) -> Vec<String> {
16 let mut out = Vec::with_capacity(argv.len() + 1);
17 let mut iter = argv.into_iter();
18 if let Some(arg0) = iter.next() {
19 out.push(arg0);
20 }
21 let mut found = false;
22 for arg in iter {
23 if !found
24 && arg.starts_with('-')
25 && !arg.starts_with("--")
26 && arg.len() > 1
27 && is_signal_spec(&arg[1..])
28 {
29 out.push("--signal".to_string());
30 out.push(arg[1..].to_string());
31 found = true;
32 } else {
33 out.push(arg);
34 }
35 }
36 out
37}
38
39fn is_signal_spec(s: &str) -> bool {
44 s == "0" || parse_signum_any(s).is_some()
45}
46
47pub const MAN: ManContent = ManContent {
48 description: Some(include_str!("../man/description.man")),
49 extra_sections: &[
50 ("EXAMPLES", include_str!("../man/examples.man")),
51 ("NOTES", include_str!("../man/notes.man")),
52 ("DIVERGENCES", include_str!("../man/divergences.man")),
53 ("SEE ALSO", include_str!("../man/see_also.man")),
54 ],
55};
56
57const SIGNAL: &str = "Signal";
58const MATCHING: &str = "Matching";
59const FILTERS: &str = "Filters";
60const SELECTION: &str = "Selection";
61const OUTPUT: &str = "Output";
62
63#[derive(Parser)]
65#[command(name = "pkill", version, about, max_term_width = MAX_TERM_WIDTH)]
66pub struct Args {
67 #[arg(long, default_value = "TERM", help_heading = SIGNAL)]
69 signal: String,
70
71 #[arg(short = 'q', long, value_name = "VALUE", help_heading = SIGNAL)]
75 queue: Option<i32>,
76
77 #[arg(short, long, help_heading = MATCHING)]
79 full: bool,
80
81 #[arg(short, long, help_heading = MATCHING)]
83 ignore_case: bool,
84
85 #[arg(short = 'x', long, help_heading = MATCHING)]
87 exact: bool,
88
89 #[arg(short = 'r', long, value_delimiter = ',', help_heading = FILTERS)]
91 runstates: Option<Vec<char>>,
92
93 #[arg(long, value_name = "NAME[=VALUE]", help_heading = FILTERS)]
97 env: Option<String>,
98
99 #[arg(short = 'O', long, help_heading = FILTERS)]
101 older: Option<f64>,
102
103 #[arg(short = 'p', long = "pid", value_delimiter = ',', help_heading = FILTERS)]
105 pid: Option<Vec<i32>>,
106
107 #[arg(
110 short = 'F',
111 long = "pidfile",
112 value_name = "FILE",
113 conflicts_with = "pid",
114 help_heading = FILTERS,
115 )]
116 pidfile: Option<std::path::PathBuf>,
117
118 #[arg(short = 'P', long, value_delimiter = ',', help_heading = FILTERS)]
120 parent: Option<Vec<i32>>,
121
122 #[arg(short = 'g', long = "pgroup", value_delimiter = ',', help_heading = FILTERS)]
124 pgroup: Option<Vec<i32>>,
125
126 #[arg(short = 'G', long = "group", value_delimiter = ',', help_heading = FILTERS)]
128 group: Option<Vec<u32>>,
129
130 #[arg(short = 's', long, value_delimiter = ',', help_heading = FILTERS)]
132 session: Option<Vec<i32>>,
133
134 #[arg(short = 't', long = "terminal", value_delimiter = ',', help_heading = FILTERS)]
136 terminal: Option<Vec<String>>,
137
138 #[arg(short = 'u', long = "euid", value_delimiter = ',', help_heading = FILTERS)]
140 euid: Option<Vec<String>>,
141
142 #[arg(short = 'U', long = "uid", value_delimiter = ',', help_heading = FILTERS)]
144 uid: Option<Vec<String>>,
145
146 #[arg(short, long, help_heading = SELECTION)]
148 newest: bool,
149
150 #[arg(short, long, help_heading = SELECTION)]
152 oldest: bool,
153
154 #[arg(short, long, help_heading = OUTPUT)]
156 count: bool,
157
158 #[arg(short, long, help_heading = OUTPUT)]
160 echo: bool,
161
162 pattern: Option<String>,
164}
165
166fn parse_signum(s: &str) -> Option<i32> {
171 if s == "0" {
172 return Some(0);
173 }
174 parse_signum_any(s)
175}
176
177fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
178 let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
179 rc == 0
180}
181
182fn send_queued_signal(proc: &ProcessInfo, signum: i32, value: i32) -> bool {
183 let sigval = libc::sigval {
188 sival_ptr: value as usize as *mut libc::c_void,
189 };
190 let rc = unsafe { libc::sigqueue(proc.pid as libc::pid_t, signum, sigval) };
191 rc == 0
192}
193
194pub fn run(args: Args) -> ExitCode {
195 let signum = match parse_signum(&args.signal) {
196 Some(n) => n,
197 None => {
198 eprintln!("pkill: unknown signal: {}", args.signal);
199 return ExitCode::from(2);
200 }
201 };
202
203 let pid_filter = match args.pidfile.as_deref() {
204 Some(path) => match procutils_common::procmatch::read_pidfile(path) {
205 Ok(pids) => Some(pids),
206 Err(e) => {
207 eprintln!("pkill: {e}");
208 return ExitCode::from(2);
209 }
210 },
211 None => args.pid.clone(),
212 };
213
214 let opts = MatchOptions {
215 pattern: args.pattern.clone().unwrap_or_default(),
216 full: args.full,
217 ignore_case: args.ignore_case,
218 pid: pid_filter,
219 exact: args.exact,
220 inverse: false,
221 newest: args.newest,
222 oldest: args.oldest,
223 older: args.older,
224 parent: args.parent,
225 pgroup: args.pgroup,
226 group: args.group,
227 session: args.session,
228 terminal: args.terminal,
229 euid: args.euid,
230 uid: args.uid,
231 runstates: args.runstates,
232 env: args.env,
233 };
234
235 if args.pattern.is_none() && !opts.has_filter() {
236 eprintln!("pkill: pattern is required");
237 return ExitCode::from(2);
238 }
239
240 let matches = match procutils_common::procmatch::find_matching_processes(
241 &opts, "pkill",
242 ) {
243 Ok(m) => m,
244 Err(code) => return code,
245 };
246
247 if matches.is_empty() {
248 return ExitCode::from(1);
249 }
250
251 if args.count {
252 println!("{}", matches.len());
253 return ExitCode::SUCCESS;
254 }
255
256 let mut any_failed = false;
257 for proc in &matches {
258 let sent = match args.queue {
259 Some(value) => send_queued_signal(proc, signum, value),
260 None => send_signal(proc, signum),
261 };
262 if sent {
263 if args.echo {
264 println!("{} killed (pid {})", proc.comm, proc.pid);
265 }
266 } else {
267 any_failed = true;
268 }
269 }
270
271 if any_failed {
272 ExitCode::FAILURE
273 } else {
274 ExitCode::SUCCESS
275 }
276}