Skip to main content

yash_env/subshell/
config.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2026 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//! Subshell creation via [`Config`]
18
19use super::BlockSignals;
20use super::JobControl;
21use crate::Env;
22use crate::job::{Pid, ProcessResult, RunBlocking, RunUnblocking, tcsetpgrp_with_block};
23use crate::semantics::exit_or_raise;
24use crate::stack::Frame;
25use crate::system::concurrency::WaitForSignals;
26use crate::system::resource::SetRlimit;
27use crate::system::{
28    Close, Dup, Errno, Exit, Fork, GetPid, Open, SendSignal, SetPgid, TcSetPgrp, Wait,
29};
30use crate::trap::SignalSystem;
31
32/// Configuration for subshell creation
33///
34/// This struct configures how a subshell is created. An instance of `Config`
35/// can be created with [`new`](Self::new) or [`foreground`](Self::foreground),
36/// and modified by setting the fields. To start a subshell, call
37/// [`start`](Self::start) or [`start_and_wait`](Self::start_and_wait).
38#[derive(Debug, Default, Clone)]
39#[non_exhaustive]
40pub struct Config {
41    /// Specifies disposition of the subshell with respect to job control.
42    ///
43    /// If this value is `None` (which is the default), the subshell runs in
44    /// the same process group as the parent process. If it is `Some(_)`, the
45    /// subshell becomes a new process group leader. For
46    /// `Some(JobControl::Foreground)`, it also brings itself to the foreground.
47    ///
48    /// This parameter is ignored if the shell is not
49    /// [controlling jobs](Env::controls_jobs) when starting the subshell. You
50    /// can tell the actual job control status of the subshell by checking the
51    /// second return value of [`start`](Self::start) in the parent environment
52    /// and the second argument passed to the task in the subshell environment.
53    ///
54    /// If the parent process is a job-controlling interactive shell, but the
55    /// subshell is not job-controlled, the subshell's signal dispositions for
56    /// `SIGTSTP`, `SIGTTIN`, and `SIGTTOU` are set to `Ignore`. This is to
57    /// prevent the subshell from being stopped by a job-stopping signal. Were
58    /// the subshell stopped, you could never resume it since it is not
59    /// job-controlled.
60    pub job_control: Option<JobControl>,
61
62    /// If `true`, the subshell ignores `SIGINT` and `SIGQUIT`.
63    ///
64    /// This parameter is for implementing the POSIX requirement that
65    /// asynchronous and-or lists ignore `SIGINT` and `SIGQUIT` if job control
66    /// is disabled. The value is passed to
67    /// [`TrapSet::enter_subshell`](crate::trap::TrapSet::enter_subshell) to
68    /// modify the signal dispositions for `SIGINT` and `SIGQUIT` in the
69    /// subshell.
70    ///
71    /// This parameter has no effect if the subshell is job-controlled (see
72    /// [`job_control`](Self::job_control)). The default value is `false`.
73    pub ignores_sigint_sigquit: bool,
74}
75
76impl Config {
77    /// Creates a new `Config` with default settings.
78    #[must_use]
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// Creates a new `Config` with foreground job control.
84    ///
85    /// This is a convenient function to create a `Config` for a subshell that
86    /// should run in the foreground if job control is active. The returned
87    /// `Config` has [`job_control`](Self::job_control) set to
88    /// `Some(JobControl::Foreground)`, and the other fields set to their
89    /// default values.
90    #[must_use]
91    pub fn foreground() -> Self {
92        Self {
93            job_control: Some(JobControl::Foreground),
94            ..Self::default()
95        }
96    }
97
98    /// Starts the subshell.
99    ///
100    /// This function creates a new child process that runs the task provided as
101    /// an argument.
102    ///
103    /// Although this function is `async`, it does not wait for the child to
104    /// finish, which means the parent and child processes will run
105    /// concurrently. To wait for the child to change state, call
106    /// [`Env::wait_for_subshell`], [`Env::wait_for_subshell_to_halt`], or
107    /// [`Env::wait_for_subshell_to_finish`]. If job control is active, you may
108    /// want to add the process ID to [`Env::jobs`] before waiting. To start the
109    /// subshell and wait for it to finish at once, use
110    /// [`start_and_wait`](Self::start_and_wait).
111    ///
112    /// If you set [`job_control`](Self::job_control) to
113    /// `Some(JobControl::Foreground)`, this function opens [`Env::tty`] by
114    /// calling [`Env::get_tty`]. The `tty` is used to change the foreground job
115    /// to the new subshell. However, `job_control` is effective only when the
116    /// shell is [controlling jobs](Env::controls_jobs).
117    ///
118    /// If the subshell started successfully, the return value is a pair of the
119    /// child process ID and the actual job control status. Otherwise, it
120    /// indicates the error.
121    pub async fn start<S, F>(
122        // This method may be declared to take `self` by reference, but doing so
123        // would make the returned future capture `self` by reference, which
124        // demands the caller to keep the `Config` alive until the future is
125        // dropped. To allow code like below, we take `self` by value and
126        // implement `Clone` for `Config`.
127        //   let future = Config::new().start(...);
128        //   let result = future.await;
129        self,
130        env: &mut Env<S>,
131        task: F,
132    ) -> Result<(Pid, Option<JobControl>), Errno>
133    where
134        S: BlockSignals
135            + Close
136            + Dup
137            + Exit
138            + Fork
139            + GetPid
140            + Open
141            + RunBlocking
142            + RunUnblocking
143            + SendSignal
144            + SetPgid
145            + SetRlimit
146            + SignalSystem
147            + TcSetPgrp
148            + 'static,
149        F: AsyncFnOnce(&mut Env<S>, Option<JobControl>) + 'static,
150    {
151        // Do some preparation before starting a child process
152        let job_control = env.controls_jobs().then_some(self.job_control).flatten();
153        let tty = match job_control {
154            None | Some(JobControl::Background) => None,
155            // Open the tty in the parent process so we can reuse the FD for other jobs
156            Some(JobControl::Foreground) => env.get_tty().await.ok(),
157        };
158
159        let ignore_sigint_sigquit = self.ignores_sigint_sigquit && job_control.is_none();
160        let original_mask = if ignore_sigint_sigquit {
161            // Block SIGINT and SIGQUIT before forking the child process to
162            // prevent the child from being killed by those signals until the
163            // child starts ignoring them.
164            Some(env.system.block_sigint_sigquit().await?)
165        } else {
166            None
167        };
168        let keep_internal_dispositions_for_stoppers = job_control.is_none();
169
170        // Define the child process task
171        const ME: Pid = Pid(0);
172        let child_task = move |mut child_env: Env<S>, ()| async move {
173            let env = &mut *child_env.push_frame(Frame::Subshell);
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                                tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
183                            }
184                        }
185                    }
186                }
187            }
188            env.jobs.disown_all();
189
190            env.traps
191                .enter_subshell(
192                    &env.system,
193                    ignore_sigint_sigquit,
194                    keep_internal_dispositions_for_stoppers,
195                )
196                .await;
197
198            task(env, job_control).await;
199            exit_or_raise(&env.system, env.exit_status).await
200        };
201
202        // Start the child
203        let (result, ()) = env.run_in_child_process((), child_task);
204
205        // Restore the original signal mask in the parent process. Need to do
206        // this before returning the error if the child process creation failed,
207        // to avoid leaving the parent process with an unexpected signal mask.
208        if let Some(mask) = original_mask {
209            env.system.restore_sigmask(mask).await.ok();
210        }
211
212        let child_pid = result?;
213
214        // The finishing
215        if job_control.is_some() {
216            // We should setpgid not only in the child but also in the parent to
217            // make sure the child is in a new process group before the parent
218            // returns from the start function.
219            let _ = env.system.setpgid(child_pid, ME);
220
221            // We don't tcsetpgrp in the parent. It would mess up the child
222            // which may have started another shell doing its own job control.
223        }
224
225        Ok((child_pid, job_control))
226    }
227
228    /// Starts the subshell and waits for it to finish.
229    ///
230    /// This function [starts](Self::start) `self` and
231    /// [waits](Env::wait_for_subshell) for it to finish. This function returns
232    /// when the subshell process exits or is killed by a signal. If the
233    /// subshell is job-controlled, the function also returns when the job is
234    /// suspended.
235    ///
236    /// If the subshell started successfully, the return value is the process ID
237    /// and the process result of the subshell. If there was an error starting
238    /// the subshell, this function returns the error.
239    ///
240    /// If you set [`job_control`](Self::job_control) to
241    /// `JobControl::Foreground` and job control is effective as per
242    /// [`Env::controls_jobs`], this function makes the shell the foreground job
243    /// after the subshell terminated or suspended.
244    ///
245    /// When a job-controlled subshell suspends, this function does not add it
246    /// to `env.jobs`. You have to do it for yourself if necessary.
247    pub async fn start_and_wait<S, F>(
248        // Why take `self` by value? See the comment in `start`.
249        self,
250        env: &mut Env<S>,
251        task: F,
252    ) -> Result<(Pid, ProcessResult), Errno>
253    where
254        S: BlockSignals
255            + Close
256            + Dup
257            + Exit
258            + Fork
259            + GetPid
260            + Open
261            + RunBlocking
262            + RunUnblocking
263            + SendSignal
264            + SetPgid
265            + SetRlimit
266            + SignalSystem
267            + TcSetPgrp
268            + Wait
269            + WaitForSignals
270            + 'static,
271        F: AsyncFnOnce(&mut Env<S>, Option<JobControl>) + 'static,
272    {
273        let (pid, job_control) = self.start(env, task).await?;
274        let result = loop {
275            let result = env.wait_for_subshell_to_halt(pid).await?.1;
276            if !result.is_stopped() || job_control.is_some() {
277                break result;
278            }
279        };
280
281        if job_control == Some(JobControl::Foreground) {
282            if let Some(tty) = env.tty {
283                tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
284                    .await
285                    .ok();
286            }
287        }
288
289        Ok((pid, result))
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::job::{Job, ProcessState};
297    use crate::option::Option::{Interactive, Monitor};
298    use crate::option::State::On;
299    use crate::semantics::ExitStatus;
300    use crate::source::Location;
301    use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
302    use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
303    use crate::system::{Concurrent, Disposition, Sigset as _};
304    use crate::test_helper::in_virtual_system;
305    use crate::trap::Action;
306    use assert_matches::assert_matches;
307    use futures_executor::LocalPool;
308    use std::cell::Cell;
309    use std::cell::RefCell;
310    use std::rc::Rc;
311
312    fn stub_tty(state: &RefCell<SystemState>) {
313        state
314            .borrow_mut()
315            .file_system
316            .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
317            .unwrap();
318    }
319
320    #[test]
321    fn start_returns_child_process_id() {
322        in_virtual_system(|mut env, _state| async move {
323            let parent_pid = env.main_pid;
324            let child_pid = Rc::new(Cell::new(None));
325            let child_pid_2 = Rc::clone(&child_pid);
326            let result = Config::new()
327                .start(
328                    &mut env,
329                    async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
330                        child_pid_2.set(Some(env.system.getpid()));
331                        assert_eq!(env.system.getppid(), parent_pid);
332                    },
333                )
334                .await
335                .unwrap()
336                .0;
337            env.wait_for_subshell(result).await.unwrap();
338            assert_eq!(Some(result), child_pid.get());
339        });
340    }
341
342    #[test]
343    fn start_failing() {
344        let mut executor = LocalPool::new();
345        let env = &mut Env::new_virtual();
346        let result = executor.run_until(
347            Config::new().start(env, async |_env: &mut Env<_>, _job_control| {
348                unreachable!("subshell not expected to run")
349            }),
350        );
351        assert_eq!(result, Err(Errno::ENOSYS));
352    }
353
354    #[test]
355    fn stack_frame_in_subshell() {
356        in_virtual_system(|mut env, _state| async move {
357            let pid = Config::new()
358                .start(
359                    &mut env,
360                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
361                        assert_eq!(env.stack[..], [Frame::Subshell])
362                    },
363                )
364                .await
365                .unwrap()
366                .0;
367            assert_eq!(env.stack[..], []);
368
369            env.wait_for_subshell(pid).await.unwrap();
370        });
371    }
372
373    #[test]
374    fn jobs_disowned_in_subshell() {
375        in_virtual_system(|mut env, _state| async move {
376            let index = env.jobs.add(Job::new(Pid(123)));
377            let pid = Config::new()
378                .start(
379                    &mut env,
380                    async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
381                        assert!(!env.jobs[index].is_owned)
382                    },
383                )
384                .await
385                .unwrap()
386                .0;
387            env.wait_for_subshell(pid).await.unwrap();
388
389            assert!(env.jobs[index].is_owned);
390        });
391    }
392
393    #[test]
394    fn trap_reset_in_subshell() {
395        in_virtual_system(|mut env, _state| async move {
396            env.traps
397                .set_action(
398                    &env.system,
399                    SIGCHLD,
400                    Action::Command("echo foo".into()),
401                    Location::dummy(""),
402                    false,
403                )
404                .await
405                .unwrap();
406            let pid = Config::new()
407                .start(
408                    &mut env,
409                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
410                        let (current, parent) = env.traps.get_state(SIGCHLD);
411                        assert_eq!(current.unwrap().action, Action::Default);
412                        assert_matches!(
413                            &parent.unwrap().action,
414                            Action::Command(body) => assert_eq!(&**body, "echo foo")
415                        );
416                    },
417                )
418                .await
419                .unwrap()
420                .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) = Config::new()
433                .start(
434                    &mut parent_env,
435                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
436                        let child_pid = child_env.system.getpid();
437                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
438                        assert_eq!(state_2.borrow().foreground, None);
439                        assert_eq!(job_control, None);
440                    },
441                )
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 config = Config {
461                job_control: Some(JobControl::Background),
462                ..Config::new()
463            };
464            let (child_pid, job_control) = config
465                .start(
466                    &mut parent_env,
467                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
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                .await
475                .unwrap();
476            assert_eq!(job_control, Some(JobControl::Background));
477            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
478            assert_eq!(state.borrow().foreground, None);
479
480            parent_env.wait_for_subshell(child_pid).await.unwrap();
481            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
482            assert_eq!(state.borrow().foreground, None);
483        });
484    }
485
486    #[test]
487    fn subshell_in_foreground() {
488        in_virtual_system(|mut parent_env, state| async move {
489            parent_env.options.set(Monitor, On);
490            stub_tty(&state);
491
492            let state_2 = Rc::clone(&state);
493            let (child_pid, job_control) = Config::foreground()
494                .start(
495                    &mut parent_env,
496                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
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                .await
504                .unwrap();
505            assert_eq!(job_control, Some(JobControl::Foreground));
506            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
507            // The child may not yet have become the foreground job.
508            // assert_eq!(state.borrow().foreground, Some(child_pid));
509
510            parent_env.wait_for_subshell(child_pid).await.unwrap();
511            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
512            assert_eq!(state.borrow().foreground, Some(child_pid));
513        });
514    }
515
516    #[test]
517    fn tty_after_starting_foreground_subshell() {
518        in_virtual_system(|mut parent_env, state| async move {
519            parent_env.options.set(Monitor, On);
520            stub_tty(&state);
521
522            let _ = Config::foreground()
523                .start(
524                    &mut parent_env,
525                    async move |_: &mut Env<Rc<Concurrent<VirtualSystem>>>, _| (),
526                )
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) = Config::foreground()
543                .start(
544                    &mut parent_env,
545                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
546                        let child_pid = child_env.system.getpid();
547                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
548                        assert_eq!(job_control, Some(JobControl::Foreground));
549                    },
550                )
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) = Config::foreground()
569                .start(
570                    &mut parent_env,
571                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
572                                _job_control| {
573                        let child_pid = child_env.system.getpid();
574                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
575                        assert_eq!(state_2.borrow().foreground, None);
576                    },
577                )
578                .await
579                .unwrap();
580            assert_eq!(job_control, None);
581            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
582            assert_eq!(state.borrow().foreground, None);
583
584            parent_env.wait_for_subshell(child_pid).await.unwrap();
585            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
586            assert_eq!(state.borrow().foreground, None);
587        });
588    }
589
590    #[test]
591    fn no_job_control_for_nested_subshell() {
592        in_virtual_system(|mut parent_env, state| async move {
593            let mut parent_env = parent_env.push_frame(Frame::Subshell);
594            parent_env.options.set(Monitor, On);
595            stub_tty(&state);
596
597            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
598            let state_2 = Rc::clone(&state);
599            let (child_pid, job_control) = Config::foreground()
600                .start(
601                    &mut parent_env,
602                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
603                                _job_control| {
604                        let child_pid = child_env.system.getpid();
605                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
606                        assert_eq!(state_2.borrow().foreground, None);
607                    },
608                )
609                .await
610                .unwrap();
611            assert_eq!(job_control, None);
612            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
613            assert_eq!(state.borrow().foreground, None);
614
615            parent_env.wait_for_subshell(child_pid).await.unwrap();
616            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
617            assert_eq!(state.borrow().foreground, None);
618        });
619    }
620
621    #[test]
622    fn wait_without_job_control() {
623        in_virtual_system(|mut env, _state| async move {
624            let (_pid, process_result) = Config::new()
625                .start_and_wait(
626                    &mut env,
627                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
628                        env.exit_status = ExitStatus(42)
629                    },
630                )
631                .await
632                .unwrap();
633            assert_eq!(process_result, ProcessResult::exited(42));
634        });
635    }
636
637    #[test]
638    fn wait_for_foreground_job_to_exit() {
639        in_virtual_system(|mut env, state| async move {
640            env.options.set(Monitor, On);
641            stub_tty(&state);
642
643            let (_pid, process_result) = Config::foreground()
644                .start_and_wait(
645                    &mut env,
646                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
647                        env.exit_status = ExitStatus(123)
648                    },
649                )
650                .await
651                .unwrap();
652            assert_eq!(process_result, ProcessResult::exited(123));
653            assert_eq!(state.borrow().foreground, Some(env.main_pgid));
654        });
655    }
656
657    // TODO wait_for_foreground_job_to_be_signaled
658    // TODO wait_for_foreground_job_to_be_stopped
659
660    #[test]
661    fn sigint_sigquit_not_ignored_by_default() {
662        in_virtual_system(|mut parent_env, state| async move {
663            let (child_pid, _) = Config {
664                job_control: Some(JobControl::Background),
665                ..Config::new()
666            }
667            .start(
668                &mut parent_env,
669                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
670                    env.exit_status = ExitStatus(123)
671                },
672            )
673            .await
674            .unwrap();
675            parent_env.wait_for_subshell(child_pid).await.unwrap();
676
677            let state = state.borrow();
678            let process = &state.processes[&child_pid];
679            assert_eq!(process.disposition(SIGINT), Disposition::Default);
680            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
681        })
682    }
683
684    #[test]
685    fn sigint_sigquit_ignored_in_uncontrolled_job() {
686        in_virtual_system(|mut parent_env, state| async move {
687            let (child_pid, _) = Config {
688                job_control: Some(JobControl::Background),
689                ignores_sigint_sigquit: true,
690            }
691            .start(
692                &mut parent_env,
693                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
694                    env.exit_status = ExitStatus(123)
695                },
696            )
697            .await
698            .unwrap();
699
700            parent_env
701                .system
702                .kill(child_pid, Some(SIGINT))
703                .await
704                .unwrap();
705            parent_env
706                .system
707                .kill(child_pid, Some(SIGQUIT))
708                .await
709                .unwrap();
710
711            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
712            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
713
714            let state = state.borrow();
715            let parent_process = &state.processes[&parent_env.main_pid];
716            assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
717            assert_eq!(
718                parent_process.blocked_signals().contains(SIGQUIT),
719                Ok(false)
720            );
721            let child_process = &state.processes[&child_pid];
722            assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
723            assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
724        })
725    }
726
727    #[test]
728    fn sigint_sigquit_not_ignored_if_job_controlled() {
729        in_virtual_system(|mut parent_env, state| async move {
730            parent_env.options.set(Monitor, On);
731            stub_tty(&state);
732
733            let (child_pid, _) = Config {
734                job_control: Some(JobControl::Background),
735                ignores_sigint_sigquit: true,
736            }
737            .start(
738                &mut parent_env,
739                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
740                    env.exit_status = ExitStatus(123)
741                },
742            )
743            .await
744            .unwrap();
745            parent_env.wait_for_subshell(child_pid).await.unwrap();
746
747            let state = state.borrow();
748            let process = &state.processes[&child_pid];
749            assert_eq!(process.disposition(SIGINT), Disposition::Default);
750            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
751        })
752    }
753
754    #[test]
755    fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
756     {
757        in_virtual_system(|mut parent_env, state| async move {
758            parent_env.options.set(Interactive, On);
759            parent_env.options.set(Monitor, On);
760            parent_env
761                .traps
762                .enable_internal_dispositions_for_stoppers(&parent_env.system)
763                .await
764                .unwrap();
765            stub_tty(&state);
766
767            let (child_pid, _) = Config::new()
768                .start(
769                    &mut parent_env,
770                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
771                        env.exit_status = ExitStatus(123)
772                    },
773                )
774                .await
775                .unwrap();
776
777            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
778            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
779
780            let state = state.borrow();
781            let child_process = &state.processes[&child_pid];
782            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
783            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
784            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
785        })
786    }
787
788    #[test]
789    fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
790        in_virtual_system(|mut parent_env, state| async move {
791            parent_env.options.set(Interactive, On);
792            parent_env.options.set(Monitor, On);
793            parent_env
794                .traps
795                .enable_internal_dispositions_for_stoppers(&parent_env.system)
796                .await
797                .unwrap();
798            stub_tty(&state);
799
800            let (child_pid, _) = Config {
801                job_control: Some(JobControl::Background),
802                ..Config::new()
803            }
804            .start(
805                &mut parent_env,
806                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
807                    env.exit_status = ExitStatus(123)
808                },
809            )
810            .await
811            .unwrap();
812
813            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
814            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
815
816            let state = state.borrow();
817            let child_process = &state.processes[&child_pid];
818            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
819            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
820            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
821        })
822    }
823
824    #[test]
825    fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
826        in_virtual_system(|mut parent_env, state| async move {
827            parent_env.options.set(Interactive, On);
828            stub_tty(&state);
829
830            let (child_pid, _) = Config::new()
831                .start(
832                    &mut parent_env,
833                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
834                        env.exit_status = ExitStatus(123)
835                    },
836                )
837                .await
838                .unwrap();
839
840            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
841            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
842
843            let state = state.borrow();
844            let child_process = &state.processes[&child_pid];
845            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
846            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
847            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
848        })
849    }
850
851    #[test]
852    fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
853     {
854        in_virtual_system(|mut parent_env, state| async move {
855            parent_env.options.set(Monitor, On);
856            stub_tty(&state);
857
858            let (child_pid, _) = Config::new()
859                .start(
860                    &mut parent_env,
861                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
862                        env.exit_status = ExitStatus(123)
863                    },
864                )
865                .await
866                .unwrap();
867
868            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
869            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
870
871            let state = state.borrow();
872            let child_process = &state.processes[&child_pid];
873            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
874            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
875            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
876        })
877    }
878}