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