yash_env/
lib.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 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//! This crate defines the shell execution environment.
18//!
19//! A shell execution environment, [`Env`], is a collection of data that may
20//! affect or be affected by the execution of commands. The environment consists
21//! of application-managed parts and system-managed parts. Application-managed
22//! parts are implemented in pure Rust in this crate. Many application-managed
23//! parts like [function]s and [variable]s can be manipulated independently of
24//! interactions with the underlying system. System-managed parts, on the other
25//! hand, depend on the underlying system. Attributes like the working directory
26//! and umask are managed by the system to be accessed only by interaction with
27//! the system interface.
28//!
29//! The [`System`] trait is the interface to the system-managed parts.
30//! [`RealSystem`] provides an implementation for `System` that interacts with
31//! the underlying system. [`VirtualSystem`] is a dummy for simulating the
32//! system's behavior without affecting the actual system.
33
34use self::alias::AliasSet;
35use self::any::DataSet;
36use self::builtin::Builtin;
37use self::function::FunctionSet;
38use self::io::Fd;
39use self::job::JobList;
40use self::job::Pid;
41use self::job::ProcessResult;
42use self::job::ProcessState;
43use self::option::On;
44use self::option::OptionSet;
45use self::option::{AllExport, ErrExit, Interactive, Monitor};
46use self::semantics::Divert;
47use self::semantics::ExitStatus;
48use self::stack::Frame;
49use self::stack::Stack;
50use self::system::CaughtSignals;
51use self::system::Clock;
52use self::system::Close;
53use self::system::Dup;
54use self::system::Errno;
55use self::system::Fstat;
56use self::system::GetCwd;
57use self::system::GetPid;
58use self::system::Isatty;
59use self::system::Mode;
60use self::system::OfdAccess;
61use self::system::Open;
62use self::system::OpenFlag;
63use self::system::Select;
64pub use self::system::SharedSystem;
65use self::system::Sigaction;
66use self::system::Sigmask;
67use self::system::Signals;
68#[allow(deprecated)]
69pub use self::system::System;
70use self::system::TcSetPgrp;
71use self::system::Wait;
72#[cfg(unix)]
73pub use self::system::real::RealSystem;
74pub use self::system::r#virtual::VirtualSystem;
75use self::trap::TrapSet;
76use self::variable::PPID;
77use self::variable::Scope;
78use self::variable::VariableRefMut;
79use self::variable::VariableSet;
80use std::collections::HashMap;
81use std::fmt::Debug;
82use std::ops::ControlFlow::{self, Break, Continue};
83use std::rc::Rc;
84use std::task::Context;
85use std::task::Poll;
86use std::task::Waker;
87pub use unix_path as path;
88pub use unix_str as str;
89
90/// Whole shell execution environment.
91///
92/// The shell execution environment consists of application-managed parts and
93/// system-managed parts. Application-managed parts are directly implemented in
94/// the `Env` instance. System-managed parts are managed by a [`SharedSystem`]
95/// that contains an instance of `S` that implements [`System`].
96///
97/// # Cloning
98///
99/// `Env::clone` effectively clones the application-managed parts of the
100/// environment. Since [`SharedSystem`] is reference-counted, you will not get a
101/// deep copy of the system-managed parts. See also
102/// [`clone_with_system`](Self::clone_with_system).
103#[derive(Clone, Debug)]
104#[non_exhaustive]
105pub struct Env<S> {
106    /// Aliases defined in the environment
107    pub aliases: AliasSet,
108
109    /// Name of the current shell executable or shell script
110    ///
111    /// Special parameter `0` expands to this value.
112    pub arg0: String,
113
114    /// Built-in utilities available in the environment
115    pub builtins: HashMap<&'static str, Builtin<S>>,
116
117    /// Exit status of the last executed command
118    pub exit_status: ExitStatus,
119
120    /// Functions defined in the environment
121    pub functions: FunctionSet<S>,
122
123    /// Jobs managed in the environment
124    pub jobs: JobList,
125
126    /// Process group ID of the main shell process
127    pub main_pgid: Pid,
128
129    /// Process ID of the main shell process
130    ///
131    /// This PID represents the value of the `$` special parameter.
132    pub main_pid: Pid,
133
134    /// Shell option settings
135    pub options: OptionSet,
136
137    /// Runtime execution context stack
138    pub stack: Stack,
139
140    /// Traps defined in the environment
141    pub traps: TrapSet,
142
143    /// File descriptor to the controlling terminal
144    ///
145    /// [`get_tty`](Self::get_tty) saves a file descriptor in this variable, so
146    /// you don't have to prepare it yourself.
147    pub tty: Option<Fd>,
148
149    /// Variables and positional parameters defined in the environment
150    pub variables: VariableSet,
151
152    /// Abstract container that can store any type-erased data
153    pub any: DataSet,
154
155    /// Interface to the system-managed parts of the environment
156    pub system: SharedSystem<S>,
157}
158
159impl<S> Env<S> {
160    /// Creates a new environment with the given system.
161    ///
162    /// Members of the new environments are default-constructed except that:
163    /// - `main_pid` is initialized as `system.getpid()`
164    /// - `system` is initialized as `SharedSystem::new(system)`
165    #[must_use]
166    pub fn with_system(system: S) -> Self
167    where
168        S: GetPid,
169    {
170        Env {
171            aliases: Default::default(),
172            arg0: Default::default(),
173            builtins: Default::default(),
174            exit_status: Default::default(),
175            functions: Default::default(),
176            jobs: Default::default(),
177            main_pgid: system.getpgrp(),
178            main_pid: system.getpid(),
179            options: Default::default(),
180            stack: Default::default(),
181            traps: Default::default(),
182            tty: Default::default(),
183            variables: Default::default(),
184            any: Default::default(),
185            system: SharedSystem::new(system),
186        }
187    }
188
189    /// Clones this environment.
190    ///
191    /// The application-managed parts of the environment are cloned normally.
192    /// The system-managed parts are replaced with the provided `System`
193    /// instance.
194    #[must_use]
195    pub fn clone_with_system(&self, system: S) -> Self {
196        Env {
197            aliases: self.aliases.clone(),
198            arg0: self.arg0.clone(),
199            builtins: self.builtins.clone(),
200            exit_status: self.exit_status,
201            functions: self.functions.clone(),
202            jobs: self.jobs.clone(),
203            main_pgid: self.main_pgid,
204            main_pid: self.main_pid,
205            options: self.options,
206            stack: self.stack.clone(),
207            traps: self.traps.clone(),
208            tty: self.tty,
209            variables: self.variables.clone(),
210            any: self.any.clone(),
211            system: SharedSystem::new(system),
212        }
213    }
214}
215
216impl Env<VirtualSystem> {
217    /// Creates a new environment with a default-constructed [`VirtualSystem`].
218    #[must_use]
219    pub fn new_virtual() -> Self {
220        Env::with_system(VirtualSystem::default())
221    }
222}
223
224impl<S> Env<S> {
225    /// Initializes default variables.
226    ///
227    /// This function assigns the following variables to `self`:
228    ///
229    /// - `IFS=' \t\n'`
230    /// - `OPTIND=1`
231    /// - `PS1='$ '`
232    /// - `PS2='> '`
233    /// - `PS4='+ '`
234    /// - `PPID=(parent process ID)`
235    /// - `PWD=(current working directory)` (See [`Env::prepare_pwd`])
236    ///
237    /// This function ignores any errors that may occur.
238    ///
239    /// TODO: PS1 should be set to `"# "` for root users.
240    pub fn init_variables(&mut self)
241    where
242        S: Fstat + GetCwd + GetPid,
243    {
244        self.variables.init();
245
246        self.variables
247            .get_or_new(PPID, Scope::Global)
248            .assign(self.system.getppid().to_string(), None)
249            .ok();
250
251        self.prepare_pwd().ok();
252    }
253
254    /// Waits for some signals to be caught in the current process.
255    ///
256    /// Returns an array of signals caught.
257    ///
258    /// This function is a wrapper for [`SharedSystem::wait_for_signals`].
259    /// Before the function returns, it passes the results to
260    /// [`TrapSet::catch_signal`] so the trap set can remember the signals
261    /// caught to be handled later.
262    pub async fn wait_for_signals(&mut self) -> Rc<[signal::Number]> {
263        let result = self.system.wait_for_signals().await;
264        for signal in result.iter().copied() {
265            self.traps.catch_signal(signal);
266        }
267        result
268    }
269
270    /// Waits for a specific signal to be caught in the current process.
271    ///
272    /// This function calls [`wait_for_signals`](Self::wait_for_signals)
273    /// repeatedly until it returns results containing the specified `signal`.
274    pub async fn wait_for_signal(&mut self, signal: signal::Number) {
275        while !self.wait_for_signals().await.contains(&signal) {}
276    }
277
278    /// Returns signals that have been caught.
279    ///
280    /// This function is similar to
281    /// [`wait_for_signals`](Self::wait_for_signals) but does not wait for
282    /// signals to be caught. Instead, it only checks if any signals have been
283    /// caught but not yet consumed in the [`SharedSystem`].
284    pub fn poll_signals(&mut self) -> Option<Rc<[signal::Number]>>
285    where
286        S: Select + CaughtSignals + Clock,
287    {
288        let system = self.system.clone();
289
290        let mut future = std::pin::pin!(self.wait_for_signals());
291
292        let mut context = Context::from_waker(Waker::noop());
293        if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
294            return Some(signals);
295        }
296
297        system.select(true).ok();
298        if let Poll::Ready(signals) = future.poll(&mut context) {
299            return Some(signals);
300        }
301        None
302    }
303
304    /// Whether error messages should be printed in color
305    ///
306    /// This function decides whether messages printed to the standard error
307    /// should contain ANSI color escape sequences. The result is true only if
308    /// the standard error is a terminal.
309    ///
310    /// The current implementation simply checks if the standard error is a
311    /// terminal. This will be changed in the future to support user
312    /// configuration.
313    #[must_use]
314    fn should_print_error_in_color(&self) -> bool
315    where
316        S: Isatty,
317    {
318        // TODO Enable color depending on user config (force/auto/never)
319        // TODO Check if the terminal really supports color (needs terminfo)
320        self.system.isatty(Fd::STDERR)
321    }
322
323    /// Returns a file descriptor to the controlling terminal.
324    ///
325    /// This function returns `self.tty` if it is `Some` FD. Otherwise, it
326    /// opens `/dev/tty` and saves the new FD to `self.tty` before returning it.
327    pub fn get_tty(&mut self) -> Result<Fd, Errno>
328    where
329        S: Open + Dup + Close,
330    {
331        if let Some(fd) = self.tty {
332            return Ok(fd);
333        }
334
335        let first_fd = {
336            // POSIX.1-2024 Job control specifications are written in the
337            // assumption that a job-control shell may not have a control
338            // terminal. The shell should not make an arbitrary terminal its
339            // control terminal, so we open /dev/tty with NoCtty.
340            let mut result = self.system.open(
341                c"/dev/tty",
342                OfdAccess::ReadWrite,
343                OpenFlag::CloseOnExec | OpenFlag::NoCtty,
344                Mode::empty(),
345            );
346            if result == Err(Errno::EINVAL) {
347                // However, some systems do not support NoCtty. In that case,
348                // we open /dev/tty without NoCtty.
349                result = self.system.open(
350                    c"/dev/tty",
351                    OfdAccess::ReadWrite,
352                    OpenFlag::CloseOnExec.into(),
353                    Mode::empty(),
354                );
355            }
356            result?
357        };
358
359        let final_fd = io::move_fd_internal(&self.system, first_fd);
360        self.tty = final_fd.ok();
361        final_fd
362    }
363
364    /// Ensures the shell is in the foreground.
365    ///
366    /// If the current process belongs to the same process group as the session
367    /// leader, this function forces the current process to be in the foreground
368    /// by calling [`job::tcsetpgrp_with_block`]. Otherwise, this function
369    /// suspends the process until it is resumed in the foreground by another
370    /// job-controlling process (see [`job::tcsetpgrp_without_block`]).
371    ///
372    /// This function returns an error if the process does not have a controlling
373    /// terminal, that is, [`get_tty`](Self::get_tty) returns `Err(_)`.
374    ///
375    /// # Note on POSIX conformance
376    ///
377    /// This function implements part of the initialization of the job-control
378    /// shell. POSIX says that the shell should bring itself into the foreground
379    /// only if it is started as the controlling process (that is, the session
380    /// leader) for the terminal session. However, this function also brings the
381    /// shell into the foreground if the shell is in the same process group as
382    /// the session leader because it is unlikely that there is another
383    /// job-controlling process that can bring the shell into the foreground.
384    pub async fn ensure_foreground(&mut self) -> Result<(), Errno>
385    where
386        S: Open + Dup + Close + GetPid + Signals + Sigmask + Sigaction + TcSetPgrp,
387    {
388        let fd = self.get_tty()?;
389
390        if self.system.getsid(Pid(0)) == Ok(self.main_pgid) {
391            job::tcsetpgrp_with_block(&self.system, fd, self.main_pgid).await
392        } else {
393            job::tcsetpgrp_without_block(&self.system, fd, self.main_pgid).await
394        }
395    }
396
397    /// Waits for a subshell to terminate, suspend, or resume.
398    ///
399    /// This function waits for a subshell to change its execution state. The
400    /// `target` parameter specifies which child to wait for:
401    ///
402    /// - `-1`: any child
403    /// - `0`: any child in the same process group as the current process
404    /// - `pid`: the child whose process ID is `pid`
405    /// - `-pgid`: any child in the process group whose process group ID is `pgid`
406    ///
407    /// When [`self.system.wait`](system::Wait::wait) returned a new state of the
408    /// target, it is sent to `self.jobs` ([`JobList::update_status`]) before
409    /// being returned from this function.
410    ///
411    /// If there is no matching target, this function returns
412    /// `Err(Errno::ECHILD)`.
413    ///
414    /// If the target subshell is not job-controlled, you may want to use
415    /// [`wait_for_subshell_to_finish`](Self::wait_for_subshell_to_finish)
416    /// instead.
417    pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno>
418    where
419        S: Signals + Sigmask + Sigaction + Wait,
420    {
421        // We need to set the internal disposition before calling `wait` so we don't
422        // miss any `SIGCHLD` that may arrive between `wait` and `wait_for_signal`.
423        self.traps
424            .enable_internal_disposition_for_sigchld(&mut self.system)?;
425
426        let sigchld = self
427            .system
428            .signal_number_from_name(signal::Name::Chld)
429            .unwrap();
430        loop {
431            if let Some((pid, state)) = self.system.wait(target)? {
432                self.jobs.update_status(pid, state);
433                return Ok((pid, state));
434            }
435            self.wait_for_signal(sigchld).await;
436        }
437    }
438
439    /// Wait for a subshell to terminate or suspend.
440    ///
441    /// This function is similar to
442    /// [`wait_for_subshell`](Self::wait_for_subshell), but returns only when
443    /// the target is finished (either exited or killed by a signal) or
444    /// suspended.
445    pub async fn wait_for_subshell_to_halt(
446        &mut self,
447        target: Pid,
448    ) -> Result<(Pid, ProcessResult), Errno>
449    where
450        S: Signals + Sigmask + Sigaction + Wait,
451    {
452        loop {
453            let (pid, state) = self.wait_for_subshell(target).await?;
454            if let ProcessState::Halted(result) = state {
455                return Ok((pid, result));
456            }
457        }
458    }
459
460    /// Wait for a subshell to terminate.
461    ///
462    /// This function is similar to
463    /// [`wait_for_subshell`](Self::wait_for_subshell), but returns only when
464    /// the target is finished (either exited or killed by a signal).
465    ///
466    /// Returns the process ID of the awaited process and its exit status.
467    pub async fn wait_for_subshell_to_finish(
468        &mut self,
469        target: Pid,
470    ) -> Result<(Pid, ExitStatus), Errno>
471    where
472        S: Signals + Sigmask + Sigaction + Wait,
473    {
474        loop {
475            let (pid, result) = self.wait_for_subshell_to_halt(target).await?;
476            if !result.is_stopped() {
477                return Ok((pid, result.into()));
478            }
479        }
480    }
481
482    /// Applies all job status updates to jobs in `self.jobs`.
483    ///
484    /// This function calls [`self.system.wait`](system::Wait::wait) repeatedly until
485    /// all status updates available are applied to `self.jobs`
486    /// ([`JobList::update_status`]).
487    ///
488    /// Note that updates of subshells that are not managed in `self.jobs` are
489    /// lost when you call this function.
490    pub fn update_all_subshell_statuses(&mut self)
491    where
492        S: Wait,
493    {
494        while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
495            self.jobs.update_status(pid, state);
496        }
497    }
498}
499
500impl<S> Env<S> {
501    /// Tests whether the current environment is an interactive shell.
502    ///
503    /// This function returns true if and only if:
504    ///
505    /// - the [`Interactive`] option is `On` in `self.options`, and
506    /// - the current context is not in a subshell (no `Frame::Subshell` in `self.stack`).
507    #[must_use]
508    pub fn is_interactive(&self) -> bool {
509        self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
510    }
511
512    /// Tests whether the shell is performing job control.
513    ///
514    /// This function returns true if and only if:
515    ///
516    /// - the [`Monitor`] option is `On` in `self.options`, and
517    /// - the current context is not in a subshell (no `Frame::Subshell` in `self.stack`).
518    #[must_use]
519    pub fn controls_jobs(&self) -> bool {
520        self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
521    }
522
523    /// Get an existing variable or create a new one.
524    ///
525    /// This method is a thin wrapper around [`VariableSet::get_or_new`].
526    /// If the [`AllExport`] option is on, the variable is
527    /// [exported](VariableRefMut::export) before being returned from the
528    /// method.
529    ///
530    /// You should prefer using this method over [`VariableSet::get_or_new`] to
531    /// make sure that the [`AllExport`] option is applied.
532    pub fn get_or_create_variable<N>(&mut self, name: N, scope: Scope) -> VariableRefMut<'_>
533    where
534        N: Into<String>,
535    {
536        let mut variable = self.variables.get_or_new(name, scope);
537        if self.options.get(AllExport) == On {
538            variable.export(true);
539        }
540        variable
541    }
542
543    /// Tests whether the [`ErrExit`] option is applicable in the current context.
544    ///
545    /// This function returns true if and only if:
546    /// - the [`ErrExit`] option is on, and
547    /// - the current stack has no [`Condition`] frame.
548    ///
549    /// [`Condition`]: Frame::Condition
550    pub fn errexit_is_applicable(&self) -> bool {
551        self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
552    }
553
554    /// Returns a `Divert` if the shell should exit because of the [`ErrExit`]
555    /// shell option.
556    ///
557    /// The function returns `Break(Divert::Exit(None))` if the [`errexit`
558    /// option is applicable](Self::errexit_is_applicable) and the current
559    /// `self.exit_status` is non-zero. Otherwise, it returns `Continue(())`.
560    pub fn apply_errexit(&self) -> ControlFlow<Divert> {
561        if !self.exit_status.is_successful() && self.errexit_is_applicable() {
562            Break(Divert::Exit(None))
563        } else {
564            Continue(())
565        }
566    }
567
568    /// Updates the exit status from the given result.
569    ///
570    /// If `result` is a `Break(divert)` where `divert.exit_status()` is `Some`
571    /// exit status, this function sets `self.exit_status` to that exit status.
572    pub fn apply_result(&mut self, result: crate::semantics::Result) {
573        match result {
574            Continue(_) => {}
575            Break(divert) => {
576                if let Some(exit_status) = divert.exit_status() {
577                    self.exit_status = exit_status;
578                }
579            }
580        }
581    }
582}
583
584pub mod alias;
585pub mod any;
586pub mod builtin;
587pub mod decl_util;
588pub mod function;
589pub mod input;
590pub mod io;
591pub mod job;
592pub mod option;
593pub mod parser;
594pub mod prompt;
595pub mod pwd;
596pub mod semantics;
597pub mod signal;
598pub mod source;
599pub mod stack;
600pub mod subshell;
601pub mod system;
602pub mod trap;
603pub mod variable;
604
605#[cfg(test)]
606mod tests {
607    use super::*;
608    use crate::io::MIN_INTERNAL_FD;
609    use crate::job::Job;
610    use crate::source::Location;
611    use crate::subshell::Subshell;
612    use crate::system::r#virtual::FileBody;
613    use crate::system::r#virtual::Inode;
614    use crate::system::r#virtual::SIGCHLD;
615    use crate::system::r#virtual::SystemState;
616    use crate::trap::Action;
617    use assert_matches::assert_matches;
618    use futures_executor::LocalPool;
619    use futures_util::FutureExt as _;
620    use futures_util::task::LocalSpawnExt as _;
621    use std::cell::RefCell;
622    use std::str::from_utf8;
623
624    /// Helper function to perform a test in a virtual system with an executor.
625    pub fn in_virtual_system<F, Fut, T>(f: F) -> T
626    where
627        F: FnOnce(Env<VirtualSystem>, Rc<RefCell<SystemState>>) -> Fut,
628        Fut: Future<Output = T> + 'static,
629        T: 'static,
630    {
631        let system = VirtualSystem::new();
632        let state = Rc::clone(&system.state);
633        let mut executor = futures_executor::LocalPool::new();
634        state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
635
636        let env = Env::with_system(system);
637        let shared_system = env.system.clone();
638        let task = f(env, Rc::clone(&state));
639        let mut task = executor.spawner().spawn_local_with_handle(task).unwrap();
640        loop {
641            if let Some(result) = (&mut task).now_or_never() {
642                return result;
643            }
644            executor.run_until_stalled();
645            shared_system.select(false).unwrap();
646            SystemState::select_all(&state);
647        }
648    }
649
650    /// Helper function for asserting on the content of /dev/stderr.
651    pub fn assert_stderr<F, T>(state: &RefCell<SystemState>, f: F) -> T
652    where
653        F: FnOnce(&str) -> T,
654    {
655        let stderr = state.borrow().file_system.get("/dev/stderr").unwrap();
656        let stderr = stderr.borrow();
657        assert_matches!(&stderr.body, FileBody::Regular { content, .. } => {
658            f(from_utf8(content).unwrap())
659        })
660    }
661
662    #[test]
663    fn wait_for_signal_remembers_signal_in_trap_set() {
664        in_virtual_system(|mut env, state| async move {
665            env.traps
666                .set_action(
667                    &mut env.system,
668                    SIGCHLD,
669                    Action::Command("".into()),
670                    Location::dummy(""),
671                    false,
672                )
673                .unwrap();
674            {
675                let mut state = state.borrow_mut();
676                let process = state.processes.get_mut(&env.main_pid).unwrap();
677                assert!(process.blocked_signals().contains(&SIGCHLD));
678                let _ = process.raise_signal(SIGCHLD);
679            }
680            env.wait_for_signal(SIGCHLD).await;
681
682            let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
683            assert!(trap_state.pending);
684        })
685    }
686
687    fn poll_signals_env() -> (Env<VirtualSystem>, VirtualSystem) {
688        let system = VirtualSystem::new();
689        let mut env = Env::with_system(system.clone());
690        env.traps
691            .set_action(
692                &mut env.system,
693                SIGCHLD,
694                Action::Command("".into()),
695                Location::dummy(""),
696                false,
697            )
698            .unwrap();
699        (env, system)
700    }
701
702    #[test]
703    fn poll_signals_none() {
704        let mut env = poll_signals_env().0;
705        let result = env.poll_signals();
706        assert_eq!(result, None);
707    }
708
709    #[test]
710    fn poll_signals_some() {
711        let (mut env, system) = poll_signals_env();
712        {
713            let mut state = system.state.borrow_mut();
714            let process = state.processes.get_mut(&system.process_id).unwrap();
715            assert!(process.blocked_signals().contains(&SIGCHLD));
716            let _ = process.raise_signal(SIGCHLD);
717        }
718
719        let result = env.poll_signals().unwrap();
720        assert_eq!(*result, [SIGCHLD]);
721    }
722
723    #[test]
724    fn get_tty_opens_tty() {
725        let system = VirtualSystem::new();
726        let tty = Rc::new(RefCell::new(Inode::new([])));
727        system
728            .state
729            .borrow_mut()
730            .file_system
731            .save("/dev/tty", Rc::clone(&tty))
732            .unwrap();
733        let mut env = Env::with_system(system.clone());
734
735        let fd = env.get_tty().unwrap();
736        assert!(
737            fd >= MIN_INTERNAL_FD,
738            "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
739        );
740        system
741            .with_open_file_description(fd, |ofd| {
742                assert!(Rc::ptr_eq(ofd.inode(), &tty));
743                Ok(())
744            })
745            .unwrap();
746
747        system.state.borrow_mut().file_system = Default::default();
748
749        // get_tty returns cached FD
750        let fd = env.get_tty().unwrap();
751        system
752            .with_open_file_description(fd, |ofd| {
753                assert!(Rc::ptr_eq(ofd.inode(), &tty));
754                Ok(())
755            })
756            .unwrap();
757    }
758
759    #[test]
760    fn start_and_wait_for_subshell() {
761        in_virtual_system(|mut env, _state| async move {
762            let subshell = Subshell::new(|env, _job_control| {
763                Box::pin(async { env.exit_status = ExitStatus(42) })
764            });
765            let (pid, _) = subshell.start(&mut env).await.unwrap();
766            let result = env.wait_for_subshell(pid).await;
767            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
768        });
769    }
770
771    #[test]
772    fn start_and_wait_for_subshell_with_job_list() {
773        in_virtual_system(|mut env, _state| async move {
774            let subshell = Subshell::new(|env, _job_control| {
775                Box::pin(async { env.exit_status = ExitStatus(42) })
776            });
777            let (pid, _) = subshell.start(&mut env).await.unwrap();
778            let mut job = Job::new(pid);
779            job.name = "my job".to_string();
780            let job_index = env.jobs.add(job.clone());
781            let result = env.wait_for_subshell(pid).await;
782            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
783            job.state = ProcessState::exited(42);
784            assert_eq!(env.jobs[job_index], job);
785        });
786    }
787
788    #[test]
789    fn wait_for_subshell_no_subshell() {
790        let system = VirtualSystem::new();
791        let mut executor = LocalPool::new();
792        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
793        let mut env = Env::with_system(system);
794        executor.run_until(async move {
795            let result = env.wait_for_subshell(Pid::ALL).await;
796            assert_eq!(result, Err(Errno::ECHILD));
797        });
798    }
799
800    #[test]
801    fn update_all_subshell_statuses_without_subshells() {
802        let mut env = Env::new_virtual();
803        env.update_all_subshell_statuses();
804    }
805
806    #[test]
807    fn update_all_subshell_statuses_with_subshells() {
808        let system = VirtualSystem::new();
809        let mut executor = futures_executor::LocalPool::new();
810        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
811
812        let mut env = Env::with_system(system);
813
814        let [job_1, job_2, job_3] = executor.run_until(async {
815            // Run a subshell.
816            let subshell_1 = Subshell::new(|env, _job_control| {
817                Box::pin(async { env.exit_status = ExitStatus(12) })
818            });
819            let (pid_1, _) = subshell_1.start(&mut env).await.unwrap();
820
821            // Run another subshell.
822            let subshell_2 = Subshell::new(|env, _job_control| {
823                Box::pin(async { env.exit_status = ExitStatus(35) })
824            });
825            let (pid_2, _) = subshell_2.start(&mut env).await.unwrap();
826
827            // This one will never finish.
828            let subshell_3 =
829                Subshell::new(|_env, _job_control| Box::pin(futures_util::future::pending()));
830            let (pid_3, _) = subshell_3.start(&mut env).await.unwrap();
831
832            // Yet another subshell. We don't make this into a job.
833            let subshell_4 = Subshell::new(|env, _job_control| {
834                Box::pin(async { env.exit_status = ExitStatus(100) })
835            });
836            let (_pid_4, _) = subshell_4.start(&mut env).await.unwrap();
837
838            // Create jobs.
839            let job_1 = env.jobs.add(Job::new(pid_1));
840            let job_2 = env.jobs.add(Job::new(pid_2));
841            let job_3 = env.jobs.add(Job::new(pid_3));
842            [job_1, job_2, job_3]
843        });
844
845        // Let the jobs (except job_3) finish.
846        executor.run_until_stalled();
847
848        // We're not yet updated.
849        assert_eq!(env.jobs[job_1].state, ProcessState::Running);
850        assert_eq!(env.jobs[job_2].state, ProcessState::Running);
851        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
852
853        env.update_all_subshell_statuses();
854
855        // Now we have the results.
856        // TODO assert_eq!(env.jobs[job_1].state, ProcessState::Exited(ExitStatus(12)));
857        // TODO assert_eq!(env.jobs[job_2].state, ProcessState::Exited(ExitStatus(35)));
858        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
859    }
860
861    #[test]
862    fn get_or_create_variable_with_all_export_off() {
863        let mut env = Env::new_virtual();
864        let mut a = env.get_or_create_variable("a", Scope::Global);
865        assert!(!a.is_exported);
866        a.export(true);
867        let a = env.get_or_create_variable("a", Scope::Global);
868        assert!(a.is_exported);
869    }
870
871    #[test]
872    fn get_or_create_variable_with_all_export_on() {
873        let mut env = Env::new_virtual();
874        env.options.set(AllExport, On);
875        let mut a = env.get_or_create_variable("a", Scope::Global);
876        assert!(a.is_exported);
877        a.export(false);
878        let a = env.get_or_create_variable("a", Scope::Global);
879        assert!(a.is_exported);
880    }
881
882    #[test]
883    fn errexit_on() {
884        let mut env = Env::new_virtual();
885        env.exit_status = ExitStatus::FAILURE;
886        env.options.set(ErrExit, On);
887        assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
888    }
889
890    #[test]
891    fn errexit_with_zero_exit_status() {
892        let mut env = Env::new_virtual();
893        env.options.set(ErrExit, On);
894        assert_eq!(env.apply_errexit(), Continue(()));
895    }
896
897    #[test]
898    fn errexit_in_condition() {
899        let mut env = Env::new_virtual();
900        env.exit_status = ExitStatus::FAILURE;
901        env.options.set(ErrExit, On);
902        let env = env.push_frame(Frame::Condition);
903        assert_eq!(env.apply_errexit(), Continue(()));
904    }
905
906    #[test]
907    fn errexit_off() {
908        let mut env = Env::new_virtual();
909        env.exit_status = ExitStatus::FAILURE;
910        assert_eq!(env.apply_errexit(), Continue(()));
911    }
912
913    #[test]
914    fn apply_result_with_continue() {
915        let mut env = Env::new_virtual();
916        env.apply_result(Continue(()));
917        assert_eq!(env.exit_status, ExitStatus::default());
918    }
919
920    #[test]
921    fn apply_result_with_divert_without_exit_status() {
922        let mut env = Env::new_virtual();
923        env.apply_result(Break(Divert::Exit(None)));
924        assert_eq!(env.exit_status, ExitStatus::default());
925    }
926
927    #[test]
928    fn apply_result_with_divert_with_exit_status() {
929        let mut env = Env::new_virtual();
930        env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
931        assert_eq!(env.exit_status, ExitStatus(67));
932    }
933}