use nu_engine::command_prelude::*;
use nu_protocol::{
JobId,
engine::{FrozenJob, Job, ThreadJob},
process::check_ok,
};
use nu_system::{ForegroundWaitStatus, kill_by_pid};
#[derive(Clone)]
pub struct JobUnfreeze;
impl Command for JobUnfreeze {
fn name(&self) -> &str {
"job unfreeze"
}
fn description(&self) -> &str {
"Unfreeze a frozen process job in foreground."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job unfreeze")
.category(Category::Experimental)
.optional("id", SyntaxShape::Int, "The process id to unfreeze.")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec!["fg"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!");
let id: Option<usize> = call.opt(engine_state, stack, 0)?;
let id = id
.map(JobId::new)
.or_else(|| jobs.most_recent_frozen_job_id())
.ok_or(JobError::NoneToUnfreeze { span: head })?;
let job = match jobs.lookup(id) {
None => return Err(JobError::NotFound { span: head, id }.into()),
Some(Job::Thread(ThreadJob { .. })) => {
return Err(JobError::CannotUnfreeze { span: head, id }.into());
}
Some(Job::Frozen(FrozenJob { .. })) => jobs
.remove_job(id)
.expect("job was supposed to be in job list"),
};
drop(jobs);
unfreeze_job(engine_state, id, job, head)?;
Ok(Value::nothing(head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "job unfreeze",
description: "Unfreeze the latest frozen job",
result: None,
},
Example {
example: "job unfreeze 4",
description: "Unfreeze a specific frozen job by its PID",
result: None,
},
]
}
fn extra_description(&self) -> &str {
r#"When a running process is frozen (with the SIGTSTP signal or with the Ctrl-Z key on unix),
a background job gets registered for this process, which can then be resumed using this command."#
}
}
fn unfreeze_job(
state: &EngineState,
old_id: JobId,
job: Job,
span: Span,
) -> Result<(), ShellError> {
match job {
Job::Thread(ThreadJob { .. }) => Err(JobError::CannotUnfreeze { span, id: old_id }.into()),
Job::Frozen(FrozenJob {
unfreeze: handle,
tag,
}) => {
let pid = handle.pid();
if let Some(thread_job) = &state.current_thread_job() {
if !thread_job.try_add_pid(pid) {
kill_by_pid(pid.into()).map_err(|err| {
ShellError::Io(IoError::new_internal(
err,
"job was interrupted; could not kill foreground process",
nu_protocol::location!(),
))
})?;
}
}
let result = handle.unfreeze(
state
.is_interactive
.then(|| state.pipeline_externals_state.clone()),
);
if let Some(thread_job) = &state.current_thread_job() {
thread_job.remove_pid(pid);
}
match result {
Ok(ForegroundWaitStatus::Frozen(handle)) => {
let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!");
jobs.add_job_with_id(
old_id,
Job::Frozen(FrozenJob {
unfreeze: handle,
tag,
}),
)
.expect("job was supposed to be removed");
if state.is_interactive {
println!("\nJob {} is re-frozen", old_id.get());
}
Ok(())
}
Ok(ForegroundWaitStatus::Finished(status)) => check_ok(status, false, span),
Err(err) => Err(ShellError::Io(IoError::new_internal(
err,
"Failed to unfreeze foreground process",
nu_protocol::location!(),
))),
}
}
}
}