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::Config;
32use yash_env::subshell::JobControl;
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 mut config = Config::new();
75 config.job_control = Some(JobControl::Background);
76 config.ignores_sigint_sigquit = true;
77 let subshell = config.start(env, async move |env_2, job_control| {
78 async_body(env_2, job_control, &and_or_2).await
79 });
80 match subshell.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.insert(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).await.ok();
124 }
125 let result = and_or.execute(env).await;
126 env.apply_result(result);
127
128 run_exit_trap(env).await;
129}
130
131async fn 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 .await?;
141 assert_eq!(fd, Fd::STDIN);
142 Ok(())
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::tests::cat_builtin;
149 use crate::tests::echo_builtin;
150 use crate::tests::return_builtin;
151 use futures_util::FutureExt as _;
152 use futures_util::task::LocalSpawnExt as _;
153 use std::cell::RefCell;
154 use std::rc::Rc;
155 use yash_env::VirtualSystem;
156 use yash_env::job::ProcessState;
157 use yash_env::option::Option::{Interactive, Monitor};
158 use yash_env::option::State::On;
159 use yash_env::system::Concurrent;
160 use yash_env::system::Signals as _;
161 use yash_env::system::r#virtual::FileBody;
162 use yash_env::system::r#virtual::Inode;
163 use yash_env::system::r#virtual::SystemState;
164 use yash_env::test_helper::assert_stderr;
165 use yash_env::test_helper::assert_stdout;
166 use yash_env::test_helper::in_virtual_system;
167 use yash_env::test_helper::stub_tty;
168
169 #[test]
170 fn item_execute_sync() {
171 let mut env = Env::new_virtual();
172 env.builtins.insert("return", return_builtin());
173 let and_or: syntax::AndOrList = "return -n 42".parse().unwrap();
174 let item = syntax::Item {
175 and_or: Rc::new(and_or),
176 async_flag: None,
177 };
178 let result = item.execute(&mut env).now_or_never().unwrap();
179 assert_eq!(result, Continue(()));
180 assert_eq!(env.exit_status, ExitStatus(42));
181 }
182
183 #[test]
184 fn item_execute_async_exit_status() {
185 in_virtual_system(|mut env, _state| async move {
186 env.builtins.insert("return", return_builtin());
187 env.exit_status = ExitStatus::FAILURE;
188
189 let item = syntax::Item {
190 and_or: Rc::new("return -n 42".parse().unwrap()),
191 async_flag: Some(Location::dummy("")),
192 };
193 let result = item.execute(&mut env).await;
194 assert_eq!(result, Continue(()));
195 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
196 })
197 }
198
199 #[test]
200 fn item_execute_async_effect() {
201 let system = VirtualSystem::new();
202 let state = Rc::clone(&system.state);
203 let mut executor = futures_executor::LocalPool::new();
204 state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
205 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
206 env.builtins.insert("echo", echo_builtin());
207
208 let and_or: syntax::AndOrList = "echo foo".parse().unwrap();
209 let item = syntax::Item {
210 and_or: Rc::new(and_or),
211 async_flag: Some(Location::dummy("")),
212 };
213
214 executor
215 .spawner()
216 .spawn_local(async move {
217 let result = item.execute(&mut env).await;
218 assert_eq!(result, Continue(()));
219 })
220 .unwrap();
221 executor.run_until_stalled();
222
223 assert_stdout(&state, |stdout| assert_eq!(stdout, "foo\n"));
224 }
225
226 #[test]
227 fn item_execute_async_job() {
228 in_virtual_system(|mut env, _state| async move {
229 env.builtins.insert("return", return_builtin());
230
231 let item = syntax::Item {
232 and_or: Rc::new("return -n 42".parse().unwrap()),
233 async_flag: Some(Location::dummy("")),
234 };
235 _ = item.execute(&mut env).await;
236
237 let job = &env.jobs[0];
238 assert!(!job.job_controlled);
239 assert!(!job.state_changed);
240 assert_eq!(job.state, ProcessState::Running);
241 assert_eq!(job.pid, env.jobs.last_async_pid());
242 assert_eq!(job.name, "return -n 42");
243 })
244 }
245
246 #[test]
247 fn item_execute_async_pid() {
248 in_virtual_system(|mut env, state| async move {
249 env.builtins.insert("return", return_builtin());
250
251 let item = syntax::Item {
252 and_or: Rc::new("return -n 42".parse().unwrap()),
253 async_flag: Some(Location::dummy("")),
254 };
255 _ = item.execute(&mut env).await;
256
257 let pids = state.borrow().processes.keys().copied().collect::<Vec<_>>();
258 assert_eq!(pids, [env.main_pid, env.jobs.last_async_pid()]);
259 })
260 }
261
262 #[test]
263 fn item_execute_async_no_report_if_non_interactive() {
264 in_virtual_system(|mut env, state| async move {
265 env.builtins.insert("return", return_builtin());
266
267 let item = syntax::Item {
268 and_or: Rc::new("return -n 42".parse().unwrap()),
269 async_flag: Some(Location::dummy("")),
270 };
271 _ = item.execute(&mut env).await;
272
273 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
274 })
275 }
276
277 #[test]
278 fn item_execute_async_report_if_interactive() {
279 in_virtual_system(|mut env, state| async move {
280 env.builtins.insert("return", return_builtin());
281 env.options.set(Interactive, On);
282
283 let item = syntax::Item {
284 and_or: Rc::new("return -n 42".parse().unwrap()),
285 async_flag: Some(Location::dummy("")),
286 };
287 _ = item.execute(&mut env).await;
288
289 let expected_report = format!("[1] {}\n", env.jobs.last_async_pid());
290 assert_stderr(&state, |stderr| assert_eq!(stderr, expected_report));
291 })
292 }
293
294 #[test]
295 fn item_execute_async_fail() {
296 let system = VirtualSystem::new();
297 let state = Rc::clone(&system.state);
298 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
299 env.builtins.insert("return", return_builtin());
300
301 let and_or: syntax::AndOrList = "return -n 42".parse().unwrap();
302 let item = syntax::Item {
303 and_or: Rc::new(and_or),
304 async_flag: Some(Location::dummy("X")),
305 };
306 let result = item.execute(&mut env).now_or_never().unwrap();
307 assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::NOEXEC))));
308 assert_stderr(&state, |stderr| {
309 assert!(
310 stderr.contains("asynchronous"),
311 "unexpected error message: {stderr:?}"
312 )
313 });
314 }
315
316 #[test]
317 fn item_execute_async_background() {
318 in_virtual_system(|mut env, state| async move {
319 env.builtins.insert("return", return_builtin());
320 env.options.set(Monitor, On);
321 stub_tty(&state);
322
323 let item = syntax::Item {
324 and_or: Rc::new("return -n 42".parse().unwrap()),
325 async_flag: Some(Location::dummy("")),
326 };
327
328 _ = item.execute(&mut env).await;
329
330 let state = state.borrow();
331 let process = &state.processes[&env.jobs.last_async_pid()];
332 assert_ne!(process.pgid(), env.main_pgid);
333 assert!(env.jobs[0].job_controlled);
334 })
335 }
336
337 fn ignore_sigttin(env: &mut Env<Rc<Concurrent<VirtualSystem>>>) {
338 env.traps
339 .set_action(
340 &env.system,
341 VirtualSystem::SIGTTIN,
342 yash_env::trap::Action::Ignore,
343 Location::dummy(""),
344 false,
345 )
346 .now_or_never()
347 .unwrap()
348 .unwrap();
349 }
350
351 fn stub_dev_null_and_stdin(state: &RefCell<SystemState>) {
352 let mut state = state.borrow_mut();
353 state
354 .file_system
355 .save("/dev/null", Rc::new(RefCell::new(Inode::new([]))))
356 .unwrap();
357 state
358 .file_system
359 .get("/dev/stdin")
360 .unwrap()
361 .borrow_mut()
362 .body = FileBody::new(*b"input\n");
363 }
364
365 #[test]
366 fn item_execute_async_stdin_not_job_controlled() {
367 in_virtual_system(|mut env, state| async move {
368 env.builtins.insert("cat", cat_builtin());
369 ignore_sigttin(&mut env);
370 stub_tty(&state);
371 stub_dev_null_and_stdin(&state);
372
373 let item = syntax::Item {
374 and_or: Rc::new("cat".parse().unwrap()),
375 async_flag: Some(Location::dummy("")),
376 };
377
378 _ = item.execute(&mut env).await;
379 env.wait_for_subshell(env.jobs.last_async_pid())
380 .await
381 .unwrap();
382 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
383 })
384 }
385
386 #[test]
387 fn item_execute_async_stdin_job_controlled() {
388 in_virtual_system(|mut env, state| async move {
389 env.builtins.insert("cat", cat_builtin());
390 env.options.set(Monitor, On);
391 ignore_sigttin(&mut env);
392 stub_tty(&state);
393 stub_dev_null_and_stdin(&state);
394
395 let item = syntax::Item {
396 and_or: Rc::new("cat".parse().unwrap()),
397 async_flag: Some(Location::dummy("")),
398 };
399
400 _ = item.execute(&mut env).await;
401 env.wait_for_subshell(env.jobs.last_async_pid())
402 .await
403 .unwrap();
404 assert_stdout(&state, |stdout| assert_eq!(stdout, "input\n"));
405 })
406 }
407}