use std::io;
use super::*;
#[derive(Clone)]
struct BackgroundFinishEvent {
command_id: String,
job_id: u32,
display_pid: Option<u32>,
status: i32,
}
fn take_background_finish_event(job: &mut ShellJob) -> Option<BackgroundFinishEvent> {
let JobState::Done(status) = job.state else {
return None;
};
if job.finish_emitted {
return None;
}
job.finish_emitted = true;
Some(BackgroundFinishEvent {
command_id: job.command_id.clone(),
job_id: job.job_id,
display_pid: job.display_pid,
status,
})
}
fn emit_background_finish_events(state: &ShellState, events: &[BackgroundFinishEvent]) {
for event in events {
shell_events::emit_background_finish(
state,
&event.command_id,
event.job_id,
event.display_pid,
event.status,
);
}
}
pub(super) fn process_event_to_job_state(event: sys::ProcessEvent) -> JobState {
match event {
sys::ProcessEvent::Running | sys::ProcessEvent::Continued => JobState::Running,
sys::ProcessEvent::Stopped(sig) => JobState::Stopped(sig),
sys::ProcessEvent::Exited(code) => JobState::Done(code),
sys::ProcessEvent::Signaled(sig) => JobState::Done(128 + sig),
}
}
pub(super) fn refresh_jobs<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
let mut finished = Vec::new();
for job in &mut state.jobs {
if !matches!(job.state, JobState::Running) {
continue;
}
let Ok(event) = runtime.wait_process(job.handle, sys::WaitMode::Poll) else {
continue;
};
if !matches!(event, sys::ProcessEvent::Running) {
job.state = process_event_to_job_state(event);
if let Some(event) = take_background_finish_event(job) {
finished.push(event);
}
}
}
emit_background_finish_events(state, &finished);
}
pub(super) fn job_list<R: Runtime>(state: &mut ShellState, runtime: &mut R) -> Vec<JobInfo> {
refresh_jobs(state, runtime);
state
.jobs
.iter()
.map(|job| JobInfo {
job_id: job.job_id,
display_pid: job.display_pid,
state: job.state.clone(),
})
.collect()
}
pub(crate) fn maybe_notify_jobs<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
let mut jobs = job_list(state, runtime);
let live: HashSet<u32> = jobs.iter().map(|job| job.job_id).collect();
state.notified_jobs.retain(|job_id| live.contains(job_id));
jobs.sort_by_key(|job| job.job_id);
for job in jobs {
if state.notified_jobs.contains(&job.job_id) {
continue;
}
let status = match job.state {
JobState::Done(code) => format!("Done({code})"),
JobState::Stopped(sig) => format!("Stopped(SIG{sig})"),
JobState::Running => continue,
};
let pid = job
.display_pid
.map(|pid| pid.to_string())
.unwrap_or_else(|| "?".to_string());
shell_errln(state, &format!("[{}] {} {}", job.job_id, status, pid));
state.notified_jobs.insert(job.job_id);
}
}
fn wait_on_job_index<R: Runtime>(state: &mut ShellState, runtime: &mut R, idx: usize) -> i32 {
if let Some(event) = {
let job = &mut state.jobs[idx];
take_background_finish_event(job)
} {
shell_events::emit_background_finish(
state,
&event.command_id,
event.job_id,
event.display_pid,
event.status,
);
}
if let JobState::Done(status) = state.jobs[idx].state {
state.jobs.remove(idx);
return status;
}
let handle = state.jobs[idx].handle;
loop {
let event = match runtime.wait_process(handle, sys::WaitMode::Block) {
Ok(event) => event,
Err(_) => {
state.jobs.remove(idx);
return 128;
}
};
match process_event_to_job_state(event) {
JobState::Running => continue,
JobState::Stopped(sig) => {
state.jobs[idx].state = JobState::Stopped(sig);
return 128 + sig;
}
JobState::Done(code) => {
let event = {
state.jobs[idx].state = JobState::Done(code);
let job = &mut state.jobs[idx];
take_background_finish_event(job)
};
if let Some(event) = event {
shell_events::emit_background_finish(
state,
&event.command_id,
event.job_id,
event.display_pid,
event.status,
);
}
state.jobs.remove(idx);
return code;
}
}
}
}
pub(super) fn wait_job<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
job_id: u32,
) -> Option<i32> {
refresh_jobs(state, runtime);
let idx = state.jobs.iter().position(|job| job.job_id == job_id)?;
Some(wait_on_job_index(state, runtime, idx))
}
pub(super) fn wait_pid<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
pid: u32,
) -> Result<Option<i32>, io::Error> {
refresh_jobs(state, runtime);
if let Some(idx) = state
.jobs
.iter()
.position(|job| job.display_pid == Some(pid))
{
return Ok(Some(wait_on_job_index(state, runtime, idx)));
}
match runtime.wait_display_pid(pid) {
Ok(Some(status)) => Ok(Some(status)),
Ok(None) => Ok(None),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
pub(super) fn wait_all_jobs<R: Runtime>(state: &mut ShellState, runtime: &mut R) -> i32 {
refresh_jobs(state, runtime);
let mut last_status = 0;
for i in (0..state.jobs.len()).rev() {
last_status = wait_on_job_index(state, runtime, i);
}
last_status
}
pub(super) fn continue_job_background<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
job_id: u32,
) -> bool {
refresh_jobs(state, runtime);
let Some(job) = state.jobs.iter_mut().find(|job| job.job_id == job_id) else {
return false;
};
if runtime
.signal_process_group(job.handle, sys::RuntimeSignal::Continue)
.is_ok()
{
job.state = JobState::Running;
true
} else {
false
}
}
pub(super) fn continue_job_foreground<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
job_id: u32,
) -> Option<i32> {
refresh_jobs(state, runtime);
let handle = state
.jobs
.iter()
.find(|job| job.job_id == job_id)
.map(|job| job.handle)?;
if state.has_option(OPT_MONITOR) && state.stdin_fd.is_valid() {
let guard = runtime.claim_foreground(handle, state.stdin_fd).ok()?;
let _ = runtime.signal_process_group(handle, sys::RuntimeSignal::Continue);
let status = wait_job(state, runtime, job_id);
let _ = runtime.release_foreground(guard);
return status;
}
let _ = runtime.signal_process_group(handle, sys::RuntimeSignal::Continue);
wait_job(state, runtime, job_id)
}
#[allow(dead_code)]
pub(super) fn pause_job<R: Runtime>(state: &mut ShellState, runtime: &mut R, job_id: u32) -> bool {
refresh_jobs(state, runtime);
let Some(job) = state.jobs.iter_mut().find(|job| job.job_id == job_id) else {
return false;
};
if runtime
.signal_process_group(job.handle, sys::RuntimeSignal::Stop)
.is_ok()
{
job.state = JobState::Stopped(libc::SIGSTOP);
true
} else {
false
}
}
pub(super) fn run_background<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
aol: &AndOrList,
) -> Result<Option<u32>, i32> {
let command_id = shell_events::new_command_id();
let canonical_command = shell_events::canonical_command_list_text(aol, true);
let spawned =
super::machine::spawn_background_job(state, runtime, aol, &command_id).map_err(|err| {
let status = if err.starts_with("failed to spawn background job:") {
126
} else {
2
};
shell_errln(state, &state.prefixed_message(err));
status
})?;
let job_id = state.next_job_id;
state.next_job_id += 1;
state.jobs.push(ShellJob {
job_id,
handle: spawned.handle,
display_pid: spawned.display_pid,
state: JobState::Running,
command_id: command_id.clone(),
finish_emitted: false,
});
shell_events::emit_background_start(
state,
&command_id,
Some(&canonical_command),
&canonical_command,
job_id,
spawned.display_pid,
);
Ok(spawned.display_pid)
}