use std::collections::{HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::fs::{self, OpenOptions};
use std::io;
use std::os::unix::io::IntoRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
#[cfg(test)]
use crate::args::shell_option_labels;
pub(crate) use crate::args::{OptionParseMode, parse_option_args_with_schema};
use crate::args::{shell_long_options_with_schema, shell_option_labels_with_schema};
use crate::ast::{
AndOrList, ArithmAssignOp, ArithmBinOp, ArithmExpr, ArithmUnOp, Assignment, BinOpType,
CaseClause, Command as AstCommand, CommandList, ElsePart, ForClause, FunctionDefinition,
IfClause, IoRedirect, IoRedirectOp, LoopClause, LoopType, ParameterOp, Pipeline, Program,
SimpleCommand, Word,
};
use crate::sys::{self, Runtime};
mod arithm;
mod builtins;
mod command;
mod compound;
mod driver;
mod events;
mod exec;
mod expand;
mod frontend;
mod jobs;
mod machine;
mod read;
mod redirects;
mod resolve;
mod run;
mod simple_command;
mod startup;
mod state;
mod traps;
use self::arithm::eval_arithm;
#[cfg(all(test, feature = "test-support", feature = "unix-runtime"))]
use self::arithm::{eval_arithm_binop, eval_assign_op};
use self::builtins as shell_builtins;
pub(crate) use self::builtins::{Builtin as StandardBuiltin, standard_builtin_registry};
use self::command::run_exec_command_list_array;
pub(crate) use self::driver::initialize_shell_session;
#[cfg(feature = "frontend")]
pub(crate) use self::driver::run_non_interactive;
use self::events as shell_events;
#[cfg(feature = "frontend")]
pub(crate) use self::exec::run_machine_payload;
use self::expand as shell_expand;
#[cfg(any(feature = "frontend", all(test, feature = "test-support")))]
use self::frontend::append_history_line;
pub(crate) use self::frontend::maybe_warn_vi_unsupported;
use self::frontend::{shell_errln, shell_out, shell_outln};
use self::jobs as shell_jobs;
#[cfg(feature = "frontend")]
pub(crate) use self::jobs::maybe_notify_jobs;
#[cfg(feature = "frontend")]
pub(crate) use self::machine::load_machine_payload_text;
pub(crate) use self::machine::resolve_machine_program_path;
use self::read as shell_read;
use self::redirects::{RedirectGuard, restore_assignment_values, set_assignment_values};
use self::resolve as shell_resolve;
pub use self::run::run_program;
use self::run::run_string_with_source;
pub(crate) use self::run::{run_planned_program, run_string};
pub(crate) use self::simple_command::plan_simple_command;
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(crate) use self::startup::is_login_shell_argv;
#[cfg(all(test, feature = "test-support", feature = "unix-runtime"))]
use self::startup::run;
#[cfg(feature = "frontend")]
pub(crate) use self::startup::run_interactive;
#[cfg(feature = "frontend")]
pub(crate) use self::startup::{finalize_shell_run, report_arg_error};
#[cfg(all(test, feature = "test-support", feature = "unix-runtime"))]
use self::startup::{run_with_state, should_source_profile};
use self::state::{BackgroundMachinePayload, BranchControl, JobInfo, JobState, ShellJob};
pub use self::state::{ShellState, Variable};
use self::traps as shell_traps;
#[cfg(feature = "frontend")]
pub(crate) use self::traps::clear_all_pending_traps;
use crate::embed::{DeferredWorkDetail, DeferredWorkKind};
use crate::plan::{
DeferredExpansion, DeferredPipelineStageWork, DeferredReason, PlannedAndOr as ExecAndOrList,
PlannedCommandList as ExecCommandList, PlannedLazyAst as ExecLazyAst,
PlannedLazyNode as ExecLazyNode, PlannedPipeline as ShellExecPipeline,
PlannedPipelineStage as ExecPipelineStage, PlannedProgram as ShellExecProgram,
PlannedSimpleCommand, PlannedSimpleCommandKind, PreparedExternalStagePlan,
};
pub const OPT_ALLEXPORT: u32 = 1 << 0;
pub const OPT_NOTIFY: u32 = 1 << 1;
pub const OPT_NOCLOBBER: u32 = 1 << 2;
pub const OPT_ERREXIT: u32 = 1 << 3;
pub const OPT_NOGLOB: u32 = 1 << 4;
pub const OPT_MONITOR: u32 = 1 << 6;
pub const OPT_NOEXEC: u32 = 1 << 7;
pub const OPT_IGNOREEOF: u32 = 1 << 8;
pub const OPT_NOLOG: u32 = 1 << 9;
pub const OPT_VI: u32 = 1 << 10;
pub const OPT_NOUNSET: u32 = 1 << 11;
pub const OPT_VERBOSE: u32 = 1 << 12;
pub const OPT_XTRACE: u32 = 1 << 13;
pub const VAR_EXPORT: u32 = 1 << 0;
pub const VAR_READONLY: u32 = 1 << 1;
const TRAP_EXIT: i32 = 0;
#[derive(Debug, Clone, Copy, Default)]
struct MonitorSignalState {
sigttou: Option<libc::sighandler_t>,
sigttin: Option<libc::sighandler_t>,
sigtstp: Option<libc::sighandler_t>,
}
#[derive(Debug, Clone, Default)]
struct TrapSignalState {
handlers: HashMap<i32, libc::sighandler_t>,
}
const UNSPECIFIED_UTILITIES: &[&str] = &[
"alloc",
"autoload",
"bind",
"bindkey",
"bye",
"caller",
"cap",
"chdir",
"clone",
"comparguments",
"compcall",
"compctl",
"compdescribe",
"compfiles",
"compgen",
"compgroups",
"complete",
"compquote",
"comptags",
"comptry",
"compvalues",
"declare",
"dirs",
"disable",
"disown",
"dosh",
"echotc",
"echoti",
"help",
"history",
"hist",
"let",
"local",
"login",
"logout",
"map",
"mapfile",
"popd",
"print",
"pushd",
"readarray",
"repeat",
"savehistory",
"source",
"shopt",
"stop",
"suspend",
"typeset",
"whence",
];
pub(crate) fn default_unspecified_utility_names() -> &'static [&'static str] {
UNSPECIFIED_UTILITIES
}
const SHELL_KEYWORDS: &[&str] = &[
"if", "then", "else", "elif", "fi", "for", "while", "until", "do", "done", "case", "in",
"esac", "{", "}", "!",
];
fn resolve_path(cwd: &Path, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
cwd.join(path)
}
}
fn sync_monitor_mode(state: &mut ShellState) {
if !state.manage_signals {
return;
}
let want_monitor = state.has_option(OPT_MONITOR);
if want_monitor == state.monitor_mode_active {
return;
}
if want_monitor {
state.monitor_signals = MonitorSignalState {
sigttou: Some(unsafe { libc::signal(libc::SIGTTOU, libc::SIG_IGN) }),
sigttin: Some(unsafe { libc::signal(libc::SIGTTIN, libc::SIG_IGN) }),
sigtstp: Some(unsafe { libc::signal(libc::SIGTSTP, libc::SIG_IGN) }),
};
state.monitor_mode_active = true;
return;
}
if let Some(handler) = state.monitor_signals.sigttou.take() {
let _ = unsafe { libc::signal(libc::SIGTTOU, handler) };
}
if let Some(handler) = state.monitor_signals.sigttin.take() {
let _ = unsafe { libc::signal(libc::SIGTTIN, handler) };
}
if let Some(handler) = state.monitor_signals.sigtstp.take() {
let _ = unsafe { libc::signal(libc::SIGTSTP, handler) };
}
state.monitor_mode_active = false;
}
#[cfg(test)]
#[allow(dead_code)]
fn format_options(options: u32) -> String {
shell_option_labels()
.filter_map(|(opt, ch)| ((options & opt) != 0).then_some(ch))
.collect()
}
fn format_options_with_schema(options: u32, schema: &crate::policy::ShellOptionSchema) -> String {
shell_option_labels_with_schema(schema)
.filter_map(|(opt, ch)| ((options & opt) != 0).then_some(ch))
.collect()
}
fn print_long_options(state: &ShellState) {
for (name, value) in shell_long_options_with_schema(&state.definition.option_schema) {
let mode = if (state.options & value) != 0 {
'-'
} else {
'+'
};
shell_outln(state, &format!("set {mode}o {name}"));
}
}
fn readonly_assignment_status(
state: &mut ShellState,
name: &str,
context: &str,
expansion_context: bool,
) -> i32 {
let message = if context.is_empty() {
format!("{name}: readonly variable")
} else {
format!("{context}: {name}: readonly variable")
};
shell_errln(state, &message);
if expansion_context {
state.record_expansion_error(1);
if !state.interactive {
state.exit_code = 1;
}
}
1
}
fn spawn_external<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
argv: &[String],
source_line: Option<u32>,
) -> i32 {
if let Some(status) = shell_resolve::run_shell_override(state, runtime, argv) {
return status;
}
let program = match argv.first() {
Some(program) => program.clone(),
None => return 0,
};
let path_var = state.env_get("PATH").unwrap_or("/usr/bin:/bin");
let resolved_program = match runtime.resolve_command_path(&program, path_var) {
Ok(path) => path.display().to_string(),
Err(err) => {
let resolution = shell_resolve::command_resolution_from_error(&program, &err);
shell_resolve::report_command_resolution_error(
state,
&program,
&resolution,
source_line,
);
return shell_resolve::command_failure_status(&resolution);
}
};
match runtime.spawn_external_command(
&sys::ExternalCommand {
program: resolved_program,
argv: argv.to_vec(),
env: state.exported_env(),
cwd: state.cwd.clone(),
create_process_group: false,
passed_fds: Vec::new(),
},
sys::SpawnStdio {
stdin_fd: state.stdin_fd,
stdout_fd: state.stdout_fd,
stderr_fd: state.stderr_fd,
},
&[],
sys::SpawnMode::Foreground,
) {
Ok(child) => runtime.wait_child(child.handle),
Err(err) => sys::spawn_error_exit_status(&err),
}
}
pub(crate) fn build_program_execution<'ast, R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
program: &'ast Program,
) -> ShellExecProgram<'ast> {
exec::build_program_execution(state, runtime, program)
}
pub(crate) fn build_pipeline_execution<'ast, R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
pipeline: &'ast Pipeline,
) -> ShellExecPipeline<'ast> {
exec::build_pipeline_execution(state, runtime, pipeline)
}
pub(crate) fn execute_program_plan<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
program: &ShellExecProgram<'_>,
) -> i32 {
let status = run_exec_command_list_array(state, runtime, &program.body);
shell_traps::run_pending_traps(state, runtime);
status
}
pub(crate) fn execute_pipeline_plan<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
pipeline: &ShellExecPipeline<'_>,
) -> i32 {
let previous_command_id = state.active_command_id.clone();
if previous_command_id.is_none() && shell_events::observability_enabled(state) {
state.set_active_command_id(Some(shell_events::new_command_id()));
}
let status = exec::run_exec_pipeline(state, runtime, pipeline);
shell_traps::run_pending_traps(state, runtime);
state.set_active_command_id(previous_command_id);
status
}
#[cfg(all(test, feature = "test-support", feature = "unix-runtime"))]
mod tests;