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