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                && let Ok(()) = env.system.setpgid(ME, ME)
177            {
178                match job_control {
179                    JobControl::Background => (),
180                    JobControl::Foreground => {
181                        if let Some(tty) = tty {
182                            let pgid = env.system.getpgrp();
183                            tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
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    /// [`handle_job_status`](crate::job::handle_job_status) is a convenient
248    /// helper for doing so.
249    pub async fn start_and_wait<S, F>(
250        // Why take `self` by value? See the comment in `start`.
251        self,
252        env: &mut Env<S>,
253        task: F,
254    ) -> Result<(Pid, ProcessResult), Errno>
255    where
256        S: BlockSignals
257            + Close
258            + Dup
259            + Exit
260            + Fork
261            + GetPid
262            + Open
263            + RunBlocking
264            + RunUnblocking
265            + SendSignal
266            + SetPgid
267            + SetRlimit
268            + SignalSystem
269            + TcSetPgrp
270            + Wait
271            + WaitForSignals
272            + 'static,
273        F: AsyncFnOnce(&mut Env<S>, Option<JobControl>) + 'static,
274    {
275        let (pid, job_control) = self.start(env, task).await?;
276        let result = loop {
277            let result = env.wait_for_subshell_to_halt(pid).await?.1;
278            if !result.is_stopped() || job_control.is_some() {
279                break result;
280            }
281        };
282
283        if job_control == Some(JobControl::Foreground)
284            && let Some(tty) = env.tty
285        {
286            tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
287                .await
288                .ok();
289        }
290
291        Ok((pid, result))
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::job::{Job, ProcessState};
299    use crate::option::Option::{Interactive, Monitor};
300    use crate::option::State::On;
301    use crate::semantics::ExitStatus;
302    use crate::source::Location;
303    use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
304    use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
305    use crate::system::{Concurrent, Disposition, Sigset as _};
306    use crate::test_helper::in_virtual_system;
307    use crate::trap::Action;
308    use assert_matches::assert_matches;
309    use futures_executor::LocalPool;
310    use std::cell::Cell;
311    use std::cell::RefCell;
312    use std::rc::Rc;
313
314    fn stub_tty(state: &RefCell<SystemState>) {
315        state
316            .borrow_mut()
317            .file_system
318            .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
319            .unwrap();
320    }
321
322    #[test]
323    fn start_returns_child_process_id() {
324        in_virtual_system(|mut env, _state| async move {
325            let parent_pid = env.main_pid;
326            let child_pid = Rc::new(Cell::new(None));
327            let child_pid_2 = Rc::clone(&child_pid);
328            let result = Config::new()
329                .start(
330                    &mut env,
331                    async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
332                        child_pid_2.set(Some(env.system.getpid()));
333                        assert_eq!(env.system.getppid(), parent_pid);
334                    },
335                )
336                .await
337                .unwrap()
338                .0;
339            env.wait_for_subshell(result).await.unwrap();
340            assert_eq!(Some(result), child_pid.get());
341        });
342    }
343
344    #[test]
345    fn start_failing() {
346        let mut executor = LocalPool::new();
347        let env = &mut Env::new_virtual();
348        let result = executor.run_until(
349            Config::new().start(env, async |_env: &mut Env<_>, _job_control| {
350                unreachable!("subshell not expected to run")
351            }),
352        );
353        assert_eq!(result, Err(Errno::ENOSYS));
354    }
355
356    #[test]
357    fn stack_frame_in_subshell() {
358        in_virtual_system(|mut env, _state| async move {
359            let pid = Config::new()
360                .start(
361                    &mut env,
362                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
363                        assert_eq!(env.stack[..], [Frame::Subshell])
364                    },
365                )
366                .await
367                .unwrap()
368                .0;
369            assert_eq!(env.stack[..], []);
370
371            env.wait_for_subshell(pid).await.unwrap();
372        });
373    }
374
375    #[test]
376    fn jobs_disowned_in_subshell() {
377        in_virtual_system(|mut env, _state| async move {
378            let index = env.jobs.insert(Job::new(Pid(123)));
379            let pid = Config::new()
380                .start(
381                    &mut env,
382                    async move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
383                        assert!(!env.jobs[index].is_owned)
384                    },
385                )
386                .await
387                .unwrap()
388                .0;
389            env.wait_for_subshell(pid).await.unwrap();
390
391            assert!(env.jobs[index].is_owned);
392        });
393    }
394
395    #[test]
396    fn trap_reset_in_subshell() {
397        in_virtual_system(|mut env, _state| async move {
398            env.traps
399                .set_action(
400                    &env.system,
401                    SIGCHLD,
402                    Action::Command("echo foo".into()),
403                    Location::dummy(""),
404                    false,
405                )
406                .await
407                .unwrap();
408            let pid = Config::new()
409                .start(
410                    &mut env,
411                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
412                        let (current, parent) = env.traps.get_state(SIGCHLD);
413                        assert_eq!(current.unwrap().action, Action::Default);
414                        assert_matches!(
415                            &parent.unwrap().action,
416                            Action::Command(body) => assert_eq!(&**body, "echo foo")
417                        );
418                    },
419                )
420                .await
421                .unwrap()
422                .0;
423            env.wait_for_subshell(pid).await.unwrap();
424        });
425    }
426
427    #[test]
428    fn subshell_with_no_job_control() {
429        in_virtual_system(|mut parent_env, state| async move {
430            parent_env.options.set(Monitor, On);
431
432            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
433            let state_2 = Rc::clone(&state);
434            let (child_pid, job_control) = Config::new()
435                .start(
436                    &mut parent_env,
437                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
438                        let child_pid = child_env.system.getpid();
439                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
440                        assert_eq!(state_2.borrow().foreground, None);
441                        assert_eq!(job_control, None);
442                    },
443                )
444                .await
445                .unwrap();
446            assert_eq!(job_control, None);
447            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
448            assert_eq!(state.borrow().foreground, None);
449
450            parent_env.wait_for_subshell(child_pid).await.unwrap();
451            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
452            assert_eq!(state.borrow().foreground, None);
453        });
454    }
455
456    #[test]
457    fn subshell_in_background() {
458        in_virtual_system(|mut parent_env, state| async move {
459            parent_env.options.set(Monitor, On);
460
461            let state_2 = Rc::clone(&state);
462            let config = Config {
463                job_control: Some(JobControl::Background),
464                ..Config::new()
465            };
466            let (child_pid, job_control) = config
467                .start(
468                    &mut parent_env,
469                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
470                        let child_pid = child_env.system.getpid();
471                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
472                        assert_eq!(state_2.borrow().foreground, None);
473                        assert_eq!(job_control, Some(JobControl::Background));
474                    },
475                )
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) = Config::foreground()
496                .start(
497                    &mut parent_env,
498                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
499                        let child_pid = child_env.system.getpid();
500                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
501                        assert_eq!(state_2.borrow().foreground, Some(child_pid));
502                        assert_eq!(job_control, Some(JobControl::Foreground));
503                    },
504                )
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 _ = Config::foreground()
525                .start(
526                    &mut parent_env,
527                    async move |_: &mut Env<Rc<Concurrent<VirtualSystem>>>, _| (),
528                )
529                .await
530                .unwrap();
531            assert_matches!(parent_env.tty, Some(_));
532        });
533    }
534
535    #[test]
536    fn job_control_without_tty() {
537        // When /dev/tty is not available, the shell cannot bring the subshell to
538        // the foreground. The subshell should still be in a new process group.
539        // This is the behavior required by POSIX.
540        in_virtual_system(async |mut parent_env, state| {
541            parent_env.options.set(Monitor, On);
542
543            let state_2 = Rc::clone(&state);
544            let (child_pid, job_control) = Config::foreground()
545                .start(
546                    &mut parent_env,
547                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
548                        let child_pid = child_env.system.getpid();
549                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
550                        assert_eq!(job_control, Some(JobControl::Foreground));
551                    },
552                )
553                .await
554                .unwrap();
555            assert_eq!(job_control, Some(JobControl::Foreground));
556            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
557
558            parent_env.wait_for_subshell(child_pid).await.unwrap();
559            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
560        })
561    }
562
563    #[test]
564    fn no_job_control_with_option_disabled() {
565        in_virtual_system(|mut parent_env, state| async move {
566            stub_tty(&state);
567
568            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
569            let state_2 = Rc::clone(&state);
570            let (child_pid, job_control) = Config::foreground()
571                .start(
572                    &mut parent_env,
573                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
574                                _job_control| {
575                        let child_pid = child_env.system.getpid();
576                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
577                        assert_eq!(state_2.borrow().foreground, None);
578                    },
579                )
580                .await
581                .unwrap();
582            assert_eq!(job_control, None);
583            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
584            assert_eq!(state.borrow().foreground, None);
585
586            parent_env.wait_for_subshell(child_pid).await.unwrap();
587            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
588            assert_eq!(state.borrow().foreground, None);
589        });
590    }
591
592    #[test]
593    fn no_job_control_for_nested_subshell() {
594        in_virtual_system(|mut parent_env, state| async move {
595            let mut parent_env = parent_env.push_frame(Frame::Subshell);
596            parent_env.options.set(Monitor, On);
597            stub_tty(&state);
598
599            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
600            let state_2 = Rc::clone(&state);
601            let (child_pid, job_control) = Config::foreground()
602                .start(
603                    &mut parent_env,
604                    async move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
605                                _job_control| {
606                        let child_pid = child_env.system.getpid();
607                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
608                        assert_eq!(state_2.borrow().foreground, None);
609                    },
610                )
611                .await
612                .unwrap();
613            assert_eq!(job_control, None);
614            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
615            assert_eq!(state.borrow().foreground, None);
616
617            parent_env.wait_for_subshell(child_pid).await.unwrap();
618            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
619            assert_eq!(state.borrow().foreground, None);
620        });
621    }
622
623    #[test]
624    fn wait_without_job_control() {
625        in_virtual_system(|mut env, _state| async move {
626            let (_pid, process_result) = Config::new()
627                .start_and_wait(
628                    &mut env,
629                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
630                        env.exit_status = ExitStatus(42)
631                    },
632                )
633                .await
634                .unwrap();
635            assert_eq!(process_result, ProcessResult::exited(42));
636        });
637    }
638
639    #[test]
640    fn wait_for_foreground_job_to_exit() {
641        in_virtual_system(|mut env, state| async move {
642            env.options.set(Monitor, On);
643            stub_tty(&state);
644
645            let (_pid, process_result) = Config::foreground()
646                .start_and_wait(
647                    &mut env,
648                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
649                        env.exit_status = ExitStatus(123)
650                    },
651                )
652                .await
653                .unwrap();
654            assert_eq!(process_result, ProcessResult::exited(123));
655            assert_eq!(state.borrow().foreground, Some(env.main_pgid));
656        });
657    }
658
659    // TODO wait_for_foreground_job_to_be_signaled
660    // TODO wait_for_foreground_job_to_be_stopped
661
662    #[test]
663    fn sigint_sigquit_not_ignored_by_default() {
664        in_virtual_system(|mut parent_env, state| async move {
665            let (child_pid, _) = Config {
666                job_control: Some(JobControl::Background),
667                ..Config::new()
668            }
669            .start(
670                &mut parent_env,
671                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
672                    env.exit_status = ExitStatus(123)
673                },
674            )
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, _) = Config {
690                job_control: Some(JobControl::Background),
691                ignores_sigint_sigquit: true,
692            }
693            .start(
694                &mut parent_env,
695                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
696                    env.exit_status = ExitStatus(123)
697                },
698            )
699            .await
700            .unwrap();
701
702            parent_env
703                .system
704                .kill(child_pid, Some(SIGINT))
705                .await
706                .unwrap();
707            parent_env
708                .system
709                .kill(child_pid, Some(SIGQUIT))
710                .await
711                .unwrap();
712
713            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
714            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
715
716            let state = state.borrow();
717            let parent_process = &state.processes[&parent_env.main_pid];
718            assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
719            assert_eq!(
720                parent_process.blocked_signals().contains(SIGQUIT),
721                Ok(false)
722            );
723            let child_process = &state.processes[&child_pid];
724            assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
725            assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
726        })
727    }
728
729    #[test]
730    fn sigint_sigquit_not_ignored_if_job_controlled() {
731        in_virtual_system(|mut parent_env, state| async move {
732            parent_env.options.set(Monitor, On);
733            stub_tty(&state);
734
735            let (child_pid, _) = Config {
736                job_control: Some(JobControl::Background),
737                ignores_sigint_sigquit: true,
738            }
739            .start(
740                &mut parent_env,
741                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
742                    env.exit_status = ExitStatus(123)
743                },
744            )
745            .await
746            .unwrap();
747            parent_env.wait_for_subshell(child_pid).await.unwrap();
748
749            let state = state.borrow();
750            let process = &state.processes[&child_pid];
751            assert_eq!(process.disposition(SIGINT), Disposition::Default);
752            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
753        })
754    }
755
756    #[test]
757    fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
758     {
759        in_virtual_system(|mut parent_env, state| async move {
760            parent_env.options.set(Interactive, On);
761            parent_env.options.set(Monitor, On);
762            parent_env
763                .traps
764                .enable_internal_dispositions_for_stoppers(&parent_env.system)
765                .await
766                .unwrap();
767            stub_tty(&state);
768
769            let (child_pid, _) = Config::new()
770                .start(
771                    &mut parent_env,
772                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
773                        env.exit_status = ExitStatus(123)
774                    },
775                )
776                .await
777                .unwrap();
778
779            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
780            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
781
782            let state = state.borrow();
783            let child_process = &state.processes[&child_pid];
784            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
785            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
786            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
787        })
788    }
789
790    #[test]
791    fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
792        in_virtual_system(|mut parent_env, state| async move {
793            parent_env.options.set(Interactive, On);
794            parent_env.options.set(Monitor, On);
795            parent_env
796                .traps
797                .enable_internal_dispositions_for_stoppers(&parent_env.system)
798                .await
799                .unwrap();
800            stub_tty(&state);
801
802            let (child_pid, _) = Config {
803                job_control: Some(JobControl::Background),
804                ..Config::new()
805            }
806            .start(
807                &mut parent_env,
808                async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
809                    env.exit_status = ExitStatus(123)
810                },
811            )
812            .await
813            .unwrap();
814
815            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
816            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
817
818            let state = state.borrow();
819            let child_process = &state.processes[&child_pid];
820            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
821            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
822            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
823        })
824    }
825
826    #[test]
827    fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
828        in_virtual_system(|mut parent_env, state| async move {
829            parent_env.options.set(Interactive, On);
830            stub_tty(&state);
831
832            let (child_pid, _) = Config::new()
833                .start(
834                    &mut parent_env,
835                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
836                        env.exit_status = ExitStatus(123)
837                    },
838                )
839                .await
840                .unwrap();
841
842            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
843            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
844
845            let state = state.borrow();
846            let child_process = &state.processes[&child_pid];
847            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
848            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
849            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
850        })
851    }
852
853    #[test]
854    fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
855     {
856        in_virtual_system(|mut parent_env, state| async move {
857            parent_env.options.set(Monitor, On);
858            stub_tty(&state);
859
860            let (child_pid, _) = Config::new()
861                .start(
862                    &mut parent_env,
863                    async |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
864                        env.exit_status = ExitStatus(123)
865                    },
866                )
867                .await
868                .unwrap();
869
870            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
871            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
872
873            let state = state.borrow();
874            let child_process = &state.processes[&child_pid];
875            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
876            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
877            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
878        })
879    }
880}