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