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