use thiserror::Error;
use yash_env::Env;
use yash_env::job::Pid;
use yash_env::signal;
use yash_env::system::{Errno, Sigaction, Sigmask, Signals, Wait};
use yash_env::trap::RunSignalTrapIfCaught;
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum Error {
#[error("no job to wait for")]
NothingToWait,
#[error("trapped signal {0}")]
Trapped(signal::Number, yash_env::semantics::Result),
#[error("system error: {0}")]
SystemError(#[from] Errno),
}
pub async fn wait_for_any_job_or_trap<S>(env: &mut Env<S>) -> Result<(), Error>
where
S: Signals + Sigmask + Sigaction + Wait + 'static,
{
let RunSignalTrapIfCaught(run_trap_if_caught) = *env
.any
.get()
.expect("`RunSignalTrapIfCaught` should be in `env.any`");
env.traps
.enable_internal_disposition_for_sigchld(&env.system)
.await?;
loop {
match env.system.wait(Pid::ALL) {
Ok(None) => {
let signals = env.wait_for_signals().await;
for signal in signals.iter().cloned() {
if let Some(result) = run_trap_if_caught(env, signal).await {
return Err(Error::Trapped(signal, result));
}
}
}
Ok(Some((pid, state))) => {
env.jobs.update_status(pid, state);
return Ok(());
}
Err(Errno::ECHILD) => return Err(Error::NothingToWait),
Err(errno) => return Err(Error::SystemError(errno)),
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::stub_run_signal_trap_if_caught;
use super::*;
use futures_util::FutureExt as _;
use futures_util::poll;
use std::future::{pending, ready};
use std::ops::ControlFlow::Continue;
use std::pin::pin;
use std::task::Poll;
use yash_env::VirtualSystem;
use yash_env::job::Job;
use yash_env::job::ProcessState;
use yash_env::semantics::ExitStatus;
use yash_env::source::Location;
use yash_env::subshell::Subshell;
use yash_env::system::SendSignal as _;
use yash_env::system::r#virtual::{SIGSTOP, SIGTERM};
use yash_env::test_helper::in_virtual_system;
use yash_env::trap::Action;
use yash_env::variable::Value;
#[test]
fn running_job() {
in_virtual_system(|mut env, _| async move {
stub_run_signal_trap_if_caught(&mut env);
let subshell = Subshell::new(|_, _| Box::pin(pending()));
subshell.start(&mut env).await.unwrap();
let future = pin!(wait_for_any_job_or_trap(&mut env));
assert_eq!(poll!(future), Poll::Pending);
});
}
#[test]
fn finished_job() {
in_virtual_system(|mut env, _| async move {
stub_run_signal_trap_if_caught(&mut env);
let subshell = Subshell::new(|_, _| Box::pin(ready(())));
let pid = subshell.start(&mut env).await.unwrap().0;
let index = env.jobs.add(Job::new(pid));
let result = wait_for_any_job_or_trap(&mut env).await;
assert_eq!(result, Ok(()));
assert_eq!(
env.jobs[index].state,
ProcessState::exited(ExitStatus::default()),
);
});
}
#[test]
fn suspended_job() {
in_virtual_system(|mut env, _| async move {
stub_run_signal_trap_if_caught(&mut env);
let subshell = Subshell::new(|_, _| Box::pin(pending()));
let pid = subshell.start(&mut env).await.unwrap().0;
let index = env.jobs.add(Job::new(pid));
env.system.kill(pid, Some(SIGSTOP)).await.unwrap();
let result = wait_for_any_job_or_trap(&mut env).await;
assert_eq!(result, Ok(()));
assert_eq!(env.jobs[index].state, ProcessState::stopped(SIGSTOP));
});
}
#[test]
fn trap() {
in_virtual_system(|mut env, state| async move {
env.any
.insert(Box::new(RunSignalTrapIfCaught::<VirtualSystem>(
|env, signal| {
Box::pin(async move {
yash_semantics::trap::run_trap_if_caught(env, signal).await
})
},
)));
let system = VirtualSystem {
state,
process_id: env.main_pid,
};
let subshell = Subshell::new(|_, _| Box::pin(pending()));
subshell.start(&mut env).await.unwrap();
env.traps
.set_action(
&env.system,
SIGTERM,
Action::Command("foo=bar".into()),
Location::dummy("somewhere"),
false,
)
.await
.unwrap();
{
let mut future = pin!(wait_for_any_job_or_trap(&mut env));
assert_eq!(poll!(&mut future), Poll::Pending);
_ = system.current_process_mut().raise_signal(SIGTERM);
let result = future.await;
assert_eq!(result, Err(Error::Trapped(SIGTERM, Continue(()))));
}
assert_eq!(
env.variables.get("foo").unwrap().value,
Some(Value::scalar("bar")),
);
});
}
#[test]
fn no_child_processes() {
let mut env = Env::new_virtual();
stub_run_signal_trap_if_caught(&mut env);
let result = wait_for_any_job_or_trap(&mut env).now_or_never().unwrap();
assert_eq!(result, Err(Error::NothingToWait));
}
}