use crate::compsys::cache::CompsysCache;
use crate::compsys::CompInitResult;
use crate::history::HistoryEngine;
use crate::options::ZSH_OPTIONS_SET;
use crate::ported::builtin::{BREAKS, CONTFLAG};
use crate::ported::math::mathevali;
use crate::ported::modules::parameter::*;
use crate::ported::subst::singsub;
thread_local! {
pub static EVAL_RECURSION_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static CMDARG_EXITING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn run_preexec_hook(event_src: Option<&str>) {
use crate::ported::zsh_h::{interact, isset, INTERACTIVECOMMENTS, SHINSTDIN};
crate::p10k::note_exec_start(event_src);
let preexec_fn = crate::ported::utils::getshfunc("preexec"); let preexec_hook = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| {
t.get(&format!("preexec{}", crate::ported::zsh_h::HOOK_SUFFIX))
.map(|_| ())
}); if preexec_fn.is_none() && preexec_hook.is_none() {
return;
}
let mut args: Vec<String> = Vec::new(); args.push("preexec".to_string()); {
let hr = crate::ported::hist::hist_ring.lock().unwrap();
let cl = crate::ported::hist::curline.lock().unwrap();
let same = !hr.is_empty()
&& cl.as_ref().map(|c| c.histnum).unwrap_or(0)
== crate::ported::hist::curhist.load(Ordering::SeqCst);
if same {
args.push(hr.first().map(|h| h.node.nam.clone()).unwrap_or_default()); } else {
args.push(String::new()); }
}
let event_prog: Option<crate::ported::zsh_h::eprog> = event_src.and_then(|src| {
let noerrs = crate::ported::utils::noerrs_lock();
let saved_noerrs = std::mem::replace(&mut *noerrs.lock().unwrap(), 1);
let saved_errflag = errflag.load(Ordering::SeqCst);
let onc = crate::ported::lex::LEX_NOCOMMENTS.with(|c| c.get());
crate::ported::lex::LEX_NOCOMMENTS.with(|c| {
c.set(interact() && isset(SHINSTDIN) && !isset(INTERACTIVECOMMENTS))
});
let p = crate::ported::exec::parse_string(src, 0);
crate::ported::lex::LEX_NOCOMMENTS.with(|c| c.set(onc));
errflag.store(saved_errflag, Ordering::SeqCst);
*noerrs.lock().unwrap() = saved_noerrs;
p
});
let (job_text, cmdstr) = match event_prog {
Some(p) => (
crate::ported::text::getjobtext(Box::new(p.clone()), None), crate::ported::text::getpermtext(Box::new(p), None, 0), ),
None => (String::new(), String::new()),
};
args.push(crate::ported::mem::dupstring(&job_text));
args.push(cmdstr.clone());
crate::ported::utils::callhookfunc("preexec", Some(&args), 1, std::ptr::null_mut()); crate::ported::mem::zsfree(cmdstr); errflag.fetch_and(!ERRFLAG_ERROR, Ordering::SeqCst); }
pub fn exact_assoc_sub_key(sub: &str) -> Option<&str> {
match sub.strip_prefix('(') {
None => Some(sub),
Some(rest) => {
let close = rest.find(')')?;
let grp = &rest[..close];
if !grp.is_empty() && grp.chars().all(|c| c == 'e' || c == 'E') {
Some(&rest[close + 1..])
} else {
None
}
}
}
}
pub fn assoc_key_hit(name: &str, key: &str) -> Option<(bool, Option<String>)> {
let resolved = match crate::ported::params::resolve_nameref_name(name, None) {
crate::ported::params::nameref_resolution::Target { name: t_, .. } => t_,
_ => name.to_string(),
};
if magic_special_shadowed_by_nonhash(resolved.as_str()) {
return None;
}
if let Some(ht) = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(resolved.as_str()).and_then(|p| p.u_hash.clone()))
.filter(|h| !h.parnodes.is_empty())
{
return Some(match ht.parnodes.get(key) {
Some(e) if (e.node.flags as u32 & crate::ported::zsh_h::PM_UNSET) == 0 => (
true,
Some(crate::ported::params::getstrvalue(Some(
&mut crate::ported::zsh_h::value {
pm: Some(e.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
},
))),
),
_ => (false, None),
});
}
crate::ported::params::paramtab_hashed_storage()
.lock()
.ok()
.and_then(|s| {
s.get(resolved.as_str())
.map(|m| (m.contains_key(key), m.get(key).cloned()))
})
}
use crate::ported::utils::{errflag, ERRFLAG_ERROR};
use crate::ported::zsh_h::PM_UNDEFINED;
use crate::ported::zsh_h::WC_SIMPLE;
use crate::ported::zsh_h::{options, MAX_OPS};
use crate::ported::zsh_h::{PM_ARRAY, PM_HASHED, PM_INTEGER, PM_READONLY};
use parking_lot::Mutex;
use std::collections::HashSet;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
use std::fs;
use std::io::Read;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::io::FromRawFd;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::time::{SystemTime, UNIX_EPOCH};
use walkdir::WalkDir;
#[allow(unused_imports)]
pub(crate) use crate::func_body_fmt::FuncBodyFmt;
#[allow(unused_imports)]
pub(crate) use crate::ported::hist::bufferwords as bufferwords_z_tuple;
#[allow(unused_imports)]
pub(crate) use crate::ported::math::{parse_assign, parse_compound, parse_pre_inc};
#[allow(unused_imports)]
pub use crate::ported::params::convbase as format_int_in_base;
pub use crate::ported::params::convbase_underscore;
#[allow(unused_imports)]
pub(crate) use crate::ported::utils::base64_decode;
#[allow(unused_imports)]
pub(crate) use crate::ported::utils::{ispwd, printprompt4, quotedzputs};
pub(crate) use crate::intercepts::intercept_matches;
pub use crate::intercepts::{AdviceKind, Intercept};
pub use crate::compinit_bg::CompInitBgResult;
use std::io::Write;
use std::sync::LazyLock;
pub(crate) use crate::plugin_cache::PluginSnapshot;
pub(crate) static REGEX_CACHE: LazyLock<Mutex<HashMap<String, Regex>>> =
LazyLock::new(|| Mutex::new(HashMap::with_capacity(64)));
pub(crate) use crate::fusevm_bridge::ExecutorContext;
pub use crate::fusevm_bridge::*;
pub mod zsh_version {
include!(concat!(env!("OUT_DIR"), "/zsh_version.rs"));
}
pub(crate) static BUILTIN_NAMES: LazyLock<HashSet<String>> = LazyLock::new(|| {
let mut s: HashSet<String> = HashSet::new();
for b in crate::ported::builtin::BUILTINS.iter() {
s.insert(b.node.nam.clone());
}
for &n in crate::daemon::builtins::ZSHRS_BUILTIN_NAMES.iter() {
s.insert(n.to_string());
}
s
});
use crate::exec_jobs::{JobState, JobTable};
use crate::parse::{Redirect, RedirectOp, ShellCommand, ShellWord, VarModifier, ZshParamFlag};
use indexmap::IndexMap;
use std::collections::HashMap;
use std::env;
use std::fs::{File, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
pub use crate::bash_complete::CompSpec;
pub use crate::ported::builtin::AutoloadFlags;
pub use crate::ported::modules::zutil::zstyle_entry;
pub struct InlineEnvFrame {
pub saved: Vec<SavedInlineParam>,
pub recording: bool,
pub export: bool,
}
pub struct SavedInlineParam {
pub name: String,
pub pm: Option<crate::ported::zsh_h::param>,
pub hashed: Option<IndexMap<String, String>>,
pub prev_env: Option<String>,
}
impl InlineEnvFrame {
pub fn new() -> Self {
Self {
saved: Vec::new(),
recording: true,
export: false,
}
}
pub fn restore(self) {
for saved in self.saved.into_iter().rev() {
crate::ported::exec::restore_params(
saved.pm.into_iter().collect(),
vec![saved.name.clone()],
);
if let Ok(mut store) = crate::ported::params::paramtab_hashed_storage().lock() {
match saved.hashed {
Some(pairs) => {
store.insert(saved.name.clone(), pairs);
}
None => {
store.remove(saved.name.as_str());
}
}
}
match saved.prev_env {
Some(v) => std::env::set_var(&saved.name, &v),
None => std::env::remove_var(&saved.name),
}
}
}
}
impl Default for InlineEnvFrame {
fn default() -> Self {
Self::new()
}
}
pub struct CompGlobalsSnap {
strings: Vec<String>,
words: Vec<String>,
redirs: Vec<String>,
patmatch: Option<String>,
ints: [i64; 4],
rpms: Option<Vec<Option<String>>>,
kpms: Option<Vec<Option<String>>>,
}
fn comp_string_globals() -> [&'static std::sync::OnceLock<std::sync::Mutex<String>>; 25] {
use crate::ported::zle::complete as c;
[
&c::COMPPREFIX,
&c::COMPSUFFIX,
&c::COMPLASTPREFIX,
&c::COMPLASTSUFFIX,
&c::COMPIPREFIX,
&c::COMPISUFFIX,
&c::COMPQIPREFIX,
&c::COMPQISUFFIX,
&c::COMPQUOTE,
&c::COMPQUOTING,
&c::COMPQSTACK,
&c::COMPLIST,
&c::COMPCONTEXT,
&c::COMPPARAMETER,
&c::COMPREDIRECT,
&c::COMPPATINSERT,
&c::COMPLASTPROMPT,
&c::COMPVARED,
&c::COMPRESTORE,
&c::COMPINSERT,
&c::COMPEXACT,
&c::COMPEXACTSTR,
&c::COMPTOEND,
&c::COMPOLDLIST,
&c::COMPOLDINS,
]
}
impl CompGlobalsSnap {
pub fn save() -> Self {
use crate::ported::zle::complete as c;
use std::sync::atomic::Ordering::Relaxed;
let get = |g: &'static std::sync::OnceLock<std::sync::Mutex<String>>| {
g.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
.map(|v| v.clone())
.unwrap_or_default()
};
let getv = |g: &'static std::sync::OnceLock<std::sync::Mutex<Vec<String>>>| {
g.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|v| v.clone())
.unwrap_or_default()
};
CompGlobalsSnap {
strings: comp_string_globals().iter().map(|g| get(g)).collect(),
words: getv(&c::COMPWORDS),
redirs: getv(&c::COMPREDIRS),
patmatch: crate::ported::zle::compcore::comppatmatch
.get()
.and_then(|m| m.lock().ok().map(|v| v.clone())),
ints: [
c::COMPCURRENT.load(Relaxed),
c::COMPLISTMAX.load(Relaxed),
c::COMPLISTLINES.load(Relaxed),
c::COMPIGNORED.load(Relaxed),
],
rpms: c::comprpms.lock().map(|g| g.clone()).unwrap_or_default(),
kpms: c::compkpms.lock().map(|g| g.clone()).unwrap_or_default(),
}
}
pub fn restore(self) {
use crate::ported::zle::complete as c;
use std::sync::atomic::Ordering::Relaxed;
for (g, v) in comp_string_globals().iter().zip(self.strings) {
if let Ok(mut s) = g.get_or_init(|| std::sync::Mutex::new(String::new())).lock() {
*s = v;
}
}
if let Ok(mut g) = c::COMPWORDS.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock() {
*g = self.words;
}
if let Ok(mut g) = c::COMPREDIRS.get_or_init(|| std::sync::Mutex::new(Vec::new())).lock() {
*g = self.redirs;
}
if let Some(pm) = self.patmatch {
if let Ok(mut g) = crate::ported::zle::compcore::comppatmatch
.get_or_init(|| std::sync::Mutex::new(String::new()))
.lock()
{
*g = pm;
}
}
c::COMPCURRENT.store(self.ints[0], Relaxed);
c::COMPLISTMAX.store(self.ints[1], Relaxed);
c::COMPLISTLINES.store(self.ints[2], Relaxed);
c::COMPIGNORED.store(self.ints[3], Relaxed);
if let Ok(mut g) = c::comprpms.lock() {
*g = self.rpms;
}
if let Ok(mut g) = c::compkpms.lock() {
*g = self.kpms;
}
}
}
pub struct SubshellSnapshot {
pub paramtab: crate::ported::hashtable::hashtable_nodes<crate::ported::zsh_h::Param>,
pub paramtab_hashed_storage: crate::cow_map::CowHashMap<String, IndexMap<String, String>>,
pub comp_globals: CompGlobalsSnap,
pub positional_params: Vec<String>,
pub special_globals: Vec<(String, String)>,
pub flock_fds: Vec<i32>,
pub loop_flags: (i32, i32, i32),
pub cmdoutval: i32,
pub stopmsg: i32,
pub traps: HashMap<String, String>,
pub opts: HashMap<String, bool>,
pub tables: crate::ported::exec::SubshForkCopy,
pub shfuncs: std::sync::Arc<crate::ported::hashtable::shfunc_table>,
pub functions_compiled: crate::cow_map::CowHashMap<String, fusevm::Chunk>,
pub function_source: crate::cow_map::CowHashMap<String, String>,
pub thingytab: HashMap<String, crate::ported::zle::zle_thingy::Thingy>,
pub keymapnamtab:
crate::ported::hashtable::hashtable_nodes<crate::ported::zle::zle_keymap::KeymapName>,
pub lastpid: i32,
pub jobtab: Vec<crate::ported::zsh_h::job>,
pub curjob: i32,
pub prevjob: i32,
pub maxjob: usize,
pub thisjob: i32,
pub oldjobtab: Vec<crate::ported::zsh_h::job>,
pub oldmaxjob: usize,
pub fd_frame: crate::ported::exec::SubshFdFrame,
pub redir_depth: usize,
pub slots_mark: usize,
pub sigtrapped: Vec<i32>,
pub subsh: i32,
pub locale: Option<std::ffi::CString>,
}
pub(crate) fn subsh_locale_save() -> Option<std::ffi::CString> {
let cur = unsafe { libc::setlocale(libc::LC_ALL, std::ptr::null()) };
(!cur.is_null()).then(|| unsafe { std::ffi::CStr::from_ptr(cur) }.to_owned())
}
pub(crate) fn subsh_locale_restore(saved: Option<&std::ffi::CStr>) {
let Some(loc) = saved else { return };
let cur = unsafe { libc::setlocale(libc::LC_ALL, std::ptr::null()) };
if !cur.is_null() && unsafe { std::ffi::CStr::from_ptr(cur) } == loc {
return;
}
unsafe { libc::setlocale(libc::LC_ALL, loc.as_ptr()) };
crate::ported::params::clear_mbstate();
crate::ported::utils::inittyptab();
}
#[allow(unused_imports)]
pub(crate) use crate::ported::pattern::{
extract_numeric_ranges, numeric_range_contains, numeric_ranges_to_star,
};
pub static FORK_EVENTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub struct ShellExecutor {
pub scriptname: Option<String>,
pub scriptfilename: Option<String>,
pub subshell_snapshots: Vec<SubshellSnapshot>,
pub inline_env_stack: Vec<InlineEnvFrame>,
pub current_command_glob_failed: std::cell::Cell<bool>,
pub jobs: JobTable,
pub fpath: Vec<PathBuf>,
pub history: std::cell::OnceCell<Option<HistoryEngine>>,
pub(crate) process_sub_counter: u32,
pub completions: HashMap<String, CompSpec>, pub zstyles: Vec<zstyle_entry>, pub local_scope_depth: usize,
pub pending_underscore: Option<String>,
pub in_dq_context: u32,
pub in_scalar_assign: u32,
pub profiling_enabled: bool,
pub compsys_cache: std::cell::OnceCell<Option<CompsysCache>>,
pub compinit_pending: Option<(
std::sync::mpsc::Receiver<CompInitBgResult>,
std::time::Instant,
)>,
pub plugin_cache: std::cell::OnceCell<Option<crate::plugin_cache::PluginCache>>,
pub deferred_compdefs: Vec<Vec<String>>,
pub returning: Option<i32>, pub zsh_compat: bool,
pub bash_compat: bool,
pub posix_mode: bool,
pub worker_pool: std::sync::Arc<crate::worker::WorkerPool>,
pub intercepts: Vec<Intercept>,
pub async_jobs: HashMap<u32, crossbeam_channel::Receiver<(i32, String)>>,
pub next_async_id: u32,
pub redirect_scope_stack: Vec<Vec<(i32, i32)>>,
pub multios_scope_stack: Vec<Vec<(i32, std::thread::JoinHandle<()>)>>,
pub exec_redirs_permanent: bool,
pub pipe_output_pending: bool,
pub pipe_output_scope: Option<usize>,
pub redirect_failed: bool,
pub functions_compiled: crate::cow_map::CowHashMap<String, fusevm::Chunk>,
pub function_source: crate::cow_map::CowHashMap<String, String>,
pub function_line_base: HashMap<String, i64>,
pub function_def_file: HashMap<String, Option<String>>,
pub prompt_funcstack: Vec<(String, i64, Option<String>)>,
pub tied_array_to_scalar: HashMap<String, (String, String)>,
pub ztest_pass_count: std::sync::atomic::AtomicUsize,
pub ztest_fail_count: std::sync::atomic::AtomicUsize,
pub ztest_skip_count: std::sync::atomic::AtomicUsize,
pub ztest_pass_total: std::sync::atomic::AtomicUsize,
pub ztest_fail_total: std::sync::atomic::AtomicUsize,
pub ztest_skip_total: std::sync::atomic::AtomicUsize,
pub ztest_run_failed: std::sync::atomic::AtomicBool,
pub ztest_suppress_stdout: bool,
}
pub(crate) fn parse_isolated(input: &str) -> crate::parse::ZshProgram {
parse_isolated_at(input, None)
}
pub(crate) fn parse_isolated_at(input: &str, lineno: Option<u64>) -> crate::parse::ZshProgram {
use crate::ported::lex::{
tok, LEXERR, LEX_INPUT, LEX_LINENO, LEX_POS, LEX_UNGET_BUF, LEX_FILE_WINDOW_STRIN,
};
let ffi_desugared = input
.contains("rust")
.then(|| crate::rust_ffi::desugar(input));
let input: &str = ffi_desugared.as_deref().unwrap_or(input);
crate::ported::context::zcontext_save(); let saved_input = LEX_INPUT.with_borrow(|s| s.clone());
let saved_pos = LEX_POS.get();
let saved_unget = LEX_UNGET_BUF.with_borrow(|b| b.clone());
let saved_lineno = LEX_LINENO.get(); let saved_file_window = LEX_FILE_WINDOW_STRIN.get();
let saved_in_lexstop = crate::ported::input::lexstop.with(|c| c.get());
crate::ported::input::inpush("", crate::ported::zsh_h::INP_LINENO, None);
crate::ported::hist::strinbeg(0); crate::ported::parse::parse_init(input); if let Some(n) = lineno {
LEX_LINENO.set(n); }
let program = crate::ported::parse::parse();
let parse_err = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 || tok() == LEXERR;
if tok() == LEXERR && crate::ported::builtin::LASTVAL.load(Ordering::Relaxed) == 0 {
crate::ported::builtin::LASTVAL.store(1, Ordering::Relaxed); }
crate::ported::hist::strinend(); crate::ported::input::inpop();
LEX_INPUT.with_borrow_mut(|s| *s = saved_input);
LEX_POS.set(saved_pos);
LEX_UNGET_BUF.with_borrow_mut(|b| *b = saved_unget);
LEX_LINENO.set(saved_lineno); LEX_FILE_WINDOW_STRIN.set(saved_file_window);
crate::ported::input::lexstop.with(|c| c.set(saved_in_lexstop));
crate::ported::context::zcontext_restore(); if parse_err {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
}
program
}
fn zerr_prefix(sn: &str) -> String {
let lineno = crate::ported::lex::lineno();
let ll = crate::ported::params::locallevel.load(std::sync::atomic::Ordering::Relaxed);
if (crate::ported::zsh_h::unset(crate::ported::zsh_h::SHINSTDIN) || ll != 0) && lineno != 0 {
format!("{}:{}", sn, lineno)
} else {
sn.to_string()
}
}
thread_local! {
pub static AUTOLOAD_DEF_FILE: std::cell::RefCell<Vec<(String, String)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub struct AutoloadFileGuard(bool);
impl AutoloadFileGuard {
fn enter(name: &str) -> Self {
match crate::ported::hashtable::getshfuncfile(name) {
Some(f) => {
AUTOLOAD_DEF_FILE.with(|s| s.borrow_mut().push((name.to_string(), f)));
Self(true)
}
None => Self(false),
}
}
}
impl Drop for AutoloadFileGuard {
fn drop(&mut self) {
if self.0 {
AUTOLOAD_DEF_FILE.with(|s| {
s.borrow_mut().pop();
});
}
}
}
pub fn autoload_def_file(name: &str) -> Option<String> {
AUTOLOAD_DEF_FILE.with(|s| {
s.borrow()
.iter()
.rev()
.find(|(n, _)| n == name)
.map(|(_, f)| f.clone())
})
}
#[allow(non_snake_case)]
pub fn zexecve_recover(pth: &str, argv: &[String], eno: i32) -> Result<(String, Vec<String>), i32> {
if eno == libc::ENOEXEC || eno == libc::ENOENT {
let cpth = match std::ffi::CString::new(pth) {
Ok(c) => c,
Err(_) => return Err(libc::ENOENT),
};
let fd = unsafe { libc::open(cpth.as_ptr(), libc::O_RDONLY | libc::O_NOCTTY) }; if fd < 0 {
return Err(std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::ENOENT));
}
let mut buf = vec![0u8; crate::ported::exec::POUNDBANGLIMIT + 1]; let ct = unsafe {
libc::read(
fd,
buf.as_mut_ptr() as *mut libc::c_void,
crate::ported::exec::POUNDBANGLIMIT as libc::size_t,
)
}; unsafe {
libc::close(fd);
} if ct >= 0 {
let ct = ct as usize;
if ct >= 2 && buf[0] == b'#' && buf[1] == b'!' {
let mut t0 = 0;
while t0 < ct && buf[t0] != b'\n' {
t0 += 1;
} if t0 == ct {
crate::ported::utils::zwarn(&format!(
"{}: bad interpreter: {}: {}",
pth,
String::from_utf8_lossy(&buf[2..t0.min(ct)]),
std::io::Error::from_raw_os_error(eno)
));
} else {
while t0 > 0 && (buf[t0] == b' ' || buf[t0] == b'\t' || buf[t0] == b'\n') {
buf[t0] = 0;
t0 -= 1;
} let mut ptr_lo: usize = 2;
while ptr_lo < buf.len() && buf[ptr_lo] == b' ' {
ptr_lo += 1;
} let ptr2_lo = ptr_lo;
let mut ptr_hi = ptr2_lo;
while ptr_hi < buf.len() && buf[ptr_hi] != 0 && buf[ptr_hi] != b' ' {
ptr_hi += 1;
} let interp_str = String::from_utf8_lossy(&buf[ptr2_lo..ptr_hi]).into_owned();
if eno == libc::ENOENT {
let pprog = if !interp_str.starts_with('/') {
crate::ported::utils::pathprog(&interp_str)
.map(|p| p.display().to_string())
} else {
None
};
if let Some(pprog) = pprog {
let mut argv_new: Vec<String> = Vec::with_capacity(argv.len() + 2);
argv_new.push(interp_str.clone()); if ptr_hi >= buf.len() || buf[ptr_hi] == 0 {
argv_new.push(pth.to_string());
} else {
let mut rest_lo = ptr_hi + 1;
while rest_lo < buf.len() && buf[rest_lo] == b' ' {
rest_lo += 1;
}
let mut rest_hi = rest_lo;
while rest_hi < buf.len() && buf[rest_hi] != 0 {
rest_hi += 1;
}
let arg_str =
String::from_utf8_lossy(&buf[rest_lo..rest_hi]).into_owned();
argv_new.push(arg_str);
argv_new.push(pth.to_string());
}
for orig in argv.iter().skip(1) {
argv_new.push(orig.clone());
}
return Ok((pprog, argv_new)); }
crate::ported::utils::zwarn(&format!(
"{}: bad interpreter: {}: {}",
pth,
interp_str,
std::io::Error::from_raw_os_error(eno)
));
} else if ptr_hi < buf.len() && buf[ptr_hi] != 0 {
let mut rest_lo = ptr_hi + 1;
while rest_lo < buf.len() && buf[rest_lo] == b' ' {
rest_lo += 1;
}
let mut rest_hi = rest_lo;
while rest_hi < buf.len() && buf[rest_hi] != 0 {
rest_hi += 1;
}
let arg_str = String::from_utf8_lossy(&buf[rest_lo..rest_hi]).into_owned();
let mut argv_new: Vec<String> =
vec![interp_str.clone(), arg_str, pth.to_string()];
for orig in argv.iter().skip(1) {
argv_new.push(orig.clone());
}
return Ok((interp_str, argv_new)); } else {
let mut argv_new: Vec<String> = vec![interp_str.clone(), pth.to_string()];
for orig in argv.iter().skip(1) {
argv_new.push(orig.clone());
}
return Ok((interp_str, argv_new)); }
}
} else if eno == libc::ENOEXEC {
let nul_pos = buf[..ct].iter().position(|&b| b == 0); let isbinary = match nul_pos {
None => false, Some(npos) => {
let mut has_letter = false;
let mut binary = true;
for &b in &buf[..npos] {
if (b as char).is_ascii_lowercase() || b == b'$' || b == b'`' {
has_letter = true;
}
if has_letter && b == b'\n' {
binary = false; break;
}
}
binary
}
};
if !isbinary {
let mut argv_new: Vec<String> = Vec::with_capacity(argv.len() + 2);
argv_new.push("sh".to_string()); if pth.starts_with('-') || pth.starts_with('+') {
argv_new.push("-".to_string()); }
argv_new.push(pth.to_string()); for orig in argv.iter().skip(1) {
argv_new.push(orig.clone());
}
return Ok(("/bin/sh".to_string(), argv_new)); }
}
}
}
Err(eno) }
pub(crate) fn execute_spawn(
arg0: &str,
argv: &[String],
defpath: Option<&str>,
hashed: Option<&str>,
drop_argv0: bool,
in_place: bool,
) -> Result<libc::pid_t, i32> {
use crate::ported::exec::isgooderr;
if let Some(slash) = arg0.find('/') {
let lerrno = match zexecve_spawn(arg0, argv, drop_argv0, in_place) {
Ok(pid) => return Ok(pid),
Err(e) => e,
}; let dotted = arg0.starts_with('.')
&& (slash == 1 || (arg0.starts_with("..") && slash == 2));
if slash == 0 || crate::ported::zsh_h::unset(crate::ported::zsh_h::PATHDIRS) || dotted {
return Err(lerrno); }
}
let mut eno = 0;
let mut attempt = |pth: &str, dir: &str| -> Option<libc::pid_t> {
match zexecve_spawn(pth, argv, drop_argv0, in_place) {
Ok(pid) => Some(pid),
Err(ee) => {
if isgooderr(ee, dir) {
eno = ee;
}
None
}
}
};
let dir_of = |p: &str| -> String {
match p.rfind('/') {
Some(0) | None => "/".to_string(),
Some(i) => p[..i].to_string(),
}
};
if let Some(pbuf) = defpath {
if let Some(pid) = attempt(pbuf, &dir_of(pbuf)) {
return Ok(pid);
}
return Err(eno);
}
if let Some(nn) = hashed {
if let Some(pid) = attempt(nn, &dir_of(nn)) {
return Ok(pid);
}
}
let path = crate::ported::params::getaparam("path").unwrap_or_default();
for pp in &path {
let pth = if pp.is_empty() || pp == "." {
arg0.to_string() } else {
format!("{}/{}", pp, arg0) };
if pth.len() >= libc::PATH_MAX as usize {
continue; }
if let Some(pid) = attempt(&pth, pp) {
return Ok(pid); }
}
Err(eno)
}
fn zexecve_spawn(pth: &str, argv: &[String], drop_argv0: bool, in_place: bool) -> Result<libc::pid_t, i32> {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt as _;
let underscore = if pth.starts_with('/') {
pth.to_string() } else {
format!("{}/{}", crate::ported::params::getsparam("PWD").unwrap_or_default(), pth) };
let mut underscore_entry = b"_=".to_vec();
underscore_entry.extend_from_slice(&crate::ported::utils::unmetafy_str(&underscore));
let mut envp: Vec<CString> = Vec::new();
let mut have_underscore = false;
for (k, v) in std::env::vars_os() {
let k = k.as_bytes();
if drop_argv0 && k == b"ARGV0" {
continue; }
let entry = if k == b"_" {
have_underscore = true;
underscore_entry.clone() } else {
let mut e = k.to_vec();
e.push(b'=');
e.extend_from_slice(v.as_bytes());
e
};
if let Ok(c) = CString::new(entry) {
envp.push(c);
}
}
if !have_underscore {
if let Ok(c) = CString::new(underscore_entry) {
envp.push(c); }
}
let eno = match posix_spawn_argv(pth, argv, &envp, in_place) {
Ok(pid) => return Ok(pid),
Err(eno) => eno,
};
match zexecve_recover(pth, argv, eno) {
Ok((prog, newargv)) => posix_spawn_argv(&prog, &newargv, &envp, in_place).map_err(|_| eno),
Err(e) => Err(e), }
}
fn entersubsh_sigdef() -> Vec<i32> {
use crate::ported::zsh_h::{isset, INTERACTIVE, ZSIG_IGNORED};
let trapped = |sig: i32| -> i32 {
crate::ported::signals::sigtrapped
.lock()
.ok()
.and_then(|t| t.get(sig as usize).copied())
.unwrap_or(0)
};
let mut sigs = Vec::with_capacity(4);
if trapped(libc::SIGPIPE) & ZSIG_IGNORED == 0 {
sigs.push(libc::SIGPIPE); }
if isset(INTERACTIVE) {
sigs.push(libc::SIGTERM); if trapped(libc::SIGINT) & ZSIG_IGNORED == 0 {
sigs.push(libc::SIGINT); }
}
if trapped(libc::SIGQUIT) & ZSIG_IGNORED == 0 {
sigs.push(libc::SIGQUIT); }
sigs
}
fn execute_sigmask() -> libc::sigset_t {
use crate::ported::signals_h::SIGCOUNT;
use crate::ported::zsh_h::ZSIG_IGNORED;
unsafe {
let mut mask: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut mask);
libc::pthread_sigmask(libc::SIG_BLOCK, std::ptr::null(), &mut mask);
if crate::ported::signals::intrap.load(std::sync::atomic::Ordering::Relaxed) != 0 {
let trapped = crate::ported::signals::sigtrapped
.lock()
.map(|t| t.clone())
.unwrap_or_default();
for sig in 1..=SIGCOUNT {
let st = trapped.get(sig as usize).copied().unwrap_or(0);
if st != 0 && st != ZSIG_IGNORED {
libc::sigdelset(&mut mask, sig);
}
}
}
libc::sigdelset(&mut mask, libc::SIGCHLD); mask
}
}
fn posix_spawn_argv(
pth: &str,
argv: &[String],
envp: &[std::ffi::CString],
in_place: bool,
) -> Result<libc::pid_t, i32> {
use std::ffi::CString;
let cstr = |s: &str| {
let mut b = crate::ported::utils::unmetafy_str(s);
if let Some(n) = b.iter().position(|c| *c == 0) {
b.truncate(n); }
CString::new(b).unwrap_or_default()
};
let cpth = cstr(pth);
let cargs: Vec<CString> = argv.iter().map(|a| cstr(a)).collect();
let mut argv_ptrs: Vec<*mut libc::c_char> = cargs.iter().map(|c| c.as_ptr() as *mut _).collect();
argv_ptrs.push(std::ptr::null_mut());
let mut envp_ptrs: Vec<*mut libc::c_char> = envp.iter().map(|c| c.as_ptr() as *mut _).collect();
envp_ptrs.push(std::ptr::null_mut());
let sigdef = entersubsh_sigdef();
let sigmask = execute_sigmask();
unsafe {
if in_place {
crate::script_cache::capture_commit_all_on_exit(false);
crate::script_cache::try_flush_pending();
for &sig in &sigdef {
libc::signal(sig, libc::SIG_DFL);
}
libc::pthread_sigmask(libc::SIG_SETMASK, &sigmask, std::ptr::null_mut());
libc::execve(cpth.as_ptr(), argv_ptrs.as_ptr() as *const *const _, envp_ptrs.as_ptr() as *const *const _);
return Err(std::io::Error::last_os_error().raw_os_error().unwrap_or(libc::ENOEXEC));
}
let mut attrs: libc::posix_spawnattr_t = std::mem::zeroed();
let rc = libc::posix_spawnattr_init(&mut attrs);
if rc != 0 {
return Err(rc);
}
let mut dfl: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut dfl);
for &sig in &sigdef {
libc::sigaddset(&mut dfl, sig);
}
libc::posix_spawnattr_setsigdefault(&mut attrs, &dfl);
libc::posix_spawnattr_setsigmask(&mut attrs, &sigmask);
libc::posix_spawnattr_setflags(
&mut attrs,
(libc::POSIX_SPAWN_SETSIGDEF | libc::POSIX_SPAWN_SETSIGMASK) as _,
);
let mut pid: libc::pid_t = 0;
let rc = libc::posix_spawn(
&mut pid,
cpth.as_ptr(),
std::ptr::null(),
&attrs,
argv_ptrs.as_ptr(),
envp_ptrs.as_ptr(),
);
libc::posix_spawnattr_destroy(&mut attrs);
if rc != 0 {
return Err(rc);
}
Ok(pid)
}
}
pub fn open_plugin_cache() -> Option<crate::plugin_cache::PluginCache> {
let pc_path = crate::plugin_cache::default_cache_path();
if let Some(parent) = pc_path.parent() {
let _ = fs::create_dir_all(parent);
}
match crate::plugin_cache::PluginCache::open(&pc_path) {
Ok(pc) => {
let (plugins, functions) = pc.stats();
tracing::info!(
plugins,
cached_functions = functions,
path = %pc_path.display(),
"plugin_cache: sqlite opened"
);
Some(pc)
}
Err(e) => {
let corrupt = matches!(
e.sqlite_error_code(),
Some(rusqlite::ErrorCode::NotADatabase)
| Some(rusqlite::ErrorCode::DatabaseCorrupt)
);
if corrupt {
tracing::warn!(
error = %e,
path = %pc_path.display(),
"plugin_cache: corrupt — discarding and rebuilding"
);
let _ = fs::remove_file(&pc_path);
for side in ["-wal", "-shm"] {
let mut p = pc_path.clone().into_os_string();
p.push(side);
let _ = fs::remove_file(PathBuf::from(p));
}
match crate::plugin_cache::PluginCache::open(&pc_path) {
Ok(pc) => {
tracing::info!(
path = %pc_path.display(),
"plugin_cache: rebuilt after corruption"
);
Some(pc)
}
Err(e2) => {
tracing::warn!(error = %e2, "plugin_cache: reopen after discard failed");
None
}
}
} else {
tracing::warn!(error = %e, "plugin_cache: failed to open");
None
}
}
}
}
impl ShellExecutor {
pub fn set_scalar(&mut self, name: String, value: String) {
setsparam(&name, &value); }
pub fn pparams(&self) -> Vec<String> {
crate::ported::builtin::PPARAMS
.lock()
.map(|p| p.clone())
.unwrap_or_default()
}
pub fn set_pparams(&mut self, params: Vec<String>) {
if let Ok(mut p) = crate::ported::builtin::PPARAMS.lock() {
*p = params;
}
}
pub fn param_flags(&self, name: &str) -> i32 {
paramtab()
.read()
.ok()
.and_then(|t| t.get(name).map(|p| p.node.flags))
.unwrap_or(0)
}
pub fn is_readonly_param(&self, name: &str) -> bool {
let (flags, pm_level) = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| {
t.get(name).and_then(|p| {
let vis = crate::ported::modules::param_private::getprivatenode(&**p as *const _);
(!vis.is_null()).then(|| unsafe { ((*vis).node.flags as u32, (*vis).level) })
})
})
.unwrap_or((0, 0));
if (flags & crate::ported::zsh_h::PM_UNSET) != 0 && (flags & crate::ported::zsh_h::PM_DECLARED) == 0 {
return false;
}
if (flags & PM_READONLY) != 0 {
return true;
}
if (flags & crate::ported::zsh_h::PM_RO_BY_DESIGN) != 0 {
if (flags & crate::ported::zsh_h::PM_REMOVABLE) != 0 {
let ll = crate::ported::params::locallevel.load(Ordering::Relaxed);
let wrap = crate::ported::modules::param_private::private_wraplevel
.load(Ordering::Relaxed);
return !(ll == pm_level || ll > wrap); }
return true;
}
false
}
pub fn last_status(&self) -> i32 {
crate::ported::builtin::LASTVAL.load(Ordering::Relaxed)
}
pub fn set_last_status(&mut self, status: i32) {
crate::ported::builtin::LASTVAL.store(status, Ordering::Relaxed);
}
pub fn set_array(&mut self, name: String, value: Vec<String>) {
assignaparam(&name, value, crate::ported::zsh_h::ASSPM_WARN); }
pub fn set_assoc(&mut self, name: String, value: IndexMap<String, String>) {
let mut flat: Vec<String> = Vec::with_capacity(value.len() * 2);
for (k, v) in &value {
flat.push(k.clone());
flat.push(v.clone());
}
sethparam(&name, flat); }
pub fn scalar(&self, name: &str) -> Option<String> {
getsparam(name)
}
pub fn array(&self, name: &str) -> Option<Vec<String>> {
getaparam(name)
}
pub fn assoc(&self, name: &str) -> Option<IndexMap<String, String>> {
let resolved = match crate::ported::params::resolve_nameref_name(name, None) {
crate::ported::params::nameref_resolution::Target { subscript: Some(_), .. } => {
return Default::default()
}
crate::ported::params::nameref_resolution::Target { name: t_, .. } => t_,
_ => name.to_string(),
};
if let Some(flags) = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(resolved.as_str()).map(|p| p.node.flags as u32))
{
if (flags & PM_HASHED) == 0 {
return None;
}
}
if crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(resolved.as_str()).and_then(|p| p.u_hash.as_ref().map(|h| !h.parnodes.is_empty())))
.unwrap_or(false)
{
return crate::ported::subst::assoc_get(resolved.as_str());
}
paramtab_hashed_storage()
.lock()
.ok()
.and_then(|m| m.get(resolved.as_str()).cloned())
}
pub fn has_scalar(&self, name: &str) -> bool {
getsparam(name).is_some()
}
pub fn has_array(&self, name: &str) -> bool {
if name.starts_with(|c: char| c.is_ascii_digit()) {
return false;
}
let resolved = match crate::ported::params::resolve_nameref_name(name, None) {
crate::ported::params::nameref_resolution::Target { name: t_, .. } => t_,
_ => name.to_string(),
};
crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| {
t.get(resolved.as_str()).map(|p| {
(p.node.flags as u32 & crate::ported::zsh_h::PM_ARRAY) != 0 && p.u_arr.is_some()
})
})
.unwrap_or(false)
}
pub fn has_assoc(&self, name: &str) -> bool {
let resolved = match crate::ported::params::resolve_nameref_name(name, None) {
crate::ported::params::nameref_resolution::Target { subscript: Some(_), .. } => {
return Default::default()
}
crate::ported::params::nameref_resolution::Target { name: t_, .. } => t_,
_ => name.to_string(),
};
if let Some(flags) = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(resolved.as_str()).map(|p| p.node.flags as u32))
{
if (flags & PM_HASHED) == 0 {
return false;
}
}
if crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(resolved.as_str()).and_then(|p| p.u_hash.as_ref().map(|h| !h.parnodes.is_empty())))
.unwrap_or(false)
{
return true; }
paramtab_hashed_storage()
.lock()
.ok()
.map(|m| m.contains_key(resolved.as_str()))
.unwrap_or(false)
}
pub fn unset_assoc(&mut self, name: &str) {
unsetparam(name);
let _ = paramtab_hashed_storage()
.lock()
.ok()
.as_deref_mut()
.map(|m| m.remove(name));
}
pub fn set_alias(&mut self, name: String, value: String) {
if let Ok(mut tab) = crate::ported::hashtable::aliastab_lock().write() {
tab.add(crate::ported::hashtable::createaliasnode(&name, &value, 0));
}
}
pub fn set_global_alias(&mut self, name: String, value: String) {
if let Ok(mut tab) = crate::ported::hashtable::aliastab_lock().write() {
tab.add(crate::ported::hashtable::createaliasnode(
&name,
&value,
crate::ported::zsh_h::ALIAS_GLOBAL as u32,
));
}
}
pub fn set_suffix_alias(&mut self, name: String, value: String) {
if let Ok(mut tab) = crate::ported::hashtable::sufaliastab_lock().write() {
tab.add(crate::ported::hashtable::createaliasnode(
&name,
&value,
crate::ported::zsh_h::ALIAS_SUFFIX as u32,
));
}
}
pub fn alias_entries(&self) -> Vec<(String, String)> {
if let Ok(tab) = crate::ported::hashtable::aliastab_lock().read() {
tab.iter_sorted()
.into_iter()
.filter(|(_, a)| (a.node.flags & crate::ported::zsh_h::ALIAS_GLOBAL as i32) == 0)
.map(|(k, a)| (k.clone(), a.text.clone()))
.collect()
} else {
Vec::new()
}
}
pub fn global_alias_entries(&self) -> Vec<(String, String)> {
if let Ok(tab) = crate::ported::hashtable::aliastab_lock().read() {
tab.iter_sorted()
.into_iter()
.filter(|(_, a)| (a.node.flags & crate::ported::zsh_h::ALIAS_GLOBAL as i32) != 0)
.map(|(k, a)| (k.clone(), a.text.clone()))
.collect()
} else {
Vec::new()
}
}
pub fn suffix_alias_entries(&self) -> Vec<(String, String)> {
if let Ok(tab) = crate::ported::hashtable::sufaliastab_lock().read() {
tab.iter_sorted()
.into_iter()
.map(|(k, a)| (k.clone(), a.text.clone()))
.collect()
} else {
Vec::new()
}
}
pub fn unset_array(&mut self, name: &str) {
unsetparam(name);
}
pub fn unset_scalar(&mut self, name: &str) {
unsetparam(name);
}
pub fn new_worker(pool: std::sync::Arc<crate::worker::WorkerPool>) -> Self {
let raw_fpath: Vec<PathBuf> = env::var("FPATH")
.unwrap_or_default()
.split(':')
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.collect();
let host_at = raw_fpath.iter().position(|d| is_host_zsh_function_tree(d));
let mut fpath: Vec<PathBuf> = raw_fpath
.into_iter()
.filter(|d| !is_host_zsh_function_tree(d))
.collect();
if fpath.is_empty() {
fpath = default_fpath(); }
if let Some(d) = crate::bundled_functions::functions_dir() {
if d.is_dir() && !fpath.contains(&d) {
match host_at {
Some(i) if i <= fpath.len() => fpath.insert(i, d),
_ => fpath.push(d),
}
}
}
Self {
scriptname: Some("zsh".to_string()),
scriptfilename: Some("zsh".to_string()),
subshell_snapshots: Vec::new(),
inline_env_stack: Vec::new(),
current_command_glob_failed: std::cell::Cell::new(false),
jobs: JobTable::new(),
fpath,
history: std::cell::OnceCell::from(None),
completions: HashMap::new(),
process_sub_counter: 0,
zstyles: Vec::new(),
local_scope_depth: 0,
pending_underscore: None,
in_dq_context: 0,
in_scalar_assign: 0,
profiling_enabled: false,
compsys_cache: std::cell::OnceCell::from(None), compinit_pending: None,
plugin_cache: std::cell::OnceCell::from(None), deferred_compdefs: Vec::new(),
returning: None,
zsh_compat: false,
bash_compat: false,
posix_mode: false,
worker_pool: pool, intercepts: Vec::new(),
async_jobs: HashMap::new(),
next_async_id: 1,
redirect_scope_stack: Vec::new(),
multios_scope_stack: Vec::new(),
exec_redirs_permanent: false,
pipe_output_pending: false,
pipe_output_scope: None,
redirect_failed: false,
functions_compiled: crate::cow_map::CowHashMap::new(),
function_source: crate::cow_map::CowHashMap::new(),
function_line_base: HashMap::new(),
function_def_file: HashMap::new(),
prompt_funcstack: Vec::new(),
tied_array_to_scalar: HashMap::new(),
ztest_pass_count: std::sync::atomic::AtomicUsize::new(0),
ztest_fail_count: std::sync::atomic::AtomicUsize::new(0),
ztest_skip_count: std::sync::atomic::AtomicUsize::new(0),
ztest_pass_total: std::sync::atomic::AtomicUsize::new(0),
ztest_fail_total: std::sync::atomic::AtomicUsize::new(0),
ztest_skip_total: std::sync::atomic::AtomicUsize::new(0),
ztest_run_failed: std::sync::atomic::AtomicBool::new(false),
ztest_suppress_stdout: false,
}
}
pub fn new() -> Self {
tracing::debug!("ShellExecutor::new() initializing");
crate::bundled_docs::install_and_publish();
let home = unsafe {
let pw = libc::getpwuid(libc::getuid());
if pw.is_null() {
None
} else {
Some(
std::ffi::CStr::from_ptr((*pw).pw_dir)
.to_string_lossy()
.into_owned(),
)
}
}
.unwrap_or_else(|| "/".to_string()); let pwd_val = if ispwd(&home) {
home } else if let Some(p) = env::var("PWD")
.ok()
.filter(|p| p.len() < libc::PATH_MAX as usize && ispwd(p))
{
p } else {
crate::ported::compat::zgetcwd() };
let oldpwd_val = env::var("OLDPWD").unwrap_or_else(|_| pwd_val.clone());
let raw_fpath: Vec<PathBuf> = env::var("FPATH")
.unwrap_or_default()
.split(':')
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.collect();
let host_at = raw_fpath.iter().position(|d| is_host_zsh_function_tree(d));
let mut fpath: Vec<PathBuf> = raw_fpath
.into_iter()
.filter(|d| !is_host_zsh_function_tree(d))
.collect();
if fpath.is_empty() {
fpath = default_fpath();
}
if let Some(d) = crate::bundled_functions::functions_dir() {
let _ = crate::bundled_functions::ensure_installed();
if d.is_dir() && !fpath.contains(&d) {
match host_at {
Some(i) if i <= fpath.len() => fpath.insert(i, d),
_ => fpath.push(d),
}
}
}
let history = std::cell::OnceCell::new();
if opt_state_len() == 0 {
for (k, v) in Self::default_options() {
opt_state_set(&k, v);
}
}
let stamp_special_params = || {
use crate::ported::params::{paramtab, special_params};
use crate::ported::zsh_h::{PM_ARRAY, PM_DONTIMPORT, PM_SCALAR, PM_SPECIAL, PM_TIED};
if let Ok(mut tab) = paramtab().write() {
use crate::ported::zsh_h::{hashnode, param, PM_DONTIMPORT as PM_DI, PM_UNSET};
let head = &special_params[..crate::ported::params::SPECIAL_PARAMS_ZSH_START];
let tail: &[crate::ported::params::special_paramdef] = if crate::ported::zsh_h::EMULATION(
crate::ported::zsh_h::EMULATE_SH | crate::ported::zsh_h::EMULATE_KSH,
) {
crate::ported::params::special_params_sh } else {
&special_params[crate::ported::params::SPECIAL_PARAMS_ZSH_START..] };
for entry in head.iter().chain(tail.iter()) {
if crate::dash_mode::dash_faithful()
&& matches!(entry.name, "RANDOM" | "SECONDS")
{
continue;
}
let safe_pm_flags = entry.pm_flags
& (PM_TIED | PM_DI | PM_UNSET | crate::ported::zsh_h::PM_READONLY);
let mut bits = safe_pm_flags | PM_SPECIAL | entry.pm_type;
if (entry.pm_flags & crate::ported::zsh_h::PM_READONLY) != 0 {
bits |= crate::ported::zsh_h::PM_RO_BY_DESIGN;
}
if entry.pm_type == PM_ARRAY {
bits |= PM_DI;
}
let _ = PM_SCALAR;
let _ = PM_DONTIMPORT;
if let Some(pm) = tab.get_mut(entry.name) {
let was_integer =
(pm.node.flags as u32 & crate::ported::zsh_h::PM_INTEGER) != 0;
pm.node.flags |= bits as i32;
if entry.pm_type == crate::ported::zsh_h::PM_INTEGER && pm.base == 0 {
pm.base = 10;
}
if !was_integer
&& entry.pm_type == crate::ported::zsh_h::PM_INTEGER
&& pm.u_val == 0
{
if let Some(ref s) = pm.u_str {
pm.u_val = s.parse::<i64>().unwrap_or(0);
pm.u_str = None;
}
}
if let Some(peer) = entry.tied_name {
pm.ename = Some(peer.to_string());
}
} else {
let u_arr = if entry.pm_type == PM_ARRAY {
Some(Vec::new())
} else {
None
};
let pm: crate::ported::zsh_h::Param = Box::new(param {
node: hashnode {
next: None,
nam: entry.name.to_string(),
flags: (entry.pm_type as i32) | bits as i32,
},
u_data: 0,
u_tied: None,
u_arr,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: if entry.pm_type == crate::ported::zsh_h::PM_INTEGER {
10
} else {
0
},
width: 0,
env: None,
ename: entry.tied_name.map(|s| s.to_string()),
old: None,
level: 0,
});
tab.insert(entry.name.to_string(), pm);
}
let _ = entry.tied_name;
}
}
};
crate::ported::utils::inittyptab(); crate::startup_trace::mark("inittyptab");
stamp_special_params(); setsparam("WORDCHARS", "*?_-.[]~=/&;!#$%^(){}<>");
setsparam("IFS", " \t\n\0");
setsparam("OPTIND", "1");
setsparam("_", "");
let histchars_val = paramtab()
.read()
.ok()
.and_then(|t| {
t.get("histchars")
.or_else(|| t.get("HISTCHARS"))
.map(|pm| histcharsgetfn(pm))
})
.unwrap_or_else(|| "!^#".to_string());
setsparam("histchars", &histchars_val);
setsparam("TIMEFMT", crate::ported::zsh_system_h::DEFAULT_TIMEFMT);
let env_at_entry = |name: &str| -> Option<String> {
crate::ported::params::environ
.get()
.and_then(|v| {
v.iter()
.find(|(k, _)| k == name)
.map(|(_, val)| val.clone())
})
.or_else(|| std::env::var(name).ok())
};
setsparam(
"TMPPREFIX",
env_at_entry("TMPPREFIX")
.as_deref()
.unwrap_or(crate::ported::config_h::DEFAULT_TMPPREFIX),
); if getsparam("NULLCMD").map_or(true, |v| v.is_empty()) {
setsparam("NULLCMD", "cat");
}
if getsparam("READNULLCMD").map_or(true, |v| v.is_empty()) {
setsparam("READNULLCMD", crate::ported::config_h::DEFAULT_READNULLCMD);
}
let mut host_buf = [0u8; 256];
let host_rc = unsafe { libc::gethostname(host_buf.as_mut_ptr() as *mut libc::c_char, 256) }; let hostname = if host_rc == 0 {
std::ffi::CStr::from_bytes_until_nul(&host_buf)
.ok()
.and_then(|c| c.to_str().ok())
.unwrap_or("")
.to_string()
} else {
String::new()
};
setsparam("HOST", env_at_entry("HOST").as_deref().unwrap_or(&hostname)); let logname_default = {
let from_getlogin = unsafe {
let p = libc::getlogin(); if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
if from_getlogin.is_empty() {
crate::ported::utils::get_username() } else {
from_getlogin
}
};
setsparam(
"LOGNAME",
env_at_entry("LOGNAME")
.as_deref()
.unwrap_or(&logname_default),
); let dash_prompts = crate::extensions::dash_mode::dash_faithful();
if dash_prompts && std::env::var_os("IFS").is_none() {
*crate::ported::params::ifs_lock().lock().unwrap() =
Some(crate::ported::zsh_h::DEFAULT_IFS_SH.to_string());
}
let seed_prompt = |name: &str, alias: Option<&str>, default: &str| {
let alias = if dash_prompts { None } else { alias };
let default = match (dash_prompts, name) {
(true, "PS1") => {
if crate::ported::utils::privasserted() {
"# "
} else {
"$ "
}
}
(true, "PS2") => "> ",
(true, "PS4") => "+ ",
_ => default,
};
let cur = crate::ported::params::getsparam(name);
let have_param = cur.as_deref().map_or(false, |s| !s.is_empty());
if have_param {
return;
}
for candidate in std::iter::once(name).chain(alias.into_iter()) {
if let Ok(env_val) = std::env::var(candidate) {
setsparam(name, &env_val);
return;
}
}
setsparam(name, default);
};
seed_prompt("PS4", Some("PROMPT4"), "+%N:%i> ");
let interactive = crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE);
seed_prompt(
"PS1",
Some("PROMPT"),
if interactive { "%m%# " } else { "" },
);
seed_prompt(
"PS2",
Some("PROMPT2"),
if interactive { "%_> " } else { "" },
);
seed_prompt("PS3", Some("PROMPT3"), "?# ");
seed_prompt("SPROMPT", None, "zsh: correct '%R' to '%r' [nyae]? ");
for (alias, source) in &[
("PROMPT", "PS1"),
("PROMPT2", "PS2"),
("PROMPT3", "PS3"),
("PROMPT4", "PS4"),
] {
if dash_prompts {
continue;
}
if crate::ported::params::getsparam(alias).map_or(true, |s| s.is_empty()) {
if let Some(v) = crate::ported::params::getsparam(source) {
setsparam(alias, &v);
}
}
}
crate::ported::params::setiparam("MAILCHECK", 60); crate::ported::params::setiparam("KEYTIMEOUT", 40); crate::ported::params::setiparam("LISTMAX", 100); crate::ported::params::setiparam("FUNCNEST", 500);
unsafe {
libc::setlocale(libc::LC_ALL, c"".as_ptr());
}
crate::ported::hashtable::createaliastables();
crate::startup_trace::mark("createaliastables");
let mut arrays: HashMap<String, Vec<String>> = HashMap::new();
let path_dirs: Vec<String> = match env::var("PATH") {
Ok(p) => p.split(':').map(|s| s.to_string()).collect(),
Err(_) => ["/bin", "/usr/bin", "/usr/ucb", "/usr/local/bin"] .iter()
.map(|s| s.to_string())
.collect(),
};
arrays.insert("path".to_string(), path_dirs);
let mut exec = Self {
scriptname: Some("zsh".to_string()),
scriptfilename: Some("zsh".to_string()),
subshell_snapshots: Vec::new(),
inline_env_stack: Vec::new(),
current_command_glob_failed: std::cell::Cell::new(false),
jobs: JobTable::new(),
fpath,
history,
completions: HashMap::new(),
process_sub_counter: 0,
zstyles: Vec::new(),
local_scope_depth: 0,
pending_underscore: None,
in_dq_context: 0,
in_scalar_assign: 0,
profiling_enabled: false,
compsys_cache: std::cell::OnceCell::new(),
compinit_pending: None, plugin_cache: std::cell::OnceCell::new(),
deferred_compdefs: Vec::new(),
returning: None,
zsh_compat: false,
bash_compat: false,
posix_mode: false,
worker_pool: {
let config = crate::config::load();
let pool_size = crate::config::resolve_pool_size(&config.worker_pool);
std::sync::Arc::new(crate::worker::WorkerPool::new(pool_size))
},
intercepts: Vec::new(),
async_jobs: HashMap::new(),
next_async_id: 1,
redirect_scope_stack: Vec::new(),
multios_scope_stack: Vec::new(),
exec_redirs_permanent: false,
pipe_output_pending: false,
pipe_output_scope: None,
redirect_failed: false,
functions_compiled: crate::cow_map::CowHashMap::new(),
function_source: crate::cow_map::CowHashMap::new(),
function_line_base: HashMap::new(),
function_def_file: HashMap::new(),
prompt_funcstack: Vec::new(),
tied_array_to_scalar: HashMap::new(),
ztest_pass_count: std::sync::atomic::AtomicUsize::new(0),
ztest_fail_count: std::sync::atomic::AtomicUsize::new(0),
ztest_skip_count: std::sync::atomic::AtomicUsize::new(0),
ztest_pass_total: std::sync::atomic::AtomicUsize::new(0),
ztest_fail_total: std::sync::atomic::AtomicUsize::new(0),
ztest_skip_total: std::sync::atomic::AtomicUsize::new(0),
ztest_run_failed: std::sync::atomic::AtomicBool::new(false),
ztest_suppress_stdout: false,
};
crate::async_precmd::set_session_pool(std::sync::Arc::clone(&exec.worker_pool));
crate::startup_trace::mark("worker pool ready");
crate::startup_trace::mark("exec struct built");
let fpath_arr: Vec<String> = exec
.fpath
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect();
if !fpath_arr.is_empty() {
exec.set_array("fpath".to_string(), fpath_arr);
}
if let Ok(path) = env::var("PATH") {
let path_arr: Vec<String> = path
.split(':')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
if !path_arr.is_empty() {
exec.set_array("path".to_string(), path_arr);
}
}
for (scalar, arr) in [
("PATH", "path"),
("FPATH", "fpath"),
("MANPATH", "manpath"),
("CDPATH", "cdpath"),
("MODULE_PATH", "module_path"),
("PSVAR", "psvar"),
("FIGNORE", "fignore"),
("MAILPATH", "mailpath"),
] {
exec.tied_array_to_scalar
.insert(arr.to_string(), (scalar.to_string(), ":".to_string()));
}
for (k, v) in &arrays {
setaparam(k, v.clone()); }
let mut shlvl_env: Option<String> = None;
enum DeferredEnvSetfn {
Int(String, i64),
Scalar(String, String),
}
let mut deferred_env_setfns: Vec<DeferredEnvSetfn> = Vec::new();
{
use crate::ported::params::paramtab;
if let Ok(mut tab) = paramtab().write() {
use crate::ported::zsh_h::{param, PM_UNSET};
use crate::ported::zsh_h::hashnode as _hn;
use crate::ported::zsh_h::{PM_EXPORTED, PM_SCALAR};
let mut tied_env_arrays: Vec<(String, Vec<String>)> = Vec::new();
let environ_vars: Vec<(String, String)> = crate::ported::params::environ
.get()
.cloned()
.unwrap_or_else(|| std::env::vars().collect());
for (env_name, env_value) in environ_vars {
if env_name.is_empty() || env_name.contains('[') {
continue;
}
if env_name.as_bytes()[0].is_ascii_digit() {
continue;
}
if !crate::ported::params::isident(&env_name) {
continue;
}
if let Some(pm) = tab.get_mut(&env_name) {
if (pm.node.flags as u32 & crate::ported::zsh_h::PM_DONTIMPORT) != 0 {
continue; }
pm.node.flags |= PM_EXPORTED as i32;
if (pm.node.flags as u32 & crate::ported::zsh_h::PM_INTEGER) != 0 {
let (ival, _) =
crate::ported::utils::zstrtol_underscore(&env_value, 0, true); pm.node.flags &= !(PM_UNSET as i32);
pm.u_val = ival; deferred_env_setfns
.push(DeferredEnvSetfn::Int(env_name.clone(), ival)); pm.env = Some(format!("{env_name}={env_value}"));
continue;
}
let still_unset =
(pm.node.flags as u32 & crate::ported::zsh_h::PM_UNSET) != 0;
if still_unset {
pm.u_str = Some(env_value.clone());
pm.env = Some(format!("{}={}", env_name, env_value));
pm.node.flags &= !(PM_UNSET as i32);
if matches!(
env_name.as_str(),
"HOME"
| "USERNAME"
| "TERM"
| "WORDCHARS"
| "TERMINFO"
| "TERMINFO_DIRS"
) {
deferred_env_setfns.push(DeferredEnvSetfn::Scalar(
env_name.clone(),
env_value.clone(),
)); }
}
if (pm.node.flags as u32 & crate::ported::zsh_h::PM_TIED) != 0 {
if let Some(ref peer) = pm.ename {
if peer != "path" && peer != "fpath" {
tied_env_arrays.push((
peer.clone(),
env_value.split(':').map(String::from).collect(), ));
}
}
}
} else {
let pm: crate::ported::zsh_h::Param = Box::new(param {
node: _hn {
next: None,
nam: env_name.clone(),
flags: (PM_SCALAR | PM_EXPORTED) as i32,
},
u_data: 0,
u_tied: None,
u_arr: None,
u_str: Some(env_value.clone()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: Some(format!("{}={}", env_name, env_value)),
ename: None,
old: None,
level: 0,
});
tab.insert(env_name, pm);
}
}
for (peer, parts) in tied_env_arrays {
if let Some(apm) = tab.get_mut(peer.as_str()) {
apm.u_arr = Some(parts); apm.node.flags &= !(crate::ported::zsh_h::PM_UNSET as i32);
}
}
if let Some(pm) = tab.get_mut("SHLVL") {
let next = pm.u_val + 1; pm.u_val = next; deferred_env_setfns.push(DeferredEnvSetfn::Int("SHLVL".to_string(), next)); pm.node.flags &= !(PM_UNSET as i32);
shlvl_env = Some(next.to_string()); }
}
}
for deferred in deferred_env_setfns {
match deferred {
DeferredEnvSetfn::Int(name, ival) => {
let staged = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|tab| tab.get(name.as_str()).map(|pm| (**pm).clone()));
let Some(mut staged) = staged else { continue };
crate::ported::params::intsetfn(&mut staged, ival); if let Ok(mut tab) = crate::ported::params::paramtab().write() {
if let Some(pm) = tab.get_mut(name.as_str()) {
pm.u_val = staged.u_val;
}
}
}
DeferredEnvSetfn::Scalar(name, value) => {
let staged = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|tab| tab.get(name.as_str()).map(|pm| (**pm).clone()));
let Some(mut staged) = staged else { continue };
match name.as_str() {
"HOME" => crate::ported::params::homesetfn(&mut staged, value),
"USERNAME" => crate::ported::params::usernamesetfn(&mut staged, value),
"TERM" => crate::ported::params::termsetfn(&mut staged, value),
"WORDCHARS" => crate::ported::params::wordcharssetfn(&mut staged, value),
"TERMINFO" => crate::ported::params::terminfosetfn(&mut staged, value),
"TERMINFO_DIRS" => {
crate::ported::params::terminfodirssetfn(&mut staged, value)
}
_ => {}
}
}
}
}
if std::env::var_os("HOME").is_none()
&& crate::ported::zsh_h::EMULATION(crate::ported::zsh_h::EMULATE_ZSH)
{
let pswd = unsafe { libc::getpwuid(libc::getuid()) };
let pw_dir = if pswd.is_null() {
std::ptr::null()
} else {
unsafe { (*pswd).pw_dir }
};
let h = if pw_dir.is_null() {
"/".to_string()
} else {
crate::ported::utils::metafy(
&unsafe { std::ffi::CStr::from_ptr(pw_dir) }.to_string_lossy(),
)
};
crate::ported::params::setsparam("HOME", &h);
crate::ported::params::addenv("HOME", &h);
}
if let Some(v) = crate::ported::params::getsparam("LOGNAME") {
crate::ported::params::addenv("LOGNAME", &v); }
if let Some(v) = shlvl_env {
crate::ported::params::addenv("SHLVL", &v); }
crate::ported::init::init_io(None); crate::startup_trace::mark("exec: init_io");
let _ = crate::ported::utils::adjustwinsize(0); crate::startup_trace::mark("exec: adjustwinsize");
env::set_var("PWD", &pwd_val);
env::set_var("OLDPWD", &oldpwd_val);
crate::dash_mode::set_oldpwd_global(&oldpwd_val); crate::ported::builtin::set_pwd_env();
crate::startup_trace::mark("exec: set_pwd_env");
let mut uname_buf: libc::utsname = unsafe { std::mem::zeroed() };
let _ = unsafe { libc::uname(&mut uname_buf) };
let to_str = |b: &[libc::c_char]| -> String {
let bytes: Vec<u8> = b
.iter()
.take_while(|&&c| c != 0)
.map(|&c| c as u8)
.collect();
String::from_utf8_lossy(&bytes).into_owned()
};
let cputype = to_str(&uname_buf.machine);
crate::ported::params::setsparam("CPUTYPE", &cputype); crate::ported::params::setsparam("OSTYPE", crate::ported::config_h::OSTYPE); crate::ported::params::setsparam("MACHTYPE", crate::ported::config_h::MACHTYPE); crate::ported::params::setsparam("VENDOR", crate::ported::config_h::VENDOR);
let tty_str = unsafe {
let p = libc::ttyname(0);
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p)
.to_str()
.unwrap_or("")
.to_string()
}
};
crate::ported::params::setsparam("TTY", &tty_str); let argzero_default = env::args().next().unwrap_or_else(|| "zsh".to_string());
crate::ported::params::setsparam("ZSH_ARGZERO", &argzero_default); crate::ported::params::setsparam("ZSH_VERSION", crate::ported::patchlevel::ZSH_VERSION); crate::ported::params::setsparam(
"ZSH_PATCHLEVEL",
crate::ported::patchlevel::ZSH_PATCHLEVEL,
); if !crate::ext_builtins::hide_ext_builtins() {
crate::ported::params::setsparam(
"ZSHRS_VERSION",
crate::ported::patchlevel::ZSHRS_VERSION,
);
}
crate::bundled_docs::seed_helpdir_param();
{
use crate::ported::signals_h::SIGS;
let mut by_num: Vec<(&str, i32)> = SIGS.to_vec();
by_num.sort_by_key(|&(_, n)| n);
let mut signals_arr: Vec<String> = Vec::with_capacity(by_num.len() + 3);
signals_arr.push("EXIT".to_string()); signals_arr.extend(by_num.iter().map(|(n, _)| n.to_string()));
#[cfg(target_os = "linux")]
signals_arr.extend(
(libc::SIGRTMIN()..=libc::SIGRTMAX()).map(crate::ported::signals::rtsigname),
);
signals_arr.push("ZERR".to_string()); signals_arr.push("DEBUG".to_string()); crate::ported::params::setaparam("signals", signals_arr); }
crate::ported::params::setsparam("ZSH_NAME", "zsh"); crate::startup_trace::mark("exec: base params set");
crate::ported::init::ZSHHOOKS_ADDED.call_once(|| {
let base = crate::ported::init::zshhooks.load(std::sync::atomic::Ordering::SeqCst);
let _ = crate::ported::module::addhookdefs(std::ptr::null(), base, 4);
});
crate::ported::init::init_bltinmods(); crate::startup_trace::mark("exec: init_bltinmods");
init_partab_params(); crate::startup_trace::mark("exec: init_partab_params");
if crate::extensions::dash_mode::bash_mode() && std::env::var_os("TERM").is_none() {
crate::ported::params::setsparam("TERM", "dumb");
crate::ported::params::addenv("TERM", "dumb");
}
crate::startup_trace::mark("exec: pre-scriptname");
crate::ported::utils::set_scriptname(Some("zsh".to_string()));
let dash_c = std::env::args_os()
.skip(1)
.map(|a| a.to_string_lossy().into_owned())
.any(|a| a.starts_with('-') && !a.starts_with("--") && a.contains('c'));
if dash_c {
crate::ported::utils::set_scriptfilename(Some("zsh".to_string())); }
crate::startup_trace::mark("exec: pre-module_path_init");
crate::ported::init::module_path_init();
let fpath_final: Vec<String> = exec
.fpath
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect();
if !fpath_final.is_empty() {
unsafe { std::env::set_var("FPATH", fpath_final.join(":")) };
exec.set_array("fpath".to_string(), fpath_final);
}
crate::startup_trace::mark("new() end");
exec
}
pub fn history(&self) -> Option<&HistoryEngine> {
self.history
.get_or_init(|| HistoryEngine::new().ok())
.as_ref()
}
pub fn execute_script_file(&mut self, file_path: &str) -> Result<i32, String> {
let path = Path::new(file_path);
let abs_path = path
.canonicalize()
.unwrap_or_else(|_| path.to_path_buf())
.to_string_lossy()
.to_string();
let env_fp = crate::script_cache::env_fingerprint();
if let Some(bc_blob) =
crate::script_cache::try_load_bytes(path, crate::script_cache::BlobKind::Guarded, env_fp)
{
if let Ok(events) =
bincode::deserialize::<Vec<crate::script_cache::GuardedEvent>>(&bc_blob)
{
if !events.is_empty() {
tracing::trace!(
path = %abs_path,
events = events.len(),
"execute_script_file: bytecode cache hit"
);
return self.replay_cached_events(events, true, || {
crate::script_bytes::read_script_file(file_path).ok()
});
}
}
}
let content = crate::script_bytes::read_script_file(file_path)
.map_err(|e| format!("{}: {}", file_path, e))?;
crate::script_cache::capture_begin(path, env_fp, true);
let mut status = self.run_events_per_command(&content, true, true)?;
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if status == 0 {
status = 1; self.set_last_status(status);
}
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
}
let status = self.fire_script_exit_hooks(status)?;
Ok(status)
}
fn run_chunk(&mut self, chunk: fusevm::Chunk, label: &str) -> Result<i32, String> {
if chunk.ops.is_empty() {
return Ok(self.last_status());
}
crate::fusevm_disasm::maybe_print_stdout(label, &chunk);
let mut vm = crate::vm_pool::acquire(chunk);
vm.last_status = self.last_status();
let _ctx = ExecutorContext::enter(self);
let loops_entry = crate::ported::builtin::LOOPS.load(Ordering::Relaxed);
let redir_depth = self.redirect_scope_stack.len();
let slots_entry = crate::exec_jobs::execpline_slots_mark();
let result = vm.run();
crate::ported::builtin::LOOPS.store(loops_entry, Ordering::Relaxed);
crate::exec_jobs::execpline_slots_release_to(slots_entry);
self.unwind_redirect_scopes_to(redir_depth);
match result {
fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
self.set_last_status(vm.last_status);
}
fusevm::VMResult::Error(e) => return Err(format!("VM error: {}", e)),
}
Ok(self.last_status())
}
fn compile_script_isolated(&mut self, script: &str) -> Result<fusevm::Chunk, String> {
self.compile_script_isolated_at(script, None)
}
fn compile_script_isolated_at(
&mut self,
script: &str,
lineno: Option<u64>,
) -> Result<fusevm::Chunk, String> {
let saved_errflag = errflag.load(Ordering::Relaxed);
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
let program = parse_isolated_at(script, lineno);
let parse_failed = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0;
errflag.store(saved_errflag, Ordering::Relaxed);
if parse_failed {
if crate::extensions::dash_mode::dash_faithful()
&& !crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE)
{
let st = crate::extensions::dash_mode::fatal_error_status().unwrap_or(2);
crate::ported::builtin::LASTVAL.store(st, Ordering::Relaxed);
crate::ported::builtin::EXIT_VAL.store(st, Ordering::Relaxed);
crate::ported::builtin::EXIT_PENDING.store(1, Ordering::Relaxed);
}
return Err("__SILENCED__".to_string());
}
let mut compiler = crate::compile_zsh::ZshCompiler::new();
compiler.nested_lineno_base = lineno; if CMDARG_EXITING.with(|c| c.replace(false)) {
compiler.forked_tail_list = crate::compile_zsh::program_exiting_tail(&program);
compiler.exiting_tail_mode = 2;
}
Ok(compiler.compile(&program))
}
pub fn execute_cmdarg(&mut self, script: &str) -> Result<i32, String> {
CMDARG_EXITING.with(|c| c.set(true));
let chunk = self.compile_script_isolated(script);
CMDARG_EXITING.with(|c| c.set(false));
let status = self.run_chunk(chunk?, "cmdarg")?;
let saved_errflag = errflag.load(Ordering::Relaxed);
errflag.store(0, Ordering::Relaxed); let result = self.fire_script_exit_hooks(status);
errflag.fetch_or(saved_errflag, Ordering::Relaxed);
result
}
fn run_chunk_with_exit_hooks(
&mut self,
chunk: fusevm::Chunk,
label: &str,
) -> Result<i32, String> {
let status = self.run_chunk(chunk, label)?;
self.fire_script_exit_hooks(status)
}
fn fire_script_exit_hooks(&mut self, status: i32) -> Result<i32, String> {
let exit_body = crate::ported::builtin::traps_table()
.lock()
.ok()
.and_then(|mut t| t.remove("EXIT"));
if let Some(action) = exit_body {
tracing::debug!("firing EXIT trap (new pipeline)");
crate::ported::signals::intrap.fetch_add(1, Ordering::SeqCst); let trap_ctx = crate::ported::exec::EvalContextFrame::push("trap");
let _ = self.execute_script_zsh_pipeline(&action);
drop(trap_ctx);
crate::ported::signals::intrap.fetch_sub(1, Ordering::SeqCst); self.set_last_status(status);
}
let trapped = crate::ported::signals::sigtrapped
.lock()
.ok()
.and_then(|g| g.get(crate::signals_h::SIGEXIT as usize).copied())
.unwrap_or(0);
let exit_trap_locallevel = trapped >> crate::ported::zsh_h::ZSIG_SHIFT;
if (trapped & crate::ported::zsh_h::ZSIG_FUNC as i32) != 0
&& exit_trap_locallevel == 0
&& crate::ported::signals::intrap.load(Ordering::SeqCst) == 0
{
crate::ported::signals::intrap.fetch_add(1, Ordering::SeqCst); let _ = self.execute_script_zsh_pipeline("TRAPEXIT");
crate::ported::signals::intrap.fetch_sub(1, Ordering::SeqCst); }
thread_local! {
static ZSHEXIT_HOOK_DEPTH: std::cell::Cell<u32> = const {
std::cell::Cell::new(0)
};
}
let hook_depth = ZSHEXIT_HOOK_DEPTH.with(|c| c.get());
if hook_depth == 0 {
ZSHEXIT_HOOK_DEPTH.with(|c| c.set(hook_depth + 1));
if crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.map(|t| t.contains_key("zshexit"))
.unwrap_or(false)
{
let _ = self.execute_script_zsh_pipeline("zshexit");
}
let exit_arr = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get("zshexit_functions").and_then(|p| p.u_arr.clone()))
.unwrap_or_default();
for fn_name in exit_arr {
let exists = crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.map(|t| t.contains_key(&fn_name))
.unwrap_or(false);
if exists {
let _ = self.execute_script_zsh_pipeline(&fn_name);
}
}
ZSHEXIT_HOOK_DEPTH.with(|c| c.set(hook_depth));
}
self.set_last_status(status);
let _ = status;
Ok(self.last_status())
}
pub fn execute_script_zsh_pipeline(&mut self, script: &str) -> Result<i32, String> {
let chunk = self.compile_script_isolated(script)?;
self.run_chunk_with_exit_hooks(chunk, "execute_script_zsh_pipeline")
}
pub fn execute_string_without_exit_hooks(&mut self, script: &str) -> Result<i32, String> {
let chunk = self.compile_script_isolated(script)?;
self.run_chunk(chunk, "execstring")
}
pub fn execute_string_at_lineno(&mut self, script: &str, lineno: u64) -> Result<i32, String> {
let chunk = self.compile_script_isolated_at(script, Some(lineno))?;
self.run_chunk(chunk, "execstring")
}
pub fn execute_zwc_program(&mut self, script: &str) -> Result<i32, String> {
let chunk = {
let _relex = ZwcRelexGuard::enter();
self.compile_script_isolated(script)
};
self.run_chunk_with_exit_hooks(chunk?, "execute_zwc_program")
}
pub fn execute_script_per_command(
&mut self,
script: &str,
cache_path: Option<&Path>,
) -> Result<i32, String> {
use crate::script_cache::{self, BlobKind};
let env_fp = cache_path.map(|_| script_cache::env_fingerprint());
if let (Some(p), Some(fp)) = (cache_path, env_fp) {
if let Some(blob) = script_cache::try_load_bytes(p, BlobKind::Guarded, fp) {
if let Ok(events) = bincode::deserialize::<Vec<script_cache::GuardedEvent>>(&blob) {
if !events.is_empty() {
tracing::trace!(path = %p.display(), events = events.len(), "source: bytecode cache hit");
return self.replay_cached_events(events, false, || Some(script.to_string()));
}
}
}
}
let record = match (cache_path, env_fp) {
(Some(p), Some(fp)) => {
script_cache::capture_begin(p, fp, false);
true
}
_ => false,
};
self.run_events_per_command(script, record, false)
}
fn replay_cached_events(
&mut self,
events: Vec<crate::script_cache::GuardedEvent>,
toplevel: bool,
source: impl FnOnce() -> Option<String>,
) -> Result<i32, String> {
let base = crate::script_cache::env_generation().0;
for ev in events {
let moved = crate::script_cache::env_generation().0.wrapping_sub(base);
if moved != ev.alias_delta {
let Some(text) = source() else {
return Err("script cache: source unreadable for the guarded tail".into());
};
let desugared = text
.contains("rust")
.then(|| crate::rust_ffi::desugar(&text));
let full: &str = desugared.as_deref().unwrap_or(&text);
let Some(rest) = full.get(ev.start as usize..) else {
return Err("script cache: event offset outside the source".into());
};
return self.run_events_per_command(rest, false, toplevel);
}
self.run_chunk(ev.chunk, "source:cached")?;
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0
|| crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) != 0
|| crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) != 0
{
break;
}
}
Ok(self.last_status())
}
fn run_events_per_command(
&mut self,
script: &str,
record: bool,
toplevel: bool,
) -> Result<i32, String> {
use crate::ported::lex::{
tok, ENDINPUT, LEXERR, LEX_FILE_WINDOW_STRIN, LEX_INPUT, LEX_LINENO, LEX_POS,
LEX_UNGET_BUF,
};
let ffi_desugared = script
.contains("rust")
.then(|| crate::rust_ffi::desugar(script));
let script: &str = ffi_desugared.as_deref().unwrap_or(script);
crate::ported::context::zcontext_save(); let saved_input = LEX_INPUT.with_borrow(|s| s.clone());
let saved_pos = LEX_POS.get();
let saved_unget = LEX_UNGET_BUF.with_borrow(|b| b.clone());
let saved_lineno = LEX_LINENO.get();
let saved_in_lexstop = crate::ported::input::lexstop.with(|c| c.get());
let saved_file_window = LEX_FILE_WINDOW_STRIN.get();
crate::ported::hist::strinbeg(0);
crate::ported::parse::parse_init(script);
LEX_FILE_WINDOW_STRIN.set(crate::ported::input::strin.with(|s| s.get()));
let mut non_empty = false;
let mut vm_error: Option<String> = None;
let mut drained = false;
loop {
crate::ported::lex::lexinit(); let ev_start = LEX_POS.get().saturating_sub(
LEX_UNGET_BUF.with_borrow(|b| b.iter().map(|c| c.len_utf8()).sum::<usize>()),
);
let event_mark = toplevel.then(crate::funcdef_capture::body_mark_begin);
let prog = crate::ported::parse::parse_event(ENDINPUT); let event_src = event_mark.and_then(crate::funcdef_capture::event_text);
let Some(prog) = prog else {
let tok_v = tok(); let errflag_v = errflag.load(Ordering::Relaxed);
if (tok_v == ENDINPUT && errflag_v == 0) || tok_v == LEXERR {
drained = tok_v == ENDINPUT;
if tok_v == LEXERR
&& crate::ported::builtin::LASTVAL.load(Ordering::Relaxed) == 0
{
crate::ported::builtin::LASTVAL.store(1, Ordering::Relaxed); }
if tok_v == LEXERR
&& crate::extensions::dash_mode::dash_faithful()
&& !crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE)
{
std::process::exit(
crate::extensions::dash_mode::fatal_error_status().unwrap_or(2),
);
}
break;
}
if tok_v == ENDINPUT || errflag_v != 0 {
break;
}
continue; };
non_empty = true; if toplevel {
let _ctx = ExecutorContext::enter(self);
run_preexec_hook(event_src.as_deref()); }
let chunk = crate::compile_zsh::ZshCompiler::new().compile(&prog);
if record {
crate::script_cache::capture_push(&chunk, ev_start);
}
let saved_lex_lineno = LEX_LINENO.get();
let run = self.run_chunk(chunk, "source");
LEX_LINENO.set(saved_lex_lineno);
if let Err(e) = run {
vm_error = Some(e);
break;
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0
|| crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) != 0
{
if toplevel
&& crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) == 0
&& crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) == 0
&& !crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE)
&& crate::ported::zsh_h::isset(crate::ported::zsh_h::CONTINUEONERROR)
{
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed); continue;
}
break; }
if crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) != 0 {
break;
}
}
let err = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0; let stopped_deliberately = crate::ported::builtin::RETFLAG.load(Ordering::Relaxed) != 0
|| crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed) != 0;
if record {
crate::script_cache::capture_commit(
(drained || stopped_deliberately) && !err && vm_error.is_none(),
);
}
crate::ported::hist::strinend();
LEX_INPUT.with_borrow_mut(|s| *s = saved_input);
LEX_POS.set(saved_pos);
LEX_UNGET_BUF.with_borrow_mut(|b| *b = saved_unget);
LEX_LINENO.set(saved_lineno);
crate::ported::input::lexstop.with(|c| c.set(saved_in_lexstop));
LEX_FILE_WINDOW_STRIN.set(saved_file_window);
crate::ported::context::zcontext_restore(); if err {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); }
if let Some(e) = vm_error {
return Err(e);
}
if !non_empty {
self.set_last_status(0); }
Ok(self.last_status())
}
fn run_autoload_definition(
&mut self,
name: &str,
registered: &str,
ksh_style: bool,
from_wordcode: bool,
parse_failed: bool,
) -> Result<i32, String> {
let unaliased = crate::ported::utils::getshfunc(name)
.map(|f| (f.node.flags as u32 & crate::ported::zsh_h::PM_UNALIASED) != 0)
.unwrap_or(false);
let key = if ksh_style || !unaliased {
None
} else {
autoload_source_key(name, registered, from_wordcode)
};
if let Some((dir, sha)) = key.as_ref() {
if let Some(blob) = crate::autoload_cache::try_load_for_source(name, dir, sha) {
match bincode::deserialize::<fusevm::Chunk>(&blob) {
Ok(chunk) if !chunk.ops.is_empty() => {
tracing::debug!(
name,
ops = chunk.ops.len(),
"autoload: rkyv chunk hit, skipping parse+compile"
);
let status = self.run_chunk(chunk, "autoload:cached");
if self.functions_compiled.contains_key(name) {
return status;
}
tracing::warn!(
name,
"autoload: cached chunk did not define the function; \
dropping the entry and recompiling"
);
crate::autoload_cache::try_remove(name);
}
_ => {}
}
}
}
let chunk = {
let _relex = from_wordcode.then(ZwcRelexGuard::enter);
let _muted = parse_failed.then(ZerrMute::enter);
self.compile_script_isolated(registered)?
};
if let Some((dir, sha)) = key.as_ref() {
match bincode::serialize(&chunk) {
Ok(blob) => {
if let Err(e) = crate::autoload_cache::try_save_one(name, &blob, dir, *sha) {
tracing::warn!(name, error = %e, "autoload: rkyv chunk save failed");
}
}
Err(e) => tracing::warn!(name, error = %e, "autoload: chunk serialize failed"),
}
}
self.run_chunk(chunk, "autoload:compiled")
}
#[tracing::instrument(skip(self, script), fields(len = script.len()))]
pub fn execute_script(&mut self, script: &str) -> Result<i32, String> {
self.execute_script_zsh_pipeline(script)
}
pub fn execute_script_captured(&mut self, script: &str) -> (i32, String) {
use std::io::{Read, Seek, SeekFrom};
use std::os::unix::io::AsRawFd;
static CAPTURE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let Ok(mut tmp) = tempfile::tempfile() else {
let status = self.execute_script(script).unwrap_or(1);
return (status, String::new());
};
let _ = io::stdout().flush();
struct RestoreFds {
saved_out: i32,
saved_err: i32,
}
impl Drop for RestoreFds {
fn drop(&mut self) {
let _ = io::stdout().flush();
unsafe {
libc::dup2(self.saved_out, libc::STDOUT_FILENO);
libc::dup2(self.saved_err, libc::STDERR_FILENO);
}
crate::ported::utils::zclose(self.saved_out);
crate::ported::utils::zclose(self.saved_err);
}
}
let saved_out = crate::ported::utils::movefd(unsafe { libc::dup(libc::STDOUT_FILENO) });
let saved_err = crate::ported::utils::movefd(unsafe { libc::dup(libc::STDERR_FILENO) });
unsafe {
libc::dup2(tmp.as_raw_fd(), libc::STDOUT_FILENO);
libc::dup2(tmp.as_raw_fd(), libc::STDERR_FILENO);
}
let restore = RestoreFds {
saved_out,
saved_err,
};
let status = self.execute_script(script);
drop(restore);
let mut output = String::new();
let _ = tmp.seek(SeekFrom::Start(0));
let mut bytes = Vec::new();
if tmp.read_to_end(&mut bytes).is_ok() {
output = crate::script_bytes::decode_script_bytes(&bytes);
}
while output.ends_with('\n') {
output.pop();
}
(status.unwrap_or_else(|_| self.last_status()), output)
}
pub fn execute_program(&mut self, program: &crate::parse::ZshProgram) -> i32 {
let chunk = crate::compile_zsh::ZshCompiler::new().compile(program);
match self.run_chunk(chunk, "loop") {
Ok(status) => status,
Err(_) => 1,
}
}
pub fn function_exists(&self, name: &str) -> bool {
if self.functions_compiled.contains_key(name) {
return true;
}
crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.map(|t| t.get(name).is_some())
.unwrap_or(false)
}
pub fn function_names(&self) -> Vec<String> {
let mut set: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for k in self.functions_compiled.keys() {
set.insert(k.clone());
}
for k in self.function_source.keys() {
set.insert(k.clone());
}
set.into_iter().collect()
}
pub fn run_function_body_only(&mut self, name: &str, args: &[String]) -> Option<i32> {
let mut _autoload_file_guard: Option<AutoloadFileGuard> = None;
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
crate::ported::builtin::LASTVAL.store(1, Ordering::Relaxed); return Some(1); }
if let Some(rc) = crate::compsys::router::dispatch_compsys(name, args) {
return Some(rc);
}
if let Some(rc) = crate::compsys::ported::_regex_arguments::dispatch_if_registered(name) {
return Some(rc);
}
let mut did_autoload = false;
if !self.functions_compiled.contains_key(name) {
if name.starts_with('_') && crate::ported::utils::getshfunc(name).is_none() {
let mut _dir: Option<String> = None;
let mut _dump = None;
if crate::ported::exec::getfpfunc(name, &mut _dir, None, 1, &mut _dump).is_some() {
let _ = self.execute_script_zsh_pipeline(&format!("autoload -rUz -- {name}"));
}
}
if let Some(stub) = crate::ported::utils::getshfunc(name) {
let unaliased = (stub.node.flags as u32 & crate::ported::zsh_h::PM_UNALIASED) != 0;
let noalias_save = crate::ported::lex::noaliases(); crate::ported::lex::set_noaliases(unaliased); let _restore_noaliases = NoAliasesRestore(noalias_save); if (stub.node.flags as u32 & PM_UNDEFINED) != 0 {
did_autoload = true; let _sticky_load = AutoloadStickyGuard::enter(&stub);
let boxed = Box::new(stub.clone());
let ptr = Box::into_raw(boxed);
let load_rc = crate::ported::exec::loadautofn(ptr, 0, 0, 0);
unsafe {
let _ = Box::from_raw(ptr);
}
if load_rc != 0 {
return Some(1); }
let loaded_dir = crate::ported::utils::getshfunc(name)
.and_then(|f| f.filename)
.filter(|d| d != "zsh");
let abspath_used =
(stub.node.flags as u32 & crate::ported::zsh_h::PM_ABSPATH_USED) != 0;
let ksh_style = autoload_is_ksh_style(name); if let Some(body) = crate::ported::utils::getshfunc(name).and_then(|f| f.body) {
_autoload_file_guard = Some(AutoloadFileGuard::enter(name));
let AutoloadSource {
text: registered,
from_wordcode,
parse_failed,
} = autoload_register_source(name, &body);
{
let caller_lineno = crate::ported::lex::lineno();
let synthesized = registered != body;
let _ = self.run_autoload_definition(
name,
®istered,
ksh_style,
from_wordcode,
parse_failed,
);
crate::ported::lex::set_lineno(caller_lineno);
if synthesized {
self.function_line_base.insert(name.to_string(), -1);
}
}
}
if let Some(dir) = loaded_dir.as_deref() {
restore_loaddir(name, dir, abspath_used, ksh_style);
}
} else if let Some(body) = stub.body.clone() {
let _sticky_load = AutoloadStickyGuard::enter(&stub);
let loaded_dir = ((stub.node.flags as u32 & crate::ported::zsh_h::PM_LOADDIR)
!= 0)
.then(|| stub.filename.clone())
.flatten();
let abspath_used =
(stub.node.flags as u32 & crate::ported::zsh_h::PM_ABSPATH_USED) != 0;
let param_body = body_came_from_param_assignment(stub.node.flags);
let ksh_style = !param_body && autoload_is_ksh_style(name); _autoload_file_guard = Some(AutoloadFileGuard::enter(name));
let (registered, from_wordcode, parse_failed) = if param_body {
(param_definition_source(name, &body), false, false)
} else {
let AutoloadSource {
text,
from_wordcode,
parse_failed,
} = autoload_register_source(name, &body);
(text, from_wordcode, parse_failed)
};
{
let caller_lineno = crate::ported::lex::lineno();
let synthesized = registered != body;
let _ = self.run_autoload_definition(
name,
®istered,
ksh_style,
from_wordcode,
parse_failed,
);
crate::ported::lex::set_lineno(caller_lineno);
if synthesized {
self.function_line_base.insert(name.to_string(), -1);
}
}
if let Some(dir) = loaded_dir.as_deref() {
restore_loaddir(name, dir, abspath_used, ksh_style);
}
}
}
}
let chunk = self.functions_compiled.get(name).cloned()?;
let _load_ctx =
did_autoload.then(|| crate::ported::exec::EvalContextFrame::push("loadautofunc"));
let autofn_saved = did_autoload.then(|| {
let saved = (
crate::ported::utils::scriptname_get(),
crate::ported::utils::scriptfilename_get(),
);
crate::ported::utils::set_scriptname(Some(name.to_string())); crate::ported::utils::set_scriptfilename(crate::ported::hashtable::getshfuncfile(
name,
)); crate::ported::lex::set_lineno(0);
saved
});
let seed_status = self.last_status();
let _ = args; let redir_depth = self.redirect_scope_stack.len();
let slots_entry = crate::exec_jobs::execpline_slots_mark();
let mut vm = crate::vm_pool::acquire(chunk);
vm.last_status = seed_status;
let _ = vm.run();
let status = vm.last_status;
drop(vm);
crate::exec_jobs::execpline_slots_release_to(slots_entry);
self.unwind_redirect_scopes_to(redir_depth);
if let Some((sn, sfn)) = autofn_saved {
crate::ported::utils::set_scriptname(sn); crate::ported::utils::set_scriptfilename(sfn); }
Some(status)
}
pub fn dispatch_function_call(&mut self, name: &str, args: &[String]) -> Option<i32> {
stacker::maybe_grow(256 * 1024, 32 * 1024 * 1024, || {
self.dispatch_function_call_on_stack(name, args)
})
}
fn dispatch_function_call_on_stack(&mut self, name: &str, args: &[String]) -> Option<i32> {
let mut _autoload_file_guard: Option<AutoloadFileGuard> = None;
let _psub_scope = crate::fusevm_bridge::PsubScope::enter();
let is_disabled = crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.and_then(|t| {
let entry = t.get_including_disabled(name)?;
Some((entry.node.flags as u32 & crate::ported::zsh_h::DISABLED as u32) != 0)
})
.unwrap_or(false);
if is_disabled {
return None;
}
if let Some(rc) = crate::compsys::ported::_regex_arguments::dispatch_if_registered(name) {
return Some(rc);
}
let direct_rust_fn: Option<fn(&[String]) -> i32> =
crate::compsys::router::try_rust_dispatch(name);
let has_plugin_override = crate::extensions::plugin_host::compfn_override(name).is_some();
if (direct_rust_fn.is_some() || has_plugin_override)
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0
{
crate::ported::builtin::LASTVAL.store(1, Ordering::Relaxed); return Some(1); }
let mut did_autoload = false;
if direct_rust_fn.is_none()
&& !has_plugin_override
&& !self.functions_compiled.contains_key(name)
{
if let Some(stub) = crate::ported::utils::getshfunc(name) {
let unaliased = (stub.node.flags as u32 & crate::ported::zsh_h::PM_UNALIASED) != 0;
let noalias_save = crate::ported::lex::noaliases(); crate::ported::lex::set_noaliases(unaliased); let _restore_noaliases = NoAliasesRestore(noalias_save);
if (stub.node.flags as u32 & PM_UNDEFINED) != 0 {
did_autoload = true; let _sticky_load = AutoloadStickyGuard::enter(&stub);
let boxed = Box::new(stub.clone());
let ptr = Box::into_raw(boxed);
let load_rc = crate::ported::exec::loadautofn(ptr, 0, 0, 0);
unsafe {
let _ = Box::from_raw(ptr);
}
if load_rc != 0 {
return Some(1); }
let loaded_dir = crate::ported::utils::getshfunc(name)
.and_then(|f| f.filename)
.filter(|d| d != "zsh");
let abspath_used =
(stub.node.flags as u32 & crate::ported::zsh_h::PM_ABSPATH_USED) != 0;
if let Some(body) = crate::ported::utils::getshfunc(name).and_then(|f| f.body) {
let ksh_style = autoload_is_ksh_style(name); _autoload_file_guard = Some(AutoloadFileGuard::enter(name));
let AutoloadSource {
text: registered,
from_wordcode,
parse_failed,
} = autoload_register_source(name, &body);
{
use crate::ported::builtin::{
BREAKS, EXIT_PENDING, EXIT_VAL, RETFLAG, SHELL_EXITING,
};
use std::sync::atomic::Ordering::Relaxed;
use crate::ported::init::sourcelevel;
let saved_retflag = RETFLAG.swap(0, Relaxed);
let saved_breaks = BREAKS.swap(0, Relaxed);
let saved_exit_pending = EXIT_PENDING.swap(0, Relaxed);
let saved_exit_val = EXIT_VAL.swap(0, Relaxed);
let saved_shell_exiting = SHELL_EXITING.swap(0, Relaxed);
sourcelevel.fetch_add(1, Relaxed);
{
let _ctx =
crate::ported::exec::EvalContextFrame::push("evalautofunc");
{
let caller_lineno = crate::ported::lex::lineno();
let synthesized = registered != body;
let _ = self.run_autoload_definition(
name,
®istered,
ksh_style,
from_wordcode,
parse_failed,
);
crate::ported::lex::set_lineno(caller_lineno);
if synthesized {
self.function_line_base.insert(name.to_string(), -1);
}
}
}
sourcelevel.fetch_sub(1, Relaxed);
RETFLAG.store(saved_retflag, Relaxed);
BREAKS.store(saved_breaks, Relaxed);
EXIT_PENDING.store(saved_exit_pending, Relaxed);
EXIT_VAL.store(saved_exit_val, Relaxed);
SHELL_EXITING.store(saved_shell_exiting, Relaxed);
}
if let Some(dir) = loaded_dir.as_deref() {
restore_loaddir(name, dir, abspath_used, ksh_style);
}
if !self.functions_compiled.contains_key(name) {
if ksh_style {
crate::ported::utils::zwarn(&format!(
"{}: function not defined by file",
name
));
}
return Some(1);
}
} else if load_rc != 0 {
return Some(1);
}
} else if let Some(body) = stub.body.clone() {
let _sticky_load = AutoloadStickyGuard::enter(&stub);
let loaded_dir = ((stub.node.flags as u32 & crate::ported::zsh_h::PM_LOADDIR)
!= 0)
.then(|| stub.filename.clone())
.flatten();
let abspath_used =
(stub.node.flags as u32 & crate::ported::zsh_h::PM_ABSPATH_USED) != 0;
let param_body = body_came_from_param_assignment(stub.node.flags);
let ksh_style = !param_body && autoload_is_ksh_style(name); _autoload_file_guard = Some(AutoloadFileGuard::enter(name));
let (registered, from_wordcode, parse_failed) = if param_body {
(param_definition_source(name, &body), false, false)
} else {
let AutoloadSource {
text,
from_wordcode,
parse_failed,
} = autoload_register_source(name, &body);
(text, from_wordcode, parse_failed)
};
{
let caller_lineno = crate::ported::lex::lineno();
let synthesized = registered != body;
let _ = self.run_autoload_definition(
name,
®istered,
ksh_style,
from_wordcode,
parse_failed,
);
crate::ported::lex::set_lineno(caller_lineno);
if synthesized {
self.function_line_base.insert(name.to_string(), -1);
}
}
if let Some(dir) = loaded_dir.as_deref() {
restore_loaddir(name, dir, abspath_used, ksh_style);
}
}
}
}
let mut chunk_opt = if direct_rust_fn.is_some() || has_plugin_override {
None
} else {
Some(self.functions_compiled.get(name).cloned()?)
};
crate::ported::utils::xtrerr.store(2, Ordering::Relaxed);
const FUNCNEST_RUST_CEILING: usize = 6000;
let funcnest_user: usize = self
.scalar("FUNCNEST")
.and_then(|s| s.parse().ok())
.unwrap_or(500);
let funcnest_limit = funcnest_user.min(FUNCNEST_RUST_CEILING);
if self.local_scope_depth >= funcnest_limit {
let lineno = crate::ported::lex::lineno();
let shows_lineno = (crate::ported::zsh_h::unset(crate::ported::zsh_h::SHINSTDIN)
|| crate::ported::params::locallevel.here() != 0)
&& lineno != 0;
if shows_lineno {
eprintln!(
"{}:{}: maximum nested function level reached; increase FUNCNEST?",
name, lineno
);
} else {
eprintln!("{}: maximum nested function level reached; increase FUNCNEST?", name);
}
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); crate::ported::builtin::LASTVAL.store(1, Ordering::Relaxed); return Some(1);
}
let display_name = if name.starts_with("_zshrs_anon_") {
"(anon)".to_string()
} else {
name.to_string()
};
let line_base = self.function_line_base.get(name).copied().unwrap_or(0);
let def_file = self.function_def_file.get(name).cloned().flatten();
self.prompt_funcstack
.push((name.to_string(), line_base, def_file));
self.local_scope_depth += 1;
let synth_filename = crate::ported::hashtable::getshfuncfile(name)
.or_else(|| self.function_def_file.get(name).cloned().flatten())
.or_else(|| self.scriptfilename.clone());
let synth_lineno = {
let base = self.function_line_base.get(name).copied().unwrap_or(0);
if base < 0 {
0
} else {
std::cmp::max(1i64, base)
}
};
let synth_flags = crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.and_then(|t| {
t.get(name)
.or_else(|| t.get(display_name.as_str()))
.map(|s| s.node.flags)
})
.unwrap_or(0)
& !(crate::ported::zsh_h::PM_LOADDIR as i32);
let synth_sticky = crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.and_then(|t| {
t.get(name)
.or_else(|| t.get(display_name.as_str()))
.and_then(|s| {
s.sticky
.as_deref()
.map(|b| crate::ported::exec::sticky_emulation_dup(b, 0))
})
});
let mut synth_shf = crate::ported::zsh_h::shfunc {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: display_name.clone(),
flags: synth_flags,
},
filename: synth_filename,
lineno: synth_lineno,
funcdef: None,
redir: None,
sticky: synth_sticky,
body: None,
redir_text: None,
};
let mut doshargs: Vec<String> = vec![display_name.clone()];
doshargs.extend(args.iter().cloned());
let seed_status = self.last_status();
let body_args: Vec<String> = args.to_vec();
let name_owned = name.to_string();
let body_runner = move || -> i32 {
let _load_ctx =
did_autoload.then(|| crate::ported::exec::EvalContextFrame::push("loadautofunc"));
if let Some(rc) =
crate::extensions::plugin_host::dispatch_compfn(&name_owned, &body_args)
{
return rc;
}
if let Some(f) = direct_rust_fn {
return crate::compsys::router::run_port(&name_owned, &body_args, f);
}
let chunk = chunk_opt
.take()
.expect("chunk_opt must be Some when direct_rust_fn is None");
crate::fusevm_disasm::maybe_print_stdout(
&format!(
"function:{}",
body_args.first().map(|s| s.as_str()).unwrap_or("")
),
&chunk,
);
let mut vm = crate::vm_pool::acquire(chunk);
vm.last_status = seed_status;
let _ = vm.run();
chunk_opt = Some(std::mem::take(&mut vm.chunk));
vm.last_status
};
let saved_cmdstack: Vec<u8> =
crate::ported::prompt::CMDSTACK.with(|s| std::mem::take(&mut *s.borrow_mut()));
let redir_depth = self.redirect_scope_stack.len();
let osfc = crate::ported::exec::sfcontext.load(std::sync::atomic::Ordering::Relaxed);
if osfc == crate::ported::zsh_h::SFC_NONE {
crate::ported::exec::sfcontext.store(
crate::ported::zsh_h::SFC_DIRECT,
std::sync::atomic::Ordering::Relaxed,
); }
let _ctx = ExecutorContext::enter(self);
let status = crate::ported::exec::doshfunc(&mut synth_shf, doshargs, false, body_runner);
drop(_ctx);
crate::ported::exec::sfcontext.store(osfc, std::sync::atomic::Ordering::Relaxed); self.unwind_redirect_scopes_to(redir_depth);
crate::ported::prompt::CMDSTACK.with(|s| *s.borrow_mut() = saved_cmdstack);
self.prompt_funcstack.pop();
self.local_scope_depth -= 1;
if let Some(ret) = self.returning.take() {
self.set_last_status(ret);
Some(ret)
} else {
self.set_last_status(status);
Some(status)
}
}
pub(crate) fn execute_external(
&mut self,
cmd: &str,
args: &[String],
redirects: &[Redirect],
) -> Result<i32, String> {
self.execute_external_bg(cmd, args, redirects, false, 0)
}
pub(crate) fn execute_external_defpath(
&mut self,
cmd: &str,
args: &[String],
redirects: &[Redirect],
) -> Result<i32, String> {
self.execute_external_bg(cmd, args, redirects, false, 1)
}
fn execute_external_bg(
&mut self,
cmd: &str,
args: &[String],
_redirects: &[Redirect],
background: bool,
defpath: i32, ) -> Result<i32, String> {
tracing::trace!(cmd, bg = background, "exec external");
if crate::ported::zsh_h::isset(crate::ported::zsh_h::RESTRICTED)
&& (cmd.contains('/') || defpath != 0)
{
crate::ported::utils::zerr(&format!("{}: restricted", cmd));
return Ok(1);
}
{
let last = args.last().cloned().unwrap_or_else(|| cmd.to_string());
crate::ported::params::set_zunderscore(std::slice::from_ref(&last));
}
if !cmd.contains('/') {
if let Some(status) = crate::plugin_host::dispatch(cmd, args) {
return Ok(status);
}
}
if !cmd.contains('/')
&& !crate::native_cmds::is_forced_external()
&& crate::native_cmds::is_enabled(cmd)
{
let full: Vec<String> = std::iter::once(cmd.to_string())
.chain(args.iter().cloned())
.collect();
if let Some(status) = crate::native_cmds::dispatch(cmd, &full) {
return Ok(status);
}
}
if !cmd.contains('/') && defpath == 0 {
let path_set_and_nonempty = crate::ported::params::getsparam("PATH")
.map(|p| !p.is_empty())
.unwrap_or(false);
if !path_set_and_nonempty {
let sn =
crate::ported::utils::scriptname_get().unwrap_or_else(|| "zshrs".to_string());
if let Some(rc) = self.try_registered_ffi_command(cmd, args) {
return Ok(rc);
}
eprintln!("{}: command not found: {}", zerr_prefix(&sn), crate::ported::utils::nicedupstring(&cmd));
return Ok(127);
}
}
if !cmd.contains('/') {
if let Some(rc) = crate::ported::exec::resolvebuiltin(cmd) {
if rc != 0 {
return Ok(1);
}
return Ok(crate::fusevm_bridge::dispatch_builtin_raw(
cmd,
args.to_vec(),
));
}
}
let mut hashed_prog: Option<String> = None;
if !cmd.contains('/') && cmd != ".." {
let mut have = crate::ported::hashtable::cmdnamtab_lock()
.read()
.ok()
.and_then(|t| t.get(cmd).cloned());
if have.is_none() && crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHCMDS) {
let dirs: Vec<String> = crate::ported::params::getsparam("PATH")
.unwrap_or_default()
.split(':')
.map(String::from)
.collect();
have = crate::ported::exec::hashcmd(cmd, &dirs);
}
if have.is_some() {
crate::extensions::emulation_output::bash_hash_hit(cmd);
}
if have.is_some() {
hashed_prog = crate::ported::hashtable::cmdnamtab_lock()
.read()
.ok()
.and_then(|t| t.get_full_path(cmd))
.map(|p| p.display().to_string());
}
}
if hashed_prog.is_none()
&& defpath == 0
&& args.is_empty()
&& !cmd.is_empty()
&& crate::ported::zsh_h::isset(crate::ported::zsh_h::AUTOCD)
&& crate::ported::zsh_h::isset(crate::ported::zsh_h::SHINSTDIN)
&& !crate::fusevm_bridge::own_redirect_scope(self)
{
if let Some(dir) = crate::ported::exec::cancd(cmd) {
return Ok(crate::fusevm_bridge::dispatch_builtin_raw(
"cd",
vec!["--".to_string(), dir],
));
}
}
let mut defpath_prog: Option<String> = None;
if defpath != 0 && !cmd.contains('/') {
match crate::ported::exec::search_defpath(cmd, libc::PATH_MAX as usize) {
Some(pbuf) => defpath_prog = Some(pbuf),
None => {
let mut hook_args = Vec::with_capacity(args.len() + 1);
hook_args.push(cmd.to_string());
hook_args.extend_from_slice(args);
if let Some(rc) =
self.dispatch_function_call("command_not_found_handler", &hook_args)
{
return Ok(rc);
}
let sn = crate::ported::utils::scriptname_get()
.unwrap_or_else(|| "zshrs".to_string());
eprintln!("{}: command not found: {}", zerr_prefix(&sn), crate::ported::utils::nicedupstring(&cmd));
return Ok(127); }
}
}
let carrier = crate::fusevm_bridge::take_exec_carrier();
let job_text = crate::fusevm_bridge::take_job_text();
let exec_dash = carrier & crate::fusevm_bridge::EXEC_CARRIER_DASH != 0;
let exec_forked = !background && carrier & crate::fusevm_bridge::EXEC_CARRIER_FORKED != 0;
let multio_threads_live = self.multios_scope_stack.iter().any(|scope| !scope.is_empty());
let in_place = exec_forked && !multio_threads_live;
if exec_forked && carrier & crate::fusevm_bridge::EXEC_CARRIER_SHLVL != 0 {
crate::fusevm_bridge::exec_shlvl_decrement(); }
let argv0_env = std::env::var("ARGV0").ok(); let mut argv: Vec<String> = Vec::with_capacity(args.len() + 1);
argv.push(match &argv0_env {
Some(z) => z.clone(), None if exec_dash => format!("-{}", cmd), None => cmd.to_string(),
});
argv.extend_from_slice(args);
FORK_EVENTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
crate::lowfd::cloexec_internal_fds();
let spawned = if background {
execute_spawn(cmd, &argv, defpath_prog.as_deref(), hashed_prog.as_deref(), argv0_env.is_some(), false)
.map_err(|eno| (eno, None))
} else {
let _wait_guard = crate::fusevm_bridge::ForegroundWaitGuard::enter();
match execute_spawn(cmd, &argv, defpath_prog.as_deref(), hashed_prog.as_deref(), argv0_env.is_some(), in_place) {
Ok(pid) => match crate::fusevm_bridge::wait_pid_status(pid) {
Ok(status) => {
use std::os::unix::process::ExitStatusExt as _;
drop(_wait_guard);
crate::exec_jobs::foreground_job_report(
status.into_raw(),
job_text,
!self.subshell_snapshots.is_empty(),
);
crate::exec_jobs::foreground_job_signalled(status.into_raw());
return Ok(crate::exec_jobs::wait_status_val(status));
}
Err(e) => Err((0, Some(e))),
},
Err(eno) => Err((eno, None)),
}
};
let sn = crate::ported::utils::scriptname_get().unwrap_or_else(|| "zshrs".to_string());
let eno = match spawned {
Ok(pid) => {
let cmd_str = format!("{} {}", cmd, args.join(" "));
let job_id = self.jobs.add_pid_job(pid, cmd_str, JobState::Running);
println!("[{}] {}", job_id, pid);
return Ok(0);
}
Err((_, Some(e))) => return Err(format!("{}: {}: {}", sn, cmd, e)),
Err((eno, None)) => eno,
};
if eno != 0 {
eprintln!(
"{}: {}: {}",
zerr_prefix(&sn),
crate::ported::utils::zsh_errno_msg(eno),
cmd
);
return Ok(if eno == libc::EACCES || eno == libc::ENOEXEC { 126 } else { 127 });
}
if !background {
let mut hook_args = Vec::with_capacity(args.len() + 1);
hook_args.push(cmd.to_string());
hook_args.extend_from_slice(args);
if let Some(rc) = self.dispatch_function_call("command_not_found_handler", &hook_args) {
return Ok(rc);
}
}
if let Some(rc) = self.try_registered_ffi_command(cmd, args) {
return Ok(rc);
}
eprintln!("{}: command not found: {}", zerr_prefix(&sn), crate::ported::utils::nicedupstring(&cmd));
Ok(127)
}
fn try_registered_ffi_command(&self, cmd: &str, args: &[String]) -> Option<i32> {
if cmd.contains('/') || !fusevm::ffi::is_registered(cmd) {
return None;
}
let vals: Vec<fusevm::Value> = args.iter().map(|a| fusevm::Value::str(a.clone())).collect();
match fusevm::ffi::try_call(cmd, &vals) {
Some(Ok(v)) => {
use std::io::Write as _;
let mut out = io::stdout().lock();
let _ = writeln!(out, "{}", v.to_str());
let _ = out.flush();
Some(0)
}
Some(Err(e)) => {
eprintln!("zshrs: {e}");
Some(1)
}
None => None,
}
}
fn simple_cmd_words(&mut self, cmd_str: &str) -> Vec<String> {
let saved_errflag = errflag.load(Ordering::Relaxed);
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
let prog = parse_isolated(cmd_str);
let parse_failed = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0;
errflag.store(saved_errflag, Ordering::Relaxed);
if parse_failed {
return Vec::new();
}
let first = match prog.lists.first() {
Some(l) => l,
None => return Vec::new(),
};
let pipe = &first.sublist.pipe;
if let crate::parse::ZshCommand::Simple(simple) = &pipe.cmd {
simple
.words
.iter()
.map(|w| {
let untoked = crate::lex::untokenize(w);
singsub(&untoked)
})
.collect()
} else {
Vec::new()
}
}
pub fn compsys_cache(&self) -> Option<&CompsysCache> {
self.compsys_cache
.get_or_init(|| {
let cache_path = crate::compsys::cache::default_cache_path();
if !cache_path.exists() {
tracing::debug!("compsys: no cache at {}", cache_path.display());
return None;
}
let db_size = fs::metadata(&cache_path).map(|m| m.len()).unwrap_or(0);
match CompsysCache::open(&cache_path) {
Ok(c) => {
tracing::info!(
db_bytes = db_size,
path = %cache_path.display(),
"compsys: sqlite mirror opened (dbview/SQL inspection only; rkyv shards are the authoritative cache)"
);
Some(c)
}
Err(e) => {
tracing::warn!(error = %e, "compsys: failed to open cache");
None
}
}
})
.as_ref()
}
pub fn run_command_substitution(&mut self, cmd_str: &str) -> String {
let saved_dq = std::mem::replace(&mut self.in_dq_context, 0);
let saved_subexp = crate::ported::subst::SUBEXP_SCALAR_CTX.with(|c| c.replace(0));
let out = self.run_command_substitution_inner(cmd_str, false);
crate::ported::subst::SUBEXP_SCALAR_CTX.with(|c| c.set(saved_subexp));
self.in_dq_context = saved_dq;
out
}
pub fn run_shared_state_substitution(&mut self, cmd_str: &str) -> String {
let saved_dq = std::mem::replace(&mut self.in_dq_context, 0);
let saved_subexp = crate::ported::subst::SUBEXP_SCALAR_CTX.with(|c| c.replace(0));
let out = self.run_command_substitution_inner(cmd_str, true);
crate::ported::subst::SUBEXP_SCALAR_CTX.with(|c| c.set(saved_subexp));
self.in_dq_context = saved_dq;
out
}
fn run_command_substitution_inner(&mut self, cmd_str: &str, shared_state: bool) -> String {
let trimmed = cmd_str.trim_start();
if let Some(rest) = trimmed.strip_prefix('<').filter(|s| !s.starts_with('<')) {
let filename = rest.trim();
let is_single_word = !filename.is_empty()
&& !filename.chars().any(|c| {
matches!(
c,
' ' | '\t'
| '\n'
| ';'
| '&'
| '|'
| '<'
| '>'
| '('
| ')'
| '`'
| '"'
| '\''
)
});
if is_single_word {
let resolved = if filename.contains('$') || filename.starts_with('~') {
singsub(filename)
} else {
filename.to_string()
};
let resolved = resolved.to_string();
match crate::script_bytes::read_script_file(&resolved) {
Ok(contents) => {
return contents.trim_end_matches('\n').to_string();
}
Err(e) => {
crate::ported::utils::zwarn(&format!(
"{}: {}",
crate::ported::utils::zsh_errno_msg(
e.raw_os_error().unwrap_or(libc::ENOENT)
),
resolved
));
self.set_last_status(1); crate::ported::exec::cmdoutval.store(1, Ordering::Relaxed);
return String::new();
}
}
}
}
let (read_fd, write_fd) = {
let mut fds = [0i32; 2];
if crate::ported::exec::mpipe(&mut fds) < 0 {
return String::new();
}
(crate::lowfd::movefd_past_script(fds[0]), fds[1])
};
let saved_stdout =
crate::lowfd::movefd_past_script(unsafe { libc::dup(libc::STDOUT_FILENO) });
let _ = io::stdout().flush();
let saved_stderr_for_trap =
crate::lowfd::movefd_past_script(unsafe { libc::dup(libc::STDERR_FILENO) });
crate::fusevm_bridge::mark_saved_fd(saved_stdout);
crate::fusevm_bridge::mark_saved_fd(saved_stderr_for_trap);
crate::fusevm_bridge::CMDSUBST_OUTER_FDS
.with(|s| s.borrow_mut().push((saved_stdout, saved_stderr_for_trap)));
unsafe {
libc::dup2(write_fd, libc::STDOUT_FILENO);
}
crate::ported::utils::zclose(write_fd);
let reader_handle = crate::signal_thread::spawn(move || {
let mut buf: Vec<u8> = Vec::new();
let mut chunk = [0u8; 65536];
loop {
let n = unsafe {
libc::read(
read_fd,
chunk.as_mut_ptr() as *mut libc::c_void,
chunk.len(),
)
};
if n < 0 {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if e == libc::EINTR {
continue;
}
break;
}
if n == 0 {
break; }
buf.extend_from_slice(&chunk[..n as usize]);
}
buf
});
let _subshell_bump = if shared_state {
None
} else {
Some(crate::fusevm_bridge::CmdSubstSubshellBump::enter())
};
let _bash_errexit = (!shared_state).then(crate::dash_mode::BashCmdsubstErrexit::enter);
let _subsh_state = crate::ported::exec::SubshStateGuard::enter();
cmdpush(crate::ported::zsh_h::CS_CMDSUBST as u8); let saved_lineno = getsparam("LINENO");
let outer_lineno: u64 = self
.scalar("LINENO")
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let saved_errflag = errflag.load(Ordering::Relaxed);
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
let parsed = parse_isolated_at(cmd_str, Some(outer_lineno)); let parse_failed = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0;
errflag.store(saved_errflag, Ordering::Relaxed);
let prog = if parse_failed { None } else { Some(parsed) };
let mut cmd_status: Option<i32> = None;
if let Some(prog) = prog {
let mut compiler = crate::compile_zsh::ZshCompiler::new();
compiler.nested_lineno_base = Some(outer_lineno); let chunk = compiler.compile(&prog);
if !chunk.ops.is_empty() {
crate::fusevm_disasm::maybe_print_stdout("run_command_substitution", &chunk);
let wait_t0 = std::time::Instant::now();
let paramtab_guard = crate::ported::params::paramtab().read();
let paramtab_wait = wait_t0.elapsed();
let paramtab_snap = paramtab_guard
.ok()
.map(|t| t.clone())
.unwrap_or_default();
let wait_t1 = std::time::Instant::now();
let hashed_guard = crate::ported::params::paramtab_hashed_storage().lock();
let hashed_wait = wait_t1.elapsed();
let paramtab_hashed_snap = hashed_guard
.ok()
.map(|m| m.clone())
.unwrap_or_default();
if paramtab_wait.as_millis() > 5 || hashed_wait.as_millis() > 5 {
tracing::info!(
target: "lockwait",
paramtab_ms = paramtab_wait.as_millis() as u64,
hashed_ms = hashed_wait.as_millis() as u64,
cmd = %cmd_str.chars().take(60).collect::<String>(),
"command substitution waited on a shell-state lock"
);
}
let pparams_snap = self.pparams();
let comp_globals_snap = (!shared_state).then(CompGlobalsSnap::save);
let opts_snap = crate::ported::options::opt_state_snapshot();
let ifs_snap = crate::ported::params::ifs_lock()
.lock()
.map(|g| g.clone())
.unwrap_or_default();
let traps_snap = crate::ported::builtin::traps_table()
.lock()
.map(|t| t.clone())
.unwrap_or_default();
let shfunctab_snap = crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.map(|t| t.snapshot())
.unwrap_or_default();
let functions_compiled_snap = self.functions_compiled.clone();
let function_source_snap = self.function_source.clone();
let tables_snap = (!shared_state).then(crate::ported::exec::SubshForkCopy::save);
let locale_snap = (!shared_state).then(subsh_locale_save).flatten();
let fd_frame = (!shared_state).then(crate::ported::exec::SubshFdFrame::enter);
let sigtrapped_snap = (!shared_state).then(|| {
let parent = crate::ported::signals::sigtrapped
.lock()
.map(|g| g.clone())
.unwrap_or_default();
crate::fusevm_bridge::subshell_signal_enter();
crate::fusevm_bridge::entersubsh_reset_traps();
parent
});
let comp_arena_snap = crate::comp_match_handles::comp_arena_save();
crate::ported::signals_h::queue_signals();
let jobtab_snap = crate::ported::jobs::JOBTAB
.get()
.and_then(|t| t.lock().ok().map(|g| g.clone()));
let maxjob_snap = crate::ported::jobs::MAXJOB
.get()
.and_then(|m| m.lock().ok().map(|g| *g));
let thisjob_snap = crate::ported::jobs::THISJOB
.get()
.and_then(|t| t.lock().ok().map(|g| *g));
let curjob_snap = crate::ported::jobs::CURJOB
.get()
.and_then(|t| t.lock().ok().map(|g| *g));
let prevjob_snap = crate::ported::jobs::PREVJOB
.get()
.and_then(|t| t.lock().ok().map(|g| *g));
let slots_snap = crate::exec_jobs::execpline_slots_mark();
{
let monitor = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR) as i32;
crate::ported::jobs::clearjobtab(&mut self.jobs, monitor);
}
crate::ported::signals_h::unqueue_signals();
let mut vm = fusevm::VM::new(chunk);
register_builtins(&mut vm);
vm.set_shell_host(Box::new(ZshrsHost));
vm.last_status = self.last_status();
use crate::ported::builtin::{
BREAKS, EXIT_PENDING, EXIT_VAL, RETFLAG, SHELL_EXITING, SUBSHELL_DEPTH,
};
use std::sync::atomic::Ordering::Relaxed;
let saved_exit_pending = EXIT_PENDING.swap(0, Relaxed);
let saved_exit_val = EXIT_VAL.swap(0, Relaxed);
let saved_shell_exiting = SHELL_EXITING.swap(0, Relaxed);
let saved_retflag = RETFLAG.swap(0, Relaxed);
let saved_breaks = BREAKS.swap(0, Relaxed);
let sync_eval_ctx = |stack: &[String]| {
crate::ported::exec::EvalContextFrame::sync(stack);
};
if let Ok(mut ctx) = crate::ported::exec::zsh_eval_context.lock() {
ctx.push("cmdsubst".to_string());
sync_eval_ctx(&ctx);
}
struct CmdsubstEvalCtxGuard<F: Fn(&[String])>(F);
impl<F: Fn(&[String])> Drop for CmdsubstEvalCtxGuard<F> {
fn drop(&mut self) {
if let Ok(mut ctx) = crate::ported::exec::zsh_eval_context.lock() {
ctx.pop();
(self.0)(&ctx);
}
}
}
let _cs_eval_ctx_guard = CmdsubstEvalCtxGuard(sync_eval_ctx);
SUBSHELL_DEPTH.fetch_add(1, Relaxed);
let _ctx = ExecutorContext::enter(self);
let _ = vm.run();
let inner_exit_pending = EXIT_PENDING.load(Relaxed);
let inner_exit_val = EXIT_VAL.load(Relaxed);
let inner_status = if inner_exit_pending != 0 {
inner_exit_val & 0xFF
} else {
vm.last_status
};
cmd_status = Some(inner_status);
SUBSHELL_DEPTH.fetch_sub(1, Relaxed);
if !shared_state {
errflag.fetch_and(
!(ERRFLAG_ERROR | crate::ported::zsh_h::ERRFLAG_HARD),
Relaxed,
);
}
let snap_exit = traps_snap.get("EXIT").cloned();
let live_exit = crate::ported::builtin::traps_table()
.lock()
.ok()
.and_then(|t| t.get("EXIT").cloned());
if !shared_state && live_exit != snap_exit {
if let Some(body) = live_exit {
if let Ok(mut t) = crate::ported::builtin::traps_table().lock() {
t.remove("EXIT");
}
let _ = crate::ported::exec::execute_script(&body);
}
}
if let Some(parent) = &sigtrapped_snap {
let func = crate::ported::zsh_h::ZSIG_FUNC as i32;
let exit_idx = crate::signals_h::SIGEXIT as usize;
let live = crate::ported::signals::sigtrapped
.lock()
.ok()
.and_then(|g| g.get(exit_idx).copied())
.unwrap_or(0);
let inherited = parent.get(exit_idx).copied().unwrap_or(0);
if live & func != 0 && inherited & func == 0 {
crate::ported::signals::intrap.fetch_add(1, Ordering::SeqCst); let _ = crate::ported::exec::execute_script("TRAPEXIT");
crate::ported::signals::intrap.fetch_sub(1, Ordering::SeqCst); }
}
if shared_state {
if RETFLAG.load(Relaxed) != 0 {
RETFLAG.store(saved_retflag, Relaxed); BREAKS.store(saved_breaks, Relaxed); }
} else {
EXIT_PENDING.store(saved_exit_pending, Relaxed);
EXIT_VAL.store(saved_exit_val, Relaxed);
SHELL_EXITING.store(saved_shell_exiting, Relaxed);
RETFLAG.store(saved_retflag, Relaxed);
BREAKS.store(saved_breaks, Relaxed);
}
if !shared_state {
if let Some(snap) = tables_snap {
snap.restore();
}
drop(fd_frame);
if let Ok(mut t) = crate::ported::params::paramtab().write() {
*t = paramtab_snap;
}
if let Ok(mut m) = crate::ported::params::paramtab_hashed_storage().lock() {
*m = paramtab_hashed_snap;
}
if let Some(g) = comp_globals_snap {
g.restore();
}
self.set_pparams(pparams_snap);
crate::ported::options::opt_state_restore(opts_snap);
let ifs_changed = crate::ported::params::ifs_lock()
.lock()
.map(|mut g| {
let changed = *g != ifs_snap;
*g = ifs_snap;
changed
})
.unwrap_or(false);
if ifs_changed {
crate::ported::utils::inittyptab();
}
subsh_locale_restore(locale_snap.as_deref());
if let Ok(mut t) = crate::ported::builtin::traps_table().lock() {
*t = traps_snap;
}
if let Some(parent) = &sigtrapped_snap {
let child = match crate::ported::signals::sigtrapped.lock() {
Ok(mut st) => std::mem::replace(&mut *st, parent.clone()),
Err(_) => Vec::new(),
};
crate::fusevm_bridge::subshell_restore_signal_dispositions(parent, &child);
}
if let Ok(mut t) = crate::ported::hashtable::shfunctab_lock().write() {
t.restore(shfunctab_snap);
}
self.functions_compiled = functions_compiled_snap;
self.function_source = function_source_snap;
crate::comp_match_handles::comp_arena_restore(comp_arena_snap);
crate::ported::signals_h::queue_signals();
if let (Some(js), Some(t)) = (jobtab_snap, crate::ported::jobs::JOBTAB.get()) {
if let Ok(mut g) = t.lock() {
*g = js;
}
}
if let (Some(mj), Some(m)) = (maxjob_snap, crate::ported::jobs::MAXJOB.get()) {
if let Ok(mut g) = m.lock() {
*g = mj;
}
}
if let (Some(tj), Some(t)) = (thisjob_snap, crate::ported::jobs::THISJOB.get())
{
if let Ok(mut g) = t.lock() {
*g = tj;
}
}
if let (Some(cj), Some(t)) = (curjob_snap, crate::ported::jobs::CURJOB.get()) {
if let Ok(mut g) = t.lock() {
*g = cj;
}
}
if let (Some(pj), Some(t)) = (prevjob_snap, crate::ported::jobs::PREVJOB.get())
{
if let Ok(mut g) = t.lock() {
*g = pj;
}
}
crate::exec_jobs::execpline_slots_forget_to(slots_snap);
crate::ported::signals_h::unqueue_signals();
if sigtrapped_snap.is_some() {
crate::fusevm_bridge::subshell_signal_leave();
}
} }
}
if let Some(ln) = saved_lineno {
let n: crate::ported::zsh_h::zlong = ln.parse().unwrap_or(0);
if let Ok(mut tab) = crate::ported::params::paramtab().write() {
if let Some(pm) = tab.get_mut("LINENO") {
if (pm.node.flags & crate::ported::zsh_h::PM_SPECIAL as i32) != 0 {
pm.u_val = n;
pm.node.flags &= !(crate::ported::zsh_h::PM_UNSET as i32);
}
}
}
crate::ported::utils::set_lineno(n as i32);
crate::ported::lex::set_lineno(n as u64);
}
cmdpop();
let final_status = cmd_status.unwrap_or(0);
self.set_last_status(final_status);
crate::ported::exec::cmdoutval.store(final_status, std::sync::atomic::Ordering::Relaxed);
let _ = io::stdout().flush();
crate::fusevm_bridge::CMDSUBST_OUTER_FDS.with(|s| {
s.borrow_mut().pop();
});
if saved_stderr_for_trap >= 0 {
unsafe {
libc::dup2(saved_stderr_for_trap, libc::STDERR_FILENO);
}
crate::ported::utils::zclose(saved_stderr_for_trap);
}
if saved_stdout >= 0 {
unsafe {
libc::dup2(saved_stdout, libc::STDOUT_FILENO);
}
crate::ported::utils::zclose(saved_stdout);
} else {
unsafe { libc::close(libc::STDOUT_FILENO) }; }
let bytes = reader_handle.join().unwrap_or_default();
crate::ported::utils::zclose(read_fd);
let mut output = crate::script_bytes::decode_script_bytes(&bytes);
while !shared_state && output.ends_with('\n') {
output.pop();
}
if crate::provenance::active() {
crate::provenance::on_cmd_subst(cmd_str, &output);
}
if parse_failed {
crate::ported::utils::zerr("parse error in command substitution");
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_echo() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
let status = exec.execute_script("true").unwrap();
assert_eq!(status, 0);
}
#[test]
fn module_params_are_autoload_stubs_until_read() {
let _g = crate::test_util::global_state_lock();
unmark_module_param_used("jobstates");
assert!(
module_param_is_autoload_stub("jobstates"),
"untouched module param must read as a stub"
);
mark_module_param_used("jobstates");
assert!(
!module_param_is_autoload_stub("jobstates"),
"a read must materialize it"
);
assert!(!module_param_is_autoload_stub("path"));
assert!(!module_param_is_autoload_stub("PATH"));
}
#[test]
fn phase3_worker_runs_user_defined_function() {
let _g = crate::test_util::global_state_lock();
let mut main = ShellExecutor::new();
main.execute_script("phase3fn() { typeset -g PHASE3_FN_RESULT=fn_ran }")
.unwrap();
assert_eq!(getsparam("PHASE3_FN_RESULT"), None);
let pool = std::sync::Arc::new(crate::worker::WorkerPool::new(2));
let pool2 = std::sync::Arc::clone(&pool);
let (tx, rx) = std::sync::mpsc::channel::<()>();
pool.submit(move || {
let mut wex = ShellExecutor::new_worker(pool2);
let _ = wex.execute_script_zsh_pipeline("phase3fn");
let _ = tx.send(());
});
rx.recv().expect("worker completed");
assert_eq!(
getsparam("PHASE3_FN_RESULT"),
Some("fn_ran".to_string()),
"worker could not run the user-defined function from shared shfunctab"
);
}
#[test]
fn phase1_worker_shell_writes_reach_global_paramtab() {
let _g = crate::test_util::global_state_lock();
let _main = ShellExecutor::new();
let pool = std::sync::Arc::new(crate::worker::WorkerPool::new(4));
const N: usize = 16;
let (tx, rx) = std::sync::mpsc::channel::<usize>();
for i in 0..N {
let tx = tx.clone();
let pool_for_worker = std::sync::Arc::clone(&pool);
pool.submit(move || {
let mut wex = ShellExecutor::new_worker(pool_for_worker);
let _ =
wex.execute_script_zsh_pipeline(&format!("typeset -g PHASE1_WK_{i}=val_{i}"));
let _ = tx.send(i);
});
}
drop(tx);
let mut done = 0usize;
while rx.recv().is_ok() {
done += 1;
}
assert_eq!(done, N, "all {N} workers completed");
for i in 0..N {
assert_eq!(
getsparam(&format!("PHASE1_WK_{i}")),
Some(format!("val_{i}")),
"worker {i} typeset -g did not reach the global paramtab"
);
}
}
#[test]
fn test_if_true() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
let status = exec.execute_script("if true; then true; fi").unwrap();
assert_eq!(status, 0);
}
#[test]
fn test_if_false() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
let status = exec
.execute_script("if false; then true; else false; fi")
.unwrap();
assert_eq!(status, 1);
}
#[test]
fn test_for_loop() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
exec.execute_script("for i in a b c; do true; done")
.unwrap();
assert_eq!(exec.last_status(), 0);
}
#[test]
fn test_and_list() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
let status = exec.execute_script("true && true").unwrap();
assert_eq!(status, 0);
let status = exec.execute_script("true && false").unwrap();
assert_eq!(status, 1);
}
#[test]
fn test_or_list() {
let _g = crate::test_util::global_state_lock();
let mut exec = ShellExecutor::new();
let status = exec.execute_script("false || true").unwrap();
assert_eq!(status, 0);
}
#[test]
fn test_forklevel_default_zero_and_roundtrip() {
let _g = crate::test_util::global_state_lock();
use std::sync::atomic::Ordering;
let prev = crate::ported::exec::FORKLEVEL.load(Ordering::Relaxed);
crate::ported::exec::FORKLEVEL.store(0, Ordering::Relaxed);
assert_eq!(crate::ported::exec::FORKLEVEL.load(Ordering::Relaxed), 0);
crate::ported::exec::FORKLEVEL.store(3, Ordering::Relaxed);
assert_eq!(crate::ported::exec::FORKLEVEL.load(Ordering::Relaxed), 3);
crate::ported::exec::FORKLEVEL.store(prev, Ordering::Relaxed);
}
#[test]
#[cfg(unix)]
fn zexecve_recover_keeps_sigwinch_blocked_in_the_parent() {
fn sigwinch_blocked() -> bool {
unsafe {
let mut cur: libc::sigset_t = std::mem::zeroed();
libc::pthread_sigmask(libc::SIG_BLOCK, std::ptr::null(), &mut cur);
libc::sigismember(&cur, libc::SIGWINCH) == 1
}
}
let dir = std::env::temp_dir().join(format!(
"zshrs_winch_recover_{}_{:?}",
std::process::id(),
std::thread::current().id()
));
std::fs::create_dir_all(&dir).unwrap();
let noshebang = dir.join("noshebang");
std::fs::write(&noshebang, "echo hi\n").unwrap();
let shebang = dir.join("shebang");
std::fs::write(&shebang, "#!/bin/cat -v\necho hi\n").unwrap();
let saved = sigwinch_blocked();
crate::ported::signals_h::winch_block(); assert!(sigwinch_blocked(), "precondition: winch_block() holds");
let argv = vec!["script".to_string()];
let sh = zexecve_recover(noshebang.to_str().unwrap(), &argv, libc::ENOEXEC);
assert_eq!(
sh.as_ref().map(|(p, _)| p.as_str()),
Ok("/bin/sh"),
"c:625-627 — a shebang-less script re-execs through /bin/sh"
);
assert!(
sigwinch_blocked(),
"c:626 — the /bin/sh recovery must not unblock SIGWINCH in the parent"
);
let interp = zexecve_recover(shebang.to_str().unwrap(), &argv, libc::ENOEXEC);
assert_eq!(
interp.as_ref().map(|(p, _)| p.as_str()),
Ok("/bin/cat"),
"c:576-585 — the `#!` line names the interpreter"
);
assert!(
sigwinch_blocked(),
"c:580/c:584 — the `#!` recovery must not unblock SIGWINCH in the parent"
);
if !saved {
crate::ported::signals_h::winch_unblock();
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[cfg(unix)]
fn zexecve_recover_sh_fallback_passes_the_path_not_the_word() {
let dir = std::env::temp_dir().join(format!(
"zshrs_recover_argv_{}_{:?}",
std::process::id(),
std::thread::current().id()
));
std::fs::create_dir_all(&dir).unwrap();
let script = dir.join("tstcmd");
std::fs::write(&script, "echo no shebang\n").unwrap();
let pth = script.to_str().unwrap().to_string();
let argv = vec!["-tstcmd".to_string(), "arg".to_string()];
let got = zexecve_recover(&pth, &argv, libc::ENOEXEC);
let _ = std::fs::remove_dir_all(&dir);
let want: Vec<String> = vec!["sh".to_string(), pth.clone(), "arg".to_string()];
assert_eq!(got, Ok(("/bin/sh".to_string(), want)));
}
}
#[cfg(feature = "recorder")]
pub(crate) fn emit_path_or_assign(
name: &str,
values: &[String],
attrs: crate::recorder::ParamAttrs,
is_append: bool,
ctx: &crate::recorder::RecordCtx,
) {
let lower = name.to_ascii_lowercase();
let kind_name: Option<&'static str> = match lower.as_str() {
"path" => Some("path"),
"fpath" => Some("fpath"),
"manpath" => Some("manpath"),
"module_path" => Some("module_path"),
"cdpath" => Some("cdpath"),
_ => None,
};
match kind_name {
Some(k) => {
for v in values {
crate::recorder::emit_path_mod(v, k, ctx.clone());
if k == "fpath" {
crate::recorder::discover_completions_in_fpath_dir(v, ctx);
}
}
}
None => {
crate::recorder::emit_array_assign(
name,
values.to_vec(),
attrs,
is_append,
ctx.clone(),
);
}
}
}
use std::os::unix::fs::MetadataExt;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct ForkFlags: u32 {
const NOJOB = 1 << 0; const NEWGRP = 1 << 1; const FGTTY = 1 << 2; const KEEPSIGS = 1 << 3; }
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct SubshellFlags: u32 {
const NOMONITOR = 1 << 0; const KEEPFDS = 1 << 1; const KEEPTRAPS = 1 << 2; }
}
#[derive(Debug)]
pub enum ForkResult {
Parent(i32), Child,
}
#[derive(Debug, Clone, Copy)]
pub enum RedirMode {
Dup,
Close,
}
#[derive(Debug, Clone, Copy)]
pub enum BuiltinType {
Normal,
Disabled,
}
use crate::fusevm_bridge::with_executor;
use crate::ported::glob::*;
use crate::ported::hist::*;
use crate::ported::jobs::*;
use crate::ported::math::*;
use crate::ported::module::*;
use crate::ported::modules::cap::*;
use crate::ported::modules::terminfo::*;
use crate::ported::options::*;
use crate::ported::params::*;
use crate::ported::pattern::*;
use crate::ported::prompt::*;
use crate::ported::signals::*;
use crate::ported::subst::*;
use crate::ported::utils::{zerr, zerrnam, zwarn, zwarnnam};
use ::regex::{Error as RegexError, Regex, RegexBuilder};
pub use crate::ported::modules::regex::posix_ere_bracket_escape;
impl ShellExecutor {
pub(crate) fn all_zsh_options() -> Vec<&'static str> {
ZSH_OPTIONS_SET.iter().copied().collect()
}
pub(crate) fn default_options() -> HashMap<String, bool> {
let on = default_on_options();
Self::all_zsh_options()
.into_iter()
.map(|n| (n.to_string(), on.contains(n)))
.collect()
}
}
impl ShellExecutor {
pub(crate) fn get_variable(&self, name: &str) -> String {
getsparam(name).unwrap_or_default()
}
}
struct NoAliasesRestore(bool);
impl Drop for NoAliasesRestore {
fn drop(&mut self) {
crate::ported::lex::set_noaliases(self.0); }
}
fn restore_loaddir(name: &str, dir: &str, abspath_used: bool, ksh_style: bool) {
if ksh_style {
return; }
if let Ok(mut tab) = crate::ported::hashtable::shfunctab_lock().write() {
if let Some(shf) = tab.get_mut(name) {
crate::ported::exec::loadautofnsetfile(shf, Some(dir)); if abspath_used {
shf.node.flags |= crate::ported::zsh_h::PM_ABSPATH_USED as i32; }
}
}
}
fn body_came_from_param_assignment(flags: i32) -> bool {
(flags as u32 & crate::ported::zsh_h::PM_LOADDIR) == 0
}
fn param_definition_source(name: &str, body: &str) -> String {
format!("{name}() {{\n{body}\n}}")
}
fn autoload_is_ksh_style(name: &str) -> bool {
let flags = crate::ported::utils::getshfunc(name)
.map(|f| f.node.flags as u32)
.unwrap_or(0);
let ksh = if flags & crate::ported::zsh_h::PM_KSHSTORED != 0 {
2 } else if flags & crate::ported::zsh_h::PM_ZSHSTORED != 0 {
0 } else {
1 };
ksh == 2 || (ksh == 1 && crate::ported::zsh_h::isset(crate::ported::zsh_h::KSHAUTOLOAD))
}
fn autoload_source_key(
name: &str,
registered: &str,
from_wordcode: bool,
) -> Option<(String, [u8; 32])> {
let dir = crate::ported::utils::getshfunc(name)
.and_then(|f| f.filename)
.filter(|d| d != "zsh")?;
let sha = if from_wordcode {
crate::autoload_cache::source_digest(&format!("\0zwc\0{registered}"))
} else {
crate::autoload_cache::source_digest(registered)
};
Some((dir, sha))
}
struct ZerrMute(i32);
impl ZerrMute {
fn enter() -> Self {
let mut g = crate::ported::utils::noerrs_lock().lock().unwrap();
let prev = *g;
*g = 1;
Self(prev)
}
}
impl Drop for ZerrMute {
fn drop(&mut self) {
if let Ok(mut g) = crate::ported::utils::noerrs_lock().lock() {
*g = self.0;
}
}
}
struct AutoloadSource {
text: String,
from_wordcode: bool,
parse_failed: bool,
}
fn autoload_register_source(name: &str, body: &str) -> AutoloadSource {
let ksh_style = autoload_is_ksh_style(name);
if let Some((text, from_wordcode, parse_failed)) =
crate::autoload_cache::take_hit(name, body, ksh_style)
{
return AutoloadSource {
text,
from_wordcode,
parse_failed,
};
}
let from_wordcode = autoload_body_from_wordcode(name, body);
let _relex = from_wordcode.then(ZwcRelexGuard::enter);
let (text, parse_failed) = autoload_definition_source(name, body, ksh_style);
crate::autoload_cache::commit_search(name, body, &text, parse_failed, ksh_style);
AutoloadSource {
text,
from_wordcode,
parse_failed,
}
}
pub(crate) struct ZwcRelexGuard {
rcquotes: bool,
noaliases: bool,
}
impl ZwcRelexGuard {
pub(crate) fn enter() -> Self {
let rcquotes = crate::ported::zsh_h::isset(crate::ported::zsh_h::RCQUOTES);
if rcquotes {
crate::ported::options::opt_state_set("rcquotes", false);
}
let noaliases = crate::ported::lex::noaliases();
crate::ported::lex::set_noaliases(true); Self {
rcquotes,
noaliases,
}
}
}
impl Drop for ZwcRelexGuard {
fn drop(&mut self) {
if self.rcquotes {
crate::ported::options::opt_state_set("rcquotes", true);
}
crate::ported::lex::set_noaliases(self.noaliases);
}
}
static FUNCDEF_LEX_RCQUOTES: Mutex<Option<HashMap<String, ([u8; 32], bool)>>> = Mutex::new(None);
pub(crate) fn funcdef_note_rcquotes(name: &str, body: &str, rcquotes: bool) {
let digest = crate::autoload_cache::source_digest(body);
let rcquotes = FUNCDEF_PARSE_RCQUOTES
.lock()
.as_ref()
.and_then(|m| m.get(&digest).copied())
.unwrap_or(rcquotes);
FUNCDEF_LEX_RCQUOTES
.lock()
.get_or_insert_with(HashMap::new)
.insert(
name.to_string(),
(digest, rcquotes),
);
}
static FUNCDEF_PARSE_RCQUOTES: Mutex<Option<HashMap<[u8; 32], bool>>> = Mutex::new(None);
static FUNCDEF_ALIAS_RESOLVED: Mutex<Option<std::collections::HashSet<[u8; 32]>>> =
Mutex::new(None);
pub(crate) fn funcdef_note_resolved_body(body: Option<&str>) {
let Some(body) = body else { return };
let digest = crate::autoload_cache::source_digest(body);
FUNCDEF_ALIAS_RESOLVED
.lock()
.get_or_insert_with(std::collections::HashSet::new)
.insert(digest);
FUNCDEF_PARSE_RCQUOTES
.lock()
.get_or_insert_with(HashMap::new)
.insert(digest, crate::ported::zsh_h::isset(crate::ported::zsh_h::RCQUOTES));
}
pub(crate) fn funcdef_lex_pin(name: &str, body: &str) -> FuncdefLexPin {
let noaliases = crate::ported::lex::noaliases();
let sticky_restore = crate::ported::utils::getshfunc(name)
.and_then(|f| f.sticky.clone())
.filter(|s| crate::ported::exec::sticky_emulation_differs(Some(s)) != 0)
.map(|s| install_sticky_emulation(&s));
let captured = FUNCDEF_ALIAS_RESOLVED
.lock()
.as_ref()
.is_some_and(|set| set.contains(&crate::autoload_cache::source_digest(body)));
let deparse_noaliases = captured
|| crate::ported::utils::getshfunc(name).is_some_and(|f| {
(f.node.flags as u32 & crate::ported::zsh_h::PM_UNALIASED) != 0
})
|| autoload_body_from_wordcode(name, body);
if deparse_noaliases {
crate::ported::lex::set_noaliases(true); }
let want = {
let slot = FUNCDEF_LEX_RCQUOTES.lock();
slot.as_ref()
.and_then(|map| map.get(name))
.filter(|(sha, _)| *sha == crate::autoload_cache::source_digest(body))
.map(|(_, rcquotes)| *rcquotes)
};
let live = crate::ported::zsh_h::isset(crate::ported::zsh_h::RCQUOTES);
match want {
Some(w) if w != live => {
crate::ported::options::opt_state_set("rcquotes", w);
FuncdefLexPin {
restore: Some(live),
noaliases,
sticky_restore,
}
}
_ => FuncdefLexPin {
restore: None,
noaliases,
sticky_restore,
},
}
}
pub(crate) struct FuncdefLexPin {
restore: Option<bool>,
noaliases: bool,
sticky_restore: Option<(Vec<bool>, i32, i32, bool)>,
}
impl Drop for FuncdefLexPin {
fn drop(&mut self) {
if let Some(live) = self.restore {
crate::ported::options::opt_state_set("rcquotes", live);
}
crate::ported::lex::set_noaliases(self.noaliases);
if let Some(saved) = self.sticky_restore.take() {
restore_sticky_emulation(saved);
}
}
}
type SavedEmulation = (Vec<bool>, i32, i32, bool);
fn install_sticky_emulation(s: &crate::ported::zsh_h::emulation_options) -> SavedEmulation {
use std::sync::atomic::Ordering;
let size = crate::ported::zsh_h::OPT_SIZE as usize;
let saved: Vec<bool> = (0..size)
.map(|optno| {
optno > 0
&& crate::ported::options::opt_state_get(crate::ported::zsh_h::opt_name(optno as i32))
.unwrap_or(false)
})
.collect();
let saved_emu = (
crate::ported::options::emulation.load(Ordering::Relaxed),
crate::ported::options::EMULATION.load(Ordering::Relaxed),
crate::ported::options::FULLY_EMULATING.load(Ordering::Relaxed),
);
let mut new_opts = [-1i8; crate::ported::zsh_h::OPT_SIZE as usize];
crate::ported::options::installemulation(s.emulation, &mut new_opts); for (optno, &v) in new_opts.iter().enumerate().skip(1) {
if v >= 0 {
crate::ported::options::opt_state_set(crate::ported::zsh_h::opt_name(optno as i32), v == 1);
}
}
for on in &s.on_opts {
crate::ported::options::opt_state_set(crate::ported::zsh_h::opt_name(*on as i32), true); }
for off in &s.off_opts {
crate::ported::options::opt_state_set(crate::ported::zsh_h::opt_name(*off as i32), false); }
let emu_base = s.emulation & !crate::ported::zsh_h::EMULATE_FULLY;
crate::ported::options::emulation.store(emu_base, Ordering::Relaxed);
crate::ported::options::EMULATION.store(emu_base, Ordering::Relaxed);
crate::ported::options::FULLY_EMULATING
.store((s.emulation & crate::ported::zsh_h::EMULATE_FULLY) != 0, Ordering::Relaxed);
(saved, saved_emu.0, saved_emu.1, saved_emu.2)
}
fn restore_sticky_emulation((opts, emu, emu_cell, fully): SavedEmulation) {
use std::sync::atomic::Ordering;
for (optno, on) in opts.into_iter().enumerate().skip(1) {
crate::ported::options::opt_state_set(crate::ported::zsh_h::opt_name(optno as i32), on);
}
crate::ported::options::emulation.store(emu, Ordering::Relaxed);
crate::ported::options::EMULATION.store(emu_cell, Ordering::Relaxed);
crate::ported::options::FULLY_EMULATING.store(fully, Ordering::Relaxed);
}
struct AutoloadStickyGuard {
saved_opts: SavedEmulation,
saved_sticky: Option<crate::ported::zsh_h::Emulation_options>,
}
impl AutoloadStickyGuard {
fn enter(stub: &crate::ported::zsh_h::shfunc) -> Option<Self> {
let s = stub.sticky.as_deref()?;
if crate::ported::exec::sticky_emulation_differs(Some(s)) == 0 {
return None;
}
let dup = crate::ported::exec::sticky_emulation_dup(s, 1);
let saved_opts = install_sticky_emulation(&dup);
let saved_sticky = std::mem::replace(
&mut *crate::ported::options::sticky
.lock()
.unwrap_or_else(|e| e.into_inner()),
Some(dup),
);
Some(Self {
saved_opts,
saved_sticky,
})
}
}
impl Drop for AutoloadStickyGuard {
fn drop(&mut self) {
*crate::ported::options::sticky
.lock()
.unwrap_or_else(|e| e.into_inner()) = self.saved_sticky.take();
restore_sticky_emulation(std::mem::take(&mut self.saved_opts));
}
}
static AUTOLOAD_WORDCODE_BODY: Mutex<Option<HashMap<String, [u8; 32]>>> = Mutex::new(None);
pub(crate) fn autoload_note_wordcode_body(name: &str, body: Option<&str>) {
let mut slot = AUTOLOAD_WORDCODE_BODY.lock();
match body {
Some(text) => {
slot.get_or_insert_with(HashMap::new)
.insert(name.to_string(), crate::autoload_cache::source_digest(text));
}
None => {
if let Some(map) = slot.as_mut() {
map.remove(name);
}
}
}
}
pub(crate) fn autoload_body_from_wordcode(name: &str, body: &str) -> bool {
let slot = AUTOLOAD_WORDCODE_BODY.lock();
slot.as_ref()
.and_then(|map| map.get(name))
.is_some_and(|sha| *sha == crate::autoload_cache::source_digest(body))
}
pub(crate) fn autoload_definition_source(
name: &str,
body: &str,
ksh_style: bool,
) -> (String, bool) {
if ksh_style {
return (body.to_string(), false); }
let swap_scriptname = !crate::worker::in_worker_thread();
let old_scriptname = crate::ported::utils::scriptname_get(); if swap_scriptname {
crate::ported::utils::set_scriptname(Some(name.to_string())); }
let saved_errflag = crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed);
crate::ported::utils::errflag.fetch_and(
!crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
let prog = crate::ported::exec::parse_string(body, 1); let parse_failed = (crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed)
& crate::ported::utils::ERRFLAG_ERROR)
!= 0
|| prog.is_none();
crate::ported::utils::errflag.store(saved_errflag, std::sync::atomic::Ordering::Relaxed);
if swap_scriptname {
crate::ported::utils::set_scriptname(old_scriptname); }
let stripped = prog
.map(|prog| {
let original_len = prog.prog.len();
let prog_box = Box::new(prog);
crate::ported::exec::stripkshdef(Some(prog_box), name)
.map(|p| p.prog.len() != original_len)
.unwrap_or(false)
})
.unwrap_or(false);
let text = if stripped {
body.to_string()
} else {
format!("{name}() {{\n{body}\n}}")
};
(text, parse_failed)
}
impl ShellExecutor {
pub fn run_trap(&mut self, signal: &str) {
let action = crate::ported::builtin::traps_table()
.lock()
.ok()
.and_then(|t| t.get(signal).cloned());
if let Some(body) = action {
if !body.is_empty() {
let _ = self.execute_string_without_exit_hooks(&body);
}
}
}
}
impl ShellExecutor {
pub fn expand_glob(&self, pattern: &str) -> Vec<String> {
if !crate::ported::zsh_h::isset(crate::ported::zsh_h::EXECOPT) {
return vec![crate::ported::lex::untokenize(pattern)];
}
let errflag_before =
crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed);
let expanded = glob_path(pattern);
if !expanded.is_empty() {
if crate::ported::zsh_h::isset(crate::ported::zsh_h::CSHNULLGLOB) {
let mut pattern_tok = pattern.to_string();
crate::ported::glob::tokenize(&mut pattern_tok);
if crate::ported::pattern::haswilds(&pattern_tok) {
crate::ported::glob::BADCSHGLOB
.fetch_or(2, std::sync::atomic::Ordering::Relaxed);
}
}
return expanded;
}
if crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed) != 0 {
if errflag_before == 0 {
self.current_command_glob_failed.set(true);
}
return Vec::new();
}
if crate::ported::glob::CURGLOBDATA
.lock()
.unwrap_or_else(|e| e.into_inner())
.matchct
> 0
{
if crate::ported::zsh_h::isset(crate::ported::zsh_h::CSHNULLGLOB) {
crate::ported::glob::BADCSHGLOB.fetch_or(2, std::sync::atomic::Ordering::Relaxed);
}
return Vec::new();
}
let per_glob_nullglob = crate::ported::glob::parse_qualifiers(pattern)
.1
.map(|q| q.nullglob)
.unwrap_or(false);
let nullglob = opt_state_get("nullglob").unwrap_or(false) || per_glob_nullglob;
if nullglob {
if crate::ported::glob::IN_EXPANDREDIR.load(std::sync::atomic::Ordering::SeqCst) != 0 {
zerr(&format!(
"redirection failed (no match): {}",
crate::ported::lex::untokenize(pattern)
)); self.current_command_glob_failed.set(true);
return Vec::new(); }
return Vec::new();
}
let nomatch = opt_state_get("nomatch").unwrap_or(true);
let mut pattern_tok = pattern.to_string();
crate::ported::glob::tokenize(&mut pattern_tok); let is_glob = crate::ported::pattern::haswilds(&pattern_tok);
if is_glob && crate::ported::zsh_h::isset(crate::ported::zsh_h::CSHNULLGLOB) {
crate::ported::glob::BADCSHGLOB.fetch_or(1, std::sync::atomic::Ordering::Relaxed);
return Vec::new();
}
if nomatch && is_glob {
zerr(&format!(
"no matches found: {}",
crate::ported::lex::untokenize(pattern)
)); self.current_command_glob_failed.set(true);
return Vec::new(); }
vec![crate::ported::lex::untokenize(pattern)]
}
pub(crate) fn looks_like_glob(pattern: &str) -> bool {
let has_qual_suffix = if let Some(open) = pattern.rfind('(') {
pattern.ends_with(')') && open + 1 < pattern.len() - 1
} else {
false
};
let body = if let Some(open) = pattern.rfind('(') {
if pattern.ends_with(')') {
&pattern[..open]
} else {
pattern
}
} else {
pattern
};
let chars: Vec<char> = body.chars().collect();
let mut i = 0;
let mut has_unescaped_star = false;
let mut has_unescaped_question = false;
let mut has_unescaped_bracket_open: Option<usize> = None;
while i < chars.len() {
let c = chars[i];
if c == '\\' && i + 1 < chars.len() {
i += 2;
continue;
}
match c {
'*' => has_unescaped_star = true,
'?' => has_unescaped_question = true,
'[' if has_unescaped_bracket_open.is_none() => {
has_unescaped_bracket_open = Some(i);
}
_ => {}
}
i += 1;
}
let has_bracket_class = has_unescaped_bracket_open
.map(|i| body[i + 1..].contains(']'))
.unwrap_or(false);
let has_numeric_range =
body.contains('<') && body.contains('>') && !extract_numeric_ranges(body).is_empty();
has_unescaped_star
|| has_unescaped_question
|| has_bracket_class
|| has_qual_suffix
|| has_numeric_range
}
}
impl ShellExecutor {
pub(crate) fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
if !dest.exists() {
fs::create_dir_all(dest)?;
}
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let src_path = entry.path();
let dest_path = dest.join(entry.file_name());
if file_type.is_dir() {
Self::copy_dir_recursive(&src_path, &dest_path)?;
} else {
fs::copy(&src_path, &dest_path)?;
}
}
Ok(())
}
}
use std::cell::RefCell;
thread_local! {
static SCAN_KEYS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
static MATERIALIZED_MODULE_PARAMS: std::sync::OnceLock<Mutex<HashSet<String>>> =
std::sync::OnceLock::new();
pub fn mark_module_param_used(name: &str) {
let set = MATERIALIZED_MODULE_PARAMS.get_or_init(|| Mutex::new(HashSet::new()));
let first_touch = {
let mut g = set.lock();
g.insert(name.to_string())
};
if first_touch {
materialize_module_param(name);
}
}
fn materialize_module_param(name: &str) {
use std::cell::Cell;
thread_local! {
static LOADING: Cell<bool> = const { Cell::new(false) };
}
let Some((_, modname)) = AUTOLOAD_PARAMS.iter().find(|(p, _)| *p == name) else {
return;
};
if LOADING.with(|f| f.get()) {
return;
}
let Ok(mut tab) = crate::ported::module::MODULESTAB.try_lock() else {
return;
};
if tab
.modules
.get(*modname)
.is_some_and(|m| (m.node.flags & crate::ported::zsh_h::MOD_INIT_B) != 0)
{
return;
}
LOADING.with(|f| f.set(true));
let _ = crate::ported::module::ensurefeature(&mut tab, modname, "p:", Some(name)); LOADING.with(|f| f.set(false));
}
pub fn unmark_module_param_used(name: &str) {
let set = MATERIALIZED_MODULE_PARAMS.get_or_init(|| Mutex::new(HashSet::new()));
set.lock().remove(name);
}
pub fn module_param_is_autoload_stub(name: &str) -> bool {
if !AUTOLOAD_PARAMS.iter().any(|(p, _)| *p == name) {
return false;
}
MATERIALIZED_MODULE_PARAMS
.get_or_init(|| Mutex::new(HashSet::new()))
.lock()
.contains(name)
.eq(&false)
}
pub fn magic_special_shadowed(name: &str) -> bool {
if !PARTAB.iter().any(|e| e.name == name) && !PARTAB_ARRAY.iter().any(|e| e.name == name) {
return false;
}
let gated_loaded = module_gated_partab_module(name)
.map(|m| crate::ported::module::MODULESTAB.lock().unwrap().is_loaded(m));
crate::ported::params::paramtab()
.read()
.map_or(false, |tab| {
let Some(pm) = tab.get(name) else {
return match gated_loaded {
Some(loaded) => loaded,
None => PARTAB_SEEDED.load(std::sync::atomic::Ordering::Acquire),
};
};
{
let f = pm.node.flags as u32;
if (f & crate::ported::zsh_h::PM_UNSET) != 0
&& (f & crate::ported::zsh_h::PM_DECLARED) == 0
{
return true;
}
}
{
let f = pm.node.flags as u32;
if pm.level == 0 && (f & crate::ported::zsh_h::PM_AUTOLOAD) != 0 {
return false;
}
(f & crate::ported::zsh_h::PM_SPECIAL) == 0
}
})
}
pub fn magic_special_shadowed_by_nonhash(name: &str) -> bool {
magic_special_shadowed(name)
&& crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(name).map(|pm| pm.node.flags as u32))
.map_or(true, |f| {
use crate::ported::zsh_h::{PM_DECLARED, PM_TYPE, PM_UNSET};
PM_TYPE(f) != PM_HASHED || (f & (PM_UNSET | PM_DECLARED)) == PM_UNSET
})
}
pub fn partab_get(name: &str, key: &str) -> Option<String> {
if magic_special_shadowed(name) {
return None;
}
mark_module_param_used(name);
if let Some(modname) = module_gated_partab_module(name) {
if !crate::ported::module::MODULESTAB
.lock()
.unwrap()
.is_loaded(modname)
{
return None;
}
}
for entry in PARTAB.iter() {
if entry.name == name {
return (entry.getfn)(std::ptr::null_mut(), key)
.filter(|p| p.node.flags as u32 & crate::ported::zsh_h::PM_UNSET == 0)
.and_then(|p| p.u_str);
}
}
None
}
fn module_gated_partab_module(name: &str) -> Option<&'static str> {
match name {
"sysparams" | "errnos" => Some("zsh/system"),
"mapfile" => Some("zsh/mapfile"),
"langinfo" => Some("zsh/langinfo"),
".zle.esc" | ".zle.sgr" => Some("zsh/hlgroup"),
".sh.match" => Some("zsh/ksh93"),
_ => None,
}
}
pub fn set_readonly_special(name: &str, value: &str) {
use crate::ported::zsh_h::PM_READONLY;
let was_readonly = crate::ported::params::paramtab()
.write()
.ok()
.and_then(|mut tab| {
tab.get_mut(name).map(|pm| {
let ro = (pm.node.flags & PM_READONLY as i32) != 0;
pm.node.flags &= !(PM_READONLY as i32);
ro
})
})
.unwrap_or(false);
let _ = crate::ported::params::setsparam(name, value);
if was_readonly {
if let Ok(mut tab) = crate::ported::params::paramtab().write() {
if let Some(pm) = tab.get_mut(name) {
pm.node.flags |= PM_READONLY as i32;
}
}
}
}
pub fn partab_array_get(name: &str) -> Option<Vec<String>> {
if magic_special_shadowed(name) {
return None;
}
mark_module_param_used(name);
if let Some(modname) = module_gated_partab_module(name) {
if !crate::ported::module::MODULESTAB
.lock()
.unwrap()
.is_loaded(modname)
{
return None;
}
}
for entry in PARTAB_ARRAY.iter() {
if entry.name == name {
return Some((entry.getfn)(std::ptr::null_mut()));
}
}
None
}
pub fn partab_scan_keys(name: &str) -> Option<Vec<String>> {
if magic_special_shadowed(name) {
return None;
}
mark_module_param_used(name);
if let Some(modname) = module_gated_partab_module(name) {
if !crate::ported::module::MODULESTAB
.lock()
.unwrap()
.is_loaded(modname)
{
return None;
}
}
for entry in PARTAB.iter() {
if entry.name == name {
SCAN_KEYS.with(|k| k.borrow_mut().clear());
fn cb(pm: &crate::ported::zsh_h::param, _flags: i32) {
SCAN_KEYS.with(|k| k.borrow_mut().push(pm.node.nam.clone()));
}
(entry.scanfn)(
std::ptr::null_mut(),
Some(cb),
crate::ported::zsh_h::SCANPM_WANTKEYS as i32,
);
return Some(SCAN_KEYS.with(|k| k.borrow().clone()));
}
}
None
}
pub fn init_partab_params() {
use crate::ported::modules::parameter::{PARTAB, PARTAB_ARRAY};
use crate::ported::zsh_h::{
hashnode, param, Param, PM_HIDE, PM_HIDEVAL, PM_READONLY, PM_SPECIAL,
};
let mut tab = match paramtab().write() {
Ok(t) => t,
Err(_) => return,
};
let user_protected: &[&str] = &[
"parameters",
"reswords",
"dis_reswords",
"patchars",
"dis_patchars",
"historywords",
"errnos",
"keymaps",
"builtins", "dis_builtins", "functions_source", "dis_functions_source", "history", "jobdirs", "jobstates", "jobtexts", "modules", "userdirs", "usergroups", "widgets", "funcstack", "funcfiletrace", "funcsourcetrace", "functrace", "termcap", "terminfo", "zsh_scheduled_events", ];
let mk_pm = |name: &str, flags: i32| -> Param {
let keep_readonly = user_protected.contains(&name);
let pre_readonly_mask = if keep_readonly {
!0i32
} else {
!(PM_READONLY as i32)
};
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: (flags & pre_readonly_mask)
| PM_SPECIAL as i32
| PM_HIDE as i32
| PM_HIDEVAL as i32,
},
u_data: 0,
u_tied: None,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
})
};
let module_gated: &[&str] = &[
"sysparams", "errnos", "mapfile", "langinfo", ".zle.esc", ".zle.sgr", ".sh.match", ];
for entry in PARTAB.iter() {
if module_gated.contains(&entry.name) {
continue;
}
tab.remove(entry.name); tab.insert(entry.name.to_string(), mk_pm(entry.name, entry.flags));
}
for entry in PARTAB_ARRAY.iter() {
if module_gated.contains(&entry.name) {
continue;
}
tab.remove(entry.name); tab.insert(entry.name.to_string(), mk_pm(entry.name, entry.flags));
}
PARTAB_SEEDED.store(true, std::sync::atomic::Ordering::Release);
}
static PARTAB_SEEDED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn seed_partab_param(name: &str) {
use crate::ported::modules::parameter::{PARTAB, PARTAB_ARRAY};
use crate::ported::zsh_h::{hashnode, param, PM_HIDE, PM_HIDEVAL, PM_READONLY, PM_SPECIAL};
let mut tab = match crate::ported::params::paramtab().write() {
Ok(t) => t,
Err(_) => return,
};
if tab
.get(name)
.is_some_and(|pm| (pm.node.flags as u32 & crate::ported::zsh_h::PM_AUTOLOAD) == 0)
{
return; }
let flags = PARTAB
.iter()
.find(|e| e.name == name)
.map(|e| e.flags)
.or_else(|| {
PARTAB_ARRAY
.iter()
.find(|e| e.name == name)
.map(|e| e.flags)
});
let Some(flags) = flags else {
return;
};
tab.remove(name);
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: flags | PM_SPECIAL as i32 | PM_HIDE as i32 | PM_HIDEVAL as i32,
},
u_data: 0,
u_tied: None,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
tab.insert(name.to_string(), pm);
}
pub const AUTOLOAD_PARAMS: &[(&str, &str)] = &[
("WATCH", "zsh/watch"),
("watch", "zsh/watch"),
("aliases", "zsh/parameter"),
("builtins", "zsh/parameter"),
("commands", "zsh/parameter"),
("dirstack", "zsh/parameter"),
("dis_aliases", "zsh/parameter"),
("dis_builtins", "zsh/parameter"),
("dis_functions", "zsh/parameter"),
("dis_functions_source", "zsh/parameter"),
("dis_galiases", "zsh/parameter"),
("dis_patchars", "zsh/parameter"),
("dis_reswords", "zsh/parameter"),
("dis_saliases", "zsh/parameter"),
("funcfiletrace", "zsh/parameter"),
("funcsourcetrace", "zsh/parameter"),
("funcstack", "zsh/parameter"),
("functions", "zsh/parameter"),
("functions_source", "zsh/parameter"),
("functrace", "zsh/parameter"),
("galiases", "zsh/parameter"),
("history", "zsh/parameter"),
("historywords", "zsh/parameter"),
("jobdirs", "zsh/parameter"),
("jobstates", "zsh/parameter"),
("jobtexts", "zsh/parameter"),
("modules", "zsh/parameter"),
("nameddirs", "zsh/parameter"),
("options", "zsh/parameter"),
("parameters", "zsh/parameter"),
("patchars", "zsh/parameter"),
("reswords", "zsh/parameter"),
("saliases", "zsh/parameter"),
("userdirs", "zsh/parameter"),
("usergroups", "zsh/parameter"),
("keymaps", "zsh/zleparameter"),
("widgets", "zsh/zleparameter"),
("termcap", "zsh/termcap"),
("terminfo", "zsh/terminfo"),
("zsh_scheduled_events", "zsh/sched"),
];
pub fn autoload_param_stubs() -> Vec<(&'static str, &'static str)> {
use crate::ported::zsh_h::{MOD_INIT_B, MOD_UNLOAD};
let tab = crate::ported::module::MODULESTAB.lock().unwrap();
AUTOLOAD_PARAMS
.iter()
.copied()
.filter(|(_, m)| {
!tab.modules.get(*m).is_some_and(|md| {
(md.node.flags & MOD_INIT_B) != 0 && (md.node.flags & MOD_UNLOAD) == 0
})
})
.collect()
}
pub fn module_gated_params_for(module: &str) -> &'static [&'static str] {
match module {
"zsh/system" => &["sysparams", "errnos"],
"zsh/mapfile" => &["mapfile"],
"zsh/langinfo" => &["langinfo"],
"zsh/hlgroup" => &[".zle.esc", ".zle.sgr"],
"zsh/ksh93" => &[".sh.match"],
_ => &[],
}
}
impl ShellExecutor {
pub fn enter_posix_mode(&mut self) {
self.posix_mode = true;
self.plugin_cache = std::cell::OnceCell::from(None);
self.compsys_cache = std::cell::OnceCell::new();
self.compinit_pending = None;
self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
crate::ported::options::emulate("sh", true);
}
pub fn enter_ksh_mode(&mut self) {
self.plugin_cache = std::cell::OnceCell::from(None);
self.compsys_cache = std::cell::OnceCell::new();
self.compinit_pending = None;
self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
crate::ported::options::emulate("ksh", true);
}
pub fn enter_dash_mode(&mut self) {
self.posix_mode = true;
self.plugin_cache = std::cell::OnceCell::from(None);
self.compsys_cache = std::cell::OnceCell::new();
self.compinit_pending = None;
self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
crate::ported::options::emulate("dash", true);
}
}
pub fn glob_match_static(s: &str, pattern: &str) -> bool {
let Some(prog) = patcompile(
&{
let mut __pat_tok = (pattern).to_string();
crate::ported::glob::tokenize(&mut __pat_tok);
__pat_tok
},
PAT_HEAPDUP as i32,
None,
) else {
return false;
};
let matched = pattry(&prog, s);
matched
}
pub use crate::ported::lex::untokenize_ztokens;
pub use crate::ported::utils::unmetafy_str;
pub use crate::ported::utils::zsh_errno_msg;
pub use crate::ported::params::*;
pub(crate) fn is_host_zsh_function_tree(p: &Path) -> bool {
if p.file_name() != Some(OsStr::new("functions")) {
return false;
}
let mut comps = p.components().map(|c| c.as_os_str());
while let Some(c) = comps.next() {
if c == "share" && comps.clone().next() == Some(OsStr::new("zsh")) {
return true;
}
}
false
}
fn default_fpath() -> Vec<PathBuf> {
let mut out: Vec<PathBuf> = vec![PathBuf::from("/usr/local/share/zsh/site-functions")];
for pfx in [
"/usr/local",
"/opt/homebrew",
"/home/linuxbrew/.linuxbrew",
"/usr",
] {
let site = PathBuf::from(format!("{pfx}/share/zsh/site-functions"));
if site.is_dir() && !out.contains(&site) {
out.push(site);
}
}
out
}