use std::collections::HashMap;
use std::sync::OnceLock;
use crate::ported::zsh_h::{PRINT_WHENCE_WORD, PRINT_WHENCE_CSH};
use crate::ported::zsh_h::EMULATE_ZSH;
use crate::ported::zsh_h::{options, MAX_OPS, XTRACE, BINF_KEEPNUM, ERRFLAG_ERROR};
use crate::ported::modules::parameter::DIRSTACK;
use std::sync::atomic::Ordering;
use crate::ported::zsh_h::{OPT_HASARG, OPT_ARG, PM_INTEGER, PM_EFLOAT, PM_FFLOAT};
use crate::ported::zsh_h::{PM_LEFT, PM_RIGHT_B, PM_RIGHT_Z};
use crate::ported::zsh_h::{OPT_MINUS, OPT_ISSET, PM_UNDEFINED};
use crate::ported::zsh_h::PM_LOADDIR;
use crate::ported::zsh_h::MFF_STR;
use crate::ported::zsh_h::{PM_ABSPATH_USED, FS_FUNC};
use crate::ported::zsh_h::eprog;
use crate::ported::zsh_h::{STAT_LOCKED, STAT_NOPRINT, STAT_STOPPED};
use std::io::Read;
use crate::ported::zsh_h::{OPT_PLUS, PM_UNALIASED, PM_TAGGED, PM_TAGGED_LOCAL, PM_WARNNESTED, PM_ZSHSTORED, PM_KSHSTORED, PM_CUR_FPATH};
use crate::ported::math::{matheval, mnumber, MN_INTEGER};
use crate::ported::utils::{getkeystring, getkeystring_with, quotedzputs, GETKEYS_PRINT};
use crate::ported::zsh_h::HIST_FOREIGN;
use crate::ported::zsh_h::{HFILE_APPEND, HFILE_SKIPOLD, HFILE_USE_OPTIONS};
use crate::ported::zsh_h::{EMULATION, TYPESET_OPTSTR, PM_HASHED, PM_HIDEVAL, PM_LOWER, PM_UPPER, PM_TIED, PM_LOCAL, PM_NAMEREF, PM_READONLY, PM_ARRAY, PRINT_TYPESET, PRINT_LINE, PRINT_TYPE, PRINT_NAMEONLY, PRINT_POSIX_EXPORT, PRINT_POSIX_READONLY, PRINT_WITH_NAMESPACE, EMULATE_KSH};
use crate::ported::zsh_h::{PRINT_WHENCE_VERBOSE, PRINT_WHENCE_SIMPLE, PRINT_WHENCE_FUNCDEF, PRINT_LIST};
use crate::ported::math::mathevali;
use crate::ported::zsh_h::DISABLED;
use crate::ported::zsh_h::nameddir;
use crate::ported::zsh_h::{ALIAS_GLOBAL, ALIAS_SUFFIX};
use crate::ported::hashtable::{aliastab_lock, sufaliastab_lock, Alias};
use crate::ported::zsh_h::{EMULATE_CSH, EMULATE_SH};
#[allow(unused_imports)]
use std::{env, fs, io, io::Write, path::Path, path::PathBuf};
#[allow(unused_imports)]
use indexmap::IndexMap;
#[allow(unused_imports)]
use crate::ported::exec::{
self, BUILTIN_NAMES,
format_int_in_base,
};
use crate::ported::utils::{zerr, zerrnam, zwarn, zwarnnam};
use crate::func_body_fmt::FuncBodyFmt;
#[allow(unused_imports)]
use crate::ported::options::ZSH_OPTIONS_SET;
#[allow(unused_imports)]
use crate::parse::{Redirect, ShellCommand};
#[allow(unused_imports)]
use crate::zwc::ZwcFile;
pub use crate::ported::hashtable_h::{
BIN_TYPESET, BIN_BG, BIN_FG, BIN_JOBS, BIN_WAIT, BIN_DISOWN,
BIN_BREAK, BIN_CONTINUE, BIN_EXIT, BIN_RETURN, BIN_CD,
BIN_POPD, BIN_PUSHD, BIN_PRINT, BIN_EVAL, BIN_SCHED, BIN_FC,
BIN_R, BIN_PUSHLINE, BIN_LOGOUT, BIN_TEST, BIN_BRACKET,
BIN_READONLY, BIN_ECHO, BIN_DISABLE, BIN_ENABLE, BIN_PRINTF,
BIN_COMMAND, BIN_UNHASH, BIN_UNALIAS, BIN_UNFUNCTION,
BIN_UNSET, BIN_EXPORT, BIN_SETOPT, BIN_UNSETOPT,
};
use crate::zsh_h::{builtin, BINF_ASSIGN, BINF_BUILTIN, BINF_COMMAND, BINF_DASH, BINF_DASHDASHVALID, BINF_EXEC, BINF_HANDLES_OPTS, BINF_MAGICEQUALS, BINF_NOGLOB, BINF_PLUSOPTS, BINF_PREFIX, BINF_PRINTOPTS, BINF_PSPECIAL, BINF_SKIPDASH, BINF_SKIPINVALID, hashnode, NULLBINCMD, isset};
pub fn createbuiltintable() -> &'static HashMap<String, &'static builtin> { builtintab.get_or_init(|| {
let table: &'static Vec<builtin> = &*BUILTINS;
let watch_bintab: &'static Vec<builtin> =
&*crate::ported::modules::watch::bintab;
let mut m: HashMap<String, &'static builtin> =
HashMap::with_capacity(table.len() + watch_bintab.len());
for b in table.iter() {
m.insert(b.node.nam.clone(), b);
}
for b in watch_bintab.iter() {
m.insert(b.node.nam.clone(), b);
}
m
})
}
pub fn printbuiltinnode(hn: *mut crate::ported::zsh_h::hashnode, printflags: i32) {
if hn.is_null() { return; }
let bn = unsafe { &*hn };
if (printflags & PRINT_WHENCE_WORD as i32) != 0 { println!("{}: builtin", bn.nam); return;
}
if (printflags & PRINT_WHENCE_CSH as i32) != 0 { println!("{}: shell built-in command", bn.nam); return;
}
println!("{}", bn.nam);
}
pub fn freebuiltinnode(hn: *mut crate::ported::zsh_h::hashnode) { if hn.is_null() { return; }
let bn = unsafe { &*hn };
if (bn.flags as u32 & crate::ported::zsh_h::BINF_ADDED) == 0 { }
}
pub fn init_builtins() { if !crate::ported::zsh_h::EMULATION(EMULATE_ZSH) { if let Ok(mut tab) = crate::ported::hashtable::reswdtab_lock().write() {
tab.disable("repeat");
}
}
}
pub const OPT_ALLOC_CHUNK: i32 = 16;
pub fn new_optarg(ops: &mut crate::ported::zsh_h::options) -> i32 { if ops.argscount == 63 { return 1;
}
if ops.argsalloc == ops.argscount { ops.args.resize((ops.argsalloc + OPT_ALLOC_CHUNK) as usize, String::new());
ops.argsalloc += OPT_ALLOC_CHUNK; }
ops.argscount += 1; 0 }
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AutoloadFlags: u32 {
const NO_ALIAS = 0b00000001; const ZSH_STYLE = 0b00000010; const KSH_STYLE = 0b00000100; const TRACE = 0b00001000; const USE_CALLER_DIR = 0b00010000; const LOADED = 0b00100000; }
}
pub fn execbuiltin(args: Vec<String>, assigns: Vec<crate::ported::zsh_h::asgment>, bn: *mut crate::ported::zsh_h::builtin) -> i32 {
if bn.is_null() {
return 1;
}
let bn_ref = unsafe { &*bn };
let pp: Option<&str>; let name: String; let mut optstr: Option<String>; let mut flags: i32; let mut argc: i32; let mut execop: u8; let xtr: bool = isset(XTRACE);
let mut ops = options { ind: [0u8; MAX_OPS], args: Vec::new(), argscount: 0, argsalloc: 0 };
name = bn_ref.node.nam.clone();
if bn_ref.handlerfunc.is_none() { return 1; }
flags = bn_ref.node.flags; optstr = bn_ref.optstr.clone();
argc = args.len() as i32;
let argarr: Vec<String> = args; let mut argv: usize = 0;
if let Some(ref os) = optstr.clone() { let optstr_local = os.clone();
let mut optstr_bytes: Vec<u8> = optstr_local.into_bytes();
let mut skipinvalid = (flags & BINF_SKIPINVALID as i32) != 0;
loop {
let arg_str: String = match argarr.get(argv) {
Some(s) => s.clone(),
None => break,
};
let arg_bytes = arg_str.as_bytes();
if arg_bytes.is_empty() { break; }
let sense: i32 = if arg_bytes[0] == b'-' { 1 } else { 0 }; if sense == 0 && !((flags & BINF_PLUSOPTS as i32) != 0 && arg_bytes[0] == b'+') {
break;
}
if (flags & BINF_KEEPNUM as i32) == 0 && arg_bytes.len() >= 2
&& arg_bytes[1].is_ascii_digit() {
break;
}
if (flags & BINF_SKIPDASH as i32) != 0 && arg_bytes.len() == 1 { break;
}
if (flags & BINF_DASHDASHVALID as i32) != 0 && arg_str == "--" { argv += 1; break; }
if skipinvalid { let mut all_known = true;
for &c in &arg_bytes[1..] {
if !optstr_bytes.contains(&c) { all_known = false; break; }
}
if !all_known { break; } }
let mut k: usize = 1; if arg_bytes.len() >= 2 && arg_bytes[1] == b'-' { k = 2; }
if arg_bytes.len() == k { ops.ind[b'-' as usize] = 1; if sense == 0 { ops.ind[b'+' as usize] = 1; }
}
let mut bad_opt: Option<u8> = None;
while k < arg_bytes.len() { let c = arg_bytes[k];
execop = c; let optptr = optstr_bytes.iter().position(|&b| b == c); if let Some(optidx) = optptr { ops.ind[c as usize] = if sense != 0 { 1 } else { 2 }; if optidx + 1 < optstr_bytes.len() && optstr_bytes[optidx + 1] == b':' {
let mut argptr: Option<String> = None;
if optidx + 2 < optstr_bytes.len() && optstr_bytes[optidx + 2] == b':' {
if k + 1 < arg_bytes.len() { argptr = Some(String::from_utf8_lossy(&arg_bytes[k+1..]).into_owned()); }
} else if optidx + 2 < optstr_bytes.len() && optstr_bytes[optidx + 2] == b'%' {
if k + 1 < arg_bytes.len() && arg_bytes[k+1].is_ascii_digit() {
argptr = Some(String::from_utf8_lossy(&arg_bytes[k+1..]).into_owned());
} else if let Some(nxt) = argarr.get(argv + 1) {
if !nxt.is_empty() && nxt.as_bytes()[0].is_ascii_digit() {
argv += 1; argptr = Some(nxt.clone());
}
}
} else {
if k + 1 < arg_bytes.len() { argptr = Some(String::from_utf8_lossy(&arg_bytes[k+1..]).into_owned()); } else if let Some(nxt) = argarr.get(argv + 1) {
argv += 1; argptr = Some(nxt.clone()); } else {
crate::ported::utils::zwarnnam(&name,
&format!("argument expected: -{}", execop as char)); return 1; }
}
if let Some(ap) = argptr { if new_optarg(&mut ops) != 0 { crate::ported::utils::zwarnnam(&name,
"too many option arguments"); return 1; }
ops.ind[execop as usize] |= (ops.argscount as u8) << 2;
ops.args[(ops.argscount - 1) as usize] = ap;
k = arg_bytes.len();
}
}
k += 1;
} else {
bad_opt = Some(c); break;
}
}
if let Some(badc) = bad_opt { crate::ported::utils::zwarnnam(&name,
&format!("bad option: {}{}",
if sense != 0 { '-' } else { '+' }, badc as char)); return 1; }
argv += 1; if (flags & BINF_PRINTOPTS as i32) != 0 && ops.ind[b'R' as usize] != 0
&& ops.ind[b'f' as usize] == 0 {
optstr_bytes = b"ne".to_vec(); flags |= BINF_SKIPINVALID as i32; skipinvalid = true;
}
if ops.ind[b'-' as usize] != 0 { break;
}
}
let _ = optstr_bytes;
} else if (flags & BINF_HANDLES_OPTS as i32) == 0 && argarr.get(argv).map(|s| s == "--").unwrap_or(false) { ops.ind[b'-' as usize] = 1; argv += 1; }
let _ = optstr.take();
pp = bn_ref.defopts.as_deref(); if let Some(pp_str) = pp { for &b in pp_str.as_bytes() { if ops.ind[b as usize] == 0 { ops.ind[b as usize] = 1; }
}
}
argc -= argv as i32;
let ef = crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed);
if (ef & ERRFLAG_ERROR) != 0 { crate::ported::utils::errflag.fetch_and(!ERRFLAG_ERROR, std::sync::atomic::Ordering::Relaxed); return 1; }
if argc < bn_ref.minargs || (argc > bn_ref.maxargs && bn_ref.maxargs != -1) {
crate::ported::utils::zwarnnam(&name, if argc < bn_ref.minargs { "not enough arguments" }
else { "too many arguments" }); return 1; }
if xtr { let fullargv = &argarr; crate::ported::utils::printprompt4(); eprint!("{}", name); for s in fullargv { eprint!(" "); eprint!("{}", crate::ported::utils::quotedzputs(s)); }
for asg in &assigns { eprint!(" "); eprint!("{}", crate::ported::utils::quotedzputs(&asg.name)); if (asg.flags & crate::ported::zsh_h::ASG_ARRAY) != 0 { eprint!("=("); if let Some(ref list) = asg.array { if (asg.flags & crate::ported::zsh_h::ASG_KEY_VALUE) != 0 { let mut keynode = list.firstnode(); loop { let kidx = match keynode { Some(i) => i,
None => break, };
let vidx = match list.nextnode(kidx) { Some(i) => i,
None => break, };
eprint!("["); if let Some(k) = list.getdata(kidx) { eprint!("{}", crate::ported::utils::quotedzputs(k)); }
eprint!("]="); if let Some(v) = list.getdata(vidx) { eprint!("{}", crate::ported::utils::quotedzputs(v)); }
keynode = list.nextnode(vidx); }
} else { let mut arrnode = list.firstnode(); while let Some(idx) = arrnode { eprint!(" "); if let Some(elem) = list.getdata(idx) { eprint!("{}", crate::ported::utils::quotedzputs(elem)); }
arrnode = list.nextnode(idx); }
}
}
eprint!(" )"); } else if let Some(ref scalar) = asg.scalar { eprint!("="); eprint!("{}", crate::ported::utils::quotedzputs(scalar)); }
}
eprintln!(); }
let trimmed: Vec<String> = argarr[argv..].to_vec();
let handler = bn_ref.handlerfunc.expect("handlerfunc checked at c:264");
handler(&name, &trimmed, &ops, bn_ref.funcid) }
pub fn bin_enable(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
enum Tab { Builtin, Shfunc, Reswd, Alias, SufAlias }
let mut returnval = 0i32; let mut match_count = 0i32; if OPT_ISSET(ops, b'p') { return pat_enables(name, argv, func == BIN_ENABLE); }
let tab = if OPT_ISSET(ops, b'f') { Tab::Shfunc } else if OPT_ISSET(ops, b'r') { Tab::Reswd } else if OPT_ISSET(ops, b's') { Tab::SufAlias } else if OPT_ISSET(ops, b'a') { Tab::Alias } else { Tab::Builtin };
let enable = func == BIN_ENABLE;
let (flags1, flags2) = if enable { (0u32, DISABLED as u32) } else {
(DISABLED as u32, 0u32) };
let toggle_one = |tab: &Tab, nm: &str, on: bool| -> bool {
match tab {
Tab::Alias => crate::ported::hashtable::aliastab_lock().write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false),
Tab::SufAlias => crate::ported::hashtable::sufaliastab_lock().write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false),
Tab::Reswd => {
let exists = crate::ported::hashtable::reswdtab_lock().read()
.map(|t| t.get_including_disabled(nm).is_some())
.unwrap_or(false);
if !exists { return false; }
crate::ported::hashtable::reswdtab_lock().write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false)
}
Tab::Shfunc => {
let exists = crate::ported::hashtable::shfunctab_lock().read()
.map(|t| t.get_including_disabled(nm).is_some())
.unwrap_or(false);
if !exists { return false; }
if on {
crate::ported::hashtable::enableshfuncnode(nm);
} else {
crate::ported::hashtable::disableshfuncnode(nm);
}
true
}
Tab::Builtin => {
if createbuiltintable().get(nm).is_none() { return false; }
if let Ok(mut set) = BUILTINS_DISABLED.lock() {
if on { set.remove(nm); } else { set.insert(nm.to_string()); }
return true;
}
false
}
}
};
let collect_names = |tab: &Tab| -> Vec<String> {
match tab {
Tab::Alias => crate::ported::hashtable::aliastab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::SufAlias => crate::ported::hashtable::sufaliastab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::Reswd => crate::ported::hashtable::reswdtab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::Shfunc => crate::ported::hashtable::shfunctab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::Builtin => createbuiltintable().keys().cloned().collect(),
}
};
if argv.is_empty() { crate::ported::mem::queue_signals(); for nm in collect_names(&tab) {
println!("{}", nm);
}
let _ = (flags1, flags2);
crate::ported::mem::unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') { for arg in argv { crate::ported::mem::queue_signals(); let pprog = crate::ported::pattern::patcompile(arg, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
for nm in collect_names(&tab) {
if crate::ported::pattern::pattry(&prog, &nm) { if toggle_one(&tab, &nm, enable) {
match_count += 1; }
}
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", arg)); returnval = 1; }
crate::ported::mem::unqueue_signals(); }
if match_count == 0 { returnval = 1; }
return returnval; }
crate::ported::mem::queue_signals(); for arg in argv { if !toggle_one(&tab, arg, enable) { crate::ported::utils::zwarnnam(name,
&format!("no such hash table element: {}", arg)); returnval = 1; }
}
crate::ported::mem::unqueue_signals(); returnval }
pub fn bin_set(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() && !args.is_empty() {
let ctx = crate::recorder::recorder_ctx_global();
let mut iter = args.iter().peekable();
while let Some(a) = iter.next() {
match a.as_str() {
"-o" => {
if let Some(name) = iter.next() {
crate::recorder::emit_setopt(name, ctx.clone());
}
}
"+o" => {
if let Some(name) = iter.next() {
crate::recorder::emit_unsetopt(name, ctx.clone());
}
}
_ => {}
}
}
}
let mut argv: Vec<String> = args.to_vec();
let mut hadopt = false; let mut hadplus = false; let mut hadend = false; let mut sort: i32 = 0; let mut array: i32 = 0; let mut arrayname: Option<String> = None;
if !EMULATION(EMULATE_ZSH) && !argv.is_empty() && argv[0] == "-"
{
let v = crate::ported::options::optlookup("verbose");
let x = crate::ported::options::optlookup("xtrace");
crate::ported::options::dosetopt(v, 0, 0); crate::ported::options::dosetopt(x, 0, 0); if argv.len() == 1 { return 0; } argv.remove(0);
}
let mut idx = 0usize;
'outer: while idx < argv.len() && (argv[idx].starts_with('-') || argv[idx].starts_with('+'))
{
let arg = argv[idx].clone();
let action = arg.starts_with('-'); if !action { hadplus = true; } let body: String = if arg.len() == 1 { "--".to_string() }
else { arg.clone() };
let chars: Vec<char> = body[1..].chars().collect();
let mut ci = 0usize;
while ci < chars.len() { let c = chars[ci];
if c != '-' || action { hadopt = true; } if c == '-' { hadend = true; idx += 1; break 'outer;
}
if c == 'o' { let optname: String = if ci + 1 < chars.len() {
chars[ci + 1..].iter().collect::<String>()
} else {
idx += 1;
if idx >= argv.len() { return 0;
}
argv[idx].clone()
};
let optno = crate::ported::options::optlookup(&optname); if optno == 0 { crate::ported::utils::zerr(&format!(
"no such option: {}", optname)); } else if crate::ported::options::dosetopt(optno,
if action { 1 } else { 0 }, 0) != 0 {
crate::ported::utils::zerr(&format!(
"can't change option: {}", optname)); }
break;
}
if c == 'A' { array = if action { 1 } else { -1 }; let nameopt: Option<String> = if ci + 1 < chars.len() {
Some(chars[ci + 1..].iter().collect::<String>())
} else if idx + 1 < argv.len() {
idx += 1;
Some(argv[idx].clone())
} else { None };
arrayname = nameopt.clone();
if arrayname.is_none() { idx += 1;
break 'outer;
}
let ksharrays = crate::ported::zsh_h::isset(crate::ported::options::optlookup("ksharrays"));
if !ksharrays { idx += 1; break 'outer; }
break;
}
if c == 's' { sort = if action { 1 } else { -1 }; } else {
let optno = crate::ported::options::optlookupc(c); if optno == 0 { crate::ported::utils::zerr(&format!("bad option: -{}", c)); } else if crate::ported::options::dosetopt(optno,
if action { 1 } else { 0 }, 0) != 0 {
crate::ported::utils::zerr(&format!("can't change option: -{}", c)); }
}
ci += 1;
}
idx += 1; }
let _ = nam;
crate::ported::mem::queue_signals();
let remaining = &argv[idx..];
if arrayname.is_none() { if !hadopt && remaining.is_empty() { let mut entries: Vec<(String, String)> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
(pm.node.flags as u32 & crate::ported::zsh_h::PM_UNSET) == 0
})
.map(|(k, pm)| {
let v = pm.u_str.clone().unwrap_or_default();
(k.clone(), v)
})
.collect()
};
entries.sort_by(|a, b| {
crate::ported::hashtable::hnamcmp(&a.0, &b.0)
});
for (k, v) in entries {
if hadplus { println!("{}", k);
} else {
println!("{}={}", k,
crate::ported::utils::quotedzputs(&v));
}
}
}
if array != 0 { let mut arr_entries: Vec<(String, Vec<String>)> = {
use crate::ported::zsh_h::{PM_ARRAY, PM_TYPE};
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
PM_TYPE(pm.node.flags as u32) == PM_ARRAY
&& (pm.node.flags as u32
& crate::ported::zsh_h::PM_UNSET) == 0
})
.map(|(k, pm)| {
(k.clone(), pm.u_arr.clone().unwrap_or_default())
})
.collect()
};
arr_entries.sort_by(|a, b|
crate::ported::hashtable::hnamcmp(&a.0, &b.0)); for (k, arr) in arr_entries {
if hadplus { println!("{}", k);
} else {
let quoted: Vec<String> = arr.iter()
.map(|v| crate::ported::utils::quotedzputs(v))
.collect();
println!("{}=({})", k, quoted.join(" "));
}
}
}
if remaining.is_empty() && !hadend { crate::ported::mem::unqueue_signals();
return 0; }
}
let sorted: Vec<String> = if sort != 0 {
let mut v = remaining.to_vec();
if sort < 0 { v.sort_by(|a, b| b.cmp(a)); } else { v.sort(); }
v
} else {
remaining.to_vec()
};
if array != 0 { let aname = arrayname.unwrap_or_default();
let mut new_arr: Vec<String> = sorted;
if array < 0 { let existing: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.get(&aname).and_then(|pm| pm.u_arr.clone()).unwrap_or_default()
};
if existing.len() > new_arr.len() { new_arr.extend(existing.into_iter().skip(new_arr.len())); }
}
crate::ported::params::setaparam(&aname, new_arr);
} else {
if let Ok(mut pp) = PPARAMS.lock() {
*pp = sorted; }
}
crate::ported::mem::unqueue_signals(); 0 }
pub fn bin_pwd(_name: &str, _argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let chaselinks = crate::ported::zsh_h::isset(crate::ported::options::optlookup("chaselinks"));
if OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'P') || (chaselinks && !OPT_ISSET(ops, b'L')) {
println!("{}", crate::ported::utils::zgetcwd().unwrap_or_default()); } else {
let pwd = crate::ported::params::getsparam("PWD")
.unwrap_or_else(|| crate::ported::utils::zgetcwd().unwrap_or_default());
println!("{}", pwd); }
0 }
pub fn bin_dirs(_name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
crate::ported::mem::queue_signals(); if (argv.is_empty() && !OPT_ISSET(ops, b'c')) || OPT_ISSET(ops, b'v')
|| OPT_ISSET(ops, b'p')
{
let mut pos = 1; let fmt: &str = if OPT_ISSET(ops, b'v') { print!("0\t"); "\n{}\t" } else if OPT_ISSET(ops, b'p') { "\n"
} else {
" "
};
let pwd = crate::ported::params::getsparam("PWD")
.unwrap_or_else(|| crate::ported::utils::zgetcwd().unwrap_or_default());
if OPT_ISSET(ops, b'l') { print!("{}", pwd); } else {
print!("{}", crate::ported::utils::fprintdir(&pwd)); }
if let Ok(stack) = DIRSTACK.lock() { for entry in stack.iter() {
if fmt == "\n{}\t" {
print!("\n{}\t", pos);
} else {
print!("{}", fmt); }
pos += 1; if OPT_ISSET(ops, b'l') { print!("{}", entry); } else {
print!("{}", crate::ported::utils::fprintdir(entry)); }
}
}
crate::ported::mem::unqueue_signals(); println!(); return 0; }
if let Ok(mut stack) = DIRSTACK.lock() {
stack.clear(); for arg in argv {
stack.push(arg.clone()); }
}
crate::ported::mem::unqueue_signals(); 0 }
pub fn set_pwd_env() { let pwd = crate::ported::params::getsparam("PWD")
.or_else(|| std::env::current_dir().ok()
.map(|p| p.to_string_lossy().into_owned()));
if let Some(s) = pwd {
crate::ported::params::setsparam("PWD", &s); std::env::set_var("PWD", &s);
}
if let Some(s) = crate::ported::params::getsparam("OLDPWD") {
std::env::set_var("OLDPWD", &s);
}
}
pub fn bin_cd(nam: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let prev = DOPRINTDIR.load(Ordering::Relaxed);
DOPRINTDIR.store(if prev == -1 { 1 } else { 0 }, Ordering::Relaxed);
let chase = OPT_ISSET(ops, b'P') || (crate::ported::zsh_h::isset(crate::ported::options::optlookup("chaselinks"))
&& !OPT_ISSET(ops, b'L'));
CHASINGLINKS.store(chase as i32, Ordering::Relaxed);
crate::ported::mem::queue_signals();
let pwd = crate::ported::params::getsparam("PWD")
.unwrap_or_else(|| crate::ported::utils::zgetcwd().unwrap_or_default());
if let Ok(mut d) = crate::ported::modules::parameter::DIRSTACK.lock() {
d.insert(0, pwd); }
let dest = cd_get_dest(nam, argv, OPT_ISSET(ops, b's'), func);
if dest.is_none() { if let Ok(mut d) = crate::ported::modules::parameter::DIRSTACK.lock() {
if !d.is_empty() { d.remove(0); } }
crate::ported::mem::unqueue_signals(); return 1; }
let dest_path = dest.unwrap();
let old = crate::ported::params::getsparam("PWD");
if std::env::set_current_dir(&dest_path).is_err() {
if let Ok(mut d) = crate::ported::modules::parameter::DIRSTACK.lock() {
if !d.is_empty() { d.remove(0); }
}
crate::ported::mem::unqueue_signals();
return 1;
}
if let Some(o) = old { crate::ported::params::setsparam("OLDPWD", &o);
std::env::set_var("OLDPWD", &o);
}
let chase = CHASINGLINKS.load(std::sync::atomic::Ordering::Relaxed) != 0; let pwd: String = if chase { match std::env::current_dir() {
Ok(c) => c.to_string_lossy().into_owned(),
Err(_) => dest_path.clone(),
}
} else {
dest_path.clone() };
crate::ported::params::setsparam("PWD", &pwd);
std::env::set_var("PWD", &pwd);
cd_new_pwd(func, 0, OPT_ISSET(ops, b'q') as i32);
crate::ported::mem::unqueue_signals(); 0 }
pub fn cd_get_dest(nam: &str, argv: &[String], _hard: bool, func: i32) -> Option<String> {
if argv.is_empty() { if func == BIN_POPD {
let depth = DIRSTACK.lock().map(|d| d.len()).unwrap_or(0);
if depth < 2 { crate::ported::utils::zwarnnam(nam, "directory stack empty"); return None; }
return DIRSTACK.lock().ok()
.and_then(|d| d.get(1).cloned());
}
if func == BIN_PUSHD {
let pushdtohome = crate::ported::zsh_h::isset(crate::ported::options::optlookup("pushdtohome"));
if !pushdtohome { return DIRSTACK.lock().ok()
.and_then(|d| d.get(1).cloned());
}
}
match crate::ported::params::getsparam("HOME") {
Some(h) if !h.is_empty() => Some(h), _ => {
crate::ported::utils::zwarnnam(nam, "HOME not set"); None }
}
} else if argv.len() == 1 { let arg = &argv[0];
DOPRINTDIR.fetch_add(1, Ordering::Relaxed); let posixcd = crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixcd"));
if !posixcd && arg.len() > 1
&& (arg.starts_with('+') || arg.starts_with('-'))
&& arg[1..].chars().all(|c| c.is_ascii_digit())
{
let dd: usize = arg[1..].parse().unwrap_or(0); let pushdminus = crate::ported::zsh_h::isset(crate::ported::options::optlookup("pushdminus"));
let from_top = (arg.starts_with('+')) ^ pushdminus; return DIRSTACK.lock().ok().and_then(|d| {
if from_top { d.get(dd).cloned() }
else if d.len() > dd { d.get(d.len() - 1 - dd).cloned() }
else { None }
});
}
if arg == "-" { DOPRINTDIR.fetch_sub(1, Ordering::Relaxed);
crate::ported::params::getsparam("OLDPWD")
} else {
Some(arg.clone()) }
} else {
let pwd = crate::ported::params::getsparam("PWD")
.unwrap_or_else(|| crate::ported::utils::zgetcwd().unwrap_or_default());
let pat = &argv[0];
let new_pat = &argv[1];
match pwd.find(pat.as_str()) { None => {
crate::ported::utils::zwarnnam(nam,
&format!("string not in pwd: {}", pat)); None }
Some(idx) => {
let mut out = String::new();
out.push_str(&pwd[..idx]); out.push_str(new_pat); out.push_str(&pwd[idx + pat.len()..]); DOPRINTDIR.fetch_add(1, Ordering::Relaxed);
Some(out)
}
}
}
}
pub fn cd_do_chdir(_cnam: &str, dest: &str, _hard: i32) -> Option<String> { match std::env::set_current_dir(dest) { Ok(_) => Some(dest.to_string()), Err(_) => None, }
}
pub fn cd_able_vars(s: &str) -> Option<String> { let cdablevars = crate::ported::zsh_h::isset(crate::ported::options::optlookup("cdablevars"));
if !cdablevars { return None;
}
let (head, tail) = match s.find('/') { Some(i) => (&s[..i], &s[i..]),
None => (s, ""),
};
if head.is_empty() {
return None;
}
crate::ported::params::getsparam(head)
.map(|val| format!("{}{}", val, tail))
}
#[allow(unused_variables)]
pub fn cd_try_chdir(pfix: &str, dest: &str, hard: i32) -> Option<String> { let buf = if pfix.is_empty() {
dest.to_string()
} else if pfix.ends_with('/') {
format!("{}{}", pfix, dest)
} else {
format!("{}/{}", pfix, dest) };
match std::env::set_current_dir(&buf) { Ok(_) => Some(buf),
Err(_) => None, }
}
pub fn cd_new_pwd(_func: i32, _dir: usize, _quiet: i32) { let _old = crate::ported::params::getsparam("PWD");
if let Ok(cwd) = std::env::current_dir() {
if let Some(s) = cwd.to_str() {
let _ = s;
}
}
}
pub fn printdirstack() { let pwd = crate::ported::params::getsparam("PWD")
.or_else(|| std::env::current_dir().ok()
.and_then(|p| p.to_str().map(String::from)))
.unwrap_or_default();
print!("{}", crate::ported::utils::fprintdir(&pwd)); if let Ok(d) = DIRSTACK.lock() {
for entry in d.iter() { print!(" {}",
crate::ported::utils::fprintdir(entry)); }
}
println!(); }
pub fn fixdir(src: &str) -> String { if src.is_empty() {
return String::new();
}
let abs = src.starts_with('/');
let mut components: Vec<&str> = Vec::new();
for seg in src.split('/') {
match seg {
"" => continue, "." => continue, ".." => {
if let Some(last) = components.last() {
if *last == ".." {
components.push("..");
} else {
components.pop();
}
} else if !abs {
components.push("..");
}
}
other => components.push(other),
}
}
let body = components.join("/");
if abs {
format!("/{}", body)
} else if body.is_empty() {
".".to_string()
} else {
body
}
}
pub fn printqt(str: &str) { let rcquotes = crate::ported::zsh_h::isset(crate::ported::options::optlookup("rcquotes")); for ch in str.chars() { if ch == '\'' { print!("{}", if rcquotes { "''" } else { "'\\''" }); } else {
print!("{}", ch); }
}
}
pub fn printif(str: Option<&str>, c: u8) { if let Some(s) = str { print!(" -{} ", c as char); print!("{}", s); }
}
pub fn bin_fc(nam: &str, argv: &[String], ops: &mut crate::ported::zsh_h::options, func: i32) -> i32 {
let mut argv = argv.to_vec();
let mut first: i64 = -1;
let mut last: i64 = -1;
let mut asgf: Vec<(String, String)> = Vec::new();
if OPT_ISSET(ops, b'p') { let mut hf = "".to_string();
let mut hs: i64; let mut shs: i64; let level: i32 = if OPT_ISSET(ops, b'a') {
LOCALLEVEL.load(std::sync::atomic::Ordering::Relaxed)
} else { -1 };
hs = crate::ported::hist::histsiz.load(Ordering::Relaxed); shs = crate::ported::hist::savehistsiz.load(Ordering::Relaxed);
if !argv.is_empty() { hf = argv.remove(0); if !argv.is_empty() { let s2 = argv.remove(0);
match s2.parse::<i64>() { Ok(n) => hs = n,
Err(_) => {
crate::ported::utils::zwarnnam("fc", "HISTSIZE must be an integer");
return 1; }
}
if !argv.is_empty() { let s3 = argv.remove(0);
match s3.parse::<i64>() { Ok(n) => shs = n,
Err(_) => {
crate::ported::utils::zwarnnam("fc", "SAVEHIST must be an integer");
return 1; }
}
} else {
shs = hs; }
if !argv.is_empty() { crate::ported::utils::zwarnnam("fc", "too many arguments");
return 1; }
}
}
crate::ported::hist::pushhiststack(Some(&hf), hs, shs, level); if !hf.is_empty() { let stat_result = std::fs::metadata(&hf);
let should_read = match &stat_result {
Ok(_) => true, Err(e) => e.raw_os_error() != Some(libc::ENOENT), };
if should_read { crate::ported::hist::readhistfile( Some(&hf), 1, HFILE_USE_OPTIONS as i32);
}
}
return 0; }
if OPT_ISSET(ops, b'P') { if !argv.is_empty() { crate::ported::utils::zwarnnam("fc", "too many arguments"); return 1; }
let popped = crate::ported::hist::saveandpophiststack(
-1, HFILE_USE_OPTIONS as i32); return if popped != 0 { 0 } else { 1 }; }
let mut pprog: Option<crate::ported::pattern::PatProg> = None;
if !argv.is_empty() && OPT_ISSET(ops, b'm') { let pat = argv.remove(0);
match crate::ported::pattern::patcompile(&pat, crate::ported::zsh_h::PAT_HEAPDUP, None) {
Some(p) => pprog = Some(p),
None => {
crate::ported::utils::zwarnnam(nam, "invalid match pattern"); return 1; }
}
}
crate::ported::mem::queue_signals();
if OPT_ISSET(ops, b'R') { let path = argv.first().cloned();
let flags = if OPT_ISSET(ops, b'I') { HFILE_SKIPOLD as i32 } else { 0 };
crate::ported::hist::readhistfile( path.as_deref(), 1, flags);
crate::ported::mem::unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'W') { let path = argv.first().cloned();
let flags = if OPT_ISSET(ops, b'I') { HFILE_SKIPOLD as i32 } else { 0 };
crate::ported::hist::savehistfile( path.as_deref(), flags);
crate::ported::mem::unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'A') { let path = argv.first().cloned();
let mut flags = HFILE_APPEND as i32;
if OPT_ISSET(ops, b'I') { flags |= HFILE_SKIPOLD as i32; } crate::ported::hist::savehistfile( path.as_deref(), flags);
crate::ported::mem::unqueue_signals(); return 0; }
if crate::ported::builtins::sched::zleactive.load( std::sync::atomic::Ordering::Relaxed) != 0 {
crate::ported::mem::unqueue_signals(); crate::ported::utils::zwarnnam(nam, "no interactive history within ZLE");
return 1; }
while !argv.is_empty() && argv[0].contains('=') { let arg = argv.remove(0);
if let Some(eq) = arg.find('=') {
let n = &arg[..eq];
let v = &arg[eq + 1..];
if n.is_empty() {
crate::ported::utils::zwarnnam(nam,
&format!("invalid replacement pattern: ={}", v)); return 1;
}
asgf.push((n.to_string(), v.to_string())); }
}
if !argv.is_empty() { first = fcgetcomm(&argv.remove(0)); if first == -1 {
crate::ported::mem::unqueue_signals();
return 1; }
}
if !argv.is_empty() { last = fcgetcomm(&argv.remove(0)); if last == -1 {
crate::ported::mem::unqueue_signals();
return 1;
}
}
if !argv.is_empty() { crate::ported::mem::unqueue_signals();
crate::ported::utils::zwarnnam("fc", "too many arguments"); return 1;
}
let curhist: i64 = crate::ported::params::getiparam("HISTCMD");
if last == -1 { if OPT_ISSET(ops, b'l') && first < curhist { last = curhist; if last < 1 { last = 1; } } else {
last = first; }
}
if first == -1 { let _xflags = if OPT_ISSET(ops, b'L') { HIST_FOREIGN } else { 0 }; first = if OPT_ISSET(ops, b'l') { (curhist - 16).max(1) } else { (curhist - 1).max(1) };
if last < first { last = first; } }
let mut retval;
if OPT_ISSET(ops, b'l') { retval = fclist(&mut std::io::stdout(), ops, first, last,
&asgf, None, 0);
crate::ported::mem::unqueue_signals();
} else {
retval = 1; let fil_opt = crate::ported::utils::gettempfile(Some("zshfc")); match fil_opt {
None => { crate::ported::mem::unqueue_signals(); crate::ported::utils::zwarnnam("fc", &format!("can't open temp file: {}",
std::io::Error::last_os_error()));
}
Some((fd, fil)) => {
unsafe { libc::close(fd); } if last >= curhist { last = curhist - 1; if first > last { crate::ported::mem::unqueue_signals(); crate::ported::utils::zwarnnam("fc", "current history line would recurse endlessly, aborted");
let _ = std::fs::remove_file(&fil); return 1; }
}
ops.ind[b'n' as usize] = 1; let out = std::fs::OpenOptions::new()
.create(true).write(true).truncate(true).open(&fil).ok();
let listed = if let Some(mut f) = out { fclist(&mut f, ops, first, last, &asgf, None, 1)
} else { 1 };
if listed == 0 { let editor: String = if func == BIN_R || OPT_ISSET(ops, b's') {
"-".to_string() } else if OPT_HASARG(ops, b'e') { OPT_ARG(ops, b'e').unwrap_or("").to_string() } else {
crate::ported::params::getsparam("FCEDIT")
.or_else(|| crate::ported::params::getsparam("EDITOR"))
.unwrap_or_else(||
crate::ported::config_h::DEFAULT_FCEDIT.to_string())
};
crate::ported::mem::unqueue_signals(); if fcedit(&editor, &fil) != 0 { if crate::ported::input::stuff(&fil) != 0 { crate::ported::utils::zwarnnam("fc", &format!("{}: {}",
std::io::Error::last_os_error(), fil));
} else {
retval = LASTVAL.load( std::sync::atomic::Ordering::Relaxed);
}
}
} else {
crate::ported::mem::unqueue_signals(); }
let _ = std::fs::remove_file(&fil); }
}
}
let _ = pprog;
retval }
pub fn fcgetcomm(s: &str) -> i64 { let trimmed = s.trim_start();
let numeric = trimmed.parse::<i64>().ok();
let is_zero_prefix = trimmed.starts_with('0');
if let Some(mut cmd) = numeric {
if cmd != 0 || is_zero_prefix {
if cmd < 0 {
let curh = crate::ported::hist::curhist.load(
std::sync::atomic::Ordering::Relaxed);
cmd = crate::ported::hist::addhistnum(curh, cmd as i32, 1);
}
if cmd < 0 { cmd = 0;
}
return cmd;
}
}
match crate::ported::hist::hcomsearch(s) {
Some(n) => n,
None => {
crate::ported::utils::zwarnnam(
"fc", &format!("event not found: {}", s));
-1
}
}
}
pub fn fcsubs(sp: &mut String, sub: &[(String, String)]) -> i32 { let mut subbed = 0i32; for (old, new) in sub { if old.is_empty() {
continue;
}
let count = sp.matches(old.as_str()).count() as i32; if count > 0 {
*sp = sp.replace(old.as_str(), new); subbed += count;
}
}
subbed
}
#[allow(clippy::too_many_arguments)]
pub fn fclist(out: &mut dyn std::io::Write, ops: &crate::ported::zsh_h::options,
mut first: i64, mut last: i64,
subs: &[(String, String)],
pprog: Option<&str>,
is_command: i32) -> i32 {
use std::io::Write;
if OPT_ISSET(ops, b'r') {
std::mem::swap(&mut first, &mut last);
}
if is_command != 0 && first > last {
crate::ported::utils::zwarnnam(
"fc",
"history events can't be executed backwards, aborted",
);
return 1;
}
let near = if first < last { 1 } else { -1 };
let start_ev = match crate::ported::hist::gethistent(first, near) {
Some(e) => e,
None => {
crate::ported::utils::zwarnnam(
"fc",
if first == last {
"no such event"
} else {
"no events in that range"
},
);
return 1;
}
};
let want_time = OPT_ISSET(ops, b'd') || OPT_ISSET(ops, b'f')
|| OPT_ISSET(ops, b'E') || OPT_ISSET(ops, b'i')
|| OPT_ISSET(ops, b't');
let tdfmt: Option<&'static str> = if !want_time {
None
} else if OPT_ISSET(ops, b't') {
Some("%H:%M") } else if OPT_ISSET(ops, b'i') {
Some("%Y-%m-%d %H:%M")
} else if OPT_ISSET(ops, b'E') {
Some("%d.%m.%Y %H:%M")
} else if OPT_ISSET(ops, b'f') {
Some("%m/%d/%Y %H:%M")
} else {
Some("%H:%M")
};
let mut ev = start_ev;
let step: i64 = if first < last { 1 } else { -1 };
loop {
let entry = match crate::ported::hist::quietgethist(ev) {
Some(e) => e,
None => break,
};
let line = entry.node.nam.clone();
if let Some(pat) = pprog {
let prog = crate::ported::pattern::patcompile(pat, 0, None);
let matched = prog.as_ref()
.map(|p| crate::ported::pattern::pattry(p, &line))
.unwrap_or(true);
if !matched {
if ev == last { break; }
ev += step;
continue;
}
}
let mut text = line;
for (old, new) in subs.iter() {
if old.is_empty() { continue; }
text = text.replace(old.as_str(), new.as_str());
}
if is_command == 0 {
if !OPT_ISSET(ops, b'n') {
let _ = write!(out, "{:>5}", ev);
if OPT_ISSET(ops, b'D') {
let _ = write!(out, "{:>10}", entry.stim - entry.ftim);
}
if let Some(fmt) = tdfmt {
let formatted: Option<String> = (|| {
let mut tm: libc::tm = unsafe { std::mem::zeroed() };
let t: libc::time_t = entry.stim as libc::time_t;
let cfmt = std::ffi::CString::new(fmt).ok()?;
unsafe {
if libc::localtime_r(&t, &mut tm).is_null() {
return None;
}
let mut buf = vec![0u8; 256];
let n = libc::strftime(
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
cfmt.as_ptr(),
&tm,
);
if n == 0 { return None; }
buf.truncate(n);
String::from_utf8(buf).ok()
}
})();
if let Some(s) = formatted {
let _ = write!(out, " {}", s);
} else {
let _ = write!(out, " {}", entry.stim);
}
}
let _ = write!(out, " ");
}
}
let _ = writeln!(out, "{}", text);
if ev == last { break; }
ev += step;
if ev < 0 { break; }
}
0 }
pub fn fcedit(ename: &str, fn_: &str) -> i32 { if ename == "-" { return 1; }
let status = std::process::Command::new(ename) .arg(fn_)
.status();
match status {
Ok(s) => s.code().unwrap_or(1),
Err(_) => 1,
}
}
pub fn getasg(argvp: &mut Vec<String>, assigns: &mut Vec<(String, String)>) -> Option<(String, String)> {
if argvp.is_empty() { if !assigns.is_empty() { return Some(assigns.remove(0)); }
return None; }
let s = argvp.remove(0);
if s.starts_with('=') { crate::ported::utils::zerr("bad assignment"); return None; }
match s.find('=') { Some(i) => {
Some((s[..i].to_string(), s[i + 1..].to_string())) }
None => {
Some((s, String::new())) }
}
}
pub fn typeset_setbase(name: &str, pm: *mut crate::ported::zsh_h::param, ops: &crate::ported::zsh_h::options,
on: i32, always: i32) -> i32 {
let mut arg: Option<&str> = None; let on_u = on as u32;
if (on_u & PM_INTEGER) != 0 && OPT_HASARG(ops, b'i') { arg = OPT_ARG(ops, b'i'); } else if (on_u & PM_EFLOAT) != 0 && OPT_HASARG(ops, b'E') { arg = OPT_ARG(ops, b'E'); } else if (on_u & PM_FFLOAT) != 0 && OPT_HASARG(ops, b'F') { arg = OPT_ARG(ops, b'F'); }
if let Some(a) = arg { let base = match a.trim().parse::<i32>() {
Ok(b) => b,
Err(_) => {
if (on_u & PM_INTEGER) != 0 {
crate::ported::utils::zwarnnam(name, &format!("bad base value: {}", a)); } else {
crate::ported::utils::zwarnnam(name, &format!("bad precision value: {}", a)); }
return 1; }
};
if (on_u & PM_INTEGER) != 0 && (base < 2 || base > 36) { crate::ported::utils::zwarnnam(name, &format!("invalid base (must be 2 to 36 inclusive): {}", base)); return 1; }
if !pm.is_null() {
unsafe { (*pm).base = base; } }
} else if always != 0 { if !pm.is_null() {
unsafe { (*pm).base = 0; } }
}
0 }
pub fn typeset_setwidth(name: &str, pm: *mut crate::ported::zsh_h::param, ops: &crate::ported::zsh_h::options,
on: i32, always: i32) -> i32 {
let mut arg: Option<&str> = None; let on_u = on as u32;
if (on_u & PM_LEFT) != 0 && OPT_HASARG(ops, b'L') { arg = OPT_ARG(ops, b'L'); } else if (on_u & PM_RIGHT_B) != 0 && OPT_HASARG(ops, b'R') { arg = OPT_ARG(ops, b'R'); } else if (on_u & PM_RIGHT_Z) != 0 && OPT_HASARG(ops, b'Z') { arg = OPT_ARG(ops, b'Z'); }
if let Some(a) = arg { let width = match a.trim().parse::<i32>() {
Ok(w) => w,
Err(_) => {
crate::ported::utils::zwarnnam(name, &format!("bad width value: {}", a)); return 1; }
};
if !pm.is_null() {
unsafe { (*pm).width = width; } }
} else if always != 0 { if !pm.is_null() {
unsafe { (*pm).width = 0; } }
}
0 }
#[allow(clippy::too_many_arguments)]
pub fn typeset_single(cname: &str, pname: &str, pm: *mut crate::ported::zsh_h::param,
func: i32, mut on: i32, mut off: i32, _roff: i32,
asg: *mut crate::ported::zsh_h::asgment,
altpm: *mut crate::ported::zsh_h::param,
ops: &crate::ported::zsh_h::options,
_joinchar: i32)
-> *mut crate::ported::zsh_h::param {
use crate::ported::zsh_h::{
ASG_ARRAYP, ASG_VALUEP, OPT_ISSET, OPT_MINUS, OPT_PLUS,
PM_ARRAY, PM_AUTOLOAD, PM_DECLARED, PM_EXPORTED, PM_HASHED,
PM_HIDE, PM_LOCAL, PM_NAMEREF, PM_READONLY, PM_TYPE, PM_UNSET,
POSIXBUILTINS, isset,
};
let mut usepm: i32; let mut tc: i32 = 0; let _keeplocal: i32 = 0; let mut newspecial: i32 = 0; let _readonly: i32 = 0; let _dont_set: i32 = 0; let mut pname_owned: String = pname.to_string();
let pm_ref = unsafe { pm.as_mut() };
if let Some(pm_r) = &pm_ref {
let pm_flags = pm_r.node.flags as u32;
let locallevel_v = crate::ported::params::locallevel.load(std::sync::atomic::Ordering::Relaxed);
if (pm_flags & PM_NAMEREF) != 0
&& ((off | on) as u32 & PM_NAMEREF) == 0
&& (pm_r.level == locallevel_v || (on as u32 & PM_LOCAL) == 0)
{
let unresolved_flags = pm_r.node.flags as u32;
let extra_on_mask = !(PM_NAMEREF | PM_LOCAL | PM_READONLY) as i32;
if (pm_flags & PM_NAMEREF) != 0
&& ((unresolved_flags & PM_UNSET) == 0
|| (unresolved_flags & PM_DECLARED) != 0)
&& (on & extra_on_mask) != 0
{
if pm_r.width != 0 { crate::ported::utils::zwarnnam(cname, &format!("{}: can't change type via subscript reference", pname));
} else {
crate::ported::utils::zwarnnam(cname, &format!("{}: can't change type of a named reference", pname));
}
return std::ptr::null_mut(); }
}
}
let pm_flags = pm_ref.as_ref().map(|p| p.node.flags as u32).unwrap_or(0);
usepm = if pm_ref.is_some()
&& ((pm_flags & PM_UNSET) == 0
|| OPT_ISSET(ops, b'p')
|| (isset(POSIXBUILTINS)
&& (pm_flags & (PM_READONLY | PM_EXPORTED)) != 0))
{
1
} else {
0
};
if usepm == 0 && pm_ref.is_some() && (pm_flags & crate::ported::zsh_h::PM_SPECIAL) != 0 {
usepm = 2; }
let pm_level = pm_ref.as_ref().map(|p| p.level).unwrap_or(0);
let locallevel_v = crate::ported::params::locallevel.load(std::sync::atomic::Ordering::Relaxed);
if usepm != 0 && locallevel_v != pm_level && (on as u32 & PM_LOCAL) != 0 { if (pm_flags & crate::ported::zsh_h::PM_SPECIAL) != 0 && (on as u32 & PM_HIDE) == 0
&& (pm_flags & PM_HIDE & !off as u32) == 0
{
newspecial = 1; }
usepm = 0; }
let asg_ref = unsafe { asg.as_ref() };
tc = 0;
if let Some(a) = asg_ref {
if ASG_ARRAYP(a) && PM_TYPE(on as u32) == crate::ported::zsh_h::PM_SCALAR
&& !(usepm != 0 && (PM_TYPE(pm_flags) & (PM_ARRAY | PM_HASHED)) != 0)
{
on |= PM_ARRAY as i32; }
if usepm != 0 && ASG_ARRAYP(a) && newspecial == 0 && PM_TYPE(pm_flags) != PM_ARRAY
&& PM_TYPE(pm_flags) != PM_HASHED
{
if (on as u32 & (crate::ported::zsh_h::PM_EFLOAT
| crate::ported::zsh_h::PM_FFLOAT
| crate::ported::zsh_h::PM_INTEGER)) != 0
{
crate::ported::utils::zerrnam(cname, &format!("{}: can't assign array value to non-array", pname));
return std::ptr::null_mut();
}
if (pm_flags & crate::ported::zsh_h::PM_SPECIAL) != 0 { crate::ported::utils::zerrnam(cname, &format!("{}: can't assign array value to non-array special", pname));
return std::ptr::null_mut();
}
tc = 1; usepm = if OPT_MINUS(ops, b'p') { (on as u32 & pm_flags) as i32
} else if OPT_PLUS(ops, b'p') { (off as u32 & pm_flags) as i32
} else {
0 };
}
}
if usepm != 0 || newspecial != 0 {
let chflags = ((off as u32 & pm_flags) | (on as u32 & !pm_flags)) & (crate::ported::zsh_h::PM_INTEGER
| crate::ported::zsh_h::PM_EFLOAT
| crate::ported::zsh_h::PM_FFLOAT
| PM_HASHED | PM_ARRAY | PM_TIED | PM_AUTOLOAD);
if chflags != 0
&& chflags != (crate::ported::zsh_h::PM_EFLOAT | crate::ported::zsh_h::PM_FFLOAT)
{
tc = 1; if OPT_MINUS(ops, b'p') { usepm = (on as u32 & pm_flags) as i32;
} else if OPT_PLUS(ops, b'p') {
usepm = (off as u32 & pm_flags) as i32;
}
}
}
if usepm != 0 || newspecial != 0 {
if (on as u32 & (PM_READONLY | PM_EXPORTED)) != 0 && (usepm == 0 || (pm_flags & PM_UNSET) != 0)
&& asg_ref.is_some_and(|a| !ASG_VALUEP(a))
{
on |= PM_UNSET as i32; } else if usepm != 0 && (pm_flags & PM_READONLY) != 0 && (on as u32 & PM_READONLY) == 0
&& func != BIN_EXPORT
{
crate::ported::utils::zerr(&format!( "read-only variable: {}", pm_ref.as_ref().unwrap().node.nam));
return std::ptr::null_mut();
}
}
if usepm != 0 {
let pm_r = pm_ref.as_ref().unwrap();
if OPT_MINUS(ops, b'p') && on != 0
&& !((on as u32 & pm_flags) != 0
|| ((on as u32 & PM_LOCAL) != 0 && pm_r.level != 0))
{
return std::ptr::null_mut(); }
if OPT_PLUS(ops, b'p') && off != 0 && (off as u32 & pm_flags) == 0 {
return std::ptr::null_mut(); }
if let Some(a) = asg_ref {
let array_assign = (a.flags & crate::ported::zsh_h::ASG_ARRAY) != 0;
let pm_is_arr = (PM_TYPE(pm_flags) & (PM_ARRAY | PM_HASHED)) != 0;
if array_assign && !pm_is_arr { crate::ported::utils::zerrnam(cname, &format!("{}: inconsistent type for assignment", pname));
return std::ptr::null_mut();
}
}
}
if usepm != 0 && on == 0 && asg_ref.is_some_and(|a| !ASG_VALUEP(a)) {
return pm;
}
if tc != 0 && !OPT_ISSET(ops, b'p') {
on |= (!off as u32 & (PM_READONLY | PM_EXPORTED) & pm_flags) as i32; if let Some(pm_r) = pm_ref {
pm_r.node.flags &= !(PM_READONLY as i32); }
if let Some(pm_r) = unsafe { pm.as_mut() } {
crate::ported::params::unsetparam_pm(pm_r, 0, 1);
}
pname_owned = pname.to_string(); }
let _ = (altpm, pname_owned, _keeplocal, _dont_set, _readonly);
pm
}
pub fn bin_typeset(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() {
let ctx = crate::recorder::recorder_ctx_global();
let mut letters = String::new();
let mut tied_mode = false;
for a in argv {
if a.starts_with('-') || a.starts_with('+') {
let body = &a[1..];
letters.push_str(body);
if body.contains('T') { tied_mode = true; }
}
}
let mut attrs = crate::recorder::ParamAttrs::from_flag_chars(&letters);
match func {
crate::ported::builtin::BIN_READONLY => {
attrs.set(crate::recorder::ParamAttrs::SCALAR);
attrs.set(crate::recorder::ParamAttrs::READONLY);
}
_ => {}
}
if func == crate::ported::builtin::BIN_EXPORT {
for a in argv {
if a == "-p" || a.starts_with('-') { continue; }
if let Some((k, v)) = a.split_once('=') {
crate::recorder::emit_export(k, Some(v), ctx.clone());
} else {
crate::recorder::emit_export(a, None, ctx.clone());
}
}
} else {
let is_locallike = matches!(name, "local" | "private");
let inside_function =
LOCALLEVEL.load(std::sync::atomic::Ordering::Relaxed) > 0;
if !is_locallike || !inside_function {
let mut tied_seen = 0usize;
for a in argv {
if a.starts_with('-') || a.starts_with('+') { continue; }
if tied_mode {
tied_seen += 1;
if tied_seen > 2 { break; }
}
if let Some((k, v)) = a.split_once('=') {
crate::recorder::emit_typeset_attrs(k, Some(v), attrs, ctx.clone());
} else {
crate::recorder::emit_typeset_attrs(a, None, attrs, ctx.clone());
}
}
}
}
}
let mut ops = ops.clone();
let mut on: u32 = 0; let mut off: u32 = 0; let returnval: i32 = 0; let mut printflags: i32 = PRINT_WITH_NAMESPACE; let hasargs = !argv.is_empty();
let posix = crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixbuiltins"));
if (func == BIN_READONLY || func == BIN_EXPORT) && posix && hasargs { ops.ind[b'p' as usize] = 0; }
if OPT_ISSET(&ops, b'f') { return bin_functions(name, argv, &ops, func); }
if func == BIN_READONLY && posix && !OPT_PLUS(&ops, b'g') { ops.ind[b'g' as usize] = 1; }
let mut bit: u32 = PM_ARRAY; for ch in TYPESET_OPTSTR.chars() { let optval = ch as u8;
if OPT_MINUS(&ops, optval) { on |= bit; } else if OPT_PLUS(&ops, optval) { off |= bit; } else { bit <<= 1; continue; }
if OPT_MINUS(&ops, b'n')
&& (bit & !(PM_READONLY | PM_UPPER | PM_HIDEVAL)) != 0 {
crate::ported::utils::zwarnnam(name,
&format!("-{} not allowed with -n", ch)); }
bit <<= 1;
}
if OPT_MINUS(&ops, b'n') { if (on | off) & !(PM_READONLY | PM_UPPER | PM_HIDEVAL) != 0 { return 1; }
on |= PM_NAMEREF; } else if OPT_PLUS(&ops, b'n') { off |= PM_NAMEREF; }
let roff = off;
if (on & PM_FFLOAT) != 0 { off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_EFLOAT; on &= !PM_EFLOAT; }
if (on & PM_EFLOAT) != 0 { off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_FFLOAT; }
if (on & PM_INTEGER) != 0 { off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_EFLOAT | PM_FFLOAT; }
if (on & (PM_LEFT | PM_RIGHT_Z)) != 0 { off |= PM_RIGHT_B; }
if (on & PM_RIGHT_B) != 0 { off |= PM_LEFT | PM_RIGHT_Z; }
if (on & PM_UPPER) != 0 { off |= PM_LOWER; } if (on & PM_LOWER) != 0 { off |= PM_UPPER; } if (on & PM_HASHED) != 0 { off |= PM_ARRAY; } if (on & PM_TIED) != 0 { off |= PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_ARRAY | PM_HASHED; }
on &= !off;
crate::ported::mem::queue_signals();
if OPT_ISSET(&ops, b'p') { if posix && !EMULATION(EMULATE_KSH) { printflags |= match func {
BIN_EXPORT => PRINT_POSIX_EXPORT, BIN_READONLY => PRINT_POSIX_READONLY, _ => PRINT_TYPESET, };
} else {
printflags |= PRINT_TYPESET; }
if OPT_HASARG(&ops, b'p') { let arg = OPT_ARG(&ops, b'p').unwrap_or("");
match arg.trim().parse::<i32>() { Ok(1) => printflags |= PRINT_LINE, Ok(0) => {} _ => {
crate::ported::utils::zwarnnam(name,
&format!("bad argument to -p: {}", arg)); crate::ported::mem::unqueue_signals();
return 1; }
}
}
}
if !hasargs { if !OPT_ISSET(&ops, b'm') { printflags &= !PRINT_WITH_NAMESPACE; }
if !OPT_ISSET(&ops, b'p') { if (on | roff) == 0 { printflags |= PRINT_TYPE; }
if roff != 0 || OPT_ISSET(&ops, b'+') { printflags |= PRINT_NAMEONLY; }
}
let mut entries: Vec<(String, String)> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
let f = pm.node.flags as u32;
if (f & crate::ported::zsh_h::PM_UNSET) != 0 {
return false;
}
let on_roff = (on as u32) | (roff as u32);
on_roff == 0 || (f & on_roff) != 0
})
.map(|(k, pm)| {
let v = pm.u_str.clone().unwrap_or_default();
(k.clone(), v)
})
.collect()
};
entries.sort_by(|a, b| {
crate::ported::hashtable::hnamcmp(&a.0, &b.0)
});
for (k, v) in entries {
if (printflags & PRINT_NAMEONLY) != 0 {
println!("{}", k);
} else {
println!("{}={}", k,
crate::ported::utils::quotedzputs(&v));
}
}
crate::ported::mem::unqueue_signals();
return 0; }
let nm0 = name.chars().next().unwrap_or(' ');
if nm0 == 'l' || OPT_PLUS(&ops, b'g') { on |= PM_LOCAL; } else if !OPT_ISSET(&ops, b'g') { if OPT_MINUS(&ops, b'x') { let globalexport = crate::ported::zsh_h::isset(crate::ported::options::optlookup("globalexport"));
let locallevel = LOCALLEVEL.load(std::sync::atomic::Ordering::Relaxed);
if globalexport { ops.ind[b'g' as usize] = 1; } else if locallevel != 0 { on |= PM_LOCAL; }
} else if !(OPT_ISSET(&ops, b'x') || OPT_ISSET(&ops, b'm')) { on |= PM_LOCAL; }
}
let _ = (off, returnval, name);
let is_hashed = (on & PM_HASHED) != 0; let is_array = (on & PM_ARRAY) != 0; for arg in argv {
let arg_name: &str = match arg.find('=') {
Some(i) => &arg[..i],
None => arg.as_str(),
};
if (on & PM_LOCAL) != 0
&& !arg_name.is_empty()
&& !arg_name.starts_with('-')
&& !arg_name.starts_with('+')
{
let kind = if is_hashed { PM_HASHED } else if is_array { PM_ARRAY } else { 0 };
let _ = crate::ported::params::createparam(
arg_name, on as i32 | kind as i32 | PM_LOCAL as i32);
}
if let Some(eq) = arg.find('=') {
let n = &arg[..eq];
let raw_v = &arg[eq + 1..];
let is_paren_init = raw_v.starts_with('(') && raw_v.ends_with(')')
&& raw_v.len() >= 2;
if is_paren_init {
let inner = &raw_v[1..raw_v.len()-1]; let elems: Vec<String> = inner.split_whitespace() .map(String::from)
.collect();
if is_hashed {
let bracket_shape = !elems.is_empty()
&& elems.iter().all(|e| {
e.starts_with('[')
&& e.contains("]=")
});
let mut map: indexmap::IndexMap<String, String>
= indexmap::IndexMap::new();
if bracket_shape {
for e in &elems {
let close = e.find("]=").unwrap();
let k = e[1..close].to_string();
let v = e[close + 2..].to_string();
map.insert(k, v);
}
} else {
let mut it = elems.into_iter(); while let Some(k) = it.next() {
let v = it.next().unwrap_or_default();
map.insert(k, v); }
}
let n_owned = n.to_string();
crate::fusevm_bridge::with_executor(|exec| {
exec.set_assoc(n_owned, map.clone());
});
} else {
let n_owned = n.to_string();
let elems_owned = elems.clone();
crate::fusevm_bridge::with_executor(|exec| {
exec.set_array(n_owned, elems_owned);
});
}
} else {
let lower = (on & PM_LOWER) != 0;
let upper = (on & PM_UPPER) != 0;
let folded: String = if lower {
raw_v.to_lowercase()
} else if upper {
raw_v.to_uppercase()
} else {
raw_v.to_string()
};
crate::ported::params::setsparam(n, &folded); let already_exported = std::env::var_os(n).is_some();
if (on & crate::ported::zsh_h::PM_EXPORTED) != 0 || already_exported {
std::env::set_var(n, &folded); }
let type_mask = (PM_INTEGER | PM_EFLOAT | PM_FFLOAT
| PM_LOWER | PM_UPPER | PM_READONLY) as i32;
let to_set = (on & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT
| PM_LOWER | PM_UPPER | PM_READONLY)) as i32;
if to_set != 0 {
if let Ok(mut tab) = crate::ported::params::paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags = (pm.node.flags & !type_mask) | to_set;
}
}
}
}
} else if is_hashed || is_array {
let n_owned = arg.clone();
crate::fusevm_bridge::with_executor(|exec| {
if is_hashed {
if exec.assoc(&n_owned).is_none() {
exec.set_assoc(n_owned.clone(), indexmap::IndexMap::new());
}
} else if exec.array(&n_owned).is_none() {
exec.set_array(n_owned.clone(), Vec::new());
}
});
} else {
if crate::ported::params::getsparam(arg).is_none() {
crate::ported::params::setsparam(arg, ""); }
}
}
crate::ported::mem::unqueue_signals();
0
}
pub fn eval_autoload(shf: *mut crate::ported::zsh_h::shfunc, name: &str, ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
if shf.is_null() { return 1; }
let shf_mut = unsafe { &mut *shf };
if (shf_mut.node.flags as u32 & PM_UNDEFINED) == 0 { return 1; }
if shf_mut.funcdef.is_some() { shf_mut.funcdef = None; }
if OPT_MINUS(ops, b'X') { let fargv = vec![ crate::ported::utils::quotedzputs(name),
"\"$@\"".to_string(),
];
let p = mkautofn(shf); let _ = p; return bin_eval(name, &fargv, ops, func); }
let mode = if OPT_ISSET(ops, b'k') { 2 } else if OPT_ISSET(ops, b'z') { 0 } else { 1 };
let _d = OPT_ISSET(ops, b'd');
let r = crate::exec::loadautofn(shf, mode, 1, _d as i32); if r == 0 { 1 } else { 0 }
}
pub fn check_autoload(shf: *mut crate::ported::zsh_h::shfunc, name: &str, ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
if OPT_ISSET(ops, b'X') { return eval_autoload(shf, name, ops, func); }
let want_r = OPT_ISSET(ops, b'r');
let want_R = OPT_ISSET(ops, b'R');
if (want_r || want_R) && !shf.is_null() { let shf_mut = unsafe { &mut *shf };
if (shf_mut.node.flags as u32 & PM_UNDEFINED) == 0 {
return 0;
}
if (shf_mut.node.flags as u32 & PM_LOADDIR) != 0
&& shf_mut.filename.is_some()
{
let spec = vec![shf_mut.filename.clone().unwrap_or_default()];
if crate::exec::getfpfunc(&shf_mut.node.nam, &mut None, Some(&spec), 1).is_some() {
return 0; }
if !OPT_ISSET(ops, b'd') { if want_R { crate::ported::utils::zerr(&format!(
"{}: function definition file not found",
shf_mut.node.nam)); return 1; }
return 0; }
}
let mut dir_path: Option<String> = None;
if crate::exec::getfpfunc(&shf_mut.node.nam, &mut dir_path, None, 1).is_some() && dir_path.is_some()
{
let mut old_slot = shf_mut.filename.take();
crate::ported::hashtable::dircache_set(&mut old_slot, None); let dp = dir_path.unwrap();
let mut new_slot: Option<String> = None;
crate::ported::hashtable::dircache_set(&mut new_slot, Some(&dp)); shf_mut.filename = new_slot;
shf_mut.node.flags |= PM_LOADDIR as i32; return 0; }
if want_R { crate::ported::utils::zerr(&format!(
"{}: function definition file not found",
shf_mut.node.nam)); return 1; }
}
0 }
pub fn listusermathfunc(p: &crate::ported::zsh_h::mathfunc) { let mut showargs: i32 = if p.module.is_some() { 3
} else if p.maxargs != if p.minargs != 0 { p.minargs } else { -1 } { 2
} else if p.minargs != 0 { 1
} else {
0 };
let s_suffix = if (p.flags & MFF_STR) != 0 { "s" } else { "" }; print!("functions -M{} {}", s_suffix, p.name); if showargs != 0 { print!(" {}", p.minargs); showargs -= 1; }
if showargs != 0 { print!(" {}", p.maxargs); showargs -= 1; }
if showargs != 0 { print!(" "); print!("{}", crate::ported::utils::quotedzputs(p.module.as_deref().unwrap_or(""))); showargs -= 1; }
println!(); }
pub fn add_autoload_function(shf: *mut crate::ported::zsh_h::shfunc, funcname: &str) {
if shf.is_null() || funcname.is_empty() { return; }
let shf_ref = unsafe { &mut *shf };
let is_abs_path = funcname.starts_with('/') && funcname.len() > 1
&& funcname[1..].contains('/')
&& (shf_ref.node.flags as u32 & PM_UNDEFINED) != 0;
if is_abs_path {
let nam_idx = funcname.rfind('/').unwrap(); let (dir, nam) = if nam_idx == 0 { ("/".to_string(), funcname[1..].to_string()) } else {
(funcname[..nam_idx].to_string(), funcname[nam_idx + 1..].to_string())
};
let mut old_slot = shf_ref.filename.take();
crate::ported::hashtable::dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
crate::ported::hashtable::dircache_set(&mut new_slot, Some(&dir)); shf_ref.filename = new_slot;
shf_ref.node.flags |= (PM_LOADDIR | PM_ABSPATH_USED) as i32; if let Ok(mut t) = shfunctab_table().lock() {
t.insert(nam, shf as usize); }
} else {
let calling_f: Option<String> = {
let stack = crate::ported::modules::parameter::FUNCSTACK
.lock().map(|s| s.clone()).unwrap_or_default();
stack.iter().rev().find(|fs| { FS_FUNC != 0 && !fs.name.is_empty()
&& (shf_ref.node.nam.is_empty() || fs.name != shf_ref.node.nam)
}).map(|fs| fs.name.clone()) };
if let Some(cf) = calling_f { let shf2_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(&cf).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if !shf2_ptr.is_null() {
let shf2 = unsafe { &*shf2_ptr };
let needs = (PM_LOADDIR | PM_ABSPATH_USED) as i32;
if (shf2.node.flags & needs) == needs { if let Some(dir2) = &shf2.filename { let buf = format!("{}/{}", dir2, funcname); if buf.len() <= libc::PATH_MAX as usize { let buf_c = std::ffi::CString::new(buf.clone()).ok();
if let Some(bc) = buf_c {
if unsafe { libc::access(bc.as_ptr(), libc::R_OK) } == 0 { let mut old_slot = shf_ref.filename.take();
crate::ported::hashtable::dircache_set(&mut old_slot, None); let dir2c = dir2.clone();
let mut new_slot: Option<String> = None;
crate::ported::hashtable::dircache_set(&mut new_slot, Some(&dir2c)); shf_ref.filename = new_slot;
shf_ref.node.flags |= (PM_LOADDIR | PM_ABSPATH_USED) as i32; }
}
}
}
}
}
}
if let Ok(mut t) = shfunctab_table().lock() {
t.insert(funcname.to_string(), shf as usize); }
}
}
pub fn bin_functions(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut returnval: i32 = 0; let mut on: u32 = 0; let mut off: u32 = 0; let _pflags: i32 = 0; let _expand: i32 = 0;
if OPT_PLUS(ops, b'u') { off |= PM_UNDEFINED; } else if OPT_MINUS(ops, b'u') || OPT_ISSET(ops, b'X') { on |= PM_UNDEFINED; }
if OPT_MINUS(ops, b'U') { on |= PM_UNALIASED | PM_UNDEFINED; } else if OPT_PLUS(ops, b'U') { off |= PM_UNALIASED; }
if OPT_MINUS(ops, b't') { on |= PM_TAGGED; } else if OPT_PLUS(ops, b't') { off |= PM_TAGGED; }
if OPT_MINUS(ops, b'T') { on |= PM_TAGGED_LOCAL; } else if OPT_PLUS(ops, b'T') { off |= PM_TAGGED_LOCAL; }
if OPT_MINUS(ops, b'W') { on |= PM_WARNNESTED; } else if OPT_PLUS(ops, b'W') { off |= PM_WARNNESTED; }
let mut roff = off; if OPT_MINUS(ops, b'z') { on |= PM_ZSHSTORED; off |= PM_KSHSTORED; } else if OPT_PLUS(ops, b'z') { off |= PM_ZSHSTORED; roff |= PM_ZSHSTORED; }
if OPT_MINUS(ops, b'k') { on |= PM_KSHSTORED; off |= PM_ZSHSTORED; } else if OPT_PLUS(ops, b'k') { off |= PM_KSHSTORED; roff |= PM_KSHSTORED; }
if OPT_MINUS(ops, b'd') { on |= PM_CUR_FPATH; off |= PM_CUR_FPATH; } else if OPT_PLUS(ops, b'd') { off |= PM_CUR_FPATH; roff |= PM_CUR_FPATH; }
let scriptname_missing = crate::ported::utils::scriptname_get().is_none();
if (off & PM_UNDEFINED) != 0 || (OPT_ISSET(ops, b'k') && OPT_ISSET(ops, b'z')) || (OPT_ISSET(ops, b'x') && !OPT_HASARG(ops, b'x')) || (OPT_MINUS(ops, b'X') && (OPT_ISSET(ops, b'm') || scriptname_missing)) || (OPT_ISSET(ops, b'c')
&& (OPT_ISSET(ops, b'x') || OPT_ISSET(ops, b'X') || OPT_ISSET(ops, b'm')))
{
crate::ported::utils::zwarnnam(name, "invalid option(s)"); return 1; }
if OPT_ISSET(ops, b'c') { if argv.len() < 2 || argv.len() > 2 { crate::ported::utils::zwarnnam(name, "-c: requires two arguments"); return 1;
}
let src_name = &argv[0];
let dst_name = &argv[1];
let src_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(src_name.as_str()).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if src_ptr.is_null() { crate::ported::utils::zwarnnam(name,
&format!("no such function: {}", src_name)); return 1;
}
if (unsafe { (*src_ptr).node.flags } as u32 & PM_UNDEFINED) != 0 {
unsafe {
(*src_ptr).funcdef = None;
}
if crate::exec::loadautofn(src_ptr, 1, 0, 0) != 0 {
return 1;
}
}
let src_ref = unsafe { &*src_ptr };
let new_filename = if (src_ref.node.flags as u32 & PM_UNDEFINED) == 0
&& src_ref.filename.is_some()
{
src_ref.filename.clone() } else {
None
};
let _ = new_filename; if dst_name.starts_with("TRAP") { let sigidx = getsigidx(&dst_name[4..]); if sigidx != -1 { if crate::ported::signals::settrap(
sigidx,
None,
crate::ported::zsh_h::ZSIG_FUNC,
) != 0 { return 1; }
crate::ported::jobs::removetrapnode(sigidx); }
}
if let Ok(mut t) = shfunctab_table().lock() {
t.insert(dst_name.clone(), src_ptr as usize); }
return 0; }
let mut expand: i32 = 0; if OPT_ISSET(ops, b'x') { let arg = OPT_ARG(ops, b'x').unwrap_or("");
match arg.trim().parse::<i32>() { Ok(n) => {
expand = n; if expand == 0 { expand = -1; } }
Err(_) => {
crate::ported::utils::zwarnnam(name, "number expected after -x"); return 1; }
}
}
let mut pflags: i32 = 0;
if OPT_PLUS(ops, b'f') || roff != 0 || OPT_ISSET(ops, b'+') { pflags |= crate::ported::zsh_h::PRINT_NAMEONLY; }
if OPT_MINUS(ops, b'M') || OPT_PLUS(ops, b'M') { if on != 0 || off != 0 || pflags != 0
|| OPT_ISSET(ops, b'X') || OPT_ISSET(ops, b'u')
|| OPT_ISSET(ops, b'U') || OPT_ISSET(ops, b'w')
{
crate::ported::utils::zwarnnam(name, "invalid option(s)"); return 1; }
if argv.is_empty() { crate::ported::mem::queue_signals(); if let Ok(table) = crate::ported::module::MATHFUNCS.lock() { for p in table.iter() { if (p.flags & crate::ported::zsh_h::MFF_USERFUNC) != 0 { listusermathfunc(p); }
}
}
crate::ported::mem::unqueue_signals(); return returnval;
} else if OPT_ISSET(ops, b'm') { for arg in argv.iter() {
crate::ported::mem::queue_signals(); if let Some(pprog) = crate::ported::pattern::patcompile(
arg, crate::ported::zsh_h::PAT_STATIC, None,
) { if OPT_PLUS(ops, b'M') { if let Ok(mut table) =
crate::ported::module::MATHFUNCS.lock()
{
table.retain(|p| {
!((p.flags & crate::ported::zsh_h::MFF_USERFUNC) != 0
&& crate::ported::pattern::pattry(&pprog, &p.name))
});
}
} else {
if let Ok(table) = crate::ported::module::MATHFUNCS.lock() {
for p in table.iter() {
if (p.flags & crate::ported::zsh_h::MFF_USERFUNC) != 0
&& crate::ported::pattern::pattry(&pprog, &p.name)
{
listusermathfunc(p);
}
}
}
}
} else { crate::ported::utils::zwarnnam(name, &format!("bad pattern : {}", arg));
returnval = 1; }
crate::ported::mem::unqueue_signals(); }
return returnval;
} else if OPT_PLUS(ops, b'M') { for arg in argv.iter() {
crate::ported::mem::queue_signals(); if let Ok(mut table) = crate::ported::module::MATHFUNCS.lock() {
let idx = table.iter().position(|p| p.name == *arg); if let Some(i) = idx {
if (table[i].flags & crate::ported::zsh_h::MFF_USERFUNC) == 0 {
crate::ported::utils::zwarnnam(name, &format!("+M {}: is a library function", arg));
returnval = 1; } else {
table.remove(i); }
}
}
crate::ported::mem::unqueue_signals(); }
return returnval;
} else {
let mut argv_iter = argv.iter();
let funcname = argv_iter.next().unwrap(); let mut minargs: i32;
let mut maxargs: i32;
if OPT_ISSET(ops, b's') { minargs = 1; maxargs = 1; } else {
minargs = 0; maxargs = -1; }
let bytes = funcname.as_bytes();
let first_bad = bytes.is_empty()
|| (bytes[0] as char).is_ascii_digit()
|| !bytes.iter().all(|&c| c.is_ascii_alphanumeric() || c == b'_');
if first_bad { crate::ported::utils::zwarnnam(name, &format!("-M {}: bad math function name", funcname));
return 1; }
if let Some(arg) = argv_iter.next() { match arg.parse::<i32>() { Ok(n) if n >= 0 => minargs = n, _ => {
crate::ported::utils::zwarnnam(name, &format!("-M: invalid min number of arguments: {}", arg));
return 1; }
}
if OPT_ISSET(ops, b's') && minargs != 1 { crate::ported::utils::zwarnnam(name, "-Ms: must take a single string argument");
return 1; }
maxargs = minargs; }
if let Some(arg) = argv_iter.next() { match arg.parse::<i32>() { Ok(n) if n >= -1 && (n == -1 || n >= minargs) => maxargs = n,
_ => {
crate::ported::utils::zwarnnam(name, &format!("-M: invalid max number of arguments: {}", arg));
return 1; }
}
if OPT_ISSET(ops, b's') && maxargs != 1 { crate::ported::utils::zwarnnam(name, "-Ms: must take a single string argument");
return 1; }
}
let modname = argv_iter.next().cloned(); if argv_iter.next().is_some() { crate::ported::utils::zwarnnam(name, "-M: too many arguments"); return 1; }
let mut flags = crate::ported::zsh_h::MFF_USERFUNC; if OPT_ISSET(ops, b's') { flags |= crate::ported::zsh_h::MFF_STR; }
let new_fn = crate::ported::zsh_h::mathfunc {
next: None, name: funcname.clone(), flags, nfunc: None,
sfunc: None,
module: modname, minargs, maxargs, funcid: 0,
};
crate::ported::mem::queue_signals(); if let Ok(mut table) = crate::ported::module::MATHFUNCS.lock() {
if let Some(i) = table.iter().position(|p| p.name == new_fn.name) {
table.remove(i); }
table.insert(0, new_fn);
}
crate::ported::mem::unqueue_signals(); return returnval;
}
}
if OPT_MINUS(ops, b'X') { if argv.len() > 1 { crate::ported::utils::zwarnnam(name, "-X: too many arguments"); return 1; }
crate::ported::mem::queue_signals(); let funcname: Option<String> = {
let stack = crate::ported::modules::parameter::FUNCSTACK
.lock().map(|s| s.clone()).unwrap_or_default();
stack.iter().rev().find(|fs| !fs.name.is_empty()) .map(|fs| fs.name.clone()) };
let ret;
if funcname.is_none() { crate::ported::utils::zwarnnam(name, "bad autoload"); ret = 1; } else {
let fname = funcname.unwrap();
let shf_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(fname.as_str()).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if !shf_ptr.is_null() { } else {
if let Ok(mut t) = shfunctab_table().lock() {
t.insert(fname.clone(), 0); }
}
if !argv.is_empty() { if !shf_ptr.is_null() {
let shf_mut = unsafe { &mut *shf_ptr };
let mut old_slot = shf_mut.filename.take();
crate::ported::hashtable::dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
crate::ported::hashtable::dircache_set(&mut new_slot, Some(&argv[0])); shf_mut.filename = new_slot;
on |= PM_UNDEFINED >> 9 << 9; }
}
ret = eval_autoload(shf_ptr, &fname, ops, _func); }
crate::ported::mem::unqueue_signals(); return ret;
}
if argv.is_empty() { crate::ported::mem::queue_signals(); if OPT_ISSET(ops, b'U') && !OPT_ISSET(ops, b'u') { on &= !PM_UNDEFINED; }
crate::ported::mem::unqueue_signals(); return returnval;
}
if OPT_ISSET(ops, b'm') { on &= !PM_UNDEFINED; let mut returnval = returnval;
for pat in argv { crate::ported::mem::queue_signals(); let pprog = crate::ported::pattern::patcompile(pat, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
if (on | off) == 0 && !OPT_ISSET(ops, b'X') { crate::ported::hashtable::scanmatchshfunc(
Some(pat),
|nm, _entry| println!("{}", nm),
);
} else {
let names: Vec<String> = shfunctab_table().lock()
.map(|t| t.keys().cloned().collect())
.unwrap_or_default();
for nm in &names {
if !crate::ported::pattern::pattry(&prog, nm) { continue;
}
let shf_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(nm.as_str()).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if shf_ptr.is_null() { continue; }
let shf_mut = unsafe { &mut *shf_ptr };
shf_mut.node.flags = (shf_mut.node.flags
| ((on & !PM_UNDEFINED) as i32)) & !(off as i32); if check_autoload(shf_ptr, &shf_mut.node.nam,
ops, _func) != 0 { returnval = 1; }
}
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", pat)); returnval = 1; }
crate::ported::mem::unqueue_signals(); }
return returnval;
}
let mut returnval = returnval;
crate::ported::mem::queue_signals(); for fname in argv { if OPT_ISSET(ops, b'w') { continue;
}
let shf_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(fname.as_str()).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if !shf_ptr.is_null() { let shf_mut = unsafe { &mut *shf_ptr };
if (on | off) != 0 { shf_mut.node.flags = (shf_mut.node.flags
| ((on & !PM_UNDEFINED) as i32)) & !(off as i32); if check_autoload(shf_ptr, &shf_mut.node.nam, ops, _func) != 0 { returnval = 1; }
} else {
println!("{}", shf_mut.node.nam); }
} else if (on & PM_UNDEFINED) != 0 { let mut sigidx: i32 = -1;
let mut ok = true;
if fname.starts_with("TRAP") { sigidx = getsigidx(&fname[4..]); if sigidx != -1 { crate::ported::jobs::removetrapnode(sigidx); }
}
if fname.starts_with('/') { let base = fname.rsplit('/').next().unwrap_or("");
if !base.is_empty() {
let base_ptr = shfunctab_table().lock()
.ok()
.and_then(|t| t.get(base).copied())
.unwrap_or(0) as *mut crate::ported::zsh_h::shfunc;
if !base_ptr.is_null() {
let bs = unsafe { &mut *base_ptr };
bs.node.flags = (bs.node.flags
| ((on & !PM_UNDEFINED) as i32)) & !(off as i32); if (bs.node.flags as u32 & PM_UNDEFINED) != 0 { let dir = if fname.len() > 1 && base.len() == fname.len() - 1 {
"/".to_string() } else {
fname[..fname.len() - base.len() - 1].to_string() };
let mut old_slot = bs.filename.take();
crate::ported::hashtable::dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
crate::ported::hashtable::dircache_set(&mut new_slot, Some(&dir)); bs.filename = new_slot;
}
if check_autoload(base_ptr, &bs.node.nam, ops, _func) != 0 { returnval = 1;
}
continue; }
}
}
let new_shf = Box::new(crate::ported::zsh_h::shfunc {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: fname.clone(),
flags: on as i32, },
filename: None,
lineno: 0,
funcdef: None,
redir: None,
sticky: None,
body: None,
});
let new_shf_ptr = Box::into_raw(new_shf);
let _ = mkautofn(new_shf_ptr); add_autoload_function(new_shf_ptr, fname); if sigidx != -1 { if crate::ported::signals::settrap(
sigidx,
None,
crate::ported::zsh_h::ZSIG_FUNC,
) != 0 { if let Ok(mut t) = shfunctab_table().lock() {
t.remove(fname);
}
returnval = 1; ok = false; }
}
if ok && check_autoload(new_shf_ptr, &fname, ops, _func) != 0 { returnval = 1; }
} else {
returnval = 1; }
}
crate::ported::mem::unqueue_signals(); let _ = (expand, pflags);
returnval
}
pub fn mkautofn(shf: *mut crate::ported::zsh_h::shfunc) -> *mut crate::ported::zsh_h::eprog { let p = Box::new(eprog {
len: 5 * std::mem::size_of::<u32>() as i32, prog: Vec::new(), strs: None, shf: if shf.is_null() { None } else { Some(unsafe { Box::from_raw(shf) }) },
npats: 0, nref: 1, flags: 0,
pats: Vec::new(),
dump: None,
});
Box::into_raw(p)
}
pub fn bin_unset(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let mut returnval = 0i32; let mut match_count = 0i32;
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() {
let ctx = crate::recorder::recorder_ctx_global();
for a in argv {
if a.starts_with('-') || a == "--" { continue; }
crate::recorder::emit_unset(a, ctx.clone());
}
}
if OPT_ISSET(ops, b'f') { return bin_unhash(name, argv, ops, func); }
if OPT_ISSET(ops, b'm') { for s in argv { crate::ported::mem::queue_signals(); let pprog = crate::ported::pattern::patcompile(s, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
let names: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.keys().cloned().collect()
};
for nm in &names {
if crate::ported::pattern::pattry(&prog, nm) { crate::ported::params::unsetparam(nm); match_count += 1; }
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", s)); returnval = 1; }
crate::ported::mem::unqueue_signals(); }
if match_count == 0 { returnval = 1; }
return returnval; }
crate::ported::mem::queue_signals(); for s in argv { let (nm, subscript) = match s.find('[') { Some(start) if s.ends_with(']') => { (&s[..start], Some(&s[start + 1..s.len() - 1])) }
Some(_) => {
crate::ported::utils::zwarnnam(name,
&format!("{}: invalid parameter name", s)); returnval = 1; continue; }
None => (s.as_str(), None),
};
if nm.is_empty() || !nm.chars().next().map_or(false, |c| c.is_alphabetic() || c == '_')
|| !nm.chars().all(|c| c.is_alphanumeric() || c == '_')
{
crate::ported::utils::zwarnnam(name,
&format!("{}: invalid parameter name", s)); returnval = 1; continue;
}
match subscript { Some(key) => {
let nm_owned = nm.to_string();
let key_owned = key.to_string();
crate::fusevm_bridge::with_executor(|exec| {
if let Some(mut map) = exec.assoc(&nm_owned) {
map.shift_remove(&key_owned); exec.set_assoc(nm_owned.clone(), map);
} else if let Some(mut arr) = exec.array(&nm_owned) {
if let Ok(i) = key_owned.parse::<i32>() {
let idx = if i > 0 { (i - 1) as usize }
else { return; };
if idx < arr.len() {
arr[idx] = String::new();
exec.set_array(nm_owned.clone(), arr);
}
}
}
});
}
None => {
let nm_owned = nm.to_string();
crate::fusevm_bridge::with_executor(|exec| {
exec.unset_scalar(&nm_owned);
exec.unset_array(&nm_owned);
exec.unset_assoc(&nm_owned);
});
let _ = crate::ported::params::paramtab().write().ok().as_deref_mut()
.map(|t| t.remove(nm)); std::env::remove_var(nm); }
}
}
crate::ported::mem::unqueue_signals(); returnval }
pub fn fetchcmdnamnode(hn: *mut crate::ported::zsh_h::hashnode, _printflags: i32) {
let nam = unsafe { (*hn).nam.clone() }; if let Ok(mut m) = MATCHEDNODES.lock() { m.push(nam); } }
pub fn bin_whence(nam: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let mut returnval: i32 = 0;
let mut printflags: i32 = 0;
let mut informed: i32 = 0;
let mut expand: i32 = 0;
let csh = OPT_ISSET(ops, b'c'); let v = OPT_ISSET(ops, b'v'); let all = OPT_ISSET(ops, b'a'); let wd = OPT_ISSET(ops, b'w');
if OPT_ISSET(ops, b'x') { let arg = OPT_ARG(ops, b'x').unwrap_or("");
match arg.trim().parse::<i32>() { Ok(n) => {
expand = n;
if expand == 0 { expand = -1; } }
Err(_) => {
crate::ported::utils::zwarnnam(nam, "number expected after -x"); return 1;
}
}
}
if OPT_ISSET(ops, b'w') { printflags |= PRINT_WHENCE_WORD; } else if OPT_ISSET(ops, b'c') { printflags |= PRINT_WHENCE_CSH; } else if OPT_ISSET(ops, b'v') { printflags |= PRINT_WHENCE_VERBOSE; } else { printflags |= PRINT_WHENCE_SIMPLE; } if OPT_ISSET(ops, b'f') { printflags |= PRINT_WHENCE_FUNCDEF; }
let mut v = v;
let _aliasflags = if func == BIN_COMMAND { if OPT_ISSET(ops, b'V') { printflags = PRINT_WHENCE_VERBOSE; v = true; PRINT_WHENCE_VERBOSE
} else {
printflags = PRINT_WHENCE_SIMPLE; v = false; PRINT_LIST }
} else {
printflags };
if OPT_ISSET(ops, b'm') { if all { if let Ok(mut m) = crate::ported::builtin::MATCHEDNODES.lock() {
m.clear();
}
}
crate::ported::mem::queue_signals(); for pat in argv { let pprog = crate::ported::pattern::patcompile(pat, crate::ported::zsh_h::PAT_HEAPDUP, None);
match pprog {
None => { crate::ported::utils::zwarnnam(nam,
&format!("bad pattern : {}", pat)); returnval = 1; continue;
}
Some(prog) => {
if !OPT_ISSET(ops, b'p') { if let Ok(t) = crate::ported::hashtable::aliastab_lock().read() {
for (n, _a) in t.iter() {
if crate::ported::pattern::pattry(&prog, n) {
println!("{}", n);
informed += 1; }
}
}
let reswords = ["do","done","esac","then","elif","else","fi",
"for","case","if","while","function","repeat",
"time","until","exec","command","select","coproc",
"nocorrect","foreach","end","!","[[","{","}",
"declare","export","float","integer","local",
"private","readonly","typeset"];
for w in &reswords { if crate::ported::pattern::pattry(&prog, w) {
println!("{}", w);
informed += 1; }
}
let names: Vec<String> = crate::ported::builtin::shfunctab_table()
.lock().map(|t| t.keys().cloned().collect())
.unwrap_or_default();
for n in &names {
if crate::ported::pattern::pattry(&prog, n) {
println!("{}", n);
informed += 1; }
}
for b in BUILTINS.iter() {
if crate::ported::pattern::pattry(&prog, &b.node.nam) {
println!("{}", b.node.nam);
informed += 1; }
}
}
if let Some(path) = crate::ported::params::getsparam("PATH") {
for dir in path.split(':') {
if dir.is_empty() { continue; }
if let Ok(rd) = std::fs::read_dir(dir) {
for entry in rd.flatten() {
if let Some(name) = entry.file_name().to_str() {
if crate::ported::pattern::pattry(&prog, name) {
if all {
if let Ok(mut m) =
crate::ported::builtin::MATCHEDNODES.lock() {
m.push(name.to_string());
}
} else {
println!("{}", name);
}
informed += 1; }
}
}
}
}
}
}
}
crate::ported::signals_h::run_queued_signals(); }
crate::ported::mem::unqueue_signals(); if !all { return if returnval != 0 || informed == 0 { 1 } else { 0 }; }
}
crate::ported::mem::queue_signals();
let argv_vec: Vec<String> = if OPT_ISSET(ops, b'm') {
crate::ported::builtin::MATCHEDNODES.lock()
.map(|m| m.clone()).unwrap_or_default()
} else { argv.to_vec() };
for arg in &argv_vec { informed = 0; let mut buf: Option<String> = None;
if !OPT_ISSET(ops, b'p') {
if let Ok(t) = crate::ported::hashtable::aliastab_lock().read() {
if let Some(a) = t.get(arg) { if (printflags & PRINT_WHENCE_WORD as i32) != 0 { println!("{}: alias", a.node.nam);
} else if (printflags & PRINT_WHENCE_CSH as i32) != 0 {
println!("{}: aliased to {}", a.node.nam, a.text);
} else if (printflags & PRINT_WHENCE_VERBOSE as i32) != 0 {
println!("{} is an alias for {}", a.node.nam, a.text);
} else if (printflags & PRINT_LIST as i32) != 0 {
println!("alias {}={}", a.node.nam, a.text);
} else {
println!("{}={}", a.node.nam, a.text);
}
informed = 1; if !all { continue; } }
}
if let Some(idx) = arg.rfind('.') { if idx > 0 && idx + 1 < arg.len() {
let suf = &arg[idx + 1..];
if let Ok(t) = crate::ported::hashtable::sufaliastab_lock().read() {
if let Some(a) = t.get(suf) { println!("{}={}", a.node.nam, a.text); informed = 1; if !all { continue; } }
}
}
}
let reswords = ["do","done","esac","then","elif","else","fi",
"for","case","if","while","function","repeat",
"time","until","exec","command","select","coproc",
"nocorrect","foreach","end","!","[[","{","}",
"declare","export","float","integer","local",
"private","readonly","typeset"];
if reswords.contains(&arg.as_str()) { if (printflags & PRINT_WHENCE_WORD as i32) != 0 {
println!("{}: reserved", arg);
} else if (printflags & PRINT_WHENCE_CSH as i32) != 0 {
println!("{}: shell reserved word", arg);
} else if (printflags & PRINT_WHENCE_VERBOSE as i32) != 0 {
println!("{} is a reserved word", arg);
} else {
println!("{}", arg); }
informed = 1; if !all { continue; } }
if let Ok(t) = crate::ported::builtin::shfunctab_table().lock() {
if t.contains_key(arg) { if (printflags & PRINT_WHENCE_FUNCDEF as i32) != 0 {
let body = crate::ported::utils::getshfunc(arg)
.unwrap_or_else(|| String::from("# body undefined"));
println!("{} () {{\n{}\n}}", arg, body);
} else if (printflags & PRINT_WHENCE_WORD as i32) != 0 {
println!("{}: function", arg);
} else if (printflags & PRINT_WHENCE_CSH as i32) != 0 {
println!("{}: shell function", arg);
} else if (printflags & PRINT_WHENCE_VERBOSE as i32) != 0 {
println!("{} is a shell function", arg);
} else {
println!("{}", arg); }
informed = 1; if !all { continue; } }
}
if BUILTINS.iter().any(|b| b.node.nam == *arg) { if wd {
println!("{}: builtin", arg); } else if csh {
println!("{}: shell built-in command", arg); } else if v {
println!("{} is a shell builtin", arg); } else {
println!("{}", arg); }
informed = 1; if !all { continue; } }
let hashed_path: Option<String> = {
match crate::ported::hashtable::cmdnamtab_lock().read() {
Ok(tab) => tab.get(arg).and_then(|cn| {
if (cn.node.flags & crate::ported::zsh_h::HASHED as i32) != 0 {
cn.cmd.clone() } else {
None
}
}),
Err(_) => None,
}
};
if let Some(p) = hashed_path {
if (printflags & PRINT_LIST) != 0 {
println!("hash {}={}", arg, p);
} else {
println!("{}", p);
}
informed = 1; if !all { continue; } }
}
if all && !arg.starts_with('/') { if let Some(path) = crate::ported::params::getsparam("PATH") {
for dir in path.split(':') {
if dir.is_empty() { continue; }
let full = format!("{}/{}", dir, arg);
let p = std::path::Path::new(&full);
if p.is_file() { if wd {
println!("{}: command", arg);
} else if v && !csh {
print!("{} is ", arg);
println!("{}", crate::ported::utils::quotedzputs(&full));
} else {
println!("{}", full);
}
informed = 1; }
}
}
if !informed != 0 && (wd || v || csh) { println!("{}{}", arg, if wd { ": none" } else { " not found" });
returnval = 1;
}
continue;
}
if func == BIN_COMMAND && OPT_ISSET(ops, b'p') { if BUILTINS.iter().any(|b| b.node.nam == *arg) { println!("{}: builtin", arg); informed = 1;
continue;
}
}
buf = findcmd(arg, 1, (func == BIN_COMMAND && OPT_ISSET(ops, b'p')) as i32);
if let Some(path) = buf { if wd { println!("{}: command", arg); } else if v && !csh { print!("{} is ", arg); println!("{}", crate::ported::utils::quotedzputs(&path)); } else {
println!("{}", path); }
informed = 1; continue;
}
if let Some(cnam) = findcmd(arg, 1, 0) { if wd { println!("{}: command", arg); } else if v && !csh { print!("{} is ", arg); println!("{}", crate::ported::utils::quotedzputs(&cnam)); } else {
println!("{}", cnam); }
informed = 1; continue;
}
if v || csh || wd { println!("{}{}", arg, if wd { ": none" } else { " not found" }); }
returnval = 1; }
crate::ported::mem::unqueue_signals();
returnval | (informed == 0) as i32 }
pub fn bin_hash(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut returnval = 0i32; let mut printflags = 0i32; let dir_mode = OPT_ISSET(ops, b'd');
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() && dir_mode {
let ctx = crate::recorder::recorder_ctx_global();
for a in argv {
if a.starts_with('-') { continue; }
if let Some((k, v)) = a.split_once('=') {
crate::recorder::emit_hash_d(k, v, ctx.clone());
}
}
}
if OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'f') { if !argv.is_empty() { crate::ported::utils::zwarnnam("hash", "too many arguments"); return 1; }
if OPT_ISSET(ops, b'r') { if dir_mode {
crate::ported::hashnameddir::emptynameddirtable();
} else {
crate::ported::hashtable::emptycmdnamtable();
}
}
if OPT_ISSET(ops, b'f') { if dir_mode {
crate::ported::hashnameddir::fillnameddirtable();
} else {
let path_str = crate::ported::params::getsparam("PATH").unwrap_or_default();
let path_arr: Vec<String> =
path_str.split(':').map(|s| s.to_string()).collect();
crate::ported::hashtable::fillcmdnamtable(&path_arr);
}
}
return 0; }
if OPT_ISSET(ops, b'L') { printflags |= PRINT_LIST; }
if argv.is_empty() { crate::ported::mem::queue_signals(); if dir_mode {
if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
for (_n, nd) in t.iter() { crate::ported::hashnameddir::printnameddirnode(nd, printflags);
}
}
} else {
let path_arr: Vec<String> = Vec::new();
if let Ok(t) = crate::ported::hashtable::cmdnamtab_lock().read() {
for (_n, cn) in t.iter() { print!("{}",
crate::ported::hashtable::printcmdnamnode(
cn, &path_arr, printflags as u32));
}
}
}
crate::ported::mem::unqueue_signals(); return 0; }
crate::ported::mem::queue_signals(); let mut idx = 0;
while idx < argv.len() { let arg = &argv[idx];
idx += 1;
if OPT_ISSET(ops, b'm') { let pprog = crate::ported::pattern::patcompile(arg, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
if dir_mode {
if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
for (n, nd) in t.iter() {
if crate::ported::pattern::pattry(&prog, n) { crate::ported::hashnameddir::printnameddirnode(nd, printflags);
}
}
}
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", arg)); returnval = 1; }
continue;
}
let (n, val) = match arg.find('=') {
Some(eq) => (&arg[..eq], Some(&arg[eq + 1..])),
None => (arg.as_str(), None),
};
if let Some(v) = val { if dir_mode { if !n.chars().all(|c| c.is_alphanumeric() || c == '_') { crate::ported::utils::zwarnnam(name,
&format!("invalid character in directory name: {}", n)); returnval = 1; continue; }
let nd = nameddir {
node: hashnode { next: None, nam: n.to_string(), flags: 0 },
dir: v.to_string(),
diff: 0,
};
crate::ported::hashnameddir::addnameddirnode(n, nd); } else {
std::env::set_var(format!("__zshrs_hash_{}", n), v);
}
if OPT_ISSET(ops, b'v') { if dir_mode {
if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
if let Some(nd) = t.get(n) { crate::ported::hashnameddir::printnameddirnode(nd, 0);
}
}
}
}
} else {
if dir_mode {
let snapshot = crate::ported::hashnameddir::nameddirtab()
.lock().ok().and_then(|t| t.get(n).cloned());
match snapshot {
Some(nd) => {
if OPT_ISSET(ops, b'v') { crate::ported::hashnameddir::printnameddirnode(&nd, 0);
}
}
None => {
crate::ported::utils::zwarnnam(name,
&format!("no such directory name: {}", n)); returnval = 1; }
}
} else {
let found = crate::ported::params::getsparam("PATH").is_some_and(|p| {
p.split(':').any(|d|
!d.is_empty() && std::path::Path::new(&format!("{}/{}", d, n)).exists()
)
});
if !found {
crate::ported::utils::zwarnnam(name,
&format!("no such command: {}", n)); returnval = 1; }
}
}
}
crate::ported::mem::unqueue_signals(); returnval }
pub fn bin_unhash(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let mut returnval = 0i32; let mut all = 0i32; let mut match_count = 0i32;
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() && func == crate::ported::builtin::BIN_UNALIAS {
let ctx = crate::recorder::recorder_ctx_global();
for a in argv {
if a.starts_with('-') && a != "-" { continue; }
crate::recorder::emit_unalias(a, ctx.clone());
}
}
enum Tab { CmdNam, NamedDir, Shfunc, Alias, SufAlias }
let tab: Tab;
if func == BIN_UNALIAS { tab = if OPT_ISSET(ops, b's') { Tab::SufAlias } else { Tab::Alias }; if OPT_ISSET(ops, b'a') { if !argv.is_empty() { crate::ported::utils::zwarnnam(name, "-a: too many arguments"); return 1; }
all = 1; } else if argv.is_empty() { crate::ported::utils::zwarnnam(name, "not enough arguments"); return 1; }
} else if OPT_ISSET(ops, b'd') { tab = Tab::NamedDir; } else if OPT_ISSET(ops, b'f') { tab = Tab::Shfunc; } else if OPT_ISSET(ops, b's') { tab = Tab::SufAlias; } else if func == BIN_UNHASH && OPT_ISSET(ops, b'a') { tab = Tab::Alias; } else { tab = Tab::CmdNam; }
let clear_all = |t: &Tab| match t {
Tab::Alias => { let _ = crate::ported::hashtable::aliastab_lock().write().map(|mut g| g.clear()); }
Tab::SufAlias => { let _ = crate::ported::hashtable::sufaliastab_lock().write().map(|mut g| g.clear()); }
Tab::NamedDir => { crate::ported::hashnameddir::emptynameddirtable(); }
Tab::Shfunc => { let _ = shfunctab_table().lock().map(|mut g| g.clear()); }
Tab::CmdNam => { crate::ported::hashtable::emptycmdnamtable(); } };
let remove_one = |t: &Tab, nm: &str| -> bool {
match t {
Tab::Alias => crate::ported::hashtable::aliastab_lock().write()
.map(|mut g| g.remove(nm).is_some()).unwrap_or(false),
Tab::SufAlias => crate::ported::hashtable::sufaliastab_lock().write()
.map(|mut g| g.remove(nm).is_some()).unwrap_or(false),
Tab::NamedDir => crate::ported::hashnameddir::removenameddirnode(nm).is_some(),
Tab::Shfunc => shfunctab_table().lock()
.map(|mut g| g.remove(nm).is_some()).unwrap_or(false),
Tab::CmdNam => crate::ported::hashtable::cmdnamtab_lock().write()
.map(|mut g| g.remove(nm).is_some()).unwrap_or(false),
}
};
if all != 0 { crate::ported::mem::queue_signals(); clear_all(&tab); crate::ported::mem::unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') { for arg in argv { crate::ported::mem::queue_signals(); let pprog = crate::ported::pattern::patcompile(arg, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
let names: Vec<String> = match &tab {
Tab::Alias => crate::ported::hashtable::aliastab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::SufAlias => crate::ported::hashtable::sufaliastab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect()).unwrap_or_default(),
Tab::NamedDir => crate::ported::hashnameddir::nameddirtab().lock()
.map(|t| t.keys().cloned().collect()).unwrap_or_default(),
Tab::Shfunc => shfunctab_table().lock()
.map(|t| t.keys().cloned().collect()).unwrap_or_default(),
Tab::CmdNam => crate::ported::hashtable::cmdnamtab_lock().read()
.map(|t| t.iter().map(|(n,_)| n.clone()).collect())
.unwrap_or_default(),
};
for nm in &names {
if crate::ported::pattern::pattry(&prog, nm) { if remove_one(&tab, nm) {
match_count += 1; }
}
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", arg)); returnval = 1; }
crate::ported::mem::unqueue_signals(); }
if match_count == 0 { returnval = 1; }
return returnval; }
crate::ported::mem::queue_signals(); for arg in argv { if remove_one(&tab, arg) { } else if func == BIN_UNSET
&& crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixbuiltins"))
{
returnval = 0; } else {
crate::ported::utils::zwarnnam(name,
&format!("no such hash table element: {}", arg)); returnval = 1; }
}
crate::ported::mem::unqueue_signals(); returnval }
pub fn bin_alias(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut returnval = 0i32; let mut flags1 = 0u32; let mut flags2 = DISABLED as u32; let mut printflags = 0i32; let mut use_suffix = false;
let type_opts = (OPT_ISSET(ops, b'r') as i32) + (OPT_ISSET(ops, b'g') as i32)
+ (OPT_ISSET(ops, b's') as i32);
if type_opts != 0 { if type_opts > 1 { crate::ported::utils::zwarnnam(name, "illegal combination of options"); return 1; }
if OPT_ISSET(ops, b'g') { flags1 |= ALIAS_GLOBAL as u32; } else {
flags2 |= ALIAS_GLOBAL as u32; }
if OPT_ISSET(ops, b's') { flags1 |= ALIAS_SUFFIX as u32; use_suffix = true; } else {
flags2 |= ALIAS_SUFFIX as u32; }
}
if OPT_ISSET(ops, b'L') { printflags |= PRINT_LIST; } else if OPT_PLUS(ops, b'g') || OPT_PLUS(ops, b'r') || OPT_PLUS(ops, b's')
|| OPT_PLUS(ops, b'm') || OPT_ISSET(ops, b'+') {
printflags |= PRINT_NAMEONLY; }
let print_alias = |a: &Alias, pflags: i32| {
if (pflags & PRINT_NAMEONLY) != 0 {
println!("{}", a.node.nam);
return;
}
if (pflags & PRINT_LIST) != 0 {
print!("alias ");
if (a.node.flags & ALIAS_SUFFIX as i32) != 0 { print!("-s ");
} else if (a.node.flags & ALIAS_GLOBAL as i32) != 0 { print!("-g ");
}
if a.node.nam.starts_with('-') || a.node.nam.starts_with('+') { print!("-- ");
}
}
println!("{}={}", a.node.nam, a.text);
};
if argv.is_empty() { crate::ported::mem::queue_signals(); let lock = if use_suffix { sufaliastab_lock() } else { aliastab_lock() };
if let Ok(t) = lock.read() {
for (_n, a) in t.iter() { if (a.node.flags & flags1 as i32) == flags1 as i32
&& (a.node.flags & flags2 as i32) == 0 {
print_alias(a, printflags);
}
}
}
crate::ported::mem::unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') { for pat in argv { crate::ported::mem::queue_signals(); let pprog = crate::ported::pattern::patcompile(pat, crate::ported::zsh_h::PAT_HEAPDUP, None);
if let Some(prog) = pprog {
let lock = if use_suffix { sufaliastab_lock() } else { aliastab_lock() };
if let Ok(t) = lock.read() {
for (_n, a) in t.iter() { if (a.node.flags & flags1 as i32) == flags1 as i32
&& (a.node.flags & flags2 as i32) == 0
&& crate::ported::pattern::pattry(&prog, &a.node.nam)
{
print_alias(a, printflags);
}
}
}
} else {
crate::ported::utils::zwarnnam(name,
&format!("bad pattern : {}", pat)); returnval = 1; }
crate::ported::mem::unqueue_signals(); }
return returnval; }
crate::ported::mem::queue_signals(); let mut idx = 0;
while idx < argv.len() { let arg = &argv[idx];
idx += 1;
if let Some(eq) = arg.find('=') { if !OPT_ISSET(ops, b'L') { let n = &arg[..eq];
let v = &arg[eq + 1..];
let lock = if use_suffix { sufaliastab_lock() } else { aliastab_lock() };
if let Ok(mut t) = lock.write() {
let a = crate::ported::hashtable::createaliasnode(n, v, flags1); t.add(a);
}
continue;
}
}
let n = if let Some(eq) = arg.find('=') { &arg[..eq] } else { arg.as_str() };
let lock = if use_suffix { sufaliastab_lock() } else { aliastab_lock() };
let found = lock.read().ok().and_then(|t|
t.get_including_disabled(n).map(|a| (a.node.nam.clone(), a.node.flags as u32, a.text.clone()))
);
match found {
Some((nm, fl, txt)) => { let show = type_opts == 0
|| use_suffix
|| (OPT_ISSET(ops, b'r')
&& (fl & (ALIAS_GLOBAL | ALIAS_SUFFIX) as u32) == 0)
|| (OPT_ISSET(ops, b'g')
&& (fl & ALIAS_GLOBAL as u32) != 0);
if show {
let a = crate::ported::hashtable::createaliasnode(&nm, &txt, fl);
print_alias(&a, printflags);
}
}
None => { returnval = 1; }
}
}
crate::ported::mem::unqueue_signals(); returnval }
pub fn bin_true(_name: &str, _argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
0 }
pub fn bin_false(_name: &str, _argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
1 }
pub fn bin_print(name: &str, args: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let nonewline = OPT_ISSET(ops, b'n'); let raw = OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'R'); let one_per_line = OPT_ISSET(ops, b'l'); let _printf_mode = func == BIN_PRINTF || OPT_HASARG(ops, b'f'); let echo_mode = func == BIN_ECHO;
let _ = (name, raw);
let dest_var: Option<String> = if OPT_HASARG(ops, b'v') {
OPT_ARG(ops, b'v').map(String::from)
} else { None };
if _printf_mode {
let fmt = if let Some(f) = OPT_ARG(ops, b'f') {
f.to_string()
} else if !args.is_empty() {
args[0].clone()
} else {
return 0;
};
let rest: &[String] = if OPT_HASARG(ops, b'f') { args } else { &args[1..] };
let out = printf_format(&fmt, rest);
if let Some(ref v) = dest_var {
crate::ported::params::setsparam(v, &out);
} else {
print!("{}", out);
}
return 0;
}
let sep = if one_per_line { "\n" } else { " " };
let mut processed_args: Vec<String> = if OPT_ISSET(ops, b'P') {
args.iter()
.map(|a| crate::ported::prompt::expand_prompt(a)) .collect()
} else {
args.to_vec()
};
if OPT_ISSET(ops, b'o') || OPT_ISSET(ops, b'O') {
let case_sensitive = OPT_ISSET(ops, b'i');
if case_sensitive {
processed_args.sort();
} else {
processed_args.sort_by_key(|s| s.to_lowercase());
}
if OPT_ISSET(ops, b'O') {
processed_args.reverse();
}
}
if !raw {
let echo_E = echo_mode && OPT_ISSET(ops, b'E');
if !echo_E {
for a in processed_args.iter_mut() {
let (s, _) = crate::ported::utils::getkeystring_with(a,
crate::ported::utils::GETKEYS_PRINT);
*a = s;
}
}
}
let body = processed_args.join(sep);
if let Some(ref v) = dest_var {
crate::ported::params::setsparam(v, &body);
} else {
print!("{}", body);
if !nonewline && !echo_mode {
println!();
} else if echo_mode && !nonewline {
println!();
}
}
0
}
pub fn bin_shift(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut num: i32 = 1; let mut ret: i32 = 0; let mut idx = 0usize;
crate::ported::mem::queue_signals(); if !argv.is_empty() { let first = &argv[0];
let is_array = {
use crate::ported::zsh_h::{PM_ARRAY, PM_TYPE};
let tab = crate::ported::params::paramtab().read().unwrap();
tab.get(first)
.map(|pm| PM_TYPE(pm.node.flags as u32) == PM_ARRAY)
.unwrap_or(false)
};
if !is_array { num = crate::ported::math::mathevali(first).unwrap_or_else(|_| {
ret = 1;
0
}) as i32; idx = 1;
if ret != 0
|| crate::ported::utils::errflag.load(Ordering::Relaxed) != 0
{
crate::ported::mem::unqueue_signals(); return 1;
}
}
}
if num < 0 { crate::ported::mem::unqueue_signals(); crate::ported::utils::zwarnnam(name,
"argument to shift must be non-negative"); return 1; }
if idx < argv.len() { for arr_name in &argv[idx..] { let s: Vec<String> = {
let tab = crate::ported::params::paramtab().read().unwrap();
match tab.get(arr_name).and_then(|pm| pm.u_arr.clone()) {
Some(arr) => arr,
None => continue,
}
};
if (s.len() as i32) < num { crate::ported::utils::zwarnnam(name,
"shift count must be <= $#"); ret += 1; continue; }
let s2: Vec<String> = if OPT_ISSET(ops, b'p') { s[..s.len() - num as usize].to_vec() } else {
s[num as usize..].to_vec() };
crate::ported::params::setaparam(arr_name, s2);
}
} else {
let mut pp = PPARAMS.lock().unwrap_or_else(|e| { PPARAMS.clear_poison(); e.into_inner() });
let l = pp.len() as i32;
if num > l { crate::ported::utils::zwarnnam(name, "shift count must be <= $#"); ret = 1; } else if OPT_ISSET(ops, b'p') { pp.truncate((l - num) as usize); } else {
pp.drain(..num as usize); }
drop(pp);
}
crate::ported::mem::unqueue_signals(); ret }
pub fn bin_getopts(_name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if argv.len() < 2 { return 1; }
let optstr_full = argv[0].clone();
let var = argv[1].clone();
let argv_rest: Vec<String> = argv[2..].to_vec();
let args: Vec<String> = if !argv_rest.is_empty() {
argv_rest
} else {
PPARAMS.lock().map(|p| p.clone()).unwrap_or_default()
};
let mut zoptind = ZOPTIND.load(Ordering::Relaxed);
if zoptind < 1 { zoptind = 1;
OPTCIND.store(0, Ordering::Relaxed);
}
let mut optcind = OPTCIND.load(Ordering::Relaxed);
if (args.len() as i32) < zoptind { ZOPTIND.store(zoptind, Ordering::Relaxed);
return 1;
}
let (quiet, optstr) = if optstr_full.starts_with(':') { (true, &optstr_full[1..])
} else {
(false, optstr_full.as_str())
};
let mut str_buf = args[(zoptind - 1) as usize].clone();
let mut lenstr = str_buf.len() as i32;
if lenstr == 0 { return 1; }
if optcind >= lenstr { optcind = 0;
zoptind += 1;
if zoptind as usize > args.len() { ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(optcind, Ordering::Relaxed);
return 1;
}
str_buf = args[(zoptind - 1) as usize].clone();
lenstr = str_buf.len() as i32;
}
if optcind == 0 { if lenstr < 2 || (!str_buf.starts_with('-') && !str_buf.starts_with('+')) {
ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(optcind, Ordering::Relaxed);
return 1;
}
if lenstr == 2 && &str_buf[..2] == "--" { zoptind += 1;
ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(0, Ordering::Relaxed);
return 1;
}
optcind += 1;
}
let opch = str_buf.as_bytes()[optcind as usize];
optcind += 1;
let plus = str_buf.starts_with('+');
let optbuf: String = if plus {
format!("+{}", opch as char)
} else {
format!("{}", opch as char)
};
let posix = crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixbuiltins"));
let found = optstr.bytes().position(|b| b == opch);
if opch == b':' || found.is_none() { if posix { optcind = 0;
zoptind += 1;
}
crate::ported::params::setsparam(&var, "?");
if quiet { crate::ported::params::setsparam("OPTARG", &optbuf); } else {
let prefix = if plus { "+" } else { "-" };
crate::ported::utils::zwarn(&format!(
"bad option: {}{}", prefix, opch as char)); crate::ported::params::setsparam("OPTARG", "");
}
ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(optcind, Ordering::Relaxed);
crate::ported::params::setiparam("OPTIND", zoptind as i64);
return 0;
}
let p = found.unwrap();
let optstr_bytes = optstr.as_bytes();
if p + 1 < optstr_bytes.len() && optstr_bytes[p + 1] == b':' { if optcind == lenstr { if zoptind as usize >= args.len() { if posix {
optcind = 0;
zoptind += 1;
}
if quiet { crate::ported::params::setsparam(&var, ":");
crate::ported::params::setsparam("OPTARG", &optbuf);
} else {
crate::ported::params::setsparam(&var, "?");
crate::ported::params::setsparam("OPTARG", "");
let prefix = if plus { "+" } else { "-" };
crate::ported::utils::zwarn(&format!(
"argument expected after {}{} option",
prefix, opch as char)); }
ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(optcind, Ordering::Relaxed);
crate::ported::params::setiparam("OPTIND", zoptind as i64);
return 0;
}
let p_arg = args[zoptind as usize].clone();
zoptind += 1;
crate::ported::params::setsparam("OPTARG", &p_arg); optcind = 0;
} else {
let p_arg = str_buf[(optcind as usize)..].to_string();
crate::ported::params::setsparam("OPTARG", &p_arg);
optcind = 0;
zoptind += 1;
}
} else {
crate::ported::params::setsparam("OPTARG", "");
}
crate::ported::params::setsparam(&var, &optbuf);
ZOPTIND.store(zoptind, Ordering::Relaxed);
OPTCIND.store(optcind, Ordering::Relaxed);
crate::ported::params::setiparam("OPTIND", zoptind as i64);
0 }
pub fn bin_break(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let mut num: i32 = LASTVAL.load(Ordering::Relaxed); let mut nump = 0i32; let implicit = argv.is_empty(); if !implicit { num = mathevali(&argv[0]).unwrap_or(0) as i32; nump = 1; }
if nump > 0 && (func == BIN_CONTINUE || func == BIN_BREAK) && num <= 0 { crate::ported::utils::zwarnnam(name, &format!("argument is not positive: {}", num)); return 1; }
let loops = LOOPS.load(Ordering::Relaxed);
match func {
x if x == BIN_CONTINUE => { if loops == 0 { crate::ported::utils::zwarnnam(name, "not in while, until, select, or repeat loop"); return 1; }
CONTFLAG.store(1, Ordering::Relaxed); BREAKS.store(if nump != 0 { num.min(loops) } else { 1 }, Ordering::Relaxed);
}
x if x == BIN_BREAK => { if loops == 0 { crate::ported::utils::zwarnnam(name, "not in while, until, select, or repeat loop"); return 1; }
BREAKS.store(if nump != 0 { num.min(loops) } else { 1 }, Ordering::Relaxed);
}
x if x == BIN_RETURN => {
let interactive = crate::ported::zsh_h::isset(crate::ported::options::optlookup("interactive"));
let shinstdin = crate::ported::zsh_h::isset(crate::ported::options::optlookup("shinstdin"));
let locallevel = LOCALLEVEL.load(Ordering::Relaxed);
let sourcelevel = SOURCELEVEL.load(Ordering::Relaxed);
if (interactive && shinstdin) || locallevel != 0 || sourcelevel != 0 { RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(loops, Ordering::Relaxed); LASTVAL.store(num, Ordering::Relaxed); let posixtraps =
crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixtraps"));
let cur_state =
crate::exec::TRAP_STATE.load(Ordering::Relaxed);
let cur_return =
crate::exec::TRAP_RETURN.load(Ordering::Relaxed);
if cur_state == crate::ported::zsh_h::TRAP_STATE_PRIMED && cur_return == -2 && !(posixtraps && implicit) {
crate::exec::TRAP_STATE.store( crate::ported::zsh_h::TRAP_STATE_FORCE_RETURN,
Ordering::Relaxed,
);
crate::exec::TRAP_RETURN.store(num, Ordering::Relaxed); }
return num; }
zexit(num, ZEXIT_NORMAL); }
x if x == BIN_LOGOUT => {
let loginshell = crate::ported::zsh_h::isset(crate::ported::options::optlookup("loginshell"));
if !loginshell { crate::ported::utils::zwarnnam(name, "not login shell"); return 1; }
let cur_locallevel = LOCALLEVEL.load(Ordering::Relaxed);
let forklevel = crate::exec::FORKLEVEL.load(Ordering::Relaxed);
let shell_exiting = SHELL_EXITING.load(Ordering::Relaxed);
if cur_locallevel > forklevel && shell_exiting != -1 { if STOPMSG.load(Ordering::Relaxed) == 0 {
zexit(0, crate::ported::zsh_h::ZEXIT_DEFERRED); }
if STOPMSG.load(Ordering::Relaxed) == 0 { let trap_state = crate::exec::TRAP_STATE.load(Ordering::Relaxed);
if trap_state != 0 { crate::exec::TRAP_STATE.store( crate::ported::zsh_h::TRAP_STATE_FORCE_RETURN,
Ordering::Relaxed,
);
}
RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(LOOPS.load(Ordering::Relaxed), Ordering::Relaxed);
EXIT_PENDING.store(1, Ordering::Relaxed); EXIT_VAL.store(num, Ordering::Relaxed); }
} else {
zexit(num, ZEXIT_NORMAL); }
}
x if x == BIN_EXIT => {
let cur_locallevel = LOCALLEVEL.load(Ordering::Relaxed);
let forklevel = crate::exec::FORKLEVEL.load(Ordering::Relaxed);
let shell_exiting = SHELL_EXITING.load(Ordering::Relaxed);
if cur_locallevel > forklevel && shell_exiting != -1 { if STOPMSG.load(Ordering::Relaxed) == 0 {
zexit(0, crate::ported::zsh_h::ZEXIT_DEFERRED); }
if STOPMSG.load(Ordering::Relaxed) == 0 { let trap_state = crate::exec::TRAP_STATE.load(Ordering::Relaxed);
if trap_state != 0 { crate::exec::TRAP_STATE.store( crate::ported::zsh_h::TRAP_STATE_FORCE_RETURN,
Ordering::Relaxed,
);
}
RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(LOOPS.load(Ordering::Relaxed), Ordering::Relaxed);
EXIT_PENDING.store(1, Ordering::Relaxed); EXIT_VAL.store(num, Ordering::Relaxed); }
} else {
zexit(num, ZEXIT_NORMAL); }
}
_ => {}
}
0
}
pub fn checkjobs() { use std::sync::Mutex;
let checkrunning = crate::ported::zsh_h::isset(crate::ported::options::optlookup("checkrunningjobs"));
let thisjob: i32 = *crate::ported::jobs::THISJOB
.get_or_init(|| Mutex::new(-1_i32))
.lock().expect("THISJOB poisoned");
let maxjob: i32 = *crate::ported::jobs::MAXJOB
.get_or_init(|| Mutex::new(0_usize))
.lock().expect("MAXJOB poisoned") as i32;
let mut found: Option<i32> = None;
let mut found_stat: i32 = 0;
for i in 1..=maxjob { let stat = JOBSTATS.lock()
.ok()
.and_then(|t| t.get(i as usize).copied())
.unwrap_or(0);
if i != thisjob && (stat & STAT_LOCKED) != 0 && (stat & STAT_NOPRINT) == 0 && (checkrunning || (stat & STAT_STOPPED) != 0) {
found = Some(i); found_stat = stat;
break;
}
}
if found.is_some() { if (found_stat & STAT_STOPPED) != 0 { crate::ported::utils::zerr("you have stopped jobs."); } else {
crate::ported::utils::zerr("you have running jobs."); }
STOPMSG.store(1, Ordering::Relaxed); }
}
pub fn realexit() -> ! { use std::sync::atomic::Ordering::Relaxed;
std::process::exit(if SHELL_EXITING.load(Relaxed) != 0 || EXIT_PENDING.load(Relaxed) != 0 { EXIT_VAL.load(Relaxed) } else { LASTVAL.load(Relaxed) });
}
pub fn _realexit() -> ! { use std::sync::atomic::Ordering::Relaxed;
unsafe { libc::_exit(if SHELL_EXITING.load(Relaxed) != 0 || EXIT_PENDING.load(Relaxed) != 0 { EXIT_VAL.load(Relaxed) } else { LASTVAL.load(Relaxed) }) }
}
#[allow(unused_variables)]
pub fn zexit(val: i32, from_where: i32) { use crate::ported::zsh_h::{MONITOR, ZEXIT_NORMAL, ZEXIT_SIGNAL, ZEXIT_DEFERRED};
EXIT_VAL.store(val, Ordering::Relaxed); if SHELL_EXITING.load(Ordering::Relaxed) == -1 { RETFLAG.store(1, Ordering::Relaxed); BREAKS.store(LOOPS.load(Ordering::Relaxed), Ordering::Relaxed); return; }
if crate::ported::zsh_h::isset(MONITOR) && STOPMSG.load(Ordering::Relaxed) == 0
&& from_where != ZEXIT_SIGNAL
{
checkjobs(); if STOPMSG.load(Ordering::Relaxed) != 0 { STOPMSG.store(2, Ordering::Relaxed); return; }
}
if from_where == ZEXIT_DEFERRED { return;
}
let prev_exiting = SHELL_EXITING.fetch_add(1, Ordering::Relaxed);
if prev_exiting != 0 && from_where != ZEXIT_NORMAL { return;
}
SHELL_EXITING.store(-1, Ordering::Relaxed); crate::ported::utils::errflag.store(0, Ordering::Relaxed); if crate::ported::zsh_h::isset(MONITOR) { crate::ported::signals::killrunjobs(
if from_where == ZEXIT_SIGNAL { 1 } else { 0 }
); }
realexit(); }
pub fn bin_dot(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if argv.is_empty() { return 0; }
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() && !argv[0].is_empty() {
let ctx = crate::recorder::recorder_ctx_global();
crate::recorder::emit_source(&argv[0], ctx);
}
let saved_pparams: Option<Vec<String>> = if argv.len() > 1 { let mut pp = PPARAMS.lock().unwrap_or_else(|e| { PPARAMS.clear_poison(); e.into_inner() });
let saved = pp.clone();
*pp = argv[1..].to_vec(); Some(saved)
} else { None };
let arg0 = argv[0].clone(); let _enam = arg0.clone(); let saved_argzero: Option<Option<String>> =
if isset(crate::ported::zsh_h::FUNCTIONARGZERO) {
let prev = crate::ported::utils::argzero();
crate::ported::utils::set_argzero(Some(arg0.clone()));
Some(prev)
} else {
None
};
let mut diddot = 0i32; let mut dotdot = 0i32;
let mut found_path: Option<String> = None;
if !name.starts_with('.') { let p = std::path::Path::new(&arg0);
if p.exists() && !p.is_dir() { diddot = 1; found_path = Some(arg0.clone()); }
}
if found_path.is_none() && arg0.contains('/') { if arg0.starts_with("./") { diddot += 1; } else if arg0.starts_with("../") { dotdot += 1; } let p = std::path::Path::new(&arg0);
if p.exists() && !p.is_dir() {
found_path = Some(arg0.clone()); }
}
let pathdirs = crate::ported::zsh_h::isset(crate::ported::options::optlookup("pathdirs"));
if found_path.is_none() && (!arg0.contains('/') || (pathdirs && diddot < 2 && dotdot == 0)) { let path_env = crate::ported::params::getsparam("PATH").unwrap_or_default();
for dir in path_env.split(':') { let buf = if dir.is_empty() || dir == "." { if diddot != 0 { continue; }
diddot = 1; arg0.clone() } else {
format!("{}/{}", dir, arg0) };
let p = std::path::Path::new(&buf);
if p.exists() && !p.is_dir() { found_path = Some(buf); break;
}
}
}
if let Some(saved) = saved_pparams { let mut pp = PPARAMS.lock().unwrap_or_else(|e| { PPARAMS.clear_poison(); e.into_inner() });
*pp = saved; }
if let Some(prev) = saved_argzero.clone() {
crate::ported::utils::set_argzero(prev);
}
let path = match found_path {
Some(p) => p,
None => { let posix = crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixbuiltins"));
let msg = format!("{}: {}", "no such file or directory", arg0); if posix {
crate::ported::utils::zwarnnam(name, &msg); } else {
crate::ported::utils::zwarnnam(name, &msg); }
return 1;
}
};
let result = match std::fs::read_to_string(&path) { Ok(_src) => {
let _ = path;
0
}
Err(_) => 1,
};
if let Some(prev) = saved_argzero {
crate::ported::utils::set_argzero(prev);
}
result
}
pub fn eval(argv: &[String]) -> i32 { if argv.is_empty() { return 0;
}
LASTVAL.load(std::sync::atomic::Ordering::Relaxed) }
pub fn bin_emulate(nam: &str, argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let opt_l = OPT_ISSET(ops, b'l'); let opt_l_arg = OPT_ISSET(ops, b'L'); let opt_r = OPT_ISSET(ops, b'R');
if argv.is_empty() { if opt_l_arg || opt_r { crate::ported::utils::zwarnnam(nam, "not enough arguments"); return 1; }
let bits = crate::ported::options::emulation
.load(std::sync::atomic::Ordering::Relaxed) as i32;
let shname = if (bits & EMULATE_CSH) != 0 { "csh" } else if (bits & EMULATE_KSH) != 0 { "ksh" } else if (bits & EMULATE_SH) != 0 { "sh" } else { "zsh" }; println!("{}", shname); return 0; }
let shname = &argv[0];
if argv.len() == 1 { let bytes = shname.as_bytes();
let mut ch = if !bytes.is_empty() { bytes[0] } else { 0 };
if ch == b'r' && bytes.len() >= 2 { ch = bytes[1]; }
let bits = match ch { b'c' => EMULATE_CSH, b'k' => EMULATE_KSH, b's' | b'b' => EMULATE_SH, _ => crate::ported::zsh_h::EMULATE_ZSH, };
crate::ported::options::emulation
.store(bits, std::sync::atomic::Ordering::Relaxed);
let mut cmdopts: std::collections::HashMap<String, bool> =
std::collections::HashMap::new();
for n in crate::ported::options::ZSH_OPTIONS_SET.iter() {
cmdopts.insert(
n.to_string(),
crate::ported::options::opt_state_get(n).unwrap_or(false),
);
}
if !opt_l {
let mode = shname.as_str();
let _ = mode;
}
if opt_l_arg { for nm in ["localoptions", "localtraps", "localpatterns"] {
cmdopts.insert(nm.to_string(), true);
if !opt_l {
crate::ported::options::opt_state_set(nm, true);
}
}
}
if opt_l { crate::ported::options::list_emulate_options(&cmdopts, opt_r);
return 0; }
crate::ported::pattern::clearpatterndisables();
return 0; }
if opt_l { crate::ported::utils::zwarnnam(nam, "too many arguments for -l"); return 1; }
let _ = (opt_r, shname);
0
}
pub fn bin_eval(_name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
eval(argv) }
pub fn bin_read(name: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let args = args.to_vec();
let mut nchars: i32 = 1;
if OPT_HASARG(ops, b'k') { let optarg = OPT_ARG(ops, b'k').unwrap_or("");
match optarg.trim().parse::<i32>() {
Ok(n) => nchars = n,
Err(_) => {
crate::ported::utils::zwarnnam(name,
&format!("number expected after -k: {}", optarg)); return 1;
}
}
}
let mut argi = 0usize;
let mut prompt: Option<String> = None;
if argi < args.len() && args[argi].starts_with('?') { prompt = Some(args[argi][1..].to_string());
argi += 1;
}
let want_array = OPT_ISSET(ops, b'A');
let reply = if argi < args.len() {
let r = args[argi].clone();
argi += 1;
r
} else if want_array {
"reply".to_string() } else {
"REPLY".to_string() };
if want_array && argi < args.len() { crate::ported::utils::zwarnnam(name, "only one array argument allowed"); return 1;
}
if OPT_ISSET(ops, b'l') || OPT_ISSET(ops, b'c') { return crate::ported::zle::compctl::compctlread(name, &args[argi..]);
}
let _ufd: i32 = if OPT_HASARG(ops, b'u') {
OPT_ARG(ops, b'u').and_then(|s| s.parse().ok()).unwrap_or(0)
} else { 0 };
if OPT_HASARG(ops, b't') {
let arg = OPT_ARG(ops, b't').unwrap_or("");
let tmout: f64 = arg.parse().unwrap_or(0.0);
let mut pfd = libc::pollfd { fd: 0, events: libc::POLLIN, revents: 0 };
let r = unsafe { libc::poll(&mut pfd, 1, (tmout * 1000.0) as i32) };
if r == 0 { return 4; } if r < 0 { return 2; } }
if let Some(ref p) = prompt {
eprint!("{}", p);
let _ = std::io::Write::flush(&mut std::io::stderr());
}
let mut buf = String::new();
if OPT_ISSET(ops, b'k') { let mut got = vec![0u8; nchars as usize];
let mut bytes_read = 0;
while bytes_read < nchars as usize {
let mut b = [0u8; 1];
match std::io::stdin().lock().read(&mut b) {
Ok(1) => { got[bytes_read] = b[0]; bytes_read += 1; }
_ => break,
}
}
buf = String::from_utf8_lossy(&got[..bytes_read]).into_owned();
} else {
match std::io::stdin().read_line(&mut buf) {
Ok(0) => return 1, Ok(_) => {
if buf.ends_with('\n') { buf.pop(); } }
Err(_) => return 2,
}
}
if want_array {
let parts: Vec<String> = buf.split_whitespace().map(String::from).collect();
crate::ported::params::setaparam(&reply, parts); } else if argi < args.len() {
let mut vars: Vec<String> = Vec::with_capacity(args.len() - argi + 1);
vars.push(reply);
for n in &args[argi..] { vars.push(n.clone()); }
let ifs = crate::ported::params::getsparam("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let is_ifs = |c: char| ifs.contains(c);
let trimmed = buf.trim_start_matches(|c: char| is_ifs(c) && c.is_whitespace());
let mut remaining = trimmed.to_string();
for (i, var) in vars.iter().enumerate() {
if i + 1 == vars.len() {
let final_val = remaining.trim_end_matches(|c: char|
is_ifs(c) && c.is_whitespace()).to_string();
crate::ported::params::setsparam(var, &final_val);
} else {
match remaining.find(is_ifs) {
Some(idx) => {
let field = remaining[..idx].to_string();
let rest = &remaining[idx + remaining[idx..]
.chars().next().map(|c| c.len_utf8()).unwrap_or(1)..];
let rest = rest.trim_start_matches(|c: char|
is_ifs(c) && c.is_whitespace());
crate::ported::params::setsparam(var, &field);
remaining = rest.to_string();
}
None => {
crate::ported::params::setsparam(var, &remaining);
remaining.clear();
}
}
}
}
} else {
crate::ported::params::setsparam(&reply, &buf);
}
0
}
pub fn zread(izle: i32, readchar: &mut i32, izle_timeout: i64) -> i32 { if izle != 0 { let _ = izle_timeout;
}
if *readchar >= 0 { let cc = *readchar as u8;
*readchar = -1; return cc as i32;
}
let mut buf = [0u8; 1];
let fd = {
use std::sync::atomic::Ordering;
let s = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if s >= 0 { s } else { 0 } };
loop {
let n = unsafe {
libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 1)
};
match n {
1 => return buf[0] as i32, 0 => return -1, -1 if std::io::Error::last_os_error().kind()
== std::io::ErrorKind::Interrupted => continue,
_ => return -1,
}
}
}
pub fn testlex() { if TEST_TOK.load(Ordering::Relaxed) == TEST_LEXERR { return;
}
let mut targs = TESTARGS.lock().unwrap_or_else(|e| {
TESTARGS.clear_poison(); e.into_inner()
});
let mut idx = TESTARGS_IDX.load(Ordering::Relaxed) as usize;
let cur = targs.get(idx).cloned(); if let Some(t) = cur.as_ref() {
if let Ok(mut ts) = TOKSTR.lock() { *ts = t.clone(); } }
let none = cur.is_none() || cur.as_deref() == Some("");
if none { let prev = TEST_TOK.load(Ordering::Relaxed);
TEST_TOK.store(if prev != 0 { TEST_NULLTOK } else { TEST_LEXERR }, Ordering::Relaxed);
return;
}
let arg = cur.unwrap();
let new_tok = match arg.as_str() { "-o" => TEST_DBAR, "-a" => TEST_DAMPER, "!" => TEST_BANG, "(" => TEST_INPAR, ")" => TEST_OUTPAR, "<" => TEST_INANG, ">" => TEST_OUTANG, _ => TEST_STRING, };
TEST_TOK.store(new_tok, Ordering::Relaxed);
idx += 1; TESTARGS_IDX.store(idx as i32, Ordering::Relaxed);
let _ = &mut *targs; }
pub fn bin_test(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let mut argv = argv.to_vec();
let mut sense = 0i32;
if func == BIN_BRACKET { if argv.is_empty() || argv.last().map(|s| s.as_str()) != Some("]") { crate::ported::utils::zwarnnam(name, "']' expected"); return 2; }
argv.pop(); }
if argv.is_empty() { return 1; }
let nargs = argv.len(); if nargs == 3 || nargs == 4 { if argv[0] == "(" && argv[nargs - 1] == ")" && (nargs != 3 || crate::ported::text::is_cond_binary_op(&argv[1]) == 0)
{
argv.pop(); argv.remove(0); }
}
if argv.len() == 3 && argv[0] == "!" { sense = 1; argv.remove(0); }
let args_refs: Vec<&str> = argv.iter().map(|s| s.as_str()).collect();
let options = std::collections::HashMap::new();
let mut variables = std::collections::HashMap::new();
{
let tab = crate::ported::params::paramtab().read().unwrap();
for (k, pm) in tab.iter() {
if (pm.node.flags as u32 & crate::ported::zsh_h::PM_UNSET) != 0 {
continue;
}
let v = pm.u_str.clone().unwrap_or_default();
variables.insert(k.clone(), v);
}
}
for (k, v) in std::env::vars() {
variables.entry(k).or_insert(v);
}
let posix = crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixbuiltins"));
let mut ret = crate::ported::cond::evalcond(&args_refs, &options, &variables, posix);
if ret < 2 && sense != 0 { ret = if ret == 0 { 1 } else { 0 }; }
ret }
pub fn bin_times(_name: &str, _argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut buf: libc::tms = unsafe { std::mem::zeroed() }; let clktck = crate::ported::jobs::get_clktck() as f64; let clktck = if clktck <= 0.0 { 100.0 } else { clktck };
if unsafe { libc::times(&mut buf) } == (-1i64) as libc::clock_t { return 1; }
let pttime = |t: libc::clock_t| {
let secs = t as f64 / clktck;
print!("{}m{:.3}s", (secs / 60.0) as i64, secs % 60.0);
};
pttime(buf.tms_utime); print!(" "); pttime(buf.tms_stime); println!(); pttime(buf.tms_cutime); print!(" "); pttime(buf.tms_cstime); println!(); 0 }
pub fn bin_trap(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() {
let listing = argv.is_empty()
|| (argv.len() == 1 && (argv[0] == "-l" || argv[0] == "-p"));
if !listing && argv.len() >= 2 {
let ctx = crate::recorder::recorder_ctx_global();
let handler = &argv[0];
for sig in &argv[1..] {
crate::recorder::emit_trap(sig, handler, ctx.clone());
}
}
}
let mut argv = argv.to_vec();
if !argv.is_empty() && argv[0] == "--" { argv.remove(0); }
if argv.is_empty() { crate::ported::mem::queue_signals(); let traps = traps_table().lock().map(|t| t.clone()).unwrap_or_default();
for (sig, body) in traps.iter() { print!("trap -- "); print!("{}", crate::ported::utils::quotedzputs(body)); println!(" {}", sig); }
crate::ported::mem::unqueue_signals(); return 0; }
let first = &argv[0];
if getsigidx(first) != -1 || first == "-" { let start = if first == "-" { 1 } else { 0 }; let mut had_error = 0i32;
if start >= argv.len() { if let Ok(mut t) = traps_table().lock() {
t.clear(); }
} else {
for arg in &argv[start..] { let sig = getsigidx(arg);
if sig == -1 { crate::ported::utils::zwarnnam(name,
&format!("undefined signal: {}", arg)); had_error = 1; break; }
if let Ok(mut t) = traps_table().lock() {
t.remove(arg); }
}
}
return had_error; }
let arg = argv.remove(0); if argv.is_empty() { if arg.starts_with("SIG") || arg.chars().next().is_some_and(|c| c.is_ascii_digit()) {
crate::ported::utils::zwarnnam(name,
&format!("undefined signal: {}", arg)); } else {
crate::ported::utils::zwarnnam(name, "signal expected"); }
return 1; }
for sigarg in &argv { let sig = getsigidx(sigarg);
if sig == -1 { crate::ported::utils::zwarnnam(name,
&format!("undefined signal: {}", sigarg)); break; }
if let Ok(mut t) = traps_table().lock() {
t.insert(sigarg.clone(), arg.clone()); }
}
0
}
pub fn bin_ttyctl(_name: &str, _argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use std::sync::Mutex;
let cell = crate::ported::jobs::TTYFROZEN.get_or_init(|| Mutex::new(0_i32));
if OPT_ISSET(ops, b'f') { *cell.lock().expect("TTYFROZEN poisoned") = 1; } else if OPT_ISSET(ops, b'u') { *cell.lock().expect("TTYFROZEN poisoned") = 0; } else {
let f = *cell.lock().expect("TTYFROZEN poisoned");
println!("tty is {}frozen", if f != 0 { "" } else { "not " }); }
0 }
pub fn bin_let(_name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
use crate::ported::utils::{errflag, ERRFLAG_ERROR};
use std::sync::atomic::Ordering;
let mut val: mnumber = mnumber { l: 0, d: 0.0, type_: MN_INTEGER }; for expr in argv { if let Ok(v) = matheval(expr) { val = v;
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 { errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed); return 2; }
if val.type_ == MN_INTEGER { (val.l == 0) as i32
} else {
(val.d == 0.0) as i32
}
}
pub fn bin_umask(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
crate::ported::mem::queue_signals(); let mut um: u32 = unsafe { libc::umask(0o777) } as u32; unsafe { libc::umask(um as libc::mode_t); } crate::ported::mem::unqueue_signals();
if args.is_empty() { if OPT_ISSET(ops, b'S') { let who_chars = ['u', 'g', 'o']; for (i, who) in who_chars.iter().enumerate() { print!("{}=", who); let mut what_iter = ['r', 'w', 'x'].iter(); while let Some(w) = what_iter.next() { if (um & 0o400) == 0 { print!("{}", w); }
um <<= 1; }
if i < 2 { print!(","); } else { println!(); } }
} else {
if (um & 0o700) != 0 { print!("0"); }
println!("{:03o}", um); }
return 0; }
let s = &args[0];
if s.chars().next().is_some_and(|c| c.is_ascii_digit()) { match u32::from_str_radix(s, 8) { Ok(n) => um = n, Err(_) => {
crate::ported::utils::zwarnnam(nam, "bad umask"); return 1; }
}
} else {
let bytes = s.as_bytes();
let mut i = 0;
loop {
let mut whomask: u32 = 0; while i < bytes.len() { match bytes[i] {
b'u' => { whomask |= 0o700; i += 1; } b'g' => { whomask |= 0o070; i += 1; } b'o' => { whomask |= 0o007; i += 1; } b'a' => { whomask |= 0o777; i += 1; } _ => break,
}
}
if whomask == 0 { whomask = 0o777; } let umaskop = if i < bytes.len() { bytes[i] } else { 0 }; if !(umaskop == b'+' || umaskop == b'-' || umaskop == b'=') { if umaskop != 0 { crate::ported::utils::zwarnnam(nam,
&format!("bad symbolic mode operator: {}", umaskop as char)); } else {
crate::ported::utils::zwarnnam(nam, "bad umask"); }
return 1; }
i += 1;
let mut mask: u32 = 0; while i < bytes.len() && bytes[i] != b',' { match bytes[i] {
b'r' => mask |= 0o444 & whomask, b'w' => mask |= 0o222 & whomask, b'x' => mask |= 0o111 & whomask, other => {
crate::ported::utils::zwarnnam(nam,
&format!("bad symbolic mode permission: {}", other as char)); return 1; }
}
i += 1;
}
match umaskop {
b'+' => um &= !mask, b'-' => um |= mask, _ => um = (um | whomask) & !mask, }
if i < bytes.len() && bytes[i] == b',' { i += 1; } else {
break; }
}
if i < bytes.len() { crate::ported::utils::zwarnnam(nam,
&format!("bad character in symbolic mode: {}", bytes[i] as char)); return 1; }
}
unsafe { libc::umask(um as libc::mode_t); } 0 }
pub fn bin_notavail(nam: &str, _argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
crate::ported::utils::zwarnnam(nam, "not available on this system"); 1 }
pub static BUILTINS: std::sync::LazyLock<Vec<builtin>> = std::sync::LazyLock::new(|| vec![
BIN_PREFIX("-", BINF_DASH),
BIN_PREFIX("builtin", BINF_BUILTIN),
BIN_PREFIX("command", BINF_COMMAND),
BIN_PREFIX("exec", BINF_EXEC),
BIN_PREFIX("noglob", BINF_NOGLOB),
BUILTIN("[", BINF_HANDLES_OPTS, Some(bin_test as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_BRACKET, None, None),
BUILTIN(".", BINF_PSPECIAL, Some(bin_dot as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN(":", BINF_PSPECIAL, Some(bin_true as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, Some(bin_alias as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("Lgmrs"), None),
BUILTIN("autoload", BINF_PLUSOPTS, None, 0, -1, 0, Some("dmktrRTUwWXz"), Some("u")),
BUILTIN("bg", 0, Some(crate::ported::jobs::bin_fg as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_BG, None, None),
BUILTIN("break", BINF_PSPECIAL, Some(bin_break as crate::ported::zsh_h::HandlerFunc), 0, 1, BIN_BREAK, None, None),
BUILTIN("bye", 0, None, 0, 1, BIN_EXIT, None, None),
BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, Some(bin_cd as crate::ported::zsh_h::HandlerFunc), 0, 2, BIN_CD, Some("qsPL"), None),
BUILTIN("chdir", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, Some(bin_cd as crate::ported::zsh_h::HandlerFunc), 0, 2, BIN_CD, Some("qsPL"), None),
BUILTIN("continue", BINF_PSPECIAL, Some(bin_break as crate::ported::zsh_h::HandlerFunc), 0, 1, BIN_CONTINUE, None, None),
BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("AE:%F:%HL:%R:%TUZ:%afghi:%klmnp:%rtuxz"), None),
BUILTIN("dirs", 0, Some(bin_dirs as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("clpv"), None),
BUILTIN("disable", 0, Some(bin_enable as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_DISABLE, Some("afmprs"), None),
BUILTIN("disown", 0, Some(crate::ported::jobs::bin_fg as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_DISOWN, None, None),
BUILTIN("echo", BINF_SKIPINVALID, Some(bin_print as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_ECHO, Some("neE"), Some("-")),
BUILTIN("emulate", 0, Some(bin_emulate as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("lLR"), None),
BUILTIN("enable", 0, Some(bin_enable as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_ENABLE, Some("afmprs"), None),
BUILTIN("eval", BINF_PSPECIAL, Some(bin_eval as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_EVAL, None, None),
BUILTIN("exit", BINF_PSPECIAL, Some(bin_break as crate::ported::zsh_h::HandlerFunc), 0, 1, BIN_EXIT, None, None),
BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_EXPORT, Some("E:%F:%HL:%R:%TUZ:%afhi:%lp:%rtu"), Some("xg")),
BUILTIN("false", 0, Some(bin_false as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("fc", 0, None, 0, -1, BIN_FC, Some("aAdDe:EfiIlLmnpPrRst:W"), None),
BUILTIN("fg", 0, None, 0, -1, BIN_FG, None, None),
BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("E:%F:%HL:%R:%Z:%ghlp:%rtux"), Some("E")),
BUILTIN("functions", BINF_PLUSOPTS, Some(bin_functions as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("ckmMstTuUWx:z"), None),
BUILTIN("getln", 0, None, 0, -1, 0, Some("ecnAlE"), Some("zr")),
BUILTIN("getopts", 0, Some(bin_getopts as crate::ported::zsh_h::HandlerFunc), 2, -1, 0, None, None),
BUILTIN("hash", BINF_MAGICEQUALS, Some(bin_hash as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("Ldfmrv"), None),
BUILTIN("hashinfo", 0, None, 0, 0, 0, None, None),
BUILTIN("history", 0, None, 0, -1, BIN_FC, Some("adDEfiLmnpPrt:"), Some("l")),
BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("HL:%R:%Z:%ghi:%lp:%rtux"), Some("i")),
BUILTIN("jobs", 0, Some(crate::ported::jobs::bin_fg as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_JOBS, Some("dlpZrs"), None),
BUILTIN("kill", BINF_HANDLES_OPTS, None, 0, -1, 0, None, None),
BUILTIN("let", 0, Some(bin_let as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("AE:%F:%HL:%R:%TUZ:%ahi:%lnp:%rtux"), None),
BUILTIN("logout", 0, Some(bin_break as crate::ported::zsh_h::HandlerFunc), 0, 1, BIN_LOGOUT, None, None),
BUILTIN("mem", 0, None, 0, 0, 0, Some("v"), None),
BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, None, 0, 1, BIN_POPD, Some("q"), None),
BUILTIN("patdebug", 0, None, 1, -1, 0, Some("p"), None),
BUILTIN("print", BINF_PRINTOPTS, Some(bin_print as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_PRINT, Some("abcC:Df:ilmnNoOpPrRsSu:v:x:X:z-"), None),
BUILTIN("printf", BINF_SKIPINVALID | BINF_SKIPDASH, Some(bin_print as crate::ported::zsh_h::HandlerFunc), 1, -1, BIN_PRINTF, Some("v:"), None),
BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, None, 0, 2, BIN_PUSHD, Some("qsPL"), None),
BUILTIN("pushln", 0, None, 0, -1, BIN_PRINT, None, Some("-nz")),
BUILTIN("pwd", 0, Some(bin_pwd as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, Some("rLP"), None),
BUILTIN("r", 0, None, 0, -1, BIN_R, Some("IlLnr"), None),
BUILTIN("read", 0, Some(bin_read as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("cd:ek:%lnpqrst:%zu:AE"), None),
BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_READONLY, Some("AE:%F:%HL:%R:%TUZ:%afghi:%lptux"), Some("r")),
BUILTIN("rehash", 0, Some(bin_hash as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, Some("df"), Some("r")),
BUILTIN("return", BINF_PSPECIAL, Some(bin_break as crate::ported::zsh_h::HandlerFunc), 0, 1, BIN_RETURN, None, None),
BUILTIN("set", BINF_PSPECIAL | BINF_HANDLES_OPTS, Some(bin_set as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("setopt", 0, None, 0, -1, BIN_SETOPT, None, None),
BUILTIN("shift", BINF_PSPECIAL, Some(bin_shift as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("p"), None),
BUILTIN("source", BINF_PSPECIAL, Some(bin_dot as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("suspend", 0, None, 0, 0, 0, Some("f"), None),
BUILTIN("test", BINF_HANDLES_OPTS, Some(bin_test as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_TEST, None, None),
BUILTIN("ttyctl", 0, Some(bin_ttyctl as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, Some("fu"), None),
BUILTIN("limit", 0, None, 0, -1, 0, Some("sh"), None), BUILTIN("ulimit", 0, None, 0, -1, 0, None, None), BUILTIN("unlimit", 0, None, 0, -1, 0, Some("hs"), None), BUILTIN("times", BINF_PSPECIAL, Some(bin_times as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, None, None),
BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, Some(bin_trap as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("true", 0, Some(bin_true as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("type", 0, Some(bin_whence as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("ampfsSw"), Some("v")),
BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, Some(bin_typeset as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("AE:%F:%HL:%R:%TUZ:%afghi:%klp:%rtuxmnz"), None),
BUILTIN("umask", 0, Some(bin_umask as crate::ported::zsh_h::HandlerFunc), 0, 1, 0, Some("S"), None),
BUILTIN("unalias", 0, Some(bin_unhash as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_UNALIAS, Some("ams"), None),
BUILTIN("unfunction", 0, Some(bin_unhash as crate::ported::zsh_h::HandlerFunc), 1, -1, BIN_UNFUNCTION, Some("m"), Some("f")),
BUILTIN("unhash", 0, Some(bin_unhash as crate::ported::zsh_h::HandlerFunc), 1, -1, BIN_UNHASH, Some("adfms"), None),
BUILTIN("unset", BINF_PSPECIAL, Some(bin_unset as crate::ported::zsh_h::HandlerFunc), 1, -1, BIN_UNSET, Some("fmvn"), None),
BUILTIN("unsetopt", 0, None, 0, -1, BIN_UNSETOPT, None, None),
BUILTIN("wait", 0, Some(crate::ported::jobs::bin_fg as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_WAIT, None, None),
BUILTIN("whence", 0, Some(bin_whence as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("acmpvfsSwx:"), None),
BUILTIN("where", 0, Some(bin_whence as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("pmsSwx:"), Some("ca")),
BUILTIN("which", 0, Some(bin_whence as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("ampsSwx:"), Some("c")),
BUILTIN("zmodload", 0, Some(crate::ported::module::bin_zmodload as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("AFRILP:abcfdilmpsue"), None),
BUILTIN("zcompile", 0, None, 0, -1, 0, Some("tUMRcmzka"), None),
BUILTIN("zstyle", 0, Some(crate::ported::modules::zutil::bin_zstyle as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("LeLdgabsTtmnH"), None),
BUILTIN("zformat", 0, Some(crate::ported::modules::zutil::bin_zformat as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("Faf"), None),
BUILTIN("zparseopts", 0, Some(crate::ported::modules::zutil::bin_zparseopts as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("D-EFK-M-a:"), None),
BUILTIN("zregexparse", 0, Some(crate::ported::modules::zutil::bin_zregexparse as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("c"), None),
BUILTIN("cap", 0, Some(crate::ported::modules::cap::bin_cap as crate::ported::zsh_h::HandlerFunc), 0, 1, 0, None, None),
BUILTIN("getcap", 0, Some(crate::ported::modules::cap::bin_getcap as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("setcap", 0, Some(crate::ported::modules::cap::bin_setcap as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("pcre_compile", 0, Some(crate::ported::modules::pcre::bin_pcre_compile as crate::ported::zsh_h::HandlerFunc), 1, 1, 0, Some("aimx"), None),
BUILTIN("pcre_study", 0, Some(crate::ported::modules::pcre::bin_pcre_study as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, None, None),
BUILTIN("pcre_match", 0, None, 1, -1, 0, Some("ab:nv:"), None),
BUILTIN("ztcp", 0, Some(crate::ported::modules::tcp::bin_ztcp as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("acdflLtv"), None),
BUILTIN("ztie", 0, Some(crate::ported::modules::db_gdbm::bin_ztie as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("d:f:r"), None),
BUILTIN("zuntie", 0, Some(crate::ported::modules::db_gdbm::bin_zuntie as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, Some("u"), None),
BUILTIN("zgdbmpath", 0, Some(crate::ported::modules::db_gdbm::bin_zgdbmpath as crate::ported::zsh_h::HandlerFunc), 1, 1, 0, None, None),
BUILTIN("echoti", 0, Some(crate::ported::modules::terminfo::bin_echoti as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("fg", 0, Some(crate::ported::jobs::bin_fg as crate::ported::zsh_h::HandlerFunc), 0, -1, BIN_FG, None, None),
BUILTIN("kill", BINF_HANDLES_OPTS, Some(crate::ported::jobs::bin_kill as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, None, None),
BUILTIN("suspend", 0, Some(crate::ported::jobs::bin_suspend as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, Some("f"), None),
BUILTIN("bindkey", 0, Some(crate::ported::zle::zle_keymap::bin_bindkey as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("evaMldDANmrsLR"), None),
BUILTIN("vared", 0, Some(crate::ported::zle::zle_main::bin_vared as crate::ported::zsh_h::HandlerFunc), 1, 1, 0, Some("AaceghM:m:p:r:i:f:"), None),
BUILTIN("compadd", 0, Some(crate::ported::zle::complete::bin_compadd as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("J:V:1X:fnqQF:Wsi"), None),
BUILTIN("compset", 0, Some(crate::ported::zle::complete::bin_compset as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, Some("npqPS:"), None),
BUILTIN("zle", 0, Some(crate::ported::zle::zle_thingy::bin_zle as crate::ported::zsh_h::HandlerFunc), 0, -1, 0, Some("aAcCDfFIKlLmMNRTU"), None),
BUILTIN("mkdir", 0, Some(crate::ported::modules::files::bin_mkdir as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, Some("pm:"), None),
BUILTIN("rmdir", 0, Some(crate::ported::modules::files::bin_rmdir as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, None, None),
BUILTIN("ln", 0, Some(crate::ported::modules::files::bin_ln as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, Some("dfins"), None),
BUILTIN("rm", 0, Some(crate::ported::modules::files::bin_rm as crate::ported::zsh_h::HandlerFunc), 1, -1, 0, Some("dfiRrs"), None),
BUILTIN("chmod", 0, Some(crate::ported::modules::files::bin_chmod as crate::ported::zsh_h::HandlerFunc), 2, -1, 0, Some("Rs"), None),
BUILTIN("chown", 0, Some(crate::ported::modules::files::bin_chown as crate::ported::zsh_h::HandlerFunc), 2, -1, 0, Some("Rs"), None),
BUILTIN("sync", 0, Some(crate::ported::modules::files::bin_sync as crate::ported::zsh_h::HandlerFunc), 0, 0, 0, None, None),
]);
static builtintab: OnceLock<HashMap<String, &'static builtin>> = OnceLock::new();
pub static BUILTINS_DISABLED: std::sync::LazyLock< std::sync::Mutex<std::collections::HashSet<String>>
> = std::sync::LazyLock::new(|| {
std::sync::Mutex::new(std::collections::HashSet::new())
});
static SHFUNCTAB_INNER: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, usize>>>
= std::sync::OnceLock::new();
pub static MATCHEDNODES: std::sync::Mutex<Vec<String>> =
std::sync::Mutex::new(Vec::new());
pub static STOPMSG: 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 JOBSTATS: std::sync::Mutex<Vec<i32>> = std::sync::Mutex::new(Vec::new());
pub static SHELL_EXITING: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static EXIT_PENDING: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static EXIT_VAL: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static LASTVAL: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static TEST_TOK: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
const TEST_LEXERR: i32 = -1; const TEST_NULLTOK: i32 = 0;
const TEST_DBAR: i32 = 2; const TEST_DAMPER: i32 = 3; const TEST_BANG: i32 = 4; const TEST_INPAR: i32 = 5; const TEST_OUTPAR: i32 = 6; const TEST_INANG: i32 = 7; const TEST_OUTANG: i32 = 8; const TEST_STRING: i32 = 9;
pub static TESTARGS: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
pub static TESTARGS_IDX: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static TOKSTR: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static DOPRINTDIR: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static CHASINGLINKS: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static PPARAMS: std::sync::Mutex<Vec<String>> =
std::sync::Mutex::new(Vec::new());
pub static ZOPTIND: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(1);
pub static OPTCIND: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static INEVAL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LOOPS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static BREAKS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static CONTFLAG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static RETFLAG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub use crate::ported::params::locallevel as LOCALLEVEL;
pub static SOURCELEVEL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub use crate::ported::zsh_h::ZEXIT_NORMAL;
#[allow(non_snake_case)]
pub fn BUILTIN(
name: &str,
flags: u32,
handler: Option<crate::ported::zsh_h::HandlerFunc>,
min: i32,
max: i32,
funcid: i32,
optstr: Option<&str>,
defopts: Option<&str>,
) -> builtin {
builtin {
node: hashnode {
next: None,
nam: name.to_string(),
flags: flags as i32,
},
handlerfunc: handler,
minargs: min,
maxargs: max,
funcid,
optstr: optstr.map(|s| s.to_string()),
defopts: defopts.map(|s| s.to_string()),
}
}
static TRAPS_INNER: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, String>>>
= std::sync::OnceLock::new();
#[allow(non_snake_case)]
fn BIN_PREFIX(name: &str, flags: u32) -> builtin {
BUILTIN(name, flags | BINF_PREFIX, None, 0, 0, 0, None, None)
}
fn printf_format(fmt: &str, args: &[String]) -> String {
let (fmt, _) = getkeystring(fmt); let mut out = String::new();
let mut arg_i: usize = 0;
loop {
let prev = arg_i;
let mut iter = fmt.chars().peekable();
while let Some(c) = iter.next() {
if c != '%' {
out.push(c);
continue;
}
let mut spec = String::from("%");
loop {
match iter.peek() {
Some(&c) if matches!(c, '-' | '+' | ' ' | '#' | '0') => {
spec.push(c); iter.next();
}
_ => break,
}
}
while let Some(&c) = iter.peek() {
if c.is_ascii_digit() { spec.push(c); iter.next(); }
else { break; }
}
if iter.peek() == Some(&'.') {
spec.push('.'); iter.next();
while let Some(&c) = iter.peek() {
if c.is_ascii_digit() { spec.push(c); iter.next(); }
else { break; }
}
}
match iter.next() {
Some('%') => out.push('%'),
Some('s') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
spec.push('s');
out.push_str(&format_spec_str(&spec, &a));
arg_i += 1;
}
Some('d') | Some('i') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: i64 = a.parse().unwrap_or(0);
spec.push('d');
out.push_str(&format_spec_int(&spec, n));
arg_i += 1;
}
Some('u') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: u64 = a.parse().unwrap_or(0);
spec.push('u');
out.push_str(&format_spec_uint(&spec, n));
arg_i += 1;
}
Some('x') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: i64 = a.parse().unwrap_or(0);
spec.push('x');
out.push_str(&format!("{:x}", n));
arg_i += 1;
}
Some('X') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: i64 = a.parse().unwrap_or(0);
spec.push('X');
out.push_str(&format!("{:X}", n));
arg_i += 1;
}
Some('o') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: i64 = a.parse().unwrap_or(0);
spec.push('o');
out.push_str(&format!("{:o}", n));
arg_i += 1;
}
Some('f') | Some('F') | Some('g') | Some('G') | Some('e') | Some('E') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: f64 = a.parse().unwrap_or(0.0);
spec.push('f');
out.push_str(&format_spec_float(&spec, n));
arg_i += 1;
}
Some('c') => {
if let Some(a) = args.get(arg_i) {
if let Some(ch) = a.chars().next() { out.push(ch); }
}
arg_i += 1;
}
Some('q') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
out.push_str("edzputs(&a));
arg_i += 1;
}
Some('b') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let (s, _) = getkeystring_with(&a, GETKEYS_PRINT);
out.push_str(&s);
arg_i += 1;
}
Some(other) => { out.push('%'); out.push(other); }
None => out.push('%'),
}
}
if arg_i == prev || arg_i >= args.len() { break; }
}
out
}
fn format_spec_str(spec: &str, s: &str) -> String {
let (left_align, width, prec) = parse_width_prec(spec);
let truncated: &str = if let Some(p) = prec {
let end: usize = s.chars().take(p).map(|c| c.len_utf8()).sum();
&s[..end.min(s.len())]
} else { s };
let pad = width.saturating_sub(truncated.chars().count());
if left_align {
format!("{}{}", truncated, " ".repeat(pad))
} else {
format!("{}{}", " ".repeat(pad), truncated)
}
}
fn format_spec_int(spec: &str, n: i64) -> String {
let (left_align, width, _prec) = parse_width_prec(spec);
let zero_pad = spec.contains('0') && !left_align;
let body = n.to_string();
let pad = width.saturating_sub(body.chars().count());
if pad == 0 { body }
else if left_align { format!("{}{}", body, " ".repeat(pad)) }
else if zero_pad {
if let Some(rest) = body.strip_prefix('-') {
format!("-{}{}", "0".repeat(pad), rest)
} else { format!("{}{}", "0".repeat(pad), body) }
} else { format!("{}{}", " ".repeat(pad), body) }
}
fn format_spec_uint(spec: &str, n: u64) -> String {
format_spec_int(spec, n as i64)
}
fn format_spec_float(spec: &str, n: f64) -> String {
let (left_align, width, prec) = parse_width_prec(spec);
let p = prec.unwrap_or(6);
let body = format!("{:.*}", p, n);
let pad = width.saturating_sub(body.chars().count());
if pad == 0 { body }
else if left_align { format!("{}{}", body, " ".repeat(pad)) }
else { format!("{}{}", " ".repeat(pad), body) }
}
fn parse_width_prec(spec: &str) -> (bool, usize, Option<usize>) {
let s = spec.trim_start_matches('%');
let mut i = 0;
let bytes = s.as_bytes();
let mut left_align = false;
while i < bytes.len() && matches!(bytes[i], b'-' | b'+' | b' ' | b'#' | b'0') {
if bytes[i] == b'-' { left_align = true; }
i += 1;
}
let width_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() { i += 1; }
let width: usize = s[width_start..i].parse().unwrap_or(0);
let mut prec: Option<usize> = None;
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
let p_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() { i += 1; }
prec = Some(s[p_start..i].parse().unwrap_or(0));
}
(left_align, width, prec)
}
pub fn findcmd(name: &str, _docopy: i32, _default_path: i32) -> Option<String> { if name.contains('/') {
let p = std::path::Path::new(name);
return if p.is_file() { Some(name.to_string()) } else { None };
}
let path = crate::ported::params::getsparam("PATH")?;
for dir in path.split(':') {
if dir.is_empty() { continue; }
let candidate = format!("{}/{}", dir, name);
if std::path::Path::new(&candidate).is_file() {
return Some(candidate);
}
}
None
}
fn getsigidx(name: &str) -> i32 {
let s = name.strip_prefix("SIG").unwrap_or(name);
if let Ok(n) = s.parse::<i32>() {
return n;
}
match s {
"HUP" => 1, "INT" => 2, "QUIT" => 3, "ILL" => 4,
"TRAP" => 5, "ABRT" => 6, "FPE" => 8, "KILL" => 9,
"USR1" => 10, "SEGV" => 11, "USR2" => 12, "PIPE" => 13,
"ALRM" => 14, "TERM" => 15, "CHLD" => 17, "CONT" => 18,
"STOP" => 19, "TSTP" => 20, "TTIN" => 21, "TTOU" => 22,
"URG" => 23, "XCPU" => 24, "XFSZ" => 25, "VTALRM" => 26,
"PROF" => 27, "WINCH" => 28, "IO" => 29, "PWR" => 30,
"SYS" => 31, "EXIT" => 0,
_ => -1,
}
}
fn pat_enables(name: &str, argv: &[String], on: bool) -> i32 { let patp: Vec<&str> = argv.iter().map(|s| s.as_str()).collect();
crate::ported::pattern::pat_enables(name, &patp, on)
}
pub fn shfunctab_table() -> &'static std::sync::Mutex<std::collections::HashMap<String, usize>> {
SHFUNCTAB_INNER.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
pub fn traps_table() -> &'static std::sync::Mutex<std::collections::HashMap<String, String>> {
TRAPS_INNER.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
#[cfg(test)]
mod tests {
use crate::zsh_h::BINF_PREFIX;
use super::*;
#[test]
fn bin_trap_clear_undefined_signal_returns_nonzero() {
let empty = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_trap("trap",
&["-".into(), "BOGUS_NEVER_A_SIGNAL".into()],
&empty, 0);
assert_ne!(r, 0,
"trap - <undefined> must report error per c:7399 (got {})", r);
}
#[test]
fn bin_emulate_dispatches_on_first_char_per_c537() {
use crate::ported::zsh_h::{EMULATE_CSH, EMULATE_KSH, EMULATE_SH};
let empty = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let saved = crate::ported::options::emulation
.load(std::sync::atomic::Ordering::Relaxed);
for (name, expected) in [
("csh", EMULATE_CSH),
("ksh", EMULATE_KSH),
("sh", EMULATE_SH),
("rcsh", EMULATE_CSH), ("rksh", EMULATE_KSH), ("bash", EMULATE_SH), ] {
crate::ported::options::emulation
.store(0, std::sync::atomic::Ordering::Relaxed);
bin_emulate("emulate", &[name.into()], &empty, 0);
let bits = crate::ported::options::emulation
.load(std::sync::atomic::Ordering::Relaxed);
assert_eq!(bits, expected,
"emulate {} must set bits {:#x}, got {:#x}",
name, expected, bits);
}
crate::ported::options::emulation
.store(saved, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn bin_trap_clear_valid_signal_returns_zero() {
let empty = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_trap("trap", &["-".into(), "USR1".into()], &empty, 0);
assert_eq!(r, 0,
"trap - USR1 must succeed even with no prior trap (got {})", r);
}
#[test]
fn registration_table_matches_c_count() {
assert_eq!(BUILTINS.len(), 112,
"BUILTINS table size changed — bump count or update the eagerly-loaded-module list above");
}
#[test]
fn registration_table_contains_all_c_builtins() {
let c_names: &[&str] = &[
"-", ".", ":", "[",
"alias", "autoload", "bg", "break", "builtin", "bye",
"cd", "chdir", "command", "continue", "declare", "dirs",
"disable", "disown", "echo", "emulate", "enable", "eval",
"exec", "exit", "export", "false", "fc", "fg", "float",
"functions", "getln", "getopts", "hash", "hashinfo",
"history", "integer", "jobs", "kill", "let", "local",
"logout", "mem", "noglob", "patdebug", "popd", "print",
"printf", "pushd", "pushln", "pwd", "r", "read",
"readonly", "rehash", "return", "set", "setopt", "shift",
"source", "suspend", "test", "times", "trap", "true",
"ttyctl", "type", "typeset", "umask", "unalias",
"unfunction", "unhash", "unset", "unsetopt", "wait",
"whence", "where", "which", "zcompile", "zmodload",
];
assert_eq!(c_names.len(), 79,
"C builtin.c row count is 79 — recount if changed");
let table_names: std::collections::HashSet<&str> =
BUILTINS.iter().map(|b| b.node.nam.as_str()).collect();
for c_name in c_names {
assert!(table_names.contains(*c_name),
"missing C builtin '{}' from BUILTINS table", c_name);
}
}
#[test]
fn lookup_finds_known_builtins() {
for name in ["cd", "echo", "print", "fg", "bg", "jobs", "wait", "typeset", "test", "[", "."] {
assert!(createbuiltintable().get(name).copied().is_some(), "missing: {name}");
}
}
#[test]
fn lookup_misses_unknown() {
assert!(createbuiltintable().get("not-a-builtin-zZz").copied().is_none());
}
#[test]
fn prefix_entries_have_prefix_flag() {
for name in ["-", "builtin", "command", "exec", "noglob"] {
let b = createbuiltintable().get(name).copied().unwrap();
assert!(b.node.flags as u32 & BINF_PREFIX != 0, "{name} missing BINF_PREFIX");
}
}
#[test]
fn fixdir_canonicalizes_absolute_paths() {
assert_eq!(fixdir("/tmp/./foo"), "/tmp/foo");
assert_eq!(fixdir("/tmp//foo"), "/tmp/foo");
assert_eq!(fixdir("/tmp/bar/../foo"), "/tmp/foo");
assert_eq!(fixdir("/tmp/bar/baz/../.."), "/tmp");
}
#[test]
fn fixdir_drops_dotdot_past_root() {
assert_eq!(fixdir("/.."), "/");
assert_eq!(fixdir("/../.."), "/");
assert_eq!(fixdir("/foo/../../bar"), "/bar");
}
#[test]
fn fixdir_relative_keeps_leading_dotdot() {
assert_eq!(fixdir("../foo"), "../foo");
assert_eq!(fixdir("../../foo"), "../../foo");
assert_eq!(fixdir("foo/../bar"), "bar");
}
#[test]
fn fixdir_empty_collapses_to_dot() {
assert_eq!(fixdir("./"), ".");
assert_eq!(fixdir("foo/.."), ".");
}
#[test]
fn fixdir_empty_input_returns_empty() {
assert_eq!(fixdir(""), "");
}
#[test]
fn fg_dispatch_id_distinguishes_aliases() {
assert_eq!(createbuiltintable().get("fg").copied().unwrap().funcid, BIN_FG);
assert_eq!(createbuiltintable().get("bg").copied().unwrap().funcid, BIN_BG);
assert_eq!(createbuiltintable().get("jobs").copied().unwrap().funcid, BIN_JOBS);
assert_eq!(createbuiltintable().get("wait").copied().unwrap().funcid, BIN_WAIT);
assert_eq!(createbuiltintable().get("disown").copied().unwrap().funcid, BIN_DISOWN);
}
#[test]
fn fixdir_pops_dotdot_against_previous_component() {
assert_eq!(fixdir("/a/b/../c"), "/a/c");
assert_eq!(fixdir("/a/b/../../c"), "/c");
assert_eq!(fixdir("/foo/.."), "/");
}
#[test]
fn fixdir_drops_dot_components() {
assert_eq!(fixdir("/a/./b"), "/a/b");
assert_eq!(fixdir("./a"), "a");
assert_eq!(fixdir("./."), ".");
}
#[test]
fn fixdir_collapses_consecutive_slashes() {
assert_eq!(fixdir("/a//b"), "/a/b");
assert_eq!(fixdir("/a///b/c"), "/a/b/c");
}
#[test]
fn fixdir_dotdot_past_root_clamps_to_root() {
assert_eq!(fixdir("/.."), "/");
assert_eq!(fixdir("/../../a"), "/a");
}
#[test]
fn fixdir_relative_leading_dotdot_is_preserved() {
assert_eq!(fixdir("../foo"), "../foo");
assert_eq!(fixdir("../../foo"), "../../foo");
}
#[test]
fn fcgetcomm_numeric_zero_only_for_literal_zero_prefix() {
assert_eq!(fcgetcomm("0"), 0, "literal `0` is event 0");
assert_eq!(fcgetcomm("42"), 42);
assert_eq!(fcgetcomm("definitely_not_a_history_command_zshrs"), -1);
}
#[test]
fn cd_able_vars_returns_none_without_cdablevars_option() {
let r = cd_able_vars("HOME/anything");
if !crate::ported::zsh_h::isset(crate::ported::options::optlookup("cdablevars")) {
assert!(r.is_none());
}
}
#[test]
fn init_builtins_is_idempotent() {
init_builtins();
let count1 = createbuiltintable().len();
init_builtins();
let count2 = createbuiltintable().len();
assert_eq!(count1, count2, "init_builtins must not duplicate entries");
}
#[test]
fn fcsubs_applies_each_substitution_in_order() {
let mut s = "echo foo bar foo".to_string();
let n = fcsubs(&mut s, &[("foo".to_string(), "FOO".to_string())]);
assert_eq!(s, "echo FOO bar FOO");
assert_eq!(n, 2, "two `foo` matches replaced");
}
#[test]
fn fcsubs_skips_empty_pattern() {
let mut s = "anything".to_string();
let n = fcsubs(&mut s, &[("".to_string(), "X".to_string())]);
assert_eq!(s, "anything", "empty pattern must be skipped");
assert_eq!(n, 0);
}
#[test]
fn fcsubs_chains_substitutions_left_to_right() {
let mut s = "a".to_string();
let n = fcsubs(&mut s, &[
("a".to_string(), "b".to_string()),
("b".to_string(), "c".to_string()),
]);
assert_eq!(s, "c", "second sub sees post-first-sub text");
assert_eq!(n, 2);
}
#[test]
fn fcsubs_no_match_returns_zero_unchanged() {
let mut s = "hello world".to_string();
let n = fcsubs(&mut s, &[("xyz".to_string(), "abc".to_string())]);
assert_eq!(s, "hello world", "no match → unchanged");
assert_eq!(n, 0);
}
#[test]
fn fixdir_plain_relative_path_unchanged() {
assert_eq!(fixdir("subdir"), "subdir");
assert_eq!(fixdir("a/b/c"), "a/b/c");
assert_eq!(fixdir("."), ".");
}
static BIN_LET_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn bin_let_clears_errflag_on_math_error() {
let _g = BIN_LET_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
use crate::ported::utils::{errflag, ERRFLAG_ERROR};
use std::sync::atomic::Ordering;
let saved = errflag.load(Ordering::Relaxed);
errflag.store(0, Ordering::Relaxed);
let ops = crate::ported::zsh_h::options {
ind: [0; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let argv = vec!["1".to_string()];
assert_eq!(bin_let("let", &argv, &ops, 0), 0,
"c:7482 — last expr non-zero → return 0 (success)");
let argv = vec!["0".to_string()];
assert_eq!(bin_let("let", &argv, &ops, 0), 1,
"c:7482 — last expr zero → return 1 (failure)");
errflag.store(ERRFLAG_ERROR, Ordering::Relaxed);
let argv = vec!["1".to_string()];
let rc = bin_let("let", &argv, &ops, 0);
assert_eq!(rc, 2,
"c:7479 — pre-set ERRFLAG_ERROR triggers c:7476-7480 cleanup, returns 2");
assert_eq!(errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR, 0,
"c:7478 — ERRFLAG_ERROR must be CLEARED after let error");
errflag.store(saved, Ordering::Relaxed);
}
#[test]
fn bin_let_walks_all_argv_last_wins() {
let _g = BIN_LET_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
use crate::ported::utils::{errflag, ERRFLAG_ERROR};
use std::sync::atomic::Ordering;
errflag.store(0, Ordering::Relaxed);
let ops = crate::ported::zsh_h::options {
ind: [0; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let argv = vec!["5".to_string(), "0".to_string()];
assert_eq!(bin_let("let", &argv, &ops, 0), 1,
"c:7474 — last arg wins (here: 0 → return 1)");
let argv = vec!["0".to_string(), "5".to_string()];
assert_eq!(bin_let("let", &argv, &ops, 0), 0,
"c:7474 — last arg wins (here: 5 → return 0)");
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
}
}