nu_command/experimental/
job_unfreeze.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{
3    JobId,
4    engine::{FrozenJob, Job, ThreadJob},
5    process::check_ok,
6};
7use nu_system::{ForegroundWaitStatus, kill_by_pid};
8
9#[derive(Clone)]
10pub struct JobUnfreeze;
11
12impl Command for JobUnfreeze {
13    fn name(&self) -> &str {
14        "job unfreeze"
15    }
16
17    fn description(&self) -> &str {
18        "Unfreeze a frozen process job in foreground."
19    }
20
21    fn signature(&self) -> nu_protocol::Signature {
22        Signature::build("job unfreeze")
23            .category(Category::Experimental)
24            .optional("id", SyntaxShape::Int, "The process id to unfreeze.")
25            .input_output_types(vec![(Type::Nothing, Type::Nothing)])
26            .allow_variants_without_examples(true)
27    }
28
29    fn search_terms(&self) -> Vec<&str> {
30        vec!["fg"]
31    }
32
33    fn run(
34        &self,
35        engine_state: &EngineState,
36        stack: &mut Stack,
37        call: &Call,
38        _input: PipelineData,
39    ) -> Result<PipelineData, ShellError> {
40        let head = call.head;
41
42        let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!");
43
44        let id: Option<usize> = call.opt(engine_state, stack, 0)?;
45        let id = id
46            .map(JobId::new)
47            .or_else(|| jobs.most_recent_frozen_job_id())
48            .ok_or(JobError::NoneToUnfreeze { span: head })?;
49
50        let job = match jobs.lookup(id) {
51            None => return Err(JobError::NotFound { span: head, id }.into()),
52            Some(Job::Thread(ThreadJob { .. })) => {
53                return Err(JobError::CannotUnfreeze { span: head, id }.into());
54            }
55            Some(Job::Frozen(FrozenJob { .. })) => jobs
56                .remove_job(id)
57                .expect("job was supposed to be in job list"),
58        };
59
60        drop(jobs);
61
62        unfreeze_job(engine_state, id, job, head)?;
63
64        Ok(Value::nothing(head).into_pipeline_data())
65    }
66
67    fn examples(&self) -> Vec<Example<'_>> {
68        vec![
69            Example {
70                example: "job unfreeze",
71                description: "Unfreeze the latest frozen job",
72                result: None,
73            },
74            Example {
75                example: "job unfreeze 4",
76                description: "Unfreeze a specific frozen job by its PID",
77                result: None,
78            },
79        ]
80    }
81
82    fn extra_description(&self) -> &str {
83        r#"When a running process is frozen (with the SIGTSTP signal or with the Ctrl-Z key on unix),
84a background job gets registered for this process, which can then be resumed using this command."#
85    }
86}
87
88fn unfreeze_job(
89    state: &EngineState,
90    old_id: JobId,
91    job: Job,
92    span: Span,
93) -> Result<(), ShellError> {
94    match job {
95        Job::Thread(ThreadJob { .. }) => Err(JobError::CannotUnfreeze { span, id: old_id }.into()),
96        Job::Frozen(FrozenJob {
97            unfreeze: handle,
98            tag,
99        }) => {
100            let pid = handle.pid();
101
102            if let Some(thread_job) = &state.current_thread_job()
103                && !thread_job.try_add_pid(pid)
104            {
105                kill_by_pid(pid.into()).map_err(|err| {
106                    ShellError::Io(IoError::new_internal(
107                        err,
108                        "job was interrupted; could not kill foreground process",
109                        nu_protocol::location!(),
110                    ))
111                })?;
112            }
113
114            let result = handle.unfreeze(
115                state
116                    .is_interactive
117                    .then(|| state.pipeline_externals_state.clone()),
118            );
119
120            if let Some(thread_job) = &state.current_thread_job() {
121                thread_job.remove_pid(pid);
122            }
123
124            match result {
125                Ok(ForegroundWaitStatus::Frozen(handle)) => {
126                    let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!");
127
128                    jobs.add_job_with_id(
129                        old_id,
130                        Job::Frozen(FrozenJob {
131                            unfreeze: handle,
132                            tag,
133                        }),
134                    )
135                    .expect("job was supposed to be removed");
136
137                    if state.is_interactive {
138                        println!("\nJob {} is re-frozen", old_id.get());
139                    }
140                    Ok(())
141                }
142
143                Ok(ForegroundWaitStatus::Finished(status)) => check_ok(status, false, span),
144
145                Err(err) => Err(ShellError::Io(IoError::new_internal(
146                    err,
147                    "Failed to unfreeze foreground process",
148                    nu_protocol::location!(),
149                ))),
150            }
151        }
152    }
153}