nu_command/experimental/
job_unfreeze.rs1use 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}