1use clap::Parser;
2use procutils_common::{
3 MAX_TERM_WIDTH,
4 man::ManContent,
5 procmatch::{MatchOptions, ProcessInfo, 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 ("DIVERGENCES", include_str!("../man/divergences.man")),
15 ("SEE ALSO", include_str!("../man/see_also.man")),
16 ],
17};
18
19pub fn preprocess_argv(argv: Vec<String>) -> Vec<String> {
23 let mut out = Vec::with_capacity(argv.len() + 1);
24 let mut iter = argv.into_iter();
25 if let Some(arg0) = iter.next() {
26 out.push(arg0);
27 }
28 let mut found = false;
29 for arg in iter {
30 if !found
31 && arg.starts_with('-')
32 && !arg.starts_with("--")
33 && arg.len() > 1
34 && parse_signum_any(&arg[1..]).is_some()
35 {
36 out.push("--signal".to_string());
37 out.push(arg[1..].to_string());
38 found = true;
39 } else {
40 out.push(arg);
41 }
42 }
43 out
44}
45
46#[derive(Parser)]
48#[command(name = "killall", version, about, max_term_width = MAX_TERM_WIDTH)]
49pub struct Args {
50 pub name: Vec<String>,
52
53 #[arg(short = 's', long, default_value = "TERM")]
55 pub signal: String,
56
57 #[arg(short = 'I', long)]
59 pub ignore_case: bool,
60
61 #[arg(short = 'u', long, value_name = "USER")]
63 pub user: Option<String>,
64
65 #[arg(short = 'l', long, conflicts_with_all = ["name", "user", "verbose"])]
67 pub list: bool,
68
69 #[arg(short = 'q', long)]
71 pub quiet: bool,
72
73 #[arg(short = 'v', long)]
75 pub verbose: bool,
76}
77
78pub fn run(args: Args) -> ExitCode {
79 if args.list {
80 for (_, name) in all_signals() {
81 println!("{name}");
82 }
83 return ExitCode::SUCCESS;
84 }
85
86 if args.name.is_empty() {
87 eprintln!("killall: at least one process name is required");
88 return ExitCode::from(2);
89 }
90
91 let signum = match parse_signum_any(&args.signal) {
92 Some(n) => n,
93 None => {
94 eprintln!("killall: unknown signal: {}", args.signal);
95 return ExitCode::from(2);
96 }
97 };
98
99 let mut any_missing = false;
100 let mut any_kill_failed = false;
101
102 for name in &args.name {
103 let opts = MatchOptions {
104 pattern: regex::escape(name),
105 full: false,
106 ignore_case: args.ignore_case,
107 exact: true,
108 inverse: false,
109 newest: false,
110 oldest: false,
111 older: None,
112 pid: None,
113 parent: None,
114 pgroup: None,
115 group: None,
116 session: None,
117 terminal: None,
118 euid: None,
119 uid: args.user.clone().map(|u| vec![u]),
120 runstates: None,
121 env: None,
122 };
123
124 let matches = match find_matching_processes(&opts, "killall") {
125 Ok(m) => m,
126 Err(code) => return code,
127 };
128
129 if matches.is_empty() {
130 if !args.quiet {
131 eprintln!("killall: {name}: no process found");
132 }
133 any_missing = true;
134 continue;
135 }
136
137 for proc in &matches {
138 if send_signal(proc, signum) {
139 if args.verbose {
140 println!(
141 "Killed {comm}({pid}) with signal {sig}",
142 comm = proc.comm,
143 pid = proc.pid,
144 sig = args.signal,
145 );
146 }
147 } else {
148 any_kill_failed = true;
149 }
150 }
151 }
152
153 if any_missing || any_kill_failed {
154 ExitCode::FAILURE
155 } else {
156 ExitCode::SUCCESS
157 }
158}
159
160fn send_signal(proc: &ProcessInfo, signum: i32) -> bool {
161 let rc = unsafe { libc::kill(proc.pid as libc::pid_t, signum) };
162 rc == 0
163}