proc_cli/commands/
stop.rs1#[cfg(unix)]
11use crate::core::parse_signal_name;
12use crate::core::{parse_targets, resolve_in_dir, resolve_targets_excluding_self, Process};
13use crate::error::{ProcError, Result};
14use crate::ui::{OutputFormat, Printer};
15use clap::Args;
16use dialoguer::Confirm;
17use std::path::PathBuf;
18
19#[derive(Args, Debug)]
21pub struct StopCommand {
22 #[arg(required = true)]
24 target: String,
25
26 #[arg(long, short = 'y')]
28 yes: bool,
29
30 #[arg(long)]
32 dry_run: bool,
33
34 #[arg(long, short = 'j')]
36 json: bool,
37
38 #[arg(long, short = 'v')]
40 verbose: bool,
41
42 #[arg(long, short, default_value = "10")]
44 timeout: u64,
45
46 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
48 pub in_dir: Option<String>,
49
50 #[arg(long = "by", short = 'b')]
52 pub by_name: Option<String>,
53
54 #[arg(long, short = 'S')]
56 pub signal: Option<String>,
57}
58
59impl StopCommand {
60 pub fn execute(&self) -> Result<()> {
62 let format = if self.json {
63 OutputFormat::Json
64 } else {
65 OutputFormat::Human
66 };
67 let printer = Printer::new(format, self.verbose);
68
69 let targets = parse_targets(&self.target);
72 let (mut processes, not_found) = resolve_targets_excluding_self(&targets);
73
74 if !not_found.is_empty() {
76 printer.warning(&format!("Not found: {}", not_found.join(", ")));
77 }
78
79 let in_dir_filter = resolve_in_dir(&self.in_dir);
81 processes.retain(|p| {
82 if let Some(ref dir_path) = in_dir_filter {
83 if let Some(ref cwd) = p.cwd {
84 if !PathBuf::from(cwd).starts_with(dir_path) {
85 return false;
86 }
87 } else {
88 return false;
89 }
90 }
91 if let Some(ref name) = self.by_name {
92 if !p.name.to_lowercase().contains(&name.to_lowercase()) {
93 return false;
94 }
95 }
96 true
97 });
98
99 if processes.is_empty() {
100 return Err(ProcError::ProcessNotFound(self.target.clone()));
101 }
102
103 if self.dry_run {
105 printer.print_processes(&processes);
106 printer.warning(&format!(
107 "Dry run: would stop {} process{}",
108 processes.len(),
109 if processes.len() == 1 { "" } else { "es" }
110 ));
111 return Ok(());
112 }
113
114 if !self.yes && !self.json {
116 printer.print_confirmation("stop", &processes);
117
118 let prompt = format!(
119 "Stop {} process{}?",
120 processes.len(),
121 if processes.len() == 1 { "" } else { "es" }
122 );
123
124 if !Confirm::new()
125 .with_prompt(prompt)
126 .default(false)
127 .interact()?
128 {
129 printer.warning("Aborted");
130 return Ok(());
131 }
132 }
133
134 #[cfg(unix)]
136 let custom_signal = if let Some(ref sig_name) = self.signal {
137 Some(parse_signal_name(sig_name)?)
138 } else {
139 None
140 };
141
142 let mut stopped = Vec::new();
144 let mut failed = Vec::new();
145
146 for proc in &processes {
147 #[cfg(unix)]
148 let send_result = if let Some(signal) = custom_signal {
149 proc.send_signal(signal)
150 } else {
151 proc.terminate()
152 };
153 #[cfg(not(unix))]
154 let send_result = proc.terminate();
155
156 match send_result {
157 Ok(()) => {
158 let stopped_gracefully = self.wait_for_exit(proc);
160 if stopped_gracefully {
161 stopped.push(proc.clone());
162 } else {
163 match proc.kill_and_wait() {
165 Ok(_) => stopped.push(proc.clone()),
166 Err(e) => failed.push((proc.clone(), e.to_string())),
167 }
168 }
169 }
170 Err(e) => failed.push((proc.clone(), e.to_string())),
171 }
172 }
173
174 printer.print_action_result("Stopped", &stopped, &failed);
176
177 Ok(())
178 }
179
180 fn wait_for_exit(&self, proc: &Process) -> bool {
181 let start = std::time::Instant::now();
182 let timeout = std::time::Duration::from_secs(self.timeout);
183
184 while start.elapsed() < timeout {
185 if !proc.is_running() {
186 return true;
187 }
188 std::thread::sleep(std::time::Duration::from_millis(100));
189 }
190
191 false
192 }
193}