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().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            // Block SIGINT and SIGQUIT before forking the child process to
193            // prevent the child from being killed by those signals until the
194            // child starts ignoring them.
195            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        // Define the child process task
202        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        // Start the child
237        let result = env.system.new_child_process().map(|child| child(env, task));
238
239        // Restore the original signal mask in the parent process. Need to do
240        // this before returning the error if the child process creation failed,
241        // to avoid leaving the parent process with an unexpected signal mask.
242        if let Some(mask) = original_mask {
243            restore_sigmask(&env.system, &mask).await.ok();
244        }
245
246        let child_pid = result?;
247
248        // The finishing
249        if job_control.is_some() {
250            // We should setpgid not only in the child but also in the parent to
251            // make sure the child is in a new process group before the parent
252            // returns from the start function.
253            let _ = env.system.setpgid(child_pid, ME);
254
255            // We don't tcsetpgrp in the parent. It would mess up the child
256            // which may have started another shell doing its own job control.
257        }
258
259        Ok((child_pid, job_control))
260    }
261
262    /// Starts the subshell and waits for it to finish.
263    ///
264    /// This function [starts](Self::start) `self` and
265    /// [waits](Env::wait_for_subshell) for it to finish. This function returns
266    /// when the subshell process exits or is killed by a signal. If the
267    /// subshell is job-controlled, the function also returns when the job is
268    /// suspended.
269    ///
270    /// If the subshell started successfully, the return value is the process ID
271    /// and the process result of the subshell. If there was an error starting
272    /// the subshell, this function returns the error.
273    ///
274    /// If you set [`job_control`](Self::job_control) to
275    /// `JobControl::Foreground` and job control is effective as per
276    /// [`Env::controls_jobs`], this function makes the shell the foreground job
277    /// after the subshell terminated or suspended.
278    ///
279    /// When a job-controlled subshell suspends, this function does not add it
280    /// to `env.jobs`. You have to do it for yourself if necessary.
281    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            // The child may not yet have become the foreground job.
510            // assert_eq!(state.borrow().foreground, Some(child_pid));
511
512            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        // When /dev/tty is not available, the shell cannot bring the subshell to
536        // the foreground. The subshell should still be in a new process group.
537        // This is the behavior required by POSIX.
538        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    // TODO wait_for_foreground_job_to_be_signaled
647    // TODO wait_for_foreground_job_to_be_stopped
648
649    #[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}