1use super::BlockSignals;
20use super::JobControl;
21use crate::Env;
22use crate::job::{Pid, ProcessResult, RunBlocking, RunUnblocking, tcsetpgrp_with_block};
23use crate::semantics::exit_or_raise;
24use crate::stack::Frame;
25use crate::system::concurrency::WaitForSignals;
26use crate::system::resource::SetRlimit;
27use crate::system::{
28 Close, Dup, Errno, Exit, Fork, GetPid, Open, SendSignal, SetPgid, TcSetPgrp, Wait,
29};
30use crate::trap::SignalSystem;
31
32#[derive(Debug, Default, Clone)]
39#[non_exhaustive]
40pub struct Config {
41 pub job_control: Option<JobControl>,
61
62 pub ignores_sigint_sigquit: bool,
74}
75
76impl Config {
77 #[must_use]
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 #[must_use]
91 pub fn foreground() -> Self {
92 Self {
93 job_control: Some(JobControl::Foreground),
94 ..Self::default()
95 }
96 }
97
98 pub async fn start<S, F>(
122 self,
130 env: &mut Env<S>,
131 task: F,
132 ) -> Result<(Pid, Option<JobControl>), Errno>
133 where
134 S: BlockSignals
135 + Close
136 + Dup
137 + Exit
138 + Fork
139 + GetPid
140 + Open
141 + RunBlocking
142 + RunUnblocking
143 + SendSignal
144 + SetPgid
145 + SetRlimit
146 + SignalSystem
147 + TcSetPgrp
148 + 'static,
149 F: AsyncFnOnce(&mut Env<S>, Option<JobControl>) + 'static,
150 {
151 let job_control = env.controls_jobs().then_some(self.job_control).flatten();
153 let tty = match job_control {
154 None | Some(JobControl::Background) => None,
155 Some(JobControl::Foreground) => env.get_tty().await.ok(),
157 };
158
159 let ignore_sigint_sigquit = self.ignores_sigint_sigquit && job_control.is_none();
160 let original_mask = if ignore_sigint_sigquit {
161 Some(env.system.block_sigint_sigquit().await?)
165 } else {
166 None
167 };
168 let keep_internal_dispositions_for_stoppers = job_control.is_none();
169
170 const ME: Pid = Pid(0);
172 let child_task = move |mut child_env: Env<S>, ()| async move {
173 let env = &mut *child_env.push_frame(Frame::Subshell);
174
175 if let Some(job_control) = job_control {
176 if let Ok(()) = env.system.setpgid(ME, ME) {
177 match job_control {
178 JobControl::Background => (),
179 JobControl::Foreground => {
180 if let Some(tty) = tty {
181 let pgid = env.system.getpgrp();
182 tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
183 }
184 }
185 }
186 }
187 }
188 env.jobs.disown_all();
189
190 env.traps
191 .enter_subshell(
192 &env.system,
193 ignore_sigint_sigquit,
194 keep_internal_dispositions_for_stoppers,
195 )
196 .await;
197
198 task(env, job_control).await;
199 exit_or_raise(&env.system, env.exit_status).await
200 };
201
202 let (result, ()) = env.run_in_child_process((), child_task);
204
205 if let Some(mask) = original_mask {
209 env.system.restore_sigmask(mask).await.ok();
210 }
211
212 let child_pid = result?;
213
214 if job_control.is_some() {
216 let _ = env.system.setpgid(child_pid, ME);
220
221 }
224
225 Ok((child_pid, job_control))
226 }
227
228 pub async fn start_and_wait<S, F>(
248 self,
250 env: &mut Env<S>,
251 task: F,
252 ) -> Result<(Pid, ProcessResult), Errno>
253 where
254 S: BlockSignals
255 + Close
256 + Dup
257 + Exit
258 + Fork
259 + GetPid
260 + Open
261 + RunBlocking
262 + RunUnblocking
263 + SendSignal
264 + SetPgid
265 + SetRlimit
266 + SignalSystem
267 + TcSetPgrp
268 + Wait
269 + WaitForSignals
270 + 'static,
271 F: AsyncFnOnce(&mut Env<S>, Option<JobControl>) + 'static,
272 {
273 let (pid, job_control) = self.start(env, task).await?;
274 let result = loop {
275 let result = env.wait_for_subshell_to_halt(pid).await?.1;
276 if !result.is_stopped() || job_control.is_some() {
277 break result;
278 }
279 };
280
281 if job_control == Some(JobControl::Foreground) {
282 if let Some(tty) = env.tty {
283 tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
284 .await
285 .ok();
286 }
287 }
288
289 Ok((pid, result))
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::job::{Job, ProcessState};
297 use crate::option::Option::{Interactive, Monitor};
298 use crate::option::State::On;
299 use crate::semantics::ExitStatus;
300 use crate::source::Location;
301 use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
302 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
303 use crate::system::{Concurrent, Disposition, Sigset as _};
304 use crate::test_helper::in_virtual_system;
305 use crate::trap::Action;
306 use assert_matches::assert_matches;
307 use futures_executor::LocalPool;
308 use std::cell::Cell;
309 use std::cell::RefCell;
310 use std::rc::Rc;
311
312 fn stub_tty(state: &RefCell<SystemState>) {
313 state
314 .borrow_mut()
315 .file_system
316 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
317 .unwrap();
318 }
319
320 #[test]
321 fn start_returns_child_process_id() {
322 in_virtual_system(|mut env, _state| async move {
323 let parent_pid = env.main_pid;
324 let child_pid = Rc::new(Cell::new(None));
325 let child_pid_2 = Rc::clone(&child_pid);
326 let result = Config::new()
327 .start(
328 &mut env,
329 async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
330 child_pid_2.set(Some(env.system.getpid()));
331 assert_eq!(env.system.getppid(), parent_pid);
332 },
333 )
334 .await
335 .unwrap()
336 .0;
337 env.wait_for_subshell(result).await.unwrap();
338 assert_eq!(Some(result), child_pid.get());
339 });
340 }
341
342 #[test]
343 fn start_failing() {
344 let mut executor = LocalPool::new();
345 let env = &mut Env::new_virtual();
346 let result = executor.run_until(
347 Config::new().start(env, async |_env: &mut Env<_>, _job_control| {
348 unreachable!("subshell not expected to run")
349 }),
350 );
351 assert_eq!(result, Err(Errno::ENOSYS));
352 }
353
354 #[test]
355 fn stack_frame_in_subshell() {
356 in_virtual_system(|mut env, _state| async move {
357 let pid = Config::new()
358 .start(
359 &mut env,
360 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
361 assert_eq!(env.stack[..], [Frame::Subshell])
362 },
363 )
364 .await
365 .unwrap()
366 .0;
367 assert_eq!(env.stack[..], []);
368
369 env.wait_for_subshell(pid).await.unwrap();
370 });
371 }
372
373 #[test]
374 fn jobs_disowned_in_subshell() {
375 in_virtual_system(|mut env, _state| async move {
376 let index = env.jobs.add(Job::new(Pid(123)));
377 let pid = Config::new()
378 .start(
379 &mut env,
380 async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
381 assert!(!env.jobs[index].is_owned)
382 },
383 )
384 .await
385 .unwrap()
386 .0;
387 env.wait_for_subshell(pid).await.unwrap();
388
389 assert!(env.jobs[index].is_owned);
390 });
391 }
392
393 #[test]
394 fn trap_reset_in_subshell() {
395 in_virtual_system(|mut env, _state| async move {
396 env.traps
397 .set_action(
398 &env.system,
399 SIGCHLD,
400 Action::Command("echo foo".into()),
401 Location::dummy(""),
402 false,
403 )
404 .await
405 .unwrap();
406 let pid = Config::new()
407 .start(
408 &mut env,
409 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
410 let (current, parent) = env.traps.get_state(SIGCHLD);
411 assert_eq!(current.unwrap().action, Action::Default);
412 assert_matches!(
413 &parent.unwrap().action,
414 Action::Command(body) => assert_eq!(&**body, "echo foo")
415 );
416 },
417 )
418 .await
419 .unwrap()
420 .0;
421 env.wait_for_subshell(pid).await.unwrap();
422 });
423 }
424
425 #[test]
426 fn subshell_with_no_job_control() {
427 in_virtual_system(|mut parent_env, state| async move {
428 parent_env.options.set(Monitor, On);
429
430 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
431 let state_2 = Rc::clone(&state);
432 let (child_pid, job_control) = Config::new()
433 .start(
434 &mut parent_env,
435 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
436 let child_pid = child_env.system.getpid();
437 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
438 assert_eq!(state_2.borrow().foreground, None);
439 assert_eq!(job_control, None);
440 },
441 )
442 .await
443 .unwrap();
444 assert_eq!(job_control, None);
445 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
446 assert_eq!(state.borrow().foreground, None);
447
448 parent_env.wait_for_subshell(child_pid).await.unwrap();
449 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
450 assert_eq!(state.borrow().foreground, None);
451 });
452 }
453
454 #[test]
455 fn subshell_in_background() {
456 in_virtual_system(|mut parent_env, state| async move {
457 parent_env.options.set(Monitor, On);
458
459 let state_2 = Rc::clone(&state);
460 let config = Config {
461 job_control: Some(JobControl::Background),
462 ..Config::new()
463 };
464 let (child_pid, job_control) = config
465 .start(
466 &mut parent_env,
467 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
468 let child_pid = child_env.system.getpid();
469 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
470 assert_eq!(state_2.borrow().foreground, None);
471 assert_eq!(job_control, Some(JobControl::Background));
472 },
473 )
474 .await
475 .unwrap();
476 assert_eq!(job_control, Some(JobControl::Background));
477 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
478 assert_eq!(state.borrow().foreground, None);
479
480 parent_env.wait_for_subshell(child_pid).await.unwrap();
481 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
482 assert_eq!(state.borrow().foreground, None);
483 });
484 }
485
486 #[test]
487 fn subshell_in_foreground() {
488 in_virtual_system(|mut parent_env, state| async move {
489 parent_env.options.set(Monitor, On);
490 stub_tty(&state);
491
492 let state_2 = Rc::clone(&state);
493 let (child_pid, job_control) = Config::foreground()
494 .start(
495 &mut parent_env,
496 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
497 let child_pid = child_env.system.getpid();
498 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
499 assert_eq!(state_2.borrow().foreground, Some(child_pid));
500 assert_eq!(job_control, Some(JobControl::Foreground));
501 },
502 )
503 .await
504 .unwrap();
505 assert_eq!(job_control, Some(JobControl::Foreground));
506 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
507 parent_env.wait_for_subshell(child_pid).await.unwrap();
511 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
512 assert_eq!(state.borrow().foreground, Some(child_pid));
513 });
514 }
515
516 #[test]
517 fn tty_after_starting_foreground_subshell() {
518 in_virtual_system(|mut parent_env, state| async move {
519 parent_env.options.set(Monitor, On);
520 stub_tty(&state);
521
522 let _ = Config::foreground()
523 .start(
524 &mut parent_env,
525 async move |_: &mut Env<Rc<Concurrent<VirtualSystem>>>, _| (),
526 )
527 .await
528 .unwrap();
529 assert_matches!(parent_env.tty, Some(_));
530 });
531 }
532
533 #[test]
534 fn job_control_without_tty() {
535 in_virtual_system(async |mut parent_env, state| {
539 parent_env.options.set(Monitor, On);
540
541 let state_2 = Rc::clone(&state);
542 let (child_pid, job_control) = Config::foreground()
543 .start(
544 &mut parent_env,
545 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
546 let child_pid = child_env.system.getpid();
547 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
548 assert_eq!(job_control, Some(JobControl::Foreground));
549 },
550 )
551 .await
552 .unwrap();
553 assert_eq!(job_control, Some(JobControl::Foreground));
554 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
555
556 parent_env.wait_for_subshell(child_pid).await.unwrap();
557 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
558 })
559 }
560
561 #[test]
562 fn no_job_control_with_option_disabled() {
563 in_virtual_system(|mut parent_env, state| async move {
564 stub_tty(&state);
565
566 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
567 let state_2 = Rc::clone(&state);
568 let (child_pid, job_control) = Config::foreground()
569 .start(
570 &mut parent_env,
571 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
572 _job_control| {
573 let child_pid = child_env.system.getpid();
574 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
575 assert_eq!(state_2.borrow().foreground, None);
576 },
577 )
578 .await
579 .unwrap();
580 assert_eq!(job_control, None);
581 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
582 assert_eq!(state.borrow().foreground, None);
583
584 parent_env.wait_for_subshell(child_pid).await.unwrap();
585 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
586 assert_eq!(state.borrow().foreground, None);
587 });
588 }
589
590 #[test]
591 fn no_job_control_for_nested_subshell() {
592 in_virtual_system(|mut parent_env, state| async move {
593 let mut parent_env = parent_env.push_frame(Frame::Subshell);
594 parent_env.options.set(Monitor, On);
595 stub_tty(&state);
596
597 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
598 let state_2 = Rc::clone(&state);
599 let (child_pid, job_control) = Config::foreground()
600 .start(
601 &mut parent_env,
602 async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
603 _job_control| {
604 let child_pid = child_env.system.getpid();
605 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
606 assert_eq!(state_2.borrow().foreground, None);
607 },
608 )
609 .await
610 .unwrap();
611 assert_eq!(job_control, None);
612 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
613 assert_eq!(state.borrow().foreground, None);
614
615 parent_env.wait_for_subshell(child_pid).await.unwrap();
616 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
617 assert_eq!(state.borrow().foreground, None);
618 });
619 }
620
621 #[test]
622 fn wait_without_job_control() {
623 in_virtual_system(|mut env, _state| async move {
624 let (_pid, process_result) = Config::new()
625 .start_and_wait(
626 &mut env,
627 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
628 env.exit_status = ExitStatus(42)
629 },
630 )
631 .await
632 .unwrap();
633 assert_eq!(process_result, ProcessResult::exited(42));
634 });
635 }
636
637 #[test]
638 fn wait_for_foreground_job_to_exit() {
639 in_virtual_system(|mut env, state| async move {
640 env.options.set(Monitor, On);
641 stub_tty(&state);
642
643 let (_pid, process_result) = Config::foreground()
644 .start_and_wait(
645 &mut env,
646 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
647 env.exit_status = ExitStatus(123)
648 },
649 )
650 .await
651 .unwrap();
652 assert_eq!(process_result, ProcessResult::exited(123));
653 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
654 });
655 }
656
657 #[test]
661 fn sigint_sigquit_not_ignored_by_default() {
662 in_virtual_system(|mut parent_env, state| async move {
663 let (child_pid, _) = Config {
664 job_control: Some(JobControl::Background),
665 ..Config::new()
666 }
667 .start(
668 &mut parent_env,
669 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
670 env.exit_status = ExitStatus(123)
671 },
672 )
673 .await
674 .unwrap();
675 parent_env.wait_for_subshell(child_pid).await.unwrap();
676
677 let state = state.borrow();
678 let process = &state.processes[&child_pid];
679 assert_eq!(process.disposition(SIGINT), Disposition::Default);
680 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
681 })
682 }
683
684 #[test]
685 fn sigint_sigquit_ignored_in_uncontrolled_job() {
686 in_virtual_system(|mut parent_env, state| async move {
687 let (child_pid, _) = Config {
688 job_control: Some(JobControl::Background),
689 ignores_sigint_sigquit: true,
690 }
691 .start(
692 &mut parent_env,
693 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
694 env.exit_status = ExitStatus(123)
695 },
696 )
697 .await
698 .unwrap();
699
700 parent_env
701 .system
702 .kill(child_pid, Some(SIGINT))
703 .await
704 .unwrap();
705 parent_env
706 .system
707 .kill(child_pid, Some(SIGQUIT))
708 .await
709 .unwrap();
710
711 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
712 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
713
714 let state = state.borrow();
715 let parent_process = &state.processes[&parent_env.main_pid];
716 assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
717 assert_eq!(
718 parent_process.blocked_signals().contains(SIGQUIT),
719 Ok(false)
720 );
721 let child_process = &state.processes[&child_pid];
722 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
723 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
724 })
725 }
726
727 #[test]
728 fn sigint_sigquit_not_ignored_if_job_controlled() {
729 in_virtual_system(|mut parent_env, state| async move {
730 parent_env.options.set(Monitor, On);
731 stub_tty(&state);
732
733 let (child_pid, _) = Config {
734 job_control: Some(JobControl::Background),
735 ignores_sigint_sigquit: true,
736 }
737 .start(
738 &mut parent_env,
739 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
740 env.exit_status = ExitStatus(123)
741 },
742 )
743 .await
744 .unwrap();
745 parent_env.wait_for_subshell(child_pid).await.unwrap();
746
747 let state = state.borrow();
748 let process = &state.processes[&child_pid];
749 assert_eq!(process.disposition(SIGINT), Disposition::Default);
750 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
751 })
752 }
753
754 #[test]
755 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
756 {
757 in_virtual_system(|mut parent_env, state| async move {
758 parent_env.options.set(Interactive, On);
759 parent_env.options.set(Monitor, On);
760 parent_env
761 .traps
762 .enable_internal_dispositions_for_stoppers(&parent_env.system)
763 .await
764 .unwrap();
765 stub_tty(&state);
766
767 let (child_pid, _) = Config::new()
768 .start(
769 &mut parent_env,
770 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
771 env.exit_status = ExitStatus(123)
772 },
773 )
774 .await
775 .unwrap();
776
777 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
778 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
779
780 let state = state.borrow();
781 let child_process = &state.processes[&child_pid];
782 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
783 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
784 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
785 })
786 }
787
788 #[test]
789 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
790 in_virtual_system(|mut parent_env, state| async move {
791 parent_env.options.set(Interactive, On);
792 parent_env.options.set(Monitor, On);
793 parent_env
794 .traps
795 .enable_internal_dispositions_for_stoppers(&parent_env.system)
796 .await
797 .unwrap();
798 stub_tty(&state);
799
800 let (child_pid, _) = Config {
801 job_control: Some(JobControl::Background),
802 ..Config::new()
803 }
804 .start(
805 &mut parent_env,
806 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
807 env.exit_status = ExitStatus(123)
808 },
809 )
810 .await
811 .unwrap();
812
813 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
814 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
815
816 let state = state.borrow();
817 let child_process = &state.processes[&child_pid];
818 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
819 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
820 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
821 })
822 }
823
824 #[test]
825 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
826 in_virtual_system(|mut parent_env, state| async move {
827 parent_env.options.set(Interactive, On);
828 stub_tty(&state);
829
830 let (child_pid, _) = Config::new()
831 .start(
832 &mut parent_env,
833 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
834 env.exit_status = ExitStatus(123)
835 },
836 )
837 .await
838 .unwrap();
839
840 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
841 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
842
843 let state = state.borrow();
844 let child_process = &state.processes[&child_pid];
845 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
846 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
847 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
848 })
849 }
850
851 #[test]
852 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
853 {
854 in_virtual_system(|mut parent_env, state| async move {
855 parent_env.options.set(Monitor, On);
856 stub_tty(&state);
857
858 let (child_pid, _) = Config::new()
859 .start(
860 &mut parent_env,
861 async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
862 env.exit_status = ExitStatus(123)
863 },
864 )
865 .await
866 .unwrap();
867
868 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
869 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
870
871 let state = state.borrow();
872 let child_process = &state.processes[&child_pid];
873 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
874 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
875 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
876 })
877 }
878}