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
//! Wait builtin — wait for background jobs to complete.
//!
//! Accesses the shared job table via [`ShellRef`](super::ShellRef). Jobs are
//! driven by the interpreter alongside the foreground script, so awaiting
//! here lets them run (see `interpreter/jobs.rs`).
use async_trait::async_trait;
use super::{Builtin, Context};
use crate::error::Result;
use crate::interpreter::{BuiltinSideEffect, ExecResult};
/// `wait` builtin — wait for background jobs to complete.
///
/// Usage: `wait [-n] [-p VAR] [ID...]`; an ID is a PID (`$!`) or a jobspec
/// (`%N`, `%%`, `%-`).
///
/// - No ID: wait for every job; status 0.
/// - IDs: wait for each; status of the last; 127 for an unknown ID.
/// - `-n`: wait for the next job (of the IDs, if given) to finish; its
/// status, or 127 when there is none. `-p VAR` stores its PID.
///
/// Output of the jobs waited for that was not reported yet is part of this
/// command's output.
pub struct Wait;
#[async_trait]
impl Builtin for Wait {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
let Some(shell) = ctx.shell.as_ref() else {
// No shell state — no-op (no jobs to wait for)
return Ok(ExecResult::ok(String::new()));
};
let jobs = shell.jobs();
let mut next = false;
let mut pid_var: Option<String> = None;
let mut ids: Vec<&str> = Vec::new();
let mut args = ctx.args.iter();
while let Some(arg) = args.next() {
match arg.as_str() {
"-n" => next = true,
"-f" => {}
"-p" => match args.next() {
Some(v) => pid_var = Some(v.clone()),
None => {
return Ok(ExecResult::err(
"bash: wait: -p: option requires an argument\n",
2,
));
}
},
"--" => ids.extend(args.by_ref().map(String::as_str)),
_ => ids.push(arg),
}
}
let mut stdout = crate::StreamData::new();
let mut stderr = crate::StreamData::new();
let mut exit_code = 0i32;
let mut reaped_pid: Option<u32> = None;
if next {
// Wait for any (listed) job to finish; 127 when none is left.
let wanted: Option<Vec<usize>> = (!ids.is_empty()).then(|| {
let table = jobs.lock();
ids.iter().filter_map(|s| table.resolve(s)).collect()
});
let picked = jobs
.wait_until(|t| {
let candidates: Vec<usize> = match &wanted {
Some(w) => w
.iter()
.copied()
.filter(|id| t.ids().contains(id))
.collect(),
None => t.ids(),
};
if candidates.is_empty() {
return Some(None);
}
let done = t.finished_ids();
candidates
.iter()
.find(|id| done.contains(id))
.map(|id| Some(*id))
})
.await;
match picked {
Some(id) => {
let mut table = jobs.lock();
reaped_pid = u32::try_from(id).ok();
if let Some(r) = table.reap(id) {
stdout.append(&r.stdout);
stderr.append(&r.stderr);
exit_code = r.exit_code;
}
}
None => exit_code = 127,
}
} else if ids.is_empty() {
jobs.wait_until(|t| (t.running_count() == 0).then_some(()))
.await;
let mut table = jobs.lock();
for id in table.ids() {
if let Some(r) = table.reap(id) {
stdout.append(&r.stdout);
stderr.append(&r.stderr);
}
}
} else {
for spec in ids {
let Some(id) = jobs.lock().resolve(spec) else {
let msg = if spec.starts_with('%') {
format!("bash: wait: {spec}: no such job\n")
} else if spec.parse::<u32>().is_ok() {
format!("bash: wait: pid {spec} is not a child of this shell\n")
} else {
format!("bash: wait: `{spec}': not a pid or valid job spec\n")
};
stderr.append(&crate::StreamData::from(msg));
exit_code = 127;
continue;
};
let r = jobs
.wait_until(|t| {
t.reap(id).map(Some).or_else(|| {
// Reaped by someone else meanwhile.
(!t.ids().contains(&id)).then_some(None)
})
})
.await;
match r {
Some(r) => {
stdout.append(&r.stdout);
stderr.append(&r.stderr);
exit_code = r.exit_code;
}
None => exit_code = 127,
}
}
}
let mut result = ExecResult {
stdout,
stderr,
exit_code,
..Default::default()
};
if let Some(var) = pid_var {
result.side_effects.push(BuiltinSideEffect::SetVariable {
name: var,
value: reaped_pid.map(|p| p.to_string()).unwrap_or_default(),
});
}
result
.side_effects
.push(BuiltinSideEffect::SetLastExitCode(exit_code));
Ok(result)
}
}
// Integration tests for wait live in tests/spec_cases/bash/{wait,jobs}.test.sh
// (wait needs the full interpreter to have background jobs).