Skip to main content

proc_cli/commands/
thaw.rs

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