1use crate::Env;
29use crate::job::Pid;
30use crate::job::ProcessResult;
31use crate::job::tcsetpgrp_with_block;
32use crate::semantics::exit_or_raise;
33use crate::signal;
34use crate::stack::Frame;
35use crate::system::ChildProcessTask;
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::Sigaction;
46use crate::system::Sigmask;
47use crate::system::SigmaskOp;
48use crate::system::Signals;
49use crate::system::TcSetPgrp;
50use crate::system::Wait;
51use crate::system::resource::SetRlimit;
52use std::marker::PhantomData;
53use std::pin::Pin;
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum JobControl {
58 Foreground,
60 Background,
62}
63
64#[must_use = "a subshell is not started unless you call `Subshell::start`"]
68pub struct Subshell<S, F> {
69 task: F,
70 job_control: Option<JobControl>,
71 ignores_sigint_sigquit: bool,
72 phantom_data: PhantomData<fn(&mut Env<S>)>,
73}
74
75impl<S, F> std::fmt::Debug for Subshell<S, F> {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("Subshell")
78 .field("job_control", &self.job_control)
79 .field("ignores_sigint_sigquit", &self.ignores_sigint_sigquit)
80 .finish_non_exhaustive()
81 }
82}
83
84impl<S, F> Subshell<S, F>
85where
86 S: Close
87 + Dup
88 + Exit
89 + Fork
90 + GetPid
91 + Open
92 + SendSignal
93 + SetPgid
94 + SetRlimit
95 + Sigaction
96 + Sigmask
97 + Signals
98 + TcSetPgrp,
99 F: for<'a> FnOnce(&'a mut Env<S>, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
100 + 'static,
101 {
103 pub fn new(task: F) -> Self {
117 Subshell {
118 task,
119 job_control: None,
120 ignores_sigint_sigquit: false,
121 phantom_data: PhantomData,
122 }
123 }
124
125 pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
145 self.job_control = job_control.into();
146 self
147 }
148
149 pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
156 self.ignores_sigint_sigquit = ignore;
157 self
158 }
159
160 pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
182 let job_control = env.controls_jobs().then_some(self.job_control).flatten();
184 let tty = match job_control {
185 None | Some(JobControl::Background) => None,
186 Some(JobControl::Foreground) => env.get_tty().ok(),
188 };
189 let mut mask_guard = MaskGuard::new(env);
193 let ignore_sigint_sigquit = self.ignores_sigint_sigquit
194 && job_control.is_none()
195 && mask_guard.block_sigint_sigquit();
196 let keep_internal_dispositions_for_stoppers = job_control.is_none();
197
198 const ME: Pid = Pid(0);
200 let task: ChildProcessTask<S> = Box::new(move |env| {
201 Box::pin(async move {
202 let mut env = env.push_frame(Frame::Subshell);
203 let env = &mut *env;
204
205 if let Some(job_control) = job_control {
206 if let Ok(()) = env.system.setpgid(ME, ME) {
207 match job_control {
208 JobControl::Background => (),
209 JobControl::Foreground => {
210 if let Some(tty) = tty {
211 let pgid = env.system.getpgrp();
212 tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
213 }
214 }
215 }
216 }
217 }
218 env.jobs.disown_all();
219
220 env.traps.enter_subshell(
221 &mut env.system,
222 ignore_sigint_sigquit,
223 keep_internal_dispositions_for_stoppers,
224 );
225
226 (self.task)(env, job_control).await;
227 exit_or_raise(&env.system, env.exit_status).await
228 })
229 });
230
231 let child = mask_guard.env.system.new_child_process()?;
233 let child_pid = child(mask_guard.env, task);
234
235 if job_control.is_some() {
237 let _ = mask_guard.env.system.setpgid(child_pid, ME);
241
242 }
245
246 Ok((child_pid, job_control))
247 }
248
249 pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
269 where
270 S: Wait,
271 {
272 let (pid, job_control) = self.start(env).await?;
273 let result = loop {
274 let result = env.wait_for_subshell_to_halt(pid).await?.1;
275 if !result.is_stopped() || job_control.is_some() {
276 break result;
277 }
278 };
279
280 if job_control == Some(JobControl::Foreground) {
281 if let Some(tty) = env.tty {
282 tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
283 .await
284 .ok();
285 }
286 }
287
288 Ok((pid, result))
289 }
290}
291
292#[derive(Debug)]
297struct MaskGuard<'a, S: Signals + Sigmask> {
298 env: &'a mut Env<S>,
299 old_mask: Option<Vec<signal::Number>>,
300}
301
302impl<'a, S: Signals + Sigmask> MaskGuard<'a, S> {
303 fn new(env: &'a mut Env<S>) -> Self {
304 let old_mask = None;
305 Self { env, old_mask }
306 }
307
308 fn block_sigint_sigquit(&mut self) -> bool {
309 assert_eq!(self.old_mask, None);
310
311 let mut old_mask = Vec::new();
312
313 let success = self
314 .env
315 .system
316 .sigmask(
317 Some((SigmaskOp::Add, &[S::SIGINT, S::SIGQUIT])),
318 Some(&mut old_mask),
319 )
320 .is_ok();
321 if success {
322 self.old_mask = Some(old_mask);
323 }
324 success
325 }
326}
327
328impl<S: Signals + Sigmask> Drop for MaskGuard<'_, S> {
329 fn drop(&mut self) {
330 if let Some(old_mask) = &self.old_mask {
331 self.env
332 .system
333 .sigmask(Some((SigmaskOp::Set, old_mask)), None)
334 .ok();
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::job::{Job, ProcessState};
343 use crate::option::Option::{Interactive, Monitor};
344 use crate::option::State::On;
345 use crate::semantics::ExitStatus;
346 use crate::source::Location;
347 use crate::system::Disposition;
348 use crate::system::r#virtual::Inode;
349 use crate::system::r#virtual::SystemState;
350 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
351 use crate::tests::in_virtual_system;
352 use crate::trap::Action;
353 use assert_matches::assert_matches;
354 use futures_executor::LocalPool;
355 use std::cell::Cell;
356 use std::cell::RefCell;
357 use std::rc::Rc;
358
359 fn stub_tty(state: &RefCell<SystemState>) {
360 state
361 .borrow_mut()
362 .file_system
363 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
364 .unwrap();
365 }
366
367 #[test]
368 fn subshell_start_returns_child_process_id() {
369 in_virtual_system(|mut env, _state| async move {
370 let parent_pid = env.main_pid;
371 let child_pid = Rc::new(Cell::new(None));
372 let child_pid_2 = Rc::clone(&child_pid);
373 let subshell = Subshell::new(move |env, _job_control| {
374 Box::pin(async move {
375 child_pid_2.set(Some(env.system.getpid()));
376 assert_eq!(env.system.getppid(), parent_pid);
377 })
378 });
379 let result = subshell.start(&mut env).await.unwrap().0;
380 env.wait_for_subshell(result).await.unwrap();
381 assert_eq!(Some(result), child_pid.get());
382 });
383 }
384
385 #[test]
386 fn subshell_start_failing() {
387 let mut executor = LocalPool::new();
388 let env = &mut Env::new_virtual();
389 let subshell =
390 Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
391 let result = executor.run_until(subshell.start(env));
392 assert_eq!(result, Err(Errno::ENOSYS));
393 }
394
395 #[test]
396 fn stack_frame_in_subshell() {
397 in_virtual_system(|mut env, _state| async move {
398 let subshell = Subshell::new(|env, _job_control| {
399 Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
400 });
401 let pid = subshell.start(&mut env).await.unwrap().0;
402 assert_eq!(env.stack[..], []);
403
404 env.wait_for_subshell(pid).await.unwrap();
405 });
406 }
407
408 #[test]
409 fn jobs_disowned_in_subshell() {
410 in_virtual_system(|mut env, _state| async move {
411 let index = env.jobs.add(Job::new(Pid(123)));
412 let subshell = Subshell::new(move |env, _job_control| {
413 Box::pin(async move { assert!(!env.jobs[index].is_owned) })
414 });
415 let pid = subshell.start(&mut env).await.unwrap().0;
416 env.wait_for_subshell(pid).await.unwrap();
417
418 assert!(env.jobs[index].is_owned);
419 });
420 }
421
422 #[test]
423 fn trap_reset_in_subshell() {
424 in_virtual_system(|mut env, _state| async move {
425 env.traps
426 .set_action(
427 &mut env.system,
428 SIGCHLD,
429 Action::Command("echo foo".into()),
430 Location::dummy(""),
431 false,
432 )
433 .unwrap();
434 let subshell = Subshell::new(|env, _job_control| {
435 Box::pin(async {
436 let (current, parent) = env.traps.get_state(SIGCHLD);
437 assert_eq!(current.unwrap().action, Action::Default);
438 assert_matches!(
439 &parent.unwrap().action,
440 Action::Command(body) => assert_eq!(&**body, "echo foo")
441 );
442 })
443 });
444 let pid = subshell.start(&mut env).await.unwrap().0;
445 env.wait_for_subshell(pid).await.unwrap();
446 });
447 }
448
449 #[test]
450 fn subshell_with_no_job_control() {
451 in_virtual_system(|mut parent_env, state| async move {
452 parent_env.options.set(Monitor, On);
453
454 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
455 let state_2 = Rc::clone(&state);
456 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
457 Box::pin(async move {
458 let child_pid = child_env.system.getpid();
459 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
460 assert_eq!(state_2.borrow().foreground, None);
461 assert_eq!(job_control, None);
462 })
463 })
464 .job_control(None)
465 .start(&mut parent_env)
466 .await
467 .unwrap();
468 assert_eq!(job_control, None);
469 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
470 assert_eq!(state.borrow().foreground, None);
471
472 parent_env.wait_for_subshell(child_pid).await.unwrap();
473 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
474 assert_eq!(state.borrow().foreground, None);
475 });
476 }
477
478 #[test]
479 fn subshell_in_background() {
480 in_virtual_system(|mut parent_env, state| async move {
481 parent_env.options.set(Monitor, On);
482
483 let state_2 = Rc::clone(&state);
484 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
485 Box::pin(async move {
486 let child_pid = child_env.system.getpid();
487 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
488 assert_eq!(state_2.borrow().foreground, None);
489 assert_eq!(job_control, Some(JobControl::Background));
490 })
491 })
492 .job_control(JobControl::Background)
493 .start(&mut parent_env)
494 .await
495 .unwrap();
496 assert_eq!(job_control, Some(JobControl::Background));
497 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
498 assert_eq!(state.borrow().foreground, None);
499
500 parent_env.wait_for_subshell(child_pid).await.unwrap();
501 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
502 assert_eq!(state.borrow().foreground, None);
503 });
504 }
505
506 #[test]
507 fn subshell_in_foreground() {
508 in_virtual_system(|mut parent_env, state| async move {
509 parent_env.options.set(Monitor, On);
510 stub_tty(&state);
511
512 let state_2 = Rc::clone(&state);
513 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
514 Box::pin(async move {
515 let child_pid = child_env.system.getpid();
516 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
517 assert_eq!(state_2.borrow().foreground, Some(child_pid));
518 assert_eq!(job_control, Some(JobControl::Foreground));
519 })
520 })
521 .job_control(JobControl::Foreground)
522 .start(&mut parent_env)
523 .await
524 .unwrap();
525 assert_eq!(job_control, Some(JobControl::Foreground));
526 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
527 parent_env.wait_for_subshell(child_pid).await.unwrap();
531 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
532 assert_eq!(state.borrow().foreground, Some(child_pid));
533 });
534 }
535
536 #[test]
537 fn tty_after_starting_foreground_subshell() {
538 in_virtual_system(|mut parent_env, state| async move {
539 parent_env.options.set(Monitor, On);
540 stub_tty(&state);
541
542 let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
543 .job_control(JobControl::Foreground)
544 .start(&mut parent_env)
545 .await
546 .unwrap();
547 assert_matches!(parent_env.tty, Some(_));
548 });
549 }
550
551 #[test]
552 fn job_control_without_tty() {
553 in_virtual_system(async |mut parent_env, state| {
557 parent_env.options.set(Monitor, On);
558
559 let state_2 = Rc::clone(&state);
560 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
561 Box::pin(async move {
562 let child_pid = child_env.system.getpid();
563 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
564 assert_eq!(job_control, Some(JobControl::Foreground));
565 })
566 })
567 .job_control(JobControl::Foreground)
568 .start(&mut parent_env)
569 .await
570 .unwrap();
571 assert_eq!(job_control, Some(JobControl::Foreground));
572 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
573
574 parent_env.wait_for_subshell(child_pid).await.unwrap();
575 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
576 })
577 }
578
579 #[test]
580 fn no_job_control_with_option_disabled() {
581 in_virtual_system(|mut parent_env, state| async move {
582 stub_tty(&state);
583
584 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
585 let state_2 = Rc::clone(&state);
586 let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
587 Box::pin(async move {
588 let child_pid = child_env.system.getpid();
589 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
590 assert_eq!(state_2.borrow().foreground, None);
591 })
592 })
593 .job_control(JobControl::Foreground)
594 .start(&mut parent_env)
595 .await
596 .unwrap();
597 assert_eq!(job_control, None);
598 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
599 assert_eq!(state.borrow().foreground, None);
600
601 parent_env.wait_for_subshell(child_pid).await.unwrap();
602 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
603 assert_eq!(state.borrow().foreground, None);
604 });
605 }
606
607 #[test]
608 fn no_job_control_for_nested_subshell() {
609 in_virtual_system(|mut parent_env, state| async move {
610 let mut parent_env = parent_env.push_frame(Frame::Subshell);
611 parent_env.options.set(Monitor, On);
612 stub_tty(&state);
613
614 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
615 let state_2 = Rc::clone(&state);
616 let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
617 Box::pin(async move {
618 let child_pid = child_env.system.getpid();
619 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
620 assert_eq!(state_2.borrow().foreground, None);
621 })
622 })
623 .job_control(JobControl::Foreground)
624 .start(&mut parent_env)
625 .await
626 .unwrap();
627 assert_eq!(job_control, None);
628 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
629 assert_eq!(state.borrow().foreground, None);
630
631 parent_env.wait_for_subshell(child_pid).await.unwrap();
632 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
633 assert_eq!(state.borrow().foreground, None);
634 });
635 }
636
637 #[test]
638 fn wait_without_job_control() {
639 in_virtual_system(|mut env, _state| async move {
640 let subshell = Subshell::new(|env, _job_control| {
641 Box::pin(async { env.exit_status = ExitStatus(42) })
642 });
643 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
644 assert_eq!(process_result, ProcessResult::exited(42));
645 });
646 }
647
648 #[test]
649 fn wait_for_foreground_job_to_exit() {
650 in_virtual_system(|mut env, state| async move {
651 env.options.set(Monitor, On);
652 stub_tty(&state);
653
654 let subshell = Subshell::new(|env, _job_control| {
655 Box::pin(async { env.exit_status = ExitStatus(123) })
656 })
657 .job_control(JobControl::Foreground);
658 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
659 assert_eq!(process_result, ProcessResult::exited(123));
660 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
661 });
662 }
663
664 #[test]
668 fn sigint_sigquit_not_ignored_by_default() {
669 in_virtual_system(|mut parent_env, state| async move {
670 let (child_pid, _) = Subshell::new(|env, _job_control| {
671 Box::pin(async { env.exit_status = ExitStatus(123) })
672 })
673 .job_control(JobControl::Background)
674 .start(&mut parent_env)
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, _) = Subshell::new(|env, _job_control| {
690 Box::pin(async { env.exit_status = ExitStatus(123) })
691 })
692 .job_control(JobControl::Background)
693 .ignore_sigint_sigquit(true)
694 .start(&mut parent_env)
695 .await
696 .unwrap();
697
698 parent_env
699 .system
700 .kill(child_pid, Some(SIGINT))
701 .await
702 .unwrap();
703 parent_env
704 .system
705 .kill(child_pid, Some(SIGQUIT))
706 .await
707 .unwrap();
708
709 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
710 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
711
712 let state = state.borrow();
713 let parent_process = &state.processes[&parent_env.main_pid];
714 assert!(!parent_process.blocked_signals().contains(&SIGINT));
715 assert!(!parent_process.blocked_signals().contains(&SIGQUIT));
716 let child_process = &state.processes[&child_pid];
717 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
718 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
719 })
720 }
721
722 #[test]
723 fn sigint_sigquit_not_ignored_if_job_controlled() {
724 in_virtual_system(|mut parent_env, state| async move {
725 parent_env.options.set(Monitor, On);
726 stub_tty(&state);
727
728 let (child_pid, _) = Subshell::new(|env, _job_control| {
729 Box::pin(async { env.exit_status = ExitStatus(123) })
730 })
731 .job_control(JobControl::Background)
732 .ignore_sigint_sigquit(true)
733 .start(&mut parent_env)
734 .await
735 .unwrap();
736 parent_env.wait_for_subshell(child_pid).await.unwrap();
737
738 let state = state.borrow();
739 let process = &state.processes[&child_pid];
740 assert_eq!(process.disposition(SIGINT), Disposition::Default);
741 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
742 })
743 }
744
745 #[test]
746 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
747 {
748 in_virtual_system(|mut parent_env, state| async move {
749 parent_env.options.set(Interactive, On);
750 parent_env.options.set(Monitor, On);
751 parent_env
752 .traps
753 .enable_internal_dispositions_for_stoppers(&mut parent_env.system)
754 .unwrap();
755 stub_tty(&state);
756
757 let (child_pid, _) = Subshell::new(|env, _job_control| {
758 Box::pin(async { env.exit_status = ExitStatus(123) })
759 })
760 .start(&mut parent_env)
761 .await
762 .unwrap();
763
764 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
765 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
766
767 let state = state.borrow();
768 let child_process = &state.processes[&child_pid];
769 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
770 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
771 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
772 })
773 }
774
775 #[test]
776 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
777 in_virtual_system(|mut parent_env, state| async move {
778 parent_env.options.set(Interactive, On);
779 parent_env.options.set(Monitor, On);
780 parent_env
781 .traps
782 .enable_internal_dispositions_for_stoppers(&mut parent_env.system)
783 .unwrap();
784 stub_tty(&state);
785
786 let (child_pid, _) = Subshell::new(|env, _job_control| {
787 Box::pin(async { env.exit_status = ExitStatus(123) })
788 })
789 .job_control(JobControl::Background)
790 .start(&mut parent_env)
791 .await
792 .unwrap();
793
794 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
795 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
796
797 let state = state.borrow();
798 let child_process = &state.processes[&child_pid];
799 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
800 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
801 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
802 })
803 }
804
805 #[test]
806 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
807 in_virtual_system(|mut parent_env, state| async move {
808 parent_env.options.set(Interactive, On);
809 stub_tty(&state);
810
811 let (child_pid, _) = Subshell::new(|env, _job_control| {
812 Box::pin(async { env.exit_status = ExitStatus(123) })
813 })
814 .start(&mut parent_env)
815 .await
816 .unwrap();
817
818 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
819 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
820
821 let state = state.borrow();
822 let child_process = &state.processes[&child_pid];
823 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
824 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
825 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
826 })
827 }
828
829 #[test]
830 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
831 {
832 in_virtual_system(|mut parent_env, state| async move {
833 parent_env.options.set(Monitor, On);
834 stub_tty(&state);
835
836 let (child_pid, _) = Subshell::new(|env, _job_control| {
837 Box::pin(async { env.exit_status = ExitStatus(123) })
838 })
839 .start(&mut parent_env)
840 .await
841 .unwrap();
842
843 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
844 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
845
846 let state = state.borrow();
847 let child_process = &state.processes[&child_pid];
848 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
849 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
850 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
851 })
852 }
853}