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