Skip to main content

yash_env/
subshell.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Utility for starting subshells
18//!
19//! This module defines [`Subshell`], a builder for starting a subshell. It is
20//! [constructed](Subshell::new) with a function you want to run in a subshell.
21//! After configuring the builder with some options, you can
22//! [start](Subshell::start) the subshell.
23//!
24//! [`Subshell`] is implemented as a wrapper around [`Fork::new_child_process`].
25//! You should prefer `Subshell` for the purpose of creating a subshell because
26//! it helps to arrange the child process properly.
27
28use 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/// Job state of a newly created subshell
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum JobControl {
58    /// The subshell becomes the foreground process group.
59    Foreground,
60    /// The subshell becomes a background process group.
61    Background,
62}
63
64/// Subshell builder
65///
66/// See the [module documentation](self) for details.
67#[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    // TODO Revisit to simplify this function type when impl Future is allowed in return type
102{
103    /// Creates a new subshell builder with a task.
104    ///
105    /// The task will run in a subshell after it is started. The task takes two
106    /// arguments:
107    ///
108    /// 1. The environment in which the subshell runs, and
109    /// 2. Job control status for the subshell.
110    ///
111    /// If the task returns an `Err(Divert::...)`, it is handled as follows:
112    ///
113    /// - `Interrupt` and `Exit` with `Some(exit_status)` override the exit
114    ///   status in `Env`.
115    /// - Other `Divert` values are ignored.
116    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    /// Specifies disposition of the subshell with respect to job control.
126    ///
127    /// If the argument is `None` (which is the default), the subshell runs in
128    /// the same process group as the parent process. If it is `Some(_)`, the
129    /// subshell becomes a new process group. For `JobControl::Foreground`, it
130    /// also brings itself to the foreground.
131    ///
132    /// This parameter is ignored if the shell is not [controlling
133    /// jobs](Env::controls_jobs) when starting the subshell. You can tell the
134    /// actual job control status of the subshell by the second return value of
135    /// [`start`](Self::start) in the parent environment and the second argument
136    /// passed to the task in the subshell environment.
137    ///
138    /// If the parent process is a job-controlling interactive shell, but the
139    /// subshell is not job-controlled, the subshell's signal dispositions for
140    /// SIGTSTP, SIGTTIN, and SIGTTOU are set to `Ignore`. This is to prevent
141    /// the subshell from being stopped by a job-stopping signal. Were the
142    /// subshell stopped, you could never resume it since it is not
143    /// job-controlled.
144    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    /// Makes the subshell ignore SIGINT and SIGQUIT.
150    ///
151    /// If `ignore` is true and the subshell is not job-controlled, the subshell
152    /// sets its signal dispositions for SIGINT and SIGQUIT to `Ignore`.
153    ///
154    /// The default is `false`.
155    pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
156        self.ignores_sigint_sigquit = ignore;
157        self
158    }
159
160    /// Starts the subshell.
161    ///
162    /// This function creates a new child process that runs the task contained
163    /// in this builder.
164    ///
165    /// Although this function is `async`, it does not wait for the child to
166    /// finish, which means the parent and child processes will run
167    /// concurrently. To wait for the child to finish, you need to call
168    /// [`Env::wait_for_subshell`] or [`Env::wait_for_subshell_to_finish`]. If
169    /// job control is active, you may want to add the process ID to `env.jobs`
170    /// before waiting.
171    ///
172    /// If you set [`job_control`](Self::job_control) to
173    /// `JobControl::Foreground`, this function opens `env.tty` by calling
174    /// [`Env::get_tty`]. The `tty` is used to change the foreground job to the
175    /// new subshell. However, `job_control` is effective only when the shell is
176    /// [controlling jobs](Env::controls_jobs).
177    ///
178    /// If the subshell started successfully, the return value is a pair of the
179    /// child process ID and the actual job control. Otherwise, it indicates the
180    /// error.
181    pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
182        // Do some preparation before starting a child process
183        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            // Open the tty in the parent process so we can reuse the FD for other jobs
187            Some(JobControl::Foreground) => env.get_tty().ok(),
188        };
189        // Block SIGINT and SIGQUIT before forking the child process to prevent
190        // the child from being killed by those signals until the child starts
191        // ignoring them.
192        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        // Define the child process task
199        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        // Start the child
232        let child = mask_guard.env.system.new_child_process()?;
233        let child_pid = child(mask_guard.env, task);
234
235        // The finishing
236        if job_control.is_some() {
237            // We should setpgid not only in the child but also in the parent to
238            // make sure the child is in a new process group before the parent
239            // returns from the start function.
240            let _ = mask_guard.env.system.setpgid(child_pid, ME);
241
242            // We don't tcsetpgrp in the parent. It would mess up the child
243            // which may have started another shell doing its own job control.
244        }
245
246        Ok((child_pid, job_control))
247    }
248
249    /// Starts the subshell and waits for it to finish.
250    ///
251    /// This function [starts](Self::start) `self` and
252    /// [waits](Env::wait_for_subshell) for it to finish. This function returns
253    /// when the subshell process exits or is killed by a signal. If the
254    /// subshell is job-controlled, the function also returns when the job is
255    /// suspended.
256    ///
257    /// If the subshell started successfully, the return value is the process ID
258    /// and the process result of the subshell. If there was an error starting
259    /// the subshell, this function returns the error.
260    ///
261    /// If you set [`job_control`](Self::job_control) to
262    /// `JobControl::Foreground` and job control is effective as per
263    /// [`Env::controls_jobs`], this function makes the shell the foreground job
264    /// after the subshell terminated or suspended.
265    ///
266    /// When a job-controlled subshell suspends, this function does not add it
267    /// to `env.jobs`. You have to do it for yourself if necessary.
268    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/// Guard object for temporarily blocking signals
293///
294/// This object blocks SIGINT and SIGQUIT and remembers the previous signal
295/// blocking mask, which is restored when the object is dropped.
296#[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            // The child may not yet have become the foreground job.
528            // assert_eq!(state.borrow().foreground, Some(child_pid));
529
530            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        // When /dev/tty is not available, the shell cannot bring the subshell to
554        // the foreground. The subshell should still be in a new process group.
555        // This is the behavior required by POSIX.
556        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    // TODO wait_for_foreground_job_to_be_signaled
665    // TODO wait_for_foreground_job_to_be_stopped
666
667    #[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}