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;
78use self::system::Signals;
79use self::system::TcSetPgrp;
80use self::system::Wait;
81use self::system::concurrency::Select;
82use self::system::concurrency::WaitForSignals;
83#[cfg(unix)]
84pub use self::system::real::RealSystem;
85pub use self::system::r#virtual::VirtualSystem;
86use self::trap::Action;
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    /// Tests whether the SIGINT trap action is the default.
613    ///
614    /// Returns `true` if and only if SIGINT has not been set to a non-default
615    /// trap action.
616    ///
617    /// This method is useful when deciding whether a caught SIGINT should
618    /// immediately interrupt the current operation by returning
619    /// [`Divert::Interrupt`] in an [interactive](Self::is_interactive) shell.
620    /// If this method returns `true`, there is no user-defined trap for SIGINT,
621    /// so the shell can treat the signal as an immediate interrupt. If it
622    /// returns `false`, the shell should instead defer to the user-defined trap
623    /// action.
624    #[must_use]
625    pub fn sigint_has_default_action(&self) -> bool
626    where
627        S: Signals,
628    {
629        let state = self.traps.get_state(S::SIGINT).0;
630        state.is_none_or(|state| state.action == Action::Default)
631    }
632
633    /// Get an existing variable or create a new one.
634    ///
635    /// This method is a thin wrapper around [`VariableSet::get_or_new`].
636    /// If the [`AllExport`] option is on, the variable is
637    /// [exported](VariableRefMut::export) before being returned from the
638    /// method.
639    ///
640    /// You should prefer using this method over [`VariableSet::get_or_new`] to
641    /// make sure that the [`AllExport`] option is applied.
642    pub fn get_or_create_variable<N>(&mut self, name: N, scope: Scope) -> VariableRefMut<'_>
643    where
644        N: Into<String>,
645    {
646        let mut variable = self.variables.get_or_new(name, scope);
647        if self.options.get(AllExport) == On {
648            variable.export(true);
649        }
650        variable
651    }
652
653    /// Tests whether the [`ErrExit`] option is applicable in the current context.
654    ///
655    /// This function returns true if and only if:
656    /// - the [`ErrExit`] option is on, and
657    /// - the current stack has no [`Condition`] frame.
658    ///
659    /// [`Condition`]: Frame::Condition
660    pub fn errexit_is_applicable(&self) -> bool {
661        self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
662    }
663
664    /// Returns a `Divert` if the shell should exit because of the [`ErrExit`]
665    /// shell option.
666    ///
667    /// The function returns `Break(Divert::Exit(None))` if the [`errexit`
668    /// option is applicable](Self::errexit_is_applicable) and the current
669    /// `self.exit_status` is non-zero. Otherwise, it returns `Continue(())`.
670    pub fn apply_errexit(&self) -> ControlFlow<Divert> {
671        if !self.exit_status.is_successful() && self.errexit_is_applicable() {
672            Break(Divert::Exit(None))
673        } else {
674            Continue(())
675        }
676    }
677
678    /// Updates the exit status from the given result.
679    ///
680    /// If `result` is a `Break(divert)` where `divert.exit_status()` is `Some`
681    /// exit status, this function sets `self.exit_status` to that exit status.
682    pub fn apply_result(&mut self, result: crate::semantics::Result) {
683        match result {
684            Continue(_) => {}
685            Break(divert) => {
686                if let Some(exit_status) = divert.exit_status() {
687                    self.exit_status = exit_status;
688                }
689            }
690        }
691    }
692}
693
694pub mod alias;
695pub mod any;
696pub mod builtin;
697pub mod decl_util;
698pub mod function;
699pub mod input;
700pub mod io;
701pub mod job;
702pub mod option;
703pub mod parser;
704pub mod prompt;
705pub mod pwd;
706pub mod semantics;
707pub mod signal;
708pub mod source;
709pub mod stack;
710pub mod subshell;
711pub mod system;
712pub mod trap;
713pub mod variable;
714pub mod waker;
715
716mod fork;
717
718#[cfg(any(test, feature = "yash-executor"))]
719mod executor_helper;
720#[cfg(any(test, feature = "test-helper"))]
721pub mod test_helper;
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726    use crate::io::MIN_INTERNAL_FD;
727    use crate::job::Job;
728    use crate::source::Location;
729    use crate::subshell::Config;
730    use crate::system::Exit as _;
731    use crate::system::Sigset as _;
732    use crate::system::r#virtual::Inode;
733    use crate::system::r#virtual::SIGCHLD;
734    use crate::test_helper::in_virtual_system;
735    use crate::trap::Action;
736    use futures_executor::LocalPool;
737    use futures_util::FutureExt as _;
738    use std::cell::Cell;
739    use std::cell::RefCell;
740
741    #[test]
742    fn wait_for_signal_remembers_signal_in_trap_set() {
743        in_virtual_system(|mut env, state| async move {
744            env.traps
745                .set_action(
746                    &env.system,
747                    SIGCHLD,
748                    Action::Command("".into()),
749                    Location::dummy(""),
750                    false,
751                )
752                .await
753                .unwrap();
754            {
755                let mut state = state.borrow_mut();
756                let process = state.processes.get_mut(&env.main_pid).unwrap();
757                assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
758                let _ = process.raise_signal(SIGCHLD);
759            }
760            env.wait_for_signal(SIGCHLD).await;
761
762            let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
763            assert!(trap_state.pending);
764        })
765    }
766
767    fn poll_signals_env() -> (Env<Rc<Concurrent<VirtualSystem>>>, VirtualSystem) {
768        let system = VirtualSystem::new();
769        let mut env = Env::with_system(Rc::new(Concurrent::new(system.clone())));
770        env.traps
771            .set_action(
772                &env.system,
773                SIGCHLD,
774                Action::Command("".into()),
775                Location::dummy(""),
776                false,
777            )
778            .now_or_never()
779            .unwrap()
780            .unwrap();
781        (env, system)
782    }
783
784    #[test]
785    fn poll_signals_none() {
786        let mut env = poll_signals_env().0;
787        let result = env.poll_signals();
788        assert_eq!(result, None);
789    }
790
791    #[test]
792    fn poll_signals_some() {
793        let (mut env, system) = poll_signals_env();
794        {
795            let mut state = system.state.borrow_mut();
796            let process = state.processes.get_mut(&system.process_id).unwrap();
797            assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
798            let _ = process.raise_signal(SIGCHLD);
799        }
800
801        let result = env.poll_signals().unwrap();
802        assert_eq!(result.as_slice(), [SIGCHLD]);
803    }
804
805    #[test]
806    fn get_tty_opens_tty() {
807        let system = VirtualSystem::new();
808        let tty = Rc::new(RefCell::new(Inode::new([])));
809        system
810            .state
811            .borrow_mut()
812            .file_system
813            .save("/dev/tty", Rc::clone(&tty))
814            .unwrap();
815        let mut env = Env::with_system(system.clone());
816
817        let fd = env.get_tty().now_or_never().unwrap().unwrap();
818        assert!(
819            fd >= MIN_INTERNAL_FD,
820            "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
821        );
822        system
823            .with_open_file_description(fd, |ofd| {
824                assert!(Rc::ptr_eq(ofd.inode(), &tty));
825                Ok(())
826            })
827            .unwrap();
828
829        system.state.borrow_mut().file_system = Default::default();
830
831        // get_tty returns cached FD
832        let fd = env.get_tty().now_or_never().unwrap().unwrap();
833        system
834            .with_open_file_description(fd, |ofd| {
835                assert!(Rc::ptr_eq(ofd.inode(), &tty));
836                Ok(())
837            })
838            .unwrap();
839    }
840
841    #[test]
842    fn run_in_child_process_runs_child_and_returns_shared_data() {
843        in_virtual_system(|mut env, _state| async move {
844            let parent_pid = env.system.getpid();
845            let child_seen_pid = Rc::new(Cell::new(None));
846            let child_seen_ppid = Rc::new(Cell::new(None));
847            let child_seen_pid_2 = Rc::clone(&child_seen_pid);
848            let child_seen_ppid_2 = Rc::clone(&child_seen_ppid);
849
850            let (result, shared_data) = env.run_in_child_process(
851                42_u32,
852                |child_env: Env<Rc<Concurrent<VirtualSystem>>>, data| async move {
853                    assert_eq!(data, 42);
854                    child_seen_pid_2.set(Some(child_env.system.getpid()));
855                    child_seen_ppid_2.set(Some(child_env.system.getppid()));
856                    child_env.system.exit(ExitStatus(13)).await;
857                },
858            );
859
860            assert_eq!(shared_data, 42);
861            let child_pid = result.unwrap();
862            assert_ne!(child_pid, parent_pid);
863            assert_eq!(
864                env.wait_for_subshell(child_pid).await,
865                Ok((child_pid, ProcessState::exited(13)))
866            );
867            assert_eq!(child_seen_pid.get(), Some(child_pid));
868            assert_eq!(child_seen_ppid.get(), Some(parent_pid));
869        });
870    }
871
872    #[test]
873    fn run_in_child_process_passes_clone_of_parent_env_to_child() {
874        in_virtual_system(|mut env, _state| async move {
875            env.arg0 = "parent-shell".to_string();
876            env.exit_status = ExitStatus(5);
877            env.variables
878                .get_or_new("PARENT", Scope::Global)
879                .assign("before", None)
880                .unwrap();
881
882            let (result, ()) = env.run_in_child_process(
883                (),
884                |child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
885                    assert_eq!(child_env.arg0, "parent-shell");
886                    assert_eq!(child_env.exit_status, ExitStatus(5));
887                    assert_eq!(child_env.variables.get_scalar("PARENT"), Some("before"));
888                    child_env.system.exit(ExitStatus(0)).await;
889                },
890            );
891
892            let child_pid = result.unwrap();
893            _ = env.wait_for_subshell(child_pid).await;
894        })
895    }
896
897    #[test]
898    fn run_in_child_process_restores_parent_environment() {
899        in_virtual_system(|mut env, _state| async move {
900            env.arg0 = "parent-shell".to_string();
901            env.exit_status = ExitStatus(5);
902            env.variables
903                .get_or_new("PARENT", Scope::Global)
904                .assign("before", None)
905                .unwrap();
906
907            let (result, ()) = env.run_in_child_process(
908                (),
909                |mut child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
910                    child_env
911                        .variables
912                        .get_or_new("PARENT", Scope::Global)
913                        .assign("after", None)
914                        .unwrap();
915                    child_env.system.exit(ExitStatus(0)).await;
916                },
917            );
918
919            let child_pid = result.unwrap();
920            assert_eq!(
921                env.wait_for_subshell(child_pid).await,
922                Ok((child_pid, ProcessState::exited(0)))
923            );
924            assert_eq!(env.arg0, "parent-shell");
925            assert_eq!(env.exit_status, ExitStatus(5));
926            assert_eq!(env.variables.get_scalar("PARENT"), Some("before"));
927        });
928    }
929
930    #[test]
931    fn start_and_wait_for_subshell() {
932        in_virtual_system(|mut env, _state| async move {
933            let (pid, _) = Config::new()
934                .start(&mut env, async |env, _job_control| {
935                    env.exit_status = ExitStatus(42)
936                })
937                .await
938                .unwrap();
939            let result = env.wait_for_subshell(pid).await;
940            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
941        });
942    }
943
944    #[test]
945    fn start_and_wait_for_subshell_with_job_list() {
946        in_virtual_system(|mut env, _state| async move {
947            let (pid, _) = Config::new()
948                .start(&mut env, async |env, _job_control| {
949                    env.exit_status = ExitStatus(42)
950                })
951                .await
952                .unwrap();
953            let mut job = Job::new(pid);
954            job.name = "my job".to_string();
955            let job_index = env.jobs.insert(job.clone());
956            let result = env.wait_for_subshell(pid).await;
957            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
958            job.state = ProcessState::exited(42);
959            assert_eq!(env.jobs[job_index], job);
960        });
961    }
962
963    #[test]
964    fn wait_for_subshell_no_subshell() {
965        let system = VirtualSystem::new();
966        let mut executor = LocalPool::new();
967        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
968        let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
969        executor.run_until(async move {
970            let result = env.wait_for_subshell(Pid::ALL).await;
971            assert_eq!(result, Err(Errno::ECHILD));
972        });
973    }
974
975    #[test]
976    fn update_all_subshell_statuses_without_subshells() {
977        let mut env = Env::new_virtual();
978        env.update_all_subshell_statuses();
979    }
980
981    #[test]
982    fn update_all_subshell_statuses_with_subshells() {
983        let system = VirtualSystem::new();
984        let mut executor = futures_executor::LocalPool::new();
985        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
986
987        let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
988
989        let [job_1, job_2, job_3] = executor.run_until(async {
990            // Run a subshell.
991            let (pid_1, _) = Config::new()
992                .start(&mut env, async |env, _job_control| {
993                    env.exit_status = ExitStatus(12)
994                })
995                .await
996                .unwrap();
997
998            // Run another subshell.
999            let (pid_2, _) = Config::new()
1000                .start(&mut env, async |env, _job_control| {
1001                    env.exit_status = ExitStatus(35)
1002                })
1003                .await
1004                .unwrap();
1005
1006            // This one will never finish.
1007            let (pid_3, _) = Config::new()
1008                .start(&mut env, async |_env, _job_control| {
1009                    futures_util::future::pending().await
1010                })
1011                .await
1012                .unwrap();
1013
1014            // Yet another subshell. We don't make this into a job.
1015            let (_pid_4, _) = Config::new()
1016                .start(&mut env, async |env, _job_control| {
1017                    env.exit_status = ExitStatus(100)
1018                })
1019                .await
1020                .unwrap();
1021
1022            // Create jobs.
1023            let job_1 = env.jobs.insert(Job::new(pid_1));
1024            let job_2 = env.jobs.insert(Job::new(pid_2));
1025            let job_3 = env.jobs.insert(Job::new(pid_3));
1026            [job_1, job_2, job_3]
1027        });
1028
1029        // Let the jobs (except job_3) finish.
1030        executor.run_until_stalled();
1031
1032        // We're not yet updated.
1033        assert_eq!(env.jobs[job_1].state, ProcessState::Running);
1034        assert_eq!(env.jobs[job_2].state, ProcessState::Running);
1035        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1036
1037        env.update_all_subshell_statuses();
1038
1039        // Now we have the results.
1040        // TODO assert_eq!(env.jobs[job_1].state, ProcessState::Exited(ExitStatus(12)));
1041        // TODO assert_eq!(env.jobs[job_2].state, ProcessState::Exited(ExitStatus(35)));
1042        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1043    }
1044
1045    #[test]
1046    fn get_or_create_variable_with_all_export_off() {
1047        let mut env = Env::new_virtual();
1048        let mut a = env.get_or_create_variable("a", Scope::Global);
1049        assert!(!a.is_exported);
1050        a.export(true);
1051        let a = env.get_or_create_variable("a", Scope::Global);
1052        assert!(a.is_exported);
1053    }
1054
1055    #[test]
1056    fn get_or_create_variable_with_all_export_on() {
1057        let mut env = Env::new_virtual();
1058        env.options.set(AllExport, On);
1059        let mut a = env.get_or_create_variable("a", Scope::Global);
1060        assert!(a.is_exported);
1061        a.export(false);
1062        let a = env.get_or_create_variable("a", Scope::Global);
1063        assert!(a.is_exported);
1064    }
1065
1066    #[test]
1067    fn errexit_on() {
1068        let mut env = Env::new_virtual();
1069        env.exit_status = ExitStatus::FAILURE;
1070        env.options.set(ErrExit, On);
1071        assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
1072    }
1073
1074    #[test]
1075    fn errexit_with_zero_exit_status() {
1076        let mut env = Env::new_virtual();
1077        env.options.set(ErrExit, On);
1078        assert_eq!(env.apply_errexit(), Continue(()));
1079    }
1080
1081    #[test]
1082    fn errexit_in_condition() {
1083        let mut env = Env::new_virtual();
1084        env.exit_status = ExitStatus::FAILURE;
1085        env.options.set(ErrExit, On);
1086        let env = env.push_frame(Frame::Condition);
1087        assert_eq!(env.apply_errexit(), Continue(()));
1088    }
1089
1090    #[test]
1091    fn errexit_off() {
1092        let mut env = Env::new_virtual();
1093        env.exit_status = ExitStatus::FAILURE;
1094        assert_eq!(env.apply_errexit(), Continue(()));
1095    }
1096
1097    #[test]
1098    fn apply_result_with_continue() {
1099        let mut env = Env::new_virtual();
1100        env.apply_result(Continue(()));
1101        assert_eq!(env.exit_status, ExitStatus::default());
1102    }
1103
1104    #[test]
1105    fn apply_result_with_divert_without_exit_status() {
1106        let mut env = Env::new_virtual();
1107        env.apply_result(Break(Divert::Exit(None)));
1108        assert_eq!(env.exit_status, ExitStatus::default());
1109    }
1110
1111    #[test]
1112    fn apply_result_with_divert_with_exit_status() {
1113        let mut env = Env::new_virtual();
1114        env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
1115        assert_eq!(env.exit_status, ExitStatus(67));
1116    }
1117}