use self::alias::AliasSet;
use self::any::DataSet;
use self::builtin::Builtin;
use self::function::FunctionSet;
use self::io::Fd;
use self::job::JobList;
use self::job::Pid;
use self::job::ProcessResult;
use self::job::ProcessState;
use self::option::On;
use self::option::OptionSet;
use self::option::{AllExport, ErrExit, Interactive, Monitor};
use self::semantics::Divert;
use self::semantics::ExitStatus;
use self::stack::Frame;
use self::stack::Stack;
use self::system::CaughtSignals;
use self::system::Clock;
use self::system::Close;
use self::system::Concurrent;
use self::system::Dup;
use self::system::Errno;
use self::system::Fstat;
use self::system::GetCwd;
use self::system::GetPid;
use self::system::Isatty;
use self::system::Mode;
use self::system::OfdAccess;
use self::system::Open;
use self::system::OpenFlag;
use self::system::Select;
#[allow(deprecated)]
pub use self::system::SharedSystem;
use self::system::Sigaction;
use self::system::Sigmask;
use self::system::SignalList;
use self::system::Signals;
#[allow(deprecated)]
pub use self::system::System;
use self::system::TcSetPgrp;
use self::system::Wait;
#[cfg(unix)]
pub use self::system::real::RealSystem;
pub use self::system::r#virtual::VirtualSystem;
use self::trap::TrapSet;
use self::variable::PPID;
use self::variable::Scope;
use self::variable::VariableRefMut;
use self::variable::VariableSet;
use std::collections::HashMap;
use std::fmt::Debug;
use std::ops::ControlFlow::{self, Break, Continue};
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
pub use unix_path as path;
pub use unix_str as str;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Env<S> {
pub aliases: AliasSet,
pub arg0: String,
pub builtins: HashMap<&'static str, Builtin<S>>,
pub exit_status: ExitStatus,
pub functions: FunctionSet<S>,
pub jobs: JobList,
pub main_pgid: Pid,
pub main_pid: Pid,
pub options: OptionSet,
pub stack: Stack,
pub traps: TrapSet,
pub tty: Option<Fd>,
pub variables: VariableSet,
pub any: DataSet,
pub system: Rc<Concurrent<S>>,
}
impl<S> Env<S> {
#[must_use]
pub fn with_system(system: S) -> Self
where
S: GetPid,
{
Env {
aliases: Default::default(),
arg0: Default::default(),
builtins: Default::default(),
exit_status: Default::default(),
functions: Default::default(),
jobs: Default::default(),
main_pgid: system.getpgrp(),
main_pid: system.getpid(),
options: Default::default(),
stack: Default::default(),
traps: Default::default(),
tty: Default::default(),
variables: Default::default(),
any: Default::default(),
system: Rc::new(Concurrent::new(system)),
}
}
#[must_use]
pub fn clone_with_system(&self, system: S) -> Self {
Env {
aliases: self.aliases.clone(),
arg0: self.arg0.clone(),
builtins: self.builtins.clone(),
exit_status: self.exit_status,
functions: self.functions.clone(),
jobs: self.jobs.clone(),
main_pgid: self.main_pgid,
main_pid: self.main_pid,
options: self.options,
stack: self.stack.clone(),
traps: self.traps.clone(),
tty: self.tty,
variables: self.variables.clone(),
any: self.any.clone(),
system: Rc::new(Concurrent::new(system)),
}
}
}
impl Env<VirtualSystem> {
#[must_use]
pub fn new_virtual() -> Self {
Env::with_system(VirtualSystem::default())
}
}
impl<S> Env<S> {
pub fn init_variables(&mut self)
where
S: Fstat + GetCwd + GetPid,
{
self.variables.init();
self.variables
.get_or_new(PPID, Scope::Global)
.assign(self.system.getppid().to_string(), None)
.ok();
self.prepare_pwd().ok();
}
pub async fn wait_for_signals(&mut self) -> Rc<SignalList> {
let result = self.system.wait_for_signals().await;
for signal in result.iter().copied() {
self.traps.catch_signal(signal);
}
result
}
pub async fn wait_for_signal(&mut self, signal: signal::Number) {
while !self.wait_for_signals().await.contains(&signal) {}
}
pub fn poll_signals(&mut self) -> Option<Rc<SignalList>>
where
S: Select + CaughtSignals + Clock,
{
let system = Rc::clone(&self.system);
let mut future = std::pin::pin!(self.wait_for_signals());
let mut context = Context::from_waker(Waker::noop());
if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
return Some(signals);
}
system.peek();
if let Poll::Ready(signals) = future.poll(&mut context) {
return Some(signals);
}
None
}
#[must_use]
fn should_print_error_in_color(&self) -> bool
where
S: Isatty,
{
self.system.isatty(Fd::STDERR)
}
pub async fn get_tty(&mut self) -> Result<Fd, Errno>
where
S: Open + Dup + Close,
{
if let Some(fd) = self.tty {
return Ok(fd);
}
let first_fd = {
let mut result = self
.system
.open(
c"/dev/tty",
OfdAccess::ReadWrite,
OpenFlag::CloseOnExec | OpenFlag::NoCtty,
Mode::empty(),
)
.await;
if result == Err(Errno::EINVAL) {
result = self
.system
.open(
c"/dev/tty",
OfdAccess::ReadWrite,
OpenFlag::CloseOnExec.into(),
Mode::empty(),
)
.await;
}
result?
};
let final_fd = io::move_fd_internal(&self.system, first_fd);
self.tty = final_fd.ok();
final_fd
}
pub async fn ensure_foreground(&mut self) -> Result<(), Errno>
where
S: Open + Dup + Close + GetPid + Signals + Sigmask + Sigaction + TcSetPgrp,
{
let fd = self.get_tty().await?;
if self.system.getsid(Pid(0)) == Ok(self.main_pgid) {
job::tcsetpgrp_with_block(&self.system, fd, self.main_pgid).await
} else {
job::tcsetpgrp_without_block(&self.system, fd, self.main_pgid).await
}
}
pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno>
where
S: Signals + Sigmask + Sigaction + Wait,
{
self.traps
.enable_internal_disposition_for_sigchld(&self.system)
.await?;
loop {
if let Some((pid, state)) = self.system.wait(target)? {
self.jobs.update_status(pid, state);
return Ok((pid, state));
}
self.wait_for_signal(S::SIGCHLD).await;
}
}
pub async fn wait_for_subshell_to_halt(
&mut self,
target: Pid,
) -> Result<(Pid, ProcessResult), Errno>
where
S: Signals + Sigmask + Sigaction + Wait,
{
loop {
let (pid, state) = self.wait_for_subshell(target).await?;
if let ProcessState::Halted(result) = state {
return Ok((pid, result));
}
}
}
pub async fn wait_for_subshell_to_finish(
&mut self,
target: Pid,
) -> Result<(Pid, ExitStatus), Errno>
where
S: Signals + Sigmask + Sigaction + Wait,
{
loop {
let (pid, result) = self.wait_for_subshell_to_halt(target).await?;
if !result.is_stopped() {
return Ok((pid, result.into()));
}
}
}
pub fn update_all_subshell_statuses(&mut self)
where
S: Wait,
{
while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
self.jobs.update_status(pid, state);
}
}
}
impl<S> Env<S> {
#[must_use]
pub fn is_interactive(&self) -> bool {
self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
}
#[must_use]
pub fn controls_jobs(&self) -> bool {
self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
}
pub fn get_or_create_variable<N>(&mut self, name: N, scope: Scope) -> VariableRefMut<'_>
where
N: Into<String>,
{
let mut variable = self.variables.get_or_new(name, scope);
if self.options.get(AllExport) == On {
variable.export(true);
}
variable
}
pub fn errexit_is_applicable(&self) -> bool {
self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
}
pub fn apply_errexit(&self) -> ControlFlow<Divert> {
if !self.exit_status.is_successful() && self.errexit_is_applicable() {
Break(Divert::Exit(None))
} else {
Continue(())
}
}
pub fn apply_result(&mut self, result: crate::semantics::Result) {
match result {
Continue(_) => {}
Break(divert) => {
if let Some(exit_status) = divert.exit_status() {
self.exit_status = exit_status;
}
}
}
}
}
pub mod alias;
pub mod any;
pub mod builtin;
pub mod decl_util;
pub mod function;
pub mod input;
pub mod io;
pub mod job;
pub mod option;
pub mod parser;
pub mod prompt;
pub mod pwd;
pub mod semantics;
pub mod signal;
pub mod source;
pub mod stack;
pub mod subshell;
pub mod system;
pub mod trap;
pub mod variable;
pub mod waker;
#[cfg(any(test, feature = "yash-executor"))]
mod executor_helper;
#[cfg(any(test, feature = "test-helper"))]
pub mod test_helper;
#[cfg(test)]
mod tests {
use super::*;
use crate::io::MIN_INTERNAL_FD;
use crate::job::Job;
use crate::source::Location;
use crate::subshell::Subshell;
use crate::system::r#virtual::Inode;
use crate::system::r#virtual::SIGCHLD;
use crate::test_helper::in_virtual_system;
use crate::trap::Action;
use futures_executor::LocalPool;
use futures_util::FutureExt as _;
use std::cell::RefCell;
#[test]
fn wait_for_signal_remembers_signal_in_trap_set() {
in_virtual_system(|mut env, state| async move {
env.traps
.set_action(
&env.system,
SIGCHLD,
Action::Command("".into()),
Location::dummy(""),
false,
)
.await
.unwrap();
{
let mut state = state.borrow_mut();
let process = state.processes.get_mut(&env.main_pid).unwrap();
assert!(process.blocked_signals().contains(&SIGCHLD));
let _ = process.raise_signal(SIGCHLD);
}
env.wait_for_signal(SIGCHLD).await;
let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
assert!(trap_state.pending);
})
}
fn poll_signals_env() -> (Env<VirtualSystem>, VirtualSystem) {
let system = VirtualSystem::new();
let mut env = Env::with_system(system.clone());
env.traps
.set_action(
&env.system,
SIGCHLD,
Action::Command("".into()),
Location::dummy(""),
false,
)
.now_or_never()
.unwrap()
.unwrap();
(env, system)
}
#[test]
fn poll_signals_none() {
let mut env = poll_signals_env().0;
let result = env.poll_signals();
assert_eq!(result, None);
}
#[test]
fn poll_signals_some() {
let (mut env, system) = poll_signals_env();
{
let mut state = system.state.borrow_mut();
let process = state.processes.get_mut(&system.process_id).unwrap();
assert!(process.blocked_signals().contains(&SIGCHLD));
let _ = process.raise_signal(SIGCHLD);
}
let result = env.poll_signals().unwrap();
assert_eq!(result.as_slice(), [SIGCHLD]);
}
#[test]
fn get_tty_opens_tty() {
let system = VirtualSystem::new();
let tty = Rc::new(RefCell::new(Inode::new([])));
system
.state
.borrow_mut()
.file_system
.save("/dev/tty", Rc::clone(&tty))
.unwrap();
let mut env = Env::with_system(system.clone());
let fd = env.get_tty().now_or_never().unwrap().unwrap();
assert!(
fd >= MIN_INTERNAL_FD,
"get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
);
system
.with_open_file_description(fd, |ofd| {
assert!(Rc::ptr_eq(ofd.inode(), &tty));
Ok(())
})
.unwrap();
system.state.borrow_mut().file_system = Default::default();
let fd = env.get_tty().now_or_never().unwrap().unwrap();
system
.with_open_file_description(fd, |ofd| {
assert!(Rc::ptr_eq(ofd.inode(), &tty));
Ok(())
})
.unwrap();
}
#[test]
fn start_and_wait_for_subshell() {
in_virtual_system(|mut env, _state| async move {
let subshell = Subshell::new(|env, _job_control| {
Box::pin(async { env.exit_status = ExitStatus(42) })
});
let (pid, _) = subshell.start(&mut env).await.unwrap();
let result = env.wait_for_subshell(pid).await;
assert_eq!(result, Ok((pid, ProcessState::exited(42))));
});
}
#[test]
fn start_and_wait_for_subshell_with_job_list() {
in_virtual_system(|mut env, _state| async move {
let subshell = Subshell::new(|env, _job_control| {
Box::pin(async { env.exit_status = ExitStatus(42) })
});
let (pid, _) = subshell.start(&mut env).await.unwrap();
let mut job = Job::new(pid);
job.name = "my job".to_string();
let job_index = env.jobs.add(job.clone());
let result = env.wait_for_subshell(pid).await;
assert_eq!(result, Ok((pid, ProcessState::exited(42))));
job.state = ProcessState::exited(42);
assert_eq!(env.jobs[job_index], job);
});
}
#[test]
fn wait_for_subshell_no_subshell() {
let system = VirtualSystem::new();
let mut executor = LocalPool::new();
system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
let mut env = Env::with_system(system);
executor.run_until(async move {
let result = env.wait_for_subshell(Pid::ALL).await;
assert_eq!(result, Err(Errno::ECHILD));
});
}
#[test]
fn update_all_subshell_statuses_without_subshells() {
let mut env = Env::new_virtual();
env.update_all_subshell_statuses();
}
#[test]
fn update_all_subshell_statuses_with_subshells() {
let system = VirtualSystem::new();
let mut executor = futures_executor::LocalPool::new();
system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
let mut env = Env::with_system(system);
let [job_1, job_2, job_3] = executor.run_until(async {
let subshell_1 = Subshell::new(|env, _job_control| {
Box::pin(async { env.exit_status = ExitStatus(12) })
});
let (pid_1, _) = subshell_1.start(&mut env).await.unwrap();
let subshell_2 = Subshell::new(|env, _job_control| {
Box::pin(async { env.exit_status = ExitStatus(35) })
});
let (pid_2, _) = subshell_2.start(&mut env).await.unwrap();
let subshell_3 =
Subshell::new(|_env, _job_control| Box::pin(futures_util::future::pending()));
let (pid_3, _) = subshell_3.start(&mut env).await.unwrap();
let subshell_4 = Subshell::new(|env, _job_control| {
Box::pin(async { env.exit_status = ExitStatus(100) })
});
let (_pid_4, _) = subshell_4.start(&mut env).await.unwrap();
let job_1 = env.jobs.add(Job::new(pid_1));
let job_2 = env.jobs.add(Job::new(pid_2));
let job_3 = env.jobs.add(Job::new(pid_3));
[job_1, job_2, job_3]
});
executor.run_until_stalled();
assert_eq!(env.jobs[job_1].state, ProcessState::Running);
assert_eq!(env.jobs[job_2].state, ProcessState::Running);
assert_eq!(env.jobs[job_3].state, ProcessState::Running);
env.update_all_subshell_statuses();
assert_eq!(env.jobs[job_3].state, ProcessState::Running);
}
#[test]
fn get_or_create_variable_with_all_export_off() {
let mut env = Env::new_virtual();
let mut a = env.get_or_create_variable("a", Scope::Global);
assert!(!a.is_exported);
a.export(true);
let a = env.get_or_create_variable("a", Scope::Global);
assert!(a.is_exported);
}
#[test]
fn get_or_create_variable_with_all_export_on() {
let mut env = Env::new_virtual();
env.options.set(AllExport, On);
let mut a = env.get_or_create_variable("a", Scope::Global);
assert!(a.is_exported);
a.export(false);
let a = env.get_or_create_variable("a", Scope::Global);
assert!(a.is_exported);
}
#[test]
fn errexit_on() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus::FAILURE;
env.options.set(ErrExit, On);
assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
}
#[test]
fn errexit_with_zero_exit_status() {
let mut env = Env::new_virtual();
env.options.set(ErrExit, On);
assert_eq!(env.apply_errexit(), Continue(()));
}
#[test]
fn errexit_in_condition() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus::FAILURE;
env.options.set(ErrExit, On);
let env = env.push_frame(Frame::Condition);
assert_eq!(env.apply_errexit(), Continue(()));
}
#[test]
fn errexit_off() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus::FAILURE;
assert_eq!(env.apply_errexit(), Continue(()));
}
#[test]
fn apply_result_with_continue() {
let mut env = Env::new_virtual();
env.apply_result(Continue(()));
assert_eq!(env.exit_status, ExitStatus::default());
}
#[test]
fn apply_result_with_divert_without_exit_status() {
let mut env = Env::new_virtual();
env.apply_result(Break(Divert::Exit(None)));
assert_eq!(env.exit_status, ExitStatus::default());
}
#[test]
fn apply_result_with_divert_with_exit_status() {
let mut env = Env::new_virtual();
env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
assert_eq!(env.exit_status, ExitStatus(67));
}
}