1use crate::Env;
30use crate::job::Pid;
31use crate::job::ProcessResult;
32use crate::signal;
33use crate::stack::Frame;
34use crate::system::ChildProcessTask;
35use crate::system::Errno;
36use crate::system::SigmaskOp;
37use crate::system::System;
38use crate::system::SystemEx as _;
39use std::pin::Pin;
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum JobControl {
44 Foreground,
46 Background,
48}
49
50#[must_use = "a subshell is not started unless you call `Subshell::start`"]
54pub struct Subshell<F> {
55 task: F,
56 job_control: Option<JobControl>,
57 ignores_sigint_sigquit: bool,
58}
59
60impl<F> std::fmt::Debug for Subshell<F> {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("Subshell").finish_non_exhaustive()
63 }
64}
65
66impl<F> Subshell<F>
67where
68 F: for<'a> FnOnce(&'a mut Env, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
69 + 'static,
70 {
72 pub fn new(task: F) -> Self {
86 Subshell {
87 task,
88 job_control: None,
89 ignores_sigint_sigquit: false,
90 }
91 }
92
93 pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
113 self.job_control = job_control.into();
114 self
115 }
116
117 pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
124 self.ignores_sigint_sigquit = ignore;
125 self
126 }
127
128 pub async fn start(self, env: &mut Env) -> Result<(Pid, Option<JobControl>), Errno> {
150 let job_control = env.controls_jobs().then_some(self.job_control).flatten();
152 let tty = match job_control {
153 None | Some(JobControl::Background) => None,
154 Some(JobControl::Foreground) => env.get_tty().ok(),
156 };
157 let mut mask_guard = MaskGuard::new(env);
161 let ignore_sigint_sigquit = self.ignores_sigint_sigquit
162 && job_control.is_none()
163 && mask_guard.block_sigint_sigquit();
164 let keep_internal_dispositions_for_stoppers = job_control.is_none();
165
166 const ME: Pid = Pid(0);
168 let task: ChildProcessTask = Box::new(move |env| {
169 Box::pin(async move {
170 let mut env = env.push_frame(Frame::Subshell);
171 let env = &mut *env;
172
173 if let Some(job_control) = job_control {
174 if let Ok(()) = env.system.setpgid(ME, ME) {
175 match job_control {
176 JobControl::Background => (),
177 JobControl::Foreground => {
178 if let Some(tty) = tty {
179 let pgid = env.system.getpgrp();
180 let _ = env.system.tcsetpgrp_with_block(tty, pgid);
181 }
182 }
183 }
184 }
185 }
186 env.jobs.disown_all();
187
188 env.traps.enter_subshell(
189 &mut env.system,
190 ignore_sigint_sigquit,
191 keep_internal_dispositions_for_stoppers,
192 );
193
194 (self.task)(env, job_control).await;
195 env.system.exit_or_raise(env.exit_status).await
196 })
197 });
198
199 let child = mask_guard.env.system.new_child_process()?;
201 let child_pid = child(mask_guard.env, task);
202
203 if job_control.is_some() {
205 let _ = mask_guard.env.system.setpgid(child_pid, ME);
209
210 }
213
214 Ok((child_pid, job_control))
215 }
216
217 pub async fn start_and_wait(self, env: &mut Env) -> Result<(Pid, ProcessResult), Errno> {
237 let (pid, job_control) = self.start(env).await?;
238 let result = loop {
239 let result = env.wait_for_subshell_to_halt(pid).await?.1;
240 if !result.is_stopped() || job_control.is_some() {
241 break result;
242 }
243 };
244
245 if job_control == Some(JobControl::Foreground) {
246 if let Some(tty) = env.tty {
247 env.system.tcsetpgrp_with_block(tty, env.main_pgid).ok();
248 }
249 }
250
251 Ok((pid, result))
252 }
253}
254
255#[derive(Debug)]
260struct MaskGuard<'a> {
261 env: &'a mut Env,
262 old_mask: Option<Vec<signal::Number>>,
263}
264
265impl<'a> MaskGuard<'a> {
266 fn new(env: &'a mut Env) -> Self {
267 let old_mask = None;
268 Self { env, old_mask }
269 }
270
271 fn block_sigint_sigquit(&mut self) -> bool {
272 assert_eq!(self.old_mask, None);
273
274 let Some(sigint) = self.env.system.signal_number_from_name(signal::Name::Int) else {
275 return false;
276 };
277 let Some(sigquit) = self.env.system.signal_number_from_name(signal::Name::Quit) else {
278 return false;
279 };
280
281 let mut old_mask = Vec::new();
282
283 let success = self
284 .env
285 .system
286 .sigmask(
287 Some((SigmaskOp::Add, &[sigint, sigquit])),
288 Some(&mut old_mask),
289 )
290 .is_ok();
291 if success {
292 self.old_mask = Some(old_mask);
293 }
294 success
295 }
296}
297
298impl Drop for MaskGuard<'_> {
299 fn drop(&mut self) {
300 if let Some(old_mask) = &self.old_mask {
301 self.env
302 .system
303 .sigmask(Some((SigmaskOp::Set, old_mask)), None)
304 .ok();
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::job::{Job, ProcessState};
313 use crate::option::Option::{Interactive, Monitor};
314 use crate::option::State::On;
315 use crate::semantics::ExitStatus;
316 use crate::source::Location;
317 use crate::system::Disposition;
318 use crate::system::Errno;
319 use crate::system::r#virtual::Inode;
320 use crate::system::r#virtual::SystemState;
321 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
322 use crate::tests::in_virtual_system;
323 use crate::trap::Action;
324 use assert_matches::assert_matches;
325 use futures_executor::LocalPool;
326 use std::cell::Cell;
327 use std::cell::RefCell;
328 use std::rc::Rc;
329
330 fn stub_tty(state: &RefCell<SystemState>) {
331 state
332 .borrow_mut()
333 .file_system
334 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
335 .unwrap();
336 }
337
338 #[test]
339 fn subshell_start_returns_child_process_id() {
340 in_virtual_system(|mut env, _state| async move {
341 let parent_pid = env.main_pid;
342 let child_pid = Rc::new(Cell::new(None));
343 let child_pid_2 = Rc::clone(&child_pid);
344 let subshell = Subshell::new(move |env, _job_control| {
345 Box::pin(async move {
346 child_pid_2.set(Some(env.system.getpid()));
347 assert_eq!(env.system.getppid(), parent_pid);
348 })
349 });
350 let result = subshell.start(&mut env).await.unwrap().0;
351 env.wait_for_subshell(result).await.unwrap();
352 assert_eq!(Some(result), child_pid.get());
353 });
354 }
355
356 #[test]
357 fn subshell_start_failing() {
358 let mut executor = LocalPool::new();
359 let env = &mut Env::new_virtual();
360 let subshell =
361 Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
362 let result = executor.run_until(subshell.start(env));
363 assert_eq!(result, Err(Errno::ENOSYS));
364 }
365
366 #[test]
367 fn stack_frame_in_subshell() {
368 in_virtual_system(|mut env, _state| async move {
369 let subshell = Subshell::new(|env, _job_control| {
370 Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
371 });
372 let pid = subshell.start(&mut env).await.unwrap().0;
373 assert_eq!(env.stack[..], []);
374
375 env.wait_for_subshell(pid).await.unwrap();
376 });
377 }
378
379 #[test]
380 fn jobs_disowned_in_subshell() {
381 in_virtual_system(|mut env, _state| async move {
382 let index = env.jobs.add(Job::new(Pid(123)));
383 let subshell = Subshell::new(move |env, _job_control| {
384 Box::pin(async move { assert!(!env.jobs[index].is_owned) })
385 });
386 let pid = subshell.start(&mut env).await.unwrap().0;
387 env.wait_for_subshell(pid).await.unwrap();
388
389 assert!(env.jobs[index].is_owned);
390 });
391 }
392
393 #[test]
394 fn trap_reset_in_subshell() {
395 in_virtual_system(|mut env, _state| async move {
396 env.traps
397 .set_action(
398 &mut env.system,
399 SIGCHLD,
400 Action::Command("echo foo".into()),
401 Location::dummy(""),
402 false,
403 )
404 .unwrap();
405 let subshell = Subshell::new(|env, _job_control| {
406 Box::pin(async {
407 let (current, parent) = env.traps.get_state(SIGCHLD);
408 assert_eq!(current.unwrap().action, Action::Default);
409 assert_matches!(
410 &parent.unwrap().action,
411 Action::Command(body) => assert_eq!(&**body, "echo foo")
412 );
413 })
414 });
415 let pid = subshell.start(&mut env).await.unwrap().0;
416 env.wait_for_subshell(pid).await.unwrap();
417 });
418 }
419
420 #[test]
421 fn subshell_with_no_job_control() {
422 in_virtual_system(|mut parent_env, state| async move {
423 parent_env.options.set(Monitor, On);
424
425 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
426 let state_2 = Rc::clone(&state);
427 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
428 Box::pin(async move {
429 let child_pid = child_env.system.getpid();
430 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
431 assert_eq!(state_2.borrow().foreground, None);
432 assert_eq!(job_control, None);
433 })
434 })
435 .job_control(None)
436 .start(&mut parent_env)
437 .await
438 .unwrap();
439 assert_eq!(job_control, None);
440 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
441 assert_eq!(state.borrow().foreground, None);
442
443 parent_env.wait_for_subshell(child_pid).await.unwrap();
444 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
445 assert_eq!(state.borrow().foreground, None);
446 });
447 }
448
449 #[test]
450 fn subshell_in_background() {
451 in_virtual_system(|mut parent_env, state| async move {
452 parent_env.options.set(Monitor, On);
453
454 let state_2 = Rc::clone(&state);
455 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
456 Box::pin(async move {
457 let child_pid = child_env.system.getpid();
458 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
459 assert_eq!(state_2.borrow().foreground, None);
460 assert_eq!(job_control, Some(JobControl::Background));
461 })
462 })
463 .job_control(JobControl::Background)
464 .start(&mut parent_env)
465 .await
466 .unwrap();
467 assert_eq!(job_control, Some(JobControl::Background));
468 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
469 assert_eq!(state.borrow().foreground, None);
470
471 parent_env.wait_for_subshell(child_pid).await.unwrap();
472 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
473 assert_eq!(state.borrow().foreground, None);
474 });
475 }
476
477 #[test]
478 fn subshell_in_foreground() {
479 in_virtual_system(|mut parent_env, state| async move {
480 parent_env.options.set(Monitor, On);
481 stub_tty(&state);
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, Some(child_pid));
489 assert_eq!(job_control, Some(JobControl::Foreground));
490 })
491 })
492 .job_control(JobControl::Foreground)
493 .start(&mut parent_env)
494 .await
495 .unwrap();
496 assert_eq!(job_control, Some(JobControl::Foreground));
497 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
498 parent_env.wait_for_subshell(child_pid).await.unwrap();
502 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
503 assert_eq!(state.borrow().foreground, Some(child_pid));
504 });
505 }
506
507 #[test]
508 fn tty_after_starting_foreground_subshell() {
509 in_virtual_system(|mut parent_env, state| async move {
510 parent_env.options.set(Monitor, On);
511 stub_tty(&state);
512
513 let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
514 .job_control(JobControl::Foreground)
515 .start(&mut parent_env)
516 .await
517 .unwrap();
518 assert_matches!(parent_env.tty, Some(_));
519 });
520 }
521
522 #[test]
523 fn job_control_without_tty() {
524 in_virtual_system(async |mut parent_env, state| {
528 parent_env.options.set(Monitor, On);
529
530 let state_2 = Rc::clone(&state);
531 let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
532 Box::pin(async move {
533 let child_pid = child_env.system.getpid();
534 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
535 assert_eq!(job_control, Some(JobControl::Foreground));
536 })
537 })
538 .job_control(JobControl::Foreground)
539 .start(&mut parent_env)
540 .await
541 .unwrap();
542 assert_eq!(job_control, Some(JobControl::Foreground));
543 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
544
545 parent_env.wait_for_subshell(child_pid).await.unwrap();
546 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
547 })
548 }
549
550 #[test]
551 fn no_job_control_with_option_disabled() {
552 in_virtual_system(|mut parent_env, state| async move {
553 stub_tty(&state);
554
555 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
556 let state_2 = Rc::clone(&state);
557 let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
558 Box::pin(async move {
559 let child_pid = child_env.system.getpid();
560 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
561 assert_eq!(state_2.borrow().foreground, None);
562 })
563 })
564 .job_control(JobControl::Foreground)
565 .start(&mut parent_env)
566 .await
567 .unwrap();
568 assert_eq!(job_control, None);
569 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
570 assert_eq!(state.borrow().foreground, None);
571
572 parent_env.wait_for_subshell(child_pid).await.unwrap();
573 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
574 assert_eq!(state.borrow().foreground, None);
575 });
576 }
577
578 #[test]
579 fn no_job_control_for_nested_subshell() {
580 in_virtual_system(|mut parent_env, state| async move {
581 let mut parent_env = parent_env.push_frame(Frame::Subshell);
582 parent_env.options.set(Monitor, On);
583 stub_tty(&state);
584
585 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
586 let state_2 = Rc::clone(&state);
587 let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
588 Box::pin(async move {
589 let child_pid = child_env.system.getpid();
590 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
591 assert_eq!(state_2.borrow().foreground, None);
592 })
593 })
594 .job_control(JobControl::Foreground)
595 .start(&mut parent_env)
596 .await
597 .unwrap();
598 assert_eq!(job_control, None);
599 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
600 assert_eq!(state.borrow().foreground, None);
601
602 parent_env.wait_for_subshell(child_pid).await.unwrap();
603 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
604 assert_eq!(state.borrow().foreground, None);
605 });
606 }
607
608 #[test]
609 fn wait_without_job_control() {
610 in_virtual_system(|mut env, _state| async move {
611 let subshell = Subshell::new(|env, _job_control| {
612 Box::pin(async { env.exit_status = ExitStatus(42) })
613 });
614 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
615 assert_eq!(process_result, ProcessResult::exited(42));
616 });
617 }
618
619 #[test]
620 fn wait_for_foreground_job_to_exit() {
621 in_virtual_system(|mut env, state| async move {
622 env.options.set(Monitor, On);
623 stub_tty(&state);
624
625 let subshell = Subshell::new(|env, _job_control| {
626 Box::pin(async { env.exit_status = ExitStatus(123) })
627 })
628 .job_control(JobControl::Foreground);
629 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
630 assert_eq!(process_result, ProcessResult::exited(123));
631 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
632 });
633 }
634
635 #[test]
639 fn sigint_sigquit_not_ignored_by_default() {
640 in_virtual_system(|mut parent_env, state| async move {
641 let (child_pid, _) = Subshell::new(|env, _job_control| {
642 Box::pin(async { env.exit_status = ExitStatus(123) })
643 })
644 .job_control(JobControl::Background)
645 .start(&mut parent_env)
646 .await
647 .unwrap();
648 parent_env.wait_for_subshell(child_pid).await.unwrap();
649
650 let state = state.borrow();
651 let process = &state.processes[&child_pid];
652 assert_eq!(process.disposition(SIGINT), Disposition::Default);
653 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
654 })
655 }
656
657 #[test]
658 fn sigint_sigquit_ignored_in_uncontrolled_job() {
659 in_virtual_system(|mut parent_env, state| async move {
660 let (child_pid, _) = Subshell::new(|env, _job_control| {
661 Box::pin(async { env.exit_status = ExitStatus(123) })
662 })
663 .job_control(JobControl::Background)
664 .ignore_sigint_sigquit(true)
665 .start(&mut parent_env)
666 .await
667 .unwrap();
668
669 parent_env
670 .system
671 .kill(child_pid, Some(SIGINT))
672 .await
673 .unwrap();
674 parent_env
675 .system
676 .kill(child_pid, Some(SIGQUIT))
677 .await
678 .unwrap();
679
680 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
681 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
682
683 let state = state.borrow();
684 let parent_process = &state.processes[&parent_env.main_pid];
685 assert!(!parent_process.blocked_signals().contains(&SIGINT));
686 assert!(!parent_process.blocked_signals().contains(&SIGQUIT));
687 let child_process = &state.processes[&child_pid];
688 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
689 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
690 })
691 }
692
693 #[test]
694 fn sigint_sigquit_not_ignored_if_job_controlled() {
695 in_virtual_system(|mut parent_env, state| async move {
696 parent_env.options.set(Monitor, On);
697 stub_tty(&state);
698
699 let (child_pid, _) = Subshell::new(|env, _job_control| {
700 Box::pin(async { env.exit_status = ExitStatus(123) })
701 })
702 .job_control(JobControl::Background)
703 .ignore_sigint_sigquit(true)
704 .start(&mut parent_env)
705 .await
706 .unwrap();
707 parent_env.wait_for_subshell(child_pid).await.unwrap();
708
709 let state = state.borrow();
710 let process = &state.processes[&child_pid];
711 assert_eq!(process.disposition(SIGINT), Disposition::Default);
712 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
713 })
714 }
715
716 #[test]
717 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
718 {
719 in_virtual_system(|mut parent_env, state| async move {
720 parent_env.options.set(Interactive, On);
721 parent_env.options.set(Monitor, On);
722 parent_env
723 .traps
724 .enable_internal_dispositions_for_stoppers(&mut parent_env.system)
725 .unwrap();
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 .start(&mut parent_env)
732 .await
733 .unwrap();
734
735 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
736 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
737
738 let state = state.borrow();
739 let child_process = &state.processes[&child_pid];
740 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
741 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
742 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
743 })
744 }
745
746 #[test]
747 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
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 .job_control(JobControl::Background)
761 .start(&mut parent_env)
762 .await
763 .unwrap();
764
765 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
766 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
767
768 let state = state.borrow();
769 let child_process = &state.processes[&child_pid];
770 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
771 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
772 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
773 })
774 }
775
776 #[test]
777 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
778 in_virtual_system(|mut parent_env, state| async move {
779 parent_env.options.set(Interactive, On);
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 .start(&mut parent_env)
786 .await
787 .unwrap();
788
789 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
790 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
791
792 let state = state.borrow();
793 let child_process = &state.processes[&child_pid];
794 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
795 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
796 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
797 })
798 }
799
800 #[test]
801 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
802 {
803 in_virtual_system(|mut parent_env, state| async move {
804 parent_env.options.set(Monitor, 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}