proc_cli/commands/kill.rs
1//! `proc kill` - Kill processes
2//!
3//! Examples:
4//! proc kill node # Kill the processes named node
5//! proc kill node --cmd # ... or with node anywhere in their command line
6//! proc kill :3000 # Kill what's on port 3000
7//! proc kill 1234 # Kill specific PID
8//! proc kill 1234 5678 # Kill several targets: separate arguments
9//! proc kill :3000 :8080 node # Mixed targets (port + port + name)
10//! proc kill node --dry-run # Show what would be killed, kill nothing
11//! proc kill :3000 --yes # Skip confirmation (a port or PID, not a name)
12//!
13//! Commas still work (`proc kill :3000,:8080`) but are deprecated.
14
15use crate::core::{
16 apply_to_each, flatten_targets, guard_targets, resolve_targets_excluding_self, Guards,
17 NameMatch, Process,
18};
19use crate::error::{ProcError, Result};
20use crate::ui::Printer;
21use clap::Args;
22
23/// Kill process(es)
24#[derive(Args, Debug)]
25pub struct KillCommand {
26 /// Target(s): process name, PID, or :port (separate arguments for several)
27 #[arg(required = true, num_args = 1.., value_parser = crate::core::validate_target_arg)]
28 pub target: Vec<String>,
29
30 /// Skip confirmation prompt
31 #[arg(long, short = 'y')]
32 pub yes: bool,
33
34 /// Show what would be killed without actually killing
35 #[arg(long)]
36 pub dry_run: bool,
37
38 /// Output as JSON
39 #[arg(long, short = 'j')]
40 pub json: bool,
41
42 /// Show verbose output
43 #[arg(long, short = 'v')]
44 pub verbose: bool,
45
46 /// Send SIGTERM instead of SIGKILL (graceful)
47 #[arg(long, short = 'g')]
48 pub graceful: bool,
49
50 /// Filter by directory; takes the next word as the directory unless it starts with '-'
51 /// (bare --in is the current directory, --in=DIR is explicit)
52 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
53 pub in_dir: Option<String>,
54
55 /// Filter by process name
56 #[arg(long = "by", short = 'b')]
57 pub by_name: Option<String>,
58
59 /// Act only if the process still has this start time (Unix seconds, `start_time`
60 /// in its JSON): a PID the OS gave to a new process is ruled out. One PID target
61 #[arg(long = "start-time", value_name = "SECONDS")]
62 pub start_time: Option<u64>,
63
64 /// Match name targets and --by as a substring of the process name or full command line (like pkill -f)
65 #[arg(long)]
66 pub cmd: bool,
67}
68
69impl KillCommand {
70 /// Executes the kill command, forcefully terminating matched processes.
71 pub fn execute(&self) -> Result<()> {
72 let printer = Printer::from_flags(self.json, self.verbose);
73
74 // Split each value on commas and resolve to processes
75 // Use resolve_targets_excluding_self to avoid killing ourselves
76 let targets = flatten_targets(&self.target);
77 let mode = NameMatch::from_flag(self.cmd);
78 let resolved = resolve_targets_excluding_self(&targets, mode);
79
80 // Warn about targets that weren't found
81 if let Some(warning) = resolved.warning() {
82 printer.warning(&warning);
83 }
84 // the JSON results list the targets that matched nothing
85 let printer = printer.with_not_found(&resolved.not_found);
86 let mut processes = resolved.processes;
87
88 // Apply --in and --by: guards that rule out everything found say so
89 let guards = Guards::new(&self.in_dir, &self.by_name, self.start_time);
90 guard_targets(&mut processes, &self.target, &guards, mode)?;
91
92 // Real CPU readings whenever the list is shown or returned (dry run, the
93 // confirmation prompt, or the error for a missing `--yes`); only `--yes`
94 // acts at once, without the wait
95 if self.dry_run || !self.yes {
96 Process::measure_cpu(&mut processes);
97 }
98
99 if processes.is_empty() {
100 return Err(ProcError::ProcessNotFound(self.target.join(" ")));
101 }
102
103 // Dry run: just show what would be killed
104 if self.dry_run {
105 printer.print_dry_run("kill", &processes);
106 return Ok(());
107 }
108
109 // Confirm before killing (unless --yes)
110 if !printer.ask_confirm("kill", &processes, self.yes)? {
111 return Ok(());
112 }
113
114 // Kill the processes
115 let (killed, failed) = apply_to_each(&processes, |proc| {
116 if self.graceful {
117 proc.terminate()
118 } else {
119 proc.kill()
120 }
121 });
122
123 printer.print_action_result("kill", &killed, &failed)
124 }
125}