use std::os::unix::fs::PermissionsExt;
use std::sync::atomic::Ordering;
use crate::ported::builtin::{cd_able_vars, fixdir, BUILTINS, DOPRINTDIR, EXIT_VAL, LASTVAL};
use crate::ported::builtins::rlimits::setlimits;
use crate::ported::builtins::sched::zleactive;
use crate::ported::compat::zgettime_monotonic_if_available;
use crate::ported::config_h::DEFAULT_PATH;
use crate::ported::context::{zcontext_restore, zcontext_save};
use crate::ported::hashtable::{
cmdnam_unhashed, cmdnamtab_lock, dircache_set, hashdir, pathchecked, shfunctab_lock,
};
use crate::ported::hist::{strinbeg, strinend};
use crate::ported::init::{shout, underscorelen, underscoreused, zunderscore, SHTTY};
use crate::ported::input::{inpop, inpush};
use crate::ported::jobs::{expandjobtab, get_usage, release_pgrp, waitforpid, JOBTAB, THISJOB};
use crate::ported::lex::{
hgetc, parsestr, tok, untokenize, ztokens, LEXERR, LEX_LEXSTOP, LEX_LINENO,
};
use crate::ported::mem::{dupstring, dyncat, popheap, pushheap};
use crate::ported::modules::clone::mypgrp;
use crate::ported::options::{dosetopt, opt_state_set, sticky};
use crate::ported::params::{
endparamscope, getsparam, locallevel, paramtab, setiparam, zgetenv, zputenv,
};
use crate::ported::parse::{closedumps, ecrawstr, parse_list};
use crate::ported::prompt::{cmdpop, cmdpush};
use crate::ported::signals::{
intrap, queue_signals, settrap, signal_mask, signal_unblock, sigtrapped, trapisfunc,
traplocallevel, unqueue_signals, unsettrap,
};
use crate::ported::signals_h::{
child_block, child_unblock, dont_queue_signals, signal_default, signal_ignore, winch_unblock,
SIGCOUNT,
};
use crate::ported::subst::{quotesubst, singsub};
use crate::ported::utils::{
errflag, fdtable_get, fdtable_set, gettempfile, gettempname, inc_locallevel, movefd, pathprog,
printprompt4, quotedzputs, redup, unmeta, unmetafy, write_loop, zclose, zerr, zwarn,
ERRFLAG_ERROR, MAX_ZSH_FD,
};
use crate::ported::zsh_h::{
builtin, cmdnam, emulation_options, eprog, execstack, funcwrap, hashnode, isset, multio, redir,
shfunc, unset, wc_code, Emulation_options, Inang, Inpar, Meta, Nularg, Outpar, Pound,
BINF_BUILTIN, BINF_CLEARENV, BINF_COMMAND, BINF_DASH, BINF_EXEC, BINF_PREFIX, CHASEDOTS,
CHASELINKS, CLOBBER, CLOBBEREMPTY, CS_CMDSUBST, ERRFLAG_INT, FDT_EXTERNAL, FDT_INTERNAL,
FDT_PROC_SUBST, FDT_SAVED_MASK, FDT_TYPE_MASK, FDT_UNUSED, FDT_XTRACE, HASHDIRS, INP_LINENO,
INTERACTIVE, IS_CLOBBER_REDIR, IS_DASH, JOBTEXTSIZE, MAX_PIPESTATS, MONITOR, MULTIOS,
MULTIOUNIT, PATHDIRS, PM_LOADDIR, PM_READONLY, PM_UNDEFINED, POSIXBUILTINS, POSIXJOBS,
POSIXTRAPS, REDIRF_FROM_HEREDOC, REDIR_CLOSE, REDIR_HEREDOCDASH, REDIR_HERESTR, REDIR_INPIPE,
REDIR_OUTPIPE, USEZLE, VERBOSE, WC_LIST, WC_LIST_TYPE, WC_PIPE, WC_PIPE_END, WC_PIPE_TYPE,
WC_REDIR, WC_REDIR_TYPE, WC_REDIR_VARID, WC_SIMPLE, WC_SIMPLE_ARGC, WC_SUBLIST, WC_SUBLIST_END,
WC_SUBLIST_FLAGS, WC_SUBLIST_TYPE, WC_TYPESET, ZSIG_FUNC, ZSIG_IGNORED, Z_END,
};
use crate::ported::zsh_system_h::timespec as ZshTimespec;
use crate::ported::ztype_h::{inull, itok};
use crate::zsh_h::XTRACE;
pub const ADDVAR_EXPORT: i32 = 1 << 0; pub const ADDVAR_RESTORE: i32 = 1 << 2;
pub static TRAP_STATE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static TRAP_RETURN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static FORKLEVEL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static noerrexit: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static this_noerrexit: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LIST_PIPE_TEXT: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static noerrs: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static nohistsave: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static subsh: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static zsh_subshell: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static retflag: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static cmdoutpid: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static procsubstpid: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static cmdoutval: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static use_cmdoutval: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static sfcontext: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static list_pipe: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static simple_pline: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static list_pipe_pid: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static nowait: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static pline_level: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static list_pipe_child: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static list_pipe_job: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static doneps4: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static esprefork: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static esglob: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
pub static exstack: std::sync::Mutex<Option<Box<execstack>>> = std::sync::Mutex::new(None);
pub static STTYval: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub fn gethere(strp: &mut String, typ: i32) -> Option<String> {
let mut buf: String; let mut bsiz: usize; let mut qt: i32 = 0; let mut strip: i32 = 0; let mut t: usize; let mut c: Option<char>; let mut str: String = strp.clone();
for s in str.bytes() {
if inull(s) {
qt = 1; break; }
}
str = quotesubst(&str); str = untokenize(&str); if typ == REDIR_HEREDOCDASH {
strip = 1; while str.starts_with('\t') {
str.remove(0);
}
}
*strp = str.clone();
bsiz = 256;
buf = String::with_capacity(bsiz);
let _ = bsiz;
loop {
t = buf.len();
loop {
c = hgetc();
if !(c == Some('\t') && strip != 0) {
break;
}
}
loop {
if LEX_LEXSTOP.with(|f| f.get()) || c == Some('\n') || c.is_none() {
break;
}
if qt == 0 && c == Some('\\') {
buf.push('\\'); c = hgetc(); if c == Some('\n') {
buf.pop(); c = hgetc(); continue; }
}
if let Some(ch) = c {
buf.push(ch);
}
c = hgetc(); }
if &buf[t..] == str.as_str() {
break;
}
if LEX_LEXSTOP.with(|f| f.get()) {
t = buf.len();
break;
}
buf.push('\n');
}
buf.truncate(t);
buf = dupstring(&buf);
if qt == 0 {
let ef = errflag.load(Ordering::Relaxed);
if let Ok(parsed) = parsestr(&buf) {
buf = parsed;
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let cur = errflag.load(Ordering::Relaxed);
errflag.store(ef | (cur & ERRFLAG_INT), Ordering::Relaxed);
}
}
Some(buf) }
pub fn getoutput(cmd: &str, qt: i32) -> Vec<String> {
let prog: Option<eprog>;
let mut s: String; let onc = crate::ported::lex::LEX_NOCOMMENTS.with(|c| c.get());
let new_nc = crate::ported::zsh_h::interact()
&& crate::ported::init::sourcelevel.load(Ordering::Relaxed) == 0
&& !isset(crate::ported::zsh_h::INTERACTIVECOMMENTS);
crate::ported::lex::LEX_NOCOMMENTS.with(|c| c.set(new_nc));
prog = parse_string(cmd, 0);
crate::ported::lex::LEX_NOCOMMENTS.with(|c| c.set(onc));
if prog.is_none() {
return Vec::new(); }
let prog = prog.unwrap();
if !isset(crate::ported::zsh_h::EXECOPT) {
return Vec::new(); }
if let Some(red_name) = simple_redir_name(&prog, crate::ported::zsh_h::REDIR_READ) {
s = red_name;
s = singsub(&s); if errflag.load(Ordering::Relaxed) != 0 {
return Vec::new(); }
let s = untokenize(&s); let path_meta = unmeta(&s); let cpath = match std::ffi::CString::new(path_meta.as_bytes()) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let stream = unsafe {
libc::open(cpath.as_ptr(), libc::O_RDONLY | libc::O_NOCTTY) };
if stream == -1 {
let errno = std::io::Error::last_os_error();
zerr(&format!("{}: {}", errno, s));
LASTVAL.store(1, Ordering::Relaxed);
cmdoutval.store(1, Ordering::Relaxed);
return Vec::new(); }
let mut readerror: i32 = 0;
let retval = readoutput(stream, qt, &mut readerror); if readerror != 0 {
zerr(&format!(
"error when reading {}: {}", s,
std::io::Error::from_raw_os_error(readerror)
));
LASTVAL.store(1, Ordering::Relaxed);
cmdoutval.store(1, Ordering::Relaxed);
}
return retval; }
cmdoutval.store(0, Ordering::Relaxed); let buf = crate::ported::exec_hooks::run_command_substitution(cmd);
LASTVAL.store(cmdoutval.load(Ordering::Relaxed), Ordering::Relaxed);
let buf = buf.trim_end_matches('\n');
if qt != 0 {
if buf.is_empty() {
vec![String::from(Nularg)] } else {
vec![buf.to_string()] }
} else {
crate::ported::utils::spacesplit(buf, false) }
}
pub fn loadautofn(
shf: *mut shfunc, _ks: i32,
autol: i32,
_ignore_loaddir: i32,
) -> i32 {
if shf.is_null() {
return 1;
}
let name = unsafe { (*shf).node.nam.clone() };
let mut dir_path: Option<String> = None;
let path = match getfpfunc(&name, &mut dir_path, None, 0) {
Some(p) => p,
None => {
crate::ported::utils::zwarn(&format!(
"{}: function definition file not found",
name
));
return 1; }
};
let _ = autol;
let body = match std::fs::read_to_string(&path) {
Ok(t) => t,
Err(_) => return 1,
};
unsafe {
loadautofnsetfile(&mut *shf, dir_path.as_deref().or(Some(&path)));
}
unsafe {
(*shf).node.flags &= !(PM_UNDEFINED as i32);
}
if let Ok(mut tab) = shfunctab_lock().write() {
if let Some(existing) = tab.get_mut(&name) {
existing.body = Some(body);
existing.filename = dir_path;
} else {
tab.add(shfunc {
node: hashnode {
next: None,
nam: name.clone(),
flags: 0,
},
filename: dir_path,
lineno: 0,
funcdef: None,
redir: None,
sticky: None,
body: Some(body),
});
}
}
0
}
pub fn getfpfunc(
name: &str,
dir_path_out: &mut Option<String>, spec_path: Option<&[String]>,
_all_loaded: i32,
) -> Option<String> {
let dirs: Vec<String> = match spec_path {
Some(s) => s.to_vec(),
None => crate::ported::params::getaparam("fpath")
.filter(|v| !v.is_empty())
.or_else(|| getsparam("FPATH").map(|v| v.split(':').map(String::from).collect()))
.or_else(|| {
std::env::var("FPATH")
.ok()
.map(|v| v.split(':').map(String::from).collect())
})
.unwrap_or_default(),
};
for dir in &dirs {
if dir.is_empty() {
continue;
}
let path = format!("{}/{}", dir, name);
if std::path::Path::new(&path).exists() {
*dir_path_out = Some(dir.clone());
return Some(path);
}
}
None
}
pub fn resolvebuiltin<'a>(
cmdarg: &str, hn: &'a builtin,
) -> Option<&'a builtin> {
if hn.handlerfunc.is_none() {
let modname = hn.optstr.clone().unwrap_or_default();
let _ = {
let mut t = crate::ported::module::MODULESTAB.lock().unwrap();
crate::ported::module::ensurefeature(&mut t, &modname, "b:", Some(cmdarg))
};
if let Some(re) = BUILTINS.iter().find(|b| b.node.nam == cmdarg) {
if re.handlerfunc.is_some() {
return Some(re); }
}
zerr(&format!(
"autoloading module {} failed to define builtin: {}",
modname, cmdarg
));
return None; }
Some(hn) }
#[allow(non_camel_case_types)]
#[derive(Debug, Default, Clone)]
pub struct execcmd_dispatch {
pub precmd_skip: usize,
pub is_builtin: bool,
pub is_shfunc: bool,
pub cflags: u32,
pub use_defpath: bool,
pub has_command_vv: bool,
pub exec_argv0: Option<String>,
pub is_empty_command: bool,
}
pub fn execcmd_compile_head(args: &[String], type_: u32) -> execcmd_dispatch {
let mut hn: Option<&'static builtin> = None; let mut is_shfunc = false; let mut is_builtin = false; let mut use_defpath = false; let mut cflags: u32 = 0; let mut orig_cflags: u32 = 0; let _ = orig_cflags;
let mut exec_argv0: Option<String> = None;
let mut has_command_vv = false;
let mut preargs: Vec<String> = args.to_vec(); let mut precmd_skip: usize = 0;
if (type_ == WC_SIMPLE || type_ == WC_TYPESET) && !preargs.is_empty() {
while precmd_skip < preargs.len() {
let cmdarg = untokenize(&preargs[precmd_skip]);
if (cflags & (BINF_BUILTIN | BINF_COMMAND)) == 0 {
if shfunctab_lock()
.read()
.map(|t| t.iter().any(|(k, _)| k == &cmdarg))
.unwrap_or(false)
{
is_shfunc = true; break; }
}
let entry = BUILTINS.iter().find(|b| b.node.nam == cmdarg);
let Some(entry) = entry else {
break;
};
hn = Some(entry);
orig_cflags |= cflags;
cflags &= !(BINF_BUILTIN | BINF_COMMAND);
cflags |= entry.node.flags as u32;
if (entry.node.flags as u32 & BINF_PREFIX) == 0 {
is_builtin = true; break; }
precmd_skip += 1;
if (cflags & BINF_COMMAND) != 0 && precmd_skip < preargs.len() {
let mut argnode: usize = precmd_skip; let mut pnode: Option<usize> = None; let mut has_p = false; let mut has_vv = false; let mut has_other = false; while argnode < preargs.len()
&& IS_DASH(preargs[argnode].chars().next().unwrap_or('\0'))
{
let argdata = preargs[argnode].clone(); let bytes = argdata.as_bytes();
if bytes.len() < 2 || (IS_DASH(bytes[1] as char) && bytes.len() == 2) {
break; }
for &c in &bytes[1..] {
match c as char {
'p' => {
has_p = true; pnode = Some(argnode); }
'v' | 'V' => {
has_vv = true; }
_ => {
has_other = true; }
}
}
if has_other {
has_p = false; has_vv = false; break; }
argnode += 1; if argnode >= preargs.len() {
break; }
}
if has_vv {
has_command_vv = true; is_builtin = true;
break; } else if has_p {
use_defpath = true; if let Some(pn) = pnode {
if pn < preargs.len() {
preargs.remove(pn);
}
}
}
if argnode < preargs.len() {
let argdata = &preargs[argnode];
let b = argdata.as_bytes();
if b.len() == 2 && IS_DASH(b[0] as char) && IS_DASH(b[1] as char) {
preargs.remove(argnode); }
}
} else if (cflags & BINF_EXEC) != 0 && precmd_skip < preargs.len() {
let mut argnode: usize = precmd_skip; let mut error_done = false;
while argnode < preargs.len() {
let argdata = preargs[argnode].clone();
let bytes = argdata.as_bytes();
if bytes.is_empty() || !IS_DASH(bytes[0] as char) || bytes.len() < 2 {
break; }
let oldnode = argnode; argnode += 1; if argnode >= preargs.len() {
zerr(
"exec requires a command to execute",
);
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); error_done = true;
break; }
preargs.remove(oldnode);
argnode -= 1; if bytes.len() == 2 && IS_DASH(bytes[0] as char) && IS_DASH(bytes[1] as char) {
break; }
let mut k = 1usize;
while k < bytes.len() && !error_done {
let cmdopt = bytes[k] as char; match cmdopt {
'a' => {
if k + 1 < bytes.len() {
exec_argv0 =
Some(String::from_utf8_lossy(&bytes[k + 1..]).into_owned()); k = bytes.len(); } else {
if argnode >= preargs.len() {
zerr(
"exec flag -a requires a parameter",
);
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); error_done = true;
break; }
exec_argv0 = Some(preargs[argnode].clone()); preargs.remove(argnode); }
}
'c' => {
cflags |= BINF_CLEARENV; }
'l' => {
cflags |= BINF_DASH; }
_ => {
zerr(
&format!("unknown exec flag -{}", cmdopt),
);
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); error_done = true;
break; }
}
k += 1;
}
if error_done {
break;
}
}
if let Some(ref a0) = exec_argv0 {
let cleaned = untokenize(a0); exec_argv0 = Some(cleaned);
}
if error_done {
return execcmd_dispatch {
precmd_skip,
is_builtin,
is_shfunc,
cflags,
use_defpath,
has_command_vv,
exec_argv0,
is_empty_command: false,
};
}
}
if (cflags & BINF_COMMAND) != 0 && !isset(POSIXBUILTINS) {
hn = None; break; }
}
}
let is_empty_command = precmd_skip >= preargs.len();
let _ = hn;
execcmd_dispatch {
precmd_skip,
is_builtin,
is_shfunc,
cflags,
use_defpath,
has_command_vv,
exec_argv0,
is_empty_command,
}
}
pub fn parse_string(s: &str, reset_lineno: i32) -> Option<eprog> {
let p: Option<eprog>;
let oldlineno: i64;
zcontext_save(); inpush(s, INP_LINENO, None); strinbeg(0); oldlineno = LEX_LINENO.get() as i64; if reset_lineno != 0 {
LEX_LINENO.set(1); }
p = parse_list(); LEX_LINENO.set(oldlineno as u64); if tok() == LEXERR && LASTVAL.load(Ordering::Relaxed) == 0 {
LASTVAL.store(1, Ordering::Relaxed);
}
strinend(); inpop(); zcontext_restore(); p }
pub fn isgooderr(e: i32, dir: &str) -> bool {
let unmeta_dir = unmeta(dir);
let cstr = std::ffi::CString::new(unmeta_dir.as_bytes()).unwrap_or_default();
let access_ok = unsafe { libc::access(cstr.as_ptr(), libc::X_OK) } == 0;
(e != libc::EACCES || access_ok) && e != libc::ENOENT && e != libc::ENOTDIR
}
pub fn iscom(s: &str) -> bool {
let us = unmeta(s); let cstr = match std::ffi::CString::new(us.as_str()) {
Ok(c) => c,
Err(_) => return false,
};
let x_ok = unsafe { libc::access(cstr.as_ptr(), libc::X_OK) } == 0;
if !x_ok {
return false;
}
let meta = match std::fs::metadata(&us) {
Ok(m) => m,
Err(_) => return false,
};
meta.file_type().is_file()
}
pub fn isreallycom(cn: &cmdnam) -> bool {
let fullnam: String;
if (cn.node.flags & crate::ported::zsh_h::HASHED) != 0 {
fullnam = cn.cmd.clone().unwrap_or_default();
} else if cn.name.is_none() || cn.name.as_ref().unwrap().is_empty() {
return false;
} else {
let path0 = &cn.name.as_ref().unwrap()[0];
fullnam = format!("{}/{}", path0, cn.node.nam);
}
iscom(&fullnam) }
pub fn isrelative(s: &str) -> i32 {
let bytes = s.as_bytes();
if bytes.is_empty() || bytes[0] != b'/' {
return 1; }
for i in 1..bytes.len() {
let c = bytes[i];
let prev = bytes[i - 1];
if c == b'.' && prev == b'/' {
let next = bytes.get(i + 1).copied().unwrap_or(0);
if next == b'/' || next == 0 {
return 1;
}
if next == b'.' {
let next2 = bytes.get(i + 2).copied().unwrap_or(0);
if next2 == b'/' || next2 == 0 {
return 1;
}
}
}
}
0 }
pub fn setunderscore(str: &str) {
queue_signals(); if !str.is_empty() {
let mut zu = zunderscore.lock().unwrap();
*zu = str.to_string();
let nl = (str.len() + 1 + 31) & !31; underscorelen.store(nl, Ordering::Relaxed); underscoreused.store((str.len() + 1) as i32, Ordering::Relaxed);
} else {
let mut zu = zunderscore.lock().unwrap();
zu.clear(); underscoreused.store(1, Ordering::Relaxed); }
unqueue_signals(); }
pub fn mpipe(pp: &mut [i32; 2]) -> i32 {
let mut fds: [libc::c_int; 2] = [-1; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
zerr(&format!(
"pipe failed: {}",
std::io::Error::last_os_error()
));
return -1; }
pp[0] = movefd(fds[0]); pp[1] = movefd(fds[1]); 0 }
pub const ANONYMOUS_FUNCTION_NAME: &str = "(anon)";
pub fn is_anonymous_function_name(name: &str) -> i32 {
if name == ANONYMOUS_FUNCTION_NAME {
1
} else {
0
}
}
pub fn execsave() {
let mut es = Box::new(execstack {
next: None,
list_pipe_pid: list_pipe_pid.load(Ordering::Relaxed), nowait: nowait.load(Ordering::Relaxed), pline_level: pline_level.load(Ordering::Relaxed), list_pipe_child: list_pipe_child.load(Ordering::Relaxed), list_pipe_job: list_pipe_job.load(Ordering::Relaxed), list_pipe_text: {
let mut buf = [0u8; JOBTEXTSIZE];
if let Ok(s) = LIST_PIPE_TEXT.lock() {
let bytes = s.as_bytes();
let n = bytes.len().min(JOBTEXTSIZE - 1);
buf[..n].copy_from_slice(&bytes[..n]);
}
buf
},
lastval: LASTVAL.load(Ordering::Relaxed), noeval: crate::ported::math::m_noeval(),
badcshglob: crate::ported::glob::BADCSHGLOB.load(Ordering::Relaxed), cmdoutpid: cmdoutpid.load(Ordering::Relaxed), cmdoutval: cmdoutval.load(Ordering::Relaxed), use_cmdoutval: use_cmdoutval.load(Ordering::Relaxed), procsubstpid: procsubstpid.load(Ordering::Relaxed), trap_return: TRAP_RETURN.load(Ordering::Relaxed), trap_state: TRAP_STATE.load(Ordering::Relaxed), trapisfunc: trapisfunc.load(Ordering::Relaxed), traplocallevel: traplocallevel.load(Ordering::Relaxed), noerrs: noerrs.load(Ordering::Relaxed), this_noerrexit: this_noerrexit.load(Ordering::Relaxed), underscore: Some(zunderscore.lock().unwrap().clone()),
});
let mut head = exstack.lock().unwrap();
es.next = head.take();
*head = Some(es);
noerrs.store(0, Ordering::Relaxed);
cmdoutpid.store(0, Ordering::Relaxed);
}
pub fn execrestore() {
let mut head = exstack.lock().unwrap();
let en = match head.take() {
Some(en) => en,
None => {
crate::DPUTS!(true, "BUG: execrestore() without execsave()");
return;
}
};
queue_signals(); *head = en.next; drop(head);
list_pipe_pid.store(en.list_pipe_pid, Ordering::Relaxed); nowait.store(en.nowait, Ordering::Relaxed); pline_level.store(en.pline_level, Ordering::Relaxed); list_pipe_child.store(en.list_pipe_child, Ordering::Relaxed); list_pipe_job.store(en.list_pipe_job, Ordering::Relaxed); if let Ok(mut s) = LIST_PIPE_TEXT.lock() {
let nul = en
.list_pipe_text
.iter()
.position(|&b| b == 0)
.unwrap_or(JOBTEXTSIZE);
*s = String::from_utf8_lossy(&en.list_pipe_text[..nul]).into_owned();
}
LASTVAL.store(en.lastval, Ordering::Relaxed); crate::ported::math::m_noeval_set(en.noeval);
crate::ported::glob::BADCSHGLOB.store(en.badcshglob, Ordering::Relaxed);
cmdoutpid.store(en.cmdoutpid, Ordering::Relaxed); cmdoutval.store(en.cmdoutval, Ordering::Relaxed); use_cmdoutval.store(en.use_cmdoutval, Ordering::Relaxed); procsubstpid.store(en.procsubstpid, Ordering::Relaxed); TRAP_RETURN.store(en.trap_return, Ordering::Relaxed); TRAP_STATE.store(en.trap_state, Ordering::Relaxed); trapisfunc.store(en.trapisfunc, Ordering::Relaxed); traplocallevel.store(en.traplocallevel, Ordering::Relaxed); noerrs.store(en.noerrs, Ordering::Relaxed); this_noerrexit.store(en.this_noerrexit, Ordering::Relaxed); if let Some(ref u) = en.underscore {
setunderscore(u); }
unqueue_signals(); }
pub fn execstring(s: &str, _dont_change_job: i32, _exiting: i32, _context: &str) {
pushheap(); if isset(VERBOSE) {
let mut stderr = std::io::stderr().lock();
use std::io::Write;
let _ = stderr.write_all(s.as_bytes()); let _ = stderr.write_all(b"\n"); let _ = stderr.flush(); }
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(s);
popheap(); }
pub fn runshfunc(prog: &eprog, mut wrap: Option<&funcwrap>, name: &str) {
queue_signals(); let ouu = underscoreused.load(Ordering::Relaxed) as usize;
let ou: Option<String> = if ouu > 0 {
Some(zunderscore.lock().unwrap().clone()) } else {
None
};
while let Some(w) = wrap {
let mod_name: Option<String> = w.module.as_ref().map(|m| m.node.nam.clone());
if let Some(ref n) = mod_name {
if let Ok(mut tab) = crate::ported::module::MODULESTAB.lock() {
if let Some(m) = tab.modules.get_mut(n) {
m.wrapper += 1;
}
}
}
let cont = if let Some(h) = w.handler {
let next_sentinel = Box::new(funcwrap {
next: None,
flags: 0,
handler: None,
module: None,
});
h(Box::new(prog.clone()), next_sentinel, name)
} else {
1
};
if let Some(ref n) = mod_name {
let should_unload = {
if let Ok(mut tab) = crate::ported::module::MODULESTAB.lock() {
if let Some(m) = tab.modules.get_mut(n) {
m.wrapper -= 1;
m.wrapper == 0 && (m.node.flags & crate::ported::zsh_h::MOD_UNLOAD) != 0
} else {
false
}
} else {
false
}
};
if should_unload {
if let Ok(mut tab) = crate::ported::module::MODULESTAB.lock() {
let _ = tab.unload_module(n); }
}
}
if cont == 0 {
unqueue_signals(); return; }
wrap = None;
}
inc_locallevel();
if let Some(ref src) = prog.strs {
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(src);
} else {
execode(Box::new(prog.clone()), 1, 0, "shfunc");
let _ = name;
}
if let Some(ou_str) = ou {
setunderscore(&ou_str); }
endparamscope(); let exit_pending = crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed);
let exit_level = crate::ported::builtin::EXIT_LEVEL.load(Ordering::Relaxed);
let cur_locallevel = locallevel.load(Ordering::Relaxed) as i32;
let cur_forklevel = FORKLEVEL.load(Ordering::Relaxed);
let in_exit_trap = crate::ported::signals::in_exit_trap.load(Ordering::Relaxed); if exit_pending != 0 && exit_level >= cur_locallevel + 1 && in_exit_trap == 0 {
if cur_locallevel > cur_forklevel {
RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(LOOPS.load(Ordering::Relaxed), Ordering::Relaxed); } else {
crate::ported::builtin::STOPMSG.store(1, Ordering::Relaxed); let val = EXIT_VAL.load(Ordering::Relaxed);
crate::ported::builtin::zexit(val, crate::ported::zsh_h::ZEXIT_NORMAL);
}
}
unqueue_signals(); }
pub fn sticky_emulation_dup(src: &emulation_options, _useheap: i32) -> Emulation_options {
let mut newsticky = Box::new(emulation_options {
emulation: src.emulation, n_on_opts: 0,
n_off_opts: 0,
on_opts: Vec::new(),
off_opts: Vec::new(),
});
if src.n_on_opts != 0 {
newsticky.n_on_opts = src.n_on_opts; newsticky.on_opts = src.on_opts.clone(); }
if src.n_off_opts != 0 {
newsticky.n_off_opts = src.n_off_opts; newsticky.off_opts = src.off_opts.clone(); }
newsticky }
pub fn shfunc_set_sticky(shf: &mut shfunc) {
let sticky_guard = sticky.lock().unwrap();
if let Some(ref s) = *sticky_guard {
shf.sticky = Some(sticky_emulation_dup(s, 0)); } else {
shf.sticky = None; }
}
pub fn search_defpath(cmd: &str, plen: usize) -> Option<String> {
for ps in DEFAULT_PATH.split(':') {
if !ps.starts_with('/') {
continue;
}
if ps.len() >= plen {
continue; }
let full_len = ps.len() + 1 + cmd.len();
if full_len >= plen {
continue; }
let buf = format!("{}/{}", ps, cmd); if iscom(&buf) {
return Some(buf); }
}
None }
pub fn checkclobberparam(f: &redir) -> i32 {
let s = match &f.varid {
Some(v) => v.clone(),
None => return 1, };
let mut vbuf = crate::ported::zsh_h::value {
pm: None,
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: 0,
};
let mut cursor: &str = s.as_str();
let v_opt = crate::ported::params::getvalue(Some(&mut vbuf), &mut cursor, 0);
if v_opt.is_none() {
return 1; }
let readonly = vbuf
.pm
.as_ref()
.map(|p| (p.node.flags as u32 & PM_READONLY) != 0)
.unwrap_or(false);
if readonly {
zwarn(&format!(
"can't allocate file descriptor to readonly parameter {}",
s
));
return 0; }
if !isset(CLOBBER) {
let val_str = crate::ported::params::getstrvalue(Some(&mut vbuf));
if let Ok(fd) = val_str.trim().parse::<i32>() {
let max_fd = MAX_ZSH_FD.load(Ordering::Relaxed);
if fd >= 0 && fd <= max_fd {
let kind = fdtable_get(fd);
if kind == FDT_EXTERNAL {
zwarn(&format!("{}: file descriptor {} already open", s, fd)); return 0; }
}
}
}
1 }
pub fn clobber_open(f: &redir) -> i32 {
let ufname_owned = unmeta(f.name.as_deref().unwrap_or("")); let ufname = match std::ffi::CString::new(ufname_owned.as_str()) {
Ok(c) => c,
Err(_) => return -1,
};
if isset(CLOBBER) || IS_CLOBBER_REDIR(f.typ) {
let fd = unsafe {
libc::open(
ufname.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC | libc::O_NOCTTY,
0o666 as libc::c_uint,
)
};
return fd; }
let fd = unsafe {
libc::open(
ufname.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL | libc::O_NOCTTY,
0o666 as libc::c_uint,
)
};
if fd >= 0 {
return fd; }
let oerrno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
let fd = unsafe {
libc::open(
ufname.as_ptr(),
libc::O_WRONLY | libc::O_NOCTTY,
0o666 as libc::c_uint,
)
};
if fd != -1 {
let mut buf: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut buf) } == 0 {
if (buf.st_mode & libc::S_IFMT) != libc::S_IFREG {
return fd; }
if isset(CLOBBEREMPTY) && buf.st_size == 0 {
return fd; }
}
unsafe {
libc::close(fd);
} }
#[cfg(target_os = "macos")]
unsafe {
*libc::__error() = oerrno;
}
#[cfg(target_os = "linux")]
unsafe {
*libc::__errno_location() = oerrno;
}
-1 }
pub fn findcmd(arg0: &str, _docopy: i32, default_path: i32) -> Option<String> {
if default_path != 0 {
return search_defpath(arg0, libc::PATH_MAX as usize);
}
if arg0.len() > libc::PATH_MAX as usize {
return None;
}
if arg0.contains('/') {
if iscom(arg0) {
return Some(arg0.to_string()); }
if arg0.starts_with('/')
|| !isset(PATHDIRS)
|| arg0.starts_with("./")
|| arg0.starts_with("../")
{
return None;
}
}
let path = getsparam("PATH")?;
for dir in path.split(':') {
if dir.is_empty() {
continue;
}
let candidate = format!("{}/{}", dir, arg0);
if iscom(&candidate) {
return Some(candidate);
}
}
None }
pub fn addfd(
forked: i32,
save: &mut [i32; 10],
mfds: &mut [Option<Box<multio>>; 10],
fd1: i32,
fd2: i32,
rflag: i32,
varid: Option<&str>,
) {
let mut pipes: [i32; 2] = [-1; 2];
if let Some(vid) = varid {
let fd_moved = movefd(fd2); if fd_moved == -1 {
zerr(&format!(
"cannot move fd {}: {}",
fd2,
std::io::Error::last_os_error()
));
return; }
fdtable_set(fd_moved, FDT_EXTERNAL);
setiparam(vid, fd_moved as i64);
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
let _ = zclose(fd_moved); }
return;
}
let fd1u = fd1 as usize;
if fd1u >= mfds.len() {
return;
}
if mfds[fd1u].is_none() || unset(MULTIOS) {
if mfds[fd1u].is_none() {
mfds[fd1u] = Some(Box::new(multio {
ct: 0,
rflag: 0,
pipe: -1,
fds: vec![-1; MULTIOUNIT],
}));
if forked == 0 && save[fd1u] == -2 {
if fd1 == fd2 {
save[fd1u] = -1; } else {
let fd_n = movefd(fd1); if fd_n < 0 {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if e != libc::EBADF {
zerr(&format!(
"cannot duplicate fd {}: {}",
fd1,
std::io::Error::from_raw_os_error(e)
));
mfds[fd1u] = None; closemnodes(mfds); return; }
} else {
crate::DPUTS!(
fdtable_get(fd_n) != FDT_INTERNAL,
"Saved file descriptor not marked as internal"
);
let cur = fdtable_get(fd_n);
fdtable_set(fd_n, cur | FDT_SAVED_MASK);
}
save[fd1u] = fd_n; }
}
}
let _ = redup(fd2, fd1);
if let Some(mn) = mfds[fd1u].as_mut() {
mn.ct = 1; mn.fds[0] = fd1; mn.rflag = rflag; }
} else {
let cur_rflag = mfds[fd1u].as_ref().map(|m| m.rflag).unwrap_or(0);
if cur_rflag != rflag {
zerr(&format!("file mode mismatch on fd {}", fd1)); closemnodes(mfds); return; }
let cur_ct = mfds[fd1u].as_ref().map(|m| m.ct).unwrap_or(0);
if cur_ct == 1 {
let fd_n = movefd(fd1);
if fd_n < 0 {
zerr(&format!(
"multio failed for fd {}: {}",
fd1,
std::io::Error::last_os_error()
));
closemnodes(mfds); return; }
if let Some(mn) = mfds[fd1u].as_mut() {
mn.fds[0] = fd_n; }
let fd_n2 = movefd(fd2);
if fd_n2 < 0 {
zerr(&format!(
"multio failed for fd {}: {}",
fd2,
std::io::Error::last_os_error()
));
closemnodes(mfds); return; }
if let Some(mn) = mfds[fd1u].as_mut() {
mn.fds[1] = fd_n2; }
if mpipe(&mut pipes) < 0 {
zerr(&format!(
"multio failed for fd {}: {}",
fd2,
std::io::Error::last_os_error()
));
closemnodes(mfds); return; }
if let Some(mn) = mfds[fd1u].as_mut() {
mn.pipe = pipes[(1 - rflag) as usize];
}
let _ = redup(pipes[rflag as usize], fd1);
if let Some(mn) = mfds[fd1u].as_mut() {
mn.ct = 2;
}
} else {
if let Some(mn) = mfds[fd1u].as_mut() {
while mn.fds.len() <= cur_ct as usize {
mn.fds.push(-1);
}
}
let fd_n = movefd(fd2);
if fd_n < 0 {
zerr(&format!(
"multio failed for fd {}: {}",
fd2,
std::io::Error::last_os_error()
));
closemnodes(mfds); return; }
if let Some(mn) = mfds[fd1u].as_mut() {
let slot = mn.ct as usize;
if slot < mn.fds.len() {
mn.fds[slot] = fd_n;
mn.ct += 1;
}
}
}
}
}
pub fn closemn(mfds: &mut [Option<Box<multio>>; 10], fd: i32, type_: i32) {
let needs_tee = fd >= 0
&& (fd as usize) < mfds.len()
&& mfds[fd as usize].as_ref().is_some_and(|m| m.ct >= 2);
if needs_tee {
let mn = mfds[fd as usize].take().unwrap();
let mut buf = [0u8; 4092]; child_block();
let mut bgtime = ZshTimespec {
tv_sec: 0,
tv_nsec: 0,
};
let pid = zfork(Some(&mut bgtime));
if pid != 0 {
for i in 0..mn.ct as usize {
if i < mn.fds.len() {
let _ = zclose(mn.fds[i]); }
}
let _ = zclose(mn.pipe); if pid == -1 {
child_unblock(); return; }
let mut mn_back = mn;
mn_back.ct = 1; mn_back.fds[0] = fd; mfds[fd as usize] = Some(mn_back);
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addproc(
j,
pid,
"",
true,
Some(std::time::Instant::now()),
-1,
-1,
);
}
}
}
let _ = bgtime;
child_unblock(); return; }
opt_state_set("interactive", false); dont_queue_signals(); child_unblock(); closeallelse(&mn); if mn.rflag != 0 {
loop {
let len = unsafe {
libc::read(mn.pipe, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
};
if len == 0 {
break;
}
if len < 0 {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if e == libc::EINTR {
continue;
} else {
break; }
}
for i in 0..mn.ct as usize {
if i >= mn.fds.len() {
break;
}
if write_loop(mn.fds[i], &buf[..len as usize]).is_err() {
break; }
}
}
} else {
for i in 0..mn.ct as usize {
if i >= mn.fds.len() {
break;
}
loop {
let len = unsafe {
libc::read(mn.fds[i], buf.as_mut_ptr() as *mut libc::c_void, buf.len())
};
if len == 0 {
break;
}
if len < 0 {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if e == libc::EINTR && unsafe { libc::isatty(mn.fds[i]) } == 0 {
continue;
} else {
break; }
}
if write_loop(mn.pipe, &buf[..len as usize]).is_err() {
break; }
}
}
}
unsafe {
libc::_exit(0);
}
} else if fd >= 0 && type_ == REDIR_CLOSE {
if (fd as usize) < mfds.len() {
mfds[fd as usize] = None;
}
}
}
pub fn closemnodes(mfds: &mut [Option<Box<multio>>; 10]) {
for i in 0..10 {
if let Some(mn) = mfds[i].take() {
for j in 0..mn.ct as usize {
if j < mn.fds.len() {
let _ = zclose(mn.fds[j]); }
}
}
}
}
pub fn closeallelse(mn: &multio) {
let openmax = MAX_ZSH_FD.load(Ordering::Relaxed) + 1; for i in 0..openmax {
if mn.pipe == i {
continue;
}
let mut found = false;
for j in 0..mn.ct as usize {
if j < mn.fds.len() && mn.fds[j] == i {
found = true;
break; }
}
if !found {
let _ = zclose(i); }
}
}
pub fn fixfds(save: &[i32; 10]) {
let old_errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); for i in 0..10i32 {
if save[i as usize] != -2 {
redup(save[i as usize], i); }
}
#[cfg(target_os = "macos")]
unsafe {
*libc::__error() = old_errno;
}
#[cfg(target_os = "linux")]
unsafe {
*libc::__errno_location() = old_errno;
}
}
pub fn closem(how: i32, all: i32) {
let max = MAX_ZSH_FD.load(Ordering::Relaxed); for i in 10i32..=max {
let kind = fdtable_get(i); if kind == FDT_UNUSED {
continue;
}
if all == 0 && (kind == FDT_PROC_SUBST || kind == FDT_EXTERNAL) {
continue;
}
if how != FDT_UNUSED && (kind & FDT_TYPE_MASK) != how {
continue;
}
if i == SHTTY.load(Ordering::Relaxed) {
SHTTY.store(-1, Ordering::Relaxed); }
let _ = zclose(i);
}
}
pub fn hashcmd(arg0: &str, pp: &[String]) -> Option<cmdnam> {
if arg0.starts_with('/') {
return None; }
let mut found_idx: Option<usize> = None;
for (i, dir) in pp.iter().enumerate() {
if !dir.starts_with('/') {
continue;
}
if dir.len() + 1 + arg0.len() >= libc::PATH_MAX as usize {
continue; }
let buf = format!("{}/{}", dir, arg0); if iscom(&buf) {
found_idx = Some(i);
break; }
}
let pp_idx = match found_idx {
Some(i) => i,
None => return None, };
let path_slice: Vec<String> = pp[pp_idx..].to_vec(); let cn = cmdnam_unhashed(arg0, path_slice); if let Ok(mut tab) = cmdnamtab_lock().write() {
tab.add(cn.clone());
}
if isset(HASHDIRS) {
let start = pathchecked.load(Ordering::Relaxed); for pq in start..=pp_idx {
if pq < pp.len() {
hashdir(&pp[pq], pq); }
}
pathchecked.store(pp_idx + 1, Ordering::Relaxed); }
Some(cn) }
pub fn zfork(ts: Option<&mut ZshTimespec>) -> libc::pid_t {
let pid: libc::pid_t;
let thisjob_lock = THISJOB.get_or_init(|| std::sync::Mutex::new(-1));
let thisjob = *thisjob_lock.lock().unwrap();
if thisjob != -1 {
let needed = (thisjob + 1) as usize;
let needs_expand = JOBTAB
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|t| needed >= t.len().saturating_sub(1))
.unwrap_or(false);
if needs_expand {
let mut tab = JOBTAB.get().unwrap().lock().unwrap();
if !expandjobtab(&mut tab, needed) {
zerr("job table full"); return -1; }
}
}
if let Some(ts) = ts {
zgettime_monotonic_if_available(ts);
}
queue_signals(); pid = unsafe { libc::fork() }; unqueue_signals(); if pid == -1 {
zerr(&format!(
"fork failed: {}",
std::io::Error::last_os_error()
));
return -1; }
#[cfg(unix)]
if pid == 0 {
let _ = setlimits(""); }
pid }
pub fn loadautofnsetfile(shf: &mut shfunc, fdir: Option<&str>) {
let loaddir = (shf.node.flags as u32 & PM_LOADDIR) != 0;
let same = match (&shf.filename, fdir) {
(Some(a), Some(b)) => a == b,
_ => false,
};
if !loaddir || !same {
dircache_set(&mut shf.filename, None);
if let Some(fdir) = fdir {
shf.node.flags |= PM_LOADDIR as i32; dircache_set(&mut shf.filename, Some(fdir)); } else {
shf.node.flags &= !(PM_LOADDIR as i32); shf.filename = Some(shf.node.nam.clone()); }
}
}
pub fn commandnotfound(arg0: &str, args: &mut Vec<String>) -> i32 {
let has_handler = shfunctab_lock()
.read()
.map(|t| t.get("command_not_found_handler").is_some())
.unwrap_or(false);
if !has_handler {
LASTVAL.store(127, Ordering::Relaxed); return 1; }
args.insert(0, arg0.to_string());
args.insert(0, "command_not_found_handler".to_string());
let shf_clone: Option<shfunc> = shfunctab_lock()
.read()
.ok()
.and_then(|t| t.get("command_not_found_handler").cloned());
if let Some(mut shf) = shf_clone {
let body_args = args.clone();
let body_runner = move || -> i32 {
crate::ported::exec_hooks::run_function_body(
"command_not_found_handler",
&body_args[1..],
)
.unwrap_or(0)
};
let lv = doshfunc(&mut shf, args.clone(), true, body_runner);
LASTVAL.store(lv, Ordering::Relaxed);
}
0 }
pub fn namedpipe() -> Option<String> {
let tnam = gettempname(None, true); let tnam = match tnam {
Some(t) => t,
None => {
zerr(&format!(
"failed to create named pipe: {}",
std::io::Error::last_os_error()
));
return None; }
};
let cstr = match std::ffi::CString::new(tnam.as_str()) {
Ok(c) => c,
Err(_) => return None,
};
if unsafe { libc::mkfifo(cstr.as_ptr(), 0o600) } < 0 {
zerr(&format!(
"failed to create named pipe: {}, {}",
tnam,
std::io::Error::last_os_error()
));
return None; }
Some(tnam) }
pub fn readoutput(in_fd: i32, qt: i32, readerror: &mut i32) -> Vec<String> {
let mut buf: Vec<u8> = Vec::with_capacity(64); let mut readret: isize = 0; dont_queue_signals(); child_unblock(); let mut inbuf = [0u8; 64]; loop {
let r = unsafe { libc::read(in_fd, inbuf.as_mut_ptr() as *mut libc::c_void, inbuf.len()) };
readret = r as isize;
if readret <= 0 {
if readret < 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
continue;
}
break; }
for i in 0..(readret as usize) {
let c = inbuf[i];
if crate::ported::ztype_h::imeta(c) {
buf.push(Meta as u8); buf.push(c ^ 32); } else {
buf.push(c); }
}
}
child_block(); *readerror = if readret < 0 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
} else {
0
};
unsafe {
libc::close(in_fd);
}
while buf.last() == Some(&b'\n') {
buf.pop();
}
let s = String::from_utf8_lossy(&buf).into_owned();
if qt != 0 {
if buf.is_empty() {
return vec![String::from(Nularg)]; }
return vec![s]; }
let mut words = crate::ported::utils::spacesplit(&s, false); if isset(crate::ported::zsh_h::GLOBSUBST) {
for w in words.iter_mut() {
crate::ported::glob::shtokenize(w); }
}
words
}
pub fn parsecmd(cmd: &str, eptr: Option<&mut usize>) -> Option<eprog> {
let bytes = cmd.as_bytes();
if bytes.len() < 2 {
return None;
}
let mut str_idx: usize = 2;
while str_idx < bytes.len() && (bytes[str_idx] as char) != Outpar {
str_idx += 1;
}
if str_idx >= bytes.len() || (bytes[1] as char) != Inpar {
let errstr = if bytes.len() >= 2 {
untokenize(&cmd[..2]) } else {
String::new()
};
zerr(&format!("unterminated `{}...)'", errstr)); return None; }
if let Some(p) = eptr {
*p = str_idx + 1;
}
let body = &cmd[2..str_idx];
let prog = parse_string(body, 0);
if prog.is_none() {
zerr("parse error in process substitution"); return None; }
prog }
pub const POUNDBANGLIMIT: usize = 128;
pub fn makecline(list: &[String]) -> Vec<String> {
if isset(XTRACE) {
if doneps4.load(Ordering::Relaxed) == 0 {
printprompt4(); }
let mut first = true;
let mut err = std::io::stderr().lock();
use std::io::Write;
for s in list.iter() {
if !first {
let _ = err.write_all(b" "); }
first = false;
let _ = err.write_all(quotedzputs(s).as_bytes()); }
let _ = err.write_all(b"\n"); let _ = err.flush(); }
list.to_vec() }
pub fn execute(args: &mut Vec<String>, flags: u32, defpath: i32) {
let mut eno: i32 = 0;
let mut ee: i32; let mut arg0 = if args.is_empty() {
return;
} else {
args[0].clone()
}; {
let mut stty = STTYval.lock().unwrap();
if let Some(s) = stty.take() {
if !s.is_empty()
&& unsafe { libc::isatty(0) } != 0
&& unsafe { libc::tcgetpgrp(0) } == unsafe { libc::getpid() }
{
drop(stty);
let cmd = format!("stty {}", s); execstring(&cmd, 1, 0, "stty"); }
}
}
if let Some(z) = zgetenv("ARGV0") {
args[0] = z.clone(); unsafe {
let key = std::ffi::CString::new("ARGV0").unwrap();
libc::unsetenv(key.as_ptr()); }
arg0 = args[0].clone();
} else if (flags & BINF_DASH) != 0 {
args[0] = format!("-{}", arg0); arg0 = args[0].clone();
}
let argv = makecline(args); let newenvp_owned: Option<Vec<String>> = if (flags & BINF_CLEARENV) != 0 {
Some(Vec::new()) } else {
None
};
let newenvp = newenvp_owned.as_deref();
closem(FDT_XTRACE, 0); child_unblock(); if arg0.len() >= libc::PATH_MAX as usize {
zerr(&format!("command too long: {}", arg0)); unsafe {
libc::_exit(1);
} }
if let Some(slash_pos) = arg0.find('/') {
let lerrno = zexecve(&arg0, &argv, newenvp); let is_dot = arg0.starts_with('.')
&& (slash_pos == 1 || (arg0.len() > 2 && &arg0[..2] == ".." && slash_pos == 2));
if slash_pos == 0 || unset(PATHDIRS) || is_dot {
zerr(&format!(
"{}: {}",
std::io::Error::from_raw_os_error(lerrno),
arg0
)); let code = if lerrno == libc::EACCES || lerrno == libc::ENOEXEC {
126
} else {
127
};
unsafe {
libc::_exit(code);
} }
}
if defpath != 0 {
let pbuf = match search_defpath(&arg0, libc::PATH_MAX as usize) {
Some(p) => p, None => {
if commandnotfound(&arg0, args) == 0 {
unsafe {
libc::_exit(LASTVAL.load(Ordering::Relaxed));
}
}
zerr(&format!("command not found: {}", arg0)); unsafe {
libc::_exit(127);
} }
};
ee = zexecve(&pbuf, &argv, newenvp); let dir = pbuf.rsplit_once('/').map(|(d, _)| d).unwrap_or("");
if isgooderr(ee, if dir.is_empty() { "/" } else { dir }) {
eno = ee;
}
} else {
let hashed_path: Option<String> = {
let tab = cmdnamtab_lock().read().ok();
tab.and_then(|t| {
t.get(&arg0).and_then(|cn| {
if (cn.node.flags & crate::ported::zsh_h::HASHED) != 0 {
cn.cmd.clone()
} else {
None
}
})
})
};
if let Some(nn) = hashed_path {
ee = zexecve(&nn, &argv, newenvp);
let dir = nn.rsplit_once('/').map(|(d, _)| d).unwrap_or("");
if isgooderr(ee, if dir.is_empty() { "/" } else { dir }) {
eno = ee;
}
if eno == 0 && ee != 0 {
ee = 0;
}
}
let path_str = getsparam("PATH").unwrap_or_default();
for pp in path_str.split(':') {
if pp.is_empty() || pp == "." {
ee = zexecve(&arg0, &argv, newenvp); if isgooderr(ee, pp) {
eno = ee;
}
} else {
let candidate = format!("{}/{}", pp, arg0); ee = zexecve(&candidate, &argv, newenvp); if isgooderr(ee, pp) {
eno = ee;
}
}
}
}
if eno != 0 {
zerr(&format!(
"{}: {}",
std::io::Error::from_raw_os_error(eno),
arg0
)); } else if commandnotfound(&arg0, args) == 0 {
unsafe {
libc::_exit(LASTVAL.load(Ordering::Relaxed));
} } else {
zerr(&format!("command not found: {}", arg0)); }
let code = if eno == libc::EACCES || eno == libc::ENOEXEC {
126
} else {
127
}; unsafe {
libc::_exit(code);
}
}
pub fn zexecve(pth: &str, argv: &[String], newenvp: Option<&[String]>) -> i32 {
use std::ffi::CString;
let pth_abs = if pth.starts_with('/') {
pth.to_string() } else {
format!("{}/{}", getsparam("PWD").unwrap_or_default(), pth) };
zputenv(&format!("_={}", pth_abs)); closedumps(); winch_unblock(); let cpth = match CString::new(pth) {
Ok(c) => c,
Err(_) => return libc::ENOENT,
};
let cargs: Vec<CString> = argv
.iter()
.filter_map(|a| CString::new(a.as_str()).ok())
.collect();
let mut argv_ptrs: Vec<*const libc::c_char> = cargs.iter().map(|c| c.as_ptr()).collect();
argv_ptrs.push(std::ptr::null());
let env_holder: Vec<CString>;
let env_ptrs: Vec<*const libc::c_char>;
let envp: *const *const libc::c_char = match newenvp {
Some(env) => {
env_holder = env
.iter()
.filter_map(|e| CString::new(e.as_str()).ok())
.collect();
env_ptrs = {
let mut v: Vec<*const libc::c_char> =
env_holder.iter().map(|c| c.as_ptr()).collect();
v.push(std::ptr::null());
v
};
env_ptrs.as_ptr()
}
None => unsafe {
extern "C" {
static environ: *const *const libc::c_char;
}
environ
},
};
unsafe {
libc::execve(cpth.as_ptr(), argv_ptrs.as_ptr(), envp); }
let eno = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::ENOEXEC); if eno == libc::ENOEXEC || eno == libc::ENOENT {
let fd = unsafe { libc::open(cpth.as_ptr(), libc::O_RDONLY | libc::O_NOCTTY) }; if fd < 0 {
return std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::ENOENT); }
let mut buf = vec![0u8; POUNDBANGLIMIT + 1]; let ct = unsafe {
libc::read(
fd,
buf.as_mut_ptr() as *mut libc::c_void,
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 {
zerr(&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('/') {
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());
}
winch_unblock(); return zexecve(&pprog, &argv_new, newenvp); }
zerr(&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());
}
winch_unblock(); return zexecve(&interp_str, &argv_new, newenvp); } 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());
}
winch_unblock(); return zexecve(&interp_str, &argv_new, newenvp); }
}
} 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 !argv.is_empty() && (argv[0].starts_with('-') || argv[0].starts_with('+')) {
argv_new.push("-".to_string()); }
for orig in argv.iter() {
argv_new.push(orig.clone());
}
winch_unblock(); return zexecve("/bin/sh", &argv_new, newenvp); }
}
}
}
eno }
pub fn getoutputfile(cmd: &str, eptr: Option<&mut usize>) -> Option<String> {
let bytes = cmd.as_bytes();
let _ = bytes;
let mut ends_at: usize = 0;
let prog = parsecmd(cmd, Some(&mut ends_at))?; if let Some(p) = eptr {
*p = ends_at;
}
let mut nam = gettempname(None, true)?; let mut s: Option<String> = simple_redir_name(&prog, REDIR_HERESTR).map(|raw| {
let mut sub = singsub(&raw); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
String::new() } else {
sub = untokenize(&sub); dyncat(&sub, "\n") }
});
if let Some(ref sv) = s {
if sv.is_empty() {
s = None;
}
}
if s.is_none() {
child_block(); }
let c_nam = match std::ffi::CString::new(nam.clone()) {
Ok(c) => c,
Err(_) => {
if s.is_none() {
child_unblock();
}
return None;
}
};
let fd = unsafe {
libc::open(
c_nam.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL | libc::O_NOCTTY,
0o600 as libc::c_uint,
)
};
if fd < 0 {
zerr(&format!(
"process substitution failed: {}",
std::io::Error::last_os_error()
)); if s.is_none() {
child_unblock(); }
return None; }
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addfilelist(j, Some(&nam), 0);
}
}
}
if let Some(sv) = s {
let mut buf: Vec<u8> = sv.into_bytes();
let _len = unmetafy(&mut buf); let _ = write_loop(fd, &buf); unsafe {
libc::close(fd);
} return Some(nam); }
let pid = zfork(None);
cmdoutpid.store(pid, Ordering::Relaxed);
if pid == -1 {
unsafe {
libc::close(fd);
} child_unblock(); return Some(nam); } else if pid != 0 {
unsafe {
libc::close(fd);
} let _ = waitforpid(pid); cmdoutval.store(0, Ordering::Relaxed); return Some(nam); }
closem(FDT_UNUSED, 0); let _ = redup(fd, 1); entersubsh(esub::PGRP | esub::NOMONITOR, None); cmdpush(CS_CMDSUBST as u8); let body_end = if ends_at > 0 { ends_at - 1 } else { 2 };
let body = if body_end > 2 && body_end <= cmd.len() {
&cmd[2..body_end]
} else {
""
};
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(body);
cmdpop(); unsafe {
libc::close(1);
} std::process::exit(0); #[allow(unreachable_code)]
{
let _ = &mut nam;
unsafe {
libc::kill(libc::getpid(), libc::SIGKILL);
}
None
}
}
pub fn getproc(cmd: &str, eptr: Option<&mut usize>) -> Option<String> {
let bytes = cmd.as_bytes();
let out: i32 = if !bytes.is_empty() && (bytes[0] as char) == Inang {
1 } else {
0
};
let tj_check = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj_check == -1 {
zerr(&format!("process substitution {} cannot be used here", cmd)); return None; }
let mut ends_at: usize = 0;
let _prog = parsecmd(cmd, Some(&mut ends_at))?; if let Some(p) = eptr {
*p = ends_at;
}
let mut pipes: [i32; 2] = [-1; 2];
if mpipe(&mut pipes) < 0 {
return None;
}
let mut bgtime: ZshTimespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let pid = zfork(Some(&mut bgtime)); if pid != 0 {
let pnam = format!("/dev/fd/{}", pipes[(1 - out) as usize]); let _ = zclose(pipes[out as usize]); if pid == -1 {
let _ = zclose(pipes[(1 - out) as usize]); return None; }
let fd = pipes[(1 - out) as usize]; fdtable_set(fd, FDT_PROC_SUBST); if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addfilelist(j, None, fd);
}
}
}
if out == 0 {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addproc(
j,
pid,
"",
true,
Some(std::time::Instant::now()),
-1,
-1,
);
}
}
}
}
procsubstpid.store(pid, Ordering::Relaxed); return Some(pnam); }
entersubsh(esub::ASYNC | esub::PGRP, None); let _ = redup(pipes[out as usize], out); closem(FDT_UNUSED, 0); cmdpush(CS_CMDSUBST as u8); let body_end = if ends_at > 0 { ends_at - 1 } else { 2 };
let body = if body_end > 2 && body_end <= cmd.len() {
&cmd[2..body_end]
} else {
""
};
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(body);
cmdpop(); let _ = zclose(out); std::process::exit(LASTVAL.load(Ordering::Relaxed)); }
pub mod esub {
pub const ASYNC: i32 = 0x01; pub const PGRP: i32 = 0x02; pub const KEEPTRAP: i32 = 0x04; pub const FAKE: i32 = 0x08; pub const REVERTPGRP: i32 = 0x10; pub const NOMONITOR: i32 = 0x20; pub const JOB_CONTROL: i32 = 0x40; }
#[allow(non_camel_case_types)]
#[derive(Default)]
pub struct entersubsh_ret {
pub gleader: i32, pub list_pipe_job: i32, }
pub fn entersubsh(flags: i32, retp: Option<&mut entersubsh_ret>) {
let monitor: i32;
let job_control_ok: i32;
if (flags & esub::KEEPTRAP) == 0 {
for sig in 0..=SIGCOUNT {
let st = {
let guard = sigtrapped.lock().unwrap();
guard.get(sig as usize).copied().unwrap_or(0)
};
let func_set = (st & ZSIG_FUNC) != 0; let posix_ignored = isset(POSIXTRAPS) && ((st & ZSIG_IGNORED) != 0); if !func_set && !posix_ignored {
unsettrap(sig); }
}
}
monitor = if isset(MONITOR) { 1 } else { 0 }; job_control_ok = if monitor != 0 && (flags & esub::JOB_CONTROL) != 0 && isset(POSIXJOBS) {
1
} else {
0
};
EXIT_VAL.store(0, Ordering::Relaxed); if (flags & esub::NOMONITOR) != 0 {
dosetopt(MONITOR, 0, 0); }
if !isset(MONITOR) {
if (flags & esub::ASYNC) != 0 {
let _ = settrap(libc::SIGINT, None, 0); let _ = settrap(libc::SIGQUIT, None, 0); if unsafe { libc::isatty(0) } != 0 {
unsafe {
libc::close(0);
} let devnull = std::ffi::CString::new("/dev/null").unwrap();
if unsafe { libc::open(devnull.as_ptr(), libc::O_RDWR | libc::O_NOCTTY) } != 0 {
zerr(&format!(
"can't open /dev/null: {}",
std::io::Error::last_os_error()
));
unsafe {
libc::_exit(1);
} }
}
}
} else if (flags & esub::PGRP) != 0 {
let thisjob = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if thisjob != -1 {
let lpj = list_pipe_job.load(Ordering::Relaxed);
let lp = list_pipe.load(Ordering::Relaxed);
let lpc = list_pipe_child.load(Ordering::Relaxed);
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let lpj_gleader = guard.get(lpj as usize).map(|j| j.gleader).unwrap_or(0);
if lpj_gleader != 0 && (lp != 0 || lpc != 0) {
let pgid = if unsafe { libc::setpgid(0, lpj_gleader) } == -1
|| (unsafe { libc::killpg(lpj_gleader, 0) } == -1
&& std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH))
{
let new_gl = if lpc != 0 {
mypgrp.load(Ordering::Relaxed)
} else {
unsafe { libc::getpid() }
};
if let Some(j) = guard.get_mut(lpj as usize) {
j.gleader = new_gl;
}
if let Some(j) = guard.get_mut(thisjob as usize) {
j.gleader = new_gl;
}
unsafe { libc::setpgid(0, new_gl) };
if (flags & esub::ASYNC) == 0 {
unsafe { libc::tcsetpgrp(2, new_gl) }; }
new_gl
} else {
lpj_gleader
};
if let Some(r) = retp {
if (flags & esub::ASYNC) == 0 {
r.gleader = pgid; r.list_pipe_job = lpj; }
}
} else {
let thisjob_gleader =
guard.get(thisjob as usize).map(|j| j.gleader).unwrap_or(0);
if thisjob_gleader == 0 || unsafe { libc::setpgid(0, thisjob_gleader) } == -1 {
let new_gl = unsafe { libc::getpid() };
if let Some(j) = guard.get_mut(thisjob as usize) {
j.gleader = new_gl; }
if lpj != thisjob {
let lpj_was_unset = guard
.get(lpj as usize)
.map(|j| j.gleader == 0)
.unwrap_or(true);
if lpj_was_unset {
if let Some(j) = guard.get_mut(lpj as usize) {
j.gleader = new_gl; }
}
}
unsafe { libc::setpgid(0, new_gl) }; if (flags & esub::ASYNC) == 0 {
unsafe { libc::tcsetpgrp(2, new_gl) }; if let Some(r) = retp {
r.gleader = new_gl; if lpj != thisjob {
r.list_pipe_job = lpj; }
}
}
}
}
}
} else {
unsafe { libc::setpgid(0, 0) };
}
}
if (flags & esub::FAKE) == 0 {
subsh.store(1, Ordering::Relaxed); }
zsh_subshell.fetch_add(1, Ordering::Relaxed);
if (flags & esub::REVERTPGRP) != 0
&& unsafe { libc::getpid() } == mypgrp.load(Ordering::Relaxed)
{
release_pgrp(); }
*shout.lock().unwrap() = 0; if (flags & esub::NOMONITOR) != 0 {
signal_ignore(libc::SIGTTOU); signal_ignore(libc::SIGTTIN); signal_ignore(libc::SIGTSTP); } else if job_control_ok == 0 {
signal_default(libc::SIGTTOU); signal_default(libc::SIGTTIN); signal_default(libc::SIGTSTP); }
let interact = isset(INTERACTIVE); if interact {
signal_default(libc::SIGTERM); let int_st = sigtrapped
.lock()
.unwrap()
.get(libc::SIGINT as usize)
.copied()
.unwrap_or(0);
if (int_st & ZSIG_IGNORED) == 0 {
signal_default(libc::SIGINT); }
let pipe_st = sigtrapped
.lock()
.unwrap()
.get(libc::SIGPIPE as usize)
.copied()
.unwrap_or(0);
if pipe_st == 0 {
signal_default(libc::SIGPIPE); }
}
let quit_st = sigtrapped
.lock()
.unwrap()
.get(libc::SIGQUIT as usize)
.copied()
.unwrap_or(0);
if (quit_st & ZSIG_IGNORED) == 0 {
signal_default(libc::SIGQUIT); }
if intrap.load(Ordering::Relaxed) != 0 {
for sig in 1..=SIGCOUNT {
let st = sigtrapped
.lock()
.unwrap()
.get(sig as usize)
.copied()
.unwrap_or(0);
if st != 0 && st != ZSIG_IGNORED {
let m = signal_mask(sig);
let _ = signal_unblock(&m); }
}
}
if job_control_ok == 0 {
dosetopt(MONITOR, 0, 0); }
dosetopt(USEZLE, 0, 0); zleactive.store(0, Ordering::Relaxed); let max = MAX_ZSH_FD.load(Ordering::Relaxed);
for i in 10..=max {
if (fdtable_get(i) & FDT_SAVED_MASK) != 0 {
let _ = zclose(i); }
}
let mut dummy_table = crate::exec_jobs::JobTable::new();
crate::ported::jobs::clearjobtab(&mut dummy_table, monitor);
let _ = get_usage(); FORKLEVEL.store(
locallevel.load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
pub fn getpipe(cmd: &str, nullexec: i32) -> i32 {
let bytes = cmd.as_bytes();
let out: i32 = if !bytes.is_empty() && (bytes[0] as char) == Inang {
1 } else {
0 };
let mut ends_at: usize = 0;
let prog = parsecmd(cmd, Some(&mut ends_at)); if prog.is_none() {
return -1; }
if ends_at < bytes.len() && bytes[ends_at] != 0 {
zerr("invalid syntax for process substitution in redirection"); return -1; }
let mut pipes: [i32; 2] = [-1; 2];
if mpipe(&mut pipes) < 0 {
return -1;
}
let mut bgtime: ZshTimespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let pid = zfork(Some(&mut bgtime)); if pid != 0 {
let _ = zclose(pipes[out as usize]); if pid == -1 {
let _ = zclose(pipes[(1 - out) as usize]); return -1; }
if nullexec == 0 {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addproc(
j,
pid,
"",
true, Some(std::time::Instant::now()),
-1,
-1,
);
}
}
}
}
procsubstpid.store(pid, Ordering::Relaxed); return pipes[(1 - out) as usize]; }
entersubsh(esub::ASYNC | esub::PGRP | esub::NOMONITOR, None); let _ = redup(pipes[out as usize], out); closem(FDT_UNUSED, 0); cmdpush(CS_CMDSUBST as u8); let body_end = if ends_at > 0 { ends_at - 1 } else { 2 };
let body = if body_end > 2 && body_end <= bytes.len() {
&cmd[2..body_end]
} else {
""
};
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(body);
cmdpop(); std::process::exit(LASTVAL.load(Ordering::Relaxed));
}
pub fn spawnpipes(l: &mut [redir], nullexec: i32) {
for f in l.iter_mut() {
if f.typ == REDIR_OUTPIPE || f.typ == REDIR_INPIPE {
let str_ = f.name.clone().unwrap_or_default(); let nullexec_eff = if f.varid.as_deref().map_or(false, |v| !v.is_empty()) {
1
} else {
nullexec
};
f.fd2 = getpipe(&str_, nullexec_eff); }
}
}
pub fn cancd2(s: &str) -> i32 {
let us: String;
let chasedots = isset(CHASEDOTS); let chaselinks = isset(CHASELINKS);
if !chasedots && !chaselinks {
let pwd_str = getsparam("PWD").unwrap_or_default(); let mut raw = if !s.starts_with('/') {
format!(
"{}/{}",
if pwd_str.len() > 1 { &pwd_str[..] } else { "" },
s
)
} else {
s.to_string()
};
raw = fixdir(&raw);
us = raw;
} else {
us = unmeta(s); }
let cstr = match std::ffi::CString::new(us.as_str()) {
Ok(c) => c,
Err(_) => return 0,
};
if unsafe { libc::access(cstr.as_ptr(), libc::X_OK) } != 0 {
return 0;
}
let meta = match std::fs::metadata(&us) {
Ok(m) => m,
Err(_) => return 0,
};
if !meta.file_type().is_dir() {
return 0;
}
1
}
pub fn cancd(s: &str) -> Option<String> {
let bytes = s.as_bytes();
let nocdpath = bytes.first().copied() == Some(b'.')
&& (bytes.get(1).copied() == Some(b'/')
|| bytes.get(1).is_none()
|| (bytes.get(1).copied() == Some(b'.')
&& (bytes.get(2).copied() == Some(b'/') || bytes.get(2).is_none())));
if !s.starts_with('/') {
if cancd2(s) != 0 {
return Some(s.to_string());
}
let cstr = std::ffi::CString::new(unmeta(s).as_str()).ok()?;
if unsafe { libc::access(cstr.as_ptr(), libc::X_OK) } == 0 {
return None; }
if !nocdpath {
let cdpath_str = getsparam("CDPATH").unwrap_or_default();
for cp in cdpath_str.split(':') {
let sbuf = if !cp.is_empty() {
format!("{}/{}", cp, s) } else {
s.to_string() };
if cancd2(&sbuf) != 0 {
DOPRINTDIR.store(-1, Ordering::Relaxed); return Some(sbuf); }
}
}
if let Some(t) = cd_able_vars(s) {
if cancd2(&t) != 0 {
DOPRINTDIR.store(-1, Ordering::Relaxed); return Some(t); }
}
return None; }
if cancd2(s) != 0 {
Some(s.to_string())
} else {
None
}
}
pub fn simple_redir_name(prog: &eprog, redir_type: i32) -> Option<String> {
let pc = &prog.prog;
if pc.len() < 7 {
return None;
}
if wc_code(pc[0]) != WC_LIST
|| (WC_LIST_TYPE(pc[0]) & Z_END as u32) == 0 || wc_code(pc[1]) != WC_SUBLIST
|| WC_SUBLIST_FLAGS(pc[1]) != 0 || WC_SUBLIST_TYPE(pc[1]) != WC_SUBLIST_END || wc_code(pc[2]) != WC_PIPE
|| WC_PIPE_TYPE(pc[2]) != WC_PIPE_END || wc_code(pc[3]) != WC_REDIR
|| WC_REDIR_TYPE(pc[3]) != redir_type || WC_REDIR_VARID(pc[3]) != 0 || pc[4] != 0 || wc_code(pc[6]) != WC_SIMPLE
|| WC_SIMPLE_ARGC(pc[6]) != 0
{
return None; }
Some(dupstring(&ecrawstr(prog, 5, None)))
}
pub fn getherestr(fn_: &redir) -> i32 {
let mut t: String = fn_.name.clone().unwrap_or_default(); t = singsub(&t); t = untokenize(&t); let mut bytes: Vec<u8> = t.into_bytes();
let _len = unmetafy(&mut bytes);
if (fn_.flags & REDIRF_FROM_HEREDOC) == 0 {
bytes.push(b'\n'); }
let (fd, s) = match gettempfile(None) {
Some(p) => p,
None => return -1, };
let _ = write_loop(fd, &bytes); let _ = zclose(fd); let cstr = std::ffi::CString::new(s.as_str()).unwrap_or_default();
let new_fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDONLY | libc::O_NOCTTY) }; unsafe {
libc::unlink(cstr.as_ptr());
} new_fd }
pub fn quote_tokenized_output(str_in: &str, file: &mut impl std::io::Write) -> std::io::Result<()> {
let bytes = str_in.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
let c = bytes[i];
match c {
x if x == Meta => {
if i + 1 < bytes.len() {
file.write_all(&[bytes[i + 1] ^ 32])?; i += 2;
} else {
i += 1;
}
continue; }
x if x as char == Nularg => {
i += 1;
continue; }
b'\\' | b'<' | b'>' | b'(' | b'|' | b')' | b'^' | b'#' | b'~' | b'[' | b']' | b'*'
| b'?' | b'$' | b' ' => {
file.write_all(b"\\")?; }
b'\t' => {
file.write_all(b"$'\\t'")?; i += 1;
continue;
}
b'\n' => {
file.write_all(b"$'\\n'")?; i += 1;
continue;
}
b'\r' => {
file.write_all(b"$'\\r'")?; i += 1;
continue;
}
b'=' => {
if i == 0 {
file.write_all(b"\\")?; }
}
_ => {
if itok(c) {
let pound = Pound as u8;
if c >= pound {
let idx = (c - pound) as usize;
let zt = ztokens.as_bytes();
if idx < zt.len() {
file.write_all(&[zt[idx]])?; }
}
i += 1;
continue;
}
}
}
file.write_all(&[c])?; i += 1;
}
Ok(())
}
use crate::ported::math::{matheval as wc_matheval, mathevali as wc_mathevali};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::r#loop::try_tryflag;
use crate::ported::builtin::{BREAKS, CONTFLAG, LOOPS, RETFLAG};
use crate::ported::linklist::LinkList;
use crate::ported::mem::freeheap;
use crate::ported::params::setloopvar;
use crate::ported::params::{assignaparam, assignsparam, unsetparam};
use crate::ported::parse::{ecgetlist, ecgetstr};
use crate::ported::pattern::haswilds;
use crate::ported::signals_h::{queue_signal_level, restore_queue_signals};
use crate::ported::subst::{globlist, prefork};
use crate::ported::zsh_h::{
estate, wordcode, EC_DUP, EC_DUPTOK, EC_NODUP, NOERREXIT_EXIT, NOERREXIT_RETURN, PAT_STATIC,
WC_CASE, WC_CASE_AND, WC_CASE_OR, WC_CASE_SKIP, WC_CASE_TESTAND, WC_CASE_TYPE, WC_CURSH_SKIP,
WC_END, WC_FOR_COND, WC_FOR_LIST, WC_FOR_SKIP, WC_FOR_TYPE, WC_FUNCDEF, WC_FUNCDEF_SKIP, WC_IF,
WC_IF_ELSE, WC_IF_SKIP, WC_IF_TYPE, WC_REPEAT_SKIP, WC_TIMED_EMPTY, WC_TIMED_TYPE, WC_TRY_SKIP,
WC_WHILE_SKIP, WC_WHILE_TYPE, WC_WHILE_UNTIL,
};
use crate::ported::zsh_h::{
ALLEXPORT, ASSPM_AUGMENT, ASSPM_KEY_VALUE, ASSPM_WARN, GLOBASSIGN, KSHARRAYS, PREFORK_ASSIGN,
PREFORK_KEY_VALUE, PREFORK_SINGLE, WC_ASSIGN, WC_ASSIGN_INC, WC_ASSIGN_NUM, WC_ASSIGN_SCALAR,
WC_ASSIGN_TYPE, WC_ASSIGN_TYPE2,
};
use crate::ported::zsh_h::{
CS_ALWAYS, CS_CASE, CS_COND, CS_CURSH, CS_ELIF, CS_ELIFTHEN, CS_ELSE, CS_FOR, CS_IF, CS_IFTHEN,
CS_MATH, CS_REPEAT, CS_UNTIL, CS_WHILE, MN_INTEGER,
};
fn execsubst(list: &mut Vec<String>) {
if list.is_empty() {
return; }
let mut ll: crate::ported::subst::LinkList = std::mem::take(list).into_iter().collect();
let prefork_flags = esprefork.load(Ordering::Relaxed); let mut rf: i32 = 0;
prefork(&mut ll, prefork_flags, &mut rf); if esglob.load(Ordering::Relaxed) != 0 && errflag.load(Ordering::Relaxed) == 0 {
globlist(&mut ll, 0); }
*list = ll.into_iter().collect();
}
fn addvars(state: &mut estate, pc: usize, addflags: i32) {
let mut vl: LinkList<String>; let xtr: bool; let mut isstr: bool; let mut htok: i32 = 0; let mut arr: Vec<String>; let mut name: String;
let mut flags: i32; let opc = state.pc; let mut ac: wordcode;
flags = if (addflags & ADDVAR_RESTORE) == 0 {
ASSPM_WARN } else {
0 };
xtr = isset(XTRACE); if xtr {
printprompt4(); doneps4.store(1, Ordering::Relaxed); }
state.pc = pc;
loop {
if state.pc >= state.prog.prog.len() {
break;
}
ac = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(ac) != WC_ASSIGN {
state.pc -= 1;
break;
}
let mut myflags = flags; name = ecgetstr(state, EC_DUPTOK, Some(&mut htok)); if htok != 0 {
name = untokenize(&name).to_string(); }
if WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC {
myflags |= ASSPM_AUGMENT; }
if xtr {
if WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC {
eprint!("{}+=", name); } else {
eprint!("{}=", name); }
}
isstr = WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR;
if isstr {
let svl_val = ecgetstr(state, EC_DUPTOK, Some(&mut htok));
vl = LinkList::new();
vl.push_back(svl_val);
} else {
let items = ecgetlist(
state,
WC_ASSIGN_NUM(ac) as usize,
EC_DUPTOK,
Some(&mut htok),
);
vl = LinkList::new();
for it in items {
vl.push_back(it);
}
if errflag.load(Ordering::Relaxed) != 0 {
state.pc = opc; return; }
}
if htok != 0 {
let mut prefork_ret: i32 = 0;
let pf_flags = if isstr {
PREFORK_SINGLE | PREFORK_ASSIGN
} else {
PREFORK_ASSIGN
};
prefork(&mut vl, pf_flags, &mut prefork_ret); if errflag.load(Ordering::Relaxed) != 0 {
state.pc = opc; return; }
if (prefork_ret & PREFORK_KEY_VALUE) != 0 {
myflags |= ASSPM_KEY_VALUE; }
let needs_glob = if !isstr {
true
} else {
isset(GLOBASSIGN)
&& isstr
&& !vl.is_empty()
&& haswilds(vl.nodes.front().map(|s| s.as_str()).unwrap_or(""))
};
if needs_glob {
globlist(&mut vl, prefork_ret); if isset(GLOBASSIGN) && isstr {
unsetparam(&name); }
if errflag.load(Ordering::Relaxed) != 0 {
state.pc = opc; return; }
}
}
if isstr && (vl.is_empty() || vl.len() == 1) {
let val: String; if vl.is_empty() {
val = String::new(); } else {
let peek = vl.nodes.front().cloned().unwrap_or_default();
val = untokenize(&peek).to_string(); }
if xtr {
eprint!("{}", quotedzputs(&val)); eprint!(" "); }
let pm = if (addflags & ADDVAR_EXPORT) != 0 && !name.contains('[') {
if name == "STTY" {
let mut stty = STTYval.lock().unwrap();
*stty = Some(val.clone()); }
let allexp = isset(ALLEXPORT);
opt_state_set("allexport", true);
if isset(KSHARRAYS) {
unsetparam(&name); }
let pm = assignsparam(&name, &val, myflags); opt_state_set("allexport", allexp);
pm
} else {
assignsparam(&name, &val, myflags) };
if pm.is_none() {
LASTVAL.store(1, Ordering::Relaxed); if cmdoutval.load(Ordering::Relaxed) == 0 {
cmdoutval.store(1, Ordering::Relaxed); }
}
if errflag.load(Ordering::Relaxed) != 0 {
state.pc = opc; return; }
continue; }
arr = Vec::with_capacity(vl.len() + 1);
while let Some(s) = vl.pop_front() {
arr.push(s);
}
if xtr {
eprint!("( "); for s in &arr {
eprint!("{}", quotedzputs(s)); eprint!(" "); }
eprint!(") "); }
if assignaparam(&name, arr, myflags).is_none() {
LASTVAL.store(1, Ordering::Relaxed); if cmdoutval.load(Ordering::Relaxed) == 0 {
cmdoutval.store(1, Ordering::Relaxed); }
}
if errflag.load(Ordering::Relaxed) != 0 {
state.pc = opc; return; }
}
state.pc = opc; }
pub fn execcursh(state: &mut estate, do_exec: i32) -> i32 {
let prior = state.prog.prog[state.pc.wrapping_sub(1)];
let end = state.pc + WC_CURSH_SKIP(prior) as usize;
state.pc += 1;
{
let lp = list_pipe.load(Ordering::Relaxed);
let lpj = list_pipe_job.load(Ordering::Relaxed);
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if lp == 0 && tj != -1 && tj != lpj {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let has = crate::ported::jobs::hasprocs(&guard, tj as usize);
if !has {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::deletejob(j, false);
}
}
}
}
}
cmdpush(CS_CURSH as u8); let _ = execlist(state, 1, do_exec); cmdpop(); state.pc = end; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed) }
pub fn execcond(state: &mut estate, _do_exec: i32) -> i32 {
state.pc -= 1; if isset(XTRACE) {
printprompt4();
eprint!("[[");
}
cmdpush(CS_COND as u8); let stat: i32 = 0;
if stat == 2 {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
}
cmdpop(); if isset(XTRACE) {
eprintln!(" ]]");
}
stat }
pub fn execarith(state: &mut estate, _do_exec: i32) -> i32 {
if isset(XTRACE) {
printprompt4();
eprint!("((");
}
cmdpush(CS_MATH as u8); let mut htok: i32 = 0;
let mut e = ecgetstr(state, EC_DUPTOK, Some(&mut htok)); if htok != 0 {
e = singsub(&e); }
if isset(XTRACE) {
eprint!(" {}", e);
}
let val_result = wc_matheval(&e); cmdpop(); if isset(XTRACE) {
eprintln!(" ))");
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 || val_result.is_err() {
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
return 2;
}
let val = val_result.unwrap();
if val.type_ == MN_INTEGER {
if val.l == 0 {
1
} else {
0
}
} else if val.d == 0.0 {
1
} else {
0
}
}
pub fn exectime(state: &mut estate, _do_exec: i32) -> i32 {
let jb = *THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap(); let prior = state.prog.prog[state.pc.wrapping_sub(1)];
if WC_TIMED_TYPE(prior) == WC_TIMED_EMPTY {
crate::ported::jobs::shelltime(None, None, None, 0);
return 0; }
let slcode = state.prog.prog[state.pc];
state.pc += 1;
use crate::ported::zsh_h::{Z_SYNC, Z_TIMED};
let _ = execpline(state, slcode, Z_TIMED as i32 | Z_SYNC as i32, 0);
*THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap() = jb; LASTVAL.load(Ordering::Relaxed) }
pub fn execshfunc(shf: &mut shfunc, args: &mut Vec<String>) {
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
return;
}
{
let lp = list_pipe.load(Ordering::Relaxed);
let lpj = list_pipe_job.load(Ordering::Relaxed);
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if lp == 0 && tj != -1 && tj != lpj {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let has = crate::ported::jobs::hasprocs(&guard, tj as usize);
if !has {
let _last_file_list: Vec<String> = if let Some(j) = guard.get_mut(tj as usize) {
std::mem::take(&mut j.filelist)
} else {
Vec::new()
};
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::deletejob(j, false); }
}
}
}
}
if isset(XTRACE) {
printprompt4();
for (i, a) in args.iter().enumerate() {
if i > 0 {
eprint!(" ");
}
eprint!("{}", quotedzputs(a));
}
eprintln!();
}
let prog_owned: Option<eprog> = if shf.funcdef.is_some() {
None
} else if let Some(ref body) = shf.body {
Some(eprog {
strs: Some(body.clone()),
..Default::default()
})
} else {
None
};
let prog_ref: Option<&eprog> = match (shf.funcdef.as_deref(), prog_owned.as_ref()) {
(Some(p), _) => Some(p),
(_, Some(p)) => Some(p),
_ => None,
};
if let Some(_prog) = prog_ref {
let name_for_body = shf.node.nam.clone();
let body_args_owned: Vec<String> = if args.len() > 1 {
args[1..].to_vec()
} else {
Vec::new()
};
let body_runner = move || -> i32 {
crate::ported::exec_hooks::run_function_body(&name_for_body, &body_args_owned)
.unwrap_or(0)
};
let _ = doshfunc(shf, args.clone(), false, body_runner);
}
}
#[allow(non_snake_case)]
pub fn doshfunc(
shfunc: &mut shfunc, doshargs: Vec<String>, noreturnval: bool, mut body_runner: impl FnMut() -> i32, ) -> i32 {
use crate::ported::builtin::{BREAKS, CONTFLAG, LASTVAL, LOOPS, RETFLAG};
use crate::ported::jobs::{NUMPIPESTATS, PIPESTATS};
use crate::ported::modules::parameter::FUNCSTACK;
use crate::ported::params::endparamscope;
use crate::ported::params::locallevel as locallevel_atomic;
use crate::ported::zsh_h::{FS_EVAL, FS_FUNC, FS_SOURCE, FUNCTIONARGZERO, PM_UNDEFINED};
use std::sync::atomic::Ordering;
let name = shfunc.node.nam.clone(); let flags = shfunc.node.flags; let fname = dupstring(&name); let _ = fname;
queue_signals();
let funcsave_breaks = BREAKS.load(Ordering::Relaxed); let funcsave_contflag = CONTFLAG.load(Ordering::Relaxed); let funcsave_loops = LOOPS.load(Ordering::Relaxed); let funcsave_lastval = LASTVAL.load(Ordering::Relaxed); let funcsave_numpipestats = {
NUMPIPESTATS
.get_or_init(|| std::sync::Mutex::new(0))
.lock()
.map(|n| *n)
.unwrap_or(0)
};
let funcsave_noerrexit = noerrexit.load(Ordering::Relaxed); if TRAP_STATE.load(Ordering::Relaxed) == TRAP_STATE_PRIMED {
TRAP_RETURN.fetch_sub(1, Ordering::Relaxed); }
noerrexit.fetch_and(!NOERREXIT_RETURN, Ordering::Relaxed);
let funcsave_pipestats: Option<Vec<i32>> = if noreturnval {
let p = PIPESTATS.get_or_init(|| std::sync::Mutex::new([0; MAX_PIPESTATS]));
p.lock().ok().map(|g| g[..funcsave_numpipestats].to_vec()) } else {
None
};
let _ = name.as_str();
crate::ported::signals::starttrapscope();
crate::ported::pattern::startpatternscope();
let pptab: Vec<String> = crate::ported::builtin::PPARAMS
.lock()
.map(|p| p.clone())
.unwrap_or_default();
let funcsave_scriptname = crate::ported::utils::scriptname_get();
if (flags as u32 & PM_UNDEFINED) == 0 {
crate::ported::utils::set_scriptname(Some(dupstring(&name))); }
let funcsave_optind: Option<String> = crate::ported::params::getsparam("OPTIND");
let funcsave_optarg: Option<String> = crate::ported::params::getsparam("OPTARG");
let funcsave_opts = crate::ported::options::opt_state_snapshot();
opt_state_set("printexitvalue", false);
let funcsave_argv0: Option<String> = if !doshargs.is_empty() {
let positionals: Vec<String> = if doshargs.len() > 1 {
doshargs[1..].to_vec()
} else {
Vec::new()
};
if let Ok(mut pp) = crate::ported::builtin::PPARAMS.lock() {
*pp = positionals;
}
if isset(FUNCTIONARGZERO) {
let prev = crate::ported::utils::argzero();
crate::ported::utils::set_argzero(Some(doshargs[0].clone())); prev
} else {
None
}
} else {
if let Ok(mut pp) = crate::ported::builtin::PPARAMS.lock() {
*pp = Vec::new();
}
if isset(FUNCTIONARGZERO) {
let prev = crate::ported::utils::argzero();
crate::ported::utils::set_argzero(prev.clone()); prev
} else {
None
}
};
inc_locallevel();
let lineno_now = crate::ported::input::lineno.with(|c| c.get()) as i64;
let (caller, prev_tp): (Option<String>, Option<i32>) = {
let stk = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
if let Some(p) = stk.last() {
(Some(p.name.clone()), Some(p.tp))
} else {
let z = funcsave_argv0
.clone()
.or_else(crate::ported::utils::argzero);
(z, None)
}
};
let flineno = shfunc.lineno;
let filename = shfunc.filename.clone().or_else(|| Some(String::new()));
{
let frame = crate::ported::zsh_h::funcstack {
prev: None, name: dupstring(&name), filename, caller, flineno, lineno: lineno_now, tp: FS_FUNC, };
let _ = prev_tp; let mut stack = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
stack.push(frame); }
crate::vm_helper::push_zsh_eval_context("shfunc");
struct EvalContextGuard;
impl Drop for EvalContextGuard {
fn drop(&mut self) {
crate::vm_helper::pop_zsh_eval_context();
}
}
let _eval_ctx_guard = EvalContextGuard;
let saved_lineno = crate::ported::lex::lineno();
crate::ported::lex::set_lineno(0);
let saved_zunderscore = crate::ported::params::getsparam("_").unwrap_or_default();
let body_status = body_runner();
crate::ported::params::set_zunderscore(std::slice::from_ref(&saved_zunderscore));
crate::ported::lex::set_lineno(saved_lineno);
LASTVAL.store(body_status, Ordering::Relaxed);
{
let mut stack = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
stack.pop();
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
RETFLAG.store(0, Ordering::SeqCst); BREAKS.store(funcsave_breaks, Ordering::SeqCst); }
if let Ok(mut pp) = crate::ported::builtin::PPARAMS.lock() {
*pp = pptab; }
if let Some(saved) = funcsave_argv0 {
crate::ported::utils::set_argzero(Some(saved)); }
if let Some(saved) = funcsave_optind {
if let Ok(n) = saved.parse::<i64>() {
crate::ported::params::setiparam("OPTIND", n);
} else {
crate::ported::params::setsparam("OPTIND", &saved);
}
}
if let Some(saved) = funcsave_optarg {
crate::ported::params::setsparam("OPTARG", &saved);
}
crate::ported::utils::set_scriptname(funcsave_scriptname);
crate::ported::pattern::endpatternscope();
if crate::ported::options::opt_state_get("localoptions").unwrap_or(false) {
let current = crate::ported::options::opt_state_snapshot();
for (k, _) in ¤t {
if !funcsave_opts.contains_key(k) {
crate::ported::options::opt_state_unset(k);
}
}
for (k, v) in &funcsave_opts {
opt_state_set(k, *v);
}
} else {
for opt in [
"xtrace",
"printexitvalue",
"localoptions",
"localloops",
"warnnestedvar",
] {
if let Some(v) = funcsave_opts.get(opt) {
opt_state_set(opt, *v);
}
}
}
if crate::ported::options::opt_state_get("localloops").unwrap_or(false) {
BREAKS.store(funcsave_breaks, Ordering::SeqCst); CONTFLAG.store(funcsave_contflag, Ordering::SeqCst); LOOPS.store(funcsave_loops, Ordering::SeqCst); }
{
use crate::ported::params::locallevel as ll;
let prev = ll.load(Ordering::Relaxed);
if prev > 0 {
ll.store(prev - 1, Ordering::Relaxed);
}
crate::ported::signals::endtrapscope();
ll.store(prev, Ordering::Relaxed);
}
if TRAP_STATE.load(Ordering::Relaxed) == TRAP_STATE_PRIMED {
TRAP_RETURN.fetch_add(1, Ordering::Relaxed); }
let ret = LASTVAL.load(Ordering::Relaxed);
noerrexit.store(funcsave_noerrexit, Ordering::Relaxed);
if noreturnval {
LASTVAL.store(funcsave_lastval, Ordering::Relaxed); if let Some(saved_ps) = funcsave_pipestats {
let n = NUMPIPESTATS.get_or_init(|| std::sync::Mutex::new(0));
if let Ok(mut nguard) = n.lock() {
*nguard = funcsave_numpipestats; }
let p = PIPESTATS.get_or_init(|| std::sync::Mutex::new([0; MAX_PIPESTATS]));
if let Ok(mut pguard) = p.lock() {
for (i, v) in saved_ps.iter().enumerate() {
if i < pguard.len() {
pguard[i] = *v; }
}
}
}
}
endparamscope();
unqueue_signals();
let exit_pending = crate::ported::builtin::EXIT_PENDING.load(Ordering::Relaxed);
let exit_level = crate::ported::builtin::EXIT_LEVEL.load(Ordering::Relaxed);
let cur_locallevel = locallevel.load(Ordering::Relaxed) as i32;
let cur_forklevel = FORKLEVEL.load(Ordering::Relaxed);
let in_exit_trap = crate::ported::signals::in_exit_trap.load(Ordering::Relaxed); if exit_pending != 0 && exit_level >= cur_locallevel + 1 && in_exit_trap == 0 {
if cur_locallevel > cur_forklevel {
RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(LOOPS.load(Ordering::Relaxed), Ordering::Relaxed); } else {
crate::ported::builtin::STOPMSG.store(1, Ordering::Relaxed); let val = EXIT_VAL.load(Ordering::Relaxed);
crate::ported::builtin::zexit(val, crate::ported::zsh_h::ZEXIT_NORMAL);
}
}
ret }
const TRAP_STATE_PRIMED: i32 = 2;
#[allow(non_snake_case)]
pub fn execfuncdef(state: &mut estate, mut redir_prog: Option<crate::ported::zsh_h::Eprog>) -> i32 {
use crate::ported::hashtable::{dircache_set, shfunctab_lock};
use crate::ported::jobs::{getsigidx, removetrapnode};
use crate::ported::parse::{dupeprog, freeeprog, incrdumpcount};
use crate::ported::signals::settrap;
use crate::ported::utils::scriptfilename_get;
use crate::ported::zsh_h::{
eprog as eprog_t, hashnode, patprog as patprog_t, shfunc as shfunc_t, Patprog,
EC_DUPTOK as _, EF_HEAP, EF_MAP, EF_REAL, FS_EVAL, FS_FUNC, PM_ANONYMOUS, PM_TAGGED,
PM_TAGGED_LOCAL, PRINTEXITVALUE, SHINSTDIN, ZSIG_FUNC,
};
let mut shf: Box<shfunc_t>;
let mut s: Option<String> = None;
let mut signum: i32;
let nprg: i32;
let sbeg: i32;
let nstrs: i32;
let npats: i32;
let do_tracing: i32;
let len: i32;
let plen: i32;
let mut htok: i32 = 0;
let mut ret: i32 = 0;
let mut anon_func: i32 = 0;
let _beg: usize = state.pc;
let mut end: usize;
let names: Vec<String>;
let tracing_flags: i32;
end = state.pc + WC_FUNCDEF_SKIP(state.prog.prog[state.pc.wrapping_sub(1)]) as usize;
let num = state.prog.prog[state.pc] as usize;
state.pc += 1;
names = ecgetlist(state, num, EC_DUPTOK, Some(&mut htok));
sbeg = state.prog.prog[state.pc] as i32;
state.pc += 1;
nstrs = state.prog.prog[state.pc] as i32;
state.pc += 1;
npats = state.prog.prog[state.pc] as i32;
state.pc += 1;
do_tracing = state.prog.prog[state.pc] as i32;
state.pc += 1;
nprg = end.saturating_sub(state.pc) as i32;
plen = nprg.saturating_mul(size_of::<wordcode>() as i32);
len = plen + npats.saturating_mul(size_of::<usize>() as i32) + nstrs;
tracing_flags = if do_tracing != 0 {
PM_TAGGED_LOCAL as i32
} else {
0
};
let mut names_mut: Vec<String> = names;
if htok != 0 && !names_mut.is_empty() {
execsubst(&mut names_mut); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = end; return 1; }
}
let mut names_iter = names_mut.into_iter();
loop {
let no_names = num == 0;
if !no_names {
match names_iter.next() {
Some(nm) => s = Some(nm),
None => break,
}
}
let prog: Box<eprog_t>;
let dump_present = state.prog.dump.is_some();
let make_pat = || -> Patprog {
Box::new(patprog_t {
startoff: 0,
size: 0,
mustoff: 0,
patmlen: 0,
globflags: 0,
globend: 0,
flags: 0,
patnpar: 0,
patstartch: 0,
})
};
if no_names {
let pats: Vec<Patprog> = (0..npats).map(|_| make_pat()).collect();
let prog_words: Vec<wordcode> = state.prog.prog[state.pc..end].to_vec();
let strs_tail = state.strs.as_ref().map(|t| {
let off = (sbeg as usize).min(t.len());
t[off..].to_string()
});
prog = Box::new(eprog_t {
flags: EF_HEAP,
len,
npats,
nref: -1, pats,
prog: prog_words,
strs: strs_tail,
shf: None, dump: None, });
} else if dump_present {
if let Some(dp) = state.prog.dump.as_deref() {
incrdumpcount(dp); }
let pats: Vec<Patprog> = (0..npats).map(|_| make_pat()).collect();
let prog_words: Vec<wordcode> = state.prog.prog[state.pc..end].to_vec();
let strs_tail = state.strs.as_ref().map(|t| {
let off = (sbeg as usize).min(t.len());
t[off..].to_string()
});
prog = Box::new(eprog_t {
flags: EF_MAP, len,
npats,
nref: 1, pats,
prog: prog_words,
strs: strs_tail,
shf: None, dump: state.prog.dump.clone(), });
} else {
let pats: Vec<Patprog> = (0..npats).map(|_| make_pat()).collect();
let pc_end = state.pc + nprg as usize;
let prog_words: Vec<wordcode> = state.prog.prog[state.pc..pc_end].to_vec();
let strs_copy = state.strs.as_ref().map(|t| {
let off = (sbeg as usize).min(t.len());
let n_avail = t.len().saturating_sub(off);
let take = (nstrs as usize).min(n_avail);
t[off..off + take].to_string()
});
prog = Box::new(eprog_t {
flags: EF_REAL, len,
npats,
nref: 1, pats,
prog: prog_words,
strs: strs_copy,
shf: None, dump: None, });
}
shf = Box::new(shfunc_t {
node: hashnode {
next: None,
nam: String::new(),
flags: tracing_flags,
},
filename: scriptfilename_get(), lineno: {
let cur_lineno = crate::ported::input::lineno.with(|l| l.get()) as i64;
if let Ok(stk) = crate::ported::modules::parameter::FUNCSTACK.lock() {
if let Some(top) = stk.last() {
if top.tp == FS_FUNC || top.tp == FS_EVAL {
top.flineno + cur_lineno
} else {
cur_lineno
}
} else {
cur_lineno
}
} else {
cur_lineno
}
},
funcdef: Some(prog), redir: None,
sticky: None,
body: None,
});
if !no_names && names_iter.len() > 0 && redir_prog.is_some() {
if let Some(rp) = redir_prog.as_deref() {
shf.redir = Some(Box::new(dupeprog(rp, false)));
}
} else {
shf.redir = redir_prog.take();
}
shfunc_set_sticky(&mut shf);
if no_names {
let mut args: Vec<String>;
anon_func = 1; shf.node.flags |= PM_ANONYMOUS as i32;
state.pc = end; end += state.prog.prog[state.pc] as usize;
state.pc += 1;
let arg_count = state.prog.prog[state.pc] as usize;
state.pc += 1;
args = ecgetlist(state, arg_count, EC_DUPTOK, Some(&mut htok));
if htok != 0 && !args.is_empty() {
execsubst(&mut args); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if let Some(mut fd) = shf.funcdef.take() {
freeeprog(&mut fd);
}
if shf.redir.is_some() {
if let Some(mut rd) = shf.redir.take() {
freeeprog(&mut rd);
}
}
dircache_set(&mut shf.filename, None); drop(shf); state.pc = end; return 1; }
}
let under_val = if !args.is_empty() {
args.last().cloned().unwrap_or_default()
} else {
String::new()
};
setunderscore(&under_val);
shf.node.nam = ANONYMOUS_FUNCTION_NAME.to_string(); args.insert(0, shf.node.nam.clone());
execshfunc(&mut shf, &mut args); ret = LASTVAL.load(Ordering::Relaxed);
if isset(PRINTEXITVALUE) && isset(SHINSTDIN) && ret != 0 {
eprintln!("zsh: exit {}", ret); }
if let Some(mut fd) = shf.funcdef.take() {
freeeprog(&mut fd);
}
if let Some(mut rd) = shf.redir.take() {
freeeprog(&mut rd);
}
dircache_set(&mut shf.filename, None); drop(shf); break; } else {
let nm = s.as_deref().unwrap_or("");
if nm.len() > 4 && nm.starts_with("TRAP") {
if let Some(sn) = getsigidx(&nm[4..]) {
signum = sn;
if settrap(signum, None, ZSIG_FUNC) != 0 {
if let Some(mut fd) = shf.funcdef.take() {
freeeprog(&mut fd); }
dircache_set(&mut shf.filename, None); drop(shf); state.pc = end; return 1; }
removetrapnode(signum);
if let Ok(mut t) = crate::ported::builtin::traps_table().lock() {
t.remove(&nm[4..]);
}
}
}
if let Ok(stk) = crate::ported::modules::parameter::FUNCSTACK.lock() {
if let Some(top) = stk.last() {
if top.tp == FS_FUNC && top.name == nm {
if let Ok(rd) = shfunctab_lock().read() {
if let Some(old) = rd.get(nm) {
shf.node.flags |=
old.node.flags & (PM_TAGGED as i32 | PM_TAGGED_LOCAL as i32);
}
}
}
}
}
shf.node.nam = nm.to_string();
if let Ok(mut wr) = shfunctab_lock().write() {
wr.add(*shf);
}
}
}
if anon_func == 0 {
setunderscore("");
}
if let Some(mut rd) = redir_prog.take() {
freeeprog(&mut rd);
}
state.pc = end;
ret
}
pub fn execsimple(state: &mut estate) -> i32 {
let mut code = state.prog.prog[state.pc];
state.pc += 1;
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed);
return 1;
}
if !isset(crate::ported::zsh_h::EXECOPT) {
LASTVAL.store(0, Ordering::Relaxed);
return 0;
}
if !crate::ported::zsh_h::IN_EVAL_TRAP()
&& crate::ported::builtin::INEVAL.load(Ordering::SeqCst) == 0
&& code != 0
{
crate::ported::input::lineno.with(|l| l.set((code as usize).saturating_sub(1)));
}
code = wc_code(state.prog.prog[state.pc]);
state.pc += 1;
let otj = *THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap();
*THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap() = -1;
use crate::ported::zsh_h::{
WC_ARITH, WC_CASE, WC_COND, WC_FOR, WC_REPEAT, WC_SELECT, WC_SUBSH, WC_TIMED, WC_TRY,
WC_WHILE,
};
use crate::ported::zsh_h::{WC_ASSIGN, WC_CURSH};
let lv = if code == WC_ASSIGN {
addvars(state, state.pc.saturating_sub(1), 0);
setunderscore(""); if isset(XTRACE) {
eprintln!();
}
let ef = errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR;
if ef != 0 {
ef
} else {
0
}
} else {
let q = queue_signal_level();
dont_queue_signals();
let result = if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
ERRFLAG_ERROR
} else if code == WC_FUNCDEF {
execfuncdef(state, None)
} else {
match code {
WC_CURSH => execcursh(state, 0),
WC_SUBSH => execcursh(state, 0), WC_FOR => execfor(state, 0),
WC_SELECT => execselect(state, 0),
WC_CASE => execcase(state, 0),
WC_IF => execif(state, 0),
WC_WHILE => execwhile(state, 0),
WC_REPEAT => execrepeat(state, 0),
WC_TIMED => exectime(state, 0),
WC_COND => execcond(state, 0),
WC_ARITH => execarith(state, 0),
WC_TRY => exectry(state, 0),
_ => 0,
}
};
restore_queue_signals(q);
result
};
*THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap() = otj;
LASTVAL.store(lv, Ordering::Relaxed); lv
}
pub fn execlist(state: &mut estate, dont_change_job: i32, mut exiting: i32) -> i32 {
let mut last_status: i32 = 0;
let mut donetrap: i32 = 0; let cj = *THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap(); let _ = dont_change_job; if state.pc >= state.prog.prog.len() {
return last_status;
}
let mut code = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(code) != WC_LIST {
LASTVAL.store(0, Ordering::Relaxed);
return 0;
}
use crate::ported::zsh_h::{WC_LIST_SKIP, WC_LIST_TYPE, Z_END, Z_SIMPLE, Z_SYNC};
while wc_code(code) == WC_LIST
&& BREAKS.load(Ordering::SeqCst) == 0
&& RETFLAG.load(Ordering::SeqCst) == 0
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
{
let ltype = WC_LIST_TYPE(code) as i32;
let csp = crate::ported::prompt::CMDSTACK.with(|s| s.borrow().len());
if (ltype & Z_SIMPLE as i32) != 0 {
let next_pc = state.pc + WC_LIST_SKIP(code) as usize;
let s = execsimple(state);
last_status = s;
state.pc = next_pc;
} else {
if state.pc >= state.prog.prog.len() {
break;
}
code = state.prog.prog[state.pc];
state.pc += 1;
use crate::ported::zsh_h::{
WC_SUBLIST_AND, WC_SUBLIST_END, WC_SUBLIST_NOT, WC_SUBLIST_OR, WC_SUBLIST_SIMPLE,
WC_SUBLIST_SKIP,
};
let mut sub_code = code;
let _ = dont_change_job;
while wc_code(sub_code) == WC_SUBLIST {
let flags = WC_SUBLIST_FLAGS(sub_code);
let next = state.pc + WC_SUBLIST_SKIP(sub_code) as usize;
let sl_type = WC_SUBLIST_TYPE(sub_code) as i32;
let last1 = if WC_SUBLIST_TYPE(sub_code) == WC_SUBLIST_END {
exiting
} else {
0
};
if flags == WC_SUBLIST_SIMPLE {
last_status = execsimple(state); } else {
let _ = execpline(state, sub_code, sl_type, last1); last_status = LASTVAL.load(Ordering::Relaxed);
}
if (flags & WC_SUBLIST_NOT) != 0 {
last_status = if last_status == 0 { 1 } else { 0 };
LASTVAL.store(last_status, Ordering::Relaxed);
}
state.pc = next;
if WC_SUBLIST_TYPE(sub_code) == WC_SUBLIST_END {
break;
}
if state.pc >= state.prog.prog.len() {
break;
}
if sl_type == WC_SUBLIST_AND as i32 && last_status != 0 {
while state.pc < state.prog.prog.len() {
let c = state.prog.prog[state.pc];
if wc_code(c) != WC_SUBLIST {
break;
}
state.pc = state.pc + 1 + WC_SUBLIST_SKIP(c) as usize;
if WC_SUBLIST_TYPE(c) == WC_SUBLIST_END {
break;
}
}
break;
}
if sl_type == WC_SUBLIST_OR as i32 && last_status == 0 {
while state.pc < state.prog.prog.len() {
let c = state.prog.prog[state.pc];
if wc_code(c) != WC_SUBLIST {
break;
}
state.pc = state.pc + 1 + WC_SUBLIST_SKIP(c) as usize;
if WC_SUBLIST_TYPE(c) == WC_SUBLIST_END {
break;
}
}
break;
}
sub_code = state.prog.prog[state.pc];
state.pc += 1;
}
}
crate::ported::prompt::CMDSTACK.with(|s| {
let mut g = s.borrow_mut();
if g.len() > csp {
g.truncate(csp);
}
});
donetrap = 0;
if state.pc >= state.prog.prog.len() {
break;
}
let next_code = state.prog.prog[state.pc];
if wc_code(next_code) != WC_LIST {
break;
}
state.pc += 1;
code = next_code;
if (ltype & Z_END as i32) != 0 {
exiting = 1;
}
}
if dont_change_job != 0 {
*THISJOB
.get_or_init(|| std::sync::Mutex::new(-1))
.lock()
.unwrap() = cj;
}
let _ = donetrap;
this_noerrexit.store(1, Ordering::Relaxed);
LASTVAL.store(last_status, Ordering::Relaxed);
last_status
}
pub fn execcmd_getargs(preargs: &mut LinkList<String>, args: &mut LinkList<String>, expand: i32) {
if args.firstnode().is_none() {
return;
} else if expand != 0 {
let mut svl: LinkList<String> = Default::default();
if let Some(idx) = args.firstnode() {
if let Some(head) = crate::ported::linklist::uremnode(args, idx) {
svl.push_back(head);
}
}
let mut rf = 0i32;
prefork(&mut svl, 0, &mut rf);
crate::ported::linklist::joinlists(preargs, &mut svl);
} else {
if let Some(idx) = args.firstnode() {
if let Some(head) = crate::ported::linklist::uremnode(args, idx) {
preargs.push_back(head);
}
}
}
}
pub fn execcmd_fork(
state: &mut estate,
how: i32,
typ: i32,
varspc: Option<usize>,
filelistp: &mut Vec<String>,
text: &str,
oautocont: i32,
close_if_forked: i32,
) -> i32 {
use crate::ported::signals::sigtrapped as sigtrapped_static;
use crate::ported::signals_h::SIGEXIT;
use crate::ported::zsh_h::{
AUTOCONTINUE, BGNICE, WC_ASSIGN as ZWC_ASSIGN, WC_ASSIGN_NUM as ZWC_ASSIGN_NUM,
WC_ASSIGN_SCALAR as ZWC_ASSIGN_SCALAR, WC_ASSIGN_TYPE as ZWC_ASSIGN_TYPE,
WC_SUBSH as ZWC_SUBSH, ZSIG_IGNORED, Z_ASYNC,
};
let pid: libc::pid_t; let mut synch: [i32; 2] = [-1, -1]; let flags: i32; let mut esret: entersubsh_ret = entersubsh_ret::default(); let mut bgtime = ZshTimespec::default();
child_block(); esret.gleader = -1; esret.list_pipe_job = -1;
if unsafe { libc::pipe(synch.as_mut_ptr()) } < 0 {
zerr(&format!("pipe failed: {}", std::io::Error::last_os_error()));
return -1; }
pid = zfork(Some(&mut bgtime));
if pid == -1 {
unsafe {
libc::close(synch[0]); libc::close(synch[1]); }
LASTVAL.store(1, Ordering::Relaxed); errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); return -1; }
if pid != 0 {
unsafe { libc::close(synch[1]) }; let mut buf = [0u8; size_of::<entersubsh_ret>()];
let _ = crate::ported::utils::read_loop(synch[0], &mut buf);
if buf.len() >= 8 {
esret.gleader = i32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]);
esret.list_pipe_job = i32::from_ne_bytes([buf[4], buf[5], buf[6], buf[7]]);
}
unsafe { libc::close(synch[0]) }; if (how & Z_ASYNC as i32) != 0 {
crate::ported::modules::clone::lastpid.store(pid, Ordering::Relaxed);
} else {
let thisjob_idx = {
if let Some(m) = THISJOB.get() {
*m.lock().unwrap()
} else {
-1
}
};
let stty_already = if thisjob_idx >= 0 {
if let Some(jt) = JOBTAB.get() {
let guard = jt.lock().unwrap();
guard
.get(thisjob_idx as usize)
.map(|j| j.stty_in_env != 0)
.unwrap_or(true)
} else {
true
}
} else {
true
};
if !stty_already && varspc.is_some() {
let mut p = varspc.unwrap();
loop {
if p >= state.prog.prog.len() {
break;
}
let ac = state.prog.prog[p];
if wc_code(ac) != ZWC_ASSIGN {
break;
}
let name = ecrawstr(&state.prog, p + 1, None);
if name == "STTY" {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
if let Some(j) = guard.get_mut(thisjob_idx as usize) {
j.stty_in_env = 1;
}
}
break; }
p += if ZWC_ASSIGN_TYPE(ac) == ZWC_ASSIGN_SCALAR {
3 } else {
(ZWC_ASSIGN_NUM(ac) + 2) as usize };
}
}
}
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = {
if let Some(m) = THISJOB.get() {
*m.lock().unwrap()
} else {
-1
}
};
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addproc(
j,
pid,
text,
false,
Some(std::time::Instant::now()),
esret.gleader,
esret.list_pipe_job,
);
}
}
}
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
let _ = AUTOCONTINUE; }
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = {
if let Some(m) = THISJOB.get() {
*m.lock().unwrap()
} else {
-1
}
};
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::pipecleanfilelist(j, true);
}
}
}
return pid; }
unsafe { libc::close(synch[0]) }; flags = (if (how & Z_ASYNC as i32) != 0 {
esub::ASYNC
} else {
0
}) | esub::PGRP; let mut flags = flags;
if typ != ZWC_SUBSH as i32 && (how & Z_ASYNC as i32) == 0 {
flags |= esub::KEEPTRAP; }
if typ == ZWC_SUBSH as i32 && (how & Z_ASYNC as i32) == 0 {
flags |= esub::JOB_CONTROL; }
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = {
if let Some(m) = THISJOB.get() {
*m.lock().unwrap()
} else {
-1
}
};
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
*filelistp = std::mem::take(&mut j.filelist);
}
}
}
entersubsh(flags, Some(&mut esret)); let mut buf = [0u8; 8];
buf[0..4].copy_from_slice(&esret.gleader.to_ne_bytes());
buf[4..8].copy_from_slice(&esret.list_pipe_job.to_ne_bytes());
if write_loop(synch[1], &buf).map(|n| n as usize).unwrap_or(0) != buf.len() {
zerr(&format!(
"Failed to send entersubsh_ret report: {}",
std::io::Error::last_os_error()
));
return -1; }
unsafe { libc::close(synch[1]) }; let _ = zclose(close_if_forked);
let sigint_state = {
let guard = sigtrapped_static.lock().unwrap();
guard.get(libc::SIGINT as usize).copied().unwrap_or(0)
};
if (sigint_state & ZSIG_IGNORED) != 0 {
crate::ported::signals::holdintr(); }
{
let mut guard = sigtrapped_static.lock().unwrap();
if let Some(slot) = guard.get_mut(SIGEXIT as usize) {
*slot = 0;
}
}
if (how & Z_ASYNC as i32) != 0 && isset(BGNICE) {
#[cfg(target_os = "macos")]
unsafe {
*libc::__error() = 0;
if libc::nice(5) == -1 && *libc::__error() != 0 {
zwarn(&format!(
"nice(5) failed: {}",
std::io::Error::last_os_error()
));
}
}
#[cfg(target_os = "linux")]
unsafe {
*libc::__errno_location() = 0;
if libc::nice(5) == -1 && *libc::__errno_location() != 0 {
zwarn(&format!(
"nice(5) failed: {}",
std::io::Error::last_os_error()
));
}
}
}
0 }
pub fn execcmd_analyse(state: &mut estate, eparams: &mut crate::ported::zsh_h::execcmd_params) {
use crate::ported::zsh_h::{
WC_ASSIGN as ZWC_ASSIGN, WC_REDIR as ZWC_REDIR, WC_SIMPLE as ZWC_SIMPLE,
WC_SIMPLE_ARGC as ZWC_SIMPLE_ARGC, WC_TYPESET as ZWC_TYPESET,
WC_TYPESET_ARGC as ZWC_TYPESET_ARGC,
};
let mut code: wordcode; let mut i: i32; let _ = i;
eparams.beg = state.pc;
eparams.redir =
if state.pc < state.prog.prog.len() && wc_code(state.prog.prog[state.pc]) == ZWC_REDIR {
Some(crate::ported::parse::ecgetredirs(state))
} else {
None
};
if state.pc < state.prog.prog.len() && wc_code(state.prog.prog[state.pc]) == ZWC_ASSIGN {
cmdoutval.store(0, Ordering::Relaxed); eparams.varspc = Some(state.pc); loop {
if state.pc >= state.prog.prog.len() {
break;
}
code = state.prog.prog[state.pc];
if wc_code(code) != ZWC_ASSIGN {
break;
}
state.pc += if WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR {
3 } else {
(WC_ASSIGN_NUM(code) + 2) as usize };
}
} else {
eparams.varspc = None; }
if state.pc >= state.prog.prog.len() {
eparams.args = None;
eparams.assignspc = None;
eparams.typ = 0;
eparams.postassigns = 0;
eparams.htok = 0;
return;
}
code = state.prog.prog[state.pc];
state.pc += 1;
eparams.typ = wc_code(code) as i32;
eparams.postassigns = 0;
match eparams.typ as wordcode {
x if x == ZWC_SIMPLE => {
let mut htok = 0;
let argc = ZWC_SIMPLE_ARGC(code) as usize;
eparams.args = Some(ecgetlist(state, argc, EC_DUP, Some(&mut htok)));
eparams.htok = htok;
eparams.assignspc = None;
}
x if x == ZWC_TYPESET => {
let mut htok = 0;
let argc = ZWC_TYPESET_ARGC(code) as usize;
eparams.args = Some(ecgetlist(state, argc, EC_DUP, Some(&mut htok)));
eparams.htok = htok;
if state.pc < state.prog.prog.len() {
eparams.postassigns = state.prog.prog[state.pc] as i32;
state.pc += 1;
}
eparams.assignspc = Some(state.pc);
let mut k = 0i32;
while k < eparams.postassigns {
if state.pc >= state.prog.prog.len() {
break;
}
code = state.prog.prog[state.pc];
state.pc += if WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR {
3 } else {
(WC_ASSIGN_NUM(code) + 2) as usize };
k += 1;
}
}
_ => {
eparams.args = None;
eparams.assignspc = None;
eparams.htok = 0;
}
}
}
pub static zsh_eval_context: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
pub static DONETRAP: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn save_params(
state: &mut estate,
pc: usize,
restore_p: &mut Vec<crate::ported::zsh_h::param>,
remove_p: &mut Vec<String>,
) {
use crate::ported::zsh_h::{
PM_READONLY, PM_SPECIAL, WC_ASSIGN, WC_ASSIGN_NUM as ZWC_ASSIGN_NUM,
WC_ASSIGN_SCALAR as ZWC_ASSIGN_SCALAR, WC_ASSIGN_TYPE as ZWC_ASSIGN_TYPE,
};
let mut p = pc;
loop {
if p >= state.prog.prog.len() {
break;
}
let ac = state.prog.prog[p];
if wc_code(ac) != WC_ASSIGN {
break;
}
let s = ecrawstr(&state.prog, p + 1, None);
let pm_clone: Option<crate::ported::zsh_h::param> = {
let tab = paramtab().read().unwrap();
tab.get(&s).map(|b| (**b).clone())
};
if let Some(pm) = pm_clone {
if pm.env.is_some() {
crate::ported::params::delenv(&s);
}
if (pm.node.flags & PM_SPECIAL as i32) == 0 {
let mut tpm = pm.clone();
tpm.node.nam = s.clone();
restore_p.push(tpm); } else if (pm.node.flags & PM_READONLY as i32) == 0 {
let mut tpm = pm.clone();
tpm.node.nam = pm.node.nam.clone();
restore_p.push(tpm); }
remove_p.push(s.clone());
} else {
remove_p.push(s.clone());
}
p += if ZWC_ASSIGN_TYPE(ac) == ZWC_ASSIGN_SCALAR {
3
} else {
(ZWC_ASSIGN_NUM(ac) + 2) as usize
};
}
}
pub fn restore_params(restorelist: Vec<crate::ported::zsh_h::param>, removelist: Vec<String>) {
use crate::ported::zsh_h::{PM_READONLY, PM_SPECIAL};
for s in &removelist {
let flags = {
let tab = paramtab().read().unwrap();
tab.get(s).map(|p| p.node.flags)
};
if let Some(f) = flags {
if (f & PM_SPECIAL as i32) == 0 {
let mut tab = paramtab().write().unwrap();
if let Some(pm_mut) = tab.get_mut(s) {
pm_mut.node.flags &= !(PM_READONLY as i32);
}
drop(tab);
let mut tab = paramtab().write().unwrap();
if let Some(pm_mut) = tab.get_mut(s) {
let _ = crate::ported::params::unsetparam_pm(pm_mut, 0, 0); }
}
}
}
for pm in restorelist {
if (pm.node.flags & PM_SPECIAL as i32) != 0 {
let mut tab = paramtab().write().unwrap();
tab.insert(pm.node.nam.clone(), Box::new(pm));
} else {
let mut tab = paramtab().write().unwrap();
tab.insert(pm.node.nam.clone(), Box::new(pm));
}
}
}
pub fn execode(p: crate::ported::zsh_h::Eprog, dont_change_job: i32, exiting: i32, context: &str) {
let prog_ref = *p;
let mut s = estate {
prog: Box::new(prog_ref.clone()),
pc: 0,
strs: prog_ref.strs.clone(),
strs_offset: 0,
};
let pushed = {
if let Ok(mut ctx) = zsh_eval_context.lock() {
ctx.push(context.to_string());
true
} else {
false
}
};
crate::ported::parse::useeprog(&mut s.prog);
execlist(&mut s, dont_change_job, exiting);
crate::ported::parse::freeeprog(&mut s.prog);
if pushed {
if let Ok(mut ctx) = zsh_eval_context.lock() {
ctx.pop();
}
}
}
pub fn execautofn_basic(state: &mut estate, _do_exec: i32) -> i32 {
let shf = match state.prog.shf.as_deref() {
Some(s) => s.clone(),
None => return LASTVAL.load(Ordering::Relaxed),
};
{
let mut stk = crate::ported::modules::parameter::FUNCSTACK.lock().unwrap();
if let Some(top) = stk.last_mut() {
if top.filename.is_none() {
top.filename = crate::ported::hashtable::getshfuncfile(&shf.node.nam);
}
}
}
let oldscriptname = crate::ported::utils::scriptname_get();
let oldscriptfilename = crate::ported::utils::scriptfilename_get();
crate::ported::utils::set_scriptname(Some(shf.node.nam.clone()));
crate::ported::utils::set_scriptfilename(crate::ported::hashtable::getshfuncfile(
&shf.node.nam,
));
if let Some(funcdef) = shf.funcdef.clone() {
execode(funcdef, 1, 0, "loadautofunc");
}
crate::ported::utils::set_scriptname(oldscriptname);
crate::ported::utils::set_scriptfilename(oldscriptfilename);
LASTVAL.load(Ordering::Relaxed) }
pub fn execautofn(state: &mut estate, _do_exec: i32) -> i32 {
let shf_ptr: *mut shfunc = match state.prog.shf.as_mut() {
Some(b) => &mut **b as *mut shfunc,
None => return 1,
};
if loadautofn(shf_ptr, 1, 0, 0) != 0 {
return 1;
}
execautofn_basic(state, 0)
}
pub fn execpline2(
state: &mut estate,
pcode: wordcode,
how: i32,
input: i32,
output: i32,
last1: i32,
) {
use crate::ported::builtin::{BREAKS, INEVAL, RETFLAG};
use crate::ported::zsh_h::{
execcmd_params, CS_PIPE, WC_PIPE_END, WC_PIPE_LINENO as ZWC_PIPE_LINENO,
WC_PIPE_TYPE as ZWC_PIPE_TYPE, Z_ASYNC,
};
let mut eparams: execcmd_params = execcmd_params::default();
if BREAKS.load(Ordering::SeqCst) != 0 || RETFLAG.load(Ordering::SeqCst) != 0 {
return;
}
if !crate::ported::zsh_h::IN_EVAL_TRAP()
&& INEVAL.load(Ordering::SeqCst) == 0
&& ZWC_PIPE_LINENO(pcode) != 0
{
let new_lineno = ZWC_PIPE_LINENO(pcode).saturating_sub(1) as usize;
crate::ported::input::lineno.with(|l| l.set(new_lineno));
}
if pline_level.load(Ordering::Relaxed) == 1 {
if (how & Z_ASYNC as i32) != 0 || sfcontext.load(Ordering::Relaxed) == 0 {
let pc_for_text = state.pc
+ if ZWC_PIPE_TYPE(pcode) == WC_PIPE_END {
0
} else {
1
};
let text = crate::ported::text::getjobtext(state.prog.clone(), Some(pc_for_text));
if let Ok(mut lpt) = LIST_PIPE_TEXT.lock() {
*lpt = text;
}
} else {
if let Ok(mut lpt) = LIST_PIPE_TEXT.lock() {
lpt.clear();
}
}
}
if ZWC_PIPE_TYPE(pcode) == WC_PIPE_END {
execcmd_analyse(state, &mut eparams); execcmd_exec(
state,
&mut eparams,
input,
output,
how,
if last1 != 0 { 1 } else { 2 }, -1, );
} else {
let mut pipes: [i32; 2] = [-1, -1]; let old_list_pipe = list_pipe.load(Ordering::Relaxed); let next = if state.pc < state.prog.prog.len() {
state.pc + state.prog.prog[state.pc] as usize
} else {
state.pc
};
if state.pc < state.prog.prog.len() {
state.pc += 1;
}
execcmd_analyse(state, &mut eparams);
if mpipe(&mut pipes) < 0 {
}
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = {
if let Some(m) = THISJOB.get() {
*m.lock().unwrap()
} else {
-1
}
};
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::addfilelist(j, None, pipes[0]);
}
}
}
execcmd_exec(state, &mut eparams, input, pipes[1], how, 0, pipes[0]);
let _ = zclose(pipes[1]); state.pc = next;
cmdpush(CS_PIPE as u8);
list_pipe.store(1, Ordering::Relaxed);
let next_pcode = if state.pc < state.prog.prog.len() {
state.prog.prog[state.pc]
} else {
0
};
if state.pc < state.prog.prog.len() {
state.pc += 1;
}
execpline2(state, next_pcode, how, pipes[0], output, last1);
list_pipe.store(old_list_pipe, Ordering::Relaxed);
cmdpop();
}
}
pub fn execpline(state: &mut estate, slcode: wordcode, how: i32, last1: i32) -> i32 {
use crate::ported::zsh_h::{WC_SUBLIST_FLAGS, WC_SUBLIST_NOT, Z_TIMED};
let slflags = WC_SUBLIST_FLAGS(slcode); if state.pc >= state.prog.prog.len() || wc_code(state.prog.prog[state.pc]) != WC_PIPE {
if (how & Z_TIMED as i32) == 0 {
let ret = if (slflags & WC_SUBLIST_NOT) != 0 {
1
} else {
0
};
LASTVAL.store(ret, Ordering::Relaxed);
return ret;
}
}
let mut last1 = last1;
if (slflags & WC_SUBLIST_NOT) != 0 {
last1 = 0; }
let mut code = state.prog.prog[state.pc];
state.pc += 1;
let mut last_status: i32 = 0;
use crate::ported::zsh_h::{WC_PIPE_END, WC_PIPE_TYPE};
let _ = how;
let _ = last1;
loop {
use crate::ported::zsh_h::{
WC_ARITH, WC_CASE, WC_COND, WC_CURSH, WC_FOR, WC_FUNCDEF, WC_IF, WC_REPEAT, WC_SELECT,
WC_SIMPLE, WC_SUBSH, WC_TIMED, WC_TRY, WC_WHILE,
};
let s = if state.pc < state.prog.prog.len() {
let inner = state.prog.prog[state.pc];
match wc_code(inner) {
WC_SIMPLE => execsimple(state),
WC_SUBSH | WC_CURSH => execcursh(state, 0),
WC_FOR => execfor(state, 0),
WC_SELECT => execselect(state, 0),
WC_CASE => execcase(state, 0),
WC_IF => execif(state, 0),
WC_WHILE => execwhile(state, 0),
WC_REPEAT => execrepeat(state, 0),
WC_FUNCDEF => execfuncdef(state, None),
WC_TIMED => exectime(state, 0),
WC_COND => execcond(state, 0),
WC_ARITH => execarith(state, 0),
WC_TRY => exectry(state, 0),
_ => {
state.pc += 1;
0
}
}
} else {
0
};
last_status = s;
if WC_PIPE_TYPE(code) == WC_PIPE_END {
break;
}
if state.pc >= state.prog.prog.len() {
break;
}
let next_code = state.prog.prog[state.pc];
if wc_code(next_code) != WC_PIPE {
break;
}
state.pc += 1;
code = next_code;
}
LASTVAL.store(last_status, Ordering::Relaxed);
last_status
}
#[cfg(any())]
mod _execcmd_tail_doc_anchor {
}
pub fn execfor(state: &mut estate, do_exec: i32) -> i32 {
use crate::ported::zsh_h::Z_END;
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let iscond = WC_FOR_TYPE(code) == WC_FOR_COND; let mut last_iter = false; let mut val: i64 = 0; let mut vars: Vec<String> = Vec::new();
let mut args: Vec<String> = Vec::new();
let mut cond_expr: String = String::new();
let mut advance_expr: String = String::new();
let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_FOR_SKIP(code) as usize; let mut ctok = 0i32;
let mut atok = 0i32;
if iscond {
let init = ecgetstr(state, EC_NODUP, None); let init_sub = singsub(&init); if isset(XTRACE) {
let init_show = untokenize(&init_sub);
printprompt4();
eprintln!("{}", init_show);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let _ = wc_matheval(&init_sub); }
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
cond_expr = ecgetstr(state, EC_NODUP, Some(&mut ctok)); advance_expr = ecgetstr(state, EC_NODUP, Some(&mut atok)); } else {
let count = state.prog.prog[state.pc] as usize;
state.pc += 1;
vars = ecgetlist(state, count, EC_NODUP, None);
if WC_FOR_TYPE(code) == WC_FOR_LIST {
let mut htok = 0i32;
let arg_count = state.prog.prog[state.pc] as usize;
state.pc += 1;
args = ecgetlist(state, arg_count, EC_DUPTOK, Some(&mut htok));
if args.is_empty() {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 0;
}
if htok != 0 {
execsubst(&mut args); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
}
} else {
args = crate::ported::builtin::PPARAMS
.lock()
.map(|p| p.clone())
.unwrap_or_default();
}
}
if !iscond && args.is_empty() {
LASTVAL.store(0, Ordering::Relaxed);
}
LOOPS.fetch_add(1, Ordering::SeqCst); pushheap(); cmdpush(CS_FOR as u8); let loop_pc = state.pc; let mut args_iter = args.into_iter();
while !last_iter {
if iscond {
let mut cs = cond_expr.clone();
if ctok != 0 {
cs = singsub(&cs);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let trimmed = cs.trim_start();
if !trimmed.is_empty() {
if isset(XTRACE) {
printprompt4();
eprintln!("{}", trimmed);
}
val = wc_mathevali(trimmed).unwrap_or(0);
} else {
val = 1;
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if val == 0 {
break;
}
} else {
let mut count = 0;
for name in &vars {
let value = match args_iter.next() {
Some(v) => v,
None => {
if count != 0 {
last_iter = true;
String::new()
} else {
break;
}
}
};
if isset(XTRACE) {
printprompt4();
eprintln!("{}={}", name, value);
}
setloopvar(name, &value);
count += 1;
}
if count == 0 {
break;
}
}
state.pc = loop_pc; let _do_exec_now = do_exec != 0 && !args_iter.clone().any(|_| true); let _ = execlist(state, 1, if _do_exec_now { 1 } else { 0 });
if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
if iscond && (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let mut adv = advance_expr.clone();
if atok != 0 {
adv = singsub(&adv);
}
if isset(XTRACE) {
printprompt4();
eprintln!("{}", adv);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let _ = wc_matheval(&adv);
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
LASTVAL.store(1, Ordering::Relaxed);
break;
}
freeheap(); }
popheap(); cmdpop(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
state.pc = end_pc;
this_noerrexit.store(1, Ordering::Relaxed);
let _ = Z_END;
LASTVAL.load(Ordering::Relaxed)
}
pub fn execselect(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)];
let end_pc = state.pc + WC_FOR_SKIP(code) as usize;
state.pc = end_pc;
this_noerrexit.store(1, Ordering::Relaxed);
LASTVAL.load(Ordering::Relaxed)
}
pub fn execwhile(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let isuntil = WC_WHILE_TYPE(code) == WC_WHILE_UNTIL; let end_pc = state.pc + WC_WHILE_SKIP(code) as usize; let olderrexit = noerrexit.load(Ordering::Relaxed); let mut oldval: i32 = 0; pushheap(); cmdpush(if isuntil {
CS_UNTIL as u8
} else {
CS_WHILE as u8
}); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let old_simple_pline = simple_pline.load(Ordering::Relaxed); if state.prog.prog.get(loop_pc) == Some(&WC_END)
&& state.prog.prog.get(loop_pc + 1) == Some(&WC_END)
{
simple_pline.store(1, Ordering::Relaxed);
while BREAKS.load(Ordering::SeqCst) == 0 {
std::thread::yield_now();
}
BREAKS.fetch_sub(1, Ordering::SeqCst);
simple_pline.store(old_simple_pline, Ordering::Relaxed);
} else {
loop {
state.pc = loop_pc; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
noerrexit.store(olderrexit, Ordering::Relaxed); let cond_status = LASTVAL.load(Ordering::Relaxed); let cond_passed = (cond_status == 0) ^ isuntil;
if !cond_passed {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
if RETFLAG.load(Ordering::SeqCst) == 0 {
LASTVAL.store(oldval, Ordering::Relaxed);
}
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
break;
}
simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
freeheap(); oldval = LASTVAL.load(Ordering::Relaxed); }
}
cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execrepeat(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_REPEAT_SKIP(code) as usize; let mut htok = 0i32;
let mut tmp = ecgetstr(state, EC_DUPTOK, Some(&mut htok)); if htok != 0 {
tmp = singsub(&tmp); tmp = untokenize(&tmp); }
let count = wc_mathevali(&tmp).unwrap_or(0); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
LASTVAL.store(0, Ordering::Relaxed); pushheap(); cmdpush(CS_REPEAT as u8); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let mut remaining = count;
while remaining > 0 {
remaining -= 1;
state.pc = loop_pc;
let _ = execlist(state, 1, 0); freeheap(); if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
}
cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execif(state: &mut estate, do_exec: i32) -> i32 {
let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let olderrexit = noerrexit.load(Ordering::Relaxed); let end_pc = state.pc + WC_IF_SKIP(code0) as usize; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); let mut s = 0i32; let mut run = 0i32; while state.pc < end_pc {
let code = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(code) != WC_IF || WC_IF_TYPE(code) == WC_IF_ELSE {
run = if wc_code(code) == WC_IF && WC_IF_TYPE(code) == WC_IF_ELSE {
2
} else {
1
};
if run == 1 {
state.pc -= 1; }
break;
}
let next_pc = state.pc + WC_IF_SKIP(code) as usize; cmdpush(if s != 0 { CS_ELIF as u8 } else { CS_IF as u8 }); let _ = execlist(state, 1, 0); cmdpop(); if LASTVAL.load(Ordering::Relaxed) == 0 {
run = 1;
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
s = 1;
state.pc = next_pc;
}
noerrexit.store(olderrexit, Ordering::Relaxed); if run != 0 {
cmdpush(if run == 2 {
CS_ELSE as u8
} else if s != 0 {
CS_ELIFTHEN as u8
} else {
CS_IFTHEN as u8
});
let _ = execlist(state, 1, do_exec);
cmdpop();
} else if RETFLAG.load(Ordering::SeqCst) == 0
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
{
LASTVAL.store(0, Ordering::Relaxed); }
state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execcase(state: &mut estate, do_exec: i32) -> i32 {
let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_CASE_SKIP(code0) as usize; let raw_word = ecgetstr(state, EC_DUP, None);
let word_sub = singsub(&raw_word);
let word = untokenize(&word_sub);
let mut anypatok = false; cmdpush(CS_CASE as u8); let mut code = 0u32;
while state.pc < end_pc {
code = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(code) != WC_CASE {
break;
}
let next_pc = state.pc + WC_CASE_SKIP(code) as usize; let nalts = state.prog.prog[state.pc] as i32; state.pc += 1;
let mut patok = false;
let mut nalts_remaining = nalts;
while !patok && nalts_remaining > 0 {
queue_signals(); let mut htok = 0i32;
let pat_raw = ecrawstr(&state.prog, state.pc, Some(&mut htok));
let pat = if htok != 0 {
singsub(&pat_raw)
} else {
pat_raw
};
if let Some(pprog) = patcompile(&pat, PAT_STATIC, None) {
if pattry(&pprog, &word) {
patok = true;
anypatok = true;
}
} else {
zerr(&format!("bad pattern: {}", pat)); }
state.pc += 2; nalts_remaining -= 1;
unqueue_signals(); }
state.pc += (2 * nalts_remaining) as usize; if patok {
let _ = execlist(
state,
1,
((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
);
while RETFLAG.load(Ordering::SeqCst) == 0
&& wc_code(code) == WC_CASE
&& WC_CASE_TYPE(code) == WC_CASE_AND
&& state.pc < end_pc
{
state.pc = next_pc;
code = state.prog.prog[state.pc];
state.pc += 1;
let inner_next = state.pc + WC_CASE_SKIP(code) as usize;
let inner_nalts = state.prog.prog[state.pc] as usize;
state.pc += 1 + 2 * inner_nalts;
let _ = execlist(
state,
1,
((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
);
let _ = inner_next;
}
if WC_CASE_TYPE(code) != WC_CASE_TESTAND {
break;
}
}
state.pc = next_pc; }
cmdpop(); state.pc = end_pc; if !anypatok {
LASTVAL.store(0, Ordering::Relaxed);
}
this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn exectry(state: &mut estate, _do_exec: i32) -> i32 {
let header = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_TRY_SKIP(header) as usize; let try_inner = state.prog.prog[state.pc]; let always_pc = state.pc + 1 + WC_TRY_SKIP(try_inner) as usize; state.pc += 1; pushheap(); cmdpush(CS_CURSH as u8); try_tryflag.fetch_add(1, Ordering::SeqCst); let _ = execlist(state, 1, 0); try_tryflag.fetch_sub(1, Ordering::SeqCst); let try_status = LASTVAL.load(Ordering::Relaxed);
let endval = if try_status != 0 {
try_status
} else {
(errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) as i32
};
freeheap(); cmdpop(); cmdpush(CS_ALWAYS as u8); let saved_err = errflag.load(Ordering::Relaxed);
let save_try_err = (saved_err & ERRFLAG_ERROR) != 0;
let save_try_int = (saved_err & ERRFLAG_INT) != 0;
errflag.fetch_and(!(ERRFLAG_ERROR | ERRFLAG_INT), Ordering::Relaxed);
let save_retflag = RETFLAG.swap(0, Ordering::SeqCst);
let save_breaks = BREAKS.swap(0, Ordering::SeqCst);
let save_contflag = CONTFLAG.swap(0, Ordering::SeqCst);
state.pc = always_pc; let _ = execlist(state, 1, 0); if save_try_err {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
} else {
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
}
if save_try_int {
errflag.fetch_or(ERRFLAG_INT, Ordering::Relaxed);
} else {
errflag.fetch_and(!ERRFLAG_INT, Ordering::Relaxed);
}
if RETFLAG.load(Ordering::SeqCst) == 0 {
RETFLAG.store(save_retflag, Ordering::SeqCst);
}
if BREAKS.load(Ordering::SeqCst) == 0 {
BREAKS.store(save_breaks, Ordering::SeqCst);
}
if CONTFLAG.load(Ordering::SeqCst) == 0 {
CONTFLAG.store(save_contflag, Ordering::SeqCst);
}
cmdpop(); popheap(); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); endval
}
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::redundant_field_names)]
#[allow(unused_assignments)]
#[allow(unused_variables)]
#[allow(unused_mut)]
#[allow(unused_imports)]
#[allow(unreachable_code)]
#[allow(dead_code)]
pub fn execcmd_exec(
state: &mut estate,
eparams: &mut crate::ported::zsh_h::execcmd_params,
input: i32,
output: i32,
mut how: i32,
mut last1: i32,
close_if_forked: i32,
) {
use crate::ported::zsh_h::{
Star, ASG_ARRAY, ASG_KEY_VALUE, AUTOCD, AUTOCONTINUE, AUTORESUME, BGNICE,
BINF_ASSIGN as BINF_ASSIGN_FLAG, BINF_BUILTIN, BINF_COMMAND, BINF_EXEC, BINF_MAGICEQUALS,
BINF_NOGLOB, BINF_PREFIX, BINF_PSPECIAL, CSHNULLCMD, ERRFLAG_INT, EXECOPT, FDT_EXTERNAL,
FDT_INTERNAL, FDT_TYPE_MASK, FDT_UNUSED, FDT_XTRACE, HASHCMDS, HFILE_USE_OPTIONS,
IS_APPEND_REDIR, IS_DASH, IS_ERROR_REDIR, MAGICEQUALSUBST, NOTIFY, PM_READONLY, PM_SPECIAL,
POSIXBUILTINS, PREFORK_ASSIGN, PREFORK_KEY_VALUE, PREFORK_SINGLE, PREFORK_TYPESET,
PRINTEXITVALUE, RCS, REDIR_CLOSE, REDIR_HERESTR, REDIR_INPIPE, REDIR_MERGEIN,
REDIR_MERGEOUT, REDIR_OUTPIPE, REDIR_READ, REDIR_READWRITE, RMSTARSILENT, SHINSTDIN,
SHNULLCMD, STAT_BUILTIN, STAT_CURSH, STAT_DONE, STAT_NOPRINT, WC_ASSIGN as ZWC_ASSIGN,
WC_ASSIGN_INC as ZWC_ASSIGN_INC, WC_ASSIGN_NUM as ZWC_ASSIGN_NUM,
WC_ASSIGN_SCALAR as ZWC_ASSIGN_SCALAR, WC_ASSIGN_TYPE as ZWC_ASSIGN_TYPE,
WC_ASSIGN_TYPE2 as ZWC_ASSIGN_TYPE2, WC_AUTOFN, WC_CURSH, WC_FUNCDEF, WC_REDIR, WC_SIMPLE,
WC_SUBSH, WC_TIMED, WC_TYPESET, XTRACE, Z_ASYNC, Z_DISOWN, Z_SYNC, Z_TIMED,
};
let mut hn: Option<*mut builtin> = None; let mut filelist: Vec<String> = Vec::new(); let mut mfds: [Option<Box<multio>>; 10] = [None, None, None, None, None, None, None, None, None, None];
let mut text: Option<String> = None; let mut save: [i32; 10] = [-2; 10]; let mut fil: i32; let mut dfil: i32 = 0; let mut is_cursh: i32 = 0; let mut do_exec: i32 = 0; let mut redir_err: i32 = 0; let mut i: i32; let mut nullexec: i32 = 0; let mut magic_assign: i32 = 0; let mut forked: i32 = 0; let mut old_lastval: i32; let mut is_shfunc: i32 = 0; let mut is_builtin: i32 = 0; let mut is_exec: i32 = 0; let mut use_defpath: i32 = 0; let mut cflags: u32 = 0; let mut orig_cflags: u32 = 0; let mut checked: i32 = 0; let mut oautocont: i32 = -1; let mut newxtrerr: Option<i32> = None;
let mut args: Option<Vec<String>> = eparams.args.take(); let mut redir: Option<Vec<redir>> = eparams.redir.take(); let varspc: Option<usize> = eparams.varspc; let typ: i32 = eparams.typ;
let mut shti = crate::ported::jobs::timeinfo::default(); let mut chti = crate::ported::jobs::timeinfo::default(); let mut then_ts = std::time::Instant::now(); if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(Some(&mut shti), Some(&mut chti), Some(&mut then_ts), 0);
}
doneps4.store(0, Ordering::Relaxed);
old_lastval = LASTVAL.load(Ordering::Relaxed); if args.is_none() && varspc.is_some() {
let ef = errflag.load(Ordering::Relaxed);
LASTVAL.store(
if ef != 0 {
ef
} else {
cmdoutval.load(Ordering::Relaxed)
},
Ordering::Relaxed,
); }
use_cmdoutval.store(if args.is_none() { 1 } else { 0 }, Ordering::Relaxed);
if (typ == WC_SIMPLE as i32 || typ == WC_TYPESET as i32)
&& args.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
&& args.as_ref().unwrap()[0].starts_with('%')
{
if (how & Z_DISOWN as i32) != 0 {
oautocont = if crate::ported::options::opt_state_get("autocontinue").unwrap_or(false) {
1
} else {
0
}; opt_state_set("autocontinue", true); }
let head = if (how & Z_DISOWN as i32) != 0 {
"disown".to_string()
} else if (how & Z_ASYNC as i32) != 0 {
"bg".to_string()
} else {
"fg".to_string()
};
if let Some(ref mut v) = args {
v.insert(0, head);
}
how = Z_SYNC as i32; }
if isset(AUTORESUME)
&& typ == WC_SIMPLE as i32
&& (how & Z_SYNC as i32) != 0
&& args.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
&& redir.as_ref().map(|v| v.is_empty()).unwrap_or(true)
&& input == 0
&& args.as_ref().unwrap().len() == 1
{
if unset(NOTIFY) {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let long_list = isset(crate::ported::zsh_h::LONGLISTJOBS);
for i in 1..guard.len() {
if (guard[i].stat & crate::ported::zsh_h::STAT_CHANGED) != 0 {
let s = crate::ported::jobs::printjob(&guard[i], i, long_list, None, None); if !s.is_empty() {
eprint!("{}", s);
}
}
}
}
}
let head = args.as_ref().unwrap()[0].clone();
let maxjob = JOBTAB
.get()
.map(|m| m.lock().unwrap().len() as i32)
.unwrap_or(0);
let thisjob = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
let found = if let Some(jt) = JOBTAB.get() {
let guard = jt.lock().unwrap();
crate::ported::jobs::findjobnam(&head, &guard, maxjob - 1, thisjob).is_some()
} else {
false
};
if found {
if let Some(ref mut v) = args {
v.insert(0, "fg".to_string());
}
}
}
use crate::ported::exec::{restore_params, save_params};
use crate::ported::exec::isreallycom;
use crate::ported::exec::execautofn_basic;
if (how & Z_ASYNC as i32) != 0
|| output != 0
|| (last1 == 2 && input != 0 && {
(crate::ported::options::emulation.load(Ordering::Relaxed)
& crate::ported::zsh_h::EMULATE_SH)
!= 0
})
{
text = Some(crate::ported::text::getjobtext(
state.prog.clone(),
Some(eparams.beg),
));
let mut filelist_for_fork = filelist.clone();
let pid = execcmd_fork(
state,
how,
typ,
varspc,
&mut filelist_for_fork,
text.as_deref().unwrap_or(""),
oautocont,
close_if_forked,
);
match pid {
-1 => {
redir_err = 1; return execcmd_exec_done_path(
redir_err,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
forked,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
0 => {
}
_ => {
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
);
}
return;
}
}
last1 = 1; forked = 1; } else {
text = None;
}
let mut preargs: Vec<String> = Vec::new();
let mut exec_argv0: Option<String> = None;
if (typ == WC_SIMPLE as i32 || typ == WC_TYPESET as i32) && args.is_some() {
let head_args: Vec<String> = args.as_ref().unwrap().clone();
let dispatch = execcmd_compile_head(&head_args, typ as u32);
cflags = dispatch.cflags;
if dispatch.is_builtin {
is_builtin = 1;
}
if dispatch.is_shfunc {
is_shfunc = 1;
}
if dispatch.use_defpath {
use_defpath = 1;
}
exec_argv0 = dispatch.exec_argv0;
orig_cflags = cflags;
if let Some(ref mut v) = args {
v.drain(0..dispatch.precmd_skip);
}
let _ = head_args;
preargs.clear();
if (cflags & BINF_MAGICEQUALS) != 0 && typ != WC_TYPESET as i32 {
magic_assign = 1;
}
hn = None;
if is_builtin != 0 {
if let Some(target) = args.as_ref().and_then(|v| v.first()) {
if let Some(entry) = BUILTINS.iter().find(|b| b.node.nam == *target) {
hn = Some(entry as *const builtin as *mut builtin);
}
}
}
} else {
}
let esprefork_v: i32 =
if magic_assign != 0 || (isset(MAGICEQUALSUBST) && typ != WC_TYPESET as i32) {
PREFORK_TYPESET } else {
0
};
esprefork.store(esprefork_v, Ordering::Relaxed);
if args.is_some() && eparams.htok != 0 {
let mut as_linklist: LinkList<String> = Default::default();
if let Some(ref v) = args {
for s in v {
as_linklist.push_back(s.clone());
}
}
let mut rf = 0i32;
prefork(&mut as_linklist, esprefork_v, &mut rf);
let mut out: Vec<String> = Vec::new();
while let Some(s) = as_linklist.pop_front() {
out.push(s);
}
args = Some(out);
}
if !preargs.is_empty() {
let mut joined = preargs.clone();
if let Some(ref v) = args {
joined.extend(v.iter().cloned());
}
args = Some(joined);
}
if typ == WC_SIMPLE as i32 || typ == WC_TYPESET as i32 {
let mut unglobbed: i32 = 0;
loop {
if (cflags & BINF_NOGLOB) == 0 {
while checked == 0
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
&& args.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
&& crate::ported::lex::has_token(&args.as_ref().unwrap()[0])
{
let mut head_vec: Vec<String> = Vec::new();
if let Some(ref mut v) = args {
head_vec.push(v.remove(0));
}
crate::ported::glob::zglob(&mut head_vec, 0usize, 0);
if let Some(ref mut v) = args {
for (i, s) in head_vec.into_iter().enumerate() {
v.insert(i, s);
}
}
}
} else if unglobbed == 0 {
if let Some(ref mut v) = args {
for s in v.iter_mut() {
*s = untokenize(s); }
}
unglobbed = 1; }
if (cflags & BINF_EXEC) != 0 && last1 != 0 {
do_exec = 1; }
if args.as_ref().map(|v| v.is_empty()).unwrap_or(true) {
if redir.as_ref().map(|v| !v.is_empty()).unwrap_or(false) {
if do_exec != 0 {
nullexec = 1; break;
} else if varspc.is_some() {
nullexec = 2; break;
} else if {
let nc = getsparam("NULLCMD");
let nc_empty = nc.as_deref().map(|s| s.is_empty()).unwrap_or(true);
nc_empty || isset(CSHNULLCMD) || (cflags & BINF_PREFIX) != 0
} {
zerr("redirection with no command");
LASTVAL.store(1, Ordering::Relaxed); errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); if forked != 0 {
crate::ported::builtin::_realexit();
}
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
);
}
return; } else if {
let nc = getsparam("NULLCMD");
let nc_empty = nc.as_deref().map(|s| s.is_empty()).unwrap_or(true);
nc_empty || isset(SHNULLCMD)
} {
if args.is_none() {
args = Some(Vec::new());
}
args.as_mut().unwrap().push(":".to_string()); } else if {
let rnc = getsparam("READNULLCMD");
let rnc_nonempty = rnc.as_deref().map(|s| !s.is_empty()).unwrap_or(false);
rnc_nonempty
&& redir.as_ref().unwrap().len() == 1
&& redir.as_ref().unwrap()[0].typ == REDIR_READ
} {
if args.is_none() {
args = Some(Vec::new());
}
let rnc = getsparam("READNULLCMD").unwrap_or_default();
args.as_mut().unwrap().push(rnc); } else {
if args.is_none() {
args = Some(Vec::new());
}
let nc = getsparam("NULLCMD").unwrap_or_default();
args.as_mut().unwrap().push(nc); }
} else if (cflags & BINF_PREFIX) != 0 && (cflags & BINF_COMMAND) != 0 {
LASTVAL.store(0, Ordering::Relaxed); if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
); }
return; } else {
if crate::ported::glob::BADCSHGLOB.load(Ordering::Relaxed) == 1 {
zerr("no match"); LASTVAL.store(1, Ordering::Relaxed); if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
); }
return; }
cmdoutval.store(
if use_cmdoutval.load(Ordering::Relaxed) != 0 {
LASTVAL.load(Ordering::Relaxed)
} else {
0
},
Ordering::Relaxed,
);
if varspc.is_some() {
LASTVAL.store(old_lastval, Ordering::Relaxed); addvars(state, varspc.unwrap_or(0), 0); }
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed); } else {
LASTVAL.store(cmdoutval.load(Ordering::Relaxed), Ordering::Relaxed);
}
if isset(XTRACE) {
eprintln!();
}
if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
); }
return; }
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0
|| checked != 0
|| is_builtin != 0
|| if isset(POSIXBUILTINS) {
(cflags & BINF_EXEC) != 0
} else {
(cflags & BINF_COMMAND) != 0
}
{
break; }
let cmdarg = args.as_ref().unwrap()[0].clone();
if (cflags & (BINF_BUILTIN | BINF_COMMAND)) == 0 {
let in_shfunctab = shfunctab_lock()
.read()
.map(|t| t.iter().any(|(k, _)| k.as_str() == cmdarg.as_str()))
.unwrap_or(false);
if in_shfunctab {
is_shfunc = 1; break; }
}
let builtin_entry: Option<&'static builtin> = BUILTINS
.iter()
.find(|b| b.node.nam.as_str() == cmdarg.as_str());
if builtin_entry.is_none() {
if (cflags & BINF_BUILTIN) != 0 {
zwarn(&format!("no such builtin: {}", cmdarg)); LASTVAL.store(1, Ordering::Relaxed); if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
); }
return; }
break; }
let entry = builtin_entry.unwrap();
if (entry.node.flags as u32 & BINF_PREFIX) == 0 {
is_builtin = 1; hn = Some(entry as *const builtin as *mut builtin);
break; }
cflags &= !(BINF_BUILTIN | BINF_COMMAND);
cflags |= entry.node.flags as u32;
if let Some(ref mut v) = args {
v.remove(0); }
hn = None; }
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if LASTVAL.load(Ordering::Relaxed) == 0 {
LASTVAL.store(1, Ordering::Relaxed); }
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(Some(&mut shti), Some(&mut chti), Some(&mut then_ts), 1);
}
return; }
if text.is_none()
&& sfcontext.load(Ordering::Relaxed) == 0
&& (isset(MONITOR) || (how & Z_TIMED as i32) != 0)
{
text = Some(crate::ported::text::getjobtext(
state.prog.clone(),
Some(eparams.beg),
)); }
if typ != WC_FUNCDEF as i32 {
let last_str = args
.as_ref()
.and_then(|v| v.last())
.cloned()
.unwrap_or_default();
setunderscore(&last_str); }
if typ == WC_SIMPLE as i32
&& crate::ported::zsh_h::interact()
&& unset(RMSTARSILENT)
&& isset(SHINSTDIN)
&& args.as_ref().map(|v| v.len() >= 2).unwrap_or(false)
&& args.as_ref().unwrap()[0] == "rm"
{
let args_v = args.as_ref().unwrap().clone();
for s in args_v.iter().skip(1) {
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
break;
}
let l = s.len();
if s.len() == 1 && s.as_bytes()[0] == Star as u8 {
let pwd = getsparam("PWD").unwrap_or_default();
if !crate::ported::utils::checkrmall(&pwd) {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); break; }
} else if l >= 2 {
let bytes = s.as_bytes();
if bytes[l - 2] == b'/' && bytes[l - 1] == Star as u8 {
let prefix = if l == 2 {
"/".to_string()
} else {
String::from_utf8_lossy(&bytes[..l - 2]).into_owned()
};
if !crate::ported::utils::checkrmall(&prefix) {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); break; }
}
}
}
}
if typ == WC_FUNCDEF as i32 {
if state.prog.prog.get(state.pc).copied().unwrap_or(0) != 0 {
redir = None; }
} else if is_shfunc != 0 || typ == WC_AUTOFN as i32 {
if is_shfunc != 0 {
} else {
if let Some(ref mut sh) = state.prog.shf {
let shf_ptr: *mut shfunc = sh.as_mut() as *mut shfunc;
let r = loadautofn(shf_ptr, 1, 0, 0);
if r != 0 {
LASTVAL.store(1, Ordering::Relaxed);
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if forked != 0 {
crate::ported::builtin::_realexit();
}
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
);
}
return; }
}
}
let shfn_name = args
.as_ref()
.and_then(|v| v.first())
.cloned()
.unwrap_or_default();
let shf_redir_eprog: Option<crate::ported::zsh_h::Eprog> = {
if let Ok(tab) = shfunctab_lock().read() {
tab.get(&shfn_name).and_then(|s| s.redir.clone())
} else {
None
}
};
if let Some(red_eprog) = shf_redir_eprog {
let mut tmp_state = estate {
prog: red_eprog.clone(),
pc: 0,
strs: red_eprog.strs.clone(),
strs_offset: 0,
};
let redir2 = crate::ported::parse::ecgetredirs(&mut tmp_state);
if redir.is_none() {
redir = Some(redir2); } else if let Some(ref mut r) = redir {
for n in redir2 {
r.push(n);
}
}
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed); if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if forked != 0 {
crate::ported::builtin::_realexit(); }
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(Some(&mut shti), Some(&mut chti), Some(&mut then_ts), 1);
}
return; }
if (typ == WC_SIMPLE as i32 || typ == WC_TYPESET as i32) && nullexec == 0 {
let trycd = isset(AUTOCD)
&& isset(SHINSTDIN)
&& redir.as_ref().map(|v| v.is_empty()).unwrap_or(true)
&& args.as_ref().map(|v| v.len() == 1).unwrap_or(false)
&& !args.as_ref().unwrap()[0].is_empty(); if hn.is_none() {
let cmdarg = args.as_ref().unwrap()[0].clone();
let mut dohashcmd = isset(HASHCMDS); let mut have_cmdnam: Option<cmdnam> = {
let tab = cmdnamtab_lock().read().ok();
tab.and_then(|t| {
t.iter()
.find(|(k, _)| k.as_str() == cmdarg.as_str())
.map(|(_, v)| v.clone())
})
};
if have_cmdnam.is_some() && trycd && !isreallycom(have_cmdnam.as_ref().unwrap()) {
cmdnam_unhashed(&cmdarg, Vec::new());
have_cmdnam = None;
if let Some(cn) = have_cmdnam.as_ref() {
if (cn.node.flags & crate::ported::zsh_h::HASHED) == 0 {
dohashcmd = true;
}
}
}
if have_cmdnam.is_none() && dohashcmd && cmdarg != ".." {
let has_slash = cmdarg.contains('/'); if !has_slash {
let path_dirs = getsparam("PATH").unwrap_or_default();
let dirs: Vec<String> = path_dirs.split(':').map(String::from).collect();
have_cmdnam = hashcmd(&cmdarg, &dirs);
}
}
let _ = have_cmdnam;
}
if hn.is_none() && trycd {
let cmdarg = args.as_ref().unwrap()[0].clone();
if let Some(s) = cancd(&cmdarg) {
args.as_mut().unwrap()[0] = s; args.as_mut().unwrap().insert(0, "--".to_string()); args.as_mut().unwrap().insert(0, "cd".to_string()); let cd_entry = BUILTINS.iter().find(|b| b.node.nam.as_str() == "cd");
if let Some(cd) = cd_entry {
hn = Some(cd as *const builtin as *mut builtin);
is_builtin = 1; }
}
}
}
is_cursh =
(is_builtin != 0 || is_shfunc != 0 || nullexec != 0 || typ >= WC_CURSH as i32) as i32;
if forked == 0 {
if do_exec == 0
&& (((is_builtin != 0 || is_shfunc != 0) && output != 0)
|| (is_cursh == 0
&& (last1 != 1
|| crate::ported::signals::nsigtrapped.load(Ordering::Relaxed) != 0
|| JOBTAB
.get()
.map(|jt| crate::ported::jobs::havefiles(&jt.lock().unwrap()))
.unwrap_or(false)
|| false)))
{
let mut filelist_for_fork = filelist.clone();
let pid = execcmd_fork(
state,
how,
typ,
varspc,
&mut filelist_for_fork,
text.as_deref().unwrap_or(""),
oautocont,
close_if_forked,
);
match pid {
-1 => {
redir_err = 1;
return execcmd_exec_done_path(
redir_err,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
forked,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
0 => {
}
_ => {
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
if (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(
Some(&mut shti),
Some(&mut chti),
Some(&mut then_ts),
1,
);
}
return;
}
}
forked = 1; } else if is_cursh != 0 {
let thisjob = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if thisjob >= 0 {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
if let Some(j) = guard.get_mut(thisjob as usize) {
j.stat |= STAT_CURSH; if j.procs.is_empty() {
j.stat |= STAT_NOPRINT; }
if is_builtin != 0 {
j.stat |= STAT_BUILTIN; }
}
}
}
} else {
is_exec = 1; if typ == WC_SUBSH as i32 {
forked = 1; }
}
}
if (cflags & BINF_NOGLOB) == 0 && args.is_some() && eparams.htok != 0 {
let mut oargs: LinkList<String> = Default::default();
if let Some(ref v) = args {
for s in v {
oargs.push_back(s.clone());
}
}
globlist(&mut oargs, 0); let mut out: Vec<String> = Vec::new();
while let Some(s) = oargs.pop_front() {
out.push(s);
}
args = Some(out);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed); return execcmd_exec_err_path(
forked,
&mut save,
&mut mfds,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
redir_err,
);
}
if input != 0 {
addfd(forked, &mut save, &mut mfds, 0, input, 0, None); }
if output != 0 {
addfd(forked, &mut save, &mut mfds, 1, output, 1, None); }
if let Some(ref mut r) = redir {
spawnpipes(r.as_mut_slice(), nullexec);
}
while let Some(redir_list) = redir.as_mut() {
if redir_list.is_empty() {
break;
}
let mut fn_ = redir_list.remove(0); if fn_.typ == REDIR_INPIPE {
if checkclobberparam(&fn_) == 0 || fn_.fd2 == -1 {
if fn_.fd2 != -1 {
let _ = zclose(fn_.fd2); }
closemnodes(&mut mfds); fixfds(&save); {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fn_.fd2,
0,
fn_.varid.as_deref(),
);
} else if fn_.typ == REDIR_OUTPIPE {
if checkclobberparam(&fn_) == 0 || fn_.fd2 == -1 {
if fn_.fd2 != -1 {
let _ = zclose(fn_.fd2); }
closemnodes(&mut mfds); fixfds(&save); {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fn_.fd2,
1,
fn_.varid.as_deref(),
);
} else {
let mut closed: i32; if fn_.typ != REDIR_HERESTR {
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
closemnodes(&mut mfds); fixfds(&save); {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
if !isset(EXECOPT) {
continue;
}
let fil_local: i32;
match fn_.typ {
t if t == REDIR_HERESTR => {
if checkclobberparam(&fn_) == 0 {
fil_local = -1; } else {
fil_local = getherestr(&fn_); }
if fil_local == -1 {
let e = std::io::Error::last_os_error();
let raw = e.raw_os_error().unwrap_or(0);
if raw != 0 && raw != libc::EINTR {
zwarn(&format!("can't create temp file for here document: {}", e));
}
closemnodes(&mut mfds); fixfds(&save); {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fil_local,
0,
fn_.varid.as_deref(),
);
}
t if t == REDIR_READ || t == REDIR_READWRITE => {
if checkclobberparam(&fn_) == 0 {
fil_local = -1; } else {
let name = fn_.name.clone().unwrap_or_default();
let unmeta_name = unmeta(&name);
let cstr = match std::ffi::CString::new(unmeta_name.as_str()) {
Ok(c) => c,
Err(_) => {
closemnodes(&mut mfds);
fixfds(&save);
{
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
}
break;
}
};
if fn_.typ == REDIR_READ {
fil_local = unsafe {
libc::open(cstr.as_ptr(), libc::O_RDONLY | libc::O_NOCTTY)
};
} else {
fil_local = unsafe {
libc::open(
cstr.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_NOCTTY,
0o666,
)
};
}
}
if fil_local == -1 {
closemnodes(&mut mfds); fixfds(&save); let e = std::io::Error::last_os_error();
if e.raw_os_error().unwrap_or(0) != libc::EINTR {
zwarn(&format!("{}: {}", e, fn_.name.as_deref().unwrap_or("")));
}
{
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fil_local,
0,
fn_.varid.as_deref(),
);
if nullexec == 1
&& fn_.fd1 == 0
&& fn_.varid.is_none()
&& isset(SHINSTDIN)
&& isset(INTERACTIVE)
{
crate::ported::init::init_io(None); }
}
t if t == REDIR_CLOSE => {
let mut fd1_local = fn_.fd1;
if let Some(varname) = fn_.varid.as_deref() {
let mut bad: u8 = 0;
let value_opt = getsparam(varname);
let is_ro = paramtab()
.read()
.ok()
.and_then(|t| {
t.get(varname)
.map(|p| (p.node.flags as u32 & PM_READONLY) != 0)
})
.unwrap_or(false);
if value_opt.is_none() {
bad = 1; } else if is_ro {
bad = 2; } else {
let s = value_opt.as_deref().unwrap_or("");
match s.trim().parse::<i32>() {
Ok(n) => {
fd1_local = n;
fn_.fd1 = n;
let max_fd = MAX_ZSH_FD.load(Ordering::Relaxed);
if n >= 10
&& n <= max_fd
&& (fdtable_get(n) & FDT_TYPE_MASK) == FDT_INTERNAL
{
bad = 3;
}
}
Err(_) => {
bad = 1; }
}
}
if bad != 0 {
match bad {
3 => zwarn(&format!(
"file descriptor {} used by shell, not closed",
fn_.fd1
)),
2 => zwarn(&format!(
"can't close file descriptor from readonly parameter {}",
varname
)),
_ => zwarn(&format!(
"parameter {} does not contain a file descriptor",
varname
)),
}
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
break;
}
}
closed = 0;
if forked == 0 && fd1_local < 10 && save[fd1_local as usize] == -2 {
let mv = movefd(fd1_local); save[fd1_local as usize] = mv;
if mv >= 0 {
closed = 1; }
}
if fd1_local < 10 {
closemn(&mut mfds, fd1_local, REDIR_CLOSE);
}
let _ = &mut fd1_local;
if closed == 0 && zclose(fn_.fd1) < 0 && fn_.varid.is_some() {
zwarn(&format!(
"failed to close file descriptor {}: {}",
fn_.fd1,
std::io::Error::last_os_error()
)); }
}
t if t == REDIR_MERGEIN || t == REDIR_MERGEOUT => {
if fn_.fd2 < 10 {
closemn(&mut mfds, fn_.fd2, fn_.typ); }
if checkclobberparam(&fn_) == 0 {
fil_local = -1; } else if fn_.fd2 > 9 {
let max_fd = MAX_ZSH_FD.load(Ordering::Relaxed);
let cin = crate::ported::modules::clone::coprocin.load(Ordering::Relaxed);
let cout = crate::ported::modules::clone::coprocout.load(Ordering::Relaxed);
let in_table = if fn_.fd2 <= max_fd {
let kind = fdtable_get(fn_.fd2) & FDT_TYPE_MASK;
kind != FDT_UNUSED && kind != FDT_EXTERNAL
} else {
false
};
if in_table || fn_.fd2 == cin || fn_.fd2 == cout {
fil_local = -1; #[cfg(target_os = "macos")]
unsafe {
*libc::__error() = libc::EBADF;
}
#[cfg(target_os = "linux")]
unsafe {
*libc::__errno_location() = libc::EBADF;
}
} else {
let fd = if fn_.fd2 == -2 {
if fn_.typ == REDIR_MERGEOUT {
crate::ported::modules::clone::coprocout.load(Ordering::Relaxed)
} else {
crate::ported::modules::clone::coprocin.load(Ordering::Relaxed)
}
} else {
fn_.fd2
};
let dup_fd = unsafe { libc::dup(fd) };
fil_local = movefd(dup_fd);
}
} else {
let fd = if fn_.fd2 == -2 {
if fn_.typ == REDIR_MERGEOUT {
crate::ported::modules::clone::coprocout.load(Ordering::Relaxed)
} else {
crate::ported::modules::clone::coprocin.load(Ordering::Relaxed)
}
} else {
fn_.fd2
};
let dup_fd = unsafe { libc::dup(fd) };
fil_local = movefd(dup_fd);
}
if fil_local == -1 {
closemnodes(&mut mfds); fixfds(&save); if std::io::Error::last_os_error().raw_os_error().unwrap_or(0) != 0 {
let desc = if fn_.fd2 == -2 {
"coprocess".to_string()
} else {
format!("{}", fn_.fd2)
};
zwarn(&format!("{}: {}", desc, std::io::Error::last_os_error()));
}
{
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
let merge_is_out = if fn_.typ == REDIR_MERGEOUT { 1 } else { 0 };
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fil_local,
merge_is_out,
fn_.varid.as_deref(),
);
}
_ => {
let mut dfil: i32;
if checkclobberparam(&fn_) == 0 {
fil_local = -1; } else if IS_APPEND_REDIR(fn_.typ) {
let name = fn_.name.clone().unwrap_or_default();
let unmeta_name = unmeta(&name);
let cstr = match std::ffi::CString::new(unmeta_name.as_str()) {
Ok(c) => c,
Err(_) => {
closemnodes(&mut mfds);
fixfds(&save);
{
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
}
break;
}
};
let mode = if !isset(CLOBBER)
&& !isset(crate::ported::zsh_h::APPENDCREATE)
&& !IS_CLOBBER_REDIR(fn_.typ)
{
libc::O_WRONLY | libc::O_APPEND | libc::O_NOCTTY
} else {
libc::O_WRONLY | libc::O_APPEND | libc::O_CREAT | libc::O_NOCTTY
};
fil_local = unsafe { libc::open(cstr.as_ptr(), mode, 0o666) };
} else {
fil_local = clobber_open(&fn_);
}
if fil_local != -1 && IS_ERROR_REDIR(fn_.typ) {
let dup_fd = unsafe { libc::dup(fil_local) };
dfil = movefd(dup_fd); } else {
dfil = 0; }
if fil_local == -1 || dfil == -1 {
if fil_local != -1 {
unsafe { libc::close(fil_local) }; }
closemnodes(&mut mfds); fixfds(&save); let e = std::io::Error::last_os_error();
let raw = e.raw_os_error().unwrap_or(0);
if raw != 0 && raw != libc::EINTR {
zwarn(&format!("{}: {}", e, fn_.name.as_deref().unwrap_or("")));
}
{
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
addfd(
forked,
&mut save,
&mut mfds,
fn_.fd1,
fil_local,
1,
fn_.varid.as_deref(),
);
if IS_ERROR_REDIR(fn_.typ) {
addfd(forked, &mut save, &mut mfds, 2, dfil, 1, None);
}
let _ = &mut dfil;
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
closemnodes(&mut mfds); fixfds(&save); {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
LASTVAL.store(1, Ordering::Relaxed);
} break;
}
}
}
i = 0;
while i < 10 {
if let Some(m) = mfds.get(i as usize).and_then(|o| o.as_ref()) {
if m.ct >= 2 {
closemn(&mut mfds, i, REDIR_CLOSE); }
}
i += 1;
}
if nullexec != 0 {
if let Some(vspc) = varspc {
let mut restorelist: Vec<crate::ported::zsh_h::param> = Vec::new();
let mut removelist: Vec<String> = Vec::new();
if !isset(POSIXBUILTINS) && nullexec != 2 {
save_params(state, vspc, &mut restorelist, &mut removelist);
}
addvars(state, vspc, 0); if !restorelist.is_empty() {
restore_params(restorelist, removelist); }
}
let ef = errflag.load(Ordering::Relaxed);
LASTVAL.store(
if ef != 0 {
ef
} else {
cmdoutval.load(Ordering::Relaxed)
},
Ordering::Relaxed,
); if nullexec == 1 {
i = 0;
while i < 10 {
if save[i as usize] != -2 {
let _ = zclose(save[i as usize]); }
i += 1;
}
let thisjob = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if thisjob >= 0 {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
if let Some(j) = guard.get_mut(thisjob as usize) {
j.stat |= STAT_DONE; }
}
}
return execcmd_exec_done_path(
redir_err,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
forked,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
if isset(XTRACE) {
eprintln!();
}
} else if isset(EXECOPT) && (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let _q = 0;
if is_exec != 0 {
let mut flags: i32 = if (how & Z_ASYNC as i32) != 0 {
esub::ASYNC
} else {
0
} | esub::PGRP
| esub::FAKE; if typ != WC_SUBSH as i32 {
flags |= esub::KEEPTRAP; }
if (do_exec != 0 || (typ >= WC_CURSH as i32 && last1 == 1)) && forked == 0 {
flags |= esub::REVERTPGRP; }
entersubsh(flags, None); }
if typ == WC_FUNCDEF as i32 {
let redir_prog: Option<crate::ported::zsh_h::Eprog> = None;
let lv = execfuncdef(state, redir_prog);
LASTVAL.store(lv, Ordering::Relaxed);
} else if typ >= WC_CURSH as i32 {
if last1 == 1 {
do_exec = 1; }
if typ == WC_AUTOFN as i32 {
let lv = execautofn_basic(state, do_exec); LASTVAL.store(lv, Ordering::Relaxed);
} else {
let lv = dispatch_execfuncs(state, typ, do_exec);
LASTVAL.store(lv, Ordering::Relaxed);
}
} else if is_builtin != 0 || is_shfunc != 0 {
let mut restorelist: Vec<crate::ported::zsh_h::param> = Vec::new();
let mut removelist: Vec<String> = Vec::new();
let mut do_save: i32 = 0;
if forked == 0 {
if isset(POSIXBUILTINS) {
if is_shfunc != 0
|| (hn.map(|p| unsafe { (*p).node.flags as u32 }).unwrap_or(0)
& (BINF_PSPECIAL | BINF_ASSIGN_FLAG))
!= 0
{
do_save = if (orig_cflags & BINF_COMMAND) != 0 {
1
} else {
0
};
} else {
do_save = 1; }
} else {
if (cflags & (BINF_COMMAND | BINF_ASSIGN_FLAG)) != 0 || magic_assign == 0 {
do_save = 1; }
}
if do_save != 0 {
if let Some(vspc) = varspc {
save_params(state, vspc, &mut restorelist, &mut removelist);
}
}
}
if varspc.is_some() {
let mut addflags: i32 = 0; if is_shfunc != 0 {
addflags |= ADDVAR_EXPORT; }
if !restorelist.is_empty() {
addflags |= ADDVAR_RESTORE; }
addvars(state, varspc.unwrap_or(0), addflags); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if !restorelist.is_empty() {
restore_params(restorelist, removelist); }
LASTVAL.store(1, Ordering::Relaxed); fixfds(&save); return execcmd_exec_done_path(
redir_err,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
forked,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
}
if is_shfunc != 0 {
let mut a_vec: Vec<String> = args.clone().unwrap_or_default();
let name = args
.as_ref()
.and_then(|v| v.first())
.cloned()
.unwrap_or_default();
let mut shf_clone: Option<shfunc> = if let Ok(tab) = shfunctab_lock().read() {
tab.get(&name).cloned()
} else {
None
};
if let Some(ref mut shf) = shf_clone {
execshfunc(shf, &mut a_vec);
}
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::pipecleanfilelist(j, false);
}
}
}
} else {
let mut assigns: Vec<crate::ported::zsh_h::asgment> = Vec::new(); let postassigns = eparams.postassigns; if forked != 0 {
closem(FDT_INTERNAL, 0); }
if postassigns != 0 {
use crate::ported::zsh_h::{
ASG_ARRAY, ASG_KEY_VALUE, EC_DUPTOK as ECDUPTOK_LOCAL, PREFORK_ASSIGN,
PREFORK_KEY_VALUE, PREFORK_SINGLE, PREFORK_TYPESET, WC_ASSIGN_INC,
WC_ASSIGN_NUM, WC_ASSIGN_SCALAR, WC_ASSIGN_TYPE, WC_ASSIGN_TYPE2,
};
let opc = state.pc; state.pc = eparams.assignspc.unwrap_or(state.pc); let mut pa_remaining = postassigns;
while pa_remaining > 0 {
pa_remaining -= 1;
let mut pa_htok: i32 = 0; if state.pc >= state.prog.prog.len() {
break;
}
let ac = state.prog.prog[state.pc]; state.pc += 1;
let mut name = ecgetstr(state, ECDUPTOK_LOCAL, Some(&mut pa_htok)); if pa_htok != 0 {
let mut svl: LinkList<String> = Default::default();
svl.push_back(name.clone());
if WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR
&& WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC
{
let mut dummy_htok: i32 = 0;
let _ = ecgetstr(state, ECDUPTOK_LOCAL, Some(&mut dummy_htok));
let mut rf = 0i32;
prefork(&mut svl, PREFORK_TYPESET, &mut rf); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = opc; break;
}
let mut rf2 = 0i32;
globlist(&mut svl, rf2); let _ = &mut rf2;
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = opc; break;
}
while let Some(data) = svl.pop_front() {
let (asg_name, asg_val): (String, Option<String>) =
if let Some(eq_pos) = data.find('=') {
(
data[..eq_pos].to_string(),
Some(data[eq_pos + 1..].to_string()),
)
} else {
(data, None)
};
assigns.push(crate::ported::zsh_h::asgment {
node: crate::ported::zsh_h::linknode {
next: None,
prev: None,
dat: 0,
},
name: asg_name,
flags: 0,
scalar: asg_val,
array: None,
});
}
continue; }
let mut rf = 0i32;
prefork(&mut svl, PREFORK_SINGLE, &mut rf);
name = if svl.is_empty() {
String::new()
} else {
svl.pop_front().unwrap_or_default()
};
}
name = untokenize(&name);
let mut asg = crate::ported::zsh_h::asgment {
node: crate::ported::zsh_h::linknode {
next: None,
prev: None,
dat: 0,
},
name,
flags: 0,
scalar: None,
array: None,
};
if WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR {
let mut val_htok: i32 = 0;
let mut val = ecgetstr(state, ECDUPTOK_LOCAL, Some(&mut val_htok)); asg.flags = 0; if WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC {
asg.scalar = None;
} else {
if val_htok != 0 {
let mut svl: LinkList<String> = Default::default();
svl.push_back(val.clone());
let mut rf = 0i32;
prefork(&mut svl, PREFORK_SINGLE | PREFORK_ASSIGN, &mut rf); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = opc; break;
}
val = if svl.is_empty() {
String::new()
} else {
svl.pop_front().unwrap_or_default()
};
}
asg.scalar = Some(untokenize(&val));
}
} else {
asg.flags = ASG_ARRAY; let mut arr_htok: i32 = 0;
let arr_words = ecgetlist(
state,
WC_ASSIGN_NUM(ac) as usize,
ECDUPTOK_LOCAL,
Some(&mut arr_htok),
); let mut arr_list: LinkList<String> = Default::default();
for s in arr_words {
arr_list.push_back(s);
}
if !arr_list.is_empty()
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
{
let mut prefork_ret = 0i32;
prefork(&mut arr_list, PREFORK_ASSIGN, &mut prefork_ret); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = opc; break;
}
if (prefork_ret & PREFORK_KEY_VALUE) != 0 {
asg.flags |= ASG_KEY_VALUE; }
globlist(&mut arr_list, prefork_ret); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = opc; break;
}
}
asg.array = Some(arr_list);
}
assigns.push(asg);
}
state.pc = opc; }
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let mut a_vec: Vec<String> = args.clone().unwrap_or_default();
if !a_vec.is_empty() {
a_vec.remove(0);
}
let ret = crate::ported::builtin::execbuiltin(
a_vec,
assigns,
hn.unwrap_or(std::ptr::null_mut()),
); if (errflag.load(Ordering::Relaxed) & ERRFLAG_INT) == 0 {
LASTVAL.store(ret, Ordering::Relaxed); }
}
if (do_save & BINF_COMMAND as i32) != 0 {
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed); }
}
if isset(PRINTEXITVALUE)
&& isset(SHINSTDIN)
&& LASTVAL.load(Ordering::Relaxed) != 0
&& subsh.load(Ordering::Relaxed) == 0
{
eprintln!("zsh: exit {}", LASTVAL.load(Ordering::Relaxed)); }
if do_exec != 0 {
if subsh.load(Ordering::Relaxed) != 0 {
crate::ported::builtin::_realexit(); }
if isset(RCS)
&& crate::ported::zsh_h::interact()
&& nohistsave.load(Ordering::Relaxed) == 0
{
crate::ported::hist::savehistfile(None, HFILE_USE_OPTIONS as i32);
}
crate::ported::builtin::realexit(); }
if !restorelist.is_empty() {
restore_params(restorelist, removelist); }
} else {
if subsh.load(Ordering::Relaxed) == 0 {
if forked == 0 {
let cur = getsparam("SHLVL")
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(1);
setiparam("SHLVL", cur - 1); }
if do_exec != 0
&& isset(RCS)
&& crate::ported::zsh_h::interact()
&& nohistsave.load(Ordering::Relaxed) == 0
{
crate::ported::hist::savehistfile(None, HFILE_USE_OPTIONS as i32);
}
}
if typ == WC_SIMPLE as i32 || typ == WC_TYPESET as i32 {
if varspc.is_some() {
let mut addflags: i32 = ADDVAR_EXPORT; if forked != 0 {
addflags |= ADDVAR_RESTORE; }
addvars(state, varspc.unwrap_or(0), addflags); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
std::process::exit(1); }
}
closem(FDT_INTERNAL, 0); let cpi = crate::ported::modules::clone::coprocin.load(Ordering::Relaxed);
if cpi != -1 {
let _ = zclose(cpi); crate::ported::modules::clone::coprocin.store(-1, Ordering::Relaxed);
}
let cpo = crate::ported::modules::clone::coprocout.load(Ordering::Relaxed);
if cpo != -1 {
let _ = zclose(cpo); crate::ported::modules::clone::coprocout.store(-1, Ordering::Relaxed);
}
if forked == 0 {
setlimits(""); }
if (how & Z_ASYNC as i32) != 0 {
let mut guard = STTYval.lock().unwrap();
*guard = None; }
let mut a_vec: Vec<String> = args.clone().unwrap_or_default();
execute(&mut a_vec, cflags, use_defpath); } else {
list_pipe.store(0, Ordering::Relaxed); if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
let tj = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if tj >= 0 {
if let Some(j) = guard.get_mut(tj as usize) {
crate::ported::jobs::pipecleanfilelist(j, false);
}
}
}
state.pc += 1; let _ = execlist(state, 0, 1); }
}
}
return execcmd_exec_done_path(
redir_err,
oautocont,
how,
&mut shti,
&mut chti,
&mut then_ts,
forked,
&mut newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
#[allow(clippy::too_many_arguments)]
fn execcmd_exec_done_path(
redir_err: i32,
oautocont: i32,
how: i32,
shti: &mut crate::ported::jobs::timeinfo,
chti: &mut crate::ported::jobs::timeinfo,
then_ts: &mut std::time::Instant,
forked: i32,
newxtrerr: &mut Option<i32>,
cflags: u32,
orig_cflags: u32,
is_cursh: i32,
do_exec: i32,
) {
use crate::ported::zsh_h::{
AUTOCONTINUE, BINF_COMMAND, BINF_EXEC, BINF_PSPECIAL, INTERACTIVE, POSIXBUILTINS, Z_TIMED,
};
if isset(POSIXBUILTINS)
&& (cflags & (BINF_PSPECIAL | BINF_EXEC)) != 0
&& (orig_cflags & BINF_COMMAND) == 0
{
let _forked_or_subsh = forked | zsh_subshell.load(Ordering::Relaxed); if redir_err != 0 || (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if !isset(INTERACTIVE) {
if _forked_or_subsh != 0 {
unsafe { libc::_exit(1) }; } else {
std::process::exit(1); }
}
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); }
}
if (is_cursh != 0 || do_exec != 0) && (how & Z_TIMED as i32) != 0 {
crate::ported::jobs::shelltime(Some(shti), Some(chti), Some(then_ts), 1);
}
if let Some(fd) = newxtrerr.take() {
let _ = zclose(fd); }
{
let mut guard = STTYval.lock().unwrap();
*guard = None;
}
if oautocont >= 0 {
opt_state_set("autocontinue", oautocont != 0);
}
}
#[allow(clippy::too_many_arguments)]
fn execcmd_exec_err_path(
forked: i32,
save: &mut [i32; 10],
mfds: &mut [Option<Box<multio>>; 10],
oautocont: i32,
how: i32,
shti: &mut crate::ported::jobs::timeinfo,
chti: &mut crate::ported::jobs::timeinfo,
then_ts: &mut std::time::Instant,
newxtrerr: &mut Option<i32>,
cflags: u32,
orig_cflags: u32,
is_cursh: i32,
do_exec: i32,
redir_err: i32,
) {
use crate::ported::zsh_h::FDT_UNUSED;
if forked != 0 {
let mut i: i32 = 0;
while i < 10 {
if fdtable_get(i) != FDT_UNUSED {
unsafe { libc::close(i) }; }
i += 1;
}
closem(FDT_UNUSED, 1); let thisjob = THISJOB.get().map(|m| *m.lock().unwrap()).unwrap_or(-1);
if thisjob != -1 {
if let Some(jt) = JOBTAB.get() {
let mut guard = jt.lock().unwrap();
crate::ported::jobs::waitjobs(&mut guard, thisjob as usize); }
}
crate::ported::builtin::_realexit(); }
fixfds(save);
execcmd_exec_done_path(
redir_err,
oautocont,
how,
shti,
chti,
then_ts,
forked,
newxtrerr,
cflags,
orig_cflags,
is_cursh,
do_exec,
);
}
fn dispatch_execfuncs(state: &mut estate, typ: i32, do_exec: i32) -> i32 {
use crate::ported::zsh_h::{
WC_ARITH, WC_AUTOFN, WC_CASE, WC_COND, WC_CURSH, WC_FOR, WC_FUNCDEF, WC_IF, WC_REPEAT,
WC_SELECT, WC_SUBSH, WC_TIMED, WC_TRY, WC_WHILE,
};
match typ as wordcode {
x if x == WC_CURSH => execcursh(state, do_exec),
x if x == WC_FOR => execfor(state, do_exec),
x if x == WC_SELECT => execselect(state, do_exec),
x if x == WC_WHILE => execwhile(state, do_exec),
x if x == WC_REPEAT => execrepeat(state, do_exec),
x if x == WC_CASE => execcase(state, do_exec),
x if x == WC_IF => execif(state, do_exec),
x if x == WC_COND => execcond(state, do_exec),
x if x == WC_ARITH => execarith(state, do_exec),
x if x == WC_TRY => exectry(state, do_exec),
x if x == WC_FUNCDEF => execfuncdef(state, None),
x if x == WC_AUTOFN => execautofn(state, do_exec),
x if x == WC_TIMED => exectime(state, do_exec),
x if x == WC_SUBSH => execcursh(state, do_exec), _ => 0,
}
}
pub fn stripkshdef(
prog: Option<crate::ported::zsh_h::Eprog>,
name: &str,
) -> Option<crate::ported::zsh_h::Eprog> {
use crate::ported::parse::ecrawstr;
use crate::ported::zsh_h::{
wc_code, wordcode, Dash, EF_HEAP, EF_MAP, WC_FUNCDEF, WC_FUNCDEF_SKIP, WC_LIST,
WC_LIST_TYPE, Z_END, Z_SIMPLE, Z_SYNC,
};
let mut prog = prog?;
if prog.prog.len() < 3 {
return Some(prog);
}
let code0: wordcode = prog.prog[0];
if wc_code(code0) != WC_LIST
|| (WC_LIST_TYPE(code0) & (Z_SYNC | Z_END | Z_SIMPLE) as wordcode)
!= (Z_SYNC | Z_END | Z_SIMPLE) as wordcode
{
return Some(prog);
}
let code: wordcode = prog.prog[2];
let pc_after_code: usize = 3;
if wc_code(code) != WC_FUNCDEF || prog.prog[pc_after_code] != 1 {
return Some(prog);
}
let name_slot = pc_after_code + 1; let name_in_def = ecrawstr(&prog, name_slot, None);
let n1 = name.as_bytes();
let n2 = name_in_def.as_bytes();
let mut i = 0usize;
let mut j = 0usize;
while i < n1.len() && j < n2.len() {
let c1 = n1[i] as char;
let c2 = n2[j] as char;
if c1 != c2 && c1 != Dash && c1 != '-' && c2 != Dash && c2 != '-' {
break;
}
i += 1;
j += 1;
}
if i < n1.len() || j < n2.len() {
return Some(prog);
}
let sbeg = prog.prog[pc_after_code + 2] as usize;
let nstrs = prog.prog[pc_after_code + 3] as usize;
let npats = prog.prog[pc_after_code + 4] as i32;
let skip = WC_FUNCDEF_SKIP(code) as usize;
let end_pc = pc_after_code + skip;
let body_start = pc_after_code + 6;
if end_pc < body_start || end_pc > prog.prog.len() {
return Some(prog);
}
let nprg = end_pc - body_start;
let plen = nprg * size_of::<wordcode>();
let len = plen + (npats as usize) * size_of::<usize>() + nstrs;
let dummy_pat = || {
Box::new(crate::ported::zsh_h::patprog {
startoff: 0,
size: 0,
mustoff: 0,
patmlen: 0,
globflags: 0,
globend: 0,
flags: 0,
patnpar: 0,
patstartch: 0,
})
};
let new_pats: Vec<crate::ported::zsh_h::Patprog> =
(0..npats.max(0)).map(|_| dummy_pat()).collect();
let old_strs = prog.strs.take().unwrap_or_default();
let old_bytes = old_strs.as_bytes();
let new_strs = if sbeg + nstrs <= old_bytes.len() {
Some(String::from_utf8_lossy(&old_bytes[sbeg..sbeg + nstrs]).into_owned())
} else {
Some(String::new())
};
let new_prog: Vec<wordcode> = prog.prog[body_start..end_pc].to_vec();
if (prog.flags & EF_MAP) != 0 {
prog.pats = new_pats;
prog.prog = new_prog;
prog.strs = new_strs;
prog.len = len as i32;
prog.npats = npats;
prog.shf = None;
return Some(prog);
}
let ret = Box::new(eprog {
flags: EF_HEAP,
len: len as i32,
npats,
nref: -1, pats: new_pats,
prog: new_prog,
strs: new_strs,
shf: None, dump: None,
});
Some(ret)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exec_corpus_isrelative_empty_is_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative(""), 1, "empty path is relative");
}
#[test]
fn exec_corpus_isrelative_bare_name_is_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("foo"), 1);
assert_eq!(isrelative("bin/cmd"), 1);
}
#[test]
fn exec_corpus_isrelative_absolute_clean_is_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/foo"), 0, "/foo is absolute");
assert_eq!(isrelative("/bin/ls"), 0);
assert_eq!(isrelative("/"), 0, "root is absolute");
}
#[test]
fn exec_corpus_isrelative_absolute_with_dotdot_is_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
isrelative("/foo/../bar"),
1,
"absolute path with ../ is still 'relative' per zsh"
);
}
#[test]
fn exec_corpus_isrelative_absolute_with_dot_is_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
isrelative("/./x"),
1,
"absolute with ./ component reported relative"
);
}
#[test]
fn exec_corpus_is_anonymous_function_name_matches_sentinel() {
assert_eq!(is_anonymous_function_name("(anon)"), 1);
}
#[test]
fn exec_corpus_is_anonymous_function_name_rejects_normal() {
assert_eq!(is_anonymous_function_name("regular_name"), 0);
assert_eq!(is_anonymous_function_name(""), 0);
assert_eq!(
is_anonymous_function_name("anon"),
0,
"plain 'anon' (no parens) is NOT the sentinel"
);
}
#[test]
fn exec_corpus_iscom_missing_path_false() {
assert!(!iscom("/this/path/does/not/exist/zshrs_xyz"));
}
#[test]
fn exec_corpus_iscom_directory_false() {
assert!(!iscom("/tmp"), "/tmp is a dir, not a regular command");
}
#[test]
fn exec_corpus_iscom_known_binary_true() {
if std::path::Path::new("/bin/sh").exists() {
assert!(iscom("/bin/sh"), "/bin/sh is a real executable");
}
}
#[test]
fn exec_corpus_stripkshdef_null_input_returns_none() {
assert!(stripkshdef(None, "foo").is_none());
}
#[test]
fn exec_corpus_stripkshdef_empty_prog_returns_input() {
let prog = Box::new(eprog {
prog: vec![],
..Default::default()
});
let out = stripkshdef(Some(prog), "foo");
assert!(out.is_some(), "empty prog → returned unchanged");
assert!(out.unwrap().prog.is_empty(), "no mutation");
}
#[test]
fn exec_corpus_stripkshdef_non_list_head_returns_input() {
use crate::ported::zsh_h::{wc_bld, WC_SUBLIST};
let prog = Box::new(eprog {
prog: vec![wc_bld(WC_SUBLIST, 0), 0, 0],
..Default::default()
});
let out = stripkshdef(Some(prog), "foo");
assert!(out.is_some());
let p = out.unwrap();
use crate::ported::zsh_h::wc_code;
assert_eq!(
wc_code(p.prog[0]),
WC_SUBLIST,
"header word preserved verbatim"
);
}
#[test]
fn isrelative_absolute_path_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/usr/local/bin"), 0);
}
#[test]
fn isrelative_no_leading_slash_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("foo/bar"), 1);
}
#[test]
fn isrelative_dot_component_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/foo/./bar"), 1, "/./ in path → relative");
}
#[test]
fn isrelative_dotdot_component_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/foo/../bar"), 1, "/../ in path → relative");
}
#[test]
fn isrelative_empty_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative(""), 1, "empty string → not absolute");
}
#[test]
fn isrelative_dotfile_in_path_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
isrelative("/usr/.config/zsh"),
0,
"dotfile name '.config' is NOT a relative walk"
);
}
#[test]
fn is_anonymous_function_name_anon_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(is_anonymous_function_name("(anon)"), 1);
}
#[test]
fn is_anonymous_function_name_normal_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(is_anonymous_function_name("foo"), 0);
assert_eq!(is_anonymous_function_name(""), 0);
assert_eq!(is_anonymous_function_name("(other)"), 0);
}
#[test]
fn isgooderr_eacces_unreadable_dir_returns_false() {
let _g = crate::test_util::global_state_lock();
assert!(
!isgooderr(libc::EACCES, "/no/such/dir/zshrs_test"),
"unreadable dir with EACCES should NOT be 'good error'"
);
}
#[test]
fn isgooderr_enoent_always_false() {
let _g = crate::test_util::global_state_lock();
assert!(!isgooderr(libc::ENOENT, "/tmp"));
assert!(!isgooderr(libc::ENOENT, "/no/such/dir"));
assert!(!isgooderr(libc::ENOENT, ""));
}
#[test]
fn isgooderr_enotdir_always_false() {
let _g = crate::test_util::global_state_lock();
assert!(!isgooderr(libc::ENOTDIR, "/tmp"));
assert!(!isgooderr(libc::ENOTDIR, "/"));
}
#[test]
fn isgooderr_other_errno_returns_true() {
let _g = crate::test_util::global_state_lock();
assert!(isgooderr(libc::EPERM, "/tmp"));
assert!(isgooderr(libc::EIO, "/tmp"));
assert!(isgooderr(libc::ENOMEM, "/tmp"));
}
#[test]
fn iscom_directory_returns_false() {
let _g = crate::test_util::global_state_lock();
assert!(!iscom("/tmp"));
assert!(!iscom("/"));
}
#[test]
fn iscom_nonexistent_path_returns_false() {
let _g = crate::test_util::global_state_lock();
assert!(!iscom("/no/such/path/zshrs_iscom_test"));
assert!(!iscom(""));
}
#[test]
#[cfg(unix)]
fn iscom_bin_sh_returns_true() {
let _g = crate::test_util::global_state_lock();
assert!(iscom("/bin/sh"), "/bin/sh must be executable on POSIX");
}
#[test]
fn is_anonymous_function_name_strict_match_only() {
let _g = crate::test_util::global_state_lock();
assert_eq!(is_anonymous_function_name("(anon"), 0, "no trailing paren");
assert_eq!(is_anonymous_function_name("anon)"), 0, "no leading paren");
assert_eq!(is_anonymous_function_name("(ANON)"), 0, "wrong case");
assert_eq!(
is_anonymous_function_name(" (anon) "),
0,
"leading/trailing space"
);
assert_eq!(is_anonymous_function_name("(anon) "), 0, "trailing space");
assert_eq!(is_anonymous_function_name(" (anon)"), 0, "leading space");
}
#[test]
fn anonymous_function_name_const_is_literal_anon() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ANONYMOUS_FUNCTION_NAME, "(anon)");
}
#[test]
fn isrelative_dot_slash_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("./foo"), 1);
assert_eq!(isrelative("./"), 1);
}
#[test]
fn isrelative_dotdot_slash_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("../foo"), 1);
assert_eq!(isrelative("../"), 1);
}
#[test]
fn isrelative_root_hidden_file_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/.foo"), 0, "/.foo is absolute path to dotfile");
assert_eq!(isrelative("/.bashrc"), 0, "/.bashrc is absolute");
}
#[test]
fn isrelative_root_double_dot_file_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(isrelative("/..bar"), 0);
}
#[test]
fn setunderscore_empty_clears_state() {
let _g = crate::test_util::global_state_lock();
setunderscore(""); let zu = zunderscore.lock().unwrap();
assert!(zu.is_empty(), "zunderscore must be empty after clear");
drop(zu);
let used = underscoreused.load(Ordering::Relaxed);
assert_eq!(used, 1, "underscoreused must be 1 (NUL only) after clear");
}
#[test]
fn setunderscore_with_value_stores_string_and_length() {
let _g = crate::test_util::global_state_lock();
setunderscore("hello");
let zu = zunderscore.lock().unwrap();
assert_eq!(*zu, "hello");
drop(zu);
let used = underscoreused.load(Ordering::Relaxed);
assert_eq!(used, 6, "len('hello')+1 = 6");
}
#[test]
fn setunderscore_rounds_underscorelen_to_32() {
let _g = crate::test_util::global_state_lock();
setunderscore("ab"); let nl = underscorelen.load(Ordering::Relaxed);
assert_eq!(nl, 32, "(2+1+31) & !31 = 32");
}
#[test]
#[cfg(unix)]
fn cancd2_existing_dir_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(cancd2("/tmp"), 1, "/tmp is a valid cd target");
}
#[test]
fn cancd2_nonexistent_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(cancd2("/__never_exists_zshrs_cancd2__"), 0);
}
#[test]
#[cfg(unix)]
fn cancd2_regular_file_returns_zero() {
let _g = crate::test_util::global_state_lock();
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("regular_file");
std::fs::write(&p, "x").unwrap();
assert_eq!(
cancd2(p.to_str().unwrap()),
0,
"regular file not a cd target"
);
}
#[test]
fn quote_tokenized_output_empty_writes_nothing() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("", &mut buf).unwrap();
assert!(buf.is_empty());
}
#[test]
fn quote_tokenized_output_plain_ascii_unchanged() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("hello", &mut buf).unwrap();
assert_eq!(buf, b"hello");
}
#[test]
fn quote_tokenized_output_space_backslash_quoted() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("a b", &mut buf).unwrap();
assert_eq!(buf, b"a\\ b");
}
#[test]
fn quote_tokenized_output_tab_dollar_escape() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("a\tb", &mut buf).unwrap();
assert_eq!(buf, b"a$'\\t'b");
}
#[test]
fn quote_tokenized_output_newline_dollar_escape() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("a\nb", &mut buf).unwrap();
assert_eq!(buf, b"a$'\\n'b");
}
#[test]
fn quote_tokenized_output_cr_dollar_escape() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("a\rb", &mut buf).unwrap();
assert_eq!(buf, b"a$'\\r'b");
}
#[test]
fn quote_tokenized_output_shell_metas_get_backslash() {
let _g = crate::test_util::global_state_lock();
for c in &[b'<', b'>', b'(', b')', b'|', b'#', b'$', b'*', b'?', b'~'] {
let mut buf = Vec::new();
let s = String::from_utf8(vec![b'a', *c, b'b']).unwrap();
quote_tokenized_output(&s, &mut buf).unwrap();
assert_eq!(buf, vec![b'a', b'\\', *c, b'b'], "char {:?}", *c as char);
}
}
#[test]
fn quote_tokenized_output_equals_at_start_quoted() {
let _g = crate::test_util::global_state_lock();
let mut buf = Vec::new();
quote_tokenized_output("=foo", &mut buf).unwrap();
assert_eq!(buf, b"\\=foo");
}
#[test]
fn iscom_empty_string_returns_false() {
let _g = crate::test_util::global_state_lock();
assert!(!iscom(""), "empty cmd name → not a command");
}
#[test]
fn iscom_returns_bool_type() {
let _g = crate::test_util::global_state_lock();
let _: bool = iscom("ls");
}
#[test]
fn isrelative_absolute_path_returns_zero_pin() {
assert_eq!(isrelative("/usr/bin"), 0, "/usr/bin is absolute");
assert_eq!(isrelative("/"), 0, "/ is absolute");
}
#[test]
fn isrelative_relative_path_returns_one_pin() {
assert_eq!(isrelative("foo"), 1, "foo is relative");
assert_eq!(isrelative("./foo"), 1, "./foo is relative");
assert_eq!(isrelative("../foo"), 1, "../foo is relative");
}
#[test]
fn isrelative_empty_returns_relative() {
let r = isrelative("");
assert!(r == 0 || r == 1, "must be 0 or 1");
}
#[test]
fn is_anonymous_function_name_returns_i32_type() {
let _: i32 = is_anonymous_function_name("(anon)");
}
#[test]
fn is_anonymous_function_name_empty_returns_zero() {
assert_eq!(
is_anonymous_function_name(""),
0,
"empty name is not anonymous"
);
}
#[test]
fn is_anonymous_function_name_is_deterministic() {
for s in ["", "name", "(anon)", "(anon: foo)"] {
let first = is_anonymous_function_name(s);
for _ in 0..3 {
assert_eq!(
is_anonymous_function_name(s),
first,
"is_anonymous_function_name({:?}) must be deterministic",
s
);
}
}
}
#[test]
fn parse_string_returns_option_eprog_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<eprog> = parse_string("", 0);
}
#[test]
fn setunderscore_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
setunderscore("");
}
#[test]
fn isgooderr_returns_bool_type() {
let _: bool = isgooderr(0, "/tmp");
}
#[test]
fn makecline_returns_vec_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Vec<String> = makecline(&[]);
}
#[test]
fn makecline_empty_input_returns_empty() {
let _g = crate::test_util::global_state_lock();
let r = makecline(&[]);
assert!(r.is_empty(), "empty input → empty output");
}
#[test]
fn makecline_preserves_input_order() {
let _g = crate::test_util::global_state_lock();
let input = vec!["one".to_string(), "two".to_string(), "three".to_string()];
let out = makecline(&input);
assert_eq!(out, input, "makecline must preserve order");
}
#[test]
fn makecline_returns_independent_copy() {
let _g = crate::test_util::global_state_lock();
let input = vec!["a".to_string(), "b".to_string()];
let out = makecline(&input);
assert_eq!(out.len(), input.len(), "lengths match");
let mut out_mut = out;
out_mut.push("c".to_string());
assert_eq!(input.len(), 2, "input unchanged");
}
#[test]
fn cancd_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
let saved_pwd = crate::ported::params::getsparam("PWD");
crate::ported::params::setsparam("PWD", "/");
let r = cancd("");
if let Some(p) = saved_pwd {
crate::ported::params::setsparam("PWD", &p);
} else {
crate::ported::params::unsetparam("PWD");
}
assert!(r.is_some(), "empty path → Some(pwd) per cancd2-via-PWD path");
}
#[test]
fn cancd_root_returns_some() {
let _g = crate::test_util::global_state_lock();
let r = cancd("/");
assert_eq!(r.as_deref(), Some("/"), "root dir cancd → Some(/)");
}
#[test]
fn cancd_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<String> = cancd("/");
}
#[test]
fn cancd_nonexistent_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(
cancd("/__nonexistent_zshrs_dir_xyz__").is_none(),
"nonexistent dir → None"
);
}
#[test]
fn cancd_tmp_returns_some() {
let _g = crate::test_util::global_state_lock();
let r = cancd("/tmp");
assert!(r.is_some(), "/tmp exists → Some");
}
#[test]
fn cancd_is_deterministic_for_stable_paths() {
let _g = crate::test_util::global_state_lock();
for p in ["/", "/tmp", "/__never__"] {
let first = cancd(p).is_some();
for _ in 0..3 {
assert_eq!(
cancd(p).is_some(),
first,
"cancd({:?}) must be deterministic",
p
);
}
}
}
#[test]
fn iscom_is_deterministic_for_stable_paths() {
let _g = crate::test_util::global_state_lock();
for p in ["/tmp", "/__never__", "/bin/sh"] {
let first = iscom(p);
for _ in 0..3 {
assert_eq!(iscom(p), first, "iscom({:?}) must be deterministic", p);
}
}
}
#[test]
fn commandnotfound_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut args = Vec::new();
let _: i32 = commandnotfound("", &mut args);
}
}