Skip to main content

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_filters, apply_to_each, flatten_targets, resolve_targets_excluding_self, NameMatch,
17    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    /// Match name targets and --by as a substring of the process name or full command line (like pkill -f)
60    #[arg(long)]
61    pub cmd: bool,
62}
63
64impl KillCommand {
65    /// Executes the kill command, forcefully terminating matched processes.
66    pub fn execute(&self) -> Result<()> {
67        let printer = Printer::from_flags(self.json, self.verbose);
68
69        // Split each value on commas and resolve to processes
70        // Use resolve_targets_excluding_self to avoid killing ourselves
71        let targets = flatten_targets(&self.target);
72        let mode = NameMatch::from_flag(self.cmd);
73        let resolved = resolve_targets_excluding_self(&targets, mode);
74
75        // Warn about targets that weren't found
76        if let Some(warning) = resolved.warning() {
77            printer.warning(&warning);
78        }
79        // the JSON results list the targets that matched nothing
80        let printer = printer.with_not_found(&resolved.not_found);
81        let mut processes = resolved.processes;
82
83        // Apply --in and --by filters
84        apply_filters(&mut processes, &self.in_dir, &self.by_name, mode);
85
86        // Real CPU readings whenever the list is shown or returned (dry run, the
87        // confirmation prompt, or the error for a missing `--yes`); only `--yes`
88        // acts at once, without the wait
89        if self.dry_run || !self.yes {
90            Process::measure_cpu(&mut processes);
91        }
92
93        if processes.is_empty() {
94            return Err(ProcError::ProcessNotFound(self.target.join(" ")));
95        }
96
97        // Dry run: just show what would be killed
98        if self.dry_run {
99            printer.print_dry_run("kill", &processes);
100            return Ok(());
101        }
102
103        // Confirm before killing (unless --yes)
104        if !printer.ask_confirm("kill", &processes, self.yes)? {
105            return Ok(());
106        }
107
108        // Kill the processes
109        let (killed, failed) = apply_to_each(&processes, |proc| {
110            if self.graceful {
111                proc.terminate()
112            } else {
113                proc.kill()
114            }
115        });
116
117        printer.print_action_result("kill", &killed, &failed)
118    }
119}