1use crate::Env;
32use crate::job::Pid;
33use crate::job::ProcessResult;
34use crate::job::RunBlocking;
35use crate::job::RunUnblocking;
36use crate::system::Close;
37use crate::system::Dup;
38use crate::system::Errno;
39use crate::system::Exit;
40use crate::system::Fork;
41use crate::system::GetPid;
42use crate::system::Open;
43use crate::system::SendSignal;
44use crate::system::SetPgid;
45use crate::system::Sigmask;
46use crate::system::SigmaskOp;
47use crate::system::Signals;
48use crate::system::Sigset as _;
49use crate::system::TcSetPgrp;
50use crate::system::Wait;
51use crate::system::concurrency::WaitForSignals;
52use crate::system::resource::SetRlimit;
53use crate::trap::SignalSystem;
54use std::marker::PhantomData;
55use std::pin::Pin;
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum JobControl {
60 Foreground,
62 Background,
64}
65
66#[deprecated(
71 note = "use `Config` for subshell configuration instead",
72 since = "0.14.0"
73)]
74#[must_use = "a subshell is not started unless you call `Subshell::start`"]
75pub struct Subshell<S, F> {
76 task: F,
77 job_control: Option<JobControl>,
78 ignores_sigint_sigquit: bool,
79 phantom_data: PhantomData<fn(&mut Env<S>)>,
80}
81
82#[allow(deprecated, reason = "for backward compatible API")]
83impl<S, F> std::fmt::Debug for Subshell<S, F> {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("Subshell")
86 .field("job_control", &self.job_control)
87 .field("ignores_sigint_sigquit", &self.ignores_sigint_sigquit)
88 .finish_non_exhaustive()
89 }
90}
91
92#[allow(deprecated, reason = "for backward compatible API")]
93impl<S, F> Subshell<S, F>
94where
95 S: BlockSignals
96 + Close
97 + Dup
98 + Exit
99 + Fork
100 + GetPid
101 + Open
102 + RunBlocking
103 + RunUnblocking
104 + SendSignal
105 + SetPgid
106 + SetRlimit
107 + SignalSystem
108 + TcSetPgrp
109 + WaitForSignals
110 + 'static,
111 F: for<'a> FnOnce(&'a mut Env<S>, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
112 + 'static,
113 {
115 pub fn new(task: F) -> Self {
129 Subshell {
130 task,
131 job_control: None,
132 ignores_sigint_sigquit: false,
133 phantom_data: PhantomData,
134 }
135 }
136
137 pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
157 self.job_control = job_control.into();
158 self
159 }
160
161 pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
168 self.ignores_sigint_sigquit = ignore;
169 self
170 }
171
172 fn into_config_and_task(self) -> (Config, F) {
173 let config = Config {
174 job_control: self.job_control,
175 ignores_sigint_sigquit: self.ignores_sigint_sigquit,
176 };
177 (config, self.task)
178 }
179
180 pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
202 let (config, task) = self.into_config_and_task();
203 config
204 .start(env, async move |env, job_control| {
205 task(env, job_control).await
206 })
207 .await
208 }
209
210 pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
230 where
231 S: Wait,
232 {
233 let (config, task) = self.into_config_and_task();
234 config
235 .start_and_wait(env, async move |env, job_control| {
236 task(env, job_control).await
237 })
238 .await
239 }
240}
241
242pub trait BlockSignals: Signals {
260 type SavedMask;
261
262 fn block_sigint_sigquit(
273 &self,
274 ) -> impl Future<Output = Result<Self::SavedMask, Errno>> + use<'_, Self>;
275
276 fn restore_sigmask(
282 &self,
283 mask: Self::SavedMask,
284 ) -> impl Future<Output = Result<(), Errno>> + use<'_, Self>;
285}
286
287impl<S> BlockSignals for S
288where
289 S: Sigmask + ?Sized,
290{
291 type SavedMask = S::Sigset;
292
293 async fn block_sigint_sigquit(&self) -> Result<Self::SavedMask, Errno> {
294 let mut old_mask = S::Sigset::new();
295 self.sigmask(
296 Some((
297 SigmaskOp::Add,
298 &S::Sigset::from_signals([S::SIGINT, S::SIGQUIT])?,
299 )),
300 Some(&mut old_mask),
301 )
302 .await?;
303 Ok(old_mask)
304 }
305
306 async fn restore_sigmask(&self, mask: Self::SavedMask) -> Result<(), Errno> {
307 self.sigmask(Some((SigmaskOp::Set, &mask)), None).await
308 }
309}
310
311mod config;
312pub use config::Config;
313
314#[allow(deprecated, reason = "for backward compatible API")]
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::job::{Job, ProcessState};
319 use crate::option::Option::{Interactive, Monitor};
320 use crate::option::State::On;
321 use crate::semantics::ExitStatus;
322 use crate::source::Location;
323 use crate::stack::Frame;
324 use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
325 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
326 use crate::system::{Concurrent, Disposition};
327 use crate::test_helper::in_virtual_system;
328 use crate::trap::Action;
329 use assert_matches::assert_matches;
330 use futures_executor::LocalPool;
331 use std::cell::Cell;
332 use std::cell::RefCell;
333 use std::rc::Rc;
334
335 fn stub_tty(state: &RefCell<SystemState>) {
336 state
337 .borrow_mut()
338 .file_system
339 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
340 .unwrap();
341 }
342
343 #[test]
344 fn subshell_start_returns_child_process_id() {
345 in_virtual_system(|mut env, _state| async move {
346 let parent_pid = env.main_pid;
347 let child_pid = Rc::new(Cell::new(None));
348 let child_pid_2 = Rc::clone(&child_pid);
349 let subshell = Subshell::new(
350 move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
351 Box::pin(async move {
352 child_pid_2.set(Some(env.system.getpid()));
353 assert_eq!(env.system.getppid(), parent_pid);
354 })
355 },
356 );
357 let result = subshell.start(&mut env).await.unwrap().0;
358 env.wait_for_subshell(result).await.unwrap();
359 assert_eq!(Some(result), child_pid.get());
360 });
361 }
362
363 #[test]
364 fn subshell_start_failing() {
365 let mut executor = LocalPool::new();
366 let env = &mut Env::new_virtual();
367 let subshell =
368 Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
369 let result = executor.run_until(subshell.start(env));
370 assert_eq!(result, Err(Errno::ENOSYS));
371 }
372
373 #[test]
374 fn stack_frame_in_subshell() {
375 in_virtual_system(|mut env, _state| async move {
376 let subshell = Subshell::new(|env, _job_control| {
377 Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
378 });
379 let pid = subshell.start(&mut env).await.unwrap().0;
380 assert_eq!(env.stack[..], []);
381
382 env.wait_for_subshell(pid).await.unwrap();
383 });
384 }
385
386 #[test]
387 fn jobs_disowned_in_subshell() {
388 in_virtual_system(|mut env, _state| async move {
389 let index = env.jobs.add(Job::new(Pid(123)));
390 let subshell = Subshell::new(move |env, _job_control| {
391 Box::pin(async move { assert!(!env.jobs[index].is_owned) })
392 });
393 let pid = subshell.start(&mut env).await.unwrap().0;
394 env.wait_for_subshell(pid).await.unwrap();
395
396 assert!(env.jobs[index].is_owned);
397 });
398 }
399
400 #[test]
401 fn trap_reset_in_subshell() {
402 in_virtual_system(|mut env, _state| async move {
403 env.traps
404 .set_action(
405 &env.system,
406 SIGCHLD,
407 Action::Command("echo foo".into()),
408 Location::dummy(""),
409 false,
410 )
411 .await
412 .unwrap();
413 let subshell = Subshell::new(|env, _job_control| {
414 Box::pin(async {
415 let (current, parent) = env.traps.get_state(SIGCHLD);
416 assert_eq!(current.unwrap().action, Action::Default);
417 assert_matches!(
418 &parent.unwrap().action,
419 Action::Command(body) => assert_eq!(&**body, "echo foo")
420 );
421 })
422 });
423 let pid = subshell.start(&mut env).await.unwrap().0;
424 env.wait_for_subshell(pid).await.unwrap();
425 });
426 }
427
428 #[test]
429 fn subshell_with_no_job_control() {
430 in_virtual_system(|mut parent_env, state| async move {
431 parent_env.options.set(Monitor, On);
432
433 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
434 let state_2 = Rc::clone(&state);
435 let (child_pid, job_control) = Subshell::new(
436 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
437 Box::pin(async move {
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 )
445 .job_control(None)
446 .start(&mut parent_env)
447 .await
448 .unwrap();
449 assert_eq!(job_control, None);
450 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
451 assert_eq!(state.borrow().foreground, None);
452
453 parent_env.wait_for_subshell(child_pid).await.unwrap();
454 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
455 assert_eq!(state.borrow().foreground, None);
456 });
457 }
458
459 #[test]
460 fn subshell_in_background() {
461 in_virtual_system(|mut parent_env, state| async move {
462 parent_env.options.set(Monitor, On);
463
464 let state_2 = Rc::clone(&state);
465 let (child_pid, job_control) = Subshell::new(
466 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
467 Box::pin(async move {
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 )
475 .job_control(JobControl::Background)
476 .start(&mut parent_env)
477 .await
478 .unwrap();
479 assert_eq!(job_control, Some(JobControl::Background));
480 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
481 assert_eq!(state.borrow().foreground, None);
482
483 parent_env.wait_for_subshell(child_pid).await.unwrap();
484 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
485 assert_eq!(state.borrow().foreground, None);
486 });
487 }
488
489 #[test]
490 fn subshell_in_foreground() {
491 in_virtual_system(|mut parent_env, state| async move {
492 parent_env.options.set(Monitor, On);
493 stub_tty(&state);
494
495 let state_2 = Rc::clone(&state);
496 let (child_pid, job_control) = Subshell::new(
497 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
498 Box::pin(async move {
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 )
506 .job_control(JobControl::Foreground)
507 .start(&mut parent_env)
508 .await
509 .unwrap();
510 assert_eq!(job_control, Some(JobControl::Foreground));
511 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
512 parent_env.wait_for_subshell(child_pid).await.unwrap();
516 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
517 assert_eq!(state.borrow().foreground, Some(child_pid));
518 });
519 }
520
521 #[test]
522 fn tty_after_starting_foreground_subshell() {
523 in_virtual_system(|mut parent_env, state| async move {
524 parent_env.options.set(Monitor, On);
525 stub_tty(&state);
526
527 let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
528 .job_control(JobControl::Foreground)
529 .start(&mut parent_env)
530 .await
531 .unwrap();
532 assert_matches!(parent_env.tty, Some(_));
533 });
534 }
535
536 #[test]
537 fn job_control_without_tty() {
538 in_virtual_system(async |mut parent_env, state| {
542 parent_env.options.set(Monitor, On);
543
544 let state_2 = Rc::clone(&state);
545 let (child_pid, job_control) = Subshell::new(
546 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
547 Box::pin(async move {
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 )
554 .job_control(JobControl::Foreground)
555 .start(&mut parent_env)
556 .await
557 .unwrap();
558 assert_eq!(job_control, Some(JobControl::Foreground));
559 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
560
561 parent_env.wait_for_subshell(child_pid).await.unwrap();
562 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
563 })
564 }
565
566 #[test]
567 fn no_job_control_with_option_disabled() {
568 in_virtual_system(|mut parent_env, state| async move {
569 stub_tty(&state);
570
571 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
572 let state_2 = Rc::clone(&state);
573 let (child_pid, job_control) = Subshell::new(
574 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
575 Box::pin(async move {
576 let child_pid = child_env.system.getpid();
577 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
578 assert_eq!(state_2.borrow().foreground, None);
579 })
580 },
581 )
582 .job_control(JobControl::Foreground)
583 .start(&mut parent_env)
584 .await
585 .unwrap();
586 assert_eq!(job_control, None);
587 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
588 assert_eq!(state.borrow().foreground, None);
589
590 parent_env.wait_for_subshell(child_pid).await.unwrap();
591 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
592 assert_eq!(state.borrow().foreground, None);
593 });
594 }
595
596 #[test]
597 fn no_job_control_for_nested_subshell() {
598 in_virtual_system(|mut parent_env, state| async move {
599 let mut parent_env = parent_env.push_frame(Frame::Subshell);
600 parent_env.options.set(Monitor, On);
601 stub_tty(&state);
602
603 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
604 let state_2 = Rc::clone(&state);
605 let (child_pid, job_control) = Subshell::new(
606 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
607 Box::pin(async move {
608 let child_pid = child_env.system.getpid();
609 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
610 assert_eq!(state_2.borrow().foreground, None);
611 })
612 },
613 )
614 .job_control(JobControl::Foreground)
615 .start(&mut parent_env)
616 .await
617 .unwrap();
618 assert_eq!(job_control, None);
619 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
620 assert_eq!(state.borrow().foreground, None);
621
622 parent_env.wait_for_subshell(child_pid).await.unwrap();
623 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
624 assert_eq!(state.borrow().foreground, None);
625 });
626 }
627
628 #[test]
629 fn wait_without_job_control() {
630 in_virtual_system(|mut env, _state| async move {
631 let subshell = Subshell::new(|env, _job_control| {
632 Box::pin(async { env.exit_status = ExitStatus(42) })
633 });
634 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.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 subshell = Subshell::new(|env, _job_control| {
646 Box::pin(async { env.exit_status = ExitStatus(123) })
647 })
648 .job_control(JobControl::Foreground);
649 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
650 assert_eq!(process_result, ProcessResult::exited(123));
651 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
652 });
653 }
654
655 #[test]
659 fn sigint_sigquit_not_ignored_by_default() {
660 in_virtual_system(|mut parent_env, state| async move {
661 let (child_pid, _) = Subshell::new(|env, _job_control| {
662 Box::pin(async { env.exit_status = ExitStatus(123) })
663 })
664 .job_control(JobControl::Background)
665 .start(&mut parent_env)
666 .await
667 .unwrap();
668 parent_env.wait_for_subshell(child_pid).await.unwrap();
669
670 let state = state.borrow();
671 let process = &state.processes[&child_pid];
672 assert_eq!(process.disposition(SIGINT), Disposition::Default);
673 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
674 })
675 }
676
677 #[test]
678 fn sigint_sigquit_ignored_in_uncontrolled_job() {
679 in_virtual_system(|mut parent_env, state| async move {
680 let (child_pid, _) = Subshell::new(|env, _job_control| {
681 Box::pin(async { env.exit_status = ExitStatus(123) })
682 })
683 .job_control(JobControl::Background)
684 .ignore_sigint_sigquit(true)
685 .start(&mut parent_env)
686 .await
687 .unwrap();
688
689 parent_env
690 .system
691 .kill(child_pid, Some(SIGINT))
692 .await
693 .unwrap();
694 parent_env
695 .system
696 .kill(child_pid, Some(SIGQUIT))
697 .await
698 .unwrap();
699
700 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
701 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
702
703 let state = state.borrow();
704 let parent_process = &state.processes[&parent_env.main_pid];
705 assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
706 assert_eq!(
707 parent_process.blocked_signals().contains(SIGQUIT),
708 Ok(false)
709 );
710 let child_process = &state.processes[&child_pid];
711 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
712 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
713 })
714 }
715
716 #[test]
717 fn sigint_sigquit_not_ignored_if_job_controlled() {
718 in_virtual_system(|mut parent_env, state| async move {
719 parent_env.options.set(Monitor, On);
720 stub_tty(&state);
721
722 let (child_pid, _) = Subshell::new(|env, _job_control| {
723 Box::pin(async { env.exit_status = ExitStatus(123) })
724 })
725 .job_control(JobControl::Background)
726 .ignore_sigint_sigquit(true)
727 .start(&mut parent_env)
728 .await
729 .unwrap();
730 parent_env.wait_for_subshell(child_pid).await.unwrap();
731
732 let state = state.borrow();
733 let process = &state.processes[&child_pid];
734 assert_eq!(process.disposition(SIGINT), Disposition::Default);
735 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
736 })
737 }
738
739 #[test]
740 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
741 {
742 in_virtual_system(|mut parent_env, state| async move {
743 parent_env.options.set(Interactive, On);
744 parent_env.options.set(Monitor, On);
745 parent_env
746 .traps
747 .enable_internal_dispositions_for_stoppers(&parent_env.system)
748 .await
749 .unwrap();
750 stub_tty(&state);
751
752 let (child_pid, _) = Subshell::new(|env, _job_control| {
753 Box::pin(async { env.exit_status = ExitStatus(123) })
754 })
755 .start(&mut parent_env)
756 .await
757 .unwrap();
758
759 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
760 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
761
762 let state = state.borrow();
763 let child_process = &state.processes[&child_pid];
764 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
765 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
766 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
767 })
768 }
769
770 #[test]
771 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
772 in_virtual_system(|mut parent_env, state| async move {
773 parent_env.options.set(Interactive, On);
774 parent_env.options.set(Monitor, On);
775 parent_env
776 .traps
777 .enable_internal_dispositions_for_stoppers(&parent_env.system)
778 .await
779 .unwrap();
780 stub_tty(&state);
781
782 let (child_pid, _) = Subshell::new(|env, _job_control| {
783 Box::pin(async { env.exit_status = ExitStatus(123) })
784 })
785 .job_control(JobControl::Background)
786 .start(&mut parent_env)
787 .await
788 .unwrap();
789
790 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
791 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
792
793 let state = state.borrow();
794 let child_process = &state.processes[&child_pid];
795 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
796 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
797 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
798 })
799 }
800
801 #[test]
802 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
803 in_virtual_system(|mut parent_env, state| async move {
804 parent_env.options.set(Interactive, On);
805 stub_tty(&state);
806
807 let (child_pid, _) = Subshell::new(|env, _job_control| {
808 Box::pin(async { env.exit_status = ExitStatus(123) })
809 })
810 .start(&mut parent_env)
811 .await
812 .unwrap();
813
814 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
815 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
816
817 let state = state.borrow();
818 let child_process = &state.processes[&child_pid];
819 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
820 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
821 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
822 })
823 }
824
825 #[test]
826 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
827 {
828 in_virtual_system(|mut parent_env, state| async move {
829 parent_env.options.set(Monitor, On);
830 stub_tty(&state);
831
832 let (child_pid, _) = Subshell::new(|env, _job_control| {
833 Box::pin(async { env.exit_status = ExitStatus(123) })
834 })
835 .start(&mut parent_env)
836 .await
837 .unwrap();
838
839 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
840 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
841
842 let state = state.borrow();
843 let child_process = &state.processes[&child_pid];
844 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
845 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
846 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
847 })
848 }
849}