use crate::common::report::{merge_reports, report_error, report_simple_failure};
use itertools::Itertools as _;
use yash_env::Env;
use yash_env::job::Pid;
use yash_env::option::State::Off;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::system::{Fcntl, Isatty, Sigaction, Sigmask, Signals, Wait, Write};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum JobSpec {
ProcessId(Pid),
JobId(Field),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Command {
pub jobs: Vec<JobSpec>,
}
pub mod core;
pub mod search;
pub mod status;
pub mod syntax;
impl Command {
async fn await_jobs<S, I>(env: &mut Env<S>, indexes: I) -> Result<ExitStatus, core::Error>
where
S: Signals + Sigmask + Sigaction + Wait + 'static,
I: IntoIterator<Item = Option<usize>>,
{
let job_control = Off;
let mut exit_status = None;
for index in indexes {
exit_status = Some(match index {
None => ExitStatus::NOT_FOUND,
Some(index) => {
status::wait_while_running(env, &mut status::job_status(index, job_control))
.await?
}
});
}
if let Some(exit_status) = exit_status {
return Ok(exit_status);
}
status::wait_while_running(env, &mut status::any_job_is_running(job_control)).await
}
pub async fn execute<S>(self, env: &mut Env<S>) -> crate::Result
where
S: Fcntl + Isatty + Sigaction + Sigmask + Signals + Wait + Write + 'static,
{
let jobs = self.jobs.into_iter();
let (indexes, errors): (Vec<_>, Vec<_>) = jobs
.map(|spec| search::resolve(&env.jobs, spec))
.partition_result();
if let Some(report) = merge_reports(&errors) {
return report_error(env, report).await;
}
match Self::await_jobs(env, indexes).await {
Ok(exit_status) => exit_status.into(),
Err(core::Error::Trapped(signal, divert)) => {
crate::Result::with_exit_status_and_divert(ExitStatus::from(signal), divert)
}
Err(error) => report_simple_failure(env, &error.to_string()).await,
}
}
}
pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> crate::Result
where
S: Fcntl + Isatty + Sigaction + Sigmask + Signals + Wait + Write + 'static,
{
match syntax::parse(env, args) {
Ok(command) => command.execute(env).await,
Err(error) => report_error(env, &error).await,
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::poll;
use std::pin::pin;
use std::task::Poll;
use yash_env::job::{Job, ProcessResult};
use yash_env::option::{Monitor, On};
use yash_env::subshell::{JobControl, Subshell};
use yash_env::system::GetPid as _;
use yash_env::system::SendSignal as _;
use yash_env::system::r#virtual::SIGSTOP;
use yash_env::system::r#virtual::VirtualSystem;
use yash_env::trap::RunSignalTrapIfCaught;
use yash_env_test_helper::{in_virtual_system, stub_tty};
pub(super) fn stub_run_signal_trap_if_caught<S: 'static>(env: &mut Env<S>) {
env.any.insert(Box::new(RunSignalTrapIfCaught::<S>(|_, _| {
Box::pin(std::future::ready(None))
})));
}
async fn suspend(env: &mut Env<VirtualSystem>) {
let target = env.system.getpid();
env.system.kill(target, Some(SIGSTOP)).await.unwrap();
}
async fn start_self_suspending_job(env: &mut Env<VirtualSystem>) {
let subshell =
Subshell::new(|env, _| Box::pin(suspend(env))).job_control(JobControl::Foreground);
let (pid, subshell_result) = subshell.start_and_wait(env).await.unwrap();
assert_eq!(subshell_result, ProcessResult::Stopped(SIGSTOP));
let mut job = Job::new(pid);
job.job_controlled = true;
job.state = subshell_result.into();
env.jobs.add(job);
}
#[test]
fn suspended_job() {
in_virtual_system(|mut env, state| async move {
stub_tty(&state);
stub_run_signal_trap_if_caught(&mut env);
env.options.set(Monitor, On);
start_self_suspending_job(&mut env).await;
let main = pin!(async move { main(&mut env, vec![]).await });
let poll = poll!(main);
assert_eq!(poll, Poll::Pending);
})
}
}