1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// This file is part of yash, an extended POSIX shell.
// Copyright (C) 2022 WATANABE Yuki
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Wait built-in
//!
//! This module implements the [`wait` built-in], which waits for asynchronous
//! jobs to finish.
//!
//! [`wait` built-in]: https://magicant.github.io/yash-rs/builtins/wait.html
//!
//! # Implementation notes
//!
//! The built-in treats disowned jobs as if they were finished with an exit
//! status of 127.
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;
/// Job specification (job ID or process ID)
///
/// Each operand of the `wait` built-in is parsed into a `JobSpec` value.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum JobSpec {
/// Process ID (non-negative decimal integer)
ProcessId(Pid),
/// Job ID (string of the form `%…`)
JobId(Field),
}
/// Parsed command line arguments to the `wait` built-in
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Command {
/// Operands that specify which jobs to wait for
///
/// If empty, the built-in waits for all existing asynchronous jobs.
pub jobs: Vec<JobSpec>,
}
pub mod core;
pub mod search;
pub mod status;
pub mod syntax;
impl Command {
/// Waits for jobs specified by the indexes.
///
/// If `indexes` is empty, waits for all jobs.
async fn await_jobs<I>(env: &mut Env, indexes: I) -> Result<ExitStatus, core::Error>
where
I: IntoIterator<Item = Option<usize>>,
{
// Currently, we ignore the job control option as required by POSIX.
// TODO: Add some way to specify this option
let job_control = Off; // env.options.get(Monitor);
// Await jobs specified by the indexes
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);
}
// If there were no indexes, await all jobs
status::wait_while_running(env, &mut status::any_job_is_running(job_control)).await
}
/// Executes the `wait` built-in.
pub async fn execute(self, env: &mut Env) -> crate::Result {
// Resolve job specifications to indexes
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;
}
// Await jobs specified by the indexes
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,
}
}
}
/// Entry point for executing the `wait` built-in
pub async fn main(env: &mut Env, args: Vec<Field>) -> crate::Result {
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::System as _;
use yash_env::job::{Job, ProcessResult};
use yash_env::option::{Monitor, On};
use yash_env::subshell::{JobControl, Subshell};
use yash_env::system::r#virtual::SIGSTOP;
use yash_env_test_helper::{in_virtual_system, stub_tty};
async fn suspend(env: &mut Env) {
let target = env.system.getpid();
env.system.kill(target, Some(SIGSTOP)).await.unwrap();
}
async fn start_self_suspending_job(env: &mut Env) {
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() {
// Suspended jobs are not treated as finished, so the built-in waits indefinitely.
in_virtual_system(|mut env, state| async move {
stub_tty(&state);
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);
})
}
}