Skip to main content

yash_env/
subshell.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Utility for starting subshells
18//!
19//! This module defines [`Config`], a builder for starting a subshell. It is a
20//! collection of options for starting a subshell, and provides methods to start
21//! a subshell with the configured options. These methods are implemented as
22//! wrappers around [`Env::run_in_child_process`], which in turn uses
23//! [`Fork::run_in_child_process`]. You should prefer `Config` for the purpose
24//! of creating a subshell because it helps to arrange the child process
25//! properly.
26//!
27//! This module also defines a deprecated struct [`Subshell`] with a similar API
28//! to `Config` for backward compatibility. New code should use `Config` instead
29//! of `Subshell`.
30
31use crate::Env;
32use crate::job::Pid;
33use crate::job::ProcessResult;
34use crate::job::RunBlocking;
35use crate::job::RunUnblocking;
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::Sigmask;
46use crate::system::SigmaskOp;
47use crate::system::Signals;
48use crate::system::Sigset as _;
49use crate::system::TcSetPgrp;
50use crate::system::Wait;
51use crate::system::concurrency::WaitForSignals;
52use crate::system::resource::SetRlimit;
53use crate::trap::SignalSystem;
54use std::marker::PhantomData;
55use std::pin::Pin;
56
57/// Job state of a newly created subshell
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum JobControl {
60    /// The subshell becomes the foreground process group.
61    Foreground,
62    /// The subshell becomes a background process group.
63    Background,
64}
65
66/// Subshell builder
67///
68/// This is a helper for starting a subshell provided before [`Config`] was
69/// introduced. Use `Config` instead since it provides a better API.
70#[deprecated(
71    note = "use `Config` for subshell configuration instead",
72    since = "0.14.0"
73)]
74#[must_use = "a subshell is not started unless you call `Subshell::start`"]
75pub struct Subshell<S, F> {
76    task: F,
77    job_control: Option<JobControl>,
78    ignores_sigint_sigquit: bool,
79    phantom_data: PhantomData<fn(&mut Env<S>)>,
80}
81
82#[allow(deprecated, reason = "for backward compatible API")]
83impl<S, F> std::fmt::Debug for Subshell<S, F> {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("Subshell")
86            .field("job_control", &self.job_control)
87            .field("ignores_sigint_sigquit", &self.ignores_sigint_sigquit)
88            .finish_non_exhaustive()
89    }
90}
91
92#[allow(deprecated, reason = "for backward compatible API")]
93impl<S, F> Subshell<S, F>
94where
95    S: BlockSignals
96        + Close
97        + Dup
98        + Exit
99        + Fork
100        + GetPid
101        + Open
102        + RunBlocking
103        + RunUnblocking
104        + SendSignal
105        + SetPgid
106        + SetRlimit
107        + SignalSystem
108        + TcSetPgrp
109        + WaitForSignals
110        + 'static,
111    F: for<'a> FnOnce(&'a mut Env<S>, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
112        + 'static,
113    // TODO Revisit to simplify this function type when impl Future is allowed in return type
114{
115    /// Creates a new subshell builder with a task.
116    ///
117    /// The task will run in a subshell after it is started. The task takes two
118    /// arguments:
119    ///
120    /// 1. The environment in which the subshell runs, and
121    /// 2. Job control status for the subshell.
122    ///
123    /// If the task returns an `Err(Divert::...)`, it is handled as follows:
124    ///
125    /// - `Interrupt` and `Exit` with `Some(exit_status)` override the exit
126    ///   status in `Env`.
127    /// - Other `Divert` values are ignored.
128    pub fn new(task: F) -> Self {
129        Subshell {
130            task,
131            job_control: None,
132            ignores_sigint_sigquit: false,
133            phantom_data: PhantomData,
134        }
135    }
136
137    /// Specifies disposition of the subshell with respect to job control.
138    ///
139    /// If the argument is `None` (which is the default), the subshell runs in
140    /// the same process group as the parent process. If it is `Some(_)`, the
141    /// subshell becomes a new process group. For `JobControl::Foreground`, it
142    /// also brings itself to the foreground.
143    ///
144    /// This parameter is ignored if the shell is not [controlling
145    /// jobs](Env::controls_jobs) when starting the subshell. You can tell the
146    /// actual job control status of the subshell by the second return value of
147    /// [`start`](Self::start) in the parent environment and the second argument
148    /// passed to the task in the subshell environment.
149    ///
150    /// If the parent process is a job-controlling interactive shell, but the
151    /// subshell is not job-controlled, the subshell's signal dispositions for
152    /// SIGTSTP, SIGTTIN, and SIGTTOU are set to `Ignore`. This is to prevent
153    /// the subshell from being stopped by a job-stopping signal. Were the
154    /// subshell stopped, you could never resume it since it is not
155    /// job-controlled.
156    pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
157        self.job_control = job_control.into();
158        self
159    }
160
161    /// Makes the subshell ignore SIGINT and SIGQUIT.
162    ///
163    /// If `ignore` is true and the subshell is not job-controlled, the subshell
164    /// sets its signal dispositions for SIGINT and SIGQUIT to `Ignore`.
165    ///
166    /// The default is `false`.
167    pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
168        self.ignores_sigint_sigquit = ignore;
169        self
170    }
171
172    fn into_config_and_task(self) -> (Config, F) {
173        let config = Config {
174            job_control: self.job_control,
175            ignores_sigint_sigquit: self.ignores_sigint_sigquit,
176        };
177        (config, self.task)
178    }
179
180    /// Starts the subshell.
181    ///
182    /// This function creates a new child process that runs the task contained
183    /// in this builder.
184    ///
185    /// Although this function is `async`, it does not wait for the child to
186    /// finish, which means the parent and child processes will run
187    /// concurrently. To wait for the child to finish, you need to call
188    /// [`Env::wait_for_subshell`] or [`Env::wait_for_subshell_to_finish`]. If
189    /// job control is active, you may want to add the process ID to `env.jobs`
190    /// before waiting.
191    ///
192    /// If you set [`job_control`](Self::job_control) to
193    /// `JobControl::Foreground`, this function opens `env.tty` by calling
194    /// [`Env::get_tty`]. The `tty` is used to change the foreground job to the
195    /// new subshell. However, `job_control` is effective only when the shell is
196    /// [controlling jobs](Env::controls_jobs).
197    ///
198    /// If the subshell started successfully, the return value is a pair of the
199    /// child process ID and the actual job control. Otherwise, it indicates the
200    /// error.
201    pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
202        let (config, task) = self.into_config_and_task();
203        config
204            .start(env, async move |env, job_control| {
205                task(env, job_control).await
206            })
207            .await
208    }
209
210    /// Starts the subshell and waits for it to finish.
211    ///
212    /// This function [starts](Self::start) `self` and
213    /// [waits](Env::wait_for_subshell) for it to finish. This function returns
214    /// when the subshell process exits or is killed by a signal. If the
215    /// subshell is job-controlled, the function also returns when the job is
216    /// suspended.
217    ///
218    /// If the subshell started successfully, the return value is the process ID
219    /// and the process result of the subshell. If there was an error starting
220    /// the subshell, this function returns the error.
221    ///
222    /// If you set [`job_control`](Self::job_control) to
223    /// `JobControl::Foreground` and job control is effective as per
224    /// [`Env::controls_jobs`], this function makes the shell the foreground job
225    /// after the subshell terminated or suspended.
226    ///
227    /// When a job-controlled subshell suspends, this function does not add it
228    /// to `env.jobs`. You have to do it for yourself if necessary.
229    pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
230    where
231        S: Wait,
232    {
233        let (config, task) = self.into_config_and_task();
234        config
235            .start_and_wait(env, async move |env, job_control| {
236                task(env, job_control).await
237            })
238            .await
239    }
240}
241
242/// Trait to temporarily block the SIGINT and SIGQUIT signals
243///
244/// This trait represents the capability required by [`Config::start`] to
245/// temporarily block SIGINT and SIGQUIT while starting a subshell. Any type
246/// that implements [`Sigmask`] automatically implements this trait.
247/// Additionally, [`Concurrent`] implements this trait by delegating to the
248/// inner system.
249///
250/// This trait defines a higher-level interface to temporarily modify the signal
251/// mask. Typically, implementors of this trait will internally depend on
252/// `Sigmask` to perform the actual signal mask modification, but the details
253/// are abstracted away. In particular, using this trait as the public
254/// capability leaves room for types such as [`Concurrent`] to stop exposing
255/// `Sigmask` directly in a future release while still providing the behavior
256/// required by callers.
257///
258/// [`Concurrent`]: crate::system::concurrency::Concurrent
259pub trait BlockSignals: Signals {
260    type SavedMask;
261
262    /// Blocks SIGINT and SIGQUIT, returning the previous signal mask.
263    ///
264    /// After this function returns successfully, one of the following must be
265    /// performed:
266    ///
267    /// - Call [`restore_sigmask`](Self::restore_sigmask) with the returned mask
268    ///   to restore the original signal mask,
269    /// - Call [`SignalSystem::set_disposition`] to re-set the disposition of
270    ///   SIGINT and SIGQUIT, which installs a new signal mask,
271    /// - [Exit](Exit::exit) the process without restoring the signal mask.
272    fn block_sigint_sigquit(
273        &self,
274    ) -> impl Future<Output = Result<Self::SavedMask, Errno>> + use<'_, Self>;
275
276    /// Restores the signal mask.
277    ///
278    /// This function restores the signal mask to the state represented by
279    /// `mask`, which should be a value returned by a previous call to
280    /// [`block_sigint_sigquit`](Self::block_sigint_sigquit).
281    fn restore_sigmask(
282        &self,
283        mask: Self::SavedMask,
284    ) -> impl Future<Output = Result<(), Errno>> + use<'_, Self>;
285}
286
287impl<S> BlockSignals for S
288where
289    S: Sigmask + ?Sized,
290{
291    type SavedMask = S::Sigset;
292
293    async fn block_sigint_sigquit(&self) -> Result<Self::SavedMask, Errno> {
294        let mut old_mask = S::Sigset::new();
295        self.sigmask(
296            Some((
297                SigmaskOp::Add,
298                &S::Sigset::from_signals([S::SIGINT, S::SIGQUIT])?,
299            )),
300            Some(&mut old_mask),
301        )
302        .await?;
303        Ok(old_mask)
304    }
305
306    async fn restore_sigmask(&self, mask: Self::SavedMask) -> Result<(), Errno> {
307        self.sigmask(Some((SigmaskOp::Set, &mask)), None).await
308    }
309}
310
311mod config;
312pub use config::Config;
313
314#[allow(deprecated, reason = "for backward compatible API")]
315#[cfg(test)]
316mod tests {
317    use super::*;
318    use crate::job::{Job, ProcessState};
319    use crate::option::Option::{Interactive, Monitor};
320    use crate::option::State::On;
321    use crate::semantics::ExitStatus;
322    use crate::source::Location;
323    use crate::stack::Frame;
324    use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
325    use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
326    use crate::system::{Concurrent, Disposition};
327    use crate::test_helper::in_virtual_system;
328    use crate::trap::Action;
329    use assert_matches::assert_matches;
330    use futures_executor::LocalPool;
331    use std::cell::Cell;
332    use std::cell::RefCell;
333    use std::rc::Rc;
334
335    fn stub_tty(state: &RefCell<SystemState>) {
336        state
337            .borrow_mut()
338            .file_system
339            .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
340            .unwrap();
341    }
342
343    #[test]
344    fn subshell_start_returns_child_process_id() {
345        in_virtual_system(|mut env, _state| async move {
346            let parent_pid = env.main_pid;
347            let child_pid = Rc::new(Cell::new(None));
348            let child_pid_2 = Rc::clone(&child_pid);
349            let subshell = Subshell::new(
350                move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
351                    Box::pin(async move {
352                        child_pid_2.set(Some(env.system.getpid()));
353                        assert_eq!(env.system.getppid(), parent_pid);
354                    })
355                },
356            );
357            let result = subshell.start(&mut env).await.unwrap().0;
358            env.wait_for_subshell(result).await.unwrap();
359            assert_eq!(Some(result), child_pid.get());
360        });
361    }
362
363    #[test]
364    fn subshell_start_failing() {
365        let mut executor = LocalPool::new();
366        let env = &mut Env::new_virtual();
367        let subshell =
368            Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
369        let result = executor.run_until(subshell.start(env));
370        assert_eq!(result, Err(Errno::ENOSYS));
371    }
372
373    #[test]
374    fn stack_frame_in_subshell() {
375        in_virtual_system(|mut env, _state| async move {
376            let subshell = Subshell::new(|env, _job_control| {
377                Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
378            });
379            let pid = subshell.start(&mut env).await.unwrap().0;
380            assert_eq!(env.stack[..], []);
381
382            env.wait_for_subshell(pid).await.unwrap();
383        });
384    }
385
386    #[test]
387    fn jobs_disowned_in_subshell() {
388        in_virtual_system(|mut env, _state| async move {
389            let index = env.jobs.add(Job::new(Pid(123)));
390            let subshell = Subshell::new(move |env, _job_control| {
391                Box::pin(async move { assert!(!env.jobs[index].is_owned) })
392            });
393            let pid = subshell.start(&mut env).await.unwrap().0;
394            env.wait_for_subshell(pid).await.unwrap();
395
396            assert!(env.jobs[index].is_owned);
397        });
398    }
399
400    #[test]
401    fn trap_reset_in_subshell() {
402        in_virtual_system(|mut env, _state| async move {
403            env.traps
404                .set_action(
405                    &env.system,
406                    SIGCHLD,
407                    Action::Command("echo foo".into()),
408                    Location::dummy(""),
409                    false,
410                )
411                .await
412                .unwrap();
413            let subshell = Subshell::new(|env, _job_control| {
414                Box::pin(async {
415                    let (current, parent) = env.traps.get_state(SIGCHLD);
416                    assert_eq!(current.unwrap().action, Action::Default);
417                    assert_matches!(
418                        &parent.unwrap().action,
419                        Action::Command(body) => assert_eq!(&**body, "echo foo")
420                    );
421                })
422            });
423            let pid = subshell.start(&mut env).await.unwrap().0;
424            env.wait_for_subshell(pid).await.unwrap();
425        });
426    }
427
428    #[test]
429    fn subshell_with_no_job_control() {
430        in_virtual_system(|mut parent_env, state| async move {
431            parent_env.options.set(Monitor, On);
432
433            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
434            let state_2 = Rc::clone(&state);
435            let (child_pid, job_control) = Subshell::new(
436                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
437                    Box::pin(async move {
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            )
445            .job_control(None)
446            .start(&mut parent_env)
447            .await
448            .unwrap();
449            assert_eq!(job_control, None);
450            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
451            assert_eq!(state.borrow().foreground, None);
452
453            parent_env.wait_for_subshell(child_pid).await.unwrap();
454            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
455            assert_eq!(state.borrow().foreground, None);
456        });
457    }
458
459    #[test]
460    fn subshell_in_background() {
461        in_virtual_system(|mut parent_env, state| async move {
462            parent_env.options.set(Monitor, On);
463
464            let state_2 = Rc::clone(&state);
465            let (child_pid, job_control) = Subshell::new(
466                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
467                    Box::pin(async move {
468                        let child_pid = child_env.system.getpid();
469                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
470                        assert_eq!(state_2.borrow().foreground, None);
471                        assert_eq!(job_control, Some(JobControl::Background));
472                    })
473                },
474            )
475            .job_control(JobControl::Background)
476            .start(&mut parent_env)
477            .await
478            .unwrap();
479            assert_eq!(job_control, Some(JobControl::Background));
480            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
481            assert_eq!(state.borrow().foreground, None);
482
483            parent_env.wait_for_subshell(child_pid).await.unwrap();
484            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
485            assert_eq!(state.borrow().foreground, None);
486        });
487    }
488
489    #[test]
490    fn subshell_in_foreground() {
491        in_virtual_system(|mut parent_env, state| async move {
492            parent_env.options.set(Monitor, On);
493            stub_tty(&state);
494
495            let state_2 = Rc::clone(&state);
496            let (child_pid, job_control) = Subshell::new(
497                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
498                    Box::pin(async move {
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            )
506            .job_control(JobControl::Foreground)
507            .start(&mut parent_env)
508            .await
509            .unwrap();
510            assert_eq!(job_control, Some(JobControl::Foreground));
511            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
512            // The child may not yet have become the foreground job.
513            // assert_eq!(state.borrow().foreground, Some(child_pid));
514
515            parent_env.wait_for_subshell(child_pid).await.unwrap();
516            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
517            assert_eq!(state.borrow().foreground, Some(child_pid));
518        });
519    }
520
521    #[test]
522    fn tty_after_starting_foreground_subshell() {
523        in_virtual_system(|mut parent_env, state| async move {
524            parent_env.options.set(Monitor, On);
525            stub_tty(&state);
526
527            let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
528                .job_control(JobControl::Foreground)
529                .start(&mut parent_env)
530                .await
531                .unwrap();
532            assert_matches!(parent_env.tty, Some(_));
533        });
534    }
535
536    #[test]
537    fn job_control_without_tty() {
538        // When /dev/tty is not available, the shell cannot bring the subshell to
539        // the foreground. The subshell should still be in a new process group.
540        // This is the behavior required by POSIX.
541        in_virtual_system(async |mut parent_env, state| {
542            parent_env.options.set(Monitor, On);
543
544            let state_2 = Rc::clone(&state);
545            let (child_pid, job_control) = Subshell::new(
546                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
547                    Box::pin(async move {
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            )
554            .job_control(JobControl::Foreground)
555            .start(&mut parent_env)
556            .await
557            .unwrap();
558            assert_eq!(job_control, Some(JobControl::Foreground));
559            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
560
561            parent_env.wait_for_subshell(child_pid).await.unwrap();
562            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
563        })
564    }
565
566    #[test]
567    fn no_job_control_with_option_disabled() {
568        in_virtual_system(|mut parent_env, state| async move {
569            stub_tty(&state);
570
571            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
572            let state_2 = Rc::clone(&state);
573            let (child_pid, job_control) = Subshell::new(
574                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
575                    Box::pin(async move {
576                        let child_pid = child_env.system.getpid();
577                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
578                        assert_eq!(state_2.borrow().foreground, None);
579                    })
580                },
581            )
582            .job_control(JobControl::Foreground)
583            .start(&mut parent_env)
584            .await
585            .unwrap();
586            assert_eq!(job_control, None);
587            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
588            assert_eq!(state.borrow().foreground, None);
589
590            parent_env.wait_for_subshell(child_pid).await.unwrap();
591            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
592            assert_eq!(state.borrow().foreground, None);
593        });
594    }
595
596    #[test]
597    fn no_job_control_for_nested_subshell() {
598        in_virtual_system(|mut parent_env, state| async move {
599            let mut parent_env = parent_env.push_frame(Frame::Subshell);
600            parent_env.options.set(Monitor, On);
601            stub_tty(&state);
602
603            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
604            let state_2 = Rc::clone(&state);
605            let (child_pid, job_control) = Subshell::new(
606                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
607                    Box::pin(async move {
608                        let child_pid = child_env.system.getpid();
609                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
610                        assert_eq!(state_2.borrow().foreground, None);
611                    })
612                },
613            )
614            .job_control(JobControl::Foreground)
615            .start(&mut parent_env)
616            .await
617            .unwrap();
618            assert_eq!(job_control, None);
619            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
620            assert_eq!(state.borrow().foreground, None);
621
622            parent_env.wait_for_subshell(child_pid).await.unwrap();
623            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
624            assert_eq!(state.borrow().foreground, None);
625        });
626    }
627
628    #[test]
629    fn wait_without_job_control() {
630        in_virtual_system(|mut env, _state| async move {
631            let subshell = Subshell::new(|env, _job_control| {
632                Box::pin(async { env.exit_status = ExitStatus(42) })
633            });
634            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.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 subshell = Subshell::new(|env, _job_control| {
646                Box::pin(async { env.exit_status = ExitStatus(123) })
647            })
648            .job_control(JobControl::Foreground);
649            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
650            assert_eq!(process_result, ProcessResult::exited(123));
651            assert_eq!(state.borrow().foreground, Some(env.main_pgid));
652        });
653    }
654
655    // TODO wait_for_foreground_job_to_be_signaled
656    // TODO wait_for_foreground_job_to_be_stopped
657
658    #[test]
659    fn sigint_sigquit_not_ignored_by_default() {
660        in_virtual_system(|mut parent_env, state| async move {
661            let (child_pid, _) = Subshell::new(|env, _job_control| {
662                Box::pin(async { env.exit_status = ExitStatus(123) })
663            })
664            .job_control(JobControl::Background)
665            .start(&mut parent_env)
666            .await
667            .unwrap();
668            parent_env.wait_for_subshell(child_pid).await.unwrap();
669
670            let state = state.borrow();
671            let process = &state.processes[&child_pid];
672            assert_eq!(process.disposition(SIGINT), Disposition::Default);
673            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
674        })
675    }
676
677    #[test]
678    fn sigint_sigquit_ignored_in_uncontrolled_job() {
679        in_virtual_system(|mut parent_env, state| async move {
680            let (child_pid, _) = Subshell::new(|env, _job_control| {
681                Box::pin(async { env.exit_status = ExitStatus(123) })
682            })
683            .job_control(JobControl::Background)
684            .ignore_sigint_sigquit(true)
685            .start(&mut parent_env)
686            .await
687            .unwrap();
688
689            parent_env
690                .system
691                .kill(child_pid, Some(SIGINT))
692                .await
693                .unwrap();
694            parent_env
695                .system
696                .kill(child_pid, Some(SIGQUIT))
697                .await
698                .unwrap();
699
700            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
701            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
702
703            let state = state.borrow();
704            let parent_process = &state.processes[&parent_env.main_pid];
705            assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
706            assert_eq!(
707                parent_process.blocked_signals().contains(SIGQUIT),
708                Ok(false)
709            );
710            let child_process = &state.processes[&child_pid];
711            assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
712            assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
713        })
714    }
715
716    #[test]
717    fn sigint_sigquit_not_ignored_if_job_controlled() {
718        in_virtual_system(|mut parent_env, state| async move {
719            parent_env.options.set(Monitor, On);
720            stub_tty(&state);
721
722            let (child_pid, _) = Subshell::new(|env, _job_control| {
723                Box::pin(async { env.exit_status = ExitStatus(123) })
724            })
725            .job_control(JobControl::Background)
726            .ignore_sigint_sigquit(true)
727            .start(&mut parent_env)
728            .await
729            .unwrap();
730            parent_env.wait_for_subshell(child_pid).await.unwrap();
731
732            let state = state.borrow();
733            let process = &state.processes[&child_pid];
734            assert_eq!(process.disposition(SIGINT), Disposition::Default);
735            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
736        })
737    }
738
739    #[test]
740    fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
741     {
742        in_virtual_system(|mut parent_env, state| async move {
743            parent_env.options.set(Interactive, On);
744            parent_env.options.set(Monitor, On);
745            parent_env
746                .traps
747                .enable_internal_dispositions_for_stoppers(&parent_env.system)
748                .await
749                .unwrap();
750            stub_tty(&state);
751
752            let (child_pid, _) = Subshell::new(|env, _job_control| {
753                Box::pin(async { env.exit_status = ExitStatus(123) })
754            })
755            .start(&mut parent_env)
756            .await
757            .unwrap();
758
759            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
760            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
761
762            let state = state.borrow();
763            let child_process = &state.processes[&child_pid];
764            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
765            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
766            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
767        })
768    }
769
770    #[test]
771    fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
772        in_virtual_system(|mut parent_env, state| async move {
773            parent_env.options.set(Interactive, On);
774            parent_env.options.set(Monitor, On);
775            parent_env
776                .traps
777                .enable_internal_dispositions_for_stoppers(&parent_env.system)
778                .await
779                .unwrap();
780            stub_tty(&state);
781
782            let (child_pid, _) = Subshell::new(|env, _job_control| {
783                Box::pin(async { env.exit_status = ExitStatus(123) })
784            })
785            .job_control(JobControl::Background)
786            .start(&mut parent_env)
787            .await
788            .unwrap();
789
790            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
791            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
792
793            let state = state.borrow();
794            let child_process = &state.processes[&child_pid];
795            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
796            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
797            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
798        })
799    }
800
801    #[test]
802    fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
803        in_virtual_system(|mut parent_env, state| async move {
804            parent_env.options.set(Interactive, On);
805            stub_tty(&state);
806
807            let (child_pid, _) = Subshell::new(|env, _job_control| {
808                Box::pin(async { env.exit_status = ExitStatus(123) })
809            })
810            .start(&mut parent_env)
811            .await
812            .unwrap();
813
814            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
815            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
816
817            let state = state.borrow();
818            let child_process = &state.processes[&child_pid];
819            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
820            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
821            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
822        })
823    }
824
825    #[test]
826    fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
827     {
828        in_virtual_system(|mut parent_env, state| async move {
829            parent_env.options.set(Monitor, On);
830            stub_tty(&state);
831
832            let (child_pid, _) = Subshell::new(|env, _job_control| {
833                Box::pin(async { env.exit_status = ExitStatus(123) })
834            })
835            .start(&mut parent_env)
836            .await
837            .unwrap();
838
839            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
840            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
841
842            let state = state.borrow();
843            let child_process = &state.processes[&child_pid];
844            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
845            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
846            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
847        })
848    }
849}