Skip to main content

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