1use crate::trap::run_exit_trap;
20
21use super::Command;
22use std::ops::ControlFlow::{Break, Continue};
23use std::rc::Rc;
24use yash_env::Env;
25use yash_env::System;
26use yash_env::io::Fd;
27use yash_env::io::print_error;
28use yash_env::job::Job;
29use yash_env::semantics::Divert;
30use yash_env::semantics::ExitStatus;
31use yash_env::semantics::Result;
32use yash_env::subshell::JobControl;
33use yash_env::subshell::Subshell;
34use yash_env::system::Mode;
35use yash_env::system::OfdAccess;
36use yash_syntax::source::Location;
37use yash_syntax::syntax;
38use yash_syntax::syntax::AndOrList;
39
40impl Command for syntax::Item {
62 async fn execute(&self, env: &mut Env) -> Result {
63 match &self.async_flag {
64 None => self.and_or.execute(env).await,
65 Some(async_flag) => execute_async(env, &self.and_or, async_flag).await,
66 }
67 }
68}
69
70async fn execute_async(env: &mut Env, and_or: &Rc<AndOrList>, async_flag: &Location) -> Result {
71 let and_or_2 = Rc::clone(and_or);
72 let subshell = Subshell::new(|env_2, job_control| {
73 Box::pin(async move { async_body(env_2, job_control, &and_or_2).await })
74 });
75 let subshell = subshell
76 .job_control(JobControl::Background)
77 .ignore_sigint_sigquit(true);
78 match subshell.start(env).await {
79 Ok((pid, job_control)) => {
80 let mut job = Job::new(pid);
82 job.state_changed = false;
83 job.name = and_or.to_string();
84 if let Some(job_control) = job_control {
85 debug_assert_eq!(job_control, JobControl::Background);
86 job.job_controlled = true;
87 }
88 let job_index = env.jobs.add(job);
89 env.jobs.set_last_async_pid(pid);
90
91 if env.is_interactive() {
92 let job_number = job_index + 1;
94 let report = format!("[{job_number}] {pid}\n");
95 env.system.print_error(&report).await;
96 }
97
98 env.exit_status = ExitStatus::SUCCESS;
99 Continue(())
100 }
101 Err(errno) => {
102 print_error(
103 env,
104 "cannot start a subshell to run an asynchronous command".into(),
105 errno.to_string().into(),
106 async_flag,
107 )
108 .await;
109
110 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
111 }
112 }
113}
114
115async fn async_body(env: &mut Env, job_control: Option<JobControl>, and_or: &AndOrList) {
116 if job_control.is_none() {
117 nullify_stdin(env).ok();
118 }
119 let result = and_or.execute(env).await;
120 env.apply_result(result);
121
122 run_exit_trap(env).await;
123}
124
125fn nullify_stdin(env: &mut Env) -> std::result::Result<(), yash_env::system::Errno> {
126 env.system.close(Fd::STDIN)?;
127
128 let path = c"/dev/null";
129 let fd = env
130 .system
131 .open(path, OfdAccess::ReadOnly, Default::default(), Mode::empty())?;
132 assert_eq!(fd, Fd::STDIN);
133 Ok(())
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::tests::cat_builtin;
140 use crate::tests::echo_builtin;
141 use crate::tests::return_builtin;
142 use futures_util::FutureExt;
143 use futures_util::task::LocalSpawnExt;
144 use std::cell::RefCell;
145 use std::rc::Rc;
146 use yash_env::VirtualSystem;
147 use yash_env::job::ProcessState;
148 use yash_env::option::Option::{Interactive, Monitor};
149 use yash_env::option::State::On;
150 use yash_env::system::r#virtual::FileBody;
151 use yash_env::system::r#virtual::Inode;
152 use yash_env::system::r#virtual::SystemState;
153 use yash_env_test_helper::LocalExecutor;
154 use yash_env_test_helper::assert_stderr;
155 use yash_env_test_helper::assert_stdout;
156 use yash_env_test_helper::in_virtual_system;
157 use yash_env_test_helper::stub_tty;
158
159 #[test]
160 fn item_execute_sync() {
161 let mut env = Env::new_virtual();
162 env.builtins.insert("return", return_builtin());
163 let and_or: syntax::AndOrList = "return -n 42".parse().unwrap();
164 let item = syntax::Item {
165 and_or: Rc::new(and_or),
166 async_flag: None,
167 };
168 let result = item.execute(&mut env).now_or_never().unwrap();
169 assert_eq!(result, Continue(()));
170 assert_eq!(env.exit_status, ExitStatus(42));
171 }
172
173 #[test]
174 fn item_execute_async_exit_status() {
175 in_virtual_system(|mut env, _state| async move {
176 env.builtins.insert("return", return_builtin());
177 env.exit_status = ExitStatus::FAILURE;
178
179 let item = syntax::Item {
180 and_or: Rc::new("return -n 42".parse().unwrap()),
181 async_flag: Some(Location::dummy("")),
182 };
183 let result = item.execute(&mut env).await;
184 assert_eq!(result, Continue(()));
185 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
186 })
187 }
188
189 #[test]
190 fn item_execute_async_effect() {
191 let system = VirtualSystem::new();
192 let state = Rc::clone(&system.state);
193 let mut executor = futures_executor::LocalPool::new();
194 state.borrow_mut().executor = Some(Rc::new(LocalExecutor(executor.spawner())));
195 let mut env = Env::with_system(Box::new(system));
196 env.builtins.insert("echo", echo_builtin());
197
198 let and_or: syntax::AndOrList = "echo foo".parse().unwrap();
199 let item = syntax::Item {
200 and_or: Rc::new(and_or),
201 async_flag: Some(Location::dummy("")),
202 };
203
204 executor
205 .spawner()
206 .spawn_local(async move {
207 let result = item.execute(&mut env).await;
208 assert_eq!(result, Continue(()));
209 })
210 .unwrap();
211 executor.run_until_stalled();
212
213 assert_stdout(&state, |stdout| assert_eq!(stdout, "foo\n"));
214 }
215
216 #[test]
217 fn item_execute_async_job() {
218 in_virtual_system(|mut env, _state| async move {
219 env.builtins.insert("return", return_builtin());
220
221 let item = syntax::Item {
222 and_or: Rc::new("return -n 42".parse().unwrap()),
223 async_flag: Some(Location::dummy("")),
224 };
225 _ = item.execute(&mut env).await;
226
227 let job = &env.jobs[0];
228 assert!(!job.job_controlled);
229 assert!(!job.state_changed);
230 assert_eq!(job.state, ProcessState::Running);
231 assert_eq!(job.pid, env.jobs.last_async_pid());
232 assert_eq!(job.name, "return -n 42");
233 })
234 }
235
236 #[test]
237 fn item_execute_async_pid() {
238 in_virtual_system(|mut env, state| async move {
239 env.builtins.insert("return", return_builtin());
240
241 let item = syntax::Item {
242 and_or: Rc::new("return -n 42".parse().unwrap()),
243 async_flag: Some(Location::dummy("")),
244 };
245 _ = item.execute(&mut env).await;
246
247 let pids = state.borrow().processes.keys().copied().collect::<Vec<_>>();
248 assert_eq!(pids, [env.main_pid, env.jobs.last_async_pid()]);
249 })
250 }
251
252 #[test]
253 fn item_execute_async_no_report_if_non_interactive() {
254 in_virtual_system(|mut env, state| async move {
255 env.builtins.insert("return", return_builtin());
256
257 let item = syntax::Item {
258 and_or: Rc::new("return -n 42".parse().unwrap()),
259 async_flag: Some(Location::dummy("")),
260 };
261 _ = item.execute(&mut env).await;
262
263 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
264 })
265 }
266
267 #[test]
268 fn item_execute_async_report_if_interactive() {
269 in_virtual_system(|mut env, state| async move {
270 env.builtins.insert("return", return_builtin());
271 env.options.set(Interactive, On);
272
273 let item = syntax::Item {
274 and_or: Rc::new("return -n 42".parse().unwrap()),
275 async_flag: Some(Location::dummy("")),
276 };
277 _ = item.execute(&mut env).await;
278
279 let expected_report = format!("[1] {}\n", env.jobs.last_async_pid());
280 assert_stderr(&state, |stderr| assert_eq!(stderr, expected_report));
281 })
282 }
283
284 #[test]
285 fn item_execute_async_fail() {
286 let system = VirtualSystem::new();
287 let state = Rc::clone(&system.state);
288 let mut env = Env::with_system(Box::new(system));
289 env.builtins.insert("return", return_builtin());
290
291 let and_or: syntax::AndOrList = "return -n 42".parse().unwrap();
292 let item = syntax::Item {
293 and_or: Rc::new(and_or),
294 async_flag: Some(Location::dummy("X")),
295 };
296 let result = item.execute(&mut env).now_or_never().unwrap();
297 assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::NOEXEC))));
298 assert_stderr(&state, |stderr| {
299 assert!(
300 stderr.contains("asynchronous"),
301 "unexpected error message: {stderr:?}"
302 )
303 });
304 }
305
306 #[test]
307 fn item_execute_async_background() {
308 in_virtual_system(|mut env, state| async move {
309 env.builtins.insert("return", return_builtin());
310 env.options.set(Monitor, On);
311 stub_tty(&state);
312
313 let item = syntax::Item {
314 and_or: Rc::new("return -n 42".parse().unwrap()),
315 async_flag: Some(Location::dummy("")),
316 };
317
318 _ = item.execute(&mut env).await;
319
320 let state = state.borrow();
321 let process = &state.processes[&env.jobs.last_async_pid()];
322 assert_ne!(process.pgid(), env.main_pgid);
323 assert!(env.jobs[0].job_controlled);
324 })
325 }
326
327 fn ignore_sigttin(env: &mut Env) {
328 let signal = env
329 .system
330 .signal_number_from_name(yash_env::signal::Name::Ttin)
331 .unwrap();
332 env.traps
333 .set_action(
334 &mut env.system,
335 signal,
336 yash_env::trap::Action::Ignore,
337 Location::dummy(""),
338 false,
339 )
340 .unwrap();
341 }
342
343 fn stub_dev_null_and_stdin(state: &RefCell<SystemState>) {
344 let mut state = state.borrow_mut();
345 state
346 .file_system
347 .save("/dev/null", Rc::new(RefCell::new(Inode::new([]))))
348 .unwrap();
349 state
350 .file_system
351 .get("/dev/stdin")
352 .unwrap()
353 .borrow_mut()
354 .body = FileBody::new(*b"input\n");
355 }
356
357 #[test]
358 fn item_execute_async_stdin_not_job_controlled() {
359 in_virtual_system(|mut env, state| async move {
360 env.builtins.insert("cat", cat_builtin());
361 ignore_sigttin(&mut env);
362 stub_tty(&state);
363 stub_dev_null_and_stdin(&state);
364
365 let item = syntax::Item {
366 and_or: Rc::new("cat".parse().unwrap()),
367 async_flag: Some(Location::dummy("")),
368 };
369
370 _ = item.execute(&mut env).await;
371 env.wait_for_subshell(env.jobs.last_async_pid())
372 .await
373 .unwrap();
374 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
375 })
376 }
377
378 #[test]
379 fn item_execute_async_stdin_job_controlled() {
380 in_virtual_system(|mut env, state| async move {
381 env.builtins.insert("cat", cat_builtin());
382 env.options.set(Monitor, On);
383 ignore_sigttin(&mut env);
384 stub_tty(&state);
385 stub_dev_null_and_stdin(&state);
386
387 let item = syntax::Item {
388 and_or: Rc::new("cat".parse().unwrap()),
389 async_flag: Some(Location::dummy("")),
390 };
391
392 _ = item.execute(&mut env).await;
393 env.wait_for_subshell(env.jobs.last_async_pid())
394 .await
395 .unwrap();
396 assert_stdout(&state, |stdout| assert_eq!(stdout, "input\n"));
397 })
398 }
399}