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().await.ok(),
188 };
189
190 let ignore_sigint_sigquit = self.ignores_sigint_sigquit && job_control.is_none();
191 let original_mask = if ignore_sigint_sigquit {
192 Some(block_sigint_sigquit(&env.system).await?)
196 } else {
197 None
198 };
199 let keep_internal_dispositions_for_stoppers = job_control.is_none();
200
201 const ME: Pid = Pid(0);
203 let task: ChildProcessTask<S> = Box::new(move |env| {
204 Box::pin(async move {
205 let mut env = env.push_frame(Frame::Subshell);
206 let env = &mut *env;
207
208 if let Some(job_control) = job_control {
209 if let Ok(()) = env.system.setpgid(ME, ME) {
210 match job_control {
211 JobControl::Background => (),
212 JobControl::Foreground => {
213 if let Some(tty) = tty {
214 let pgid = env.system.getpgrp();
215 tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
216 }
217 }
218 }
219 }
220 }
221 env.jobs.disown_all();
222
223 env.traps
224 .enter_subshell(
225 &env.system,
226 ignore_sigint_sigquit,
227 keep_internal_dispositions_for_stoppers,
228 )
229 .await;
230
231 (self.task)(env, job_control).await;
232 exit_or_raise(&env.system, env.exit_status).await
233 })
234 });
235
236 let result = env.system.new_child_process().map(|child| child(env, task));
238
239 if let Some(mask) = original_mask {
243 restore_sigmask(&env.system, &mask).await.ok();
244 }
245
246 let child_pid = result?;
247
248 if job_control.is_some() {
250 let _ = env.system.setpgid(child_pid, ME);
254
255 }
258
259 Ok((child_pid, job_control))
260 }
261
262 pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
282 where
283 S: Wait,
284 {
285 let (pid, job_control) = self.start(env).await?;
286 let result = loop {
287 let result = env.wait_for_subshell_to_halt(pid).await?.1;
288 if !result.is_stopped() || job_control.is_some() {
289 break result;
290 }
291 };
292
293 if job_control == Some(JobControl::Foreground) {
294 if let Some(tty) = env.tty {
295 tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
296 .await
297 .ok();
298 }
299 }
300
301 Ok((pid, result))
302 }
303}
304
305async fn block_sigint_sigquit<S: Sigmask>(system: &S) -> Result<Vec<signal::Number>, Errno> {
306 let mut old_mask = Vec::new();
307 system
308 .sigmask(
309 Some((SigmaskOp::Add, &[S::SIGINT, S::SIGQUIT])),
310 Some(&mut old_mask),
311 )
312 .await?;
313 Ok(old_mask)
314}
315
316async fn restore_sigmask<S: Sigmask>(system: &S, mask: &[signal::Number]) -> Result<(), Errno> {
317 system.sigmask(Some((SigmaskOp::Set, mask)), None).await
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::job::{Job, ProcessState};
324 use crate::option::Option::{Interactive, Monitor};
325 use crate::option::State::On;
326 use crate::semantics::ExitStatus;
327 use crate::source::Location;
328 use crate::system::Disposition;
329 use crate::system::r#virtual::Inode;
330 use crate::system::r#virtual::SystemState;
331 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
332 use crate::test_helper::in_virtual_system;
333 use crate::trap::Action;
334 use assert_matches::assert_matches;
335 use futures_executor::LocalPool;
336 use std::cell::Cell;
337 use std::cell::RefCell;
338 use std::rc::Rc;
339
340 fn stub_tty(state: &RefCell<SystemState>) {
341 state
342 .borrow_mut()
343 .file_system
344 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
345 .unwrap();
346 }
347
348 #[test]
349 fn subshell_start_returns_child_process_id() {
350 in_virtual_system(|mut env, _state| async move {
351 let parent_pid = env.main_pid;
352 let child_pid = Rc::new(Cell::new(None));
353 let child_pid_2 = Rc::clone(&child_pid);
354 let subshell = Subshell::new(move |env, _job_control| {
355 Box::pin(async move {
356 child_pid_2.set(Some(env.system.getpid()));
357 assert_eq!(env.system.getppid(), parent_pid);
358 })
359 });
360 let result = subshell.start(&mut env).await.unwrap().0;
361 env.wait_for_subshell(result).await.unwrap();
362 assert_eq!(Some(result), child_pid.get());
363 });
364 }
365
366 #[test]
367 fn subshell_start_failing() {
368 let mut executor = LocalPool::new();
369 let env = &mut Env::new_virtual();
370 let subshell =
371 Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
372 let result = executor.run_until(subshell.start(env));
373 assert_eq!(result, Err(Errno::ENOSYS));
374 }
375
376 #[test]
377 fn stack_frame_in_subshell() {
378 in_virtual_system(|mut env, _state| async move {
379 let subshell = Subshell::new(|env, _job_control| {
380 Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
381 });
382 let pid = subshell.start(&mut env).await.unwrap().0;
383 assert_eq!(env.stack[..], []);
384
385 env.wait_for_subshell(pid).await.unwrap();
386 });
387 }
388
389 #[test]
390 fn jobs_disowned_in_subshell() {
391 in_virtual_system(|mut env, _state| async move {
392 let index = env.jobs.add(Job::new(Pid(123)));
393 let subshell = Subshell::new(move |env, _job_control| {
394 Box::pin(async move { assert!(!env.jobs[index].is_owned) })
395 });
396 let pid = subshell.start(&mut env).await.unwrap().0;
397 env.wait_for_subshell(pid).await.unwrap();
398
399 assert!(env.jobs[index].is_owned);
400 });
401 }
402
403 #[test]
404 fn trap_reset_in_subshell() {
405 in_virtual_system(|mut env, _state| async move {
406 env.traps
407 .set_action(
408 &env.system,
409 SIGCHLD,
410 Action::Command("echo foo".into()),
411 Location::dummy(""),
412 false,
413 )
414 .await
415 .unwrap();
416 let subshell = Subshell::new(|env, _job_control| {
417 Box::pin(async {
418 let (current, parent) = env.traps.get_state(SIGCHLD);
419 assert_eq!(current.unwrap().action, Action::Default);
420 assert_matches!(
421 &parent.unwrap().action,
422 Action::Command(body) => assert_eq!(&**body, "echo foo")
423 );
424 })
425 });
426 let pid = subshell.start(&mut env).await.unwrap().0;
427 env.wait_for_subshell(pid).await.unwrap();
428 });
429 }
430
431 #[test]
432 fn subshell_with_no_job_control() {
433 in_virtual_system(|mut parent_env, state| async move {
434 parent_env.options.set(Monitor, On);
435
436 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
437 let state_2 = Rc::clone(&state);
438 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
439 Box::pin(async move {
440 let child_pid = child_env.system.getpid();
441 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
442 assert_eq!(state_2.borrow().foreground, None);
443 assert_eq!(job_control, None);
444 })
445 })
446 .job_control(None)
447 .start(&mut parent_env)
448 .await
449 .unwrap();
450 assert_eq!(job_control, None);
451 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
452 assert_eq!(state.borrow().foreground, None);
453
454 parent_env.wait_for_subshell(child_pid).await.unwrap();
455 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
456 assert_eq!(state.borrow().foreground, None);
457 });
458 }
459
460 #[test]
461 fn subshell_in_background() {
462 in_virtual_system(|mut parent_env, state| async move {
463 parent_env.options.set(Monitor, On);
464
465 let state_2 = Rc::clone(&state);
466 let (child_pid, job_control) = Subshell::new(move |child_env, 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 .job_control(JobControl::Background)
475 .start(&mut parent_env)
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) = Subshell::new(move |child_env, job_control| {
496 Box::pin(async move {
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 .job_control(JobControl::Foreground)
504 .start(&mut parent_env)
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 _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
525 .job_control(JobControl::Foreground)
526 .start(&mut parent_env)
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) = Subshell::new(move |child_env, job_control| {
543 Box::pin(async move {
544 let child_pid = child_env.system.getpid();
545 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
546 assert_eq!(job_control, Some(JobControl::Foreground));
547 })
548 })
549 .job_control(JobControl::Foreground)
550 .start(&mut parent_env)
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) = Subshell::new(move |child_env, _job_control| {
569 Box::pin(async move {
570 let child_pid = child_env.system.getpid();
571 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
572 assert_eq!(state_2.borrow().foreground, None);
573 })
574 })
575 .job_control(JobControl::Foreground)
576 .start(&mut parent_env)
577 .await
578 .unwrap();
579 assert_eq!(job_control, None);
580 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
581 assert_eq!(state.borrow().foreground, None);
582
583 parent_env.wait_for_subshell(child_pid).await.unwrap();
584 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
585 assert_eq!(state.borrow().foreground, None);
586 });
587 }
588
589 #[test]
590 fn no_job_control_for_nested_subshell() {
591 in_virtual_system(|mut parent_env, state| async move {
592 let mut parent_env = parent_env.push_frame(Frame::Subshell);
593 parent_env.options.set(Monitor, On);
594 stub_tty(&state);
595
596 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
597 let state_2 = Rc::clone(&state);
598 let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
599 Box::pin(async move {
600 let child_pid = child_env.system.getpid();
601 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
602 assert_eq!(state_2.borrow().foreground, None);
603 })
604 })
605 .job_control(JobControl::Foreground)
606 .start(&mut parent_env)
607 .await
608 .unwrap();
609 assert_eq!(job_control, None);
610 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
611 assert_eq!(state.borrow().foreground, None);
612
613 parent_env.wait_for_subshell(child_pid).await.unwrap();
614 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
615 assert_eq!(state.borrow().foreground, None);
616 });
617 }
618
619 #[test]
620 fn wait_without_job_control() {
621 in_virtual_system(|mut env, _state| async move {
622 let subshell = Subshell::new(|env, _job_control| {
623 Box::pin(async { env.exit_status = ExitStatus(42) })
624 });
625 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
626 assert_eq!(process_result, ProcessResult::exited(42));
627 });
628 }
629
630 #[test]
631 fn wait_for_foreground_job_to_exit() {
632 in_virtual_system(|mut env, state| async move {
633 env.options.set(Monitor, On);
634 stub_tty(&state);
635
636 let subshell = Subshell::new(|env, _job_control| {
637 Box::pin(async { env.exit_status = ExitStatus(123) })
638 })
639 .job_control(JobControl::Foreground);
640 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
641 assert_eq!(process_result, ProcessResult::exited(123));
642 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
643 });
644 }
645
646 #[test]
650 fn sigint_sigquit_not_ignored_by_default() {
651 in_virtual_system(|mut parent_env, state| async move {
652 let (child_pid, _) = Subshell::new(|env, _job_control| {
653 Box::pin(async { env.exit_status = ExitStatus(123) })
654 })
655 .job_control(JobControl::Background)
656 .start(&mut parent_env)
657 .await
658 .unwrap();
659 parent_env.wait_for_subshell(child_pid).await.unwrap();
660
661 let state = state.borrow();
662 let process = &state.processes[&child_pid];
663 assert_eq!(process.disposition(SIGINT), Disposition::Default);
664 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
665 })
666 }
667
668 #[test]
669 fn sigint_sigquit_ignored_in_uncontrolled_job() {
670 in_virtual_system(|mut parent_env, state| async move {
671 let (child_pid, _) = Subshell::new(|env, _job_control| {
672 Box::pin(async { env.exit_status = ExitStatus(123) })
673 })
674 .job_control(JobControl::Background)
675 .ignore_sigint_sigquit(true)
676 .start(&mut parent_env)
677 .await
678 .unwrap();
679
680 parent_env
681 .system
682 .kill(child_pid, Some(SIGINT))
683 .await
684 .unwrap();
685 parent_env
686 .system
687 .kill(child_pid, Some(SIGQUIT))
688 .await
689 .unwrap();
690
691 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
692 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
693
694 let state = state.borrow();
695 let parent_process = &state.processes[&parent_env.main_pid];
696 assert!(!parent_process.blocked_signals().contains(&SIGINT));
697 assert!(!parent_process.blocked_signals().contains(&SIGQUIT));
698 let child_process = &state.processes[&child_pid];
699 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
700 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
701 })
702 }
703
704 #[test]
705 fn sigint_sigquit_not_ignored_if_job_controlled() {
706 in_virtual_system(|mut parent_env, state| async move {
707 parent_env.options.set(Monitor, On);
708 stub_tty(&state);
709
710 let (child_pid, _) = Subshell::new(|env, _job_control| {
711 Box::pin(async { env.exit_status = ExitStatus(123) })
712 })
713 .job_control(JobControl::Background)
714 .ignore_sigint_sigquit(true)
715 .start(&mut parent_env)
716 .await
717 .unwrap();
718 parent_env.wait_for_subshell(child_pid).await.unwrap();
719
720 let state = state.borrow();
721 let process = &state.processes[&child_pid];
722 assert_eq!(process.disposition(SIGINT), Disposition::Default);
723 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
724 })
725 }
726
727 #[test]
728 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
729 {
730 in_virtual_system(|mut parent_env, state| async move {
731 parent_env.options.set(Interactive, On);
732 parent_env.options.set(Monitor, On);
733 parent_env
734 .traps
735 .enable_internal_dispositions_for_stoppers(&parent_env.system)
736 .await
737 .unwrap();
738 stub_tty(&state);
739
740 let (child_pid, _) = Subshell::new(|env, _job_control| {
741 Box::pin(async { env.exit_status = ExitStatus(123) })
742 })
743 .start(&mut parent_env)
744 .await
745 .unwrap();
746
747 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
748 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
749
750 let state = state.borrow();
751 let child_process = &state.processes[&child_pid];
752 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
753 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
754 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
755 })
756 }
757
758 #[test]
759 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
760 in_virtual_system(|mut parent_env, state| async move {
761 parent_env.options.set(Interactive, On);
762 parent_env.options.set(Monitor, On);
763 parent_env
764 .traps
765 .enable_internal_dispositions_for_stoppers(&parent_env.system)
766 .await
767 .unwrap();
768 stub_tty(&state);
769
770 let (child_pid, _) = Subshell::new(|env, _job_control| {
771 Box::pin(async { env.exit_status = ExitStatus(123) })
772 })
773 .job_control(JobControl::Background)
774 .start(&mut parent_env)
775 .await
776 .unwrap();
777
778 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
779 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
780
781 let state = state.borrow();
782 let child_process = &state.processes[&child_pid];
783 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
784 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
785 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
786 })
787 }
788
789 #[test]
790 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
791 in_virtual_system(|mut parent_env, state| async move {
792 parent_env.options.set(Interactive, On);
793 stub_tty(&state);
794
795 let (child_pid, _) = Subshell::new(|env, _job_control| {
796 Box::pin(async { env.exit_status = ExitStatus(123) })
797 })
798 .start(&mut parent_env)
799 .await
800 .unwrap();
801
802 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
803 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
804
805 let state = state.borrow();
806 let child_process = &state.processes[&child_pid];
807 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
808 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
809 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
810 })
811 }
812
813 #[test]
814 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
815 {
816 in_virtual_system(|mut parent_env, state| async move {
817 parent_env.options.set(Monitor, On);
818 stub_tty(&state);
819
820 let (child_pid, _) = Subshell::new(|env, _job_control| {
821 Box::pin(async { env.exit_status = ExitStatus(123) })
822 })
823 .start(&mut parent_env)
824 .await
825 .unwrap();
826
827 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
828 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
829
830 let state = state.borrow();
831 let child_process = &state.processes[&child_pid];
832 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
833 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
834 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
835 })
836 }
837}