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