Skip to main content

proc_cli/commands/
freeze.rs

1//! `proc freeze` - Pause processes with SIGSTOP
2//!
3//! Examples:
4//!   proc freeze node              # Freeze all node processes
5//!   proc freeze :3000             # Freeze process on port 3000
6//!   proc freeze :3000,:8080       # Freeze multiple targets
7//!   proc freeze node --yes        # Skip confirmation
8//!   proc freeze node --dry-run    # Show what would be frozen
9
10#[cfg(unix)]
11use crate::core::{parse_targets, resolve_in_dir, resolve_targets_excluding_self};
12use crate::error::{ProcError, Result};
13#[cfg(unix)]
14use crate::ui::{OutputFormat, Printer};
15use clap::Args;
16#[cfg(unix)]
17use dialoguer::Confirm;
18#[cfg(unix)]
19use std::path::PathBuf;
20
21/// Freeze (pause) process(es) with SIGSTOP
22#[derive(Args, Debug)]
23pub struct FreezeCommand {
24    /// Target(s): process name, PID, or :port (comma-separated for multiple)
25    #[arg(required = true)]
26    pub target: String,
27
28    /// Skip confirmation prompt
29    #[arg(long, short = 'y')]
30    pub yes: bool,
31
32    /// Show what would be frozen without actually freezing
33    #[arg(long)]
34    pub dry_run: bool,
35
36    /// Output as JSON
37    #[arg(long, short = 'j')]
38    pub json: bool,
39
40    /// Show verbose output
41    #[arg(long, short = 'v')]
42    pub verbose: bool,
43
44    /// Filter by directory (defaults to current directory if no path given)
45    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
46    pub in_dir: Option<String>,
47
48    /// Filter by process name
49    #[arg(long = "by", short = 'b')]
50    pub by_name: Option<String>,
51}
52
53impl FreezeCommand {
54    /// Executes the freeze command, pausing matched processes with SIGSTOP.
55    #[cfg(unix)]
56    pub fn execute(&self) -> Result<()> {
57        use nix::sys::signal::Signal;
58
59        let format = if self.json {
60            OutputFormat::Json
61        } else {
62            OutputFormat::Human
63        };
64        let printer = Printer::new(format, self.verbose);
65
66        let targets = parse_targets(&self.target);
67        let (mut processes, not_found) = resolve_targets_excluding_self(&targets);
68
69        if !not_found.is_empty() {
70            printer.warning(&format!("Not found: {}", not_found.join(", ")));
71        }
72
73        // Apply --in and --by filters
74        let in_dir_filter = resolve_in_dir(&self.in_dir);
75        processes.retain(|p| {
76            if let Some(ref dir_path) = in_dir_filter {
77                if let Some(ref cwd) = p.cwd {
78                    if !PathBuf::from(cwd).starts_with(dir_path) {
79                        return false;
80                    }
81                } else {
82                    return false;
83                }
84            }
85            if let Some(ref name) = self.by_name {
86                if !p.name.to_lowercase().contains(&name.to_lowercase()) {
87                    return false;
88                }
89            }
90            true
91        });
92
93        if processes.is_empty() {
94            return Err(ProcError::ProcessNotFound(self.target.clone()));
95        }
96
97        if self.dry_run {
98            printer.print_processes(&processes);
99            printer.warning(&format!(
100                "Dry run: would freeze {} process{}",
101                processes.len(),
102                if processes.len() == 1 { "" } else { "es" }
103            ));
104            return Ok(());
105        }
106
107        if !self.yes && !self.json {
108            printer.print_confirmation("freeze", &processes);
109
110            let prompt = format!(
111                "Freeze {} process{}?",
112                processes.len(),
113                if processes.len() == 1 { "" } else { "es" }
114            );
115
116            if !Confirm::new()
117                .with_prompt(prompt)
118                .default(false)
119                .interact()?
120            {
121                printer.warning("Aborted");
122                return Ok(());
123            }
124        }
125
126        let mut succeeded = Vec::new();
127        let mut failed = Vec::new();
128
129        for proc in &processes {
130            match proc.send_signal(Signal::SIGSTOP) {
131                Ok(()) => succeeded.push(proc.clone()),
132                Err(e) => failed.push((proc.clone(), e.to_string())),
133            }
134        }
135
136        printer.print_action_result("Frozen", &succeeded, &failed);
137
138        Ok(())
139    }
140
141    /// Windows stub
142    #[cfg(not(unix))]
143    pub fn execute(&self) -> Result<()> {
144        Err(ProcError::NotSupported(
145            "freeze (SIGSTOP) is not supported on Windows".to_string(),
146        ))
147    }
148}