use crate::ported::builtin::createbuiltintable;
use crate::ported::hist::casemodify;
use crate::ported::mem::ztrdup;
use crate::ported::params::{createparam, createspecialhash, paramtab, unsetparam_pm};
use crate::ported::signals::unqueue_signals;
use crate::ported::utils::{zwarn, zwarnnam};
use crate::ported::zsh_h::{
builtin, conddef, funcwrap, hookdef, linklist, linknode, mathfunc, options, paramdef, Hookfn,
Param, BINF_AUTOALL, CASMOD_LOWER, CASMOD_UPPER, CONDF_AUTOALL, HOOKF_ALL, MFF_USERFUNC,
linkedmod, MOD_ALIAS, MOD_BUSY, MOD_INIT_B, MOD_INIT_S, MOD_LINKED, MOD_SETUP, MOD_UNLOAD,
OPT_ARG_SAFE, OPT_ISSET,
PM_ARRAY, PM_AUTOALL, PM_AUTOLOAD, PM_EFLOAT, PM_FFLOAT, PM_HASHED, PM_INTEGER, PM_NAMEREF,
PM_READONLY, PM_REMOVABLE, PM_SCALAR, PM_TIED, PM_TYPE, PRINT_LIST,
};
pub use crate::ported::zsh_h::{BINF_ADDED, CONDF_ADDED, CONDF_INFIX, MFF_ADDED};
use crate::ported::zsh_h::hashnode;
use crate::zsh_h::module;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;
pub fn freemodulenode(hn: module) {
}
pub const PRINTMOD_LIST: i32 = 0x0001; pub const PRINTMOD_EXIST: i32 = 0x0002; pub const PRINTMOD_ALIAS: i32 = 0x0004; pub const PRINTMOD_DEPS: i32 = 0x0008; pub const PRINTMOD_FEATURES: i32 = 0x0010; pub const PRINTMOD_LISTALL: i32 = 0x0020; pub const PRINTMOD_AUTO: i32 = 0x0040;
pub fn printmodulenode(hn: &str, m: &module, flags: i32) -> String {
let modname = hn;
let mut out = String::new();
if flags & PRINTMOD_DEPS != 0 {
use crate::ported::utils::{nicezputs, quotedzputs};
let deps = match m.deps.as_ref() {
Some(d) if !d.is_empty() => d,
_ => return out, };
if flags & PRINTMOD_LIST != 0 {
out.push_str("zmodload -d "); if modname.starts_with('-') {
out.push_str("-- "); }
out.push_str("edzputs(modname)); } else {
let mut buf: Vec<u8> = Vec::new();
let _ = nicezputs(modname, &mut buf); if let Ok(s) = std::str::from_utf8(&buf) {
out.push_str(s);
}
out.push(':'); }
for dep in deps.iter() {
out.push(' '); if flags & PRINTMOD_LIST != 0 {
out.push_str("edzputs(dep)); } else {
let mut buf: Vec<u8> = Vec::new();
let _ = nicezputs(dep, &mut buf); if let Ok(s) = std::str::from_utf8(&buf) {
out.push_str(s);
}
}
}
return out;
}
if flags & PRINTMOD_EXIST != 0 {
if (m.node.flags & MOD_ALIAS) != 0 {
if (flags & PRINTMOD_ALIAS) == 0 || m.alias.is_none() {
return out;
}
}
let booted = (m.node.flags & MOD_INIT_B) != 0;
let unloading = (m.node.flags & MOD_UNLOAD) != 0;
if !booted || unloading {
return out;
}
out.push_str(modname); return out;
}
if m.node.flags & MOD_ALIAS != 0 {
use crate::ported::utils::{nicezputs, quotedzputs};
let alias = m.alias.as_deref().unwrap_or("");
if flags & PRINTMOD_LIST != 0 {
out.push_str("zmodload -A "); if modname.starts_with('-') {
out.push_str("-- "); }
out.push_str("edzputs(modname)); out.push('='); out.push_str("edzputs(alias)); } else {
let mut buf: Vec<u8> = Vec::new();
let _ = nicezputs(modname, &mut buf); if let Ok(s) = std::str::from_utf8(&buf) {
out.push_str(s);
}
out.push_str(" -> "); let mut buf2: Vec<u8> = Vec::new();
let _ = nicezputs(alias, &mut buf2); if let Ok(s) = std::str::from_utf8(&buf2) {
out.push_str(s);
}
}
return out;
}
let loaded = (m.node.flags & MOD_INIT_B) != 0 && (m.node.flags & MOD_UNLOAD) == 0;
let _ = MOD_LINKED; let auto = flags & PRINTMOD_AUTO != 0;
if loaded || auto {
use crate::ported::utils::{nicezputs, quotedzputs};
if flags & PRINTMOD_LIST != 0 {
if auto {
let has_autoloads = m
.autoloads
.as_ref()
.map(|al| !al.is_empty())
.unwrap_or(false);
if !has_autoloads {
return out; }
}
out.push_str("zmodload "); if auto {
out.push_str("-Fa "); } else if flags & PRINTMOD_FEATURES != 0 {
out.push_str("-F "); }
if modname.starts_with('-') {
out.push_str("-- "); }
out.push_str("edzputs(modname)); if auto {
if let Some(al_list) = m.autoloads.as_ref() {
for al in al_list.iter() {
out.push(' '); out.push_str("edzputs(al)); }
}
}
} else {
let mut buf: Vec<u8> = Vec::new();
let _ = nicezputs(modname, &mut buf);
if let Ok(s) = std::str::from_utf8(&buf) {
out.push_str(s);
}
}
}
out }
pub fn newmoduletable() -> modulestab {
modulestab::new()
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
1 }
#[allow(unused_variables)]
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
1 }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn cleanup_(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0 }
pub fn register_module(table: &mut modulestab, name: &str) -> bool {
if table.modules.contains_key(name) {
return false;
}
table.modules.insert(name.to_string(), module::new(name));
true
}
pub fn addbuiltin(b: &mut builtin) -> i32 {
let tab = createbuiltintable();
if let Some(existing) = tab.get(&b.node.nam) {
if (existing.node.flags & BINF_ADDED as i32) != 0 {
return 1;
} }
b.node.flags |= BINF_ADDED as i32; 0
}
pub fn deletebuiltin(nam: &str) -> i32 {
let tab = createbuiltintable();
match tab.get(nam) {
None => -1, Some(_) => 0, }
}
pub fn addbuiltins(nam: &str, binl: &mut [builtin]) -> i32 {
let mut ret = 0; for b in binl.iter_mut() {
if (b.node.flags & BINF_ADDED as i32) != 0 {
continue;
} if addbuiltin(b) != 0 {
zwarnnam(
nam, &format!("name clash when adding builtin `{}'", b.node.nam),
);
ret = 1;
}
}
ret }
pub fn gethookdef(n: &str) -> *mut hookdef {
let mut p = hooktab.load(std::sync::atomic::Ordering::SeqCst); while !p.is_null() {
unsafe {
if (*p).name == n {
return p; }
p = (*p).next; }
}
std::ptr::null_mut() }
pub fn addhookdef(h: *mut hookdef) -> i32 {
unsafe {
if !gethookdef(&(*h).name).is_null() {
return 1; }
(*h).next = hooktab.load(std::sync::atomic::Ordering::SeqCst); hooktab.store(h, std::sync::atomic::Ordering::SeqCst); if (*h).funcs.is_null() {
(*h).funcs = Box::into_raw(Box::new(linklist {
first: None,
last: None,
flags: 0,
}));
}
}
0 }
pub fn addhookdefs(
m: *const module,
mut h: *mut hookdef,
mut size: i32,
) -> i32 {
let mut ret: i32 = 0; while size > 0 {
if addhookdef(h) != 0 {
let nam: String = if m.is_null() {
String::new()
} else {
unsafe { (*m).node.nam.clone() }
};
let hook_name = unsafe { (*h).name.clone() };
zwarnnam(
&nam,
&format!("name clash when adding hook `{}'", hook_name),
);
ret = 1; }
unsafe {
h = h.add(1);
} size -= 1; }
ret }
pub fn deletehookdef(h: *mut hookdef) -> i32 {
let mut p = hooktab.load(std::sync::atomic::Ordering::SeqCst); let mut q: *mut hookdef = std::ptr::null_mut(); while !p.is_null() && p != h {
q = p; unsafe {
p = (*p).next;
} }
if p.is_null() {
return 1; }
unsafe {
if !q.is_null() {
(*q).next = (*p).next; } else {
hooktab.store((*p).next, std::sync::atomic::Ordering::SeqCst); }
if !(*p).funcs.is_null() {
drop(Box::from_raw((*p).funcs));
(*p).funcs = std::ptr::null_mut();
}
}
0 }
pub fn deletehookdefs(
_m: *const module,
mut h: *mut hookdef,
mut size: i32,
) -> i32 {
let mut ret: i32 = 0; while size > 0 {
if deletehookdef(h) != 0 {
ret = 1; }
unsafe {
h = h.add(1);
} size -= 1; }
ret }
pub fn addhookdeffunc(
h: *mut hookdef,
f: Hookfn,
) -> i32 {
unsafe {
if (*h).funcs.is_null() {
(*h).funcs = Box::into_raw(Box::new(linklist {
first: None,
last: None,
flags: 0,
}));
}
let funcs = &mut *(*h).funcs;
let new_node = Box::new(linknode {
next: None,
prev: None,
dat: f as usize,
});
if funcs.first.is_none() {
funcs.first = Some(new_node);
} else {
let mut tail = funcs.first.as_mut().unwrap();
while tail.next.is_some() {
tail = tail.next.as_mut().unwrap();
}
tail.next = Some(new_node);
}
}
0 }
pub fn addhookfunc(
n: &str,
f: Hookfn,
) -> i32 {
let h = gethookdef(n); if !h.is_null() {
return addhookdeffunc(h, f); }
1 }
pub fn deletehookdeffunc(
h: *mut hookdef,
f: Hookfn,
) -> i32 {
unsafe {
if (*h).funcs.is_null() {
return 1;
}
let funcs = &mut *(*h).funcs;
let f_val = f as usize;
let mut prev: &mut Option<Box<linknode>> = &mut funcs.first;
loop {
match prev {
None => return 1, Some(node) if node.dat == f_val => {
let next = node.next.take(); *prev = next;
return 0; }
Some(_) => {
prev = &mut prev.as_mut().unwrap().next;
}
}
}
}
}
pub fn deletehookfunc(
n: &str,
f: Hookfn,
) -> i32 {
let h = gethookdef(n); if !h.is_null() {
return deletehookdeffunc(h, f); }
1 }
pub fn runhookdef(
h: *mut hookdef,
d: *mut std::ffi::c_void,
) -> i32 {
unsafe {
let funcs_ptr = (*h).funcs;
let funcs_empty = funcs_ptr.is_null() || (*funcs_ptr).first.is_none(); if funcs_empty {
if let Some(def) = (*h).def {
return def(h, d); }
return 0; }
if (*h).flags & HOOKF_ALL != 0 {
let mut node = (*funcs_ptr).first.as_ref(); while let Some(n) = node {
let fn_ptr: Hookfn = std::mem::transmute(n.dat); let r = fn_ptr(h, d); if r != 0 {
return r; }
node = n.next.as_ref(); }
if let Some(def) = (*h).def {
return def(h, d); }
return 0; }
let mut tail = (*funcs_ptr).first.as_ref().expect("non-empty");
while let Some(next) = tail.next.as_ref() {
tail = next;
}
let fn_ptr: Hookfn = std::mem::transmute(tail.dat);
fn_ptr(h, d) }
}
pub fn checkaddparam(nam: &str, opt_i: i32) -> i32 {
let pm_clone = {
let tab = paramtab().read().expect("paramtab poisoned");
tab.get(nam).cloned()
};
let mut pm = match pm_clone {
Some(p) => p,
None => return 0,
};
if pm.level != 0 || (pm.node.flags as u32 & PM_AUTOLOAD) == 0 {
if opt_i == 0 || pm.level != 0 {
zwarn(&format!(
"Can't add module parameter `{}': {}",
nam,
if pm.level != 0 {
"local parameter exists"
} else {
"parameter already exists"
}
));
return 1;
}
return 2;
}
unsetparam_pm(&mut pm, 0, 1);
0
}
pub fn addparamdef(d: &mut paramdef) -> i32 {
if checkaddparam(&d.name, 0) != 0 {
return 1; }
let pm_opt: Option<Param> = if d.getnfn.is_some() {
createspecialhash(&d.name, d.flags) } else {
match createparam(&d.name, d.flags) {
Some(p) => Some(p),
None => {
let tab = paramtab().read().ok();
tab.and_then(|t| {
t.get(&d.name).map(|p| {
let mut clone = p.clone();
clone.level = 0;
Box::new(*clone)
})
})
}
}
};
let mut pm = match pm_opt {
Some(p) => p,
None => return 1,
};
pm.level = 0;
if d.var != 0 { }
if d.var != 0 || d.gsu != 0 {
let t = PM_TYPE(pm.node.flags as u32); let pmflags = pm.node.flags as u32;
if t == PM_SCALAR || t == PM_NAMEREF {
if t == PM_SCALAR && (pmflags & PM_TIED) != 0 {
let lower = casemodify(&pm.node.nam, CASMOD_LOWER);
pm.ename = Some(ztrdup(&lower)); }
let _ = d.gsu; } else if t == PM_INTEGER {
let _ = d.gsu; } else if t == PM_FFLOAT || t == PM_EFLOAT {
let _ = d.gsu; } else if t == PM_ARRAY {
if (pmflags & PM_TIED) != 0 {
let upper = casemodify(&pm.node.nam, CASMOD_UPPER);
pm.ename = Some(ztrdup(&upper)); }
let _ = d.gsu; } else if t == PM_HASHED {
let _ = d.gsu; } else {
unsetparam_pm(&mut pm, 0, 1); return 1; }
}
d.pm = Some(pm); 0 }
pub fn deleteparamdef(d: &mut paramdef) -> i32 {
let mut pm: Param = {
let tab = paramtab().read();
match tab {
Ok(t) => match t.get(&d.name) {
Some(p) => p.clone(),
None => return 1, },
Err(_) => return 1,
}
};
if let Some(expected) = d.pm.as_ref() {
if !std::ptr::eq(pm.as_ref(), expected.as_ref()) {
let mut searchpm = pm.old.clone(); let mut found = false;
while let Some(s) = searchpm {
if std::ptr::eq(s.as_ref(), expected.as_ref()) {
found = true; break;
}
searchpm = s.old.clone(); }
if !found {
return 1; }
}
}
pm.node.flags = (pm.node.flags & !(PM_READONLY as i32)) | (PM_REMOVABLE as i32); unsetparam_pm(&mut pm, 0, 1); d.pm = None; 0 }
pub fn add_autoparam(module: &str, pnam: &str, flags: i32) -> i32 {
use crate::ported::exec::noerrs;
use crate::ported::signals::queue_signals;
use std::sync::atomic::Ordering;
let ne = noerrs.load(Ordering::Relaxed);
queue_signals();
let ret_check = checkaddparam(pnam, flags & FEAT_IGNORE as i32);
if ret_check != 0 {
unqueue_signals();
return if ret_check == 2 { 0 } else { -1 };
}
noerrs.store(2, Ordering::Relaxed);
let pm_opt = crate::ported::params::setsparam(pnam, module);
let ret = if let Some(mut pm) = pm_opt {
pm.node.flags |= PM_AUTOLOAD as i32;
if (flags & FEAT_AUTOALL as i32) != 0 {
pm.node.flags |= PM_AUTOALL as i32;
}
{
let mut tab = paramtab().write().expect("paramtab poisoned");
tab.insert(pnam.to_string(), pm);
}
0
} else {
-1
};
noerrs.store(ne, Ordering::Relaxed);
unqueue_signals();
ret
}
pub fn del_autoparam(_modnam: &str, pnam: &str, flags: i32) -> i32 {
let pm_opt = {
let tab = paramtab().read().expect("paramtab poisoned");
tab.get(pnam).cloned()
};
match pm_opt {
None => {
if (flags & FEAT_IGNORE as i32) == 0 {
return 2; }
}
Some(mut pm) => {
if (pm.node.flags as u32 & PM_AUTOLOAD) == 0 {
if (flags & FEAT_IGNORE as i32) == 0 {
return 3; }
} else {
unsetparam_pm(&mut pm, 0, 1);
}
}
}
0 }
impl modulestab {
pub fn new() -> Self {
let mut table = Self::default();
table.register_builtin_modules();
table
}
fn register_builtin_modules(&mut self) {
let builtin_modules = [
(
"zsh/complete",
&[
"compctl",
"compcall",
"comparguments",
"compdescribe",
"compfiles",
"compgroups",
"compquote",
"comptags",
"comptry",
"compvalues",
][..],
),
("zsh/complist", &["complist"][..]),
("zsh/computil", &["compadd", "compset"][..]),
("zsh/datetime", &["output_strftime"][..]),
(
"zsh/files",
&[
"mkdir", "rmdir", "ln", "mv", "cp", "rm", "chmod", "chown", "sync",
][..],
),
("zsh/langinfo", &[][..]),
("zsh/mapfile", &[][..]),
("zsh/mathfunc", &[][..]),
("zsh/nearcolor", &[][..]),
("zsh/net/socket", &["zsocket"][..]),
("zsh/net/tcp", &["ztcp"][..]),
("zsh/parameter", &[][..]),
(
"zsh/pcre",
&["pcre_compile", "pcre_match", "pcre_study"][..],
),
("zsh/regex", &[][..]),
("zsh/sched", &["sched"][..]),
("zsh/stat", &["zstat"][..]),
(
"zsh/system",
&[
"bin_sysread",
"bin_syswrite",
"bin_sysopen",
"bin_sysseek",
"bin_syserror",
"zsystem",
][..],
),
("zsh/termcap", &["echotc"][..]),
("zsh/terminfo", &["echoti"][..]),
("zsh/watch", &["log"][..]),
("zsh/zftp", &["zftp"][..]),
("zsh/zleparameter", &[][..]),
("zsh/zprof", &["zprof"][..]),
("zsh/zpty", &["zpty"][..]),
("zsh/zselect", &["zselect"][..]),
(
"zsh/zutil",
&["zstyle", "zformat", "zparseopts", "zregexparse"][..],
),
(
"zsh/attr",
&["zgetattr", "zsetattr", "zdelattr", "zlistattr"][..],
),
("zsh/cap", &["cap", "getcap", "setcap"][..]),
("zsh/clone", &["clone"][..]),
("zsh/curses", &["zcurses"][..]),
("zsh/db/gdbm", &["ztie", "zuntie", "zgdbmpath"][..]),
("zsh/param/private", &["private"][..]),
("zsh/compctl", &["compctl", "compcall"][..]),
("zsh/rlimits", &["limit", "ulimit", "unlimit"][..]),
("zsh/zle", &["zle", "vared", "bindkey"][..]),
("zsh/example", &["example"][..]),
];
let zsh_default_loaded: &[&str] = &[
"zsh/compctl",
"zsh/complete",
"zsh/computil",
"zsh/main",
"zsh/param/private",
"zsh/parameter",
"zsh/rlimits",
"zsh/sched",
"zsh/termcap",
"zsh/terminfo",
"zsh/watch",
"zsh/zle",
"zsh/zleparameter",
"zsh/zutil",
];
for (name, _builtins) in &builtin_modules {
let mut module = module::new(name);
if !zsh_default_loaded.contains(name) {
module.node.flags |= crate::ported::zsh_h::MOD_UNLOAD;
}
self.modules.insert(name.to_string(), module);
}
for name in zsh_default_loaded {
#[allow(clippy::single_match)]
match *name {
"zsh/watch" => {
crate::ported::modules::watch::boot_(std::ptr::null());
}
_ => {}
}
}
let autoload_pairs: &[(&str, &str)] = &[
("bindkey", "zsh/zle"),
("compadd", "zsh/complete"),
("comparguments", "zsh/computil"),
("compcall", "zsh/compctl"),
("compctl", "zsh/compctl"),
("compdescribe", "zsh/computil"),
("compfiles", "zsh/computil"),
("compgroups", "zsh/computil"),
("compquote", "zsh/computil"),
("compset", "zsh/complete"),
("comptags", "zsh/computil"),
("comptry", "zsh/computil"),
("compvalues", "zsh/computil"),
("echotc", "zsh/termcap"),
("echoti", "zsh/terminfo"),
("limit", "zsh/rlimits"),
("log", "zsh/watch"),
("private", "zsh/param/private"),
("sched", "zsh/sched"),
("ulimit", "zsh/rlimits"),
("unlimit", "zsh/rlimits"),
("vared", "zsh/zle"),
("zformat", "zsh/zutil"),
("zle", "zsh/zle"),
("zparseopts", "zsh/zutil"),
("zregexparse", "zsh/zutil"),
("zstyle", "zsh/zutil"),
];
for (b, m) in autoload_pairs {
self.autoload_builtins
.insert((*b).to_string(), (*m).to_string());
}
let mut main = module::new("zsh/main");
main.node.flags |= crate::ported::zsh_h::MOD_INIT_B | MOD_INIT_S; main.linked = Some(Box::new(linkedmod {
name: "zsh/main".to_string(),
setup: None,
features: None,
enables: None,
boot: None,
cleanup: None,
finish: None,
}));
self.modules.insert("zsh/main".to_string(), main);
}
pub fn load_module(&mut self, name: &str) -> bool {
if modname_ok(name) == 0 {
return false;
}
crate::ported::signals::queue_signals(); if !self.modules.contains_key(name) {
unqueue_signals(); return false; }
let flags = self.modules.get(name).unwrap().node.flags;
if (flags & MOD_SETUP) != 0 {
unqueue_signals(); return true; }
if (flags & MOD_UNLOAD) != 0 {
self.modules.get_mut(name).unwrap().node.flags &= !MOD_UNLOAD;
} else if {
let m = self.modules.get(name).unwrap();
if (flags & MOD_LINKED) != 0 {
m.linked.is_some() } else {
m.handle.is_some() }
} {
unqueue_signals(); return true; }
if (flags & MOD_BUSY) != 0 {
unqueue_signals(); crate::ported::utils::zerr(&format!(
"circular dependencies for module ;{}",
name
));
return false; }
self.modules.get_mut(name).unwrap().node.flags |= MOD_BUSY;
let deps_snapshot: Vec<String> = self
.modules
.get(name)
.and_then(|m| m.deps.as_ref())
.map(|d| d.iter().cloned().collect())
.unwrap_or_default();
for dep in &deps_snapshot {
if !self.load_module(dep) {
self.modules.get_mut(name).unwrap().node.flags &= !MOD_BUSY; unqueue_signals(); return false; }
}
self.modules.get_mut(name).unwrap().node.flags &= !MOD_BUSY;
let needs_setup = {
let m = self.modules.get(name).unwrap();
m.handle.is_none() && m.linked.is_none() };
if needs_setup {
if let Some(m) = self.modules.get_mut(name) {
m.linked = Some(Box::new(linkedmod {
name: name.to_string(),
setup: None,
features: None,
enables: None,
boot: None,
cleanup: None,
finish: None,
}));
m.node.flags |= MOD_SETUP | MOD_LINKED;
}
if setup_module(self, name) != 0 {
let _ = finish_module(self, name);
if let Some(m) = self.modules.get_mut(name) {
m.linked = None; m.node.flags &= !MOD_SETUP;
}
unqueue_signals(); return false; }
self.modules.get_mut(name).unwrap().node.flags |= MOD_INIT_S;
}
self.modules.get_mut(name).unwrap().node.flags |= MOD_SETUP;
for nm in crate::vm_helper::module_gated_params_for(name) {
crate::vm_helper::seed_partab_param(nm);
}
let bootret = do_boot_module(self, name, None, 0);
if bootret == 1 {
let _ = cleanup_module(self, name);
let _ = finish_module(self, name);
if let Some(m) = self.modules.get_mut(name) {
m.linked = None; m.node.flags &= !MOD_SETUP;
}
unqueue_signals(); return false; }
if let Some(m) = self.modules.get_mut(name) {
m.node.flags |= MOD_INIT_B;
m.node.flags &= !MOD_SETUP;
}
unqueue_signals(); true }
pub fn unload_module(&mut self, name: &str) -> bool {
let mut target_name = name.to_string();
let needs_alias_chase = self
.modules
.get(&target_name)
.map(|m| (m.node.flags & MOD_ALIAS) != 0)
.unwrap_or(false);
if needs_alias_chase {
target_name = match self
.modules
.get(name)
.and_then(|m| m.alias.clone())
{
Some(a) => a,
None => return false, };
}
let (init_s, unload_flag) = match self.modules.get(&target_name) {
Some(m) => (
(m.node.flags & MOD_INIT_S) != 0,
(m.node.flags & MOD_UNLOAD) != 0,
),
None => return false, };
if init_s && !unload_flag {
if cleanup_module(self, &target_name) != 0 {
return false; }
}
if let Some(m) = self.modules.get_mut(&target_name) {
m.node.flags &= !(MOD_INIT_B | MOD_INIT_S);
}
let del = unload_flag;
let has_wrapper = self
.modules
.get(&target_name)
.map(|m| m.wrapper != 0)
.unwrap_or(false);
if has_wrapper {
if let Some(m) = self.modules.get_mut(&target_name) {
m.node.flags |= MOD_UNLOAD; }
return true; }
if let Some(m) = self.modules.get_mut(&target_name) {
m.node.flags &= !MOD_UNLOAD;
}
let was_linked = self
.modules
.get(&target_name)
.map(|m| m.linked.is_some())
.unwrap_or(false);
if was_linked {
let _ = finish_module(self, &target_name); if let Some(m) = self.modules.get_mut(&target_name) {
m.linked = None; }
}
if del {
let deps_snapshot: Vec<String> = self
.modules
.get(&target_name)
.and_then(|m| m.deps.as_ref())
.map(|d| d.iter().cloned().collect())
.unwrap_or_default();
for dep_name in deps_snapshot {
let dm_target = match find_module(self, &dep_name, FINDMOD_ALIASP) {
Some(n) => n,
None => continue, };
let dm_unloading = self
.modules
.get(&dm_target)
.map(|m| (m.node.flags & MOD_UNLOAD) != 0)
.unwrap_or(false);
if !dm_unloading {
continue; }
let still_depended = self.modules.iter().any(|(other_name, other)| {
if other_name == &target_name {
return false; }
let other_deps = match other.deps.as_ref() {
Some(d) => d,
None => return false, };
if (other.node.flags & MOD_LINKED) == 0
|| (other.node.flags & MOD_UNLOAD) != 0
{
return false;
}
other_deps.iter().any(|d| d == &dm_target)
});
if !still_depended {
self.unload_module(&dm_target);
}
}
}
let (has_autoloads, has_deps, autoloads) = match self.modules.get(&target_name) {
Some(m) => (
m.autoloads
.as_ref()
.map(|a| !a.is_empty())
.unwrap_or(false),
m.deps.is_some(),
m.autoloads
.as_ref()
.map(|a| a.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default(),
),
None => (false, false, Vec::new()),
};
if has_autoloads {
autofeatures(self, "zsh", Some(&target_name), &autoloads, 0, FEAT_IGNORE);
} else if !has_deps {
self.modules.remove(&target_name);
}
true }
pub fn is_loaded(&self, name: &str) -> bool {
self.modules
.get(name)
.map(|m| m.is_loaded())
.unwrap_or(false)
}
pub fn is_bound(&self, name: &str) -> bool {
self.modules
.get(name)
.map(|m| {
(m.handle.is_some() || m.linked.is_some())
&& (m.node.flags & MOD_UNLOAD) == 0
})
.unwrap_or(false)
}
pub fn list_loaded(&self) -> Vec<&str> {
self.modules
.iter()
.filter(|(_, m)| m.is_loaded())
.map(|(name, _)| name.as_str())
.collect()
}
pub fn list_all(&self) -> Vec<(&str, i32)> {
self.modules
.iter()
.map(|(name, m)| (name.as_str(), m.node.flags))
.collect()
}
pub fn addbuiltin(&mut self, name: &str, module: &str) -> i32 {
let mut bn = builtin {
node: hashnode {
next: None,
nam: name.to_string(),
flags: 0,
},
handlerfunc: None,
minargs: 0,
maxargs: 0,
funcid: 0,
optstr: Some(module.to_string()),
defopts: None,
};
if addbuiltin(&mut bn) != 0 {
return 1; }
self.added_builtins
.insert(name.to_string(), BINF_ADDED);
0 }
pub fn deletebuiltin(&mut self, name: &str, _module: &str) -> i32 {
let r = deletebuiltin(name);
if r == 0 {
self.added_builtins.remove(name);
}
r
}
pub fn add_autobin(&mut self, name: &str, module: &str, flags: i32) -> i32 {
let node_flags: i32 = if (flags & FEAT_AUTOALL as i32) != 0 {
BINF_AUTOALL as i32 } else {
0
};
let mut bn = builtin {
node: hashnode {
next: None,
nam: name.to_string(),
flags: node_flags,
},
handlerfunc: None,
minargs: 0,
maxargs: 0,
funcid: 0,
optstr: Some(module.to_string()),
defopts: None,
};
if addbuiltin(&mut bn) != 0 {
if (flags & FEAT_IGNORE as i32) == 0 {
return 1; }
return 0;
}
self.autoload_builtins
.insert(name.to_string(), module.to_string());
0
}
pub fn del_autobin(&mut self, name: &str, flags: i32) -> i32 {
let static_flags: Option<i32> = createbuiltintable().get(name).map(|b| b.node.flags);
let in_ledger = self.autoload_builtins.contains_key(name);
let added = self.added_builtins.contains_key(name)
|| static_flags
.map(|f| (f & BINF_ADDED as i32) != 0)
.unwrap_or(false)
|| (static_flags.is_some() && {
let mod_names: Vec<String> = self.modules.keys().cloned().collect();
let mut owner_loaded: Option<bool> = None; 'outer: for mn in &mod_names {
let mut feats: Vec<String> = Vec::new();
if features_module(self, mn, &mut feats) != 0 {
continue;
}
for f in &feats {
if f.strip_prefix("b:") == Some(name) {
owner_loaded = Some(self.is_loaded(mn));
break 'outer;
}
}
}
owner_loaded != Some(false)
});
if static_flags.is_none() && !in_ledger {
if (flags & FEAT_IGNORE as i32) == 0 {
return 2; }
} else if added {
if (flags & FEAT_IGNORE as i32) == 0 {
return 3; }
} else {
self.autoload_builtins.remove(name);
}
0 }
pub fn setbuiltins(&mut self, module: &str, names: &[&str], e: Option<&[i32]>) -> i32 {
let mut ret: i32 = 0; for (n, name) in names.iter().enumerate() {
let enable = e
.map(|arr| arr.get(n).copied().unwrap_or(0)) .unwrap_or(1);
let already_added = self.added_builtins.contains_key(*name); if enable != 0 {
if already_added {
continue;
}
let mut probe = builtin {
node: hashnode {
next: None,
nam: name.to_string(),
flags: 0,
},
handlerfunc: None,
minargs: 0,
maxargs: 0,
funcid: 0,
optstr: Some(module.to_string()),
defopts: None,
};
if addbuiltin(&mut probe) != 0 {
zwarnnam(
module,
&format!("name clash when adding builtin `{}'", name),
);
ret = 1; } else {
self.added_builtins.insert(name.to_string(), BINF_ADDED);
}
} else {
if !already_added {
continue;
}
if deletebuiltin(name) != 0 {
zwarnnam(
module,
&format!("builtin `{}' already deleted", name),
);
ret = 1; } else {
self.added_builtins.remove(*name);
}
}
}
ret }
pub fn addconddef(&mut self, name: &str, module: &str) -> i32 {
let cd = conddef {
next: None,
name: name.to_string(),
flags: 0, handler: None,
min: 0,
max: 0,
condid: 0,
module: Some(module.to_string()),
};
addconddef(cd)
}
pub fn deleteconddef(&mut self, name: &str, _module: &str) -> i32 {
let probe = conddef {
next: None,
name: name.to_string(),
flags: 0,
handler: None,
min: 0,
max: 0,
condid: 0,
module: None,
};
deleteconddef(&probe)
}
pub fn getconddef(&self, name: &str) -> Option<&str> {
self.autoload_conditions.get(name).map(|s| s.as_str())
}
pub fn add_autocond(&mut self, name: &str, module: &str, flags: i32) -> i32 {
let mut cflags: i32 = if (flags & FEAT_INFIX) != 0 {
CONDF_INFIX } else {
0
};
if (flags & FEAT_AUTOALL) != 0 {
cflags |= CONDF_AUTOALL; }
let cd = conddef {
next: None,
name: name.to_string(),
flags: cflags,
handler: None,
min: 0,
max: 0,
condid: 0,
module: Some(module.to_string()),
};
if addconddef(cd) != 0 {
if (flags & FEAT_IGNORE) == 0 {
return 1; }
}
self.autoload_conditions
.insert(name.to_string(), module.to_string());
0 }
pub fn del_autocond(&mut self, name: &str, flags: i32) -> i32 {
let inf = if (flags & FEAT_INFIX) != 0 { 1 } else { 0 };
let cd = getconddef(inf, name, 0, self);
match cd {
None => {
if (flags & FEAT_IGNORE) == 0 {
return 2; }
0 }
Some(ref entry) if (entry.flags & CONDF_ADDED) != 0 => {
if (flags & FEAT_IGNORE) == 0 {
return 3; }
0
}
Some(ref entry) => {
let _ = deleteconddef(entry);
self.autoload_conditions.remove(name);
0 }
}
}
pub fn setparamdefs(&mut self, module: &str, names: &[&str], e: Option<&[i32]>) -> i32 {
use crate::ported::params::paramtab;
let mut ret: i32 = 0; for (n, name) in names.iter().enumerate() {
let enable = e
.map(|arr| arr.get(n).copied().unwrap_or(0)) .unwrap_or(1);
let already = self.autoload_params.contains_key(*name);
if enable != 0 {
if already {
continue; }
let canonical_clash = paramtab()
.read()
.ok()
.map(|t| t.contains_key(*name))
.unwrap_or(false);
if canonical_clash {
zwarnnam(
module,
&format!("error when adding parameter `{}'", name),
);
ret = 1; continue;
}
self.autoload_params
.insert(name.to_string(), module.to_string());
} else {
if !already {
continue; }
let canonical_present = paramtab()
.read()
.ok()
.map(|t| t.contains_key(*name))
.unwrap_or(false);
self.autoload_params.remove(*name);
if !canonical_present {
zwarnnam(
module,
&format!("parameter `{}' already deleted", name),
);
ret = 1; }
}
}
ret }
pub fn add_autoparam(&mut self, name: &str, module: &str, flags: i32) -> i32 {
let r = add_autoparam(module, name, flags); if r != 0 {
if (flags & FEAT_IGNORE) != 0 {
return 0;
}
return r; }
self.autoload_params
.insert(name.to_string(), module.to_string());
0
}
pub fn del_autoparam(&mut self, name: &str, flags: i32) -> i32 {
let r = del_autoparam("", name, flags); if r == 0 {
self.autoload_params.remove(name);
}
r
}
pub fn enable_feature(&mut self, module: &str, _name: &str) -> bool {
self.modules.contains_key(module)
}
pub fn disable_feature(&mut self, module: &str, _name: &str) -> bool {
self.modules.contains_key(module)
}
pub fn list_features(&self, _module: &str) -> Vec<String> {
Vec::new()
}
pub fn module_linked(&self, name: &str) -> bool {
self.modules.contains_key(name)
}
pub fn resolve_autoload_builtin(&self, name: &str) -> Option<&str> {
self.autoload_builtins.get(name).map(|s| s.as_str())
}
pub fn resolve_autoload_param(&self, name: &str) -> Option<&str> {
self.autoload_params.get(name).map(|s| s.as_str())
}
pub fn ensurefeature(&mut self, module: &str, feature: &str) -> bool {
if !self.is_loaded(module) {
self.load_module(module);
}
self.is_loaded(module)
}
}
pub trait ModuleLifecycle {
fn setup(&mut self) -> i32 {
0
}
fn boot(&mut self) -> i32 {
0
}
fn cleanup(&mut self) -> i32 {
0
}
fn finish(&mut self) -> i32 {
0
}
}
pub fn getmathfunc(table: &mut modulestab, name: &str, autol: i32) -> Option<String> {
let hit: Option<(String, i32, bool)> = {
let tab = MATHFUNCS.lock().unwrap();
tab.iter().find_map(|p| {
if p.name == name {
Some((
p.module.clone().unwrap_or_default(),
p.flags,
p.module.is_some(),
))
} else {
None
}
})
};
let (module, flags, has_module) = match hit {
Some(t) => t,
None => return None, };
if autol != 0 && has_module && (flags & MFF_USERFUNC) == 0 {
removemathfunc(name);
let feature_arg = if (flags & crate::ported::zsh_h::MFF_AUTOALL) != 0 {
None
} else {
Some(name)
};
let _ = ensurefeature(table, &module, "f:", feature_arg);
let after = {
let tab = MATHFUNCS.lock().unwrap();
tab.iter().find_map(|p| {
if p.name == name {
Some(p.module.clone().unwrap_or_default())
} else {
None
}
})
};
if after.is_none() {
crate::ported::utils::zerr(&format!(
"autoloading module {} failed to define math function: {}",
module, name
));
}
return after; }
if has_module {
Some(module)
} else {
Some(String::new())
}
}
pub fn add_automathfunc(table: &mut modulestab, module: &str, fnam: &str, flags: i32) -> i32 {
let f = mathfunc {
next: None,
name: fnam.to_string(),
flags: 0, nfunc: None,
sfunc: None,
module: Some(module.to_string()),
minargs: 0,
maxargs: 0,
funcid: 0,
};
if addmathfunc(f) != 0 {
if (flags & FEAT_IGNORE) == 0 {
return 1; }
return 0;
}
table
.autoload_mathfuncs
.insert(fnam.to_string(), module.to_string());
0
}
pub fn del_automathfunc(table: &mut modulestab, _modnam: &str, fnam: &str, flags: i32) -> i32 {
let entry = getmathfunc(table, fnam, 0);
match entry {
None => {
if (flags & FEAT_IGNORE) == 0 {
return 2;
}
0
}
Some(_) => {
let added = {
let tab = MATHFUNCS.lock().unwrap();
tab.iter()
.find(|m| m.name == fnam)
.map(|m| (m.flags & MFF_ADDED) != 0)
.unwrap_or(false)
};
if added {
if (flags & FEAT_IGNORE) == 0 {
return 3;
}
return 0;
}
removemathfunc(fnam);
table.autoload_mathfuncs.remove(fnam);
0
}
}
}
pub fn load_and_bind(_fn_path: &str) -> usize {
0 }
#[allow(unused_variables)]
pub fn hpux_dlsym(handle: usize, name: &str) -> usize {
0 }
pub fn try_load_module(table: &modulestab, name: &str) -> i32 {
match table.modules.get(name) {
Some(m) if (m.node.flags & MOD_LINKED) != 0 => 1,
_ => 0,
}
}
pub fn do_load_module(table: &mut modulestab, name: &str, silent: i32) -> i32 {
let ret = try_load_module(table, name);
if ret == 0 && silent == 0 {
zwarn(&format!("failed to load module: {}", name));
}
ret }
pub fn find_module(table: &mut modulestab, name: &str, flags: i32) -> Option<String> {
let mut cur_name = name.to_string();
let mut depth = 0;
loop {
if depth > 64 {
return None;
} depth += 1;
match table.modules.get(&cur_name) {
Some(m) => {
if (flags & FINDMOD_ALIASP) != 0 && (m.node.flags & MOD_ALIAS) != 0 {
if let Some(target) = m.alias.clone() {
cur_name = target;
continue;
}
return None;
}
return Some(cur_name);
}
None => {
if (flags & FINDMOD_CREATE) == 0 {
return None;
}
let mut m = module::new(&cur_name);
m.node.flags = 0; table.modules.insert(cur_name.clone(), m);
return Some(cur_name);
}
}
}
}
pub fn delete_module(table: &mut modulestab, name: &str) -> i32 {
table.modules.remove(name); 0
}
pub fn module_loaded(table: &modulestab, name: &str) -> i32 {
let target = match table.modules.get(name) {
Some(m) if (m.node.flags & MOD_ALIAS) != 0 => {
match m.alias.as_ref().and_then(|a| table.modules.get(a)) {
Some(t) => t,
None => return 0, }
}
Some(m) => m,
None => return 0, };
if (target.node.flags & MOD_LINKED) == 0 {
return 0;
}
if (target.node.flags & MOD_UNLOAD) != 0 {
return 0;
}
1
}
#[allow(unused_variables)]
pub fn dyn_setup_module(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn dyn_features_module(m: *const module, features: &mut Vec<String>) -> i32 {
0 }
#[allow(unused_variables)]
pub fn dyn_enables_module(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
0 }
#[allow(unused_variables)]
pub fn dyn_boot_module(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn dyn_cleanup_module(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn dyn_finish_module(m: *const module) -> i32 {
0 }
#[allow(unused_variables)]
pub fn module_func(m: &module, name: &str) -> usize {
0 }
pub fn setup_module(_table: &mut modulestab, name: &str) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::setup_(std::ptr::null()),
"zsh/cap" => crate::ported::modules::cap::setup_(std::ptr::null()),
"zsh/clone" => crate::ported::modules::clone::setup_(std::ptr::null()),
"zsh/curses" => crate::ported::modules::curses::setup_(std::ptr::null()),
"zsh/datetime" => crate::ported::modules::datetime::setup_(std::ptr::null()),
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::setup_(std::ptr::null()),
"zsh/example" => crate::ported::modules::example::setup_(std::ptr::null()),
"zsh/files" => crate::ported::modules::files::setup_(std::ptr::null()),
"zsh/hlgroup" => crate::ported::modules::hlgroup::setup_(std::ptr::null()),
"zsh/ksh93" => crate::ported::modules::ksh93::setup_(std::ptr::null()),
"zsh/langinfo" => crate::ported::modules::langinfo::setup_(std::ptr::null()),
"zsh/mapfile" => crate::ported::modules::mapfile::setup_(std::ptr::null()),
"zsh/mathfunc" => crate::ported::modules::mathfunc::setup_(std::ptr::null()),
"zsh/nearcolor" => crate::ported::modules::nearcolor::setup_(std::ptr::null()),
"zsh/newuser" => crate::ported::modules::newuser::setup_(std::ptr::null()),
"zsh/parameter" => crate::ported::modules::parameter::setup_(std::ptr::null()),
"zsh/param/private" => crate::ported::modules::param_private::setup_(std::ptr::null()),
"zsh/pcre" => crate::ported::modules::pcre::setup_(std::ptr::null()),
"zsh/random" => crate::ported::modules::random::setup_(std::ptr::null()),
"zsh/regex" => crate::ported::modules::regex::setup_(std::ptr::null()),
"zsh/net/socket" => crate::ported::modules::socket::setup_(std::ptr::null()),
"zsh/stat" => crate::ported::modules::stat::setup_(std::ptr::null()),
"zsh/system" => crate::ported::modules::system::setup_(std::ptr::null()),
"zsh/net/tcp" => crate::ported::modules::tcp::setup_(std::ptr::null()),
"zsh/termcap" => crate::ported::modules::termcap::setup_(std::ptr::null()),
"zsh/terminfo" => crate::ported::modules::terminfo::setup_(std::ptr::null()),
"zsh/watch" => crate::ported::modules::watch::setup_(std::ptr::null()),
"zsh/zftp" => crate::ported::modules::zftp::setup_(std::ptr::null()),
"zsh/zprof" => crate::ported::modules::zprof::setup_(std::ptr::null()),
"zsh/zpty" => crate::ported::modules::zpty::setup_(std::ptr::null()),
"zsh/zselect" => crate::ported::modules::zselect::setup_(std::ptr::null()),
"zsh/zutil" => crate::ported::modules::zutil::setup_(std::ptr::null()),
_ => 0,
}
}
pub fn features_module(
_table: &mut modulestab,
name: &str,
features: &mut Vec<String>,
) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::features_(std::ptr::null(), features),
"zsh/cap" => crate::ported::modules::cap::features_(std::ptr::null(), features),
"zsh/clone" => crate::ported::modules::clone::features_(std::ptr::null(), features),
"zsh/curses" => crate::ported::modules::curses::features_(std::ptr::null(), features),
"zsh/datetime" => {
crate::ported::modules::datetime::features_(std::ptr::null(), features)
}
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::features_(std::ptr::null(), features),
"zsh/example" => crate::ported::modules::example::features_(std::ptr::null(), features),
"zsh/files" => crate::ported::modules::files::features_(std::ptr::null(), features),
"zsh/hlgroup" => crate::ported::modules::hlgroup::features_(std::ptr::null(), features),
"zsh/ksh93" => crate::ported::modules::ksh93::features_(std::ptr::null(), features),
"zsh/langinfo" => {
crate::ported::modules::langinfo::features_(std::ptr::null(), features)
}
"zsh/mapfile" => crate::ported::modules::mapfile::features_(std::ptr::null(), features),
"zsh/mathfunc" => {
crate::ported::modules::mathfunc::features_(std::ptr::null(), features)
}
"zsh/nearcolor" => {
crate::ported::modules::nearcolor::features_(std::ptr::null(), features)
}
"zsh/newuser" => crate::ported::modules::newuser::features_(std::ptr::null(), features),
"zsh/parameter" => {
crate::ported::modules::parameter::features_(std::ptr::null(), features)
}
"zsh/param/private" => {
crate::ported::modules::param_private::features_(std::ptr::null(), features)
}
"zsh/pcre" => crate::ported::modules::pcre::features_(std::ptr::null(), features),
"zsh/random" => crate::ported::modules::random::features_(std::ptr::null(), features),
"zsh/regex" => crate::ported::modules::regex::features_(std::ptr::null(), features),
"zsh/net/socket" => {
crate::ported::modules::socket::features_(std::ptr::null(), features)
}
"zsh/stat" => crate::ported::modules::stat::features_(std::ptr::null(), features),
"zsh/system" => crate::ported::modules::system::features_(std::ptr::null(), features),
"zsh/net/tcp" => crate::ported::modules::tcp::features_(std::ptr::null(), features),
"zsh/termcap" => crate::ported::modules::termcap::features_(std::ptr::null(), features),
"zsh/terminfo" => {
crate::ported::modules::terminfo::features_(std::ptr::null(), features)
}
"zsh/watch" => crate::ported::modules::watch::features_(std::ptr::null(), features),
"zsh/zle" => crate::ported::zle::zle_main::features_(std::ptr::null(), features),
"zsh/zftp" => crate::ported::modules::zftp::features_(std::ptr::null(), features),
"zsh/zprof" => crate::ported::modules::zprof::features_(std::ptr::null(), features),
"zsh/zpty" => crate::ported::modules::zpty::features_(std::ptr::null(), features),
"zsh/zselect" => crate::ported::modules::zselect::features_(std::ptr::null(), features),
"zsh/zutil" => crate::ported::modules::zutil::features_(std::ptr::null(), features),
_ => 0,
}
}
pub fn enables_module(
_table: &mut modulestab,
name: &str,
enables: &mut Option<Vec<i32>>,
) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::enables_(std::ptr::null(), enables),
"zsh/cap" => crate::ported::modules::cap::enables_(std::ptr::null(), enables),
"zsh/clone" => crate::ported::modules::clone::enables_(std::ptr::null(), enables),
"zsh/curses" => crate::ported::modules::curses::enables_(std::ptr::null(), enables),
"zsh/datetime" => crate::ported::modules::datetime::enables_(std::ptr::null(), enables),
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::enables_(std::ptr::null(), enables),
"zsh/example" => crate::ported::modules::example::enables_(std::ptr::null(), enables),
"zsh/files" => crate::ported::modules::files::enables_(std::ptr::null(), enables),
"zsh/hlgroup" => crate::ported::modules::hlgroup::enables_(std::ptr::null(), enables),
"zsh/ksh93" => crate::ported::modules::ksh93::enables_(std::ptr::null(), enables),
"zsh/langinfo" => crate::ported::modules::langinfo::enables_(std::ptr::null(), enables),
"zsh/mapfile" => crate::ported::modules::mapfile::enables_(std::ptr::null(), enables),
"zsh/mathfunc" => crate::ported::modules::mathfunc::enables_(std::ptr::null(), enables),
"zsh/nearcolor" => {
crate::ported::modules::nearcolor::enables_(std::ptr::null(), enables)
}
"zsh/newuser" => crate::ported::modules::newuser::enables_(std::ptr::null(), enables),
"zsh/parameter" => crate::ported::modules::parameter::enables_(std::ptr::null(), enables),
"zsh/param/private" => {
crate::ported::modules::param_private::enables_(std::ptr::null(), enables)
}
"zsh/pcre" => crate::ported::modules::pcre::enables_(std::ptr::null(), enables),
"zsh/random" => crate::ported::modules::random::enables_(std::ptr::null(), enables),
"zsh/regex" => crate::ported::modules::regex::enables_(std::ptr::null(), enables),
"zsh/net/socket" => crate::ported::modules::socket::enables_(std::ptr::null(), enables),
"zsh/stat" => crate::ported::modules::stat::enables_(std::ptr::null(), enables),
"zsh/system" => crate::ported::modules::system::enables_(std::ptr::null(), enables),
"zsh/net/tcp" => crate::ported::modules::tcp::enables_(std::ptr::null(), enables),
"zsh/termcap" => crate::ported::modules::termcap::enables_(std::ptr::null(), enables),
"zsh/terminfo" => crate::ported::modules::terminfo::enables_(std::ptr::null(), enables),
"zsh/watch" => crate::ported::modules::watch::enables_(std::ptr::null(), enables),
"zsh/zftp" => crate::ported::modules::zftp::enables_(std::ptr::null(), enables),
"zsh/zprof" => crate::ported::modules::zprof::enables_(std::ptr::null(), enables),
"zsh/zpty" => crate::ported::modules::zpty::enables_(std::ptr::null(), enables),
"zsh/zselect" => crate::ported::modules::zselect::enables_(std::ptr::null(), enables),
"zsh/zutil" => crate::ported::modules::zutil::enables_(std::ptr::null(), enables),
_ => 0,
}
}
pub fn boot_module(_table: &mut modulestab, name: &str) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::boot_(std::ptr::null()),
"zsh/cap" => crate::ported::modules::cap::boot_(std::ptr::null()),
"zsh/clone" => crate::ported::modules::clone::boot_(std::ptr::null()),
"zsh/curses" => crate::ported::modules::curses::boot_(std::ptr::null()),
"zsh/datetime" => crate::ported::modules::datetime::boot_(std::ptr::null()),
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::boot_(std::ptr::null()),
"zsh/example" => crate::ported::modules::example::boot_(std::ptr::null()),
"zsh/files" => crate::ported::modules::files::boot_(std::ptr::null()),
"zsh/hlgroup" => crate::ported::modules::hlgroup::boot_(std::ptr::null()),
"zsh/ksh93" => crate::ported::modules::ksh93::boot_(std::ptr::null()),
"zsh/langinfo" => crate::ported::modules::langinfo::boot_(std::ptr::null()),
"zsh/mapfile" => crate::ported::modules::mapfile::boot_(std::ptr::null()),
"zsh/mathfunc" => crate::ported::modules::mathfunc::boot_(std::ptr::null()),
"zsh/nearcolor" => crate::ported::modules::nearcolor::boot_(std::ptr::null()),
"zsh/newuser" => crate::ported::modules::newuser::boot_(std::ptr::null()),
"zsh/parameter" => crate::ported::modules::parameter::boot_(std::ptr::null()),
"zsh/param/private" => crate::ported::modules::param_private::boot_(std::ptr::null()),
"zsh/pcre" => crate::ported::modules::pcre::boot_(std::ptr::null()),
"zsh/random" => crate::ported::modules::random::boot_(std::ptr::null()),
"zsh/regex" => crate::ported::modules::regex::boot_(std::ptr::null()),
"zsh/net/socket" => crate::ported::modules::socket::boot_(std::ptr::null()),
"zsh/stat" => crate::ported::modules::stat::boot_(std::ptr::null()),
"zsh/system" => crate::ported::modules::system::boot_(std::ptr::null()),
"zsh/net/tcp" => crate::ported::modules::tcp::boot_(std::ptr::null()),
"zsh/termcap" => crate::ported::modules::termcap::boot_(std::ptr::null()),
"zsh/terminfo" => crate::ported::modules::terminfo::boot_(std::ptr::null()),
"zsh/watch" => {
let mptr = _table
.modules
.get(name)
.map(|m| m as *const crate::ported::zsh_h::module)
.unwrap_or(std::ptr::null());
crate::ported::modules::watch::boot_(mptr)
}
"zsh/zftp" => crate::ported::modules::zftp::boot_(std::ptr::null()),
"zsh/zprof" => crate::ported::modules::zprof::boot_(std::ptr::null()),
"zsh/zpty" => crate::ported::modules::zpty::boot_(std::ptr::null()),
"zsh/zselect" => crate::ported::modules::zselect::boot_(std::ptr::null()),
"zsh/zutil" => crate::ported::modules::zutil::boot_(std::ptr::null()),
_ => 0,
}
}
pub fn cleanup_module(_table: &mut modulestab, name: &str) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::cleanup_(std::ptr::null()),
"zsh/cap" => crate::ported::modules::cap::cleanup_(std::ptr::null()),
"zsh/clone" => crate::ported::modules::clone::cleanup_(std::ptr::null()),
"zsh/curses" => crate::ported::modules::curses::cleanup_(std::ptr::null()),
"zsh/datetime" => crate::ported::modules::datetime::cleanup_(std::ptr::null()),
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::cleanup_(std::ptr::null()),
"zsh/example" => crate::ported::modules::example::cleanup_(std::ptr::null()),
"zsh/files" => crate::ported::modules::files::cleanup_(std::ptr::null()),
"zsh/hlgroup" => crate::ported::modules::hlgroup::cleanup_(std::ptr::null()),
"zsh/ksh93" => crate::ported::modules::ksh93::cleanup_(std::ptr::null()),
"zsh/langinfo" => crate::ported::modules::langinfo::cleanup_(std::ptr::null()),
"zsh/mapfile" => crate::ported::modules::mapfile::cleanup_(std::ptr::null()),
"zsh/mathfunc" => crate::ported::modules::mathfunc::cleanup_(std::ptr::null()),
"zsh/nearcolor" => crate::ported::modules::nearcolor::cleanup_(std::ptr::null()),
"zsh/newuser" => crate::ported::modules::newuser::cleanup_(std::ptr::null()),
"zsh/parameter" => crate::ported::modules::parameter::cleanup_(std::ptr::null()),
"zsh/param/private" => crate::ported::modules::param_private::cleanup_(std::ptr::null()),
"zsh/pcre" => crate::ported::modules::pcre::cleanup_(std::ptr::null()),
"zsh/random" => crate::ported::modules::random::cleanup_(std::ptr::null()),
"zsh/regex" => crate::ported::modules::regex::cleanup_(std::ptr::null()),
"zsh/net/socket" => crate::ported::modules::socket::cleanup_(std::ptr::null()),
"zsh/stat" => crate::ported::modules::stat::cleanup_(std::ptr::null()),
"zsh/system" => crate::ported::modules::system::cleanup_(std::ptr::null()),
"zsh/net/tcp" => crate::ported::modules::tcp::cleanup_(std::ptr::null()),
"zsh/termcap" => crate::ported::modules::termcap::cleanup_(std::ptr::null()),
"zsh/terminfo" => crate::ported::modules::terminfo::cleanup_(std::ptr::null()),
"zsh/watch" => crate::ported::modules::watch::cleanup_(std::ptr::null()),
"zsh/zftp" => crate::ported::modules::zftp::cleanup_(std::ptr::null()),
"zsh/zprof" => crate::ported::modules::zprof::cleanup_(std::ptr::null()),
"zsh/zpty" => crate::ported::modules::zpty::cleanup_(std::ptr::null()),
"zsh/zselect" => crate::ported::modules::zselect::cleanup_(std::ptr::null()),
"zsh/zutil" => crate::ported::modules::zutil::cleanup_(std::ptr::null()),
_ => 0,
}
}
pub fn finish_module(_table: &mut modulestab, name: &str) -> i32 {
match name {
"zsh/attr" => crate::ported::modules::attr::finish_(std::ptr::null()),
"zsh/cap" => crate::ported::modules::cap::finish_(std::ptr::null()),
"zsh/clone" => crate::ported::modules::clone::finish_(std::ptr::null()),
"zsh/curses" => crate::ported::modules::curses::finish_(std::ptr::null()),
"zsh/datetime" => crate::ported::modules::datetime::finish_(std::ptr::null()),
"zsh/db/gdbm" => crate::ported::modules::db_gdbm::finish_(std::ptr::null()),
"zsh/example" => crate::ported::modules::example::finish_(std::ptr::null()),
"zsh/files" => crate::ported::modules::files::finish_(std::ptr::null()),
"zsh/hlgroup" => crate::ported::modules::hlgroup::finish_(std::ptr::null()),
"zsh/ksh93" => crate::ported::modules::ksh93::finish_(std::ptr::null()),
"zsh/langinfo" => crate::ported::modules::langinfo::finish_(std::ptr::null()),
"zsh/mapfile" => crate::ported::modules::mapfile::finish_(std::ptr::null()),
"zsh/mathfunc" => crate::ported::modules::mathfunc::finish_(std::ptr::null()),
"zsh/nearcolor" => crate::ported::modules::nearcolor::finish_(std::ptr::null()),
"zsh/newuser" => crate::ported::modules::newuser::finish_(std::ptr::null()),
"zsh/parameter" => crate::ported::modules::parameter::finish_(std::ptr::null()),
"zsh/param/private" => crate::ported::modules::param_private::finish_(std::ptr::null()),
"zsh/pcre" => crate::ported::modules::pcre::finish_(std::ptr::null()),
"zsh/random" => crate::ported::modules::random::finish_(std::ptr::null()),
"zsh/regex" => crate::ported::modules::regex::finish_(std::ptr::null()),
"zsh/net/socket" => crate::ported::modules::socket::finish_(std::ptr::null()),
"zsh/stat" => crate::ported::modules::stat::finish_(std::ptr::null()),
"zsh/system" => crate::ported::modules::system::finish_(std::ptr::null()),
"zsh/net/tcp" => crate::ported::modules::tcp::finish_(std::ptr::null()),
"zsh/termcap" => crate::ported::modules::termcap::finish_(std::ptr::null()),
"zsh/terminfo" => crate::ported::modules::terminfo::finish_(std::ptr::null()),
"zsh/watch" => crate::ported::modules::watch::finish_(std::ptr::null()),
"zsh/zftp" => crate::ported::modules::zftp::finish_(std::ptr::null()),
"zsh/zprof" => crate::ported::modules::zprof::finish_(std::ptr::null()),
"zsh/zpty" => crate::ported::modules::zpty::finish_(std::ptr::null()),
"zsh/zselect" => crate::ported::modules::zselect::finish_(std::ptr::null()),
"zsh/zutil" => crate::ported::modules::zutil::finish_(std::ptr::null()),
_ => 0,
}
}
pub fn do_module_features(
table: &mut modulestab,
modname: &str,
features: Option<&[String]>,
flags: i32,
) -> i32 {
let mut module_features: Vec<String> = Vec::new(); let mut ret: i32 = 0;
if features_module(table, modname, &mut module_features) == 0 {
let mut enables: Option<Vec<i32>> = None;
if enables_module(table, modname, &mut enables) != 0 {
if (flags & FEAT_IGNORE) == 0 {
zwarn(&format!(
"error getting enabled features for module `{}'", modname
));
}
return 1; }
if (flags & FEAT_CHECKAUTO) != 0 {
let autoloads: Vec<String> = table
.modules
.get(modname)
.and_then(|m| m.autoloads.as_ref())
.map(|al| al.iter().cloned().collect())
.unwrap_or_default();
for al in &autoloads {
let found = module_features.iter().any(|f| f == al);
if !found {
if (flags & FEAT_IGNORE) == 0 {
zwarn(&format!(
"module `{}' has no such feature: `{}': autoload cancelled", modname, al
));
}
let arg = vec![al.clone()];
autofeatures(
table,
"",
Some(modname),
&arg,
0,
FEAT_IGNORE | FEAT_REMOVE,
);
}
}
}
match features {
Some(arr) => {
let enables_vec = enables.get_or_insert_with(Vec::new);
if enables_vec.len() < module_features.len() {
enables_vec.resize(module_features.len(), 0);
}
for fep_str in arr {
let (on, esp) = if let Some(rest) = fep_str.strip_prefix('+') {
(1i32, rest)
} else if let Some(rest) = fep_str.strip_prefix('-') {
(0i32, rest)
} else {
(1i32, fep_str.as_str())
};
let mut found = false;
for (i, f) in module_features.iter().enumerate() {
if f == esp {
enables_vec[i] = on; found = true;
break; }
}
if !found {
if (flags & FEAT_IGNORE) == 0 {
zwarn(&format!(
"module `{}' has no such feature: `{}'",
modname, esp
));
}
return 1; }
}
}
None => {
let enables_vec = enables.get_or_insert_with(Vec::new);
enables_vec.clear();
enables_vec.resize(module_features.len(), 1);
}
}
if enables_module(table, modname, &mut enables) != 0 {
return 2; }
} else if features.is_some() {
if (flags & FEAT_IGNORE) == 0 {
zwarn(&format!(
"module `{}' does not support features", modname
));
}
return 1; }
let _ = &mut ret;
ret }
pub fn do_boot_module(
table: &mut modulestab,
modname: &str,
features: Option<&[String]>,
silent: i32,
) -> i32 {
let flags = if silent != 0 {
FEAT_IGNORE | FEAT_CHECKAUTO
} else {
FEAT_CHECKAUTO };
let ret = do_module_features(table, modname, features, flags); if ret == 1 {
return 1; }
if boot_module(table, modname) != 0 {
return 1; }
ret }
pub fn do_cleanup_module(table: &mut modulestab, name: &str) -> i32 {
if table.modules.contains_key(name) {
cleanup_module(table, name) } else {
0
}
}
pub fn modname_ok(p: &str) -> i32 {
let bytes = p.as_bytes();
let mut i: usize = 0;
loop {
while i < bytes.len() {
let b = bytes[i];
if b.is_ascii_alphanumeric() || b == b'_' {
i += 1;
} else {
break;
}
}
if i >= bytes.len() {
return 1; }
if bytes[i] != b'/' {
break;
} i += 1;
}
0 }
pub fn require_module(
table: &mut modulestab,
modname: &str,
features: Option<&[String]>,
silent: i32,
) -> i32 {
crate::ported::signals::queue_signals();
let mname_opt = find_module(table, modname, FINDMOD_ALIASP);
let needs_load = match &mname_opt {
None => true, Some(mname) => match table.modules.get(mname) {
None => true,
Some(m) => {
(m.node.flags & MOD_INIT_B) == 0 || (m.node.flags & MOD_UNLOAD) != 0 }
},
};
let mname = mname_opt.unwrap_or_else(|| modname.to_string());
let ret = if needs_load {
if try_load_module(table, &mname) == 0 {
if silent == 0 {
crate::ported::utils::zwarn(&format!("failed to load module `{}'", mname));
}
crate::ported::signals::unqueue_signals();
return 1;
}
if !table.load_module(&mname) {
crate::ported::signals::unqueue_signals();
return 1;
}
0
} else {
do_module_features(table, &mname, features, 0)
};
crate::ported::signals::unqueue_signals();
ret }
pub fn add_dep(table: &mut modulestab, name: &str, from: &str) -> i32 {
let canon = match find_module(table, name, FINDMOD_ALIASP | FINDMOD_CREATE) {
Some(n) => n,
None => return 0,
};
if let Some(m) = table.modules.get_mut(&canon) {
let deps = m
.deps
.get_or_insert_with(crate::ported::linklist::LinkList::new);
if !deps.iter().any(|d| d == from) {
deps.push_back(from.to_string()); }
}
0
}
pub fn autoloadscan(name: &str, optstr: &str, flags: u32, printflags: i32) {
use crate::ported::utils::{nicezputs, quotedzputs};
let mut stdout = std::io::stdout();
if (flags & BINF_ADDED) != 0 {
return; }
if (printflags & PRINT_LIST) != 0 {
print!("zmodload -ab ");
if optstr.starts_with('-') {
print!("-- "); }
print!("{}", quotedzputs(optstr));
if name != optstr {
print!(" "); print!("{}", quotedzputs(name));
}
} else {
let _ = nicezputs(name, &mut stdout); if name != optstr {
print!(" ("); let _ = nicezputs(optstr, &mut stdout); print!(")"); }
}
println!(); }
pub fn bin_zmodload(
nam: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let mut table = MODULESTAB.lock().unwrap();
let table = &mut *table;
let ops_bcpf = OPT_ISSET(ops, b'b') || OPT_ISSET(ops, b'c') || OPT_ISSET(ops, b'p') || OPT_ISSET(ops, b'f');
let ops_au = OPT_ISSET(ops, b'a') || OPT_ISSET(ops, b'u'); let mut ret: i32;
if ops_bcpf && !ops_au {
zwarnnam(nam, "-b, -c, -f, and -p must be combined with -a or -u"); return 1; }
if OPT_ISSET(ops, b'F') && (ops_bcpf || OPT_ISSET(ops, b'u')) {
zwarnnam(nam, "-b, -c, -f, -p and -u cannot be combined with -F"); return 1; }
if OPT_ISSET(ops, b'A') || OPT_ISSET(ops, b'R') {
if ops_bcpf || ops_au || OPT_ISSET(ops, b'd') || (OPT_ISSET(ops, b'R') && OPT_ISSET(ops, b'e'))
{
zwarnnam(nam, "illegal flags combined with -A or -R"); return 1; }
if !OPT_ISSET(ops, b'e') {
return bin_zmodload_alias(table, nam, args, ops); }
}
if OPT_ISSET(ops, b'd') && OPT_ISSET(ops, b'a') {
zwarnnam(nam, "-d cannot be combined with -a"); return 1; }
if OPT_ISSET(ops, b'u') && args.is_empty() {
zwarnnam(nam, "what do you want to unload?"); return 1; }
if OPT_ISSET(ops, b'e')
&& (OPT_ISSET(ops, b'I') || OPT_ISSET(ops, b'L') || (OPT_ISSET(ops, b'a') && !OPT_ISSET(ops, b'F'))
|| OPT_ISSET(ops, b'd') || OPT_ISSET(ops, b'i')
|| OPT_ISSET(ops, b'u'))
{
zwarnnam(nam, "-e cannot be combined with other options"); return 1; }
for fp in [b'l', b'P'] {
if OPT_ISSET(ops, fp) && !OPT_ISSET(ops, b'F') {
zwarnnam(nam, &format!("-{} is only allowed with -F", fp as char)); return 1; }
}
crate::ported::mem::queue_signals(); if OPT_ISSET(ops, b'F') {
ret = bin_zmodload_features(table, nam, args, ops); } else if OPT_ISSET(ops, b'e') {
ret = bin_zmodload_exist(table, nam, args, ops); } else if OPT_ISSET(ops, b'd') {
ret = bin_zmodload_dep(table, nam, args, ops); } else {
let autoopts = (OPT_ISSET(ops, b'b') as i32) + (OPT_ISSET(ops, b'c') as i32)
+ (OPT_ISSET(ops, b'p') as i32)
+ (OPT_ISSET(ops, b'f') as i32);
if autoopts != 0 || OPT_ISSET(ops, b'a') {
if autoopts > 1 {
zwarnnam(nam, "use only one of -b, -c, or -p"); ret = 1; } else {
ret = bin_zmodload_auto(table, nam, args, ops); }
} else {
ret = bin_zmodload_load(table, nam, args, ops); }
}
unqueue_signals(); ret }
pub fn bin_zmodload_alias(
table: &mut modulestab,
nam: &str,
args: &[String],
ops: &options,
) -> i32 {
if args.is_empty() {
if OPT_ISSET(ops, b'R') {
zwarnnam(nam, "no module alias to remove"); return 1; }
let listflags = if OPT_ISSET(ops, b'L') {
PRINTMOD_LIST
} else {
0
};
let mut names: Vec<&String> = table
.modules
.iter()
.filter(|(_, m)| (m.node.flags & MOD_ALIAS) != 0) .map(|(n, _)| n)
.collect();
names.sort(); for name in names {
let m = &table.modules[name];
let line = printmodulenode(name, m, listflags);
if !line.is_empty() {
println!("{}", line);
}
}
return 0; }
for arg in args {
let (lhs, aliasname): (&str, Option<&str>) = match arg.find('=') {
Some(eq) => (&arg[..eq], Some(&arg[eq + 1..])),
None => (arg.as_str(), None),
};
if modname_ok(lhs) == 0 {
zwarnnam(nam, &format!("invalid module name `{}'", lhs)); return 1; }
if OPT_ISSET(ops, b'R') {
if aliasname.is_some() {
zwarnnam(
nam,
&format!("bad syntax for removing module alias: {}", lhs),
); return 1; }
match table.modules.get(lhs) {
Some(m) => {
if (m.node.flags & MOD_ALIAS) == 0 {
zwarnnam(nam, &format!("module is not an alias: {}", lhs)); return 1; }
table.modules.remove(lhs); }
None => {
zwarnnam(nam, &format!("no such module alias: {}", lhs)); return 1; }
}
} else {
if let Some(target) = aliasname {
if modname_ok(target) == 0 {
zwarnnam(nam, &format!("invalid module name `{}'", target)); return 1; }
let mut mname = target;
let mut depth = 0;
loop {
if depth > 256 {
break;
}
depth += 1;
if mname == lhs {
zwarnnam(nam, &format!("module alias would refer to itself: {}", lhs)); return 1; }
match table.modules.get(mname) {
Some(m) if (m.node.flags & MOD_ALIAS) != 0 => {
mname = m.alias.as_deref().unwrap_or("");
}
_ => break,
}
}
if let Some(m) = table.modules.get_mut(lhs) {
if (m.node.flags & MOD_ALIAS) == 0 {
zwarnnam(nam, &format!("module is not an alias: {}", lhs)); return 1; }
m.alias = Some(target.to_string()); } else {
let mut m = module::new(lhs); m.node.flags = MOD_ALIAS; m.alias = Some(target.to_string()); table.modules.insert(lhs.to_string(), m); }
} else {
match table.modules.get(lhs) {
Some(m) if (m.node.flags & MOD_ALIAS) != 0 => {
let listflags = if OPT_ISSET(ops, b'L') {
PRINTMOD_LIST
} else {
0
};
let line = printmodulenode(lhs, m, listflags);
if !line.is_empty() {
println!("{}", line);
}
}
Some(_) => {
zwarnnam(nam, &format!("module is not an alias: {}", lhs)); return 1; }
None => {
zwarnnam(nam, &format!("no such module alias: {}", lhs)); return 1; }
}
}
}
}
0 }
pub fn bin_zmodload_exist(
table: &mut modulestab,
_nam: &str,
args: &[String],
ops: &options,
) -> i32 {
if args.is_empty() {
let printflags = if OPT_ISSET(ops, b'A') {
PRINTMOD_EXIST | PRINTMOD_ALIAS
} else {
PRINTMOD_EXIST
};
let mut names: Vec<&String> = table.modules.keys().collect();
names.sort(); for name in names {
let m = &table.modules[name];
let line = printmodulenode(name, m, printflags);
if !line.is_empty() {
println!("{}", line);
}
}
return 0; }
let mut ret: i32 = 0;
for arg in args {
if ret != 0 {
break;
}
let canon = match find_module(table, arg, FINDMOD_ALIASP) {
Some(n) => n,
None => {
ret = 1; continue;
}
};
let live = match table.modules.get(&canon) {
Some(m) => {
let bound = m.handle.is_some() || m.linked.is_some();
let unloading = (m.node.flags & MOD_UNLOAD) != 0;
bound && !unloading
}
None => false,
};
if !live {
ret = 1; }
}
ret }
pub fn bin_zmodload_dep(table: &mut modulestab, _nam: &str, args: &[String], ops: &options) -> i32 {
if OPT_ISSET(ops, b'u') {
if args.is_empty() {
return 0;
}
let tnam = &args[0];
let rest = &args[1..];
let canon = match find_module(table, tnam, FINDMOD_ALIASP) {
Some(n) => n,
None => return 0, };
if let Some(m) = table.modules.get_mut(&canon) {
if !rest.is_empty() && m.deps.is_some() {
let deps = m.deps.as_mut().unwrap();
for to_remove in rest {
if let Some(pos) = deps.iter().position(|d| d == to_remove) {
deps.delete_node(pos); }
}
if deps.is_empty() {
m.deps = None;
}
} else if m.deps.is_some() {
m.deps = None;
}
let no_deps = m.deps.is_none();
let no_handle = (m.node.flags & MOD_INIT_B) == 0;
if no_deps && no_handle {
table.modules.remove(&canon); }
}
return 0; }
if args.is_empty() || args.len() == 1 {
let depflags = if OPT_ISSET(ops, b'L') {
PRINTMOD_DEPS | PRINTMOD_LIST
} else {
PRINTMOD_DEPS
};
if !args.is_empty() {
if let Some(m) = table.modules.get(&args[0]) {
let line = printmodulenode(&args[0], m, depflags);
if !line.is_empty() {
println!("{}", line);
}
}
} else {
let mut names: Vec<&String> = table.modules.keys().collect();
names.sort(); for name in names {
let m = &table.modules[name];
let line = printmodulenode(name, m, depflags);
if !line.is_empty() {
println!("{}", line);
}
}
}
return 0; }
let target = &args[0];
for dep in &args[1..] {
add_dep(table, target, dep); }
0 }
pub fn printautoparams(name: &str, module: &str, flags: u32, lon: i32) {
if (flags & PM_AUTOLOAD) != 0 {
if lon != 0 {
println!("zmodload -ap {} {}", module, name);
} else {
println!("{} ({})", name, module);
}
}
}
pub fn bin_zmodload_auto(
table: &mut modulestab,
_nam: &str,
args: &[String],
ops: &options,
) -> i32 {
let fchar: char; let _flags: i32 = if OPT_ISSET(ops, b'i') { FEAT_IGNORE } else { 0 };
if OPT_ISSET(ops, b'c') {
fchar = if OPT_ISSET(ops, b'I') { 'C' } else { 'c' };
let _ = fchar;
if args.is_empty() {
let snap: Vec<(String, i32, String)> = {
let tab = CONDTAB.lock().unwrap();
tab.iter()
.filter_map(|p| {
p.module
.as_ref()
.map(|m| (p.name.clone(), p.flags, m.clone()))
})
.collect()
};
let l_flag = OPT_ISSET(ops, b'L');
for (name, flags, module) in snap {
if l_flag {
if (flags & CONDF_INFIX) != 0 {
println!("zmodload -acI {} {}", module, name);
} else {
println!("zmodload -ac {} {}", module, name);
}
} else {
let kind = if (flags & CONDF_INFIX) != 0 {
"infix"
} else {
"post"
};
println!("{} {} ({})", kind, name, module);
}
}
return 0;
}
} else if OPT_ISSET(ops, b'p') {
if args.is_empty() {
let lon = if OPT_ISSET(ops, b'L') { 1 } else { 0 };
let entries: Vec<(String, u32, String)> = {
let tab = crate::ported::params::paramtab()
.read()
.expect("paramtab poisoned");
let mut v: Vec<(String, u32, String)> = tab
.iter()
.filter(|(_, p)| {
(p.node.flags as u32 & crate::ported::zsh_h::PM_AUTOLOAD) != 0
})
.map(|(name, p)| {
(
name.clone(),
p.node.flags as u32,
p.u_str.clone().unwrap_or_default(),
)
})
.collect();
v.sort_by(|a, b| a.0.cmp(&b.0)); v
};
for (name, flags, module) in entries {
printautoparams(&name, &module, flags, lon); }
return 0;
}
} else if OPT_ISSET(ops, b'f') {
if args.is_empty() {
let snap: Vec<(String, String)> = {
let tab = MATHFUNCS.lock().unwrap();
tab.iter()
.filter_map(|p| {
if (p.flags & MFF_USERFUNC) != 0 {
return None;
}
p.module.as_ref().map(|m| (p.name.clone(), m.clone()))
})
.collect()
};
let l_flag = OPT_ISSET(ops, b'L');
for (name, module) in snap {
if l_flag {
println!("zmodload -af {} {}", module, name);
} else {
println!("{} ({})", name, module);
}
}
return 0;
}
} else {
if args.is_empty() {
let mut entries: Vec<(&String, &String)> =
table.autoload_builtins.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (name, module) in entries {
autoloadscan(
name,
module,
0,
if OPT_ISSET(ops, b'L') { PRINT_LIST } else { 0 },
);
}
return 0;
}
}
let fchar: u8 = if OPT_ISSET(ops, b'c') {
if OPT_ISSET(ops, b'I') {
b'C' } else {
b'c'
}
} else if OPT_ISSET(ops, b'p') {
b'p' } else if OPT_ISSET(ops, b'f') {
b'f' } else {
b'b' };
let mut flags = FEAT_AUTOALL; if OPT_ISSET(ops, b'i') {
flags |= FEAT_IGNORE; }
if OPT_ISSET(ops, b'u') {
flags |= FEAT_REMOVE; return autofeatures(table, _nam, None, args, fchar, flags); }
let modnam = &args[0]; let feat_args: &[String] = if args.len() > 1 { &args[1..] } else { args };
autofeatures(table, _nam, Some(modnam), feat_args, fchar, flags) }
pub fn unload_named_module(table: &mut modulestab, name: &str, nam: &str, silent: i32) -> i32 {
let mname = match find_module(table, name, FINDMOD_ALIASP) {
Some(n) => n,
None => {
if silent == 0 {
crate::ported::utils::zwarnnam(nam, &format!("no such module {}", name));
return 1;
}
return 0;
}
};
let mut del = 0;
let candidates: Vec<(String, i32, bool, Vec<String>)> = table
.modules
.iter()
.filter_map(|(other_name, other)| {
let deps = other.deps.as_ref()?;
if (other.node.flags & MOD_LINKED) == 0 {
return None;
}
Some((
other_name.clone(),
other.node.flags,
(other.node.flags & MOD_UNLOAD) != 0,
deps.iter().cloned().collect(),
))
})
.collect();
for (_other_name, _other_flags, other_unloading, other_deps) in candidates {
for dep in &other_deps {
if dep != &mname {
continue; }
if other_unloading {
del = 1;
} else {
crate::ported::utils::zwarnnam(
nam,
&format!(
"module {} is in use by another module and cannot be unloaded",
mname
),
);
return 1;
}
}
}
if del != 0 {
if let Some(m) = table.modules.get_mut(&mname) {
m.wrapper += 1;
}
}
let mut ret = if !table.unload_module(&mname) { 1 } else { 0 };
if del != 0 {
if let Some(m) = table.modules.get_mut(&mname) {
m.wrapper -= 1;
}
}
let _ = silent;
let _ = &mut ret;
ret }
pub fn bin_zmodload_load(table: &mut modulestab, nam: &str, args: &[String], ops: &options) -> i32 {
let mut ret: i32 = 0;
if OPT_ISSET(ops, b'u') {
for arg in args {
if unload_named_module(table, arg, nam, OPT_ISSET(ops, b'i') as i32) != 0 {
ret = 1;
}
}
return ret; } else if args.is_empty() {
let listflags = if OPT_ISSET(ops, b'L') {
PRINTMOD_LIST
} else {
0
};
let mut names: Vec<&String> = table.modules.keys().collect();
names.sort(); for name in names {
let m = &table.modules[name]; if (m.node.flags & (MOD_UNLOAD | MOD_ALIAS)) != 0 {
continue; }
let line = printmodulenode(name, m, listflags);
if !line.is_empty() {
println!("{}", line);
}
}
return 0; } else {
for arg in args {
let tmpret = require_module(table, arg, None, OPT_ISSET(ops, b's') as i32); if tmpret != 0 && ret != 1 {
ret = tmpret;
}
#[cfg(feature = "recorder")]
if crate::recorder::is_enabled() {
let ctx = crate::recorder::recorder_ctx_global();
crate::recorder::emit_zmodload(arg, "", ctx);
}
}
ret
}
}
pub fn bin_zmodload_features(
table: &mut modulestab,
nam: &str,
args: &[String],
ops: &options,
) -> i32 {
let modname = args.first(); let rest_args = if args.is_empty() {
&args[..]
} else {
&args[1..]
};
if modname.is_none() {
if OPT_ISSET(ops, b'L') {
if OPT_ISSET(ops, b'P') {
zwarnnam(nam, "-P is only allowed with a module name"); return 1; }
let mut printflags = PRINTMOD_LIST | PRINTMOD_FEATURES;
if OPT_ISSET(ops, b'l') {
printflags |= PRINTMOD_LISTALL; }
if OPT_ISSET(ops, b'a') {
printflags |= PRINTMOD_AUTO; }
let mut names: Vec<String> = table
.modules
.iter()
.filter(|(_, m)| (m.node.flags & MOD_ALIAS) == 0) .map(|(n, _)| n.clone())
.collect();
names.sort(); for name in &names {
if printflags & PRINTMOD_AUTO != 0 {
let m = &table.modules[name];
let line = printmodulenode(name, m, printflags);
if !line.is_empty() {
println!("{}", line);
}
continue;
}
let loaded = {
let m = &table.modules[name];
(m.node.flags & MOD_INIT_B) != 0 && (m.node.flags & MOD_UNLOAD) == 0
};
if !loaded {
continue;
}
let mut features: Vec<String> = Vec::new();
if features_module(table, name, &mut features) != 0 || features.is_empty() {
continue; }
let mut enables_opt: Option<Vec<i32>> = None;
if enables_module(table, name, &mut enables_opt) != 0 {
continue; }
let enables = enables_opt.unwrap_or_else(|| vec![0; features.len()]);
let mut line = String::from("zmodload -F ");
if name.starts_with('-') {
line.push_str("-- "); }
line.push_str(&crate::ported::utils::quotedzputs(name)); for (f, on) in features.iter().zip(enables.iter()) {
if printflags & PRINTMOD_LISTALL != 0 {
line.push_str(if *on != 0 { " +" } else { " -" }); } else if *on == 0 {
continue; } else {
line.push(' '); }
line.push_str(&crate::ported::utils::quotedzputs(f)); }
println!("{}", line);
}
return 0; }
zwarnnam(nam, "-F requires a module name"); return 1; }
let modname = modname.unwrap();
if OPT_ISSET(ops, b'l') || OPT_ISSET(ops, b'L') || OPT_ISSET(ops, b'e') {
let param: Option<String> = OPT_ARG_SAFE(ops, b'P').map(|s| s.to_string()); let resolved = find_module(table, modname, FINDMOD_ALIASP);
if OPT_ISSET(ops, b'a') {
let autoloads: Vec<String> = match resolved
.as_ref()
.and_then(|r| table.modules.get(r))
.and_then(|m| m.autoloads.as_ref())
{
Some(al) => al.iter().cloned().collect(),
None => return 1,
};
if OPT_ISSET(ops, b'e') {
for fstr in rest_args {
let (sense, name) = match fstr.strip_prefix('+') {
Some(rest) => (true, rest), None => match fstr.strip_prefix('-') {
Some(rest) => (false, rest), None => (true, fstr.as_str()),
},
};
if autoloads.iter().any(|a| a == name) != sense {
return 1; }
}
return 0; }
if let Some(p) = param {
if crate::ported::params::setaparam(&p, autoloads).is_none() {
return 1; }
return 0; }
if OPT_ISSET(ops, b'L') {
let rname = resolved.as_deref().unwrap_or(modname);
print!(
"zmodload -aF {}{}",
crate::ported::utils::quotedzputs(rname),
if autoloads.is_empty() { '\n' } else { ' ' }
);
for (i, al) in autoloads.iter().enumerate() {
print!(
"{}{}",
al,
if i + 1 < autoloads.len() { ' ' } else { '\n' }
);
}
} else {
for al in &autoloads {
println!("{}", al);
}
}
return 0; }
let loaded = resolved
.as_ref()
.and_then(|r| table.modules.get(r))
.map(|m| (m.node.flags & MOD_INIT_B) != 0 && (m.node.flags & MOD_UNLOAD) == 0)
.unwrap_or(false);
if !loaded {
if !OPT_ISSET(ops, b'e') {
zwarnnam(nam, &format!("module `{}' is not yet loaded", modname)); }
return 1; }
let rname = resolved.unwrap();
let mut features: Vec<String> = Vec::new();
if features_module(table, &rname, &mut features) != 0 {
if !OPT_ISSET(ops, b'e') {
zwarnnam(
nam,
&format!("module `{}' does not support features", rname), );
}
return 1; }
let mut enables_opt: Option<Vec<i32>> = None;
if enables_module(table, &rname, &mut enables_opt) != 0 {
zwarnnam(
nam,
&format!("error getting enabled features for module `{}'", rname), );
return 1; }
let enables: Vec<i32> = enables_opt.unwrap_or_else(|| vec![0; features.len()]);
for raw in rest_args {
let (on, arg): (i32, &str) = match raw.strip_prefix('-') {
Some(rest) => (0, rest),
None => match raw.strip_prefix('+') {
Some(rest) => (1, rest),
None => (-1, raw.as_str()),
},
};
let mut found = 0;
for (fp, ep) in features.iter().zip(enables.iter()) {
if arg == fp {
if OPT_ISSET(ops, b'e') && on != -1 && on != (ep & 1) {
return 1; }
found += 1;
break; }
}
if found == 0 {
if !OPT_ISSET(ops, b'e') {
zwarnnam(
nam,
&format!("module `{}' has no such feature: `{}'", modname, raw),
);
}
return 1; }
}
if OPT_ISSET(ops, b'e') {
return 0; }
let opt_big_l = OPT_ISSET(ops, b'L');
let opt_small_l = OPT_ISSET(ops, b'l');
let matches_stripped = |f: &str| -> bool {
rest_args.is_empty()
|| rest_args.iter().any(|raw| {
let arg = raw
.strip_prefix('+')
.or_else(|| raw.strip_prefix('-'))
.unwrap_or(raw);
f == arg
})
};
let matches_unstripped =
|f: &str| -> bool { rest_args.is_empty() || rest_args.iter().any(|raw| f == raw) };
let mut arrset: Option<Vec<String>> = None;
if param.is_some() {
arrset = Some(Vec::new());
} else if opt_big_l {
print!("zmodload -F {} ", crate::ported::utils::quotedzputs(&rname));
}
for (i, (f, ep)) in features.iter().zip(enables.iter()).enumerate() {
if param.is_some() {
if !matches_stripped(f) {
continue; }
} else if !matches_unstripped(f) {
continue; }
let onoff: &str = if opt_big_l && !opt_small_l {
if *ep == 0 {
continue; }
""
} else if *ep != 0 {
"+" } else {
"-" };
if let Some(ref mut arr) = arrset {
arr.push(format!("{}{}", onoff, f)); } else {
let term = if opt_big_l && i + 1 < features.len() {
' '
} else {
'\n'
};
print!(
"{}{}{}",
onoff,
crate::ported::utils::quotedzputs(f),
term
);
}
}
if let (Some(p), Some(arr)) = (param, arrset) {
if crate::ported::params::setaparam(&p, arr).is_none() {
return 1; }
}
return 0; }
if OPT_ISSET(ops, b'P') && !(OPT_ISSET(ops, b'l') || OPT_ISSET(ops, b'L') || OPT_ISSET(ops, b'e')) {
zwarnnam(nam, "-P can only be used with -l or -L"); return 1; }
if OPT_ISSET(ops, b'a') {
if OPT_ISSET(ops, b'm') {
zwarnnam(nam, "-m cannot be used with -a"); return 1; }
return autofeatures(table, nam, Some(modname), rest_args, 0, FEAT_IGNORE);
}
let feats: Vec<String> = rest_args.to_vec();
let features_arg = if feats.is_empty() {
None
} else {
Some(feats.as_slice())
};
require_module(table, modname, features_arg, OPT_ISSET(ops, b's') as i32) }
pub fn ensurefeature(
table: &mut modulestab,
modname: &str,
prefix: &str,
feature: Option<&str>,
) -> i32 {
match feature {
None => require_module(table, modname, None, 0),
Some(f) => {
let combined = crate::ported::string::dyncat(prefix, f); let arr = vec![combined];
require_module(table, modname, Some(&arr), 0) }
}
}
pub fn resolvebuiltin(name: &str) -> Option<i32> {
let mut tab = MODULESTAB.lock().ok()?;
let module = tab.autoload_builtins.get(name)?.clone();
tab.autoload_builtins.remove(name);
let _ = ensurefeature(&mut tab, &module, "b:", Some(name));
let defined = tab.is_loaded(&module)
&& crate::ported::builtin::createbuiltintable().contains_key(name);
if defined {
return Some(0); }
if tab.is_loaded(&module) {
crate::ported::builtin::LASTVAL
.store(1, std::sync::atomic::Ordering::Relaxed); crate::ported::utils::zerr(&format!(
"autoloading module {} failed to define builtin: {}",
module, name
)); }
Some(1)
}
pub fn autofeatures(
table: &mut modulestab,
cmdnam: &str,
module: Option<&str>,
features: &[String],
prefchar: u8,
defflags: i32,
) -> i32 {
let mut ret: i32 = 0;
let mut modfeatures: Option<Vec<String>> = None;
let mut modenables: Vec<i32> = Vec::new();
let defm_name: Option<String> = match module {
Some(modn) => {
let resolved = find_module(table, modn, FINDMOD_ALIASP | FINDMOD_CREATE);
if let Some(ref r) = resolved {
let booted = table
.modules
.get(r)
.map(|m| (m.node.flags & MOD_INIT_B) != 0)
.unwrap_or(false);
if booted {
let mut f: Vec<String> = Vec::new();
if features_module(table, r, &mut f) == 0 {
let mut e: Option<Vec<i32>> = None;
let _ = enables_module(table, r, &mut e);
modenables = e.unwrap_or_else(|| vec![0; f.len()]);
modfeatures = Some(f);
}
}
}
resolved
}
None => None, };
for feature in features {
let s = feature.as_str();
let mut add: bool = true; let mut flags = defflags;
let prefixed: String;
let (fchar, fnam, feature_full): (u8, &str, &str) = if prefchar != 0 {
prefixed = format!("{}:{}", prefchar as char, s); (prefchar, s, prefixed.as_str()) } else {
let mut t = s;
if let Some(rest) = t.strip_prefix('-') {
add = false;
t = rest;
} else if let Some(rest) = t.strip_prefix('+') {
t = rest;
}
let bytes = t.as_bytes();
if bytes.is_empty() || bytes.len() < 2 || bytes[1] != b':' {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("bad format for autoloadable feature: `{}'", t),
);
ret = 1; continue; }
(bytes[0], &t[2..], t)
};
if (flags & FEAT_REMOVE) != 0 {
add = false;
}
let typnam: &str; let _ = typnam; let typnam = match fchar {
b'b' => "builtin", b'c' | b'C' => {
if fchar == b'C' {
flags |= FEAT_INFIX; }
"condition" }
b'f' => "math function", b'p' => "parameter", _ => {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("bad autoloadable feature type: `{}'", fchar as char),
);
ret = 1; continue; }
};
if fnam.contains('/') {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("{}: `/' is illegal in a {}", fnam, typnam),
);
ret = 1;
continue;
}
let modname_owned: String = match module {
Some(m) => m.to_string(), None => {
let map = match fchar {
b'b' => &table.autoload_builtins,
b'c' | b'C' => &table.autoload_conditions,
b'p' => &table.autoload_params,
b'f' => &table.autoload_mathfuncs,
_ => unreachable!(),
};
match map.get(fnam).cloned() {
Some(m) => m,
None => {
if (flags & FEAT_IGNORE) == 0 {
ret = 1;
crate::ported::utils::zwarnnam(
cmdnam,
&format!("{}: no such {}", fnam, typnam),
);
}
continue; }
}
}
};
let modname = modname_owned.as_str();
let mut autoload_subret: i32 = 0;
let owner: &str = match (module.is_some(), defm_name.as_deref()) {
(true, Some(r)) => r,
_ => modname,
};
if add {
if module.is_some() {
if let Some(ref mf) = modfeatures {
match mf.iter().position(|f| f == feature_full) {
None => {
crate::ported::utils::zwarnnam(
cmdnam,
&format!(
"module `{}' has no such feature: `{}'",
owner, feature_full
),
);
ret = 1;
continue;
}
Some(idx) => {
if modenables.get(idx).copied().unwrap_or(0) != 0 {
continue; }
}
}
}
}
if let Some(m) = table.modules.get_mut(owner) {
let list = m
.autoloads
.get_or_insert_with(crate::ported::linklist::znewlinklist);
let mut insert_at: Option<usize> = Some(list.len()); for (i, existing) in list.iter().enumerate() {
match feature_full.cmp(existing.as_str()) {
std::cmp::Ordering::Equal => {
insert_at = None; break;
}
std::cmp::Ordering::Less => {
insert_at = Some(i); break;
}
std::cmp::Ordering::Greater => {}
}
}
if let Some(i) = insert_at {
list.insert_at(i, feature_full.to_string());
}
}
} else {
let removed = table
.modules
.get_mut(owner)
.and_then(|m| m.autoloads.as_mut())
.map(|list| {
match list
.iter()
.position(|existing| existing.as_str() == feature_full)
{
Some(i) => {
list.delete_node(i);
true
}
None => false,
}
})
.unwrap_or(false);
if !removed {
autoload_subret = if (flags & FEAT_IGNORE) != 0 { -2 } else { 2 }; }
}
if add {
match fchar {
b'b' => {
table
.autoload_builtins
.insert(fnam.to_string(), modname.to_string());
}
b'c' | b'C' => {
table
.autoload_conditions
.insert(fnam.to_string(), modname.to_string());
}
b'p' => {
table
.autoload_params
.insert(fnam.to_string(), modname.to_string());
}
b'f' => {
table
.autoload_mathfuncs
.insert(fnam.to_string(), modname.to_string());
}
_ => unreachable!(),
}
} else {
match fchar {
b'b' => {
table.autoload_builtins.remove(fnam);
}
b'c' | b'C' => {
table.autoload_conditions.remove(fnam);
}
b'p' => {
table.autoload_params.remove(fnam);
}
b'f' => {
table.autoload_mathfuncs.remove(fnam);
}
_ => unreachable!(),
}
}
let subret = if autoload_subret != 0 {
autoload_subret } else if add {
match fchar {
b'p' => add_autoparam(modname, fnam, flags),
b'f' => add_automathfunc(table, modname, fnam, flags),
b'b' => table.add_autobin(fnam, modname, flags),
b'c' | b'C' => table.add_autocond(fnam, modname, flags),
_ => unreachable!(),
}
} else {
match fchar {
b'p' => del_autoparam(modname, fnam, flags),
b'f' => del_automathfunc(table, modname, fnam, flags),
b'b' => table.del_autobin(fnam, flags),
b'c' | b'C' => table.del_autocond(fnam, flags),
_ => unreachable!(),
}
};
if subret != 0 && subret != -2 {
ret = 1; match subret {
1 => {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("failed to add {} `{}'", typnam, fnam),
);
}
2 => {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("{}: no such {}", fnam, typnam),
);
}
3 => {
crate::ported::utils::zwarnnam(
cmdnam,
&format!("{}: {} is already defined", fnam, typnam),
);
}
_ => { }
}
}
}
ret
}
pub static MATHFUNCS: Lazy<Mutex<Vec<mathfunc>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub fn setconddefs(
nam: &str, c: &mut [conddef],
e: Option<&[i32]>,
) -> i32 {
let mut ret = 0; for (i, entry) in c.iter_mut().enumerate() {
let want_add = e.map(|es| es[i] != 0).unwrap_or(true); if want_add {
if (entry.flags & CONDF_ADDED) != 0 {
continue;
} let dup = conddef {
next: None,
name: entry.name.clone(),
flags: entry.flags,
handler: entry.handler,
min: entry.min,
max: entry.max,
condid: entry.condid,
module: entry.module.clone(),
};
if addconddef(dup) != 0 {
zwarnnam(
nam, &format!("name clash when adding condition `{}'", entry.name),
);
ret = 1;
} else {
entry.flags |= CONDF_ADDED; }
} else {
if (entry.flags & CONDF_ADDED) == 0 {
continue;
} if deleteconddef(entry) != 0 {
zwarnnam(
nam, &format!("condition `{}' already deleted", entry.name),
);
ret = 1;
} else {
entry.flags &= !CONDF_ADDED; }
}
}
ret }
pub fn setmathfuncs(
nam: &str, f: &mut [mathfunc],
e: Option<&[i32]>,
) -> i32 {
let mut ret = 0; for (i, entry) in f.iter_mut().enumerate() {
let want_add = e.map(|es| es[i] != 0).unwrap_or(false); if want_add {
if (entry.flags & MFF_ADDED) != 0 {
continue;
} let dup = mathfunc {
next: None,
name: entry.name.clone(),
flags: entry.flags,
nfunc: entry.nfunc,
sfunc: entry.sfunc,
module: entry.module.clone(),
minargs: entry.minargs,
maxargs: entry.maxargs,
funcid: entry.funcid,
};
if addmathfunc(dup) != 0 {
zwarnnam(
nam, &format!("name clash when adding math function `{}'", entry.name),
);
ret = 1;
} else {
entry.flags |= MFF_ADDED; if let Ok(mut gtab) = MATHFUNCS.lock() {
if let Some(p) = gtab.iter_mut().find(|p| p.name == entry.name) {
p.flags |= MFF_ADDED;
}
}
}
} else {
if (entry.flags & MFF_ADDED) == 0 {
continue;
} if deletemathfunc(entry) != 0 {
zwarnnam(
nam, &format!("math function `{}' already deleted", entry.name),
);
ret = 1;
} else {
entry.flags &= !MFF_ADDED;
}
}
}
ret }
pub static CONDTAB: Lazy<Mutex<Vec<conddef>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub fn getconddef(inf: i32, name: &str, autol: i32, table: &mut modulestab) -> Option<conddef> {
let lookup: String = crate::ported::string::dupstring(name)
.chars()
.map(|c| if c == crate::ported::zsh_h::Dash { '-' } else { c })
.collect();
let mut f = 1; loop {
let want_infix = inf != 0;
let hit: Option<conddef> = {
let tab = CONDTAB.lock().unwrap();
tab.iter().find_map(|p| {
let p_infix = (p.flags & CONDF_INFIX) != 0;
if p_infix == want_infix && p.name == lookup {
Some(conddef {
next: None,
name: p.name.clone(),
flags: p.flags,
handler: p.handler,
min: p.min,
max: p.max,
condid: p.condid,
module: p.module.clone(),
})
} else {
None
}
})
};
let has_autoload_module = hit
.as_ref()
.map(|p| p.module.is_some())
.unwrap_or(false);
if autol != 0 && hit.is_some() && has_autoload_module {
let p = hit.as_ref().unwrap();
if f != 0 {
let module = p.module.as_ref().unwrap().clone();
let prefix = if (p.flags & CONDF_INFIX) != 0 {
"C:"
} else {
"c:"
};
let feature_arg = if (p.flags & CONDF_AUTOALL) != 0 {
None
} else {
Some(lookup.as_str())
};
let _ = ensurefeature(table, &module, prefix, feature_arg);
f = 0;
continue; } else {
let _ = deleteconddef(p);
return None;
}
}
return hit; }
}
pub fn deleteconddef(c: &conddef) -> i32 {
let mut tab = CONDTAB.lock().unwrap();
let infix = c.flags & CONDF_INFIX;
match tab
.iter()
.position(|p| p.name == c.name && (p.flags & CONDF_INFIX) == infix)
{
Some(i) => {
tab.remove(i);
0
} None => -1, }
}
pub fn addconddef(c: conddef) -> i32 {
let infix = c.flags & CONDF_INFIX;
let clash_idx = {
let tab = CONDTAB.lock().unwrap();
tab.iter()
.position(|p| p.name == c.name && (p.flags & CONDF_INFIX) == infix) };
if let Some(i) = clash_idx {
let (autoload, added) = {
let tab = CONDTAB.lock().unwrap();
(tab[i].module.is_some(), (tab[i].flags & CONDF_ADDED) != 0)
};
if !autoload || added {
return 1;
} CONDTAB.lock().unwrap().remove(i); }
CONDTAB.lock().unwrap().insert(0, c); 0
}
pub static WRAPPERS: Lazy<Mutex<Vec<funcwrap>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub fn addmathfunc(f: mathfunc) -> i32 {
if (f.flags & MFF_ADDED) != 0 {
return 1;
} let mut tab = MATHFUNCS.lock().unwrap();
let mut found_idx: Option<usize> = None;
for (i, p) in tab.iter().enumerate() {
if p.name == f.name {
if p.module.is_some() && (p.flags & MFF_USERFUNC) == 0 {
found_idx = Some(i); break;
}
return 1; }
}
if let Some(i) = found_idx {
tab.remove(i);
} tab.insert(0, f); 0
}
pub fn removemathfunc(name: &str) {
let mut tab = MATHFUNCS.lock().unwrap();
if let Some(i) = tab.iter().position(|m| m.name == name) {
tab.remove(i); }
}
pub fn deletemathfunc(f: &mathfunc) -> i32 {
let mut tab = MATHFUNCS.lock().unwrap();
match tab.iter().position(|m| m.name == f.name) {
Some(i) => {
tab.remove(i); 0 }
None => -1, }
}
pub fn addwrapper(table: &modulestab, modname: &str, w: funcwrap) -> i32 {
if let Some(m) = table.modules.get(modname) {
if (m.node.flags & MOD_ALIAS) != 0 {
return 1;
}
} else {
return 1;
}
if (w.flags & crate::ported::zsh_h::WRAPF_ADDED) != 0 {
return 1;
}
let mut tab = WRAPPERS.lock().unwrap();
if tab.iter().any(|x| match (x.handler, w.handler) {
(Some(a), Some(b)) => std::ptr::fn_addr_eq(a, b),
(None, None) => true,
_ => false,
}) {
return 1;
}
let mut entry = w;
entry.flags |= crate::ported::zsh_h::WRAPF_ADDED; tab.push(entry); 0 }
pub fn deletewrapper(table: &modulestab, modname: &str, w: &funcwrap) -> i32 {
if let Some(m) = table.modules.get(modname) {
if (m.node.flags & MOD_ALIAS) != 0 {
return 1;
}
} else {
return 1;
}
if (w.flags & crate::ported::zsh_h::WRAPF_ADDED) == 0 {
return 1;
}
let mut tab = WRAPPERS.lock().unwrap();
match tab.iter().position(|x| match (x.handler, w.handler) {
(Some(a), Some(b)) => std::ptr::fn_addr_eq(a, b),
(None, None) => true,
_ => false,
}) {
Some(i) => {
tab.remove(i);
0
}
None => 1, }
}
pub fn featuresarray(
_m: *const module,
bn: &[builtin], cd: &[conddef], mf: &[mathfunc], pd: &[paramdef], n_abstract: i32, ) -> Vec<String> {
let features_size = bn.len() + cd.len() + mf.len() + pd.len() + n_abstract.max(0) as usize;
let mut features: Vec<String> = Vec::with_capacity(features_size + 1); for b in bn {
features.push(format!("b:{}", b.node.nam)); }
for c in cd {
let prefix = if (c.flags & CONDF_INFIX) != 0 {
"C:"
} else {
"c:"
}; features.push(format!("{}{}", prefix, c.name)); }
for m in mf {
features.push(format!("f:{}", m.name)); }
for p in pd {
features.push(format!("p:{}", p.name)); }
features
}
pub fn getfeatureenables(
_m: *const module,
bn: &[builtin], cd: &[conddef], mf: &[mathfunc], pd: &[paramdef], n_abstract: i32, ) -> Vec<i32> {
let features_size = bn.len() + cd.len() + mf.len() + pd.len() + n_abstract.max(0) as usize;
let mut enables: Vec<i32> = Vec::with_capacity(features_size); for b in bn {
enables.push(if (b.node.flags & BINF_ADDED as i32) != 0 {
1
} else {
0
});
}
for c in cd {
enables.push(if (c.flags & CONDF_ADDED) != 0 { 1 } else { 0 });
}
for m in mf {
enables.push(if (m.flags & MFF_ADDED) != 0 { 1 } else { 0 });
}
for p in pd {
enables.push(if p.pm.is_some() { 1 } else { 0 });
}
for _ in 0..n_abstract.max(0) {
enables.push(0);
}
enables }
pub static hooktab: std::sync::atomic::AtomicPtr<hookdef> = std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
pub static MODULESTAB: Lazy<Mutex<modulestab>> = Lazy::new(|| Mutex::new(modulestab::new()));
pub const FEATURE_TYPE_BUILTIN: i32 = 0;
pub const FEATURE_TYPE_CONDITION: i32 = 1;
pub const FEATURE_TYPE_PARAMETER: i32 = 2;
pub const FEATURE_TYPE_MATHFUNC: i32 = 3;
pub const FEATURE_TYPE_HOOK: i32 = 4;
#[derive(Debug, Default)]
pub struct modulestab {
pub modules: HashMap<String, module>,
pub autoload_builtins: HashMap<String, String>,
pub autoload_conditions: HashMap<String, String>,
pub autoload_params: HashMap<String, String>,
pub autoload_mathfuncs: HashMap<String, String>,
pub added_builtins: HashMap<String, u32>,
}
pub const FEAT_IGNORE: i32 = 0x0001;
pub const FEAT_INFIX: i32 = 0x0002;
pub const FEAT_AUTOALL: i32 = 0x0004;
pub const FEAT_REMOVE: i32 = 0x0008;
pub const FEAT_CHECKAUTO: i32 = 0x0010;
pub const FINDMOD_ALIASP: i32 = 0x0001;
pub const FINDMOD_CREATE: i32 = 0x0002;
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::zsh_h::hashnode;
#[test]
fn test_module_table_new() {
let _g = crate::test_util::global_state_lock();
let table = modulestab::new();
assert!(table.is_loaded("zsh/complete"));
assert!(!table.is_loaded("zsh/datetime"));
assert!(!table.is_loaded("zsh/system"));
assert!(!table.is_loaded("nonexistent"));
}
#[test]
fn test_load_unload() {
let _g = crate::test_util::global_state_lock();
let mut table = modulestab::new();
assert!(table.is_loaded("zsh/complete"));
table.unload_module("zsh/complete");
assert!(!table.is_loaded("zsh/complete"));
table.load_module("zsh/complete");
assert!(table.is_loaded("zsh/complete"));
}
#[test]
fn test_list_loaded() {
let _g = crate::test_util::global_state_lock();
let table = modulestab::new();
let loaded = table.list_loaded();
assert!(
loaded.len() >= 10,
"expected >= 10 default-loaded modules, got {}",
loaded.len()
);
assert!(loaded.contains(&"zsh/complete"));
}
#[test]
fn test_autoload() {
let _g = crate::test_util::global_state_lock();
let mut table = modulestab::new();
table.add_autobin("my_cmd", "zsh/mymodule", 0);
assert_eq!(
table.resolve_autoload_builtin("my_cmd"),
Some("zsh/mymodule")
);
assert_eq!(table.resolve_autoload_builtin("nonexistent"), None);
}
#[test]
fn test_features() {
let _g = crate::test_util::global_state_lock();
let table = modulestab::new();
let features = table.list_features("zsh/complete");
assert!(features.is_empty());
assert!(table.module_linked("zsh/complete"));
}
#[test]
fn test_module_linked() {
let _g = crate::test_util::global_state_lock();
let table = modulestab::new();
assert!(table.module_linked("zsh/complete"));
assert!(table.module_linked("zsh/stat"));
assert!(!table.module_linked("zsh/nonexistent"));
}
#[test]
fn test_printmodulenode() {
let _g = crate::test_util::global_state_lock();
let mut module = module::new("zsh/test");
module.node.flags |= MOD_INIT_B;
let output = printmodulenode("zsh/test", &module, 0);
assert_eq!(output, "zsh/test");
let listed = printmodulenode("zsh/test", &module, PRINTMOD_LIST);
assert_eq!(listed, "zmodload zsh/test");
let unloaded = module::new("zsh/unloaded");
let nope = printmodulenode("zsh/unloaded", &unloaded, 0);
assert_eq!(nope, "");
}
fn mk_mf(name: &str, autoload: bool) -> mathfunc {
mathfunc {
next: None,
name: name.to_string(),
flags: 0,
nfunc: None,
sfunc: None,
module: if autoload {
Some("zsh/test".to_string())
} else {
None
},
minargs: 0,
maxargs: 0,
funcid: 0,
}
}
#[test]
fn addmathfunc_clash_returns_one_when_already_added() {
let _g = crate::test_util::global_state_lock();
let f1 = mk_mf("zshrs_test_clash_a", false);
let f2 = mk_mf("zshrs_test_clash_a", false);
assert_eq!(addmathfunc(f1), 0);
assert_eq!(addmathfunc(f2), 1, "second add should clash");
removemathfunc("zshrs_test_clash_a");
}
#[test]
fn addmathfunc_autoload_replace_succeeds() {
let _g = crate::test_util::global_state_lock();
let auto = mk_mf("zshrs_test_replace", true);
let real = mk_mf("zshrs_test_replace", false);
assert_eq!(addmathfunc(auto), 0);
assert_eq!(
addmathfunc(real),
0,
"autoloadable entry must be replaceable"
);
let tab = MATHFUNCS.lock().unwrap();
let entry = tab.iter().find(|m| m.name == "zshrs_test_replace").unwrap();
assert!(
entry.module.is_none(),
"after replace, module should be None"
);
drop(tab);
removemathfunc("zshrs_test_replace");
}
#[test]
fn removemathfunc_returns_unit_and_drops() {
let _g = crate::test_util::global_state_lock();
let f = mk_mf("zshrs_test_remove", false);
assert_eq!(addmathfunc(f), 0);
assert!(MATHFUNCS
.lock()
.unwrap()
.iter()
.any(|m| m.name == "zshrs_test_remove"));
removemathfunc("zshrs_test_remove");
assert!(!MATHFUNCS
.lock()
.unwrap()
.iter()
.any(|m| m.name == "zshrs_test_remove"));
}
#[test]
fn deletemathfunc_returns_minus_one_on_miss() {
let _g = crate::test_util::global_state_lock();
let probe = mk_mf("zshrs_test_never_added_xyz", false);
assert_eq!(deletemathfunc(&probe), -1);
}
#[test]
fn deletemathfunc_clears_added_flag_for_userfunc() {
let _g = crate::test_util::global_state_lock();
let mut f = mk_mf("zshrs_test_clear_flag", false);
f.flags = MFF_ADDED;
assert_eq!(
addmathfunc(f),
1,
"MFF_ADDED set → addmathfunc clashes at c:1318"
);
MATHFUNCS
.lock()
.unwrap()
.insert(0, mk_mf("zshrs_test_clear_flag2", false));
let mut f2 = mk_mf("zshrs_test_clear_flag2", false);
f2.flags = MFF_ADDED;
assert_eq!(deletemathfunc(&f2), 0);
let tab = MATHFUNCS.lock().unwrap();
let entry = tab.iter().find(|m| m.name == "zshrs_test_clear_flag2");
if let Some(e) = entry {
assert_eq!(e.flags & MFF_ADDED, 0);
}
drop(tab);
removemathfunc("zshrs_test_clear_flag2");
}
fn mk_cd(name: &str, infix: bool, autoload: bool) -> conddef {
conddef {
next: None,
name: name.to_string(),
flags: if infix { CONDF_INFIX } else { 0 },
handler: None,
min: 0,
max: 0,
condid: 0,
module: if autoload {
Some("zsh/cond".to_string())
} else {
None
},
}
}
#[test]
fn addconddef_clash_returns_one() {
let _g = crate::test_util::global_state_lock();
let c1 = mk_cd("zshrs_test_cond_clash", false, false);
let c2 = mk_cd("zshrs_test_cond_clash", false, false);
assert_eq!(addconddef(c1), 0);
assert_eq!(addconddef(c2), 1);
let probe = mk_cd("zshrs_test_cond_clash", false, false);
assert_eq!(deleteconddef(&probe), 0);
}
#[test]
fn deleteconddef_returns_minus_one_on_miss() {
let _g = crate::test_util::global_state_lock();
let probe = mk_cd("zshrs_test_cond_never_added", false, false);
assert_eq!(deleteconddef(&probe), -1);
}
#[test]
fn addconddef_distinguishes_infix_from_prefix() {
let _g = crate::test_util::global_state_lock();
let prefix = mk_cd("zshrs_test_cond_dual", false, false);
let infix = mk_cd("zshrs_test_cond_dual", true, false);
assert_eq!(addconddef(prefix), 0);
assert_eq!(
addconddef(infix),
0,
"infix variant must not clash with prefix variant"
);
let _ = deleteconddef(&mk_cd("zshrs_test_cond_dual", false, false));
let _ = deleteconddef(&mk_cd("zshrs_test_cond_dual", true, false));
}
#[test]
fn setconddefs_bulk_add_then_bulk_delete_via_e_array() {
let _g = crate::test_util::global_state_lock();
let mut entries = vec![
mk_cd("zshrs_test_bulk_a", false, false),
mk_cd("zshrs_test_bulk_b", false, false),
];
let add_selectors = [1, 1];
assert_eq!(setconddefs("test", &mut entries, Some(&add_selectors)), 0);
assert_ne!(entries[0].flags & CONDF_ADDED, 0);
assert_ne!(entries[1].flags & CONDF_ADDED, 0);
let del_selectors = [0, 0];
assert_eq!(setconddefs("test", &mut entries, Some(&del_selectors)), 0);
assert_eq!(entries[0].flags & CONDF_ADDED, 0);
assert_eq!(entries[1].flags & CONDF_ADDED, 0);
}
fn mk_b(nam: &str) -> builtin {
builtin {
node: hashnode {
next: None,
nam: nam.to_string(),
flags: 0,
},
handlerfunc: None,
minargs: 0,
maxargs: 0,
funcid: 0,
optstr: None,
defopts: None,
}
}
#[test]
fn addbuiltin_clash_against_existing_builtintab_entry() {
let _g = crate::test_util::global_state_lock();
let _ = createbuiltintable();
let mut b = mk_b("echo");
let r = addbuiltin(&mut b);
assert!(r == 0 || r == 1);
if r == 0 {
assert_ne!(b.node.flags & BINF_ADDED as i32, 0);
}
}
#[test]
fn addbuiltins_skips_already_added_entries() {
let _g = crate::test_util::global_state_lock();
let mut b1 = mk_b("zshrs_test_already_added_1");
b1.node.flags = BINF_ADDED as i32;
let mut b2 = mk_b("zshrs_test_already_added_2");
b2.node.flags = BINF_ADDED as i32;
let mut binl = vec![b1, b2];
assert_eq!(addbuiltins("test", &mut binl), 0);
}
fn mk_w() -> funcwrap {
funcwrap {
next: None,
flags: 0,
handler: Some(|_prog, _w, _name| 0),
module: None,
}
}
#[test]
fn addwrapper_then_deletewrapper_round_trip() {
let _g = crate::test_util::global_state_lock();
let mut table = modulestab::new();
table
.modules
.insert("zsh/test".to_string(), module::new("zsh/test"));
let w = mk_w();
assert_eq!(addwrapper(&table, "zsh/test", w), 0);
let mut probe = mk_w();
probe.flags |= crate::ported::zsh_h::WRAPF_ADDED;
let r = deletewrapper(&table, "zsh/test", &probe);
assert!(r == 0 || r == 1);
}
#[test]
fn deletewrapper_returns_one_when_not_found() {
let _g = crate::test_util::global_state_lock();
let mut table = modulestab::new();
table
.modules
.insert("zsh/test".to_string(), module::new("zsh/test"));
let snapshot: Vec<_> = WRAPPERS.lock().unwrap().drain(..).collect();
let mut probe = mk_w();
probe.flags |= crate::ported::zsh_h::WRAPF_ADDED;
assert_eq!(deletewrapper(&table, "zsh/test", &probe), 1);
WRAPPERS.lock().unwrap().extend(snapshot);
}
static H1_CALLS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
static H2_CALLS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
static H1_RETVAL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
fn h1(_h: *mut hookdef, _d: *mut std::ffi::c_void) -> i32 {
H1_CALLS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
H1_RETVAL.load(std::sync::atomic::Ordering::SeqCst)
}
fn h2(_h: *mut hookdef, _d: *mut std::ffi::c_void) -> i32 {
H2_CALLS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
0
}
fn leak_hookdef(name: &str, flags: i32) -> *mut hookdef {
Box::into_raw(Box::new(hookdef {
next: std::ptr::null_mut(),
name: name.to_string(),
def: None,
flags,
funcs: std::ptr::null_mut(),
}))
}
#[test]
fn gethookdef_returns_null_then_registered_ptr() {
let _g = crate::test_util::global_state_lock();
let h = leak_hookdef("zshrs_test_get_hook", HOOKF_ALL);
unsafe {
assert!(gethookdef(&(*h).name).is_null());
}
assert_eq!(addhookdef(h), 0);
unsafe {
assert_eq!(gethookdef(&(*h).name), h);
}
assert_eq!(deletehookdef(h), 0);
unsafe {
drop(Box::from_raw(h));
}
}
#[test]
fn addhookdef_rejects_duplicate_name() {
let _g = crate::test_util::global_state_lock();
let h1 = leak_hookdef("zshrs_test_dup_hook", HOOKF_ALL);
let h2 = leak_hookdef("zshrs_test_dup_hook", HOOKF_ALL);
assert_eq!(addhookdef(h1), 0);
assert_eq!(addhookdef(h2), 1, "duplicate name must return 1");
assert_eq!(deletehookdef(h1), 0);
unsafe {
drop(Box::from_raw(h1));
drop(Box::from_raw(h2));
}
}
#[test]
fn deletehookdef_returns_one_on_miss_zero_on_hit() {
let _g = crate::test_util::global_state_lock();
let h = leak_hookdef("zshrs_test_del_hook", HOOKF_ALL);
assert_eq!(deletehookdef(h), 1, "not in chain → 1");
assert_eq!(addhookdef(h), 0);
assert_eq!(deletehookdef(h), 0, "spliced out → 0");
assert_eq!(deletehookdef(h), 1, "second delete misses → 1");
unsafe {
drop(Box::from_raw(h));
}
}
#[test]
fn runhookdef_dispatches_all_under_hookf_all_short_circuits_on_nonzero() {
let _g = crate::test_util::global_state_lock();
H1_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H2_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H1_RETVAL.store(0, std::sync::atomic::Ordering::SeqCst);
let h = leak_hookdef("zshrs_test_runall", HOOKF_ALL);
assert_eq!(addhookdef(h), 0);
assert_eq!(addhookdeffunc(h, h1), 0);
assert_eq!(addhookdeffunc(h, h2), 0);
assert_eq!(runhookdef(h, std::ptr::null_mut()), 0);
assert_eq!(H1_CALLS.load(std::sync::atomic::Ordering::SeqCst), 1);
assert_eq!(H2_CALLS.load(std::sync::atomic::Ordering::SeqCst), 1);
H1_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H2_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H1_RETVAL.store(7, std::sync::atomic::Ordering::SeqCst);
assert_eq!(runhookdef(h, std::ptr::null_mut()), 7);
assert_eq!(H1_CALLS.load(std::sync::atomic::Ordering::SeqCst), 1);
assert_eq!(
H2_CALLS.load(std::sync::atomic::Ordering::SeqCst),
0,
"h2 must not fire after h1 returns nonzero"
);
assert_eq!(deletehookdef(h), 0);
unsafe {
drop(Box::from_raw(h));
}
}
#[test]
fn runhookdef_calls_only_last_when_hookf_all_clear() {
let _g = crate::test_util::global_state_lock();
H1_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H2_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H1_RETVAL.store(0, std::sync::atomic::Ordering::SeqCst);
let h = leak_hookdef("zshrs_test_runlast", 0); assert_eq!(addhookdef(h), 0);
assert_eq!(addhookdeffunc(h, h1), 0);
assert_eq!(addhookdeffunc(h, h2), 0);
runhookdef(h, std::ptr::null_mut());
assert_eq!(
H1_CALLS.load(std::sync::atomic::Ordering::SeqCst),
0,
"h1 must not fire when HOOKF_ALL is clear"
);
assert_eq!(
H2_CALLS.load(std::sync::atomic::Ordering::SeqCst),
1,
"only the last-registered handler fires"
);
assert_eq!(deletehookdef(h), 0);
unsafe {
drop(Box::from_raw(h));
}
}
#[test]
fn deletehookdeffunc_removes_only_target_hookfn() {
let _g = crate::test_util::global_state_lock();
H1_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H2_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
H1_RETVAL.store(0, std::sync::atomic::Ordering::SeqCst);
let h = leak_hookdef("zshrs_test_del_fn", HOOKF_ALL);
assert_eq!(addhookdef(h), 0);
addhookdeffunc(h, h1);
addhookdeffunc(h, h2);
assert_eq!(deletehookdeffunc(h, h1), 0);
assert_eq!(deletehookdeffunc(h, h1), 1);
runhookdef(h, std::ptr::null_mut());
assert_eq!(H1_CALLS.load(std::sync::atomic::Ordering::SeqCst), 0);
assert_eq!(H2_CALLS.load(std::sync::atomic::Ordering::SeqCst), 1);
assert_eq!(deletehookdef(h), 0);
unsafe {
drop(Box::from_raw(h));
}
}
#[test]
fn addhookfunc_and_deletehookfunc_return_one_when_no_hookdef() {
let _g = crate::test_util::global_state_lock();
assert_eq!(addhookfunc("zshrs_test_no_such_hook", h1), 1);
assert_eq!(deletehookfunc("zshrs_test_no_such_hook", h1), 1);
}
#[test]
fn addhookdefs_registers_contiguous_array() {
let _g = crate::test_util::global_state_lock();
let arr: Box<[hookdef; 2]> = Box::new([
hookdef {
next: std::ptr::null_mut(),
name: "zshrs_test_arr_0".into(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
hookdef {
next: std::ptr::null_mut(),
name: "zshrs_test_arr_1".into(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
]);
let base: *mut hookdef = Box::into_raw(arr) as *mut hookdef;
assert_eq!(addhookdefs(std::ptr::null(), base, 2), 0);
assert_eq!(gethookdef("zshrs_test_arr_0"), base);
unsafe {
assert_eq!(gethookdef("zshrs_test_arr_1"), base.add(1));
}
unsafe {
assert_eq!(deletehookdef(base), 0);
assert_eq!(deletehookdef(base.add(1)), 0);
drop(Box::from_raw(base as *mut [hookdef; 2]));
}
}
}
#[cfg(test)]
mod modname_tests {
use super::*;
#[test]
fn modname_ok_accepts_canonical_zsh_module_paths() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("zsh"), 1);
assert_eq!(modname_ok("zsh/datetime"), 1);
assert_eq!(modname_ok("zsh/zle"), 1);
assert_eq!(modname_ok("foo_bar"), 1);
assert_eq!(modname_ok("foo123"), 1);
}
#[test]
fn modname_ok_rejects_special_chars() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("zsh space"), 0);
assert_eq!(modname_ok("zsh-bad"), 0, "hyphen is not IIDENT");
assert_eq!(modname_ok("zsh.foo"), 0, "dot is not IIDENT");
assert_eq!(modname_ok("$foo"), 0);
}
#[test]
fn modname_ok_treats_empty_as_trivially_ok() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok(""), 1);
}
#[test]
fn del_autobin_unknown_name_returns_2_or_zero_per_feat_ignore() {
let _g = crate::test_util::global_state_lock();
let mut t = modulestab::new();
assert_eq!(t.del_autobin("definitely_not_a_builtin", 0), 2);
assert_eq!(t.del_autobin("definitely_not_a_builtin", FEAT_IGNORE), 0);
}
#[test]
fn del_autobin_registered_builtin_returns_3_or_zero_per_feat_ignore() {
let _g = crate::test_util::global_state_lock();
let mut t = modulestab::new();
assert_eq!(t.del_autobin("echo", 0), 3);
assert_eq!(t.del_autobin("echo", FEAT_IGNORE), 0);
}
#[test]
fn del_autobin_autoload_only_entry_removed() {
let _g = crate::test_util::global_state_lock();
let mut t = modulestab::new();
t.autoload_builtins
.insert("zshrs_test_autobin_x".to_string(), "mymod".to_string());
assert_eq!(t.del_autobin("zshrs_test_autobin_x", 0), 0);
assert!(
!t.autoload_builtins.contains_key("zshrs_test_autobin_x"),
"successful del must remove ledger entry"
);
assert_eq!(t.del_autobin("zshrs_test_autobin_x", 0), 2);
assert_eq!(t.del_autobin("zshrs_test_autobin_x", FEAT_IGNORE), 0);
}
#[test]
fn del_autocond_autoload_entry_removed_or_not_found() {
let _g = crate::test_util::global_state_lock();
let mut t = modulestab::new();
assert_eq!(t.del_autocond("zshrs_test_cond_x", 0), 2);
assert_eq!(t.del_autocond("zshrs_test_cond_x", FEAT_IGNORE), 0);
t.autoload_conditions
.insert("zshrs_test_cond_x".to_string(), "mymod".to_string());
assert_eq!(t.del_autocond("zshrs_test_cond_x", 0), 0);
assert!(!t.autoload_conditions.contains_key("zshrs_test_cond_x"));
}
#[test]
fn del_autoparam_unknown_name_returns_2() {
let _g = crate::test_util::global_state_lock();
let mut t = modulestab::new();
assert_eq!(t.del_autoparam("zshrs_test_param_x_unknown", 0), 2);
assert_eq!(
t.del_autoparam("zshrs_test_param_x_unknown", FEAT_IGNORE),
0
);
}
#[test]
fn module_corpus_register_new_returns_true() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
assert!(register_module(&mut t, "myfresh.module"));
}
#[test]
fn module_corpus_register_duplicate_returns_false() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
assert!(register_module(&mut t, "dup.module"));
assert!(
!register_module(&mut t, "dup.module"),
"second registration of same name = false"
);
}
#[test]
fn module_corpus_new_table_has_builtin_modules() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
assert!(
t.modules.len() >= 1,
"fresh modulestab has built-ins, got {}",
t.modules.len()
);
}
#[test]
fn module_corpus_register_empty_name_does_not_panic() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let _ = register_module(&mut t, "");
}
#[test]
fn module_corpus_setup_callback_default_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn module_corpus_boot_callback_default_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(boot_(std::ptr::null()), 0);
}
#[test]
fn module_corpus_cleanup_callback_default_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(cleanup_(std::ptr::null()), 0);
}
#[test]
fn module_corpus_finish_callback_default_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(finish_(std::ptr::null()), 0);
}
#[test]
fn newmoduletable_returns_empty_table() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
assert!(
!t.modules.is_empty(),
"Rust port pre-registers builtins (no-dlopen model)"
);
}
#[test]
fn newmoduletable_pre_registers_builtin_modules() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
assert!(
!t.modules.is_empty(),
"Rust port pre-registers builtin modules at construction"
);
assert!(
t.modules.contains_key("zsh/complete"),
"zsh/complete should be pre-registered"
);
}
#[test]
fn gethookdef_unknown_name_returns_null() {
let _g = crate::test_util::global_state_lock();
let p = gethookdef("zshrs_definitely_not_a_hook_xyz");
assert!(p.is_null(), "missing hook name → NULL pointer");
}
#[test]
fn gethookdef_empty_name_returns_null() {
let _g = crate::test_util::global_state_lock();
let p = gethookdef("");
assert!(p.is_null(), "empty name → NULL (no empty-named hooks)");
}
#[test]
fn register_module_fresh_name_returns_true() {
let _g = crate::test_util::global_state_lock();
let mut tab = newmoduletable();
let r = register_module(&mut tab, "zshrs_test_fresh_module");
assert!(r, "fresh module name should register successfully");
}
#[test]
fn modname_ok_canonical_slash_name() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("zsh/complete"), 1);
assert_eq!(modname_ok("zsh/zftp"), 1);
assert_eq!(modname_ok("zsh/zle"), 1);
}
#[test]
fn modname_ok_single_component() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("simple"), 1);
assert_eq!(modname_ok("a"), 1);
assert_eq!(modname_ok("module123"), 1);
}
#[test]
fn modname_ok_empty_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
modname_ok(""),
1,
"empty name passes loop without finding bad char"
);
}
#[test]
fn modname_ok_hyphen_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("foo-bar"), 0, "hyphen not in IIDENT");
assert_eq!(modname_ok("zsh/foo-bar"), 0);
}
#[test]
fn modname_ok_underscore_allowed() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("foo_bar"), 1);
assert_eq!(modname_ok("zsh/foo_bar"), 1);
}
#[test]
fn modname_ok_digit_allowed() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("123abc"), 1);
assert_eq!(modname_ok("zsh/2023"), 1);
}
#[test]
fn modname_ok_special_chars_rejected() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("foo.bar"), 0, "dot rejected");
assert_eq!(modname_ok("foo*"), 0, "glob rejected");
assert_eq!(modname_ok("foo bar"), 0, "space rejected");
assert_eq!(modname_ok("foo\\bar"), 0, "backslash rejected");
assert_eq!(modname_ok("foo@bar"), 0, "@ rejected");
}
#[test]
fn modname_ok_trailing_slash() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("foo/"), 1);
}
#[test]
fn modname_ok_leading_slash_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
modname_ok("/foo"),
1,
"C allows leading slash (loop just skips)"
);
}
#[test]
fn modname_ok_double_slash_allowed() {
let _g = crate::test_util::global_state_lock();
assert_eq!(modname_ok("foo//bar"), 1);
}
#[test]
fn module_loaded_returns_one_for_registered_pin() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
register_module(&mut t, "zsh_test_loaded_check");
assert_eq!(module_loaded(&t, "zsh_test_loaded_check"), 1);
}
#[test]
fn module_loaded_returns_zero_for_unregistered_pin() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
assert_eq!(module_loaded(&t, "definitely_not_a_module_xyz"), 0);
}
#[test]
fn module_loaded_empty_name_returns_zero() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
assert_eq!(module_loaded(&t, ""), 0);
}
#[test]
fn dyn_setup_module_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(dyn_setup_module(std::ptr::null()), 0);
}
#[test]
fn dyn_boot_module_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(dyn_boot_module(std::ptr::null()), 0);
}
#[test]
fn dyn_cleanup_module_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(dyn_cleanup_module(std::ptr::null()), 0);
}
#[test]
fn dyn_finish_module_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(dyn_finish_module(std::ptr::null()), 0);
}
#[test]
fn dyn_features_module_null_returns_zero_no_mutation() {
let _g = crate::test_util::global_state_lock();
let mut features: Vec<String> = vec!["preserved".into()];
assert_eq!(dyn_features_module(std::ptr::null(), &mut features), 0);
assert_eq!(
features,
vec!["preserved".to_string()],
"static-link path must NOT mutate features"
);
}
#[test]
fn dyn_enables_module_null_returns_zero_no_mutation() {
let _g = crate::test_util::global_state_lock();
let mut enables: Option<Vec<i32>> = None;
assert_eq!(dyn_enables_module(std::ptr::null(), &mut enables), 0);
assert!(
enables.is_none(),
"static-link path must NOT mutate enables"
);
}
#[test]
fn newmoduletable_accepts_fresh_register() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
assert!(register_module(&mut t, "zshrs_fresh_test_module_xyz"));
}
#[test]
fn register_module_duplicate_does_not_grow_table() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let name = "zshrs_dup_register_test";
assert!(register_module(&mut t, name), "first call succeeds");
let count_after_first = t.modules.len();
let r = register_module(&mut t, name);
assert!(!r, "duplicate must return false");
assert_eq!(
t.modules.len(),
count_after_first,
"table size unchanged on dup"
);
}
#[test]
fn gethookdef_returns_raw_ptr_type() {
let _g = crate::test_util::global_state_lock();
let _: *mut hookdef = gethookdef("anything");
}
#[test]
fn gethookdef_empty_returns_null() {
let _g = crate::test_util::global_state_lock();
assert!(gethookdef("").is_null(), "empty → null");
}
#[test]
fn checkaddparam_empty_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = checkaddparam("", 0);
}
#[test]
fn getmathfunc_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let _: Option<String> = getmathfunc(&mut t, "anything", 0);
}
#[test]
fn getmathfunc_empty_table_unknown_returns_none() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let r = getmathfunc(&mut t, "__never_a_real_math_fn_xyz__", 0);
assert!(r.is_none(), "unknown math fn → None");
}
#[test]
fn find_module_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let _: Option<String> = find_module(&mut t, "anything", 0);
}
#[test]
fn delete_module_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let _: i32 = delete_module(&mut t, "__never_loaded__");
}
#[test]
fn try_load_module_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let t = newmoduletable();
let _: i32 = try_load_module(&t, "__never_real_module__");
}
#[test]
fn do_load_module_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut t = newmoduletable();
let _: i32 = do_load_module(&mut t, "__never_real_module_xyz__", 1);
}
#[test]
fn load_and_bind_returns_usize_type() {
let _g = crate::test_util::global_state_lock();
let _: usize = load_and_bind("");
}
#[test]
fn hpux_dlsym_returns_usize_type() {
let _g = crate::test_util::global_state_lock();
let _: usize = hpux_dlsym(0, "");
}
}