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").finish_non_exhaustive()
78    }
79}
80
81impl<S, F> Subshell<S, F>
82where
83    S: Close
84        + Dup
85        + Exit
86        + Fork
87        + GetPid
88        + Open
89        + SendSignal
90        + SetPgid
91        + SetRlimit
92        + Sigaction
93        + Sigmask
94        + Signals
95        + TcSetPgrp,
96    F: for<'a> FnOnce(&'a mut Env<S>, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
97        + 'static,
98    // TODO Revisit to simplify this function type when impl Future is allowed in return type
99{
100    /// Creates a new subshell builder with a task.
101    ///
102    /// The task will run in a subshell after it is started. The task takes two
103    /// arguments:
104    ///
105    /// 1. The environment in which the subshell runs, and
106    /// 2. Job control status for the subshell.
107    ///
108    /// If the task returns an `Err(Divert::...)`, it is handled as follows:
109    ///
110    /// - `Interrupt` and `Exit` with `Some(exit_status)` override the exit
111    ///   status in `Env`.
112    /// - Other `Divert` values are ignored.
113    pub fn new(task: F) -> Self {
114        Subshell {
115            task,
116            job_control: None,
117            ignores_sigint_sigquit: false,
118            phantom_data: PhantomData,
119        }
120    }
121
122    /// Specifies disposition of the subshell with respect to job control.
123    ///
124    /// If the argument is `None` (which is the default), the subshell runs in
125    /// the same process group as the parent process. If it is `Some(_)`, the
126    /// subshell becomes a new process group. For `JobControl::Foreground`, it
127    /// also brings itself to the foreground.
128    ///
129    /// This parameter is ignored if the shell is not [controlling
130    /// jobs](Env::controls_jobs) when starting the subshell. You can tell the
131    /// actual job control status of the subshell by the second return value of
132    /// [`start`](Self::start) in the parent environment and the second argument
133    /// passed to the task in the subshell environment.
134    ///
135    /// If the parent process is a job-controlling interactive shell, but the
136    /// subshell is not job-controlled, the subshell's signal dispositions for
137    /// SIGTSTP, SIGTTIN, and SIGTTOU are set to `Ignore`. This is to prevent
138    /// the subshell from being stopped by a job-stopping signal. Were the
139    /// subshell stopped, you could never resume it since it is not
140    /// job-controlled.
141    pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
142        self.job_control = job_control.into();
143        self
144    }
145
146    /// Makes the subshell ignore SIGINT and SIGQUIT.
147    ///
148    /// If `ignore` is true and the subshell is not job-controlled, the subshell
149    /// sets its signal dispositions for SIGINT and SIGQUIT to `Ignore`.
150    ///
151    /// The default is `false`.
152    pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
153        self.ignores_sigint_sigquit = ignore;
154        self
155    }
156
157    /// Starts the subshell.
158    ///
159    /// This function creates a new child process that runs the task contained
160    /// in this builder.
161    ///
162    /// Although this function is `async`, it does not wait for the child to
163    /// finish, which means the parent and child processes will run
164    /// concurrently. To wait for the child to finish, you need to call
165    /// [`Env::wait_for_subshell`] or [`Env::wait_for_subshell_to_finish`]. If
166    /// job control is active, you may want to add the process ID to `env.jobs`
167    /// before waiting.
168    ///
169    /// If you set [`job_control`](Self::job_control) to
170    /// `JobControl::Foreground`, this function opens `env.tty` by calling
171    /// [`Env::get_tty`]. The `tty` is used to change the foreground job to the
172    /// new subshell. However, `job_control` is effective only when the shell is
173    /// [controlling jobs](Env::controls_jobs).
174    ///
175    /// If the subshell started successfully, the return value is a pair of the
176    /// child process ID and the actual job control. Otherwise, it indicates the
177    /// error.
178    pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
179        // Do some preparation before starting a child process
180        let job_control = env.controls_jobs().then_some(self.job_control).flatten();
181        let tty = match job_control {
182            None | Some(JobControl::Background) => None,
183            // Open the tty in the parent process so we can reuse the FD for other jobs
184            Some(JobControl::Foreground) => env.get_tty().ok(),
185        };
186        // Block SIGINT and SIGQUIT before forking the child process to prevent
187        // the child from being killed by those signals until the child starts
188        // ignoring them.
189        let mut mask_guard = MaskGuard::new(env);
190        let ignore_sigint_sigquit = self.ignores_sigint_sigquit
191            && job_control.is_none()
192            && mask_guard.block_sigint_sigquit();
193        let keep_internal_dispositions_for_stoppers = job_control.is_none();
194
195        // Define the child process task
196        const ME: Pid = Pid(0);
197        let task: ChildProcessTask<S> = Box::new(move |env| {
198            Box::pin(async move {
199                let mut env = env.push_frame(Frame::Subshell);
200                let env = &mut *env;
201
202                if let Some(job_control) = job_control {
203                    if let Ok(()) = env.system.setpgid(ME, ME) {
204                        match job_control {
205                            JobControl::Background => (),
206                            JobControl::Foreground => {
207                                if let Some(tty) = tty {
208                                    let pgid = env.system.getpgrp();
209                                    tcsetpgrp_with_block(&env.system, tty, pgid).await.ok();
210                                }
211                            }
212                        }
213                    }
214                }
215                env.jobs.disown_all();
216
217                env.traps.enter_subshell(
218                    &mut env.system,
219                    ignore_sigint_sigquit,
220                    keep_internal_dispositions_for_stoppers,
221                );
222
223                (self.task)(env, job_control).await;
224                exit_or_raise(&env.system, env.exit_status).await
225            })
226        });
227
228        // Start the child
229        let child = mask_guard.env.system.new_child_process()?;
230        let child_pid = child(mask_guard.env, task);
231
232        // The finishing
233        if job_control.is_some() {
234            // We should setpgid not only in the child but also in the parent to
235            // make sure the child is in a new process group before the parent
236            // returns from the start function.
237            let _ = mask_guard.env.system.setpgid(child_pid, ME);
238
239            // We don't tcsetpgrp in the parent. It would mess up the child
240            // which may have started another shell doing its own job control.
241        }
242
243        Ok((child_pid, job_control))
244    }
245
246    /// Starts the subshell and waits for it to finish.
247    ///
248    /// This function [starts](Self::start) `self` and
249    /// [waits](Env::wait_for_subshell) for it to finish. This function returns
250    /// when the subshell process exits or is killed by a signal. If the
251    /// subshell is job-controlled, the function also returns when the job is
252    /// suspended.
253    ///
254    /// If the subshell started successfully, the return value is the process ID
255    /// and the process result of the subshell. If there was an error starting
256    /// the subshell, this function returns the error.
257    ///
258    /// If you set [`job_control`](Self::job_control) to
259    /// `JobControl::Foreground` and job control is effective as per
260    /// [`Env::controls_jobs`], this function makes the shell the foreground job
261    /// after the subshell terminated or suspended.
262    ///
263    /// When a job-controlled subshell suspends, this function does not add it
264    /// to `env.jobs`. You have to do it for yourself if necessary.
265    pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
266    where
267        S: Wait,
268    {
269        let (pid, job_control) = self.start(env).await?;
270        let result = loop {
271            let result = env.wait_for_subshell_to_halt(pid).await?.1;
272            if !result.is_stopped() || job_control.is_some() {
273                break result;
274            }
275        };
276
277        if job_control == Some(JobControl::Foreground) {
278            if let Some(tty) = env.tty {
279                tcsetpgrp_with_block(&env.system, tty, env.main_pgid)
280                    .await
281                    .ok();
282            }
283        }
284
285        Ok((pid, result))
286    }
287}
288
289/// Guard object for temporarily blocking signals
290///
291/// This object blocks SIGINT and SIGQUIT and remembers the previous signal
292/// blocking mask, which is restored when the object is dropped.
293#[derive(Debug)]
294struct MaskGuard<'a, S: Signals + Sigmask> {
295    env: &'a mut Env<S>,
296    old_mask: Option<Vec<signal::Number>>,
297}
298
299impl<'a, S: Signals + Sigmask> MaskGuard<'a, S> {
300    fn new(env: &'a mut Env<S>) -> Self {
301        let old_mask = None;
302        Self { env, old_mask }
303    }
304
305    fn block_sigint_sigquit(&mut self) -> bool {
306        assert_eq!(self.old_mask, None);
307
308        let Some(sigint) = self.env.system.signal_number_from_name(signal::Name::Int) else {
309            return false;
310        };
311        let Some(sigquit) = self.env.system.signal_number_from_name(signal::Name::Quit) else {
312            return false;
313        };
314
315        let mut old_mask = Vec::new();
316
317        let success = self
318            .env
319            .system
320            .sigmask(
321                Some((SigmaskOp::Add, &[sigint, sigquit])),
322                Some(&mut old_mask),
323            )
324            .is_ok();
325        if success {
326            self.old_mask = Some(old_mask);
327        }
328        success
329    }
330}
331
332impl<S: Signals + Sigmask> Drop for MaskGuard<'_, S> {
333    fn drop(&mut self) {
334        if let Some(old_mask) = &self.old_mask {
335            self.env
336                .system
337                .sigmask(Some((SigmaskOp::Set, old_mask)), None)
338                .ok();
339        }
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use crate::job::{Job, ProcessState};
347    use crate::option::Option::{Interactive, Monitor};
348    use crate::option::State::On;
349    use crate::semantics::ExitStatus;
350    use crate::source::Location;
351    use crate::system::Disposition;
352    use crate::system::r#virtual::Inode;
353    use crate::system::r#virtual::SystemState;
354    use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
355    use crate::tests::in_virtual_system;
356    use crate::trap::Action;
357    use assert_matches::assert_matches;
358    use futures_executor::LocalPool;
359    use std::cell::Cell;
360    use std::cell::RefCell;
361    use std::rc::Rc;
362
363    fn stub_tty(state: &RefCell<SystemState>) {
364        state
365            .borrow_mut()
366            .file_system
367            .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
368            .unwrap();
369    }
370
371    #[test]
372    fn subshell_start_returns_child_process_id() {
373        in_virtual_system(|mut env, _state| async move {
374            let parent_pid = env.main_pid;
375            let child_pid = Rc::new(Cell::new(None));
376            let child_pid_2 = Rc::clone(&child_pid);
377            let subshell = Subshell::new(move |env, _job_control| {
378                Box::pin(async move {
379                    child_pid_2.set(Some(env.system.getpid()));
380                    assert_eq!(env.system.getppid(), parent_pid);
381                })
382            });
383            let result = subshell.start(&mut env).await.unwrap().0;
384            env.wait_for_subshell(result).await.unwrap();
385            assert_eq!(Some(result), child_pid.get());
386        });
387    }
388
389    #[test]
390    fn subshell_start_failing() {
391        let mut executor = LocalPool::new();
392        let env = &mut Env::new_virtual();
393        let subshell =
394            Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
395        let result = executor.run_until(subshell.start(env));
396        assert_eq!(result, Err(Errno::ENOSYS));
397    }
398
399    #[test]
400    fn stack_frame_in_subshell() {
401        in_virtual_system(|mut env, _state| async move {
402            let subshell = Subshell::new(|env, _job_control| {
403                Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
404            });
405            let pid = subshell.start(&mut env).await.unwrap().0;
406            assert_eq!(env.stack[..], []);
407
408            env.wait_for_subshell(pid).await.unwrap();
409        });
410    }
411
412    #[test]
413    fn jobs_disowned_in_subshell() {
414        in_virtual_system(|mut env, _state| async move {
415            let index = env.jobs.add(Job::new(Pid(123)));
416            let subshell = Subshell::new(move |env, _job_control| {
417                Box::pin(async move { assert!(!env.jobs[index].is_owned) })
418            });
419            let pid = subshell.start(&mut env).await.unwrap().0;
420            env.wait_for_subshell(pid).await.unwrap();
421
422            assert!(env.jobs[index].is_owned);
423        });
424    }
425
426    #[test]
427    fn trap_reset_in_subshell() {
428        in_virtual_system(|mut env, _state| async move {
429            env.traps
430                .set_action(
431                    &mut env.system,
432                    SIGCHLD,
433                    Action::Command("echo foo".into()),
434                    Location::dummy(""),
435                    false,
436                )
437                .unwrap();
438            let subshell = Subshell::new(|env, _job_control| {
439                Box::pin(async {
440                    let (current, parent) = env.traps.get_state(SIGCHLD);
441                    assert_eq!(current.unwrap().action, Action::Default);
442                    assert_matches!(
443                        &parent.unwrap().action,
444                        Action::Command(body) => assert_eq!(&**body, "echo foo")
445                    );
446                })
447            });
448            let pid = subshell.start(&mut env).await.unwrap().0;
449            env.wait_for_subshell(pid).await.unwrap();
450        });
451    }
452
453    #[test]
454    fn subshell_with_no_job_control() {
455        in_virtual_system(|mut parent_env, state| async move {
456            parent_env.options.set(Monitor, On);
457
458            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
459            let state_2 = Rc::clone(&state);
460            let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
461                Box::pin(async move {
462                    let child_pid = child_env.system.getpid();
463                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
464                    assert_eq!(state_2.borrow().foreground, None);
465                    assert_eq!(job_control, None);
466                })
467            })
468            .job_control(None)
469            .start(&mut parent_env)
470            .await
471            .unwrap();
472            assert_eq!(job_control, None);
473            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
474            assert_eq!(state.borrow().foreground, None);
475
476            parent_env.wait_for_subshell(child_pid).await.unwrap();
477            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
478            assert_eq!(state.borrow().foreground, None);
479        });
480    }
481
482    #[test]
483    fn subshell_in_background() {
484        in_virtual_system(|mut parent_env, state| async move {
485            parent_env.options.set(Monitor, On);
486
487            let state_2 = Rc::clone(&state);
488            let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
489                Box::pin(async move {
490                    let child_pid = child_env.system.getpid();
491                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
492                    assert_eq!(state_2.borrow().foreground, None);
493                    assert_eq!(job_control, Some(JobControl::Background));
494                })
495            })
496            .job_control(JobControl::Background)
497            .start(&mut parent_env)
498            .await
499            .unwrap();
500            assert_eq!(job_control, Some(JobControl::Background));
501            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
502            assert_eq!(state.borrow().foreground, None);
503
504            parent_env.wait_for_subshell(child_pid).await.unwrap();
505            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
506            assert_eq!(state.borrow().foreground, None);
507        });
508    }
509
510    #[test]
511    fn subshell_in_foreground() {
512        in_virtual_system(|mut parent_env, state| async move {
513            parent_env.options.set(Monitor, On);
514            stub_tty(&state);
515
516            let state_2 = Rc::clone(&state);
517            let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
518                Box::pin(async move {
519                    let child_pid = child_env.system.getpid();
520                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
521                    assert_eq!(state_2.borrow().foreground, Some(child_pid));
522                    assert_eq!(job_control, Some(JobControl::Foreground));
523                })
524            })
525            .job_control(JobControl::Foreground)
526            .start(&mut parent_env)
527            .await
528            .unwrap();
529            assert_eq!(job_control, Some(JobControl::Foreground));
530            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
531            // The child may not yet have become the foreground job.
532            // assert_eq!(state.borrow().foreground, Some(child_pid));
533
534            parent_env.wait_for_subshell(child_pid).await.unwrap();
535            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
536            assert_eq!(state.borrow().foreground, Some(child_pid));
537        });
538    }
539
540    #[test]
541    fn tty_after_starting_foreground_subshell() {
542        in_virtual_system(|mut parent_env, state| async move {
543            parent_env.options.set(Monitor, On);
544            stub_tty(&state);
545
546            let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
547                .job_control(JobControl::Foreground)
548                .start(&mut parent_env)
549                .await
550                .unwrap();
551            assert_matches!(parent_env.tty, Some(_));
552        });
553    }
554
555    #[test]
556    fn job_control_without_tty() {
557        // When /dev/tty is not available, the shell cannot bring the subshell to
558        // the foreground. The subshell should still be in a new process group.
559        // This is the behavior required by POSIX.
560        in_virtual_system(async |mut parent_env, state| {
561            parent_env.options.set(Monitor, On);
562
563            let state_2 = Rc::clone(&state);
564            let (child_pid, job_control) = Subshell::new(move |child_env, job_control| {
565                Box::pin(async move {
566                    let child_pid = child_env.system.getpid();
567                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
568                    assert_eq!(job_control, Some(JobControl::Foreground));
569                })
570            })
571            .job_control(JobControl::Foreground)
572            .start(&mut parent_env)
573            .await
574            .unwrap();
575            assert_eq!(job_control, Some(JobControl::Foreground));
576            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
577
578            parent_env.wait_for_subshell(child_pid).await.unwrap();
579            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
580        })
581    }
582
583    #[test]
584    fn no_job_control_with_option_disabled() {
585        in_virtual_system(|mut parent_env, state| async move {
586            stub_tty(&state);
587
588            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
589            let state_2 = Rc::clone(&state);
590            let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
591                Box::pin(async move {
592                    let child_pid = child_env.system.getpid();
593                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
594                    assert_eq!(state_2.borrow().foreground, None);
595                })
596            })
597            .job_control(JobControl::Foreground)
598            .start(&mut parent_env)
599            .await
600            .unwrap();
601            assert_eq!(job_control, None);
602            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
603            assert_eq!(state.borrow().foreground, None);
604
605            parent_env.wait_for_subshell(child_pid).await.unwrap();
606            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
607            assert_eq!(state.borrow().foreground, None);
608        });
609    }
610
611    #[test]
612    fn no_job_control_for_nested_subshell() {
613        in_virtual_system(|mut parent_env, state| async move {
614            let mut parent_env = parent_env.push_frame(Frame::Subshell);
615            parent_env.options.set(Monitor, On);
616            stub_tty(&state);
617
618            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
619            let state_2 = Rc::clone(&state);
620            let (child_pid, job_control) = Subshell::new(move |child_env, _job_control| {
621                Box::pin(async move {
622                    let child_pid = child_env.system.getpid();
623                    assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
624                    assert_eq!(state_2.borrow().foreground, None);
625                })
626            })
627            .job_control(JobControl::Foreground)
628            .start(&mut parent_env)
629            .await
630            .unwrap();
631            assert_eq!(job_control, None);
632            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
633            assert_eq!(state.borrow().foreground, None);
634
635            parent_env.wait_for_subshell(child_pid).await.unwrap();
636            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
637            assert_eq!(state.borrow().foreground, None);
638        });
639    }
640
641    #[test]
642    fn wait_without_job_control() {
643        in_virtual_system(|mut env, _state| async move {
644            let subshell = Subshell::new(|env, _job_control| {
645                Box::pin(async { env.exit_status = ExitStatus(42) })
646            });
647            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
648            assert_eq!(process_result, ProcessResult::exited(42));
649        });
650    }
651
652    #[test]
653    fn wait_for_foreground_job_to_exit() {
654        in_virtual_system(|mut env, state| async move {
655            env.options.set(Monitor, On);
656            stub_tty(&state);
657
658            let subshell = Subshell::new(|env, _job_control| {
659                Box::pin(async { env.exit_status = ExitStatus(123) })
660            })
661            .job_control(JobControl::Foreground);
662            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
663            assert_eq!(process_result, ProcessResult::exited(123));
664            assert_eq!(state.borrow().foreground, Some(env.main_pgid));
665        });
666    }
667
668    // TODO wait_for_foreground_job_to_be_signaled
669    // TODO wait_for_foreground_job_to_be_stopped
670
671    #[test]
672    fn sigint_sigquit_not_ignored_by_default() {
673        in_virtual_system(|mut parent_env, state| async move {
674            let (child_pid, _) = Subshell::new(|env, _job_control| {
675                Box::pin(async { env.exit_status = ExitStatus(123) })
676            })
677            .job_control(JobControl::Background)
678            .start(&mut parent_env)
679            .await
680            .unwrap();
681            parent_env.wait_for_subshell(child_pid).await.unwrap();
682
683            let state = state.borrow();
684            let process = &state.processes[&child_pid];
685            assert_eq!(process.disposition(SIGINT), Disposition::Default);
686            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
687        })
688    }
689
690    #[test]
691    fn sigint_sigquit_ignored_in_uncontrolled_job() {
692        in_virtual_system(|mut parent_env, state| async move {
693            let (child_pid, _) = Subshell::new(|env, _job_control| {
694                Box::pin(async { env.exit_status = ExitStatus(123) })
695            })
696            .job_control(JobControl::Background)
697            .ignore_sigint_sigquit(true)
698            .start(&mut parent_env)
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!(!parent_process.blocked_signals().contains(&SIGINT));
719            assert!(!parent_process.blocked_signals().contains(&SIGQUIT));
720            let child_process = &state.processes[&child_pid];
721            assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
722            assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
723        })
724    }
725
726    #[test]
727    fn sigint_sigquit_not_ignored_if_job_controlled() {
728        in_virtual_system(|mut parent_env, state| async move {
729            parent_env.options.set(Monitor, On);
730            stub_tty(&state);
731
732            let (child_pid, _) = Subshell::new(|env, _job_control| {
733                Box::pin(async { env.exit_status = ExitStatus(123) })
734            })
735            .job_control(JobControl::Background)
736            .ignore_sigint_sigquit(true)
737            .start(&mut parent_env)
738            .await
739            .unwrap();
740            parent_env.wait_for_subshell(child_pid).await.unwrap();
741
742            let state = state.borrow();
743            let process = &state.processes[&child_pid];
744            assert_eq!(process.disposition(SIGINT), Disposition::Default);
745            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
746        })
747    }
748
749    #[test]
750    fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
751     {
752        in_virtual_system(|mut parent_env, state| async move {
753            parent_env.options.set(Interactive, On);
754            parent_env.options.set(Monitor, On);
755            parent_env
756                .traps
757                .enable_internal_dispositions_for_stoppers(&mut parent_env.system)
758                .unwrap();
759            stub_tty(&state);
760
761            let (child_pid, _) = Subshell::new(|env, _job_control| {
762                Box::pin(async { env.exit_status = ExitStatus(123) })
763            })
764            .start(&mut parent_env)
765            .await
766            .unwrap();
767
768            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
769            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
770
771            let state = state.borrow();
772            let child_process = &state.processes[&child_pid];
773            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
774            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
775            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
776        })
777    }
778
779    #[test]
780    fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
781        in_virtual_system(|mut parent_env, state| async move {
782            parent_env.options.set(Interactive, On);
783            parent_env.options.set(Monitor, On);
784            parent_env
785                .traps
786                .enable_internal_dispositions_for_stoppers(&mut parent_env.system)
787                .unwrap();
788            stub_tty(&state);
789
790            let (child_pid, _) = Subshell::new(|env, _job_control| {
791                Box::pin(async { env.exit_status = ExitStatus(123) })
792            })
793            .job_control(JobControl::Background)
794            .start(&mut parent_env)
795            .await
796            .unwrap();
797
798            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
799            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
800
801            let state = state.borrow();
802            let child_process = &state.processes[&child_pid];
803            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
804            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
805            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
806        })
807    }
808
809    #[test]
810    fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
811        in_virtual_system(|mut parent_env, state| async move {
812            parent_env.options.set(Interactive, On);
813            stub_tty(&state);
814
815            let (child_pid, _) = Subshell::new(|env, _job_control| {
816                Box::pin(async { env.exit_status = ExitStatus(123) })
817            })
818            .start(&mut parent_env)
819            .await
820            .unwrap();
821
822            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
823            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
824
825            let state = state.borrow();
826            let child_process = &state.processes[&child_pid];
827            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
828            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
829            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
830        })
831    }
832
833    #[test]
834    fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
835     {
836        in_virtual_system(|mut parent_env, state| async move {
837            parent_env.options.set(Monitor, On);
838            stub_tty(&state);
839
840            let (child_pid, _) = Subshell::new(|env, _job_control| {
841                Box::pin(async { env.exit_status = ExitStatus(123) })
842            })
843            .start(&mut parent_env)
844            .await
845            .unwrap();
846
847            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
848            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
849
850            let state = state.borrow();
851            let child_process = &state.processes[&child_pid];
852            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
853            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
854            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
855        })
856    }
857}