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