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 while not implementing `Sigmask` itself, which allows
249/// `Concurrent` to maintain internal consistency about signal masks while still
250/// providing this capability to its users.
251///
252/// This trait defines a higher-level interface to temporarily modify the signal
253/// mask. Typically, implementors of this trait will internally depend on
254/// `Sigmask` to perform the actual signal mask modification, but the details
255/// are abstracted away.
256///
257/// [`Concurrent`]: crate::system::concurrency::Concurrent
258pub trait BlockSignals: Signals {
259    type SavedMask;
260
261    /// Blocks SIGINT and SIGQUIT, returning the previous signal mask.
262    ///
263    /// After this function returns successfully, one of the following must be
264    /// performed:
265    ///
266    /// - Call [`restore_sigmask`](Self::restore_sigmask) with the returned mask
267    ///   to restore the original signal mask,
268    /// - Call [`SignalSystem::set_disposition`] to re-set the disposition of
269    ///   SIGINT and SIGQUIT, which installs a new signal mask,
270    /// - [Exit](Exit::exit) the process without restoring the signal mask.
271    fn block_sigint_sigquit(
272        &self,
273    ) -> impl Future<Output = Result<Self::SavedMask, Errno>> + use<'_, Self>;
274
275    /// Restores the signal mask.
276    ///
277    /// This function restores the signal mask to the state represented by
278    /// `mask`, which should be a value returned by a previous call to
279    /// [`block_sigint_sigquit`](Self::block_sigint_sigquit).
280    fn restore_sigmask(
281        &self,
282        mask: Self::SavedMask,
283    ) -> impl Future<Output = Result<(), Errno>> + use<'_, Self>;
284}
285
286impl<S> BlockSignals for S
287where
288    S: Sigmask + ?Sized,
289{
290    type SavedMask = S::Sigset;
291
292    async fn block_sigint_sigquit(&self) -> Result<Self::SavedMask, Errno> {
293        let mut old_mask = S::Sigset::new();
294        self.sigmask(
295            Some((
296                SigmaskOp::Add,
297                &S::Sigset::from_signals([S::SIGINT, S::SIGQUIT])?,
298            )),
299            Some(&mut old_mask),
300        )
301        .await?;
302        Ok(old_mask)
303    }
304
305    async fn restore_sigmask(&self, mask: Self::SavedMask) -> Result<(), Errno> {
306        self.sigmask(Some((SigmaskOp::Set, &mask)), None).await
307    }
308}
309
310mod config;
311pub use config::Config;
312
313#[allow(deprecated, reason = "for backward compatible API")]
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::job::{Job, ProcessState};
318    use crate::option::Option::{Interactive, Monitor};
319    use crate::option::State::On;
320    use crate::semantics::ExitStatus;
321    use crate::source::Location;
322    use crate::stack::Frame;
323    use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
324    use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
325    use crate::system::{Concurrent, Disposition};
326    use crate::test_helper::in_virtual_system;
327    use crate::trap::Action;
328    use assert_matches::assert_matches;
329    use futures_executor::LocalPool;
330    use std::cell::Cell;
331    use std::cell::RefCell;
332    use std::rc::Rc;
333
334    fn stub_tty(state: &RefCell<SystemState>) {
335        state
336            .borrow_mut()
337            .file_system
338            .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
339            .unwrap();
340    }
341
342    #[test]
343    fn subshell_start_returns_child_process_id() {
344        in_virtual_system(|mut env, _state| async move {
345            let parent_pid = env.main_pid;
346            let child_pid = Rc::new(Cell::new(None));
347            let child_pid_2 = Rc::clone(&child_pid);
348            let subshell = Subshell::new(
349                move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
350                    Box::pin(async move {
351                        child_pid_2.set(Some(env.system.getpid()));
352                        assert_eq!(env.system.getppid(), parent_pid);
353                    })
354                },
355            );
356            let result = subshell.start(&mut env).await.unwrap().0;
357            env.wait_for_subshell(result).await.unwrap();
358            assert_eq!(Some(result), child_pid.get());
359        });
360    }
361
362    #[test]
363    fn subshell_start_failing() {
364        let mut executor = LocalPool::new();
365        let env = &mut Env::new_virtual();
366        let subshell =
367            Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
368        let result = executor.run_until(subshell.start(env));
369        assert_eq!(result, Err(Errno::ENOSYS));
370    }
371
372    #[test]
373    fn stack_frame_in_subshell() {
374        in_virtual_system(|mut env, _state| async move {
375            let subshell = Subshell::new(|env, _job_control| {
376                Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
377            });
378            let pid = subshell.start(&mut env).await.unwrap().0;
379            assert_eq!(env.stack[..], []);
380
381            env.wait_for_subshell(pid).await.unwrap();
382        });
383    }
384
385    #[test]
386    fn jobs_disowned_in_subshell() {
387        in_virtual_system(|mut env, _state| async move {
388            let index = env.jobs.insert(Job::new(Pid(123)));
389            let subshell = Subshell::new(move |env, _job_control| {
390                Box::pin(async move { assert!(!env.jobs[index].is_owned) })
391            });
392            let pid = subshell.start(&mut env).await.unwrap().0;
393            env.wait_for_subshell(pid).await.unwrap();
394
395            assert!(env.jobs[index].is_owned);
396        });
397    }
398
399    #[test]
400    fn trap_reset_in_subshell() {
401        in_virtual_system(|mut env, _state| async move {
402            env.traps
403                .set_action(
404                    &env.system,
405                    SIGCHLD,
406                    Action::Command("echo foo".into()),
407                    Location::dummy(""),
408                    false,
409                )
410                .await
411                .unwrap();
412            let subshell = Subshell::new(|env, _job_control| {
413                Box::pin(async {
414                    let (current, parent) = env.traps.get_state(SIGCHLD);
415                    assert_eq!(current.unwrap().action, Action::Default);
416                    assert_matches!(
417                        &parent.unwrap().action,
418                        Action::Command(body) => assert_eq!(&**body, "echo foo")
419                    );
420                })
421            });
422            let pid = subshell.start(&mut env).await.unwrap().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) = Subshell::new(
435                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
436                    Box::pin(async move {
437                        let child_pid = child_env.system.getpid();
438                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
439                        assert_eq!(state_2.borrow().foreground, None);
440                        assert_eq!(job_control, None);
441                    })
442                },
443            )
444            .job_control(None)
445            .start(&mut parent_env)
446            .await
447            .unwrap();
448            assert_eq!(job_control, None);
449            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
450            assert_eq!(state.borrow().foreground, None);
451
452            parent_env.wait_for_subshell(child_pid).await.unwrap();
453            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
454            assert_eq!(state.borrow().foreground, None);
455        });
456    }
457
458    #[test]
459    fn subshell_in_background() {
460        in_virtual_system(|mut parent_env, state| async move {
461            parent_env.options.set(Monitor, On);
462
463            let state_2 = Rc::clone(&state);
464            let (child_pid, job_control) = Subshell::new(
465                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
466                    Box::pin(async move {
467                        let child_pid = child_env.system.getpid();
468                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
469                        assert_eq!(state_2.borrow().foreground, None);
470                        assert_eq!(job_control, Some(JobControl::Background));
471                    })
472                },
473            )
474            .job_control(JobControl::Background)
475            .start(&mut parent_env)
476            .await
477            .unwrap();
478            assert_eq!(job_control, Some(JobControl::Background));
479            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
480            assert_eq!(state.borrow().foreground, None);
481
482            parent_env.wait_for_subshell(child_pid).await.unwrap();
483            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
484            assert_eq!(state.borrow().foreground, None);
485        });
486    }
487
488    #[test]
489    fn subshell_in_foreground() {
490        in_virtual_system(|mut parent_env, state| async move {
491            parent_env.options.set(Monitor, On);
492            stub_tty(&state);
493
494            let state_2 = Rc::clone(&state);
495            let (child_pid, job_control) = Subshell::new(
496                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
497                    Box::pin(async move {
498                        let child_pid = child_env.system.getpid();
499                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
500                        assert_eq!(state_2.borrow().foreground, Some(child_pid));
501                        assert_eq!(job_control, Some(JobControl::Foreground));
502                    })
503                },
504            )
505            .job_control(JobControl::Foreground)
506            .start(&mut parent_env)
507            .await
508            .unwrap();
509            assert_eq!(job_control, Some(JobControl::Foreground));
510            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
511            // The child may not yet have become the foreground job.
512            // assert_eq!(state.borrow().foreground, Some(child_pid));
513
514            parent_env.wait_for_subshell(child_pid).await.unwrap();
515            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
516            assert_eq!(state.borrow().foreground, Some(child_pid));
517        });
518    }
519
520    #[test]
521    fn tty_after_starting_foreground_subshell() {
522        in_virtual_system(|mut parent_env, state| async move {
523            parent_env.options.set(Monitor, On);
524            stub_tty(&state);
525
526            let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
527                .job_control(JobControl::Foreground)
528                .start(&mut parent_env)
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) = Subshell::new(
545                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
546                    Box::pin(async move {
547                        let child_pid = child_env.system.getpid();
548                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
549                        assert_eq!(job_control, Some(JobControl::Foreground));
550                    })
551                },
552            )
553            .job_control(JobControl::Foreground)
554            .start(&mut parent_env)
555            .await
556            .unwrap();
557            assert_eq!(job_control, Some(JobControl::Foreground));
558            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
559
560            parent_env.wait_for_subshell(child_pid).await.unwrap();
561            assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
562        })
563    }
564
565    #[test]
566    fn no_job_control_with_option_disabled() {
567        in_virtual_system(|mut parent_env, state| async move {
568            stub_tty(&state);
569
570            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
571            let state_2 = Rc::clone(&state);
572            let (child_pid, job_control) = Subshell::new(
573                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
574                    Box::pin(async move {
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            )
581            .job_control(JobControl::Foreground)
582            .start(&mut parent_env)
583            .await
584            .unwrap();
585            assert_eq!(job_control, None);
586            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
587            assert_eq!(state.borrow().foreground, None);
588
589            parent_env.wait_for_subshell(child_pid).await.unwrap();
590            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
591            assert_eq!(state.borrow().foreground, None);
592        });
593    }
594
595    #[test]
596    fn no_job_control_for_nested_subshell() {
597        in_virtual_system(|mut parent_env, state| async move {
598            let mut parent_env = parent_env.push_frame(Frame::Subshell);
599            parent_env.options.set(Monitor, On);
600            stub_tty(&state);
601
602            let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
603            let state_2 = Rc::clone(&state);
604            let (child_pid, job_control) = Subshell::new(
605                move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
606                    Box::pin(async move {
607                        let child_pid = child_env.system.getpid();
608                        assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
609                        assert_eq!(state_2.borrow().foreground, None);
610                    })
611                },
612            )
613            .job_control(JobControl::Foreground)
614            .start(&mut parent_env)
615            .await
616            .unwrap();
617            assert_eq!(job_control, None);
618            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
619            assert_eq!(state.borrow().foreground, None);
620
621            parent_env.wait_for_subshell(child_pid).await.unwrap();
622            assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
623            assert_eq!(state.borrow().foreground, None);
624        });
625    }
626
627    #[test]
628    fn wait_without_job_control() {
629        in_virtual_system(|mut env, _state| async move {
630            let subshell = Subshell::new(|env, _job_control| {
631                Box::pin(async { env.exit_status = ExitStatus(42) })
632            });
633            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
634            assert_eq!(process_result, ProcessResult::exited(42));
635        });
636    }
637
638    #[test]
639    fn wait_for_foreground_job_to_exit() {
640        in_virtual_system(|mut env, state| async move {
641            env.options.set(Monitor, On);
642            stub_tty(&state);
643
644            let subshell = Subshell::new(|env, _job_control| {
645                Box::pin(async { env.exit_status = ExitStatus(123) })
646            })
647            .job_control(JobControl::Foreground);
648            let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
649            assert_eq!(process_result, ProcessResult::exited(123));
650            assert_eq!(state.borrow().foreground, Some(env.main_pgid));
651        });
652    }
653
654    // TODO wait_for_foreground_job_to_be_signaled
655    // TODO wait_for_foreground_job_to_be_stopped
656
657    #[test]
658    fn sigint_sigquit_not_ignored_by_default() {
659        in_virtual_system(|mut parent_env, state| async move {
660            let (child_pid, _) = Subshell::new(|env, _job_control| {
661                Box::pin(async { env.exit_status = ExitStatus(123) })
662            })
663            .job_control(JobControl::Background)
664            .start(&mut parent_env)
665            .await
666            .unwrap();
667            parent_env.wait_for_subshell(child_pid).await.unwrap();
668
669            let state = state.borrow();
670            let process = &state.processes[&child_pid];
671            assert_eq!(process.disposition(SIGINT), Disposition::Default);
672            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
673        })
674    }
675
676    #[test]
677    fn sigint_sigquit_ignored_in_uncontrolled_job() {
678        in_virtual_system(|mut parent_env, state| async move {
679            let (child_pid, _) = Subshell::new(|env, _job_control| {
680                Box::pin(async { env.exit_status = ExitStatus(123) })
681            })
682            .job_control(JobControl::Background)
683            .ignore_sigint_sigquit(true)
684            .start(&mut parent_env)
685            .await
686            .unwrap();
687
688            parent_env
689                .system
690                .kill(child_pid, Some(SIGINT))
691                .await
692                .unwrap();
693            parent_env
694                .system
695                .kill(child_pid, Some(SIGQUIT))
696                .await
697                .unwrap();
698
699            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
700            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
701
702            let state = state.borrow();
703            let parent_process = &state.processes[&parent_env.main_pid];
704            assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
705            assert_eq!(
706                parent_process.blocked_signals().contains(SIGQUIT),
707                Ok(false)
708            );
709            let child_process = &state.processes[&child_pid];
710            assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
711            assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
712        })
713    }
714
715    #[test]
716    fn sigint_sigquit_not_ignored_if_job_controlled() {
717        in_virtual_system(|mut parent_env, state| async move {
718            parent_env.options.set(Monitor, On);
719            stub_tty(&state);
720
721            let (child_pid, _) = Subshell::new(|env, _job_control| {
722                Box::pin(async { env.exit_status = ExitStatus(123) })
723            })
724            .job_control(JobControl::Background)
725            .ignore_sigint_sigquit(true)
726            .start(&mut parent_env)
727            .await
728            .unwrap();
729            parent_env.wait_for_subshell(child_pid).await.unwrap();
730
731            let state = state.borrow();
732            let process = &state.processes[&child_pid];
733            assert_eq!(process.disposition(SIGINT), Disposition::Default);
734            assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
735        })
736    }
737
738    #[test]
739    fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
740     {
741        in_virtual_system(|mut parent_env, state| async move {
742            parent_env.options.set(Interactive, On);
743            parent_env.options.set(Monitor, On);
744            parent_env
745                .traps
746                .enable_internal_dispositions_for_stoppers(&parent_env.system)
747                .await
748                .unwrap();
749            stub_tty(&state);
750
751            let (child_pid, _) = Subshell::new(|env, _job_control| {
752                Box::pin(async { env.exit_status = ExitStatus(123) })
753            })
754            .start(&mut parent_env)
755            .await
756            .unwrap();
757
758            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
759            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
760
761            let state = state.borrow();
762            let child_process = &state.processes[&child_pid];
763            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
764            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
765            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
766        })
767    }
768
769    #[test]
770    fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
771        in_virtual_system(|mut parent_env, state| async move {
772            parent_env.options.set(Interactive, On);
773            parent_env.options.set(Monitor, On);
774            parent_env
775                .traps
776                .enable_internal_dispositions_for_stoppers(&parent_env.system)
777                .await
778                .unwrap();
779            stub_tty(&state);
780
781            let (child_pid, _) = Subshell::new(|env, _job_control| {
782                Box::pin(async { env.exit_status = ExitStatus(123) })
783            })
784            .job_control(JobControl::Background)
785            .start(&mut parent_env)
786            .await
787            .unwrap();
788
789            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
790            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
791
792            let state = state.borrow();
793            let child_process = &state.processes[&child_pid];
794            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
795            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
796            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
797        })
798    }
799
800    #[test]
801    fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
802        in_virtual_system(|mut parent_env, state| async move {
803            parent_env.options.set(Interactive, On);
804            stub_tty(&state);
805
806            let (child_pid, _) = Subshell::new(|env, _job_control| {
807                Box::pin(async { env.exit_status = ExitStatus(123) })
808            })
809            .start(&mut parent_env)
810            .await
811            .unwrap();
812
813            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
814            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
815
816            let state = state.borrow();
817            let child_process = &state.processes[&child_pid];
818            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
819            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
820            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
821        })
822    }
823
824    #[test]
825    fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
826     {
827        in_virtual_system(|mut parent_env, state| async move {
828            parent_env.options.set(Monitor, On);
829            stub_tty(&state);
830
831            let (child_pid, _) = Subshell::new(|env, _job_control| {
832                Box::pin(async { env.exit_status = ExitStatus(123) })
833            })
834            .start(&mut parent_env)
835            .await
836            .unwrap();
837
838            let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
839            assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
840
841            let state = state.borrow();
842            let child_process = &state.processes[&child_pid];
843            assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
844            assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
845            assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
846        })
847    }
848}