1use crate::common::report::{merge_reports, report_error, report_simple_failure};
32use itertools::Itertools as _;
33use yash_env::Env;
34use yash_env::job::Pid;
35use yash_env::option::State::Off;
36use yash_env::semantics::ExitStatus;
37use yash_env::semantics::Field;
38use yash_env::system::{Fcntl, Isatty, Sigaction, Sigmask, Signals, Wait, Write};
39
40#[derive(Clone, Debug, Eq, PartialEq)]
44pub enum JobSpec {
45 ProcessId(Pid),
47
48 JobId(Field),
50}
51
52#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct Command {
55 pub jobs: Vec<JobSpec>,
59}
60
61pub mod core;
62pub mod search;
63pub mod status;
64pub mod syntax;
65
66impl Command {
67 async fn await_jobs<S, I>(env: &mut Env<S>, indexes: I) -> Result<ExitStatus, core::Error>
71 where
72 S: Signals + Sigmask + Sigaction + Wait + 'static,
73 I: IntoIterator<Item = Option<usize>>,
74 {
75 let job_control = Off; let mut exit_status = None;
81 for index in indexes {
82 exit_status = Some(match index {
83 None => ExitStatus::NOT_FOUND,
84 Some(index) => {
85 status::wait_while_running(env, &mut status::job_status(index, job_control))
86 .await?
87 }
88 });
89 }
90 if let Some(exit_status) = exit_status {
91 return Ok(exit_status);
92 }
93
94 status::wait_while_running(env, &mut status::any_job_is_running(job_control)).await
96 }
97
98 pub async fn execute<S>(self, env: &mut Env<S>) -> crate::Result
100 where
101 S: Fcntl + Isatty + Sigaction + Sigmask + Signals + Wait + Write + 'static,
102 {
103 let jobs = self.jobs.into_iter();
105 let (indexes, errors): (Vec<_>, Vec<_>) = jobs
106 .map(|spec| search::resolve(&env.jobs, spec))
107 .partition_result();
108 if let Some(report) = merge_reports(&errors) {
109 return report_error(env, report).await;
110 }
111
112 match Self::await_jobs(env, indexes).await {
114 Ok(exit_status) => exit_status.into(),
115 Err(core::Error::Trapped(signal, divert)) => {
116 crate::Result::with_exit_status_and_divert(ExitStatus::from(signal), divert)
117 }
118 Err(error) => report_simple_failure(env, &error.to_string()).await,
119 }
120 }
121}
122
123pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> crate::Result
125where
126 S: Fcntl + Isatty + Sigaction + Sigmask + Signals + Wait + Write + 'static,
127{
128 match syntax::parse(env, args) {
129 Ok(command) => command.execute(env).await,
130 Err(error) => report_error(env, &error).await,
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use futures_util::poll;
138 use std::pin::pin;
139 use std::task::Poll;
140 use yash_env::job::{Job, ProcessResult};
141 use yash_env::option::{Monitor, On};
142 use yash_env::subshell::{JobControl, Subshell};
143 use yash_env::system::GetPid as _;
144 use yash_env::system::SendSignal as _;
145 use yash_env::system::r#virtual::SIGSTOP;
146 use yash_env::system::r#virtual::VirtualSystem;
147 use yash_env::test_helper::{in_virtual_system, stub_tty};
148 use yash_env::trap::RunSignalTrapIfCaught;
149
150 pub(super) fn stub_run_signal_trap_if_caught<S: 'static>(env: &mut Env<S>) {
151 env.any.insert(Box::new(RunSignalTrapIfCaught::<S>(|_, _| {
152 Box::pin(std::future::ready(None))
153 })));
154 }
155
156 async fn suspend(env: &mut Env<VirtualSystem>) {
157 let target = env.system.getpid();
158 env.system.kill(target, Some(SIGSTOP)).await.unwrap();
159 }
160
161 async fn start_self_suspending_job(env: &mut Env<VirtualSystem>) {
162 let subshell =
163 Subshell::new(|env, _| Box::pin(suspend(env))).job_control(JobControl::Foreground);
164 let (pid, subshell_result) = subshell.start_and_wait(env).await.unwrap();
165 assert_eq!(subshell_result, ProcessResult::Stopped(SIGSTOP));
166 let mut job = Job::new(pid);
167 job.job_controlled = true;
168 job.state = subshell_result.into();
169 env.jobs.add(job);
170 }
171
172 #[test]
173 fn suspended_job() {
174 in_virtual_system(|mut env, state| async move {
176 stub_tty(&state);
177 stub_run_signal_trap_if_caught(&mut env);
178 env.options.set(Monitor, On);
179 start_self_suspending_job(&mut env).await;
180
181 let main = pin!(async move { main(&mut env, vec![]).await });
182 let poll = poll!(main);
183 assert_eq!(poll, Poll::Pending);
184 })
185 }
186}