1use clap::Parser;
2use procutils_common::{
3 MAX_TERM_WIDTH,
4 man::ManContent,
5 procmatch::{MatchOptions, ProcessInfo},
6 signal::parse_signal,
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 {
43 s == "0" || parse_signal(s).is_some()
44}
45
46pub const MAN: ManContent = ManContent {
47 description: Some(include_str!("../man/description.man")),
48 extra_sections: &[
49 ("EXAMPLES", include_str!("../man/examples.man")),
50 ("NOTES", include_str!("../man/notes.man")),
51 ("DIVERGENCES", include_str!("../man/divergences.man")),
52 ("SEE ALSO", include_str!("../man/see_also.man")),
53 ],
54};
55
56const SIGNAL: &str = "Signal";
57const MATCHING: &str = "Matching";
58const FILTERS: &str = "Filters";
59const SELECTION: &str = "Selection";
60const OUTPUT: &str = "Output";
61
62#[derive(Parser)]
64#[command(name = "pkill", version, about, max_term_width = MAX_TERM_WIDTH)]
65pub struct Args {
66 #[arg(long, default_value = "TERM", help_heading = SIGNAL)]
68 signal: String,
69
70 #[arg(short = 'q', long, value_name = "VALUE", help_heading = SIGNAL)]
74 queue: Option<i32>,
75
76 #[arg(short, long, help_heading = MATCHING)]
78 full: bool,
79
80 #[arg(short, long, help_heading = MATCHING)]
82 ignore_case: bool,
83
84 #[arg(short = 'x', long, help_heading = MATCHING)]
86 exact: bool,
87
88 #[arg(short = 'r', long, value_delimiter = ',', help_heading = FILTERS)]
90 runstates: Option<Vec<char>>,
91
92 #[arg(long, value_name = "NAME[=VALUE]", help_heading = FILTERS)]
96 env: Option<String>,
97
98 #[arg(short = 'O', long, help_heading = FILTERS)]
100 older: Option<f64>,
101
102 #[arg(short = 'p', long = "pid", value_delimiter = ',', help_heading = FILTERS)]
104 pid: Option<Vec<i32>>,
105
106 #[arg(
109 short = 'F',
110 long = "pidfile",
111 value_name = "FILE",
112 conflicts_with = "pid",
113 help_heading = FILTERS,
114 )]
115 pidfile: Option<std::path::PathBuf>,
116
117 #[arg(short = 'P', long, value_delimiter = ',', help_heading = FILTERS)]
119 parent: Option<Vec<i32>>,
120
121 #[arg(short = 'g', long = "pgroup", value_delimiter = ',', help_heading = FILTERS)]
123 pgroup: Option<Vec<i32>>,
124
125 #[arg(short = 'G', long = "group", value_delimiter = ',', help_heading = FILTERS)]
127 group: Option<Vec<u32>>,
128
129 #[arg(short = 's', long, value_delimiter = ',', help_heading = FILTERS)]
131 session: Option<Vec<i32>>,
132
133 #[arg(short = 't', long = "terminal", value_delimiter = ',', help_heading = FILTERS)]
135 terminal: Option<Vec<String>>,
136
137 #[arg(short = 'u', long = "euid", value_delimiter = ',', help_heading = FILTERS)]
139 euid: Option<Vec<String>>,
140
141 #[arg(short = 'U', long = "uid", value_delimiter = ',', help_heading = FILTERS)]
143 uid: Option<Vec<String>>,
144
145 #[arg(short, long, help_heading = SELECTION)]
147 newest: bool,
148
149 #[arg(short, long, help_heading = SELECTION)]
151 oldest: bool,
152
153 #[arg(short, long, help_heading = OUTPUT)]
155 count: bool,
156
157 #[arg(short, long, help_heading = OUTPUT)]
159 echo: bool,
160
161 pattern: Option<String>,
163}
164
165fn parse_signum(s: &str) -> Option<i32> {
168 if s == "0" {
169 return Some(0);
170 }
171 parse_signal(s).map(|sig| sig.as_raw())
172}
173
174fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
175 let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
176 rc == 0
177}
178
179fn send_queued_signal(proc: &ProcessInfo, signum: i32, value: i32) -> bool {
180 let sigval = libc::sigval {
185 sival_ptr: value as usize as *mut libc::c_void,
186 };
187 let rc =
188 unsafe { libc::sigqueue(proc.pid as libc::pid_t, signum, sigval) };
189 rc == 0
190}
191
192pub fn run(args: Args) -> ExitCode {
193 let signum = match parse_signum(&args.signal) {
194 Some(n) => n,
195 None => {
196 eprintln!("pkill: unknown signal: {}", args.signal);
197 return ExitCode::from(2);
198 }
199 };
200
201 let pid_filter = match args.pidfile.as_deref() {
202 Some(path) => match procutils_common::procmatch::read_pidfile(path) {
203 Ok(pids) => Some(pids),
204 Err(e) => {
205 eprintln!("pkill: {e}");
206 return ExitCode::from(2);
207 }
208 },
209 None => args.pid.clone(),
210 };
211
212 let opts = MatchOptions {
213 pattern: args.pattern.clone().unwrap_or_default(),
214 full: args.full,
215 ignore_case: args.ignore_case,
216 pid: pid_filter,
217 exact: args.exact,
218 inverse: false,
219 newest: args.newest,
220 oldest: args.oldest,
221 older: args.older,
222 parent: args.parent,
223 pgroup: args.pgroup,
224 group: args.group,
225 session: args.session,
226 terminal: args.terminal,
227 euid: args.euid,
228 uid: args.uid,
229 runstates: args.runstates,
230 env: args.env,
231 };
232
233 if args.pattern.is_none() && !opts.has_filter() {
234 eprintln!("pkill: pattern is required");
235 return ExitCode::from(2);
236 }
237
238 let matches = match procutils_common::procmatch::find_matching_processes(
239 &opts, "pkill",
240 ) {
241 Ok(m) => m,
242 Err(code) => return code,
243 };
244
245 if matches.is_empty() {
246 return ExitCode::from(1);
247 }
248
249 if args.count {
250 println!("{}", matches.len());
251 return ExitCode::SUCCESS;
252 }
253
254 let mut any_failed = false;
255 for proc in &matches {
256 let sent = match args.queue {
257 Some(value) => send_queued_signal(proc, signum, value),
258 None => send_signal(proc, signum),
259 };
260 if sent {
261 if args.echo {
262 println!("{} killed (pid {})", proc.comm, proc.pid);
263 }
264 } else {
265 any_failed = true;
266 }
267 }
268
269 if any_failed {
270 ExitCode::FAILURE
271 } else {
272 ExitCode::SUCCESS
273 }
274}