use std::collections::HashMap;
use std::io::Read;
use std::sync::atomic::{Ordering, Ordering::Relaxed};
use std::sync::{Mutex, OnceLock};
#[allow(unused_imports)]
use std::{env, fs, io, io::Write, path::Path, path::PathBuf};
use crate::func_body_fmt::FuncBodyFmt;
#[allow(unused_imports)]
use crate::parse::{Redirect, ShellCommand};
use crate::ported::compat::zgetcwd;
use crate::ported::config_h::DEFAULT_PATH;
use crate::ported::exec::{getfpfunc, iscom, loadautofn, FORKLEVEL, TRAP_RETURN, TRAP_STATE};
use crate::ported::hashnameddir::{
addnameddirnode, emptynameddirtable, fillnameddirtable, nameddirtab, printnameddirnode,
};
use crate::ported::hashtable::{
aliastab_lock, cmdnamtab_lock, createaliasnode, dircache_set, emptycmdnamtable,
fillcmdnamtable, hnamcmp, printaliasnode, printcmdnamnode, printshfuncexpand, reswdtab_lock,
scanmatchshfunc, scanshfunc, shfunctab_lock, sufaliastab_lock,
};
use crate::DPUTS;
use indexmap::IndexMap;
use crate::ported::hist::{
addhistnum, gethistent, hcomsearch, histsiz, pushhiststack, quietgethist, readhistfile,
saveandpophiststack, savehistfile, savehistsiz,
};
use crate::ported::jobs::{bin_fg, removetrapnode};
use crate::ported::math::{matheval, mathevali, mnumber, MN_INTEGER};
use crate::ported::mem::{queue_signals, unqueue_signals};
use crate::ported::module::MATHFUNCS;
use crate::ported::modules::parameter::{DIRSTACK, FUNCSTACK};
use crate::ported::options::{dosetopt, emulation, optlookup, ZSH_OPTIONS_SET};
use crate::ported::params::{
createparam, getiparam, getsparam, isident, locallevel as locallevel_param, locallevel,
paramtab, printparamnode, setaparam, setiparam, setsparam, unsetparam, unsetparam_pm,
};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::signals::settrap;
use crate::ported::utils::{
argzero, errflag, fprintdir, getkeystring, getkeystring_with, getshfunc, gettempfile, lchdir,
print_if_link, printprompt4, quotedzputs, scriptname_get, set_argzero, zerr, zerrnam, zwarn,
zwarnnam, GETKEYS_ECHO, GETKEYS_PRINT,
};
#[allow(unused_imports)]
use crate::ported::vm_helper::{self, format_int_in_base, BUILTIN_NAMES};
use crate::ported::zle::compctl::compctlread;
use crate::ported::zsh_h::{
alias, asgment, builtin, cmdnam, eprog, hashnode, isset, mathfunc, nameddir, options, param,
shfunc, HandlerFunc, Meta, ALIAS_GLOBAL, ALIAS_SUFFIX, ASG_ARRAY, ASG_ARRAYP, ASG_KEY_VALUE,
ASG_VALUEP, AUTOPUSHD, BINF_ADDED, BINF_ASSIGN, BINF_BUILTIN, BINF_COMMAND, BINF_DASH,
BINF_DASHDASHVALID, BINF_EXEC, BINF_HANDLES_OPTS, BINF_KEEPNUM, BINF_MAGICEQUALS, BINF_NOGLOB,
BINF_PLUSOPTS, BINF_PREFIX, BINF_PRINTOPTS, BINF_PSPECIAL, BINF_SKIPDASH, BINF_SKIPINVALID,
BSDECHO, CDABLEVARS, CHASELINKS, CHECKRUNNINGJOBS, DISABLED, EMULATE_CSH, EMULATE_KSH,
EMULATE_SH, EMULATE_ZSH, EMULATION, ERRFLAG_ERROR, FS_FUNC, FUNCTIONARGZERO, GLOBALEXPORT,
HASHED, HFILE_APPEND, HFILE_SKIPOLD, HFILE_USE_OPTIONS, HIST_FOREIGN, INTERACTIVE, KSHARRAYS,
LOGINSHELL, MAX_OPS, MFF_STR, MFF_USERFUNC, MONITOR, NULLBINCMD, OPT_ARG, OPT_HASARG,
OPT_ISSET, OPT_MINUS, OPT_PLUS, PATHDIRS, PAT_HEAPDUP, PAT_STATIC, PM_ABSPATH_USED, PM_ARRAY,
PM_AUTOLOAD, PM_CUR_FPATH, PM_DECLARED, PM_DEFAULTED, PM_EFLOAT, PM_EXPORTED, PM_FFLOAT, PM_HASHED, PM_HIDE,
PM_HIDEVAL, PM_INTEGER, PM_KSHSTORED, PM_LEFT, PM_LOADDIR, PM_LOCAL, PM_LOWER, PM_NAMEREF,
PM_READONLY, PM_RIGHT_B, PM_RIGHT_Z, PM_RO_BY_DESIGN, PM_SCALAR, PM_SPECIAL, PM_TAGGED, PM_TAGGED_LOCAL,
PM_TIED, PM_TYPE, PM_UNALIASED, PM_UNDEFINED, PM_UNIQUE, PM_UNSET, PM_UPPER, PM_WARNNESTED,
PM_ZSHSTORED, POSIXBUILTINS, POSIXCD, POSIXTRAPS, PRINT_INCLUDEVALUE, PRINT_LINE, PRINT_LIST,
PRINT_NAMEONLY, PRINT_POSIX_EXPORT, PRINT_POSIX_READONLY, PRINT_TYPE, PRINT_TYPESET,
PRINT_WHENCE_CSH, PRINT_WHENCE_FUNCDEF, PRINT_WHENCE_SIMPLE, PRINT_WHENCE_VERBOSE,
PRINT_WHENCE_WORD, PRINT_WITH_NAMESPACE, PUSHDIGNOREDUPS, PUSHDMINUS, PUSHDSILENT, PUSHDTOHOME,
RCQUOTES, SHINSTDIN, STAT_LOCKED, STAT_NOPRINT, STAT_STOPPED, TRAP_STATE_FORCE_RETURN,
TRAP_STATE_PRIMED, TYPESETSILENT, TYPESET_OPTSTR, VERBOSE, XTRACE, ZEXIT_DEFERRED,
ZEXIT_NORMAL, ZEXIT_SIGNAL, ZSIG_FUNC,
};
#[allow(unused_imports)]
use crate::zwc::ZwcFile;
pub use crate::ported::hashtable_h::{
BIN_BG, BIN_BRACKET, BIN_BREAK, BIN_CD, BIN_COMMAND, BIN_CONTINUE, BIN_DISABLE, BIN_DISOWN,
BIN_ECHO, BIN_ENABLE, BIN_EVAL, BIN_EXIT, BIN_EXPORT, BIN_FC, BIN_FG, BIN_JOBS, BIN_LOGOUT,
BIN_POPD, BIN_PRINT, BIN_PRINTF, BIN_PUSHD, BIN_PUSHLINE, BIN_R, BIN_READONLY, BIN_RETURN,
BIN_SCHED, BIN_SETOPT, BIN_TEST, BIN_TYPESET, BIN_UNALIAS, BIN_UNFUNCTION, BIN_UNHASH,
BIN_UNSET, BIN_UNSETOPT, BIN_WAIT,
};
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 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; }
if (printflags & PRINT_WHENCE_VERBOSE as i32) != 0 {
println!("{} is a shell builtin", bn.nam); return; }
println!("{}", bn.nam); }
pub fn freebuiltinnode(hn: *mut hashnode) {
if hn.is_null() {
return;
}
let bn = unsafe { &*hn };
if (bn.flags as u32 & BINF_ADDED) == 0 { }
}
pub fn init_builtins() {
if !EMULATION(EMULATE_ZSH) {
if let Ok(mut tab) = reswdtab_lock().write() {
tab.disable("repeat");
}
}
}
pub const OPT_ALLOC_CHUNK: i32 = 16;
pub fn new_optarg(ops: &mut 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<asgment>, bn: *mut 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() {
DPUTS!(true, "Missing builtin detected too late"); 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 {
zwarnnam(&name, &format!("argument expected: -{}", execop as char)); return 1; }
}
if let Some(ap) = argptr {
if new_optarg(&mut ops) != 0 {
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 {
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 = errflag.load(Relaxed);
if (ef & ERRFLAG_ERROR) != 0 {
if !isset(INTERACTIVE) {
return 1;
}
errflag.fetch_and(!ERRFLAG_ERROR, Relaxed); return 1; }
if argc < bn_ref.minargs || (argc > bn_ref.maxargs && bn_ref.maxargs != -1)
{
zwarnnam(
&name, if argc < bn_ref.minargs {
"not enough arguments"
} else {
"too many arguments"
},
); return 1; }
if xtr {
let fullargv = &argarr; printprompt4(); eprint!("{}", name); let is_assign = (bn_ref.node.flags as u32 & BINF_ASSIGN) != 0;
for s in fullargv {
eprint!(" "); let mut emitted = false;
if is_assign {
let sbytes = s.as_bytes();
let mut i = 0usize;
if !sbytes.is_empty()
&& (sbytes[0].is_ascii_alphabetic() || sbytes[0] == b'_')
{
i = 1;
while i < sbytes.len()
&& (sbytes[i].is_ascii_alphanumeric() || sbytes[i] == b'_')
{
i += 1;
}
if i < sbytes.len() && sbytes[i] == b'[' {
let mut depth = 1i32;
i += 1;
while i < sbytes.len() && depth > 0 {
match sbytes[i] {
b'[' => depth += 1,
b']' => depth -= 1,
_ => {}
}
i += 1;
}
if depth != 0 {
i = 0; }
}
if i > 0 {
let (sep_len, sep) = if sbytes.get(i) == Some(&b'+')
&& sbytes.get(i + 1) == Some(&b'=')
{
(2usize, "+=")
} else if sbytes.get(i) == Some(&b'=') {
(1usize, "=")
} else {
(0, "")
};
if sep_len != 0 {
eprint!("{}{}{}", &s[..i], sep, quotedzputs(&s[i + sep_len..]));
emitted = true;
}
}
}
}
if !emitted {
eprint!("{}", quotedzputs(s)); }
}
for asg in &assigns {
eprint!(" "); eprint!("{}", quotedzputs(&asg.name)); if (asg.flags & ASG_ARRAY) != 0 {
eprint!("=("); if let Some(ref list) = asg.array {
if (asg.flags & 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!("{}", quotedzputs(k));
}
eprint!("]="); if let Some(v) = list.getdata(vidx) {
eprint!("{}", 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!("{}", quotedzputs(elem));
}
arrnode = list.nextnode(idx); }
}
}
eprint!(" )"); } else if let Some(ref scalar) = asg.scalar {
eprint!("="); eprint!("{}", 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: &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 => aliastab_lock()
.write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false),
Tab::SufAlias => sufaliastab_lock()
.write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false),
Tab::Reswd => {
let exists = reswdtab_lock()
.read()
.map(|t| t.get_including_disabled(nm).is_some())
.unwrap_or(false);
if !exists {
return false;
}
reswdtab_lock()
.write()
.map(|mut t| if on { t.enable(nm) } else { t.disable(nm) })
.unwrap_or(false)
}
Tab::Shfunc => {
let exists = 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 => aliastab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
Tab::SufAlias => sufaliastab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
Tab::Reswd => reswdtab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
Tab::Shfunc => 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() {
queue_signals(); let is_disabled = |nm: &str| -> bool {
match tab {
Tab::Alias => aliastab_lock()
.read()
.ok()
.and_then(|t| {
t.get_including_disabled(nm)
.map(|a| (a.node.flags & DISABLED as i32) != 0)
})
.unwrap_or(false),
Tab::SufAlias => sufaliastab_lock()
.read()
.ok()
.and_then(|t| {
t.get_including_disabled(nm)
.map(|a| (a.node.flags & DISABLED as i32) != 0)
})
.unwrap_or(false),
Tab::Reswd => reswdtab_lock()
.read()
.ok()
.and_then(|t| {
t.get_including_disabled(nm)
.map(|r| (r.node.flags & DISABLED as i32) != 0)
})
.unwrap_or(false),
Tab::Shfunc => shfunctab_lock()
.read()
.ok()
.and_then(|t| {
t.get_including_disabled(nm)
.map(|f| (f.node.flags & DISABLED as i32) != 0)
})
.unwrap_or(false),
Tab::Builtin => BUILTINS_DISABLED
.lock()
.map(|s| s.contains(nm))
.unwrap_or(false),
}
};
let mut all_names = collect_names(&tab);
all_names.sort();
for nm in all_names {
let dis = is_disabled(&nm);
let entry_flags = if dis { DISABLED as u32 } else { 0 };
if (entry_flags & flags1) == flags1 && (entry_flags & flags2) == 0 {
match tab {
Tab::Alias => {
let val = aliastab_lock()
.read()
.ok()
.and_then(|t| t.get_including_disabled(&nm).map(|a| a.text.clone()));
if let Some(v) = val {
println!("{}={}", nm, quotedzputs(&v));
} else {
println!("{}", nm);
}
}
Tab::SufAlias => {
let val = sufaliastab_lock()
.read()
.ok()
.and_then(|t| t.get_including_disabled(&nm).map(|a| a.text.clone()));
if let Some(v) = val {
println!("{}={}", nm, quotedzputs(&v));
} else {
println!("{}", nm);
}
}
_ => println!("{}", nm),
}
}
}
unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') {
for arg in argv {
queue_signals(); let pprog = patcompile(
arg, PAT_HEAPDUP,
None,
);
if let Some(prog) = pprog {
for nm in collect_names(&tab) {
if pattry(&prog, &nm) {
if toggle_one(&tab, &nm, enable) {
match_count += 1; }
}
}
} else {
zwarnnam(name, &format!("bad pattern : {}", arg)); returnval = 1; }
unqueue_signals(); }
if match_count == 0 {
returnval = 1; }
return returnval; }
queue_signals(); for arg in argv {
if !toggle_one(&tab, arg, enable) {
zwarnnam(name, &format!("no such hash table element: {}", arg)); returnval = 1; }
}
unqueue_signals(); returnval }
pub fn bin_set(
nam: &str,
args: &[String], _ops: &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] == "-"
{
dosetopt(VERBOSE, 0, 0); dosetopt(XTRACE, 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() {
crate::ported::options::printoptionstates(hadplus);
return 0;
}
argv[idx].clone()
};
let optno = optlookup(&optname); if optno == 0 {
zerrnam(nam, &format!("no such option: {}", optname));
unqueue_signals();
return 1;
} else if dosetopt(optno, if action { 1 } else { 0 }, 0) != 0
{
zerrnam(nam, &format!("can't change option: {}", optname));
unqueue_signals();
return 1;
}
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 = isset(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 {
zerrnam(nam, &format!("bad option: -{}", c)); } else if dosetopt(optno, if action { 1 } else { 0 }, 0) != 0
{
zerrnam(nam, &format!("can't change option: -{}", c)); }
}
ci += 1;
}
idx += 1; }
let _ = nam;
queue_signals();
let remaining = &argv[idx..];
if arrayname.is_none() {
if !hadopt && remaining.is_empty() {
let names: Vec<String> = {
let tab = paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| (pm.node.flags as u32 & PM_UNSET) == 0)
.map(|(k, _)| k.clone())
.collect()
};
let mut entries: Vec<(String, String)> = names
.into_iter()
.map(|k| {
let v = crate::ported::params::getsparam(&k).unwrap_or_default();
(k, v)
})
.collect();
entries.sort_by(|a, b| hnamcmp(&a.0, &b.0));
for (k, v) in entries {
let needs_quote_name = matches!(k.as_str(), "#" | "$" | "*" | "?" | "@");
let kq: String = if needs_quote_name {
format!("'{}'", k)
} else {
k.clone()
};
if hadplus {
println!("{}", kq);
} else if matches!(k.as_str(), "*" | "@" | "argv") {
let pp = PPARAMS.lock().ok();
let elems: Vec<String> = pp
.as_ref()
.map(|p| p.iter().cloned().collect())
.unwrap_or_default();
let body: String = elems
.iter()
.map(|e| quotedzputs(e))
.collect::<Vec<_>>()
.join(" ");
if elems.is_empty() {
println!("{}=( )", kq);
} else {
println!("{}=( {} )", kq, body);
}
} else {
println!("{}={}", kq, quotedzputs(&v));
}
}
}
if array != 0 {
let mut arr_entries: Vec<(String, Vec<String>)> = {
use {PM_ARRAY, PM_TYPE};
let tab = paramtab().read().unwrap();
tab.iter()
.filter(|(_, pm)| {
PM_TYPE(pm.node.flags as u32) == PM_ARRAY
&& (pm.node.flags as u32 & PM_UNSET) == 0
})
.map(|(k, pm)| (k.clone(), pm.u_arr.clone().unwrap_or_default()))
.collect()
};
arr_entries.sort_by(|a, b| hnamcmp(&a.0, &b.0)); for (k, arr) in arr_entries {
if hadplus {
println!("{}", k);
} else {
let quoted: Vec<String> = arr.iter().map(|v| quotedzputs(v)).collect();
println!("{}=({})", k, quoted.join(" "));
}
}
}
if remaining.is_empty() && !hadend {
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 = 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())); }
}
setaparam(&aname, new_arr);
} else {
if let Ok(mut pp) = PPARAMS.lock() {
*pp = sorted; }
}
unqueue_signals(); 0 }
pub fn bin_pwd(
_name: &str,
_argv: &[String], ops: &options,
_func: i32,
) -> i32 {
let chaselinks = isset(CHASELINKS);
if OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'P') || (chaselinks && !OPT_ISSET(ops, b'L'))
{
println!("{}", zgetcwd()); } else {
let pwd_param = getsparam("PWD");
let logical_pwd = pwd_param.as_deref().filter(|pwd| {
use std::os::unix::fs::MetadataExt;
let cwd = zgetcwd();
match (std::fs::metadata(pwd), std::fs::metadata(&cwd)) {
(Ok(a), Ok(b)) => a.dev() == b.dev() && a.ino() == b.ino(),
_ => false,
}
});
println!(
"{}",
logical_pwd.map(String::from).unwrap_or_else(zgetcwd)
); }
0 }
pub fn bin_dirs(
_name: &str,
argv: &[String], ops: &options,
_func: i32,
) -> i32 {
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 = getsparam("PWD").unwrap_or_else(|| zgetcwd());
if OPT_ISSET(ops, b'l') {
print!("{}", pwd); } else {
print!("{}", 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!("{}", fprintdir(entry)); }
}
}
unqueue_signals(); println!(); return 0; }
if let Ok(mut stack) = DIRSTACK.lock() {
stack.clear(); for arg in argv {
stack.push(arg.clone()); }
}
unqueue_signals(); 0 }
pub fn set_pwd_env() {
let pwd = getsparam("PWD").or_else(|| {
env::current_dir()
.ok()
.map(|p| p.to_string_lossy().into_owned())
});
if let Some(s) = pwd {
setsparam("PWD", &s); env::set_var("PWD", &s);
}
if let Some(s) = getsparam("OLDPWD") {
env::set_var("OLDPWD", &s);
}
}
pub fn bin_cd(
nam: &str,
argv: &[String], ops: &options,
func: i32,
) -> i32 {
let prev = DOPRINTDIR.load(Relaxed);
DOPRINTDIR.store(if prev == -1 { 1 } else { 0 }, Relaxed);
let chase = OPT_ISSET(ops, b'P') || (isset(CHASELINKS)
&& !OPT_ISSET(ops, b'L'));
CHASINGLINKS.store(chase as i32, Relaxed);
queue_signals();
let pre_pwd = getsparam("PWD").unwrap_or_else(|| zgetcwd());
let dest = cd_get_dest(nam, argv, OPT_ISSET(ops, b's'), func);
if dest.is_none() {
unqueue_signals(); return 1; }
let dest_raw = dest.unwrap();
if OPT_ISSET(ops, b's') {
if let Ok(meta) = fs::symlink_metadata(&dest_raw) {
if meta.file_type().is_symlink() {
zwarnnam(nam, &format!("{}: symbolic link", dest_raw));
unqueue_signals();
return 1;
}
}
}
let dest_path = match cd_do_chdir(nam, &dest_raw, OPT_ISSET(ops, b's') as i32) {
Some(p) => p,
None => {
unqueue_signals();
return 1;
}
};
let old = getsparam("PWD");
let mut skip_popd_n_cd = false;
{
let autopushd = isset(AUTOPUSHD);
let is_stack_rotate = argv.len() == 1
&& !isset(POSIXCD)
&& argv[0].len() > 1
&& (argv[0].starts_with('+') || argv[0].starts_with('-'))
&& argv[0][1..].chars().all(|c| c.is_ascii_digit());
if let Ok(mut d) = DIRSTACK.lock() {
if is_stack_rotate && (func == BIN_PUSHD || (func == BIN_CD && autopushd)) {
let dd: usize = argv[0][1..].parse().unwrap_or(0);
let pushdminus = isset(PUSHDMINUS);
let from_top = (argv[0].starts_with('+')) ^ pushdminus;
let m = d.len();
let n = m + 1;
let k = if from_top { dd } else { n - 1 - dd };
if k < n {
let mut full: Vec<String> = Vec::with_capacity(n);
full.push(pre_pwd.clone());
full.extend(d.iter().cloned());
let rotated: Vec<String> = full[k..]
.iter()
.chain(full[..k].iter())
.cloned()
.collect();
d.clear();
d.extend(rotated.into_iter().skip(1));
}
} else if is_stack_rotate && func == BIN_CD {
let dd: usize = argv[0][1..].parse().unwrap_or(0);
let pushdminus = isset(PUSHDMINUS);
let from_top = (argv[0].starts_with('+')) ^ pushdminus;
let m = d.len();
let n = m + 1;
let k = if from_top { dd } else { n - 1 - dd };
if k >= 1 && k - 1 < d.len() {
d.remove(k - 1);
}
} else if is_stack_rotate && func == BIN_POPD {
let dd: usize = argv[0][1..].parse().unwrap_or(0);
let pushdminus = isset(PUSHDMINUS);
let from_top = (argv[0].starts_with('+')) ^ pushdminus;
let m = d.len();
let n = m + 1;
let k = if from_top { dd } else { n - 1 - dd };
if k >= 1 && k - 1 < d.len() {
d.remove(k - 1);
skip_popd_n_cd = true;
}
} else if func == BIN_PUSHD || (func == BIN_CD && autopushd) {
let dup_skip =
isset(PUSHDIGNOREDUPS) && d.first().map(|s| *s == pre_pwd).unwrap_or(false);
if !dup_skip {
d.insert(0, pre_pwd.clone());
}
} else if func == BIN_POPD {
if !d.is_empty() {
d.remove(0);
}
}
}
}
if skip_popd_n_cd {
let _ = env::set_current_dir(&pre_pwd);
cd_new_pwd(func, 0, OPT_ISSET(ops, b'q') as i32);
unqueue_signals();
return 0;
}
if let Some(o) = old {
setsparam("OLDPWD", &o);
env::set_var("OLDPWD", &o);
}
let chase = CHASINGLINKS.load(Relaxed) != 0; let pwd: String = if chase {
match env::current_dir() {
Ok(c) => c.to_string_lossy().into_owned(),
Err(_) => dest_path.clone(),
}
} else if dest_path.starts_with('/') {
dest_path.clone()
} else {
let mut segs: Vec<&str> = if pre_pwd.is_empty() || pre_pwd == "/" {
Vec::new()
} else {
pre_pwd.trim_start_matches('/').split('/').collect()
};
for part in dest_path.split('/') {
match part {
"" | "." => {}
".." => {
segs.pop();
}
_ => segs.push(part),
}
}
if segs.is_empty() {
"/".to_string()
} else {
format!("/{}", segs.join("/"))
}
};
setsparam("PWD", &pwd);
env::set_var("PWD", &pwd);
cd_new_pwd(func, 0, OPT_ISSET(ops, b'q') as i32);
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 < 1 {
zwarnnam(nam, "directory stack empty");
return None;
}
return DIRSTACK.lock().ok().and_then(|d| d.first().cloned());
}
if func == BIN_PUSHD {
let pushdtohome = isset(PUSHDTOHOME);
if !pushdtohome {
let popped = DIRSTACK
.lock()
.ok()
.and_then(|mut d| if d.is_empty() { None } else { Some(d.remove(0)) });
if let Some(target) = popped {
return Some(target);
}
}
}
match getsparam("HOME") {
Some(h) if !h.is_empty() => Some(h),
_ => {
zwarnnam(nam, "HOME not set");
None
}
}
} else if argv.len() == 1 {
let arg = &argv[0];
DOPRINTDIR.fetch_add(1, Relaxed); let posixcd = isset(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 = isset(PUSHDMINUS);
let from_top = (arg.starts_with('+')) ^ pushdminus; let pwd_now = getsparam("PWD").unwrap_or_else(|| zgetcwd());
let resolved = DIRSTACK.lock().ok().and_then(|d| {
let m = d.len();
let n = m + 1; let k = if from_top {
if dd >= n {
return None;
}
dd
} else {
if dd >= n {
return None;
}
n - 1 - dd
};
if k == 0 {
Some(pwd_now.clone())
} else {
d.get(k - 1).cloned()
}
});
if resolved.is_none() {
zwarnnam(nam, "no such entry in dir stack");
}
return resolved;
}
if arg == "-" {
DOPRINTDIR.fetch_sub(1, Relaxed);
getsparam("OLDPWD")
} else {
Some(arg.clone()) }
} else {
let pwd = getsparam("PWD").unwrap_or_else(|| zgetcwd());
let pat = &argv[0];
let new_pat = &argv[1];
match pwd.find(pat.as_str()) {
None => {
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, Relaxed);
Some(out)
}
}
}
}
pub fn cd_do_chdir(cnam: &str, dest: &str, hard: i32) -> Option<String> {
let nocdpath = dest.starts_with("./") || dest == "." || dest.starts_with("../") || dest == "..";
if dest.starts_with('/') {
if let Some(ret) = cd_try_chdir("", dest, hard) {
return Some(ret);
}
let errno = io::Error::last_os_error().raw_os_error().unwrap_or(0);
let mut msg = crate::ported::compat::strerror(errno);
if errno != libc::EIO {
if let Some(c) = msg.chars().next() {
msg = format!("{}{}", c.to_ascii_lowercase(), &msg[c.len_utf8()..]);
}
}
zwarnnam(cnam, &format!("{}: {}", msg, dest));
return None;
}
let posix_cd = isset(POSIXCD);
let cdpath_str = getsparam("CDPATH").unwrap_or_default();
let cdpath: Vec<&str> = if cdpath_str.is_empty() {
Vec::new()
} else {
cdpath_str.split(':').collect()
};
let hasdot = !nocdpath && !posix_cd && cdpath.iter().any(|p| p.is_empty() || *p == ".");
if !hasdot && !posix_cd {
if let Some(ret) = cd_try_chdir("", dest, hard) {
return Some(ret);
}
}
if !nocdpath {
for pp in cdpath.iter() {
if let Some(ret) = cd_try_chdir(pp, dest, hard) {
if !pp.is_empty()
&& *pp != "."
&& DOPRINTDIR.load(Relaxed) > 0
&& isset(INTERACTIVE)
{
println!("{}", ret);
}
return Some(ret);
}
}
}
if posix_cd {
if let Some(ret) = cd_try_chdir("", dest, hard) {
return Some(ret);
}
}
if let Some(expanded) = cd_able_vars(dest) {
if let Some(ret) = cd_try_chdir("", &expanded, hard) {
return Some(ret);
}
}
zwarnnam(cnam, &format!("no such file or directory: {}", dest));
None
}
pub fn cd_able_vars(s: &str) -> Option<String> {
let cdablevars = isset(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;
}
getsparam(head).map(|val| format!("{}{}", val, tail))
}
pub fn cd_try_chdir(pfix: &str, dest: &str, hard: i32) -> Option<String> {
let pwd = getsparam("PWD").unwrap_or_default();
let mut buf = if !pfix.is_empty() {
if pfix.starts_with('/') {
if pfix.ends_with('/') {
format!("{}{}", pfix, dest)
} else {
format!("{}/{}", pfix, dest)
}
} else {
let pwd_trim = if pwd == "/" { "" } else { pwd.as_str() };
format!("{}/{}/{}", pwd_trim, pfix, dest)
}
} else if dest.starts_with('/') {
dest.to_string()
} else {
let pwd_trim = pwd.trim_end_matches('/');
format!("{}/{}", pwd_trim, dest)
};
if CHASINGLINKS.load(Relaxed) == 0 {
buf = fixdir(&buf); }
let _ = hard;
if lchdir(&buf).is_ok() {
return Some(buf);
}
if !pfix.is_empty() && !dest.starts_with('/') {
if lchdir(dest).is_ok() {
return Some(dest.to_string());
}
}
None }
pub fn cd_new_pwd(func: i32, _dir: usize, quiet: i32) {
if quiet == 0 && func != BIN_CD && isset(INTERACTIVE) && !isset(PUSHDSILENT) {
printdirstack();
}
}
pub fn printdirstack() {
let pwd = getsparam("PWD")
.or_else(|| {
env::current_dir()
.ok()
.and_then(|p| p.to_str().map(String::from))
})
.unwrap_or_default();
print!("{}", fprintdir(&pwd)); if let Ok(d) = DIRSTACK.lock() {
for entry in d.iter() {
print!(" {}", 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 = isset(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_in: &options,
func: i32,
) -> i32 {
let mut ops = ops_in.clone();
let ops = &mut ops;
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_param.load(Relaxed)
} else {
-1
};
hs = histsiz.load(Relaxed); shs = savehistsiz.load(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(_) => {
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(_) => {
zwarnnam(
"fc", "SAVEHIST must be an integer",
);
return 1; }
}
} else {
shs = hs; }
if !argv.is_empty() {
zwarnnam(
"fc", "too many arguments",
);
return 1; }
}
}
pushhiststack(Some(&hf), hs, shs, level); if !hf.is_empty() {
let stat_result = fs::metadata(&hf);
let should_read = match &stat_result {
Ok(_) => true, Err(e) => e.raw_os_error() != Some(libc::ENOENT), };
if should_read {
readhistfile(
Some(&hf),
1,
HFILE_USE_OPTIONS as i32,
);
}
}
return 0; }
if OPT_ISSET(ops, b'P') {
if !argv.is_empty() {
zwarnnam("fc", "too many arguments"); return 1; }
let popped = saveandpophiststack(-1, HFILE_USE_OPTIONS as i32); return if popped != 0 { 0 } else { 1 }; }
let mut pprog: Option<crate::ported::pattern::PatProg> = None;
let mut pprog_src: Option<String> = None;
if !argv.is_empty() && OPT_ISSET(ops, b'm') {
let pat = argv.remove(0);
match patcompile(
&pat, PAT_HEAPDUP,
None,
) {
Some(p) => {
pprog = Some(p);
pprog_src = Some(pat); }
None => {
zwarnnam(nam, "invalid match pattern"); return 1; }
}
}
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
};
readhistfile(
path.as_deref(),
1,
flags,
);
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
};
savehistfile(
path.as_deref(),
flags,
);
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;
} savehistfile(
path.as_deref(),
flags,
);
unqueue_signals(); return 0; }
if crate::ported::builtins::sched::zleactive.load(
Relaxed,
) != 0
{
unqueue_signals(); 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() {
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 {
unqueue_signals();
return 1; }
}
if !argv.is_empty() {
last = fcgetcomm(&argv.remove(0)); if last == -1 {
unqueue_signals();
return 1;
}
}
if !argv.is_empty() {
unqueue_signals();
zwarnnam("fc", "too many arguments"); return 1;
}
let curhist: i64 = crate::ported::hist::curhist.load(Relaxed) as i64;
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 io::stdout(),
ops,
first,
last,
&asgf,
pprog_src.as_deref(),
0,
);
unqueue_signals();
} else {
retval = 1; let fil_opt = gettempfile(Some("zshfc")); match fil_opt {
None => {
unqueue_signals(); zwarnnam(
"fc", &format!("can't open temp file: {}", io::Error::last_os_error()),
);
}
Some((fd, fil)) => {
unsafe {
libc::close(fd);
} if last >= curhist {
last = curhist - 1; if first > last {
unqueue_signals(); zwarnnam(
"fc", "current history line would recurse endlessly, aborted",
);
let _ = fs::remove_file(&fil); return 1; }
}
ops.ind[b'n' as usize] = 1; let out = 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, pprog_src.as_deref(), 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 {
getsparam("FCEDIT")
.or_else(|| getsparam("EDITOR"))
.unwrap_or_else(|| crate::ported::config_h::DEFAULT_FCEDIT.to_string())
};
unqueue_signals(); if fcedit(&editor, &fil) != 0 {
if crate::ported::input::stuff(&fil) != 0 {
zwarnnam(
"fc", &format!("{}: {}", io::Error::last_os_error(), fil),
);
} else {
retval = LASTVAL.load(
Relaxed,
);
}
}
} else {
unqueue_signals(); }
let _ = 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(Relaxed);
cmd = addhistnum(curh, cmd as i32, 1);
}
if cmd < 0 {
cmd = 0;
}
return cmd;
}
}
match hcomsearch(s) {
Some(n) => n,
None => {
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 Write, ops: &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 {
zwarnnam("fc", "history events can't be executed backwards, aborted");
return 1;
}
let near = if first < last { 1 } else { -1 };
let start_ev = match gethistent(first, near) {
Some(e) => e,
None => {
zwarnnam(
"fc",
&if first == last {
format!("no such event: {}", first)
} else {
"no events in that range".to_string()
},
);
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 quietgethist(ev) {
Some(e) => e,
None => break,
};
let line = entry.node.nam.clone();
if let Some(pat) = pprog {
let prog = patcompile(pat, 0, None);
let matched = prog.as_ref().map(|p| 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 dur = entry.ftim.saturating_sub(entry.stim).max(0);
let _ = write!(out, " {}:{:02}", dur / 60, dur % 60);
}
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('=') {
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 param, ops: &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 {
zwarnnam(name, &format!("bad base value: {}", a)); } else {
zwarnnam(name, &format!("bad precision value: {}", a)); }
return 1; }
};
if (on_u & PM_INTEGER) != 0 && (base < 2 || base > 36) {
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 param, ops: &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(_) => {
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 param,
func: i32,
mut on: i32,
mut off: i32,
_roff: i32,
asg: *mut asgment,
altpm: *mut param,
ops: &options,
_joinchar: i32,
) -> *mut param {
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 = locallevel_param.load(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 {
zwarnnam(
cname, &format!("{}: can't change type via subscript reference", pname),
);
} else {
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 & PM_SPECIAL) != 0 {
usepm = 2; }
let pm_level = pm_ref.as_ref().map(|p| p.level).unwrap_or(0);
let locallevel_v = locallevel_param.load(Relaxed);
if usepm != 0 && locallevel_v != pm_level && (on as u32 & PM_LOCAL) != 0 {
if (pm_flags & 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) == 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 & (PM_EFLOAT | PM_FFLOAT | PM_INTEGER)) != 0 {
zerrnam(
cname, &format!("{}: can't assign array value to non-array", pname),
);
return std::ptr::null_mut();
}
if (pm_flags & PM_SPECIAL) != 0 {
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)) & (PM_INTEGER
| PM_EFLOAT
| PM_FFLOAT
| PM_HASHED | PM_ARRAY | PM_TIED | PM_AUTOLOAD);
if chflags != 0 && chflags != (PM_EFLOAT | 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
{
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 & ASG_ARRAY) != 0;
let pm_is_arr = (PM_TYPE(pm_flags) & (PM_ARRAY | PM_HASHED)) != 0;
if array_assign && !pm_is_arr {
zerrnam(
cname, &format!("{}: inconsistent type for assignment", pname),
);
return std::ptr::null_mut();
}
}
}
if usepm != 0 && on == 0 && _roff == 0 && asg_ref.is_some_and(|a| !ASG_VALUEP(a)) {
let with_ns = if OPT_ISSET(ops, b'm') {
PRINT_WITH_NAMESPACE
} else {
0
};
if let Some(pm_r) = unsafe { pm.as_mut() } {
if OPT_ISSET(ops, b'p') {
printparamnode(pm_r, PRINT_TYPESET | with_ns);
} else if !OPT_ISSET(ops, b'g') && (!isset(TYPESETSILENT) || OPT_ISSET(ops, b'm'))
{
printparamnode(pm_r, PRINT_INCLUDEVALUE | with_ns);
}
}
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() } {
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: &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;
}
}
}
for ch in [b'i', b'E', b'F', b'A', b'a', b'r', b'x', b'g', b'l', b'u'] {
if OPT_ISSET(&ops, ch) && !letters.contains(ch as char) {
letters.push(ch as char);
}
}
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_param.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 mut returnval: i32 = 0; let mut printflags: i32 = PRINT_WITH_NAMESPACE; let hasargs = !argv.is_empty();
let posix = isset(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
{
zwarnnam(name, &format!("-{} not allowed with -n", ch)); }
bit <<= 1;
}
if OPT_MINUS(&ops, b'n') {
zwarnnam(name, "bad option: -n");
return 1;
} 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;
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) => {} _ => {
zwarnnam(name, &format!("bad argument to -p: {}", arg)); 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 printflags_final = printflags | if roff != 0 { PRINT_NAMEONLY } else { 0 }; let names: Vec<String> = {
let tab = paramtab().read().unwrap();
let mut names: Vec<String> = tab
.iter()
.filter(|(_, pm)| {
let f = pm.node.flags as u32;
if (f & PM_UNSET) != 0 {
return false;
}
if (f & PM_HIDE) != 0 {
return false;
}
let on_roff = (on as u32) | (roff as u32);
let on_roff_expanded = if (on_roff & PM_READONLY) != 0 {
on_roff | PM_RO_BY_DESIGN
} else {
on_roff
};
on_roff_expanded == 0 || (f & on_roff_expanded) != 0
})
.map(|(k, _)| k.clone())
.collect();
names.sort_by(|a, b| hnamcmp(a, b));
names
};
for k in names {
let mut pm_clone = match paramtab().read() {
Ok(tab) => match tab.get(&k) {
Some(pm) => pm.clone(),
None => continue,
},
Err(_) => continue,
};
printparamnode(&mut pm_clone, printflags_final); }
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 = isset(GLOBALEXPORT);
let ll_v = locallevel_param.load(Relaxed);
if globalexport {
ops.ind[b'g' as usize] = 1; } else if ll_v != 0 {
on |= PM_LOCAL; }
} else if !(OPT_ISSET(&ops, b'x') || OPT_ISSET(&ops, b'm')) {
on |= PM_LOCAL; }
}
let _ = off;
let is_hashed = (on & PM_HASHED) != 0; let is_array = (on & PM_ARRAY) != 0; const REJOIN_SEP: char = '\u{1f}';
let argv: Vec<String> = {
let mut out: Vec<String> = Vec::with_capacity(argv.len());
let mut i = 0;
while i < argv.len() {
let arg = &argv[i];
let open = arg.find("=(");
let is_open = open.is_some()
&& arg
.as_bytes()
.first()
.is_some_and(|b| b.is_ascii_alphabetic() || *b == b'_')
&& !arg.ends_with(')');
if is_open {
let mut depth: i32 = 0;
for c in arg.chars() {
if c == '(' {
depth += 1;
} else if c == ')' {
depth -= 1;
}
}
let mut buf = arg.clone();
let mut j = i + 1;
while depth > 0 && j < argv.len() {
buf.push(REJOIN_SEP);
buf.push_str(&argv[j]);
for c in argv[j].chars() {
if c == '(' {
depth += 1;
} else if c == ')' {
depth -= 1;
}
}
j += 1;
}
out.push(buf);
i = j;
} else {
out.push(arg.clone());
i += 1;
}
}
out
};
let argv = argv.as_slice();
let tied_mode = (on & PM_TIED) != 0;
if tied_mode {
if argv.len() < 2 {
zwarnnam(name, "-T requires names of scalar and array");
return 1;
}
if argv.len() > 3 {
zwarnnam(name, "too many arguments for -T");
return 1;
}
let (sname, sval_opt): (&str, Option<String>) = match argv[0].find('=') {
Some(i) => (&argv[0][..i], Some(argv[0][i + 1..].to_string())),
None => (argv[0].as_str(), None),
};
let (aname, aval_opt): (&str, Option<Vec<String>>) = {
let a = argv[1].as_str();
if let Some(eq_idx) = a.find("=(") {
let aname = &a[..eq_idx];
let rest = &a[eq_idx + 2..];
let inner = rest.trim_end_matches(')');
let parts: Vec<String> = inner
.split_whitespace()
.map(|s| s.to_string())
.collect();
(aname, Some(parts))
} else if let Some(eq_idx) = a.find('=') {
let _ = eq_idx;
zwarnnam(
name,
&format!("second argument of tie must be array: {}", a),
);
return 1;
} else {
(a, None)
}
};
if sname == aname {
zerrnam(name, &format!("can't tie a variable to itself: {}", sname));
return 1;
}
if sname.contains('[') || aname.contains('[') {
zerrnam(name, &format!("can't tie array elements: {}", sname));
return 1;
}
if sval_opt.is_some() && aval_opt.is_some() {
zerrnam(
name,
&format!("only one tied parameter can have value: {}", sname),
);
return 1;
}
let _joinchar: char = if argv.len() == 3 {
let s = argv[2].as_str();
if s.is_empty() {
':'
} else {
s.chars().next().unwrap()
}
} else {
':'
};
let existing_scalar: Option<String> = {
let from_tab = paramtab().read().ok().and_then(|t| {
t.get(sname).and_then(|p| p.u_str.clone())
});
from_tab.or_else(|| std::env::var(sname).ok())
};
let init_arr: Vec<String> = if let Some(arr) = aval_opt {
arr
} else if let Some(sval) = sval_opt.as_deref() {
sval.split(':').map(|s| s.to_string()).collect()
} else if let Some(old) = existing_scalar.as_deref() {
if old.is_empty() {
Vec::new()
} else {
old.split(':').map(|s| s.to_string()).collect()
}
} else {
Vec::new()
};
let mut apm = param::default();
apm.node.nam = aname.to_string();
apm.node.flags = (PM_ARRAY | PM_TIED) as i32;
apm.u_arr = Some(init_arr.clone());
apm.ename = Some(sname.to_string());
apm.level = locallevel.load(Relaxed) as i32;
let mut spm = param::default();
spm.node.nam = sname.to_string();
spm.node.flags = (PM_SCALAR | PM_TIED) as i32;
spm.ename = Some(aname.to_string());
spm.u_str = Some(init_arr.join(":"));
spm.level = locallevel.load(Relaxed) as i32;
fn tied_scalar_getfn_shim(pm: ¶m) -> String {
crate::ported::params::tiedarrgetfn(pm).join(":")
}
fn tied_scalar_setfn_shim(pm: &mut param, val: String) {
crate::ported::params::tiedarrsetfn(pm, Some(val))
}
fn tied_scalar_unsetfn_shim(pm: &mut param, exp: i32) {
crate::ported::params::tiedarrunsetfn(pm, exp)
}
spm.gsu_s = Some(Box::new(crate::ported::zsh_h::gsu_scalar {
getfn: tied_scalar_getfn_shim,
setfn: tied_scalar_setfn_shim,
unsetfn: tied_scalar_unsetfn_shim,
}));
if let Ok(mut tab) = paramtab().write() {
tab.insert(aname.to_string(), Box::new(apm));
tab.insert(sname.to_string(), Box::new(spm));
}
return 0;
}
if OPT_ISSET(&ops, b'm') && !argv.is_empty() {
if !OPT_ISSET(&ops, b'p') {
if (on | roff) == 0 {
printflags |= PRINT_TYPE; }
if on == 0 {
printflags |= PRINT_NAMEONLY; }
}
let single_flags: i32 = if OPT_ISSET(&ops, b'p') {
PRINT_TYPESET | PRINT_WITH_NAMESPACE
} else {
PRINT_INCLUDEVALUE | PRINT_WITH_NAMESPACE
};
let do_minus_print = OPT_ISSET(&ops, b'p')
|| (!OPT_ISSET(&ops, b'g')
&& (!isset(TYPESETSILENT) || OPT_ISSET(&ops, b'm')));
for pattern in argv.iter() {
let pat = crate::ported::pattern::patcompile(
pattern,
crate::ported::zsh_h::PAT_HEAPDUP as i32,
None,
);
let pat = match pat {
Some(p) => p,
None => {
zwarnnam(name, &format!("bad pattern: {}", pattern));
returnval = 1;
continue;
}
};
let names: Vec<String> = {
let tab = paramtab().read().unwrap();
let on_roff = (on as u32) | (roff as u32);
let mut names: Vec<String> = tab
.iter()
.filter(|(k, pm)| {
let f = pm.node.flags as u32;
if (f & PM_UNSET) != 0 {
return false;
}
if (f & PM_HIDE) != 0 {
return false;
}
if on_roff != 0 && (f & on_roff) == 0 {
return false;
}
crate::ported::pattern::pattry(&pat, k)
})
.map(|(k, _)| k.clone())
.collect();
names.sort_by(|a, b| hnamcmp(a, b));
names
};
if OPT_PLUS(&ops, b'm') {
for k in names {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(&k) {
printparamnode(pm, printflags);
}
}
}
continue;
}
if do_minus_print {
for k in names {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(&k) {
printparamnode(pm, single_flags);
}
}
}
}
}
unqueue_signals();
return returnval;
}
let mut tied_name_count: usize = 0;
for arg in argv {
if tied_mode {
tied_name_count += 1;
if tied_name_count > 2 {
continue;
}
}
let arg_name: &str = match arg.find('=') {
Some(i) => &arg[..i],
None => arg.as_str(),
};
let pname_in_tab = paramtab()
.read()
.map(|t| t.get(arg_name).is_some())
.unwrap_or(false);
let first_is_digit = arg_name
.as_bytes()
.first()
.is_some_and(|b| b.is_ascii_digit());
let pname_valid =
(isident(arg_name) || pname_in_tab) && (!first_is_digit || arg_name == "0");
if !pname_valid {
if first_is_digit {
zerrnam(
name, &format!("not an identifier: {}", arg_name),
);
} else {
zerrnam(
name, &format!("not valid in this context: {}", arg_name),
);
}
continue; }
if !arg.contains('=') && OPT_ISSET(&ops, b'p') {
let with_ns = if OPT_ISSET(&ops, b'm') {
PRINT_WITH_NAMESPACE
} else {
0
};
let existed = paramtab()
.read()
.map(|t| t.contains_key(arg_name))
.unwrap_or(false);
if existed {
let mut pm_clone = match paramtab().read() {
Ok(tab) => tab.get(arg_name).cloned(),
Err(_) => None,
};
if let Some(ref mut pm) = pm_clone {
printparamnode(pm, PRINT_TYPESET | with_ns);
}
} else {
zwarnnam(name, &format!("no such variable: {}", arg_name));
returnval = 1; }
continue;
}
let cur_locallevel = locallevel.load(Relaxed) as i32;
let pm_reuse_local: bool = if pname_in_tab {
let tab = paramtab().read().unwrap();
let pm = tab.get(arg_name).unwrap();
let f = pm.node.flags as u32;
((f & PM_UNSET) == 0 || (f & PM_DECLARED) != 0)
&& (cur_locallevel == pm.level || (on as u32 & PM_LOCAL) == 0) } else {
true
};
let _ = pm_reuse_local;
if (on as u32 & PM_NAMEREF) != 0 && pname_in_tab {
let level_compare = paramtab()
.read()
.ok()
.and_then(|t| t.get(arg_name).map(|pm| pm.level))
.unwrap_or(0);
if level_compare >= cur_locallevel
|| ((on as u32 & PM_LOCAL) == 0 && level_compare < cur_locallevel)
{
}
}
let needs_new_shadow = if pname_in_tab {
paramtab()
.read()
.ok()
.and_then(|t| t.get(arg_name).map(|pm| pm.level < cur_locallevel))
.unwrap_or(true)
} else {
true
};
if (on as u32 & PM_LOCAL) != 0 && !arg_name.is_empty()
&& !arg_name.starts_with('-')
&& !arg_name.starts_with('+')
&& needs_new_shadow
{
let kind = if is_hashed {
PM_HASHED
} else if is_array {
PM_ARRAY
} else {
0
};
let _ = createparam(arg_name, kind as i32 | PM_LOCAL as i32);
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg_name) {
pm.level = cur_locallevel; let pre_assign_mask: u32 =
PM_UNIQUE | PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_LOWER | PM_UPPER;
pm.node.flags |= (on as u32 & pre_assign_mask) as i32;
pm.node.flags &= !((off as u32 & pre_assign_mask) as i32);
if !arg.contains('=')
&& isset(crate::ported::zsh_h::TYPESETTOUNSET)
{
pm.node.flags |= PM_DEFAULTED as i32;
}
}
}
}
if let Some(br) = arg_name.find('[') {
let base = &arg_name[..br];
if (on as u32 & PM_LOCAL) != 0 && cur_locallevel != 0 {
let pm_level = paramtab()
.read()
.ok()
.and_then(|t| t.get(base).map(|pm| pm.level));
if pm_level.is_none() || pm_level != Some(cur_locallevel) {
zerrnam(
name,
&format!("{}: can't create local array elements", base), );
continue; }
}
let pm_type_bits = on as u32
& (PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_ARRAY | PM_HASHED);
if pm_type_bits != 0 {
zerrnam(
name,
&format!(
"{}: inconsistent array element or slice assignment",
arg_name
),
);
continue;
}
}
if let Some(eq) = arg.find('=') {
let n = &arg[..eq];
let pre_assign_off_mask =
(PM_UNIQUE | PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_LOWER | PM_UPPER) as i32;
let off_in_pre_mask = (off as i32) & pre_assign_off_mask;
if off_in_pre_mask != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags &= !off_in_pre_mask;
}
}
}
let raw_v = &arg[eq + 1..];
let is_paren_init = raw_v.starts_with('(') && raw_v.ends_with(')') && raw_v.len() >= 2;
let mut on = on;
if is_paren_init && crate::ported::zsh_h::PM_TYPE(on) == PM_SCALAR {
on |= PM_ARRAY;
}
if is_paren_init && (on & (PM_ARRAY | PM_HASHED)) == 0 {
zerrnam(
name, &format!("{}: inconsistent type for assignment", n),
);
return 1;
}
if is_paren_init {
let inner = &raw_v[1..raw_v.len() - 1]; let split_elems: Vec<String> = if inner.contains('\u{1f}') {
inner
.split('\u{1f}')
.filter(|s| !s.chars().all(|c| c.is_ascii_whitespace()) || s.is_empty())
.map(|s| s.trim_matches(|c: char| c == ' ' || c == '\t' || c == '\n').to_string())
.filter(|s| !s.is_empty() || true) .collect::<Vec<_>>()
.into_iter()
.collect::<Vec<_>>()
} else {
inner.split_whitespace().map(String::from).collect()
};
let split_elems = if inner.contains('\u{1f}') {
let mut v: Vec<String> = split_elems;
if v.first().is_some_and(|s| s.is_empty()) {
v.remove(0);
}
if v.last().is_some_and(|s| s.is_empty()) {
v.pop();
}
v
} else {
split_elems
};
let raw_elems: Vec<String> = {
let ifs_chars: Vec<char> = crate::ported::params::getsparam("IFS")
.unwrap_or_else(|| " \t\n".to_string())
.chars()
.collect();
let mut out: Vec<String> = Vec::new();
for se in split_elems {
if let Some(stripped) = se.strip_prefix("$=") {
let val =
crate::ported::params::getsparam(stripped).unwrap_or_default();
if ifs_chars.is_empty() {
if !val.is_empty() {
out.push(val);
}
} else {
for field in val
.split(|c: char| ifs_chars.contains(&c))
.filter(|s| !s.is_empty())
{
out.push(field.to_string());
}
}
} else {
out.push(se);
}
}
out
};
let mut elems: Vec<String> = Vec::with_capacity(raw_elems.len());
for re in raw_elems {
if crate::ported::pattern::haswilds(&re) {
let compilable = crate::ported::pattern::patcompile(
&re,
crate::ported::zsh_h::PAT_HEAPDUP as i32,
None,
)
.is_some();
if !compilable {
elems.push(re);
continue;
}
let expanded = crate::ported::glob::glob_path(&re);
if expanded.is_empty() || (expanded.len() == 1 && expanded[0] == re) {
elems.push(re);
} else {
elems.extend(expanded);
}
} else {
elems.push(re);
}
}
if is_array {
let pre_assign_mask: u32 = PM_UNIQUE;
if (on as u32 & pre_assign_mask) != 0 {
let exists = paramtab()
.read()
.map(|t| t.contains_key(n))
.unwrap_or(false);
if !exists {
let _ = createparam(n, PM_ARRAY as i32);
}
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags |= (on as u32 & pre_assign_mask) as i32;
}
}
}
}
if is_hashed {
let bracket_shape = !elems.is_empty()
&& elems.iter().all(|e| e.starts_with('[') && e.contains("]="));
let mut map: IndexMap<String, String> = 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); }
}
crate::ported::exec_hooks::set_assoc(n, map.clone());
} else {
crate::ported::exec_hooks::set_array(n, elems.clone());
}
let post_assign_mask = (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE) as i32;
let post_assign_to_set = (on
& (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE)) as i32;
if post_assign_to_set != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags =
(pm.node.flags & !post_assign_mask) | post_assign_to_set;
}
}
}
} 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()
};
let pre_assign_mask =
(PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_LOWER | PM_UPPER | PM_NAMEREF) as i32;
let pre_assign_to_set = (on
& (PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_LOWER | PM_UPPER | PM_NAMEREF))
as i32;
if pre_assign_to_set != 0 {
let pname_in_tab = paramtab()
.read()
.map(|t| t.contains_key(n))
.unwrap_or(false);
if !pname_in_tab {
let _ = createparam(n, pre_assign_to_set);
} else {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags =
(pm.node.flags & !pre_assign_mask) | pre_assign_to_set;
}
}
}
}
setsparam(n, &folded); let post_assign_mask = (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE) as i32;
let post_assign_to_set = (on
& (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE)) as i32;
if post_assign_to_set != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.node.flags =
(pm.node.flags & !post_assign_mask) | post_assign_to_set;
}
}
}
{
let prec_arg: Option<&str> = if (on & PM_INTEGER) != 0 && OPT_HASARG(&ops, b'i')
{
OPT_ARG(&ops, b'i')
} else if (on & PM_EFLOAT) != 0 && OPT_HASARG(&ops, b'E') {
OPT_ARG(&ops, b'E')
} else if (on & PM_FFLOAT) != 0 && OPT_HASARG(&ops, b'F') {
OPT_ARG(&ops, b'F')
} else {
None
};
if let Some(s) = prec_arg {
if let Ok(b) = s.trim().parse::<i32>() {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.base = b;
}
}
}
}
}
{
let width_arg: Option<&str> =
if (on as u32 & PM_LEFT) != 0 && OPT_HASARG(&ops, b'L') {
OPT_ARG(&ops, b'L')
} else if (on as u32 & PM_RIGHT_B) != 0 && OPT_HASARG(&ops, b'R') {
OPT_ARG(&ops, b'R')
} else if (on as u32 & PM_RIGHT_Z) != 0 && OPT_HASARG(&ops, b'Z') {
OPT_ARG(&ops, b'Z')
} else {
None
};
if let Some(s) = width_arg {
if let Ok(w) = s.trim().parse::<i32>() {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(n) {
pm.width = w;
}
}
}
}
}
let already_exported = env::var_os(n).is_some();
if (on & PM_EXPORTED) != 0 || already_exported {
env::set_var(n, &folded); }
}
} else if is_hashed || is_array {
if is_hashed {
if crate::ported::exec_hooks::assoc(arg).is_none() {
crate::ported::exec_hooks::set_assoc(arg, IndexMap::new());
}
} else if crate::ported::exec_hooks::array(arg).is_none() {
crate::ported::exec_hooks::set_array(arg, Vec::new());
}
if is_array && (on as u32 & PM_UNIQUE) != 0 {
let current = crate::ported::exec_hooks::array(arg).unwrap_or_default();
let deduped = {
let mut seen = std::collections::HashSet::new();
current
.into_iter()
.filter(|x| seen.insert(x.clone()))
.collect::<Vec<_>>()
};
crate::ported::exec_hooks::set_array(arg, deduped);
}
let post_assign_mask = (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE
| PM_HASHED
| PM_ARRAY) as i32;
let post_assign_to_set = (on
& (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE
| PM_HASHED
| PM_ARRAY)) as i32;
if post_assign_to_set != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg_name) {
pm.node.flags = (pm.node.flags & !post_assign_mask) | post_assign_to_set;
}
}
}
let off_in_mask = (off as i32) & post_assign_mask;
if off_in_mask != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg_name) {
pm.node.flags &= !off_in_mask;
}
}
}
} else {
let pre_assign_mask =
(PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_LOWER | PM_UPPER | PM_NAMEREF) as i32;
let pre_assign_to_set = (on
& (PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_LOWER | PM_UPPER | PM_NAMEREF))
as i32;
let post_assign_mask = (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE) as i32;
let post_assign_to_set = (on
& (PM_READONLY
| PM_EXPORTED
| PM_LEFT
| PM_RIGHT_B
| PM_RIGHT_Z
| PM_TAGGED
| PM_HIDE
| PM_HIDEVAL
| PM_UNIQUE)) as i32;
let saved_val = getsparam(arg);
let already_typed = paramtab().read().ok().and_then(|t| {
t.get(arg).map(|pm| {
let f = pm.node.flags as u32;
let typ = PM_TYPE(f);
typ == PM_HASHED || typ == PM_ARRAY
})
}).unwrap_or(false);
let was_fresh = saved_val.is_none() && !already_typed;
if was_fresh {
setsparam(arg, ""); if isset(crate::ported::zsh_h::TYPESETTOUNSET) {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags |= PM_DEFAULTED as i32;
}
}
}
let _ = was_fresh;
}
let pre_assign_to_clear = (off
& (PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_LOWER | PM_UPPER | PM_NAMEREF))
as i32;
if pre_assign_to_clear != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags &= !pre_assign_to_clear;
}
}
if let Some(ref val) = saved_val {
setsparam(arg, val);
}
}
if pre_assign_to_set != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags = (pm.node.flags & !pre_assign_mask) | pre_assign_to_set;
}
}
if let Some(ref val) = saved_val {
setsparam(arg, val);
}
}
{
let prec_arg: Option<&str> = if (on & PM_INTEGER) != 0 && OPT_HASARG(&ops, b'i') {
OPT_ARG(&ops, b'i') } else if (on & PM_EFLOAT) != 0 && OPT_HASARG(&ops, b'E') {
OPT_ARG(&ops, b'E') } else if (on & PM_FFLOAT) != 0 && OPT_HASARG(&ops, b'F') {
OPT_ARG(&ops, b'F') } else {
None
};
if let Some(s) = prec_arg {
if let Ok(b) = s.trim().parse::<i32>() {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.base = b; }
}
}
}
}
if post_assign_to_set != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags = (pm.node.flags & !post_assign_mask) | post_assign_to_set;
}
}
if (post_assign_to_set as u32 & PM_UNIQUE) != 0 {
let existing = crate::ported::params::getaparam(arg);
if let Some(arr) = existing {
let mut seen: std::collections::HashSet<String> =
std::collections::HashSet::new();
let deduped: Vec<String> = arr
.into_iter()
.filter(|e| seen.insert(e.clone()))
.collect();
crate::ported::params::setaparam(arg, deduped);
}
}
if (on as u32 & PM_EXPORTED) != 0 {
let is_array_or_hashed = paramtab()
.read()
.ok()
.and_then(|t| t.get(arg).map(|pm| pm.node.flags as u32))
.map_or(false, |f| (f & (PM_ARRAY | PM_HASHED)) != 0)
|| crate::ported::params::paramtab_hashed_storage()
.lock()
.ok()
.map_or(false, |s| s.contains_key(arg))
|| crate::ported::exec_hooks::array(arg).is_some()
|| crate::ported::exec_hooks::assoc(arg).is_some();
if !is_array_or_hashed {
if let Some(val) = saved_val.as_deref().or(Some("")) {
env::set_var(arg, val);
}
}
}
}
if (off as u32 & PM_EXPORTED) != 0 {
env::remove_var(arg);
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags &= !(PM_EXPORTED as i32);
}
}
}
let off_in_mask = (off as i32) & post_assign_mask;
if off_in_mask != 0 {
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(arg) {
pm.node.flags &= !off_in_mask;
}
}
}
let user_on = (on as u32) & !PM_LOCAL;
let at_top_scope = locallevel.load(Relaxed) == 0;
if user_on == 0
&& off == 0
&& at_top_scope
&& !OPT_ISSET(&ops, b'p')
&& !OPT_ISSET(&ops, b'g')
&& (!isset(TYPESETSILENT) || OPT_ISSET(&ops, b'm'))
&& pname_in_tab
{
let with_ns = if OPT_ISSET(&ops, b'm') {
PRINT_WITH_NAMESPACE
} else {
0
};
let _ = with_ns;
let assoc = crate::ported::params::paramtab_hashed_storage()
.lock()
.ok()
.and_then(|s| s.get(arg).cloned())
.or_else(|| crate::ported::exec_hooks::assoc(arg));
if let Some(map) = assoc {
let mut entries: Vec<(&String, &String)> = map.iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
let mut s = format!("{}=( ", arg);
let mut first = true;
for (k, v) in entries {
if !first {
s.push(' ');
}
first = false;
s.push_str(&format!(
"[{}]={}",
k,
crate::ported::utils::quotedzputs(v)
));
}
s.push_str(" )");
println!("{}", s);
} else if let Some(arr) = crate::ported::params::getaparam(arg)
.or_else(|| crate::ported::exec_hooks::array(arg))
.or_else(|| {
paramtab()
.read()
.ok()
.and_then(|t| t.get(arg).and_then(|pm| pm.u_arr.clone()))
.filter(|v| !v.is_empty())
})
{
let parts: Vec<String> = arr
.iter()
.map(|v| crate::ported::utils::quotedzputs(v))
.collect();
println!("{}=( {} )", arg, parts.join(" "));
} else if let Some(val) = getsparam(arg) {
println!("{}={}", arg, crate::ported::utils::quotedzputs(&val));
}
}
}
}
unqueue_signals();
returnval
}
pub fn eval_autoload(
shf: *mut shfunc,
name: &str, ops: &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![
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');
loadautofn(shf, mode, 1, _d as i32) }
pub fn check_autoload(
shf: *mut shfunc,
name: &str, ops: &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 getfpfunc(
&shf_mut.node.nam,
&mut None, Some(&spec),
1,
)
.is_some()
{
return 0; }
if !OPT_ISSET(ops, b'd') {
if want_R {
zerr(&format!(
"{}: function definition file not found",
shf_mut.node.nam
)); return 1; }
return 0; }
}
let mut dir_path: Option<String> = None;
if getfpfunc(&shf_mut.node.nam, &mut dir_path, None, 1).is_some() && dir_path.is_some()
{
let mut old_slot = shf_mut.filename.take();
dircache_set(&mut old_slot, None); let dp = dir_path.unwrap();
let mut new_slot: Option<String> = None;
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 {
zerr(&format!(
"{}: function definition file not found",
shf_mut.node.nam
)); return 1; }
}
0 }
pub fn listusermathfunc(p: &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!("{}", quotedzputs(p.module.as_deref().unwrap_or(""))); showargs -= 1; }
println!(); }
pub fn add_autoload_function(
shf: *mut 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();
dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
dircache_set(&mut new_slot, Some(&dir)); shf_ref.filename = new_slot;
shf_ref.node.flags |= (PM_LOADDIR | PM_ABSPATH_USED) as i32; let _ = nam;
if let Ok(mut t) = shfunctab_lock().write() {
t.addnode(shf); }
} else {
let calling_f: Option<String> = {
let stack = 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_lock()
.read()
.map(|t| t.getnode2(&cf))
.unwrap_or(std::ptr::null_mut());
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();
dircache_set(&mut old_slot, None); let dir2c = dir2.clone();
let mut new_slot: Option<String> = None;
dircache_set(&mut new_slot, Some(&dir2c)); shf_ref.filename = new_slot;
shf_ref.node.flags |= (PM_LOADDIR | PM_ABSPATH_USED) as i32;
}
}
}
}
}
}
}
unsafe {
if !shf.is_null() {
(*shf).node.nam = funcname.to_string();
}
}
if let Ok(mut t) = shfunctab_lock().write() {
t.addnode(shf); }
}
}
pub fn bin_functions(
name: &str,
argv: &[String], ops: &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 = 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')))
{
zwarnnam(name, "invalid option(s)"); return 1; }
if OPT_ISSET(ops, b'c') {
if argv.len() < 2 || argv.len() > 2 {
zwarnnam(name, "-c: requires two arguments"); return 1;
}
let src_name = &argv[0];
let dst_name = &argv[1];
let src_ptr = shfunctab_lock()
.read()
.map(|t| t.getnode(src_name.as_str()))
.unwrap_or(std::ptr::null_mut());
if src_ptr.is_null() {
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 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 settrap(sigidx, None, ZSIG_FUNC) != 0 {
return 1; }
removetrapnode(sigidx); }
}
let newsh = unsafe {
let mut copy = (*src_ptr).clone();
copy.node.nam = dst_name.clone();
Box::into_raw(Box::new(copy))
};
if let Ok(mut t) = shfunctab_lock().write() {
t.addnode(newsh); }
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(_) => {
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 |= 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')
{
zwarnnam(name, "invalid option(s)"); return 1; }
if argv.is_empty() {
queue_signals(); if let Ok(table) = MATHFUNCS.lock() {
for p in table.iter() {
if (p.flags & MFF_USERFUNC) != 0 {
listusermathfunc(p); }
}
}
unqueue_signals(); return returnval;
} else if OPT_ISSET(ops, b'm') {
for arg in argv.iter() {
queue_signals(); if let Some(pprog) = patcompile(arg, PAT_STATIC, None) {
if OPT_PLUS(ops, b'M') {
if let Ok(mut table) = MATHFUNCS.lock() {
table.retain(|p| {
!((p.flags & MFF_USERFUNC) != 0 && pattry(&pprog, &p.name))
});
}
} else {
if let Ok(table) = MATHFUNCS.lock() {
for p in table.iter() {
if (p.flags & MFF_USERFUNC) != 0 && pattry(&pprog, &p.name) {
listusermathfunc(p);
}
}
}
}
} else {
zwarnnam(
name, &format!("bad pattern : {}", arg),
);
returnval = 1; }
unqueue_signals(); }
return returnval;
} else if OPT_PLUS(ops, b'M') {
for arg in argv.iter() {
queue_signals(); if let Ok(mut table) = MATHFUNCS.lock() {
let idx = table.iter().position(|p| p.name == *arg); if let Some(i) = idx {
if (table[i].flags & MFF_USERFUNC) == 0 {
zwarnnam(
name, &format!("+M {}: is a library function", arg),
);
returnval = 1; } else {
table.remove(i); }
}
}
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 {
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, _ => {
zwarnnam(
name, &format!("-M: invalid min number of arguments: {}", arg),
);
return 1; }
}
if OPT_ISSET(ops, b's') && minargs != 1 {
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,
_ => {
zwarnnam(
name, &format!("-M: invalid max number of arguments: {}", arg),
);
return 1; }
}
if OPT_ISSET(ops, b's') && maxargs != 1 {
zwarnnam(
name, "-Ms: must take a single string argument",
);
return 1; }
}
let modname = argv_iter.next().cloned(); if argv_iter.next().is_some() {
zwarnnam(name, "-M: too many arguments"); return 1; }
let mut flags = MFF_USERFUNC; if OPT_ISSET(ops, b's') {
flags |= MFF_STR; }
let new_fn = mathfunc {
next: None, name: funcname.clone(), flags, nfunc: None,
sfunc: None,
module: modname, minargs, maxargs, funcid: 0,
};
queue_signals(); if let Ok(mut table) = MATHFUNCS.lock() {
if let Some(i) = table.iter().position(|p| p.name == new_fn.name) {
table.remove(i); }
table.insert(0, new_fn);
}
unqueue_signals(); return returnval;
}
}
if OPT_MINUS(ops, b'X') {
if argv.len() > 1 {
zwarnnam(name, "-X: too many arguments"); return 1; }
queue_signals(); let funcname: Option<String> = {
let stack = 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() {
zwarnnam(name, "bad autoload"); ret = 1; } else {
let fname = funcname.unwrap();
let mut shf_ptr = shfunctab_lock()
.read()
.map(|t| t.getnode(fname.as_str()))
.unwrap_or(std::ptr::null_mut());
if !shf_ptr.is_null() { } else {
let new_shf = Box::into_raw(Box::new(shfunc {
node: hashnode {
next: None,
nam: fname.clone(),
flags: 0,
},
filename: None,
lineno: 0,
funcdef: None,
redir: None,
sticky: None,
body: None,
}));
if let Ok(mut t) = shfunctab_lock().write() {
t.addnode(new_shf); }
shf_ptr = new_shf;
}
if !argv.is_empty() {
if !shf_ptr.is_null() {
let shf_mut = unsafe { &mut *shf_ptr };
let mut old_slot = shf_mut.filename.take();
dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
dircache_set(&mut new_slot, Some(&argv[0])); shf_mut.filename = new_slot;
on |= PM_LOADDIR; }
}
ret = eval_autoload(shf_ptr, &fname, ops, _func); }
unqueue_signals(); return ret;
}
if argv.is_empty() {
queue_signals(); if OPT_ISSET(ops, b'U') && !OPT_ISSET(ops, b'u') {
on &= !PM_UNDEFINED; }
let filter_mask = on | off;
scanshfunc(|_nm, entry| {
let f = entry.node.flags as u32;
if filter_mask != 0 && (f & filter_mask) != filter_mask {
return;
}
printshfuncexpand(entry, pflags, expand);
});
unqueue_signals(); return returnval;
}
if OPT_ISSET(ops, b'm') {
on &= !PM_UNDEFINED; let mut returnval = returnval;
for pat in argv {
queue_signals(); let pprog = patcompile(
pat, PAT_HEAPDUP,
None,
);
if let Some(prog) = pprog {
if (on | off) == 0 && !OPT_ISSET(ops, b'X') {
scanmatchshfunc(Some(pat), |_nm, entry| {
printshfuncexpand(entry, pflags, expand);
});
} else {
let names: Vec<String> = shfunctab_lock()
.read()
.map(|t| t.iter().map(|(k, _)| k.clone()).collect())
.unwrap_or_default();
for nm in &names {
if !pattry(&prog, nm) {
continue;
}
let shf_ptr = shfunctab_lock()
.read()
.map(|t| t.getnode(nm.as_str()))
.unwrap_or(std::ptr::null_mut());
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 {
zwarnnam(name, &format!("bad pattern : {}", pat)); returnval = 1; }
unqueue_signals(); }
return returnval;
}
let mut returnval = returnval;
queue_signals(); for fname in argv {
if OPT_ISSET(ops, b'w') {
continue;
}
let shf_ptr = shfunctab_lock()
.read()
.map(|t| t.getnode(fname.as_str()))
.unwrap_or(std::ptr::null_mut());
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 {
printshfuncexpand(shf_mut, pflags, expand); }
} 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 {
removetrapnode(sigidx); }
}
if fname.starts_with('/') {
let base = fname.rsplit('/').next().unwrap_or("");
if !base.is_empty() {
let base_ptr = shfunctab_lock()
.read()
.map(|t| t.getnode(base))
.unwrap_or(std::ptr::null_mut());
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();
dircache_set(&mut old_slot, None); let mut new_slot: Option<String> = None;
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(shfunc {
node: 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); #[cfg(feature = "recorder")]
if crate::recorder::is_enabled() {
let ctx = crate::recorder::recorder_ctx_global();
crate::recorder::emit_function(fname, None, ctx);
}
if sigidx != -1 {
if settrap(sigidx, None, ZSIG_FUNC) != 0 {
if let Ok(mut t) = shfunctab_lock().write() {
t.remove(fname);
}
returnval = 1; ok = false; }
}
if ok && check_autoload(new_shf_ptr, &fname, ops, _func) != 0 {
returnval = 1; }
} else {
returnval = 1; }
}
unqueue_signals(); let _ = (expand, pflags);
returnval
}
pub fn mkautofn(shf: *mut shfunc) -> *mut eprog {
let p = Box::new(eprog {
len: 5 * 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: &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 {
queue_signals(); let pprog = patcompile(
s, PAT_HEAPDUP,
None,
);
if let Some(prog) = pprog {
let names: Vec<String> = {
let tab = paramtab().read().unwrap();
tab.keys().cloned().collect()
};
for nm in &names {
if pattry(&prog, nm) {
unsetparam(nm); match_count += 1; }
}
} else {
zwarnnam(name, &format!("bad pattern : {}", s)); returnval = 1; }
unqueue_signals(); }
if match_count == 0 {
returnval = 1; }
return returnval; }
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(_) => {
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 == '_')
{
zwarnnam(name, &format!("{}: invalid parameter name", s)); returnval = 1; continue;
}
match subscript {
Some(key) => {
if let Some(mut map) = crate::ported::exec_hooks::assoc(nm) {
map.shift_remove(key); crate::ported::exec_hooks::set_assoc(nm, map);
} else if let Some(mut arr) = crate::ported::exec_hooks::array(nm) {
let len_i = arr.len() as i32;
let resolve = |raw: i32| -> Option<usize> {
let pos = if raw < 0 { len_i + raw + 1 } else { raw };
if pos >= 1 && pos as usize <= arr.len() {
Some((pos - 1) as usize)
} else {
None
}
};
if let Some((s, e)) = key.split_once(',') {
let s_i = s.trim().parse::<i32>().ok();
let e_i = e.trim().parse::<i32>().ok();
if let (Some(start), Some(end)) = (s_i, e_i) {
if start >= 1 && end >= 1 {
let from = (start - 1) as usize;
let to = (end - 1) as usize;
if from < arr.len() && from <= to {
let cap_to = to.min(arr.len() - 1);
let mut new_arr: Vec<String> = Vec::with_capacity(arr.len());
new_arr.extend(arr[..from].iter().cloned());
new_arr.push(String::new());
new_arr.extend(arr[cap_to + 1..].iter().cloned());
crate::ported::exec_hooks::set_array(nm, new_arr);
}
}
}
} else if let Ok(i) = key.parse::<i32>() {
if i == 0 {
zwarn(&format!("{}: assignment to invalid subscript range", nm));
returnval = 1;
} else if i == -1 {
if !arr.is_empty() {
let idx = arr.len() - 1;
arr[idx] = String::new();
crate::ported::exec_hooks::set_array(nm, arr);
}
} else if i > 0 {
if let Some(idx) = resolve(i) {
arr[idx] = String::new();
crate::ported::exec_hooks::set_array(nm, arr);
}
}
}
}
}
None => {
unsetparam(nm);
let _ = crate::ported::params::paramtab_hashed_storage()
.lock()
.ok()
.as_deref_mut()
.map(|m| m.remove(nm));
env::remove_var(nm); }
}
}
unqueue_signals(); returnval }
pub fn fetchcmdnamnode(
hn: *mut 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: &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(_) => {
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 let Some(path) = getsparam("PATH") {
let path_arr: Vec<String> = path.split(':').map(|s| s.to_string()).collect();
fillcmdnamtable(&path_arr);
}
if all {
if let Ok(mut m) = MATCHEDNODES.lock() {
m.clear();
}
}
queue_signals(); for pat in argv {
let pprog = patcompile(
pat, PAT_HEAPDUP,
None,
);
match pprog {
None => {
zwarnnam(nam, &format!("bad pattern : {}", pat)); returnval = 1; continue;
}
Some(prog) => {
if !OPT_ISSET(ops, b'p') {
let alias_matches: Vec<alias> = aliastab_lock()
.read()
.map(|t| {
t.iter()
.filter(|(n, _)| pattry(&prog, n))
.map(|(_, a)| a.clone())
.collect()
})
.unwrap_or_default();
for a in &alias_matches {
printaliasnode(a, printflags); informed += 1; }
let mut names: Vec<String> = reswdtab_lock()
.read()
.map(|t| {
t.iter()
.filter(|(k, _)| pattry(&prog, k))
.map(|(k, _)| k.clone())
.collect()
})
.unwrap_or_default();
names.sort();
for w in &names {
println!("{}", w); informed += 1; }
let func_matches: Vec<shfunc> = shfunctab_lock()
.read()
.map(|t| {
t.iter()
.filter(|(n, _)| pattry(&prog, n))
.map(|(_, f)| f.clone())
.collect()
})
.unwrap_or_default();
for f in &func_matches {
printshfuncexpand(f, printflags, expand); informed += 1; }
let mut bn_matches: Vec<&builtin> = BUILTINS
.iter()
.filter(|b| pattry(&prog, &b.node.nam))
.collect();
bn_matches.sort_by(|a, b| a.node.nam.cmp(&b.node.nam));
for b in bn_matches {
printbuiltinnode(
&b.node as *const hashnode as *mut hashnode,
printflags,
); informed += 1; }
}
let mut cmd_matches: Vec<(String, cmdnam)> = cmdnamtab_lock()
.read()
.map(|t| {
t.iter()
.filter(|(n, _)| pattry(&prog, n))
.map(|(n, c)| (n.clone(), c.clone()))
.collect()
})
.unwrap_or_default();
let printed_path = |c: &cmdnam| -> String {
if (c.node.flags & HASHED as i32) != 0 {
c.cmd.clone().unwrap_or_default()
} else {
let dir = c
.name
.as_ref()
.and_then(|v| v.first())
.cloned()
.unwrap_or_default();
format!("{}/{}", dir, c.node.nam)
}
};
cmd_matches.sort_by(|a, b| printed_path(&a.1).cmp(&printed_path(&b.1)));
for (n, c) in &cmd_matches {
if all {
if let Ok(mut m) = MATCHEDNODES.lock() {
m.push(n.clone());
}
} else {
printcmdnamnode(c, printflags);
}
informed += 1; }
}
}
run_queued_signals(); }
unqueue_signals(); if !all {
return if returnval != 0 || informed == 0 {
1
} else {
0
}; }
}
queue_signals();
let argv_vec: Vec<String> = if OPT_ISSET(ops, b'm') {
MATCHEDNODES.lock().map(|m| m.clone()).unwrap_or_default()
} else {
argv.to_vec()
};
for arg in &argv_vec {
informed = 0; if !OPT_ISSET(ops, b'p') {
let alias_text = aliastab_lock()
.read()
.ok()
.and_then(|t| t.get(arg).map(|a| a.clone()));
if let Some(a) = alias_text {
printaliasnode(&a, aliasflags); informed = 1; if !all {
continue;
} }
if let Some(idx) = arg.rfind('.') {
if idx + 1 < arg.len() && idx > 0 && arg.as_bytes()[idx - 1] as u8 != Meta {
let suf = &arg[idx + 1..]; let suf_alias = sufaliastab_lock()
.read()
.ok()
.and_then(|t| t.get(suf).map(|a| a.clone()));
if let Some(a) = suf_alias {
printaliasnode(&a, printflags); informed = 1; if !all {
continue;
} }
}
}
let is_reswd = reswdtab_lock()
.read()
.map(|t| t.get(arg).is_some())
.unwrap_or(false);
if is_reswd {
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;
} }
let shfunc_node = getshfunc(arg);
if let Some(ref f) = shfunc_node {
printshfuncexpand(f, printflags, expand); informed = 1; if !all {
continue;
} }
let is_files_gated = matches!(
arg.as_str(),
"mkdir" | "rmdir" | "rm" | "mv" | "ln" | "chmod" | "chown" | "chgrp" | "sync"
| "zf_mkdir" | "zf_rmdir" | "zf_rm" | "zf_mv" | "zf_ln" | "zf_chmod"
| "zf_chown" | "zf_chgrp" | "zf_sync"
) && !crate::ported::module::MODULESTAB
.lock()
.unwrap()
.is_loaded("zsh/files");
let is_module_gated = |modname: &str, names: &[&str]| -> bool {
names.iter().any(|n| arg.as_str() == *n)
&& !crate::ported::module::MODULESTAB
.lock()
.unwrap()
.is_loaded(modname)
};
let is_stat_gated = is_module_gated("zsh/stat", &["stat", "zstat"]);
let is_zselect_gated = is_module_gated("zsh/zselect", &["zselect"]);
let is_zpty_gated = is_module_gated("zsh/zpty", &["zpty"]);
let is_ztcp_gated = is_module_gated("zsh/net/tcp", &["ztcp"]);
let is_zftp_gated = is_module_gated("zsh/zftp", &["zftp"]);
let is_system_gated =
is_module_gated("zsh/system", &["zsystem", "syserror"]);
let is_module_bound_gated = is_stat_gated
|| is_zselect_gated
|| is_zpty_gated
|| is_ztcp_gated
|| is_zftp_gated
|| is_system_gated;
let builtin_node: Option<*mut hashnode> = if is_files_gated || is_module_bound_gated {
None
} else {
BUILTINS
.iter()
.find(|b| b.node.nam == *arg)
.map(|b| &b.node as *const hashnode as *mut hashnode)
};
if let Some(hn) = builtin_node {
printbuiltinnode(hn, printflags); informed = 1; if !all {
continue;
} }
let hashed_path: Option<String> = {
match cmdnamtab_lock().read() {
Ok(tab) => tab.get(arg).and_then(|cn| {
if (cn.node.flags & 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) = getsparam("PATH") {
for dir in path.split(':') {
if dir.is_empty() {
continue;
}
let full = format!("{}/{}", dir, arg); if iscom(&full) {
if wd {
println!("{}: command", arg); } else {
if v && !csh {
print!("{} is ", arg); print!("{}", quotedzputs(&full)); } else {
print!("{}", full); }
if OPT_ISSET(ops, b's') || OPT_ISSET(ops, b'S') {
print_if_link(&full, OPT_ISSET(ops, b'S')); }
println!(); }
informed = 1; } else {
}
}
}
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;
}
}
if let Some(cnam) = findcmd(
arg,
1, (func == BIN_COMMAND && OPT_ISSET(ops, b'p')) as i32, ) {
if wd {
println!("{}: command", arg); } else {
if v && !csh {
print!("{} is ", arg); print!("{}", quotedzputs(&cnam)); } else {
print!("{}", cnam); }
if OPT_ISSET(ops, b's') || OPT_ISSET(ops, b'S') {
print_if_link(&cnam, OPT_ISSET(ops, b'S')); }
println!(); }
informed = 1; continue;
}
if v || csh || wd {
println!("{}{}", arg, if wd { ": none" } else { " not found" }); }
returnval = 1; }
unqueue_signals();
returnval | (informed == 0) as i32 }
pub fn bin_hash(
name: &str,
argv: &[String], ops: &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() {
zwarnnam("hash", "too many arguments"); return 1; }
if OPT_ISSET(ops, b'r') {
if dir_mode {
emptynameddirtable();
} else {
emptycmdnamtable();
}
}
if OPT_ISSET(ops, b'f') {
if dir_mode {
fillnameddirtable();
} else {
let path_str = getsparam("PATH").unwrap_or_default();
let path_arr: Vec<String> = path_str.split(':').map(|s| s.to_string()).collect();
fillcmdnamtable(&path_arr);
}
}
return 0; }
if OPT_ISSET(ops, b'L') {
printflags |= PRINT_LIST;
}
if argv.is_empty() {
queue_signals(); if dir_mode {
if let Ok(t) = nameddirtab().lock() {
for (_n, nd) in t.iter() {
printnameddirnode(nd, printflags);
}
}
} else {
if let Ok(t) = cmdnamtab_lock().read() {
for (_n, cn) in t.iter() {
printcmdnamnode(cn, printflags);
}
}
}
unqueue_signals(); return 0; }
queue_signals(); let mut idx = 0;
while idx < argv.len() {
let arg = &argv[idx];
idx += 1;
if OPT_ISSET(ops, b'm') {
let pprog = patcompile(
arg, PAT_HEAPDUP,
None,
);
if let Some(prog) = pprog {
if dir_mode {
if let Ok(t) = nameddirtab().lock() {
for (n, nd) in t.iter() {
if pattry(&prog, n) {
printnameddirnode(nd, printflags);
}
}
}
}
} else {
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 == '_') {
zwarnnam(name, &format!("invalid character in directory name: {}", n)); returnval = 1; continue; }
let expanded_v: String = if v.starts_with('~') {
let rest = &v[1..];
let tokenized = format!("\u{98}{}", rest);
crate::ported::subst::filesubstr(&tokenized, false)
.unwrap_or_else(|| v.to_string())
} else {
v.to_string()
};
let nd = nameddir {
node: hashnode {
next: None,
nam: n.to_string(),
flags: 0,
},
dir: expanded_v,
diff: 0,
};
addnameddirnode(n, nd); } else {
let cn = cmdnam {
node: hashnode {
next: None,
nam: n.to_string(),
flags: HASHED as i32, },
name: None,
cmd: Some(v.to_string()), };
if let Ok(mut tab) = cmdnamtab_lock().write() {
tab.add(cn); }
}
if OPT_ISSET(ops, b'v') {
if dir_mode {
if let Ok(t) = nameddirtab().lock() {
if let Some(nd) = t.get(n) {
printnameddirnode(nd, 0);
}
}
}
}
} else {
if dir_mode {
let snapshot = nameddirtab().lock().ok().and_then(|t| t.get(n).cloned());
match snapshot {
Some(nd) => {
if OPT_ISSET(ops, b'v') {
printnameddirnode(&nd, 0);
}
}
None => {
zwarnnam(name, &format!("no such directory name: {}", n)); returnval = 1; }
}
} else {
let in_cmdnamtab = cmdnamtab_lock()
.read()
.map(|t| t.get(n).is_some())
.unwrap_or(false);
if !in_cmdnamtab {
let path: Vec<String> = getsparam("PATH")
.map(|p| p.split(':').map(String::from).collect())
.unwrap_or_default();
if crate::ported::exec::hashcmd(n, &path).is_none() {
zwarnnam(name, &format!("no such command: {}", n)); returnval = 1; }
}
}
}
}
unqueue_signals(); returnval }
pub fn bin_unhash(
name: &str,
argv: &[String], ops: &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() {
zwarnnam(name, "-a: too many arguments"); return 1; }
all = 1; } else if argv.is_empty() {
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 _ = aliastab_lock().write().map(|mut g| g.clear());
}
Tab::SufAlias => {
let _ = sufaliastab_lock().write().map(|mut g| g.clear());
}
Tab::NamedDir => {
emptynameddirtable();
}
Tab::Shfunc => {
if let Ok(mut t) = shfunctab_lock().write() {
let names: Vec<String> = t.iter().map(|(k, _)| k.clone()).collect();
for nm in names {
let _ = t.remove(&nm);
}
}
}
Tab::CmdNam => {
emptycmdnamtable();
} };
let remove_one = |t: &Tab, nm: &str| -> bool {
match t {
Tab::Alias => aliastab_lock()
.write()
.map(|mut g| g.remove(nm).is_some())
.unwrap_or(false),
Tab::SufAlias => 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 => {
let from_tab = shfunctab_lock()
.write()
.map(|mut g| g.remove(nm).is_some())
.unwrap_or(false);
let from_exec = crate::ported::exec_hooks::unregister_function(nm);
from_tab || from_exec
}
Tab::CmdNam => cmdnamtab_lock()
.write()
.map(|mut g| g.remove(nm).is_some())
.unwrap_or(false),
}
};
if all != 0 {
queue_signals(); clear_all(&tab); unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') {
for arg in argv {
queue_signals(); let pprog = patcompile(
arg, PAT_HEAPDUP,
None,
);
if let Some(prog) = pprog {
let names: Vec<String> = match &tab {
Tab::Alias => aliastab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
Tab::SufAlias => sufaliastab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
Tab::NamedDir => nameddirtab()
.lock()
.map(|t| t.keys().cloned().collect())
.unwrap_or_default(),
Tab::Shfunc => shfunctab_lock()
.read()
.map(|t| t.iter().map(|(k, _)| k.clone()).collect())
.unwrap_or_default(),
Tab::CmdNam => cmdnamtab_lock()
.read()
.map(|t| t.iter().map(|(n, _)| n.clone()).collect())
.unwrap_or_default(),
};
for nm in &names {
if pattry(&prog, nm) {
if remove_one(&tab, nm) {
match_count += 1; }
}
}
} else {
zwarnnam(name, &format!("bad pattern : {}", arg)); returnval = 1; }
unqueue_signals(); }
if match_count == 0 {
returnval = 1; }
return returnval; }
queue_signals(); for arg in argv {
if remove_one(&tab, arg) { } else if func == BIN_UNSET && isset(POSIXBUILTINS) {
returnval = 0; } else {
zwarnnam(name, &format!("no such hash table element: {}", arg)); returnval = 1; }
}
unqueue_signals(); returnval }
pub fn bin_alias(
name: &str,
argv: &[String], ops: &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 {
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; }
if argv.is_empty() {
queue_signals(); let lock = if use_suffix {
sufaliastab_lock()
} else {
aliastab_lock()
};
if let Ok(t) = lock.read() {
let mut entries: Vec<_> = t.iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
for (_n, a) in entries {
if (a.node.flags & flags1 as i32) == flags1 as i32
&& (a.node.flags & flags2 as i32) == 0
{
printaliasnode(a, printflags);
}
}
}
unqueue_signals(); return 0; }
if OPT_ISSET(ops, b'm') {
for pat in argv {
queue_signals(); let pprog = patcompile(
pat, 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
&& pattry(&prog, &a.node.nam)
{
printaliasnode(a, printflags);
}
}
}
} else {
zwarnnam(name, &format!("bad pattern : {}", pat)); returnval = 1; }
unqueue_signals(); }
return returnval; }
queue_signals(); #[cfg(feature = "recorder")]
let recorder_active = crate::recorder::is_enabled();
#[cfg(feature = "recorder")]
let recorder_ctx = if recorder_active {
Some(crate::recorder::recorder_ctx_global())
} else {
None
};
let mut idx = 0;
while idx < argv.len() {
let arg = &argv[idx];
idx += 1;
if arg.starts_with('=') {
zwarnnam(name, "bad assignment");
break;
}
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 = createaliasnode(n, v, flags1); t.add(a);
}
#[cfg(feature = "recorder")]
if let Some(ref ctx) = recorder_ctx {
if (flags1 & ALIAS_GLOBAL as u32) != 0 {
crate::recorder::emit_galias(n, Some(v), ctx.clone());
} else if (flags1 & ALIAS_SUFFIX as u32) != 0 {
crate::recorder::emit_salias(n, Some(v), ctx.clone());
} else {
crate::recorder::emit_alias(n, Some(v), ctx.clone());
}
}
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(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 = createaliasnode(&nm, &txt, fl);
printaliasnode(&a, printflags);
}
}
None => {
returnval = 1; }
}
}
unqueue_signals(); returnval }
pub fn bin_true(
_name: &str,
_argv: &[String], _ops: &options,
_func: i32,
) -> i32 {
0 }
pub fn bin_false(
_name: &str,
_argv: &[String], _ops: &options,
_func: i32,
) -> i32 {
1 }
pub fn bin_print(
name: &str,
args: &[String], ops: &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') || OPT_ISSET(ops, b'c');
let nul_sep = OPT_ISSET(ops, b'N'); let dirify_d = OPT_ISSET(ops, b'D');
let _printf_mode = func == BIN_PRINTF || OPT_HASARG(ops, b'f'); let echo_mode = func == BIN_ECHO;
let _ = (name, raw);
if OPT_HASARG(ops, b'x') || OPT_HASARG(ops, b'X') {
let which = if OPT_HASARG(ops, b'X') { b'X' } else { b'x' };
let xarg = OPT_ARG(ops, which).unwrap_or("");
let valid = xarg.parse::<i64>().map(|n| n > 0).unwrap_or(false);
if !valid {
zwarnnam(
name,
&format!(
"positive integer expected after -{}: {}",
which as char, xarg
),
);
return 1;
}
}
let dest_var: Option<String> = if OPT_HASARG(ops, b'v') {
OPT_ARG(ops, b'v').map(String::from)
} else {
None
};
let print_dash_p_fd: Option<fs::File> = if OPT_ISSET(ops, b'p') {
let coprocout = crate::ported::modules::clone::coprocout
.load(std::sync::atomic::Ordering::Relaxed);
if coprocout < 0 {
zwarnnam(name, "-p: no coprocess");
return 1;
}
let dup_fd = unsafe { libc::dup(coprocout) };
if dup_fd < 0 {
zwarnnam(name, "-p: no coprocess");
return 1;
}
use std::os::unix::io::FromRawFd;
Some(unsafe { fs::File::from_raw_fd(dup_fd) })
} else {
None
};
let dest_fd: Option<fs::File> = if OPT_HASARG(ops, b'u') {
let argptr = OPT_ARG(ops, b'u').unwrap_or("");
match argptr.parse::<i32>() {
Ok(fdarg) => {
let dup_fd = unsafe { libc::dup(fdarg) };
if dup_fd < 0 {
zwarnnam(name, &format!("bad file number: {}", fdarg)); return 1; }
use std::os::unix::io::FromRawFd;
Some(unsafe { fs::File::from_raw_fd(dup_fd) }) }
Err(_) => {
zwarnnam(name, &format!("number expected after -u: {}", argptr)); return 1; }
}
} else {
None
};
if _printf_mode {
let args_owned: Vec<String>;
let args: &[String] = if func == BIN_PRINTF
&& !OPT_HASARG(ops, b'f')
&& !args.is_empty()
&& args[0] == "--"
{
if args.len() == 1 {
zwarnnam(name, "not enough arguments");
return 1;
}
args_owned = args[1..].to_vec();
&args_owned
} else {
args
};
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 = match printf_format(&fmt, rest) {
Ok(s) => s,
Err((partial, bad)) => {
print!("{}", partial); use std::io::Write;
let _ = std::io::stdout().flush();
let msg = format!("%{}: invalid directive", bad); crate::ported::utils::zwarnnam(name, &msg);
return 1; }
};
if OPT_ISSET(ops, b'z') {
crate::ported::zle::zle_main::BUFSTACK
.lock()
.unwrap()
.push(out);
return 0;
}
if OPT_ISSET(ops, b's') {
let event_id = crate::ported::hist::prepnexthistent();
crate::ported::hashtable::addhistnode(&out, event_id as i32);
return 0;
}
if let Some(ref v) = dest_var {
setsparam(v, &out);
} else {
use std::io::Write as _;
let stdout = io::stdout();
let mut lk = stdout.lock();
let _ = lk.write_all(out.as_bytes());
let _ = lk.flush();
}
return 0;
}
let mut processed_args: Vec<String> = if OPT_ISSET(ops, b'm') {
if args.is_empty() {
zwarnnam(name, "no pattern specified"); return 1; }
let pat = &args[0];
let pprog = patcompile(pat, PAT_STATIC, None);
match pprog {
None => {
zwarnnam(name, &format!("bad pattern: {}", pat)); return 1; }
Some(prog) => {
args[1..]
.iter()
.filter(|a| pattry(&prog, a))
.cloned()
.collect()
}
}
} else {
args.to_vec()
};
let sep = if one_per_line {
"\n"
} else if nul_sep {
"\0"
} else {
" "
};
if dirify_d {
if let Ok(home) = env::var("HOME") {
if !home.is_empty() {
for a in processed_args.iter_mut() {
if a.as_str() == home {
*a = "~".to_string();
} else if let Some(rest) = a.strip_prefix(&format!("{}/", home)) {
*a = format!("~/{}", rest);
}
}
}
}
}
if OPT_ISSET(ops, b'P') {
crate::ported::prompt::txtunknownattrs.store(
crate::ported::zsh_h::TXT_ATTR_ALL,
std::sync::atomic::Ordering::SeqCst,
);
for a in processed_args.iter_mut() {
*a = crate::ported::prompt::expand_prompt(a); a.retain(|c| c != '\x01' && c != '\x02');
}
}
if OPT_ISSET(ops, b'o') || OPT_ISSET(ops, b'O') {
let ignore_case = OPT_ISSET(ops, b'i'); if ignore_case {
processed_args.sort_by_key(|s| s.to_lowercase());
} else {
processed_args.sort();
}
if OPT_ISSET(ops, b'O') {
processed_args.reverse(); }
}
let dash_e = OPT_ISSET(ops, b'e');
let bsd_echo_active = echo_mode && isset(BSDECHO);
let suppress_escapes = OPT_ISSET(ops, b'R')
|| OPT_ISSET(ops, b'r')
|| (echo_mode && OPT_ISSET(ops, b'E'))
|| (bsd_echo_active && !dash_e);
let mut backslash_c_truncated = false;
if !suppress_escapes || dash_e {
let escape_how: u32 = if !echo_mode && !dash_e {
GETKEYS_PRINT } else {
GETKEYS_ECHO };
let _ = crate::ported::utils::getkey_truncated_take();
let mut new_args: Vec<String> = Vec::with_capacity(processed_args.len());
for a in processed_args.iter() {
let (s, _) = getkeystring_with(a, escape_how);
new_args.push(s);
if crate::ported::utils::getkey_truncated_take() {
backslash_c_truncated = true;
break;
}
}
processed_args = new_args;
}
let body = if !_printf_mode && OPT_HASARG(ops, b'C') {
let nc: usize = OPT_ARG(ops, b'C')
.and_then(|s| s.trim().parse().ok())
.filter(|&n: &usize| n > 0)
.unwrap_or(1);
let argc = processed_args.len();
let nr = (argc + nc - 1) / nc;
let across = OPT_ISSET(ops, b'a'); let max_w = processed_args
.iter()
.enumerate()
.filter(|(i, _)| {
if across {
(i % nc) != nc - 1
} else {
*i < nr * (nc.saturating_sub(1))
}
})
.map(|(_, s)| s.chars().count())
.max()
.unwrap_or(0);
let sc = max_w + 2;
let mut out = String::new();
for row in 0..nr {
for col in 0..nc {
let idx = if across {
row * nc + col
} else {
col * nr + row
};
if idx >= argc {
break;
}
let cell = &processed_args[idx];
let is_last_col_of_row = col == nc - 1;
let is_after_last_grid_col = !across && col * nr + row + nr >= argc;
if is_last_col_of_row || is_after_last_grid_col {
out.push_str(cell);
} else {
out.push_str(cell);
let pad = sc.saturating_sub(cell.chars().count());
out.extend(std::iter::repeat(' ').take(pad));
}
}
out.push('\n');
}
if out.ends_with('\n') {
out.pop();
}
out
} else {
processed_args.join(sep)
};
if OPT_ISSET(ops, b'z') {
crate::ported::zle::zle_main::BUFSTACK
.lock()
.unwrap()
.push(body); return 0;
}
if OPT_ISSET(ops, b's') || OPT_ISSET(ops, b'S') {
if OPT_ISSET(ops, b'S') && processed_args.len() > 1 {
zwarnnam(name, "option -S takes a single argument"); return 1; }
let event_id = crate::ported::hist::prepnexthistent(); crate::ported::hashtable::addhistnode(&body, event_id as i32); let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let ent = crate::ported::zsh_h::histent {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: body.clone(),
flags: 0,
},
up: None,
down: None,
zle_text: None,
stim: now,
ftim: now,
words: Vec::new(),
nwords: 0,
histnum: event_id,
};
if let Ok(mut ring) = crate::ported::hist::hist_ring.lock() {
ring.insert(0, ent);
crate::ported::hist::histlinect
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
return 0;
}
if let Some(ref v) = dest_var {
setsparam(v, &body);
} else {
let final_term: &[u8] = if nonewline || backslash_c_truncated {
b""
} else if nul_sep {
b"\0"
} else {
b"\n"
};
let dest_fd_active = print_dash_p_fd.or(dest_fd);
if let Some(mut f) = dest_fd_active {
use std::io::Write as _;
let _ = f.write_all(body.as_bytes()); let _ = f.write_all(final_term); } else {
use std::io::Write as _;
let stdout = io::stdout();
let mut lk = stdout.lock();
let _ = lk.write_all(body.as_bytes()); let _ = lk.write_all(final_term); let _ = lk.flush();
}
}
0
}
pub fn bin_shift(
name: &str,
argv: &[String], ops: &options,
_func: i32,
) -> i32 {
let mut num: i32 = 1; let mut ret: i32 = 0; let mut idx = 0usize;
queue_signals(); if !argv.is_empty() {
let first = &argv[0];
let is_array = {
use {PM_ARRAY, PM_TYPE};
let tab = paramtab().read().unwrap();
tab.get(first)
.map(|pm| PM_TYPE(pm.node.flags as u32) == PM_ARRAY)
.unwrap_or(false)
};
if !is_array {
num = mathevali(first).unwrap_or_else(|_| {
ret = 1;
0
}) as i32; idx = 1;
if ret != 0 || errflag.load(Relaxed) != 0 {
unqueue_signals(); return 1;
}
}
}
if num < 0 {
unqueue_signals(); 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 = 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 {
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() };
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 {
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);
}
unqueue_signals(); ret }
pub fn bin_getopts(
_name: &str,
argv: &[String], _ops: &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 paramtab_oi = getiparam("OPTIND");
let mut zoptind = if paramtab_oi >= 1 {
paramtab_oi as i32
} else {
ZOPTIND.load(Relaxed)
};
if zoptind < 1 {
zoptind = 1;
OPTCIND.store(0, Relaxed);
}
let mut optcind = if paramtab_oi == 1 && ZOPTIND.load(Relaxed) != 1 {
0
} else {
OPTCIND.load(Relaxed)
};
if (args.len() as i32) < zoptind {
ZOPTIND.store(zoptind, 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, Relaxed);
OPTCIND.store(optcind, Relaxed);
setiparam("OPTIND", zoptind as i64); 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, Relaxed);
OPTCIND.store(optcind, Relaxed);
setiparam("OPTIND", zoptind as i64);
return 1;
}
if lenstr == 2 && &str_buf[..2] == "--" {
zoptind += 1;
ZOPTIND.store(zoptind, Relaxed);
OPTCIND.store(0, Relaxed);
setiparam("OPTIND", zoptind as i64); 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 = isset(POSIXBUILTINS);
let found = optstr.bytes().position(|b| b == opch);
if opch == b':' || found.is_none() {
if posix {
optcind = 0;
zoptind += 1;
}
setsparam(&var, "?");
if quiet {
setsparam("OPTARG", &optbuf); } else {
let prefix = if plus { "+" } else { "-" };
zwarn(&format!("bad option: {}{}", prefix, opch as char)); setsparam("OPTARG", "");
}
ZOPTIND.store(zoptind, Relaxed);
OPTCIND.store(optcind, Relaxed);
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 {
setsparam(&var, ":");
setsparam("OPTARG", &optbuf);
} else {
setsparam(&var, "?");
setsparam("OPTARG", "");
let prefix = if plus { "+" } else { "-" };
zwarn(&format!(
"argument expected after {}{} option",
prefix, opch as char
)); }
ZOPTIND.store(zoptind, Relaxed);
OPTCIND.store(optcind, Relaxed);
setiparam("OPTIND", zoptind as i64);
return 0;
}
let p_arg = args[zoptind as usize].clone(); zoptind += 1; setsparam("OPTARG", &p_arg); optcind = 0; zoptind += 1; } else {
let p_arg = str_buf[(optcind as usize)..].to_string();
setsparam("OPTARG", &p_arg);
optcind = 0;
zoptind += 1;
}
} else {
setsparam("OPTARG", "");
}
setsparam(&var, &optbuf);
ZOPTIND.store(zoptind, Relaxed);
OPTCIND.store(optcind, Relaxed);
setiparam("OPTIND", zoptind as i64);
0 }
pub fn bin_break(
name: &str,
argv: &[String], _ops: &options,
func: i32,
) -> i32 {
let mut num: i32 = LASTVAL.load(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 {
zwarnnam(name, &format!("argument is not positive: {}", num)); return 1; }
let loops = LOOPS.load(Relaxed);
match func {
x if x == BIN_CONTINUE => {
if loops == 0 {
zerrnam(name, "not in while, until, select, or repeat loop"); return 1; }
CONTFLAG.store(1, Relaxed); BREAKS.store(
if nump != 0 { num.min(loops) } else { 1 }, Relaxed,
);
}
x if x == BIN_BREAK => {
if loops == 0 {
zerrnam(name, "not in while, until, select, or repeat loop"); return 1; }
BREAKS.store(
if nump != 0 { num.min(loops) } else { 1 }, Relaxed,
);
}
x if x == BIN_RETURN => {
let interactive = isset(INTERACTIVE);
let shinstdin = isset(SHINSTDIN);
let ll_v = locallevel_param.load(Relaxed);
let sourcelevel = crate::ported::init::sourcelevel.load(Relaxed);
if (interactive && shinstdin) || ll_v != 0 || sourcelevel != 0 {
RETFLAG.store(1, Relaxed); BREAKS.store(loops, Relaxed); LASTVAL.store(num, Relaxed); let posixtraps = isset(POSIXTRAPS);
let cur_state = TRAP_STATE.load(Relaxed);
let cur_return = TRAP_RETURN.load(Relaxed);
if cur_state == TRAP_STATE_PRIMED && cur_return == -2 && !(posixtraps && implicit)
{
TRAP_STATE.store(
TRAP_STATE_FORCE_RETURN,
Relaxed,
);
TRAP_RETURN.store(num, Relaxed);
}
return num; }
zexit(num, ZEXIT_NORMAL); }
x if x == BIN_LOGOUT => {
let loginshell = isset(LOGINSHELL);
if !loginshell {
zerrnam(name, "not login shell"); return 1; }
let cur_locallevel = locallevel_param.load(Relaxed);
let forklevel = FORKLEVEL.load(Relaxed);
let shell_exiting = SHELL_EXITING.load(Relaxed);
if cur_locallevel > forklevel && shell_exiting != -1 {
if STOPMSG.load(Relaxed) == 0 {
zexit(0, ZEXIT_DEFERRED); }
if STOPMSG.load(Relaxed) == 0 {
let trap_state = TRAP_STATE.load(Relaxed);
if trap_state != 0 {
TRAP_STATE.store(
TRAP_STATE_FORCE_RETURN,
Relaxed,
);
}
RETFLAG.store(1, Relaxed); BREAKS.store(
LOOPS.load(Relaxed), Relaxed,
);
EXIT_PENDING.store(1, Relaxed); EXIT_LEVEL.store(cur_locallevel, Relaxed); EXIT_VAL.store(num, Relaxed); }
} else {
zexit(num, ZEXIT_NORMAL); }
}
x if x == BIN_EXIT => {
let cur_locallevel = locallevel_param.load(Relaxed);
let forklevel = FORKLEVEL.load(Relaxed);
let shell_exiting = SHELL_EXITING.load(Relaxed);
if cur_locallevel > forklevel && shell_exiting != -1 {
if STOPMSG.load(Relaxed) == 0 {
zexit(0, ZEXIT_DEFERRED); }
if STOPMSG.load(Relaxed) == 0 {
let trap_state = TRAP_STATE.load(Relaxed);
if trap_state != 0 {
TRAP_STATE.store(
TRAP_STATE_FORCE_RETURN,
Relaxed,
);
}
RETFLAG.store(1, Relaxed); BREAKS.store(
LOOPS.load(Relaxed), Relaxed,
);
EXIT_PENDING.store(1, Relaxed); EXIT_LEVEL.store(cur_locallevel, Relaxed); EXIT_VAL.store(num, Relaxed); }
} else {
zexit(num, ZEXIT_NORMAL); }
}
_ => {}
}
0
}
pub fn checkjobs() {
let checkrunning = isset(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 {
zerr("you have stopped jobs."); } else {
zerr("you have running jobs."); }
STOPMSG.store(1, Relaxed); }
}
pub fn realexit() -> ! {
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() -> ! {
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) {
EXIT_VAL.store(val, Relaxed); if SHELL_EXITING.load(Relaxed) == -1 {
RETFLAG.store(1, Relaxed); BREAKS.store(LOOPS.load(Relaxed), Relaxed); return; }
if isset(MONITOR) && STOPMSG.load(Relaxed) == 0
&& from_where != ZEXIT_SIGNAL
{
checkjobs(); if STOPMSG.load(Relaxed) != 0 {
STOPMSG.store(2, Relaxed); return; }
}
if from_where == ZEXIT_DEFERRED {
return;
}
let prev_exiting = SHELL_EXITING.fetch_add(1, Relaxed);
if prev_exiting != 0 && from_where != ZEXIT_NORMAL {
return;
}
SHELL_EXITING.store(-1, Relaxed); errflag.store(0, Relaxed); if isset(MONITOR) {
crate::ported::signals::killrunjobs(if from_where == ZEXIT_SIGNAL { 1 } else { 0 });
}
if SUBSHELL_DEPTH.load(Relaxed) > 0 {
SHELL_EXITING.store(0, Relaxed);
EXIT_VAL.store(val, Relaxed);
EXIT_PENDING.store(1, Relaxed);
RETFLAG.store(1, Relaxed);
BREAKS.store(LOOPS.load(Relaxed), Relaxed);
return;
}
let exit_trap = traps_table().lock().ok().and_then(|mut t| t.remove("EXIT"));
LASTVAL.store(val, Relaxed); crate::ported::signals::in_exit_trap.store(1, Relaxed);
if let Some(body) = exit_trap {
let _ = crate::ported::exec_hooks::execute_script(&body);
}
let _ = crate::ported::signals::dotrap(crate::signals_h::SIGEXIT);
let _ = crate::ported::utils::callhookfunc("zshexit", None, 1, std::ptr::null_mut());
crate::ported::signals::in_exit_trap.store(0, Relaxed);
realexit(); }
pub fn bin_dot(
name: &str,
argv: &[String], _ops: &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(FUNCTIONARGZERO) {
let prev = argzero();
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 = 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 = Path::new(&arg0);
if p.exists() && !p.is_dir() {
found_path = Some(arg0.clone()); }
}
let pathdirs = isset(PATHDIRS);
if found_path.is_none() && (!arg0.contains('/') || (pathdirs && diddot < 2 && dotdot == 0)) {
let path_env = 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 = Path::new(&buf);
if p.exists() && !p.is_dir() {
found_path = Some(buf); break;
}
}
}
let path = match found_path {
Some(p) => p,
None => {
if let Some(prev) = saved_argzero.clone() {
set_argzero(prev);
}
if let Some(saved) = saved_pparams.clone() {
let mut pp = PPARAMS.lock().unwrap_or_else(|e| {
PPARAMS.clear_poison();
e.into_inner()
});
*pp = saved;
}
let msg = format!("{}: {}", "no such file or directory", arg0); zwarnnam(name, &msg); return 128 - 1;
}
};
let old_scriptname = crate::ported::utils::scriptname_get(); let old_scriptfilename = crate::ported::utils::scriptfilename_get(); crate::ported::utils::set_scriptname(Some(arg0.clone())); crate::ported::utils::set_scriptfilename(Some(arg0.clone()));
crate::ported::init::sourcelevel.fetch_add(1, Relaxed);
let prev_funcstack_lineno = crate::ported::input::lineno.with(|c| c.get()) as i64;
let pushed_frame = {
let mut stack = crate::ported::modules::parameter::FUNCSTACK
.lock()
.unwrap_or_else(|e| {
crate::ported::modules::parameter::FUNCSTACK.clear_poison();
e.into_inner()
});
let prev_caller = stack
.last()
.map(|fs| fs.name.clone())
.or_else(|| old_scriptfilename.clone())
.or_else(|| Some("zsh".to_string()));
let frame = crate::ported::zsh_h::funcstack {
prev: None,
name: arg0.clone(), filename: Some(arg0.clone()), caller: prev_caller, flineno: 0, lineno: prev_funcstack_lineno, tp: crate::ported::zsh_h::FS_SOURCE, };
stack.push(frame);
true
};
let result = match fs::read_to_string(&path) {
Ok(src) => crate::ported::exec_hooks::execute_script(&src).unwrap_or(1),
Err(_) => 128 - 2,
};
if pushed_frame {
let mut stack = crate::ported::modules::parameter::FUNCSTACK
.lock()
.unwrap_or_else(|e| {
crate::ported::modules::parameter::FUNCSTACK.clear_poison();
e.into_inner()
});
stack.pop();
}
crate::ported::init::sourcelevel.fetch_sub(1, Relaxed); crate::ported::utils::set_scriptname(old_scriptname);
crate::ported::utils::set_scriptfilename(old_scriptfilename);
RETFLAG.store(0, Relaxed); if let Some(prev) = saved_argzero {
set_argzero(prev);
}
if let Some(saved) = saved_pparams {
let mut pp = PPARAMS.lock().unwrap_or_else(|e| {
PPARAMS.clear_poison();
e.into_inner()
});
*pp = saved;
}
result
}
pub fn eval(argv: &[String]) -> i32 {
let oscriptname: Option<String> = scriptname_get();
let oineval: i32 = INEVAL.load(Relaxed);
let fpushed: bool;
INEVAL.store(
if !isset(crate::ported::zsh_h::EVALLINENO) {
1
} else {
0
},
Relaxed,
);
let ineval_now = INEVAL.load(Relaxed) != 0;
if !ineval_now {
crate::ported::utils::set_scriptname(Some("(eval)".to_string()));
let prev_frame = {
let stack = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
stack.last().cloned()
};
let lineno_now = crate::ported::input::lineno.with(|c| c.get()) as i64;
let caller = match &prev_frame {
Some(p) => Some(p.name.clone()),
None => argzero(), };
let (flineno, filename): (i64, Option<String>) = match &prev_frame {
None => (lineno_now, caller.clone()), Some(p) if p.tp == crate::ported::zsh_h::FS_SOURCE => {
(lineno_now, caller.clone()) }
Some(p) => {
let mut fl = p.flineno + lineno_now; if p.tp == crate::ported::zsh_h::FS_EVAL {
fl -= 1; }
let fname = p.filename.clone().or_else(|| Some(String::new())); (fl, fname)
}
};
let frame = crate::ported::zsh_h::funcstack {
prev: None, name: "(eval)".to_string(), filename, caller, flineno, lineno: lineno_now, tp: crate::ported::zsh_h::FS_EVAL, };
{
let mut stack = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
stack.push(frame); }
fpushed = true; } else {
fpushed = false; }
let joined = crate::ported::utils::zjoin(argv, ' ');
let prog = crate::ported::exec::parse_string(&joined, 1);
if let Some(prog) = prog {
let head = prog.prog.first().copied().unwrap_or(0);
if crate::ported::zsh_h::wc_code(head) != crate::ported::zsh_h::WC_LIST as u32 {
LASTVAL.store(0, Relaxed); } else {
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(&joined);
let ef = errflag.load(Relaxed);
let lv = LASTVAL.load(Relaxed);
if ef != 0 && lv == 0 {
LASTVAL.store(ef, Relaxed);
}
}
} else {
LASTVAL.store(1, Relaxed); }
if fpushed {
let mut stack = FUNCSTACK.lock().unwrap_or_else(|e| e.into_inner());
stack.pop();
}
errflag.fetch_and(!ERRFLAG_ERROR, Relaxed);
crate::ported::utils::set_scriptname(oscriptname);
INEVAL.store(oineval, Relaxed);
LASTVAL.load(Relaxed) }
pub fn bin_emulate(
nam: &str,
argv: &[String], ops: &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 {
zwarnnam(nam, "not enough arguments"); return 1; }
let bits = emulation.load(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, _ => EMULATE_ZSH, };
emulation.store(bits, Relaxed);
let mut cmdopts: HashMap<String, bool> = HashMap::new();
for n in ZSH_OPTIONS_SET.iter() {
cmdopts.insert(
n.to_string(),
crate::ported::options::opt_state_get(n).unwrap_or(false),
);
}
if !opt_l {
crate::ported::options::emulate(shname.as_str(), opt_r);
for n in ZSH_OPTIONS_SET.iter() {
cmdopts.insert(
n.to_string(),
crate::ported::options::opt_state_get(n).unwrap_or(false),
);
}
}
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 {
zwarnnam(nam, "too many arguments for -l"); return 1; }
let _ = opt_r;
crate::ported::options::emulate(shname.as_str(), opt_r);
let mut i = 1; let mut optionbreak = false;
while !optionbreak && i < argv.len() {
let arg = &argv[i];
let first = arg.chars().next().unwrap_or('\0');
if first != '-' && first != '+' {
break;
}
let action = first == '-'; if arg.len() == 1 {
i += 1;
break;
}
let bytes: Vec<char> = arg.chars().collect();
let mut j = 1;
let mut consumed_next_arg = false;
while j < bytes.len() {
let ch = bytes[j];
if ch == '-' {
if j == 1 && bytes.len() == 2 {
optionbreak = true;
i += 1;
break;
}
let name: String = bytes[j + 1..]
.iter()
.collect::<String>()
.replace('-', "_");
crate::ported::options::opt_state_set(
&name.to_lowercase().replace('_', ""),
action,
);
break;
}
if ch == 'o' || ch == 'O' {
let name = if j + 1 < bytes.len() {
bytes[j + 1..].iter().collect::<String>()
} else {
if i + 1 >= argv.len() {
zwarnnam(nam, "string expected after -o");
return 1; }
consumed_next_arg = true;
argv[i + 1].clone()
};
crate::ported::options::opt_state_set(
&name.to_lowercase().replace('_', ""),
action,
);
break; }
if ch == 'c' {
break;
}
let _ = ch;
j += 1;
}
i += 1;
if consumed_next_arg {
i += 1;
}
}
if i < argv.len() && !optionbreak {
zwarnnam(nam, &format!("unknown argument: {}", argv[i]));
return 1;
}
if opt_l_arg {
for nm in ["localoptions", "localtraps", "localpatterns"] {
crate::ported::options::opt_state_set(nm, true);
}
}
0
}
pub fn bin_eval(
_name: &str,
argv: &[String], _ops: &options,
_func: i32,
) -> i32 {
let stripped: Vec<String>;
let argv: &[String] = if let Some(first) = argv.first() {
if first == "--" {
stripped = argv[1..].to_vec();
&stripped
} else {
argv
}
} else {
argv
};
eval(argv) }
pub fn bin_read(
name: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let args = args.to_vec();
let mut nchars: i32 = 1; let mut partial_eof = false;
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(_) => {
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() {
zwarnnam(name, "only one array argument allowed"); return 1;
}
if (OPT_ISSET(ops, b'k') || OPT_ISSET(ops, b'q'))
&& !OPT_HASARG(ops, b'u')
&& !OPT_ISSET(ops, b'p')
{
let stdin_tty = unsafe { libc::isatty(0) } != 0;
let stderr_tty = unsafe { libc::isatty(2) } != 0;
if !stdin_tty && !stderr_tty {
eprintln!("not interactive and can't open terminal");
return 1;
}
}
if OPT_ISSET(ops, b'p')
|| (OPT_HASARG(ops, b'u') && OPT_ARG(ops, b'u').as_deref() == Some("p"))
{
let coprocin = crate::ported::modules::clone::coprocin
.load(std::sync::atomic::Ordering::Relaxed);
if coprocin < 0 {
zwarnnam(name, "-p: no coprocess");
return 1;
}
}
if OPT_ISSET(ops, b'l') || OPT_ISSET(ops, b'c') {
let zsh_mode =
crate::IS_ZSH_MODE.load(std::sync::atomic::Ordering::Relaxed);
if zsh_mode {
return crate::ported::init::fallback_compctlread(name);
}
return compctlread(name, &args[argi..]);
}
let ufd: i32 = if OPT_ISSET(ops, b'p') {
crate::ported::modules::clone::coprocin
.load(std::sync::atomic::Ordering::Relaxed)
} else if OPT_HASARG(ops, b'u') {
let argptr = OPT_ARG(ops, b'u').unwrap_or("");
match argptr.parse::<i32>() {
Ok(n) => n,
Err(_) => {
zwarnnam(name, &format!("number expected after -u: {}", argptr));
return 1;
}
}
} else {
0
};
let read_byte = |fd: i32| -> io::Result<Option<u8>> {
let mut b = [0u8; 1];
loop {
let n = unsafe { libc::read(fd, b.as_mut_ptr() as *mut libc::c_void, 1) };
match n {
1 => return Ok(Some(b[0])),
0 => return Ok(None),
-1 => {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
_ => return Ok(None),
}
}
};
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 {
let stdin_is_tty = unsafe { libc::isatty(0) } != 0;
if stdin_is_tty {
eprint!("{}", p);
let _ = Write::flush(&mut io::stderr());
}
}
let mut buf = String::new();
if OPT_ISSET(ops, b'z') {
let popped: Option<String> = {
let mut stack = crate::ported::zle::zle_main::BUFSTACK.lock().unwrap();
if stack.is_empty() {
None
} else {
Some(stack.remove(0))
}
};
let zbuf = popped.clone().unwrap_or_default();
let raw_mode = OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'R');
if OPT_HASARG(ops, b'd') {
let arg = OPT_ARG(ops, b'd').unwrap_or("");
let delim = arg.as_bytes().first().copied().unwrap_or(b'\0');
let mut out = Vec::<u8>::new();
for &b in zbuf.as_bytes() {
if b == delim {
break;
}
out.push(b);
}
buf = String::from_utf8_lossy(&out).into_owned();
} else {
let mut out = Vec::<u8>::new();
let bytes = zbuf.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if !raw_mode && b == b'\\' && i + 1 < bytes.len() {
let nx = bytes[i + 1];
if nx == b'\n' {
i += 2;
continue;
}
out.push(nx);
i += 2;
continue;
}
if b == b'\n' {
break;
}
out.push(b);
i += 1;
}
buf = String::from_utf8_lossy(&out).into_owned();
}
let opt_e = OPT_ISSET(ops, b'e');
let opt_big_e = OPT_ISSET(ops, b'E');
if opt_e || opt_big_e {
println!("{}", buf);
}
if !opt_e {
setsparam(&reply, &buf);
}
return if popped.is_none() || popped.as_deref() == Some("") {
1
} else {
0
};
}
if OPT_ISSET(ops, b'k') {
if nchars <= 0 {
return 1;
}
let mut got = vec![0u8; nchars as usize];
let mut bytes_read = 0;
while bytes_read < nchars as usize {
match read_byte(ufd) {
Ok(Some(b)) => {
got[bytes_read] = b;
bytes_read += 1;
}
_ => break,
}
}
buf = String::from_utf8_lossy(&got[..bytes_read]).into_owned();
} else if OPT_HASARG(ops, b'd') {
let arg = OPT_ARG(ops, b'd').unwrap_or("");
let delim = arg.as_bytes().first().copied().unwrap_or(b'\0');
let mut buf_bytes = Vec::<u8>::new();
let mut got_any = false;
loop {
match read_byte(ufd) {
Ok(Some(b)) => {
got_any = true;
if b == delim {
break;
}
buf_bytes.push(b);
}
Ok(None) => break,
Err(_) => return 2,
}
}
buf = String::from_utf8_lossy(&buf_bytes).into_owned();
if arg.is_empty() {
while buf.ends_with('\n') {
buf.pop();
}
}
if !got_any {
return 1; }
} else {
let raw_mode = OPT_ISSET(ops, b'r') || OPT_ISSET(ops, b'R');
let mut buf_bytes = Vec::<u8>::new();
let mut got_any = false;
let mut saw_newline = false;
loop {
match read_byte(ufd) {
Ok(Some(b)) => {
got_any = true;
if !raw_mode && b == b'\\' {
match read_byte(ufd) {
Ok(Some(nx)) => {
if nx == b'\n' {
continue;
}
buf_bytes.push(nx);
continue;
}
Ok(None) => {
buf_bytes.push(b'\\');
break;
}
Err(_) => return 2,
}
}
if b == b'\n' {
saw_newline = true;
break;
}
buf_bytes.push(b);
}
Ok(None) => break,
Err(_) => return 2,
}
}
if !got_any {
buf = String::new();
partial_eof = true;
} else {
buf = String::from_utf8_lossy(&buf_bytes).into_owned();
partial_eof = !saw_newline;
}
}
if want_array {
let ifs = 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 trimmed = trimmed.trim_end_matches(|c: char| is_ifs(c) && c.is_whitespace());
let mut parts: Vec<String> = Vec::new();
let mut field = String::new();
let mut chars = trimmed.chars().peekable();
while let Some(c) = chars.next() {
if is_ifs(c) {
parts.push(std::mem::take(&mut field));
if c.is_whitespace() {
while let Some(&n) = chars.peek() {
if is_ifs(n) && n.is_whitespace() {
chars.next();
} else {
break;
}
}
}
} else {
field.push(c);
}
}
if !field.is_empty() || !parts.is_empty() {
parts.push(field);
}
if parts.is_empty() {
parts.push(String::new());
}
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 = 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();
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());
setsparam(var, &field);
remaining = rest.to_string();
}
None => {
setsparam(var, &remaining);
remaining.clear();
}
}
}
}
} else {
let ifs = 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())
.trim_end_matches(|c: char| is_ifs(c) && c.is_whitespace());
let opt_e = OPT_ISSET(ops, b'e');
let opt_big_e = OPT_ISSET(ops, b'E');
if opt_e || opt_big_e {
println!("{}", trimmed);
}
if !opt_e {
setsparam(&reply, trimmed);
}
}
if partial_eof {
return 1;
}
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(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 io::Error::last_os_error().kind() == io::ErrorKind::Interrupted => continue,
_ => return -1,
}
}
}
pub fn testlex() {
if TEST_TOK.load(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(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(Relaxed);
TEST_TOK.store(
if prev != 0 { TEST_NULLTOK } else { TEST_LEXERR }, 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, Relaxed);
idx += 1; TESTARGS_IDX.store(idx as i32, Relaxed);
let _ = &mut *targs; }
pub fn bin_test(
name: &str,
argv: &[String], _ops: &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("]") {
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); }
if argv.len() == 3 && argv[0].starts_with('-') && argv[0].len() >= 3 {
if matches!(
argv[0].as_str(),
"-eq" | "-ne" | "-lt" | "-gt" | "-le" | "-ge"
| "-nt" | "-ot" | "-ef"
) {
crate::ported::utils::zwarnnam(
name,
&format!("unknown condition: {}", argv[0]),
);
return 2;
}
}
let known_binops: &[&str] = &[
"=", "==", "!=", "<", ">",
"-eq", "-ne", "-lt", "-gt", "-le", "-ge",
"-nt", "-ot", "-ef",
"&&", "||", "-a", "-o",
"=~", "-regex-match",
];
if argv.len() == 3 && argv[0].starts_with('-') && argv[0].len() == 2 {
let op_char = argv[0].chars().nth(1).unwrap_or(' ');
let is_known_unary = matches!(
op_char,
'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'k' | 'L'
| 'n' | 'o' | 'p' | 'r' | 's' | 'S' | 't' | 'u' | 'v'
| 'w' | 'x' | 'z' | 'G' | 'N' | 'O'
);
if is_known_unary && !known_binops.contains(&argv[1].as_str()) {
crate::ported::utils::zwarnnam(name, "too many arguments");
return 2;
}
}
if argv.len() == 3
&& !argv[0].starts_with('-')
&& argv[0] != "!"
&& argv[0] != "("
{
let mid = argv[1].as_str();
if mid.starts_with('-')
&& argv[1].len() >= 2
&& !known_binops.contains(&mid)
{
crate::ported::utils::zwarnnam(
name,
&format!("unknown condition: {}", argv[1]),
);
return 2;
}
if !known_binops.contains(&mid) {
crate::ported::utils::zwarn(&format!(
"condition expected: {}",
argv[1]
));
return 2;
}
}
if argv.len() == 4
&& matches!(
argv[1].as_str(),
"=" | "==" | "!=" | "-eq" | "-ne" | "-lt" | "-gt" | "-le" | "-ge"
| "-nt" | "-ot" | "-ef"
)
{
crate::ported::utils::zwarnnam(name, "too many arguments");
return 2;
}
if argv.len() == 4 && argv[0].starts_with('-') && argv[0].len() == 2 {
let op_char = argv[0].chars().nth(1).unwrap_or(' ');
if matches!(
op_char,
'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'k' | 'L'
| 'n' | 'o' | 'p' | 'r' | 's' | 'S' | 't' | 'u' | 'v'
| 'w' | 'x' | 'z' | 'G' | 'N' | 'O'
) {
crate::ported::utils::zwarnnam(name, "too many arguments");
return 2;
}
}
if argv.len() >= 4 && argv[0] != "(" {
let has_connective = argv
.iter()
.any(|a| matches!(a.as_str(), "&&" | "||" | "-a" | "-o"));
let has_paren = argv.iter().any(|a| a == "(");
let has_known_binop_mid = argv
.iter()
.any(|a| known_binops.contains(&a.as_str()));
if !has_connective && !has_paren && !has_known_binop_mid {
crate::ported::utils::zwarn(&format!(
"condition expected: {}",
argv[0]
));
let _ = name;
return 2;
}
}
{
let mut depth: i32 = 0;
let mut surplus_close = false;
let mut had_open = false;
for a in &argv {
if a == "(" {
depth += 1;
had_open = true;
} else if a == ")" {
depth -= 1;
if depth < 0 {
surplus_close = true;
break;
}
}
}
if surplus_close {
crate::ported::utils::zwarnnam(name, "too many arguments");
return 2;
}
if depth > 0 && had_open {
crate::ported::utils::zwarnnam(name, "argument expected");
return 2;
}
}
if argv.len() == 2 && argv[0] == "(" && argv[1] == ")" {
crate::ported::utils::zwarnnam(name, "argument expected");
return 2;
}
if argv.len() == 2
&& argv[0].starts_with('-')
&& argv[0].len() >= 2
&& argv[0] != "!"
{
let op_char = argv[0].chars().nth(1).unwrap_or(' ');
let is_known_unary = argv[0].len() == 2
&& matches!(
op_char,
'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'k' | 'L'
| 'n' | 'o' | 'p' | 'r' | 's' | 'S' | 't' | 'u' | 'v'
| 'w' | 'x' | 'z' | 'G' | 'N' | 'O'
);
if !is_known_unary {
crate::ported::utils::zwarnnam(
name,
&format!("unknown condition: {}", argv[0]),
);
return 2;
}
} else if argv.len() == 2
&& !argv[0].starts_with('-')
&& argv[0] != "!"
&& argv[0] != "("
&& argv[1] != ")"
{
crate::ported::utils::zwarn(&format!(
"parse error: condition expected: {}",
argv[0]
));
let _ = name;
return 2;
}
if argv.iter().any(|a| a == "<" || a == ">") {
let offending = argv
.iter()
.find(|a| a.as_str() == "<" || a.as_str() == ">")
.map(|s| s.as_str())
.unwrap_or("<");
crate::ported::utils::zwarn(&format!("condition expected: {}", offending));
let _ = name;
return 2;
}
let args_refs: Vec<&str> = argv.iter().map(|s| s.as_str()).collect();
let options = HashMap::new();
let mut variables = HashMap::new();
{
let tab = paramtab().read().unwrap();
for (k, pm) in tab.iter() {
if (pm.node.flags as u32 & PM_UNSET) != 0 {
continue;
}
let v = pm.u_str.clone().unwrap_or_default();
variables.insert(k.clone(), v);
}
}
for (k, v) in env::vars() {
variables.entry(k).or_insert(v);
}
let posix = isset(POSIXBUILTINS);
let mut ret = crate::ported::cond::evalcond(
&args_refs,
&options,
&variables,
posix,
Some(name),
);
if ret < 2 && sense != 0 {
ret = if ret == 0 { 1 } else { 0 }; }
ret }
pub fn bin_times(
_name: &str,
_argv: &[String], _ops: &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 x = t as i64;
let clktck_i = clktck as i64;
let mins = x / (60 * clktck_i);
let secs = (x / clktck_i) % clktck_i;
let csec = (x * 100 / clktck_i) % 100;
print!("{}m{}.{:02}s", mins, secs, csec);
};
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: &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() {
enum TrapEntry {
Func(String),
Str(String, String),
}
queue_signals(); let traps = traps_table().lock().map(|t| t.clone()).unwrap_or_default();
let trap_funcs: Vec<(String, i32, String)> = {
let mut acc: Vec<(String, i32, String)> = Vec::new();
if let Ok(tab) = crate::ported::hashtable::shfunctab_lock().read() {
for (fname, _) in tab.iter() {
if let Some(sig_name) = fname.strip_prefix("TRAP") {
let idx = getsigidx(sig_name);
if idx != -1 {
acc.push((fname.clone(), idx, sig_name.to_string()));
}
}
}
}
acc
};
let mut combined: Vec<(i32, TrapEntry)> = Vec::new();
for (fname, idx, _sig) in &trap_funcs {
combined.push((*idx, TrapEntry::Func(fname.clone())));
}
for (sig, body) in traps.iter() {
let idx = getsigidx(sig);
if trap_funcs.iter().any(|(_, i, _)| *i == idx) {
continue;
}
combined.push((
if idx == -1 { i32::MAX } else { idx },
TrapEntry::Str(sig.clone(), body.clone()),
));
}
combined.sort_by_key(|(idx, _)| *idx);
for (_idx, entry) in &combined {
match entry {
TrapEntry::Func(fname) => {
if let Ok(tab) = crate::ported::hashtable::shfunctab_lock().read() {
if let Some(shf) = tab.get(fname) {
crate::ported::hashtable::printshfuncnode(shf, 0);
}
}
}
TrapEntry::Str(sig, body) => {
print!("trap -- "); print!("{}", quotedzputs(body)); println!(" {}", sig); }
}
}
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 {
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()) {
zwarnnam(name, &format!("undefined signal: {}", arg)); return 1; }
return 0;
}
let mut trap_install_error = 0i32;
for sigarg in &argv {
let sig = getsigidx(sigarg);
if sig == -1 {
zwarnnam(name, &format!("undefined signal: {}", sigarg)); trap_install_error = 1; break; }
let canonical = if sig == 0 {
"EXIT".to_string()
} else if let Some(name) = crate::ported::signals_h::sigs_name(sig) {
name.to_string()
} else {
sigarg
.strip_prefix("SIG")
.or_else(|| sigarg.strip_prefix("sig"))
.unwrap_or(sigarg.as_str())
.to_uppercase()
};
if sig >= 0 && sig <= crate::ported::signals_h::SIGCOUNT && sig != libc::SIGCHLD as i32 {
let body_eprog: Option<crate::ported::zsh_h::Eprog> = if arg.is_empty() {
None
} else {
let mut prog = crate::ported::zsh_h::eprog::default();
prog.prog.push(1);
Some(Box::new(prog))
};
settrap(sig, body_eprog, 0);
}
if let Ok(mut t) = traps_table().lock() {
t.insert(canonical.clone(), arg.clone()); }
let trap_fn_name = format!("TRAP{}", canonical);
if let Ok(mut tab) = crate::ported::hashtable::shfunctab_lock().write() {
tab.remove(&trap_fn_name);
}
}
trap_install_error
}
pub fn bin_ttyctl(
_name: &str,
_argv: &[String], ops: &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: &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 {
match matheval(expr) {
Ok(v) => val = v, Err(msg) => {
zerr(&msg);
}
}
}
if (errflag.load(Relaxed) & ERRFLAG_ERROR) != 0 {
errflag.fetch_and(!ERRFLAG_ERROR, Relaxed); return 1; }
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: &options,
_func: i32,
) -> i32 {
queue_signals(); let mut um: u32 = unsafe { libc::umask(0o777) } as u32; unsafe {
libc::umask(um as libc::mode_t);
} 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(_) => {
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 {
zwarnnam(
nam,
&format!("bad symbolic mode operator: {}", umaskop as char),
); } else {
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 => {
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() {
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: &options,
_func: i32,
) -> i32 {
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 HandlerFunc),
0,
-1,
BIN_BRACKET,
None,
None,
),
BUILTIN(
".",
BINF_PSPECIAL,
Some(bin_dot as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
":",
BINF_PSPECIAL,
Some(bin_true as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"alias",
BINF_MAGICEQUALS | BINF_PLUSOPTS,
Some(bin_alias as HandlerFunc),
0,
-1,
0,
Some("Lgmrs"),
None,
),
BUILTIN(
"autoload",
BINF_PLUSOPTS,
Some(bin_functions as HandlerFunc),
0,
-1,
0,
Some("dmktrRTUwWXz"),
Some("u"),
),
BUILTIN(
"bg",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_BG,
None,
None,
),
BUILTIN(
"break",
BINF_PSPECIAL,
Some(bin_break as HandlerFunc),
0,
1,
BIN_BREAK,
None,
None,
),
BUILTIN(
"bye",
0,
Some(bin_break as HandlerFunc),
0,
1,
BIN_EXIT,
None,
None,
),
BUILTIN(
"cd",
BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID,
Some(bin_cd as HandlerFunc),
0,
2,
BIN_CD,
Some("qsPL"),
None,
),
BUILTIN(
"chdir",
BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID,
Some(bin_cd as HandlerFunc),
0,
2,
BIN_CD,
Some("qsPL"),
None,
),
BUILTIN(
"continue",
BINF_PSPECIAL,
Some(bin_break as HandlerFunc),
0,
1,
BIN_CONTINUE,
None,
None,
),
BUILTIN(
"declare",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("AE:%F:%HL:%R:%TUZ:%afghi:%klmnp:%rtuxz"),
None,
),
BUILTIN(
"dirs",
0,
Some(bin_dirs as HandlerFunc),
0,
-1,
0,
Some("clpv"),
None,
),
BUILTIN(
"disable",
0,
Some(bin_enable as HandlerFunc),
0,
-1,
BIN_DISABLE,
Some("afmprs"),
None,
),
BUILTIN(
"disown",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_DISOWN,
None,
None,
),
BUILTIN(
"echo",
BINF_SKIPINVALID,
Some(bin_print as HandlerFunc),
0,
-1,
BIN_ECHO,
Some("neE"),
Some("-"),
),
BUILTIN(
"emulate",
0,
Some(bin_emulate as HandlerFunc),
0,
-1,
0,
Some("lLR"),
None,
),
BUILTIN(
"enable",
0,
Some(bin_enable as HandlerFunc),
0,
-1,
BIN_ENABLE,
Some("afmprs"),
None,
),
BUILTIN(
"eval",
BINF_PSPECIAL,
Some(bin_eval as HandlerFunc),
0,
-1,
BIN_EVAL,
None,
None,
),
BUILTIN(
"exit",
BINF_PSPECIAL,
Some(bin_break as HandlerFunc),
0,
1,
BIN_EXIT,
None,
None,
),
BUILTIN(
"export",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
BIN_EXPORT,
Some("E:%F:%HL:%R:%TUZ:%afhi:%lp:%rtu"),
Some("xg"),
),
BUILTIN(
"false",
0,
Some(bin_false as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"fc",
0,
Some(bin_fc as HandlerFunc),
0,
-1,
BIN_FC,
Some("aAdDe:EfiIlLmnpPrRst:W"),
None,
),
BUILTIN(
"fg",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_FG,
None,
None,
),
BUILTIN(
"float",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("E:%F:%HL:%R:%Z:%ghlp:%rtux"),
Some("E"),
),
BUILTIN(
"functions",
BINF_PLUSOPTS,
Some(bin_functions as HandlerFunc),
0,
-1,
0,
Some("ckmMstTuUWx:z"),
None,
),
BUILTIN(
"getln",
0,
Some(bin_read as HandlerFunc),
0,
-1,
0,
Some("ecnAlE"),
Some("zr"),
),
BUILTIN(
"getopts",
0,
Some(bin_getopts as HandlerFunc),
2,
-1,
0,
None,
None,
),
BUILTIN(
"hash",
BINF_MAGICEQUALS,
Some(bin_hash as HandlerFunc),
0,
-1,
0,
Some("Ldfmrv"),
None,
),
BUILTIN(
"hashinfo",
0,
Some(crate::ported::hashtable::bin_hashinfo as HandlerFunc),
0,
0,
0,
None,
None,
),
BUILTIN(
"history",
0,
Some(bin_fc as HandlerFunc),
0,
-1,
BIN_FC,
Some("adDEfiLmnpPrt:"),
Some("l"),
),
BUILTIN(
"integer",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("HL:%R:%Z:%ghi:%lp:%rtux"),
Some("i"),
),
BUILTIN(
"jobs",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_JOBS,
Some("dlpZrs"),
None,
),
BUILTIN(
"kill",
BINF_HANDLES_OPTS,
Some(crate::ported::jobs::bin_kill as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN("let", 0, Some(bin_let as HandlerFunc), 1, -1, 0, None, None),
BUILTIN(
"local",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("AE:%F:%HL:%R:%TUZ:%ahi:%lnp:%rtux"),
None,
),
BUILTIN(
"logout",
0,
Some(bin_break as HandlerFunc),
0,
1,
BIN_LOGOUT,
None,
None,
),
BUILTIN(
"mem",
0,
Some(crate::ported::mem::bin_mem as HandlerFunc),
0,
0,
0,
Some("v"),
None,
),
BUILTIN(
"popd",
BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID,
Some(bin_cd as HandlerFunc),
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 HandlerFunc),
0,
-1,
BIN_PRINT,
Some("abcC:Df:ilmnNoOpPrRsSu:v:x:X:z-"),
None,
),
BUILTIN(
"printf",
BINF_SKIPINVALID | BINF_SKIPDASH,
Some(bin_print as HandlerFunc),
1,
-1,
BIN_PRINTF,
Some("v:"),
None,
),
BUILTIN(
"pushd",
BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID,
Some(bin_cd as HandlerFunc),
0,
2,
BIN_PUSHD,
Some("qsPL"),
None,
),
BUILTIN(
"pushln",
0,
Some(bin_print as HandlerFunc),
0,
-1,
BIN_PRINT,
None,
Some("-nz"),
),
BUILTIN(
"pwd",
0,
Some(bin_pwd as HandlerFunc),
0,
0,
0,
Some("rLP"),
None,
),
BUILTIN(
"r",
0,
Some(bin_fc as HandlerFunc),
0,
-1,
BIN_R,
Some("IlLnr"),
None,
),
BUILTIN(
"read",
0,
Some(bin_read as 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 HandlerFunc),
0,
-1,
BIN_READONLY,
Some("AE:%F:%HL:%R:%TUZ:%afghi:%lptux"),
Some("r"),
),
BUILTIN(
"rehash",
0,
Some(bin_hash as HandlerFunc),
0,
0,
0,
Some("df"),
Some("r"),
),
BUILTIN(
"return",
BINF_PSPECIAL,
Some(bin_break as HandlerFunc),
0,
1,
BIN_RETURN,
None,
None,
),
BUILTIN(
"set",
BINF_PSPECIAL | BINF_HANDLES_OPTS,
Some(bin_set as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"setopt",
0,
Some(crate::ported::options::bin_setopt as HandlerFunc),
0,
-1,
BIN_SETOPT,
None,
None,
),
BUILTIN(
"sched",
0,
Some(crate::ported::builtins::sched::bin_sched as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"shift",
BINF_PSPECIAL,
Some(bin_shift as HandlerFunc),
0,
-1,
0,
Some("p"),
None,
),
BUILTIN(
"source",
BINF_PSPECIAL,
Some(bin_dot as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"suspend",
0,
Some(crate::ported::jobs::bin_suspend as HandlerFunc),
0,
0,
0,
Some("f"),
None,
),
BUILTIN(
"test",
BINF_HANDLES_OPTS,
Some(bin_test as HandlerFunc),
0,
-1,
BIN_TEST,
None,
None,
),
BUILTIN(
"ttyctl",
0,
Some(bin_ttyctl as HandlerFunc),
0,
0,
0,
Some("fu"),
None,
),
BUILTIN(
"limit",
0,
Some(crate::ported::builtins::rlimits::bin_limit as HandlerFunc),
0,
-1,
0,
Some("sh"),
None,
), BUILTIN(
"ulimit",
0,
Some(crate::ported::builtins::rlimits::bin_ulimit as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"unlimit",
0,
Some(crate::ported::builtins::rlimits::bin_unlimit as HandlerFunc),
0,
-1,
0,
Some("hs"),
None,
), BUILTIN(
"times",
BINF_PSPECIAL,
Some(bin_times as HandlerFunc),
0,
0,
0,
None,
None,
),
BUILTIN(
"trap",
BINF_PSPECIAL | BINF_HANDLES_OPTS,
Some(bin_trap as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"true",
0,
Some(bin_true as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"type",
0,
Some(bin_whence as HandlerFunc),
0,
-1,
0,
Some("ampfsSw"),
Some("v"),
),
BUILTIN(
"typeset",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("AE:%F:%HL:%R:%TUZ:%afghi:%klp:%rtuxmnz"),
None,
),
BUILTIN(
"nameref",
BINF_ASSIGN,
Some(bin_typeset as HandlerFunc),
0,
-1,
0,
Some("gpru"),
Some("n"),
),
BUILTIN(
"umask",
0,
Some(bin_umask as HandlerFunc),
0,
1,
0,
Some("S"),
None,
),
BUILTIN(
"unalias",
0,
Some(bin_unhash as HandlerFunc),
0,
-1,
BIN_UNALIAS,
Some("ams"),
None,
),
BUILTIN(
"unfunction",
0,
Some(bin_unhash as HandlerFunc),
1,
-1,
BIN_UNFUNCTION,
Some("m"),
Some("f"),
),
BUILTIN(
"unhash",
0,
Some(bin_unhash as HandlerFunc),
1,
-1,
BIN_UNHASH,
Some("adfms"),
None,
),
BUILTIN(
"unset",
BINF_PSPECIAL,
Some(bin_unset as HandlerFunc),
1,
-1,
BIN_UNSET,
Some("fmvn"),
None,
),
BUILTIN(
"unsetopt",
0,
Some(crate::ported::options::bin_setopt as HandlerFunc),
0,
-1,
BIN_UNSETOPT,
None,
None,
),
BUILTIN(
"wait",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_WAIT,
None,
None,
),
BUILTIN(
"whence",
0,
Some(bin_whence as HandlerFunc),
0,
-1,
0,
Some("acmpvfsSwx:"),
None,
),
BUILTIN(
"where",
0,
Some(bin_whence as HandlerFunc),
0,
-1,
0,
Some("pmsSwx:"),
Some("ca"),
),
BUILTIN(
"which",
0,
Some(bin_whence as HandlerFunc),
0,
-1,
0,
Some("ampsSwx:"),
Some("c"),
),
BUILTIN(
"zmodload",
0,
Some(crate::ported::module::bin_zmodload as HandlerFunc),
0,
-1,
0,
Some("AFRILP:abcfdilmpsue"),
None,
),
BUILTIN(
"zcompile",
0,
Some(crate::ported::parse::bin_zcompile as HandlerFunc),
0,
-1,
0,
Some("tUMRcmzka"),
None,
),
BUILTIN(
"zstyle",
0,
Some(crate::ported::modules::zutil::bin_zstyle as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"zformat",
0,
Some(crate::ported::modules::zutil::bin_zformat as HandlerFunc),
3,
-1,
0,
None,
None,
),
BUILTIN(
"zparseopts",
0,
Some(crate::ported::modules::zutil::bin_zparseopts as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"zregexparse",
0,
Some(crate::ported::modules::zutil::bin_zregexparse as HandlerFunc),
0,
-1,
0,
Some("c"),
None,
),
BUILTIN(
"cap",
0,
Some(crate::ported::modules::cap::bin_cap as HandlerFunc),
0,
1,
0,
None,
None,
),
BUILTIN(
"getcap",
0,
Some(crate::ported::modules::cap::bin_getcap as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"setcap",
0,
Some(crate::ported::modules::cap::bin_setcap as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"pcre_compile",
0,
Some(crate::ported::modules::pcre::bin_pcre_compile as HandlerFunc),
1,
1,
0,
Some("aimx"),
None,
),
BUILTIN(
"pcre_study",
0,
Some(crate::ported::modules::pcre::bin_pcre_study as HandlerFunc),
0,
0,
0,
None,
None,
),
BUILTIN(
"pcre_match",
0,
Some(crate::ported::modules::pcre::bin_pcre_match as HandlerFunc),
1,
-1,
0,
Some("ab:nv:"),
None,
),
BUILTIN(
"ztcp",
0,
Some(crate::ported::modules::tcp::bin_ztcp as HandlerFunc),
0,
-1,
0,
Some("acdflLtv"),
None,
),
BUILTIN(
"ztie",
0,
Some(crate::ported::modules::db_gdbm::bin_ztie as HandlerFunc),
0,
-1,
0,
Some("d:f:r"),
None,
),
BUILTIN(
"zuntie",
0,
Some(crate::ported::modules::db_gdbm::bin_zuntie as HandlerFunc),
1,
-1,
0,
Some("u"),
None,
),
BUILTIN(
"zgdbmpath",
0,
Some(crate::ported::modules::db_gdbm::bin_zgdbmpath as HandlerFunc),
1,
1,
0,
None,
None,
),
BUILTIN(
"echoti",
0,
Some(crate::ported::modules::terminfo::bin_echoti as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"fg",
0,
Some(bin_fg as HandlerFunc),
0,
-1,
BIN_FG,
None,
None,
),
BUILTIN(
"kill",
BINF_HANDLES_OPTS,
Some(crate::ported::jobs::bin_kill as HandlerFunc),
0,
-1,
0,
None,
None,
),
BUILTIN(
"suspend",
0,
Some(crate::ported::jobs::bin_suspend as HandlerFunc),
0,
0,
0,
Some("f"),
None,
),
BUILTIN(
"bindkey",
0,
Some(crate::ported::zle::zle_keymap::bin_bindkey as HandlerFunc),
0,
-1,
0,
Some("evaM:ldDANmrsLRp"),
None,
),
BUILTIN(
"vared",
0,
Some(crate::ported::zle::zle_main::bin_vared as HandlerFunc),
1,
1,
0,
Some("aAcef:ghi:M:m:p:r:t:"),
None,
),
BUILTIN(
"compadd",
0,
Some(crate::ported::zle::complete::bin_compadd as HandlerFunc),
0,
-1,
0,
Some("J:V:1X:fnqQF:Wsi"),
None,
),
BUILTIN(
"compset",
0,
Some(crate::ported::zle::complete::bin_compset as HandlerFunc),
1,
-1,
0,
Some("npqPS:"),
None,
),
BUILTIN(
"comparguments",
0,
Some(crate::ported::zle::computil::bin_comparguments as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"compdescribe",
0,
Some(crate::ported::zle::computil::bin_compdescribe as HandlerFunc),
3,
-1,
0,
None,
None,
), BUILTIN(
"compfiles",
0,
Some(crate::ported::zle::computil::bin_compfiles as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"compgroups",
0,
Some(crate::ported::zle::computil::bin_compgroups as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"compquote",
0,
Some(crate::ported::zle::computil::bin_compquote as HandlerFunc),
1,
-1,
0,
Some("p"),
None,
), BUILTIN(
"comptags",
0,
Some(crate::ported::zle::computil::bin_comptags as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"comptry",
0,
Some(crate::ported::zle::computil::bin_comptry as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"compvalues",
0,
Some(crate::ported::zle::computil::bin_compvalues as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"syserror",
0,
Some(crate::ported::modules::system::bin_syserror as HandlerFunc),
0,
1,
0,
Some("e:p:"),
None,
), BUILTIN(
"sysread",
0,
Some(crate::ported::modules::system::bin_sysread as HandlerFunc),
0,
1,
0,
Some("c:i:o:s:t:"),
None,
), BUILTIN(
"syswrite",
0,
Some(crate::ported::modules::system::bin_syswrite as HandlerFunc),
1,
1,
0,
Some("c:o:"),
None,
), BUILTIN(
"sysopen",
0,
Some(crate::ported::modules::system::bin_sysopen as HandlerFunc),
1,
1,
0,
Some("rwau:o:m:"),
None,
), BUILTIN(
"sysseek",
0,
Some(crate::ported::modules::system::bin_sysseek as HandlerFunc),
1,
1,
0,
Some("u:w:"),
None,
), BUILTIN(
"zsystem",
0,
Some(crate::ported::modules::system::bin_zsystem as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"zselect",
0,
Some(crate::ported::modules::zselect::bin_zselect as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"zsocket",
0,
Some(crate::ported::modules::socket::bin_zsocket as HandlerFunc),
0,
3,
0,
Some("ad:ltv"),
None,
), BUILTIN(
"stat",
0,
Some(crate::ported::modules::stat::bin_stat as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"zstat",
0,
Some(crate::ported::modules::stat::bin_stat as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"log",
0,
Some(crate::ported::modules::watch::bin_log as HandlerFunc),
0,
0,
0,
None,
None,
), BUILTIN(
"zprof",
0,
Some(crate::ported::modules::zprof::bin_zprof as HandlerFunc),
0,
0,
0,
Some("c"),
None,
), BUILTIN(
"strftime",
0,
Some(crate::ported::modules::datetime::bin_strftime as HandlerFunc),
1,
3,
0,
Some("nqrs:"),
None,
), BUILTIN(
"zftp",
0,
Some(crate::ported::modules::zftp::bin_zftp as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"zpty",
0,
Some(crate::ported::modules::zpty::bin_zpty as HandlerFunc),
0,
-1,
0,
Some("ebdmrwLnt"),
None,
), BUILTIN(
"zcurses",
0,
Some(crate::ported::modules::curses::bin_zcurses as HandlerFunc),
1,
-1,
0,
Some(""),
None,
), BUILTIN(
"clone",
0,
Some(crate::ported::modules::clone::bin_clone as HandlerFunc),
1,
1,
0,
None,
None,
), BUILTIN(
"example",
0,
Some(crate::ported::modules::example::bin_example as HandlerFunc),
0,
-1,
0,
Some("flags"),
None,
),
BUILTIN(
"private",
BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN,
Some(crate::ported::modules::param_private::bin_private as HandlerFunc),
0,
-1,
0,
Some("AE:%F:%HL:%PR:%TUZ:%ahi:%lnmrtux"),
Some("P"),
), BUILTIN(
"echotc",
0,
Some(crate::ported::modules::termcap::bin_echotc as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"compcall",
0,
Some(crate::ported::zle::compctl::bin_compcall as HandlerFunc),
0,
0,
0,
Some("TD"),
None,
), BUILTIN(
"compctl",
0,
Some(crate::ported::zle::compctl::bin_compctl as HandlerFunc),
0,
-1,
0,
None,
None,
), BUILTIN(
"zgetattr",
0,
Some(crate::ported::modules::attr::bin_getattr as HandlerFunc),
2,
3,
0,
Some("h"),
None,
), BUILTIN(
"zsetattr",
0,
Some(crate::ported::modules::attr::bin_setattr as HandlerFunc),
3,
3,
0,
Some("h"),
None,
), BUILTIN(
"zdelattr",
0,
Some(crate::ported::modules::attr::bin_delattr as HandlerFunc),
2,
-1,
0,
Some("h"),
None,
), BUILTIN(
"zlistattr",
0,
Some(crate::ported::modules::attr::bin_listattr as HandlerFunc),
1,
2,
0,
Some("h"),
None,
), BUILTIN(
"zle",
0,
Some(crate::ported::zle::zle_thingy::bin_zle as HandlerFunc),
0,
-1,
0,
Some("aAcCDfFIKlLmMNRTU"),
None,
),
BUILTIN(
"mkdir",
0,
Some(crate::ported::modules::files::bin_mkdir as HandlerFunc),
1,
-1,
0,
Some("pm:"),
None,
),
BUILTIN(
"rmdir",
0,
Some(crate::ported::modules::files::bin_rmdir as HandlerFunc),
1,
-1,
0,
None,
None,
),
BUILTIN(
"ln",
0,
Some(crate::ported::modules::files::bin_ln as HandlerFunc),
1,
-1,
0,
Some("dfins"),
None,
),
BUILTIN(
"mv",
0,
Some(crate::ported::modules::files::bin_ln as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_MV,
Some("fi"),
None,
),
BUILTIN(
"rm",
0,
Some(crate::ported::modules::files::bin_rm as HandlerFunc),
1,
-1,
0,
Some("dfiRrs"),
None,
),
BUILTIN(
"chmod",
0,
Some(crate::ported::modules::files::bin_chmod as HandlerFunc),
2,
-1,
0,
Some("Rs"),
None,
),
BUILTIN(
"chgrp",
0,
Some(crate::ported::modules::files::bin_chown as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_CHGRP,
Some("hRs"),
None,
),
BUILTIN(
"chown",
0,
Some(crate::ported::modules::files::bin_chown as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_CHOWN,
Some("hRs"),
None,
),
BUILTIN(
"sync",
0,
Some(crate::ported::modules::files::bin_sync as HandlerFunc),
0,
0,
0,
None,
None,
),
BUILTIN(
"zf_chgrp",
0,
Some(crate::ported::modules::files::bin_chown as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_CHGRP,
Some("hRs"),
None,
), BUILTIN(
"zf_chmod",
0,
Some(crate::ported::modules::files::bin_chmod as HandlerFunc),
2,
-1,
0,
Some("Rs"),
None,
), BUILTIN(
"zf_chown",
0,
Some(crate::ported::modules::files::bin_chown as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_CHOWN,
Some("hRs"),
None,
), BUILTIN(
"zf_ln",
0,
Some(crate::ported::modules::files::bin_ln as HandlerFunc),
1,
-1,
crate::ported::modules::files::BIN_LN,
Some("dfins"),
None,
), BUILTIN(
"zf_mkdir",
0,
Some(crate::ported::modules::files::bin_mkdir as HandlerFunc),
1,
-1,
0,
Some("pm:"),
None,
), BUILTIN(
"zf_mv",
0,
Some(crate::ported::modules::files::bin_ln as HandlerFunc),
2,
-1,
crate::ported::modules::files::BIN_MV,
Some("fi"),
None,
), BUILTIN(
"zf_rm",
0,
Some(crate::ported::modules::files::bin_rm as HandlerFunc),
1,
-1,
0,
Some("dfiRrs"),
None,
), BUILTIN(
"zf_rmdir",
0,
Some(crate::ported::modules::files::bin_rmdir as HandlerFunc),
1,
-1,
0,
None,
None,
), BUILTIN(
"zf_sync",
0,
Some(crate::ported::modules::files::bin_sync as HandlerFunc),
0,
0,
0,
None,
None,
), ]
});
static builtintab: OnceLock<HashMap<String, &'static builtin>> = OnceLock::new();
pub static BUILTINS_DISABLED: std::sync::LazyLock<
Mutex<std::collections::HashSet<String>>,
> = std::sync::LazyLock::new(|| Mutex::new(std::collections::HashSet::new()));
pub static MATCHEDNODES: Mutex<Vec<String>> = 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: Mutex<Vec<i32>> = 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 EXIT_LEVEL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static SUBSHELL_DEPTH: 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: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static TESTARGS_IDX: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static TOKSTR: Mutex<String> = 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: Mutex<Vec<String>> = 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);
#[allow(non_snake_case)]
pub fn BUILTIN(
name: &str,
flags: u32,
handler: Option<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: OnceLock<Mutex<HashMap<String, String>>> = 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]) -> Result<String, (String, char)> {
let (fmt, _) =
getkeystring_with(fmt, crate::ported::zsh_h::GETKEYS_PRINTF_FMT as u32); 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,
}
}
if iter.peek() == Some(&'*') {
iter.next(); let w: i64 = args.get(arg_i).and_then(|s| s.parse().ok()).unwrap_or(0);
arg_i += 1;
spec.push_str(&w.to_string());
} else {
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();
if iter.peek() == Some(&'*') {
iter.next();
let p: i64 = args.get(arg_i).and_then(|s| s.parse().ok()).unwrap_or(0);
arg_i += 1;
spec.push_str(&p.to_string());
} else {
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 = parse_int_arg(&a);
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 = parse_int_arg(&a) as u64;
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 = parse_int_arg(&a) as u64;
spec.push('x');
out.push_str(&format_spec_radix(&spec, n, 'x'));
arg_i += 1;
}
Some('X') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n = parse_int_arg(&a) as u64;
spec.push('X');
out.push_str(&format_spec_radix(&spec, n, 'X'));
arg_i += 1;
}
Some('o') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n = parse_int_arg(&a) as u64;
spec.push('o');
out.push_str(&format_spec_radix(&spec, n, 'o'));
arg_i += 1;
}
Some(conv @ ('f' | 'F' | 'g' | 'G' | 'e' | 'E')) => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let n: f64 = a.parse().unwrap_or(0.0);
out.push_str(&format_spec_float_conv(&spec, n, conv));
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(&crate::ported::utils::quotestring(
&a,
crate::ported::zsh_h::QT_BACKSLASH_SHOWNULL,
));
arg_i += 1;
}
Some('b') => {
let a = args.get(arg_i).cloned().unwrap_or_default();
let (s, _) = getkeystring_with(
&a,
crate::ported::zsh_h::GETKEYS_PRINTF_ARG as u32,
);
out.push_str(&s);
arg_i += 1;
}
Some('n') => {
arg_i += 1;
}
Some(other) => {
return Err((out, other)); }
None => out.push('%'),
}
}
if arg_i == prev || arg_i >= args.len() {
break;
}
}
Ok(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 parse_int_arg(s: &str) -> i64 {
if let Some(rest) = s.strip_prefix('\'').or_else(|| s.strip_prefix('"')) {
return rest.chars().next().map(|c| c as i64).unwrap_or(0);
}
let t = s.trim();
if t.is_empty() {
return 0;
}
let (neg, body) = if let Some(b) = t.strip_prefix('-') {
(true, b)
} else if let Some(b) = t.strip_prefix('+') {
(false, b)
} else {
(false, t)
};
let parsed: i64 = if let Some(h) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
i64::from_str_radix(h, 16).unwrap_or(0)
} else if let Some(idx) = body.find('#') {
let base_str = &body[..idx];
let digits = &body[idx + 1..];
if let Ok(base) = base_str.parse::<u32>() {
if (2..=36).contains(&base) {
i64::from_str_radix(digits, base).unwrap_or(0)
} else {
0
}
} else {
0
}
} else {
match body.parse::<i64>() {
Ok(n) => n,
Err(_) if body.chars().all(|c| c.is_ascii_digit()) && !body.is_empty() => {
let truncated = if body.len() > 19 {
&body[..19]
} else {
body
};
let val = truncated.parse::<u64>().map(|u| u as i64).unwrap_or(0);
let trunc_after = truncated.len();
zwarnnam(
"printf",
&format!("number truncated after {} digits: {}", trunc_after, body),
);
val
}
Err(_) => {
if let Ok(f) = body.parse::<f64>() {
f.trunc() as i64
} else {
let m = matheval(body)
.map(|n| {
if n.type_ == crate::ported::zsh_h::MN_INTEGER {
n.l
} else {
n.d.trunc() as i64
}
})
.unwrap_or(0);
m
}
}
}
};
if neg {
-parsed
} else {
parsed
}
}
fn format_spec_int(spec: &str, n: i64) -> String {
let (left_align, zero_pad_flag, width, prec) = parse_flags_width_prec(spec);
let zero_pad = zero_pad_flag && !left_align && prec.is_none();
let plus_flag = spec.contains('+');
let space_flag = spec.contains(' ') && !plus_flag;
let digits = n.unsigned_abs().to_string();
let digits = if let Some(p) = prec {
if digits.len() < p {
format!("{}{}", "0".repeat(p - digits.len()), digits)
} else {
digits
}
} else {
digits
};
let body = if n < 0 {
format!("-{}", digits)
} else if plus_flag {
format!("+{}", digits)
} else if space_flag {
format!(" {}", digits)
} else {
digits
};
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('-')
.or_else(|| body.strip_prefix('+'))
.or_else(|| body.strip_prefix(' '))
{
let sign = body.chars().next().unwrap();
format!("{}{}{}", sign, "0".repeat(pad), rest)
} else {
format!("{}{}", "0".repeat(pad), body)
}
} else {
format!("{}{}", " ".repeat(pad), body)
}
}
fn format_spec_radix(spec: &str, n: u64, conv: char) -> String {
let (left_align, zero_pad_flag, width, _prec) = parse_flags_width_prec(spec);
let zero_pad = zero_pad_flag && !left_align;
let hash_flag = spec.contains('#');
let body = match conv {
'x' => format!("{:x}", n),
'X' => format!("{:X}", n),
'o' => format!("{:o}", n),
_ => n.to_string(),
};
let body = if hash_flag {
match conv {
'x' if n != 0 => format!("0x{}", body),
'X' if n != 0 => format!("0X{}", body),
'o' if !body.starts_with('0') => format!("0{}", body),
_ => body,
}
} else {
body
};
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("0x").or_else(|| body.strip_prefix("0X")) {
let prefix = &body[..2];
format!("{}{}{}", prefix, "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, zero_pad, width, prec) = parse_flags_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 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_float_conv(spec: &str, n: f64, conv: char) -> String {
let (left_align, zero_pad, width, prec) = parse_flags_width_prec(spec);
let body = match conv {
'f' | 'F' => {
let p = prec.unwrap_or(6);
format!("{:.*}", p, n)
}
'e' | 'E' => {
let p = prec.unwrap_or(6);
let exp = if n == 0.0 {
0i32
} else {
n.abs().log10().floor() as i32
};
let mantissa = n / 10f64.powi(exp);
let body = format!("{:.*}", p, mantissa);
let e_char = if conv == 'E' { 'E' } else { 'e' };
let exp_sign = if exp >= 0 { '+' } else { '-' };
format!("{}{}{}{:02}", body, e_char, exp_sign, exp.abs())
}
'g' | 'G' => {
let p_sig: i32 = prec.unwrap_or(6).max(1) as i32;
let exp = if n == 0.0 {
0i32
} else {
n.abs().log10().floor() as i32
};
let use_e = exp < -4 || exp >= p_sig;
let body = if use_e {
let mantissa = n / 10f64.powi(exp);
let dec = (p_sig - 1).max(0) as usize;
let m = format!("{:.*}", dec, mantissa);
let e_char = if conv == 'G' { 'E' } else { 'e' };
let exp_sign = if exp >= 0 { '+' } else { '-' };
format!("{}{}{}{:02}", m, e_char, exp_sign, exp.abs())
} else {
let dec = (p_sig - 1 - exp).max(0) as usize;
format!("{:.*}", dec, n)
};
if !spec.contains('#') {
let stripped = if let Some(e_pos) = body.find(|c| c == 'e' || c == 'E') {
let (mantissa, exp) = body.split_at(e_pos);
let m = if mantissa.contains('.') {
mantissa
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} else {
mantissa.to_string()
};
format!("{}{}", m, exp)
} else if body.contains('.') {
body.trim_end_matches('0').trim_end_matches('.').to_string()
} else {
body
};
stripped
} else {
body
}
}
_ => format!("{}", n),
};
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 if let Some(rest) = body.strip_prefix('+') {
format!("+{}{}", "0".repeat(pad), rest)
} else {
format!("{}{}", "0".repeat(pad), body)
}
} else {
format!("{}{}", " ".repeat(pad), body)
}
}
fn parse_width_prec(spec: &str) -> (bool, usize, Option<usize>) {
let (left_align, _zero_pad, width, prec) = parse_flags_width_prec(spec);
(left_align, width, prec)
}
fn parse_flags_width_prec(spec: &str) -> (bool, bool, usize, Option<usize>) {
let s = spec.trim_start_matches('%');
let mut i = 0;
let bytes = s.as_bytes();
let mut left_align = false;
let mut zero_pad = false;
while i < bytes.len() && matches!(bytes[i], b'-' | b'+' | b' ' | b'#' | b'0') {
match bytes[i] {
b'-' => left_align = true,
b'0' => zero_pad = 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, zero_pad, width, prec)
}
pub use crate::ported::exec::findcmd;
use crate::ported::signals_h::run_queued_signals;
fn getsigidx(name: &str) -> i32 {
crate::ported::jobs::getsigidx(name).unwrap_or(-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 traps_table() -> &'static Mutex<HashMap<String, String>> {
TRAPS_INNER.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn findcmd_absolute_path_skips_path_walk() {
let _g = crate::test_util::global_state_lock();
setsparam("PATH", "");
let resolved = findcmd("/bin/sh", 0, 0);
unsetparam("PATH");
assert_eq!(
resolved.as_deref(),
Some("/bin/sh"),
"c:914-919 — absolute path that exists must resolve to itself \
regardless of $PATH"
);
}
#[test]
fn findcmd_default_path_searches_hardcoded_dirs() {
let _g = crate::test_util::global_state_lock();
setsparam("PATH", "/nonexistent/zshrs-test-poison");
let resolved = findcmd("sh", 0, 1);
unsetparam("PATH");
assert!(
resolved.is_some(),
"c:903-908 — default_path must search DEFAULT_PATH regardless of $PATH"
);
let p = resolved.unwrap();
assert!(
DEFAULT_PATH.split(':').any(|d| p.starts_with(d)),
"resolved path must be under one of DEFAULT_PATH's dirs; got {:?}",
p
);
}
#[test]
fn bin_trap_clear_undefined_signal_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let empty = options {
ind: [0u8; 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() {
let _g = crate::test_util::global_state_lock();
let empty = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let saved = emulation.load(Relaxed);
for (name, expected) in [
("csh", EMULATE_CSH),
("ksh", EMULATE_KSH),
("sh", EMULATE_SH),
("rcsh", EMULATE_CSH), ("rksh", EMULATE_KSH), ("bash", EMULATE_SH), ] {
emulation.store(0, Relaxed);
bin_emulate("emulate", &[name.into()], &empty, 0);
let bits = emulation.load(Relaxed);
assert_eq!(
bits, expected,
"emulate {} must set bits {:#x}, got {:#x}",
name, expected, bits
);
}
emulation.store(saved, Relaxed);
}
#[test]
fn bin_trap_clear_valid_signal_returns_zero() {
let _g = crate::test_util::global_state_lock();
let empty = options {
ind: [0u8; 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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(BUILTINS.len(), 159,
"BUILTINS table size changed — bump count or update the eagerly-loaded-module list above");
}
#[test]
fn registration_table_contains_all_c_builtins() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert!(createbuiltintable()
.get("not-a-builtin-zZz")
.copied()
.is_none());
}
#[test]
fn prefix_entries_have_prefix_flag() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/.."), "/");
assert_eq!(fixdir("/../.."), "/");
assert_eq!(fixdir("/foo/../../bar"), "/bar");
}
#[test]
fn fixdir_relative_keeps_leading_dotdot() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("../foo"), "../foo");
assert_eq!(fixdir("../../foo"), "../../foo");
assert_eq!(fixdir("foo/../bar"), "bar");
}
#[test]
fn fixdir_empty_collapses_to_dot() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("./"), ".");
assert_eq!(fixdir("foo/.."), ".");
}
#[test]
fn fixdir_empty_input_returns_empty() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir(""), "");
}
#[test]
fn fg_dispatch_id_distinguishes_aliases() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/a/./b"), "/a/b");
assert_eq!(fixdir("./a"), "a");
assert_eq!(fixdir("./."), ".");
}
#[test]
fn fixdir_collapses_consecutive_slashes() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/.."), "/");
assert_eq!(fixdir("/../../a"), "/a");
}
#[test]
fn fixdir_relative_leading_dotdot_is_preserved() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("../foo"), "../foo");
assert_eq!(fixdir("../../foo"), "../../foo");
}
#[test]
fn fcgetcomm_numeric_zero_only_for_literal_zero_prefix() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
let r = cd_able_vars("HOME/anything");
if !isset(CDABLEVARS) {
assert!(r.is_none());
}
}
#[test]
fn init_builtins_is_idempotent() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("subdir"), "subdir");
assert_eq!(fixdir("a/b/c"), "a/b/c");
assert_eq!(fixdir("."), ".");
}
static BIN_LET_TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn bin_let_clears_errflag_on_math_error() {
let _g = crate::test_util::global_state_lock();
let _g = BIN_LET_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved = errflag.load(Relaxed);
errflag.store(0, Relaxed);
let ops = options {
ind: [0; 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, 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(Relaxed) & ERRFLAG_ERROR,
0,
"c:7478 — ERRFLAG_ERROR must be CLEARED after let error"
);
errflag.store(saved, Relaxed);
}
#[test]
fn bin_let_walks_all_argv_last_wins() {
let _g = crate::test_util::global_state_lock();
let _g = BIN_LET_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
errflag.store(0, Relaxed);
let ops = options {
ind: [0; 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, Relaxed);
}
#[test]
fn bin_print_sort_matches_c_case_gate() {
let _g = crate::test_util::global_state_lock();
let sort_with = |items: &[&str], ignore_case: bool, backwards: bool| -> Vec<String> {
let mut v: Vec<String> = items.iter().map(|s| s.to_string()).collect();
if ignore_case {
v.sort_by_key(|s| s.to_lowercase());
} else {
v.sort();
}
if backwards {
v.reverse();
}
v
};
let no_i = sort_with(&["foo", "Bar", "BAZ"], false, false);
assert_eq!(
no_i,
vec!["BAZ", "Bar", "foo"],
"c:4805 — without -i: case-sensitive sort (caps first by ASCII)"
);
let with_i = sort_with(&["foo", "Bar", "BAZ"], true, false);
assert_eq!(
with_i,
vec!["Bar", "BAZ", "foo"],
"c:4805 — with -i: case-insensitive sort"
);
let big_o = sort_with(&["foo", "Bar", "BAZ"], false, true);
assert_eq!(
big_o,
vec!["foo", "Bar", "BAZ"],
"c:4806 — -O reverses after sort"
);
}
#[test]
fn bin_print_printf_with_minus_z() {
let _g = crate::test_util::global_state_lock();
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b'z' as usize] = 1;
ops.ind[b'f' as usize] = 1 | (1 << 2);
ops.args = vec!["echo %s".to_string()];
ops.argscount = 1;
crate::ported::zle::zle_main::BUFSTACK
.lock()
.unwrap()
.clear();
let r = bin_print("printf", &["hello".to_string()], &ops, BIN_PRINTF);
assert_eq!(r, 0);
let buf = crate::ported::zle::zle_main::BUFSTACK.lock().unwrap();
assert_eq!(
buf.last().map(|s| s.as_str()),
Some("echo hello"),
"c:4854-4856 — printf -z must push formatted output to bufstack"
);
}
#[test]
fn bin_print_minus_z_pushes_to_bufstack() {
let _g = crate::test_util::global_state_lock();
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b'z' as usize] = 1;
crate::ported::zle::zle_main::BUFSTACK
.lock()
.unwrap()
.clear();
let r = bin_print(
"print",
&["echo".to_string(), "foo".to_string()],
&ops,
BIN_PRINT,
);
assert_eq!(r, 0, "c:5565 — -z should succeed");
let buf = crate::ported::zle::zle_main::BUFSTACK.lock().unwrap();
assert_eq!(
buf.last().map(|s| s.as_str()),
Some("echo foo"),
"c:5565 — bufstack must have `echo foo` as the top entry"
);
}
#[test]
fn bin_print_minus_s_pushes_to_history() {
let _g = crate::test_util::global_state_lock();
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b's' as usize] = 1;
crate::ported::hashtable::histtab_lock()
.write()
.unwrap()
.clear();
let r = bin_print(
"print",
&["hello".to_string(), "world".to_string()],
&ops,
BIN_PRINT,
);
assert_eq!(r, 0, "c:5574 — -s should succeed");
let tab = crate::ported::hashtable::histtab_lock().read().unwrap();
assert!(
tab.contains_key("hello world"),
"c:5574 — addhistnode must record `hello world` in histtab"
);
}
#[test]
fn bin_print_minus_m_glob_filter() {
let _g = crate::test_util::global_state_lock();
use std::io::Read as _;
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
let (rfd, wfd) = (fds[0], fds[1]);
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b'u' as usize] = 1 | (1 << 2);
ops.args = vec![wfd.to_string()];
ops.argscount = 1;
ops.ind[b'm' as usize] = 1;
let r = bin_print(
"print",
&[
"foo*".to_string(),
"foo1".to_string(),
"bar".to_string(),
"foo2".to_string(),
],
&ops,
BIN_PRINT,
);
assert_eq!(r, 0);
unsafe { libc::close(wfd) };
let mut buf = String::new();
unsafe {
use std::os::unix::io::FromRawFd;
let mut f = fs::File::from_raw_fd(rfd);
f.read_to_string(&mut buf).unwrap();
}
assert_eq!(
buf, "foo1 foo2\n",
"c:4718-4741 — -m filters to only `foo*`-matching args"
);
}
#[test]
fn bin_print_nul_separator_with_minus_N() {
let _g = crate::test_util::global_state_lock();
use std::io::Read as _;
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
let (rfd, wfd) = (fds[0], fds[1]);
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b'u' as usize] = 1 | (1 << 2);
ops.args = vec![wfd.to_string()];
ops.argscount = 1;
ops.ind[b'N' as usize] = 1;
let r = bin_print(
"print",
&["a".to_string(), "b".to_string(), "c".to_string()],
&ops,
BIN_PRINT,
);
assert_eq!(r, 0);
unsafe { libc::close(wfd) };
let mut buf = Vec::new();
unsafe {
use std::os::unix::io::FromRawFd;
let mut f = fs::File::from_raw_fd(rfd);
f.read_to_end(&mut buf).unwrap();
}
assert_eq!(
buf, b"a\0b\0c\0",
"c:5126-5132 — -N: NUL separators + NUL terminator"
);
}
#[test]
fn bin_print_writes_to_specified_fd() {
let _g = crate::test_util::global_state_lock();
use std::io::Read as _;
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
let (rfd, wfd) = (fds[0], fds[1]);
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
ops.ind[b'u' as usize] = 1;
ops.args = vec![wfd.to_string()];
ops.argscount = 1;
ops.ind[b'u' as usize] = 1 | (1 << 2);
let r = bin_print("print", &["hello".to_string()], &ops, BIN_PRINT);
assert_eq!(r, 0, "c:4847 — bin_print should return 0 on success");
unsafe { libc::close(wfd) };
let mut buf = String::new();
unsafe {
use std::os::unix::io::FromRawFd;
let mut f = fs::File::from_raw_fd(rfd);
f.read_to_string(&mut buf).unwrap();
}
assert_eq!(buf, "hello\n", "c:4847 — write should land on -u FD");
}
#[test]
fn fixdir_empty_returns_empty() {
assert_eq!(fixdir(""), "");
}
#[test]
fn fixdir_root_passes_through() {
assert_eq!(fixdir("/"), "/");
}
#[test]
fn fixdir_root_dot_collapses_to_root() {
assert_eq!(fixdir("/."), "/");
}
#[test]
fn fixdir_strips_dot_components() {
assert_eq!(fixdir("/a/./b"), "/a/b");
}
#[test]
fn fixdir_dot_dot_pops_previous_component() {
assert_eq!(fixdir("/a/b/.."), "/a");
}
#[test]
fn fixdir_dot_dot_then_continue() {
assert_eq!(fixdir("/a/b/../c"), "/a/c");
}
#[test]
fn fixdir_dot_dot_past_root_drops() {
assert_eq!(fixdir("/.."), "/");
}
#[test]
fn fixdir_multiple_dot_dot_past_root_drops() {
assert_eq!(fixdir("/../.."), "/");
}
#[test]
fn fixdir_collapses_double_slash() {
assert_eq!(fixdir("//a"), "/a");
}
#[test]
fn fixdir_collapses_repeated_slashes() {
assert_eq!(fixdir("/a//b///c"), "/a/b/c");
}
#[test]
fn fixdir_relative_no_dots_unchanged() {
assert_eq!(fixdir("a/b/c"), "a/b/c");
}
#[test]
fn fixdir_relative_drops_dot() {
assert_eq!(fixdir("a/./b"), "a/b");
}
#[test]
fn fixdir_relative_dot_dot_pops() {
assert_eq!(fixdir("a/b/.."), "a");
}
#[test]
fn fixdir_leading_dot_dot_preserved_in_relative() {
assert_eq!(fixdir(".."), "..");
}
#[test]
fn fixdir_double_leading_dot_dot_both_preserved() {
assert_eq!(fixdir("../.."), "../..");
}
#[test]
fn fixdir_dot_dot_then_dir_then_dot_dot() {
assert_eq!(fixdir("../foo/.."), "..");
}
#[test]
fn fixdir_single_dot_returns_dot() {
assert_eq!(fixdir("."), ".");
}
#[test]
fn fixdir_trailing_slash_dropped() {
assert_eq!(fixdir("/a/b/"), "/a/b");
assert_eq!(fixdir("a/b/"), "a/b");
}
use crate::ported::options::{opt_state_get, opt_state_set};
#[test]
fn cd_able_vars_returns_none_when_option_off() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("cdablevars").unwrap_or(false);
opt_state_set("cdablevars", false);
assert_eq!(cd_able_vars("HOME"), None);
opt_state_set("cdablevars", saved);
}
#[test]
fn cd_able_vars_returns_value_when_option_on_and_var_set() {
let _g = crate::test_util::global_state_lock();
let saved_opt = opt_state_get("cdablevars").unwrap_or(false);
opt_state_set("cdablevars", true);
opt_state_set("exec", true);
unsetparam("zshrs_cdav_proj");
setsparam("zshrs_cdav_proj", "/tmp/myproject");
assert_eq!(
cd_able_vars("zshrs_cdav_proj"),
Some("/tmp/myproject".to_string())
);
unsetparam("zshrs_cdav_proj");
opt_state_set("cdablevars", saved_opt);
}
#[test]
fn cd_able_vars_appends_tail_after_head_substitution() {
let _g = crate::test_util::global_state_lock();
let saved_opt = opt_state_get("cdablevars").unwrap_or(false);
opt_state_set("cdablevars", true);
opt_state_set("exec", true);
unsetparam("zshrs_cdav_PROJ");
setsparam("zshrs_cdav_PROJ", "/home/user");
assert_eq!(
cd_able_vars("zshrs_cdav_PROJ/src"),
Some("/home/user/src".to_string())
);
unsetparam("zshrs_cdav_PROJ");
opt_state_set("cdablevars", saved_opt);
}
#[test]
fn cd_able_vars_unknown_var_returns_none() {
let _g = crate::test_util::global_state_lock();
let saved_opt = opt_state_get("cdablevars").unwrap_or(false);
opt_state_set("cdablevars", true);
unsetparam("zshrs_cdav_doesnt_exist");
assert_eq!(cd_able_vars("zshrs_cdav_doesnt_exist"), None);
opt_state_set("cdablevars", saved_opt);
}
#[test]
fn cd_able_vars_empty_head_returns_none() {
let _g = crate::test_util::global_state_lock();
let saved_opt = opt_state_get("cdablevars").unwrap_or(false);
opt_state_set("cdablevars", true);
assert_eq!(cd_able_vars("/path/to/foo"), None);
opt_state_set("cdablevars", saved_opt);
}
fn empty_opts_for_corpus() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
#[test]
fn builtin_corpus_bin_true_always_zero() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
assert_eq!(bin_true("true", &[], &o, 0), 0, "bin_true no args = 0");
assert_eq!(
bin_true("true", &["x".into(), "y".into()], &o, 0),
0,
"bin_true with args = 0",
);
}
#[test]
fn builtin_corpus_bin_false_always_one() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
assert_eq!(bin_false("false", &[], &o, 0), 1, "bin_false no args = 1");
assert_eq!(
bin_false("false", &["any".into()], &o, 0),
1,
"bin_false with args = 1",
);
}
#[test]
fn builtin_corpus_bin_shift_empty_positional_returns_zero_or_one() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let r = bin_shift("shift", &[], &o, 0);
assert!(r == 0 || r == 1, "shift on empty positional, got {r}");
}
#[test]
fn builtin_corpus_bin_let_no_args_returns_one() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let r = bin_let("let", &[], &o, 0);
assert_eq!(r, 1, "let with no args = 1");
}
#[test]
fn builtin_corpus_bin_let_nonzero_expr_returns_zero() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
unsetparam("ZL_X");
let r = bin_let("let", &["ZL_X=5".into()], &o, 0);
assert_eq!(r, 0, "let 'x=5' assigns and returns 0 (nonzero result)");
assert_eq!(getiparam("ZL_X"), 5);
unsetparam("ZL_X");
}
#[test]
fn builtin_corpus_bin_let_zero_expr_returns_one() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
unsetparam("ZL_Y");
let r = bin_let("let", &["ZL_Y=0".into()], &o, 0);
assert_eq!(r, 1, "let 'x=0' returns 1 (zero result)");
assert_eq!(getiparam("ZL_Y"), 0);
unsetparam("ZL_Y");
}
#[test]
fn builtin_corpus_bin_let_multi_expr_last_wins() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
unsetparam("ZL_A");
unsetparam("ZL_B");
let r = bin_let("let", &["ZL_A=1".into(), "ZL_B=7".into()], &o, 0);
assert_eq!(r, 0, "last expr non-zero → 0");
assert_eq!(getiparam("ZL_A"), 1);
assert_eq!(getiparam("ZL_B"), 7);
unsetparam("ZL_A");
unsetparam("ZL_B");
}
#[test]
fn builtin_corpus_bin_pwd_returns_zero() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let r = bin_pwd("pwd", &[], &o, 0);
assert_eq!(r, 0, "pwd returns 0 on success");
}
#[test]
fn fixdir_empty_returns_empty_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir(""), "");
}
#[test]
fn fixdir_root_returns_root() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/"), "/", "root path preserved");
}
#[test]
fn fixdir_drops_dot_segments() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/foo/./bar"), "/foo/bar");
assert_eq!(fixdir("/./a"), "/a");
assert_eq!(fixdir("/a/."), "/a");
}
#[test]
fn fixdir_collapses_double_slash_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/foo//bar"), "/foo/bar");
assert_eq!(fixdir("//foo//bar//"), "/foo/bar");
}
#[test]
fn fixdir_pops_via_dotdot() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/foo/bar/.."), "/foo");
assert_eq!(fixdir("/a/b/c/../.."), "/a");
assert_eq!(fixdir("/a/b/../c"), "/a/c");
}
#[test]
fn fixdir_dotdot_past_root_clamps_to_root_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("/.."), "/", "absolute `..` past root → `/`");
assert_eq!(fixdir("/../.."), "/");
assert_eq!(fixdir("/a/../../.."), "/", "successive pops past root → /");
}
#[test]
fn fixdir_relative_keeps_leading_dotdot_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("../foo"), "../foo");
assert_eq!(fixdir("../../foo"), "../../foo");
}
#[test]
fn fixdir_is_idempotent() {
let _g = crate::test_util::global_state_lock();
for input in &[
"/a/b/c",
"/a/./b",
"/a/b/..",
"/../x",
"../foo",
"/foo//bar",
] {
let once = fixdir(input);
let twice = fixdir(&once);
assert_eq!(once, twice, "fixdir must be idempotent on {:?}", input);
}
}
#[test]
fn fixdir_relative_dot_returns_dot() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("."), ".", "lone `.` relative → `.`");
}
#[test]
fn fixdir_plain_relative_is_identity() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fixdir("foo"), "foo");
assert_eq!(fixdir("foo/bar"), "foo/bar");
}
#[test]
fn bin_true_always_returns_zero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_opts_for_corpus();
assert_eq!(bin_true("true", &[], &ops, 0), 0);
assert_eq!(
bin_true("true", &["arg1".into(), "arg2".into()], &ops, 0),
0
);
}
#[test]
fn bin_false_always_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_opts_for_corpus();
assert_eq!(bin_false("false", &[], &ops, 0), 1);
assert_eq!(bin_false("false", &["a".into()], &ops, 0), 1);
}
#[test]
fn bin_true_ignores_name() {
let _g = crate::test_util::global_state_lock();
let ops = empty_opts_for_corpus();
assert_eq!(bin_true("anything", &[], &ops, 0), 0);
assert_eq!(bin_true("", &[], &ops, 0), 0);
}
#[test]
fn bin_false_ignores_name() {
let _g = crate::test_util::global_state_lock();
let ops = empty_opts_for_corpus();
assert_eq!(bin_false("anything", &[], &ops, 0), 1);
assert_eq!(bin_false("", &[], &ops, 0), 1);
}
#[test]
fn bin_true_false_deterministic() {
let _g = crate::test_util::global_state_lock();
let ops = empty_opts_for_corpus();
for _ in 0..10 {
assert_eq!(bin_true("true", &[], &ops, 0), 0);
assert_eq!(bin_false("false", &[], &ops, 0), 1);
}
}
#[test]
fn realexit_contract_pin_no_test_actually_calls_it() {
}
#[test]
fn createbuiltintable_returns_hashmap_ref_type() {
let _: &HashMap<String, &builtin> = createbuiltintable();
}
#[test]
fn createbuiltintable_returns_same_ref() {
let a = createbuiltintable() as *const _;
let b = createbuiltintable() as *const _;
assert_eq!(a, b, "createbuiltintable must return same singleton");
}
#[test]
fn createbuiltintable_contains_canonical_builtins() {
let t = createbuiltintable();
for name in ["true", "false", "set", "cd", "exit", "echo"] {
assert!(
t.contains_key(name),
"builtin table must contain {:?}",
name
);
}
}
#[test]
fn init_builtins_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
init_builtins();
}
}
#[test]
fn cd_able_vars_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(cd_able_vars("").is_none(), "empty → None");
}
#[test]
fn cd_able_vars_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<String> = cd_able_vars("anything");
}
#[test]
fn fixdir_empty_returns_string_type() {
let _: String = fixdir("");
}
#[test]
fn fixdir_is_pure() {
for s in ["", "/abs", "rel", "./dot", "../parent", "a/b/c"] {
let first = fixdir(s);
for _ in 0..3 {
assert_eq!(fixdir(s), first, "fixdir({:?}) must be pure", s);
}
}
}
#[test]
fn fcgetcomm_empty_returns_i64_type() {
let _g = crate::test_util::global_state_lock();
let _: i64 = fcgetcomm("");
}
#[test]
fn fcgetcomm_invalid_is_deterministic() {
let _g = crate::test_util::global_state_lock();
for s in ["", "garbage", "not_a_number"] {
let first = fcgetcomm(s);
for _ in 0..3 {
assert_eq!(
fcgetcomm(s),
first,
"fcgetcomm({:?}) must be deterministic",
s
);
}
}
}
#[test]
fn fcsubs_empty_subs_returns_zero() {
let mut s = "hello".to_string();
let r = fcsubs(&mut s, &[]);
assert_eq!(r, 0, "empty subs → 0 (no substitutions)");
assert_eq!(s, "hello", "string unchanged");
}
#[test]
fn bin_pwd_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_pwd("pwd", &[], &o, 0);
}
#[test]
fn bin_true_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_true("true", &[], &o, 0);
}
#[test]
fn bin_false_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_false("false", &[], &o, 0);
}
#[test]
fn bin_let_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_let("let", &[], &o, 0);
}
#[test]
fn bin_shift_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_shift("shift", &[], &o, 0);
}
#[test]
fn bin_true_false_are_pure() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
for _ in 0..5 {
assert_eq!(bin_true("true", &[], &o, 0), 0);
assert_eq!(bin_false("false", &[], &o, 0), 1);
}
}
#[test]
fn bin_pwd_return_in_u8_range() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let r = bin_pwd("pwd", &[], &o, 0);
assert!(
(0..256).contains(&r),
"bin_pwd exit code must fit u8, got {}",
r
);
}
#[test]
fn bin_shift_no_args_in_zero_one_range() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let r = bin_shift("shift", &[], &o, 0);
assert!(r == 0 || r == 1, "shift no args ∈ {{0,1}}, got {}", r);
}
#[test]
fn bin_let_no_args_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let first = bin_let("let", &[], &o, 0);
for _ in 0..3 {
assert_eq!(
bin_let("let", &[], &o, 0),
first,
"bin_let no-args must be deterministic"
);
}
}
#[test]
fn bin_eval_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
let _: i32 = bin_eval("eval", &[], &o, 0);
}
#[test]
fn bin_true_full_argument_immunity() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
assert_eq!(bin_true("true", &[], &o, 0), 0);
assert_eq!(bin_true("anything", &[], &o, 0), 0);
assert_eq!(bin_true("true", &["unused".into()], &o, 99), 0);
assert_eq!(
bin_true("", &["a".into(), "b".into(), "c".into()], &o, -1),
0
);
}
#[test]
fn bin_false_full_argument_immunity() {
let _g = crate::test_util::global_state_lock();
let o = empty_opts_for_corpus();
assert_eq!(bin_false("false", &[], &o, 0), 1);
assert_eq!(bin_false("anything", &[], &o, 0), 1);
assert_eq!(bin_false("false", &["unused".into()], &o, 99), 1);
assert_eq!(
bin_false("", &["a".into(), "b".into(), "c".into()], &o, -1),
1
);
}
}