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