use crate::ported::math::{matheval, mnumber, MN_FLOAT, MN_INTEGER};
use crate::ported::options::{opt_state_get, opt_state_set};
use crate::ported::params::{isident, paramtab, setiparam, setsparam};
use crate::ported::utils::{metafy, movefd, unmeta, zclose, zerr, zstrtol, zwarnnam};
use crate::ported::zsh_h::{OPT_ARG, OPT_ISSET, module, options};
use std::sync::{Mutex, OnceLock};
const SYSREAD_BUFSIZE: usize = 8192;
pub fn getposint(instr: &str, nam: &str) -> i32 {
let (ret, eptr) = zstrtol(instr, 10);
let ret = ret as i32;
if !eptr.is_empty() || ret < 0 {
zwarnnam(nam, &format!("integer expected: {}", instr)); return -1; }
ret }
#[allow(unused_variables)]
pub fn bin_sysread(
nam: &str,
args: &[String], ops: &options,
func: i32,
) -> i32 {
let mut infd: i32 = 0; let mut outfd: i32 = -1; let mut bufsize: usize = SYSREAD_BUFSIZE; let mut outvar: Option<String> = None; let mut countvar: Option<String> = None;
if OPT_ISSET(ops, b'i') {
infd = getposint(OPT_ARG(ops, b'i').unwrap_or(""), nam); if infd < 0 {
return 1;
} }
if OPT_ISSET(ops, b'o') {
outfd = getposint(OPT_ARG(ops, b'o').unwrap_or(""), nam); if outfd < 0 {
return 1;
} }
if OPT_ISSET(ops, b's') {
let v = getposint(OPT_ARG(ops, b's').unwrap_or(""), nam); if v < 0 {
return 1;
} bufsize = v as usize;
}
if OPT_ISSET(ops, b'c') {
let cv = OPT_ARG(ops, b'c').unwrap_or("").to_string(); if !isident(&cv) {
zwarnnam(nam, &format!("not an identifier: {}", cv)); return 1; }
countvar = Some(cv);
}
if !args.is_empty() {
let ov = args[0].clone(); if !isident(&ov) {
zwarnnam(nam, &format!("not an identifier: {}", ov)); return 1; }
outvar = Some(ov);
}
let timeout_arg: Option<&str> = if OPT_ISSET(ops, b't') {
OPT_ARG(ops, b't')
} else {
None
};
let mut inbuf = vec![0u8; bufsize];
if let Some(t_str) = timeout_arg {
let to_mn = match matheval(t_str) {
Ok(m) => m,
Err(msg) => {
zerr(&msg);
return 1; }
};
let to_int: i32 = if to_mn.type_ == MN_FLOAT {
(1000.0 * to_mn.d) as i32 } else {
(1000 * to_mn.l) as i32 };
let mut ret;
loop {
let mut pfd = libc::pollfd {
fd: infd,
events: libc::POLLIN,
revents: 0,
};
ret = unsafe { libc::poll(&mut pfd, 1, to_int) };
if ret >= 0 {
break;
}
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno != libc::EINTR {
break; }
}
if ret <= 0 {
return if ret != 0 { 2 } else { 4 };
}
}
let mut count: isize;
loop {
count = unsafe { libc::read(infd, inbuf.as_mut_ptr() as *mut libc::c_void, bufsize) };
if count >= 0 {
break;
}
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno != libc::EINTR {
break;
} }
if let Some(ref cv) = countvar {
setiparam(cv, count as i64); }
if count < 0 {
return 2;
}
let count = count as usize;
if outfd >= 0 {
if count == 0 {
return 5;
} let mut p = 0usize;
let mut remaining = count;
while remaining > 0 {
let ret = unsafe {
libc::write(outfd, inbuf[p..].as_ptr() as *const libc::c_void, remaining)
};
if ret < 0 {
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno == libc::EINTR {
continue;
}
if let Some(ref ov) = outvar {
let buf_remaining = String::from_utf8_lossy(&inbuf[p..p + remaining]);
let m = metafy(&buf_remaining);
setsparam(ov, &m); }
if let Some(ref cv) = countvar {
setiparam(cv, remaining as i64); }
return 3; }
p += ret as usize; remaining -= ret as usize; }
return 0; }
let target = outvar.unwrap_or_else(|| "REPLY".to_string()); let buf_str = String::from_utf8_lossy(&inbuf[..count]);
let m = metafy(&buf_str);
setsparam(&target, &m); if count != 0 {
0
} else {
5
} }
pub fn bin_syswrite(
nam: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let mut outfd: i32 = 1; let mut countvar: Option<String> = None;
if OPT_ISSET(ops, b'o') {
outfd = getposint(OPT_ARG(ops, b'o').unwrap_or(""), nam); if outfd < 0 {
return 1;
} }
if OPT_ISSET(ops, b'c') {
let cv = OPT_ARG(ops, b'c').unwrap_or("").to_string(); if !isident(&cv) {
zwarnnam(nam, &format!("not an identifier: {}", cv)); return 1; }
countvar = Some(cv);
}
let data = match args.first() {
Some(d) => d.clone(),
None => return 1,
};
let unmeta = unmeta(&data);
let bytes = unmeta.as_bytes();
let mut totcount: usize = 0; let mut len = bytes.len();
let mut p = 0usize;
while len > 0 {
let count = unsafe { libc::write(outfd, bytes[p..].as_ptr() as *const libc::c_void, len) };
if count < 0 {
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno != libc::EINTR {
if let Some(ref cv) = countvar {
setiparam(cv, totcount as i64); }
return 2; }
continue;
}
p += count as usize; totcount += count as usize; len -= count as usize; }
if let Some(ref cv) = countvar {
setiparam(cv, totcount as i64); }
0 }
pub fn bin_sysopen(
nam: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let read_flag = OPT_ISSET(ops, b'r'); let write_flag = OPT_ISSET(ops, b'w'); let append_flag = OPT_ISSET(ops, b'a');
let append_flag_bit = if append_flag { libc::O_APPEND } else { 0 };
let mut flags = libc::O_NOCTTY
| append_flag_bit
| if append_flag || write_flag {
if read_flag {
libc::O_RDWR
} else {
libc::O_WRONLY
}
} else {
libc::O_RDONLY
};
let mut perms: u32 = 0o666;
let mut explicit: i32 = -1;
if !OPT_ISSET(ops, b'u') {
zwarnnam(nam, "file descriptor not specified"); return 1; }
let fdvar = OPT_ARG(ops, b'u').unwrap_or("").to_string(); let path = match args.first() {
Some(p) => p.clone(),
None => return 1,
};
let o_arg: Option<&str> = if OPT_ISSET(ops, b'o') {
OPT_ARG(ops, b'o')
} else {
None
};
let m_arg: Option<&str> = if OPT_ISSET(ops, b'm') {
OPT_ARG(ops, b'm')
} else {
None
};
if fdvar.len() == 1 && fdvar.chars().next().unwrap().is_ascii_digit() {
explicit = fdvar.parse().unwrap(); } else if !isident(&fdvar) {
zwarnnam(nam, &format!("not an identifier: {}", fdvar)); return 1; }
if let Some(opts) = o_arg {
for tok in opts.split(',') {
let mut name: &str = tok;
if name.len() >= 2 && name[..2].eq_ignore_ascii_case("O_") {
name = &name[2..];
}
#[cfg(unix)]
{
const OPENOPTS: &[(&str, i32)] = &[
("cloexec", libc::O_CLOEXEC), ("nofollow", libc::O_NOFOLLOW), ("sync", libc::O_SYNC), #[cfg(target_os = "linux")]
("noatime", libc::O_NOATIME), ("nonblock", libc::O_NONBLOCK), ("excl", libc::O_EXCL | libc::O_CREAT), ("creat", libc::O_CREAT), ("create", libc::O_CREAT), ("truncate", libc::O_TRUNC), ("trunc", libc::O_TRUNC), ];
let mut found: Option<i32> = None;
for (n, oflag) in OPENOPTS.iter().rev() {
if n.eq_ignore_ascii_case(name) {
found = Some(*oflag);
break;
}
}
let oflag = match found {
Some(f) => f,
None => {
zwarnnam(nam, &format!("unsupported option: {}\n", tok)); return 1; }
};
flags |= oflag; }
}
}
if let Some(m) = m_arg {
let mode_str: &str = m;
let mut ptr = 0;
let bytes = mode_str.as_bytes();
while ptr < bytes.len() && (b'0'..=b'7').contains(&bytes[ptr]) {
ptr += 1;
}
if ptr < bytes.len() || ptr < 3 {
zwarnnam(nam, &format!("invalid mode {}", mode_str)); return 1; }
let (v, _) = zstrtol(mode_str, 8);
perms = v as u32;
}
let path_c = match std::ffi::CString::new(path.as_bytes()) {
Ok(s) => s,
Err(_) => return 1,
};
let fd = unsafe {
if (flags & libc::O_CREAT) != 0 {
libc::open(path_c.as_ptr(), flags, perms as libc::c_uint) } else {
libc::open(path_c.as_ptr(), flags) }
};
if fd == -1 {
let e = std::io::Error::last_os_error();
zwarnnam(nam, &format!("can't open file {}: {}", path, e)); return 2; }
let moved_fd: i32 = if explicit > -1 {
crate::ported::utils::redup(fd, explicit) } else {
movefd(fd) };
if moved_fd == -1 {
zwarnnam(nam, &format!("can't open file {}", path)); return 2; }
if (flags & libc::O_CLOEXEC) != 0 && fd != moved_fd {
unsafe {
libc::fcntl(moved_fd, libc::F_SETFD, libc::FD_CLOEXEC);
} }
if explicit == -1 {
setiparam(&fdvar, moved_fd as i64); }
0 }
pub fn bin_sysseek(
nam: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let mut w: i32 = libc::SEEK_SET; let mut fd: i32 = 0;
if OPT_ISSET(ops, b'u') {
fd = getposint(OPT_ARG(ops, b'u').unwrap_or(""), nam); if fd < 0 {
return 1;
} }
if OPT_ISSET(ops, b'w') {
let whence = OPT_ARG(ops, b'w').unwrap_or(""); if whence.eq_ignore_ascii_case("current") || whence == "1" {
w = libc::SEEK_CUR; } else if whence.eq_ignore_ascii_case("end") || whence == "2" {
w = libc::SEEK_END; } else if !whence.eq_ignore_ascii_case("start") && whence != "0" {
zwarnnam(nam, &format!("unknown argument to -w: {}", whence)); return 1; }
}
let pos_str = match args.first() {
Some(s) => s.clone(),
None => return 1,
};
let pos = match crate::ported::math::mathevali(&pos_str) {
Ok(v) => v,
Err(msg) => {
zerr(&msg);
return 1;
}
};
if unsafe { libc::lseek(fd, pos as libc::off_t, w) } == -1 {
2
} else {
0
}
}
#[allow(unused_variables)]
pub fn math_systell(name: &str, argc: i32, argv: &[mnumber], id: i32) -> mnumber {
let fd: i32 = if argv[0].type_ == MN_INTEGER {
argv[0].l as i32
} else {
argv[0].d as i32
};
let mut ret = mnumber {
type_: MN_INTEGER, l: 0, d: 0.0,
};
if fd < 0 {
crate::ported::utils::zwarn("file descriptor out of range");
return ret;
}
ret.l = unsafe { libc::lseek(fd, 0, libc::SEEK_CUR) } as i64;
ret }
pub fn bin_syserror(
nam: &str,
args: &[String], ops: &options,
_func: i32,
) -> i32 {
let mut num: i32 = 0;
let mut errvar: Option<String> = None;
let mut pfx: String = String::new();
if OPT_ISSET(ops, b'e') {
let ev = OPT_ARG(ops, b'e').unwrap_or("").to_string(); if !isident(&ev) {
zwarnnam(nam, &format!("not an identifier: {}", ev)); return 1; }
errvar = Some(ev);
}
if OPT_ISSET(ops, b'p') {
pfx = OPT_ARG(ops, b'p').unwrap_or("").to_string(); }
if args.is_empty() {
num = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
} else {
let arg = &args[0];
let bytes = arg.as_bytes();
let mut ptr = 0usize;
while ptr < bytes.len() && bytes[ptr].is_ascii_digit() {
ptr += 1;
}
if ptr == bytes.len() && ptr > 0 {
num = arg.parse::<i32>().unwrap_or(0); } else {
let mut found = false;
for (idx, (ename, _)) in SYS_ERRNAMES.iter().enumerate() {
if *ename == arg {
num = (idx as i32) + 1; found = true;
break; }
}
if !found {
return 2; }
}
}
let msg = std::io::Error::from_raw_os_error(num).to_string();
if let Some(ev) = errvar {
let str_out = format!("{}{}", pfx, msg); setsparam(&ev, &str_out); } else {
eprintln!("{}{}", pfx, msg); }
0 }
pub fn bin_zsystem_flock(
nam: &str,
args: &[String], _ops: &options,
_func: i32,
) -> i32 {
let mut cloexec: bool = true; let mut unlock: bool = false;
let mut readlock: bool = false;
let mut timeout: f64 = -1.0; let mut timeout_interval: i64 = 1_000_000;
let mut fdvar: Option<String> = None;
let mut i = 0usize;
while i < args.len() && args[i].starts_with('-') {
let arg = &args[i];
i += 1;
let optptr = &arg[1..];
if optptr.is_empty() || optptr == "-" {
break;
}
let chars: Vec<char> = optptr.chars().collect();
let mut idx = 0usize;
while idx < chars.len() {
let opt = chars[idx];
match opt {
'e' => {
cloexec = false; }
'f' => {
let rest: String = chars[idx + 1..].iter().collect();
let fdvar_str = if !rest.is_empty() {
idx = chars.len(); rest
} else if i < args.len() {
let v = args[i].clone(); i += 1;
v
} else {
zwarnnam(
nam,
&format!("flock: option {} requires a variable name", opt),
);
return 1;
};
if !isident(&fdvar_str) {
zwarnnam(
nam,
&format!("flock: option {} requires a variable name", opt),
);
return 1; }
fdvar = Some(fdvar_str);
break;
}
'r' => readlock = true, 't' => {
let rest: String = chars[idx + 1..].iter().collect();
let optarg = if !rest.is_empty() {
idx = chars.len();
rest
} else if i < args.len() {
let v = args[i].clone();
i += 1;
v
} else {
zwarnnam(
nam,
&format!("flock: option {} requires a numeric timeout", opt),
);
return 1;
};
let tp = match matheval(&optarg) {
Ok(m) => m,
Err(msg) => {
zerr(&msg);
return 1;
}
};
timeout = if (tp.type_ & MN_FLOAT) != 0 {
tp.d
} else {
tp.l as f64
};
if timeout > 1073741823.0 {
zwarnnam(nam, &format!("flock: invalid timeout value: '{}'", optarg));
return 1;
}
break;
}
'i' => {
let rest: String = chars[idx + 1..].iter().collect();
let optarg = if !rest.is_empty() {
idx = chars.len();
rest
} else if i < args.len() {
let v = args[i].clone();
i += 1;
v
} else {
zwarnnam(
nam,
&format!("flock: option {} requires a numeric retry interval", opt),
);
return 1;
};
let mut tp = match matheval(&optarg) {
Ok(m) => m,
Err(msg) => {
zerr(&msg);
return 1;
}
};
if (tp.type_ & MN_FLOAT) == 0 {
tp.type_ = MN_FLOAT;
tp.d = tp.l as f64;
}
tp.d = (tp.d * 1e6).ceil(); if tp.d < 1.0 || tp.d > 0.999 * (i64::MAX as f64) {
zwarnnam(nam, &format!("flock: invalid interval value: '{}'", optarg));
return 1; }
timeout_interval = tp.d as i64; break;
}
'u' => unlock = true, _ => {
zwarnnam(nam, &format!("flock: unknown option: {}", opt)); return 1; }
}
idx += 1;
}
}
if i >= args.len() {
zwarnnam(nam, "flock: not enough arguments");
return 1;
}
if i + 1 < args.len() {
zwarnnam(nam, "flock: too many arguments");
return 1;
}
let path = &args[i];
if unlock {
let flock_fd: i32 = match crate::ported::math::mathevali(path) {
Ok(v) => v as i32,
Err(msg) => {
zerr(&msg);
return 1;
}
};
if crate::ported::utils::zcloselockfd(flock_fd) < 0 {
zwarnnam(
nam,
&format!("flock: file descriptor {} not in use for locking", flock_fd),
);
return 1;
}
return 0; }
let flags = if readlock {
libc::O_RDONLY | libc::O_NOCTTY
} else {
libc::O_RDWR | libc::O_NOCTTY
};
let path_unmeta = unmeta(path);
let path_c = match std::ffi::CString::new(path_unmeta) {
Ok(s) => s,
Err(_) => return 1,
};
let mut flock_fd = unsafe { libc::open(path_c.as_ptr(), flags) }; if flock_fd < 0 {
let e = std::io::Error::last_os_error();
zwarnnam(nam, &format!("failed to open {} for writing: {}", path, e));
return 1;
}
flock_fd = movefd(flock_fd); if flock_fd == -1 {
return 1;
}
if cloexec {
let fdflags = unsafe { libc::fcntl(flock_fd, libc::F_GETFD, 0) };
if fdflags != -1 {
unsafe {
libc::fcntl(flock_fd, libc::F_SETFD, fdflags | libc::FD_CLOEXEC);
}
}
}
crate::ported::utils::addlockfd(flock_fd, cloexec);
let lock_type: libc::c_short = if readlock {
libc::F_RDLCK as libc::c_short
} else {
libc::F_WRLCK as libc::c_short
};
#[allow(clippy::unnecessary_cast)]
let lck = libc::flock {
l_type: lock_type, l_whence: libc::SEEK_SET as libc::c_short, l_start: 0, l_len: 0, l_pid: 0,
};
if timeout > 0.0 {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs_f64(timeout);
loop {
let r = unsafe { libc::fcntl(flock_fd, libc::F_SETLK, &lck) };
if r >= 0 {
break;
}
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno != libc::EINTR && eno != libc::EACCES && eno != libc::EAGAIN {
zclose(flock_fd); let e = std::io::Error::last_os_error();
zwarnnam(nam, &format!("failed to lock file {}: {}", path, e));
return 1;
}
let now = std::time::Instant::now();
if now >= deadline {
zclose(flock_fd); return 2; }
let remaining = deadline - now;
let remaining_us = remaining.as_micros() as i64;
let interval = remaining_us.min(timeout_interval);
std::thread::sleep(std::time::Duration::from_micros(interval as u64));
}
} else {
let cmd = if timeout == 0.0 {
libc::F_SETLK
} else {
libc::F_SETLKW
};
loop {
let r = unsafe { libc::fcntl(flock_fd, cmd, &lck) };
if r >= 0 {
break;
}
let eno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if eno == libc::EINTR {
continue;
} zclose(flock_fd); let e = std::io::Error::last_os_error();
zwarnnam(nam, &format!("failed to lock file {}: {}", path, e));
return 1;
}
}
if let Some(ref var) = fdvar {
setiparam(var, flock_fd as i64); }
0 }
pub fn bin_zsystem_supports(
nam: &str,
args: &[String], _ops: &options,
_func: i32,
) -> i32 {
if args.is_empty() {
zwarnnam(nam, "supports: not enough arguments");
return 255;
}
if args.len() > 1 {
zwarnnam(nam, "supports: too many arguments");
return 255;
}
if args[0] == "supports" {
return 0;
} #[cfg(unix)]
if args[0] == "flock" {
return 0;
} 1 }
pub fn bin_zsystem(
nam: &str,
args: &[String], ops: &options,
func: i32,
) -> i32 {
if args.is_empty() {
zwarnnam(nam, "subcommand expected");
return 1;
}
if args[0] == "flock" {
return bin_zsystem_flock(nam, &args[1..], ops, func); }
if args[0] == "supports" {
return bin_zsystem_supports(nam, &args[1..], ops, func); }
zwarnnam(nam, &format!("unknown subcommand: {}", args[0])); 1 }
pub fn errnosgetfn(_pm: *mut crate::ported::zsh_h::param) -> Vec<String> {
SYS_ERRNAMES.iter().map(|(n, _)| n.to_string()).collect() }
pub fn fillpmsysparams(name: &str) -> Option<String> {
let num: i32 = match name {
"pid" => unsafe { libc::getpid() }, "ppid" => unsafe { libc::getppid() }, "procsubstpid" => 0,
_ => return None, };
Some(format!("{}", num)) }
pub fn getpmsysparams(_ht: *mut crate::ported::zsh_h::HashTable, name: &str) -> Option<crate::ported::zsh_h::Param> {
use crate::ported::zsh_h::{hashnode, param, Param, PM_READONLY, PM_SCALAR, PM_UNSET};
let mk = |s: String, extra: i32| -> Param {
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_SCALAR as i32 | PM_READONLY as i32 | extra,
},
u_data: 0,
u_arr: None,
u_str: Some(s),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
})
};
match fillpmsysparams(name) {
Some(v) => Some(mk(v, 0)),
None => Some(mk(String::new(), PM_UNSET as i32)),
}
}
pub fn scanpmsysparams(
_ht: *mut crate::ported::zsh_h::HashTable,
func: Option<crate::ported::zsh_h::ScanFunc>,
flags: i32,
) {
use crate::ported::zsh_h::{hashnode, param, PM_READONLY, PM_SCALAR};
let f = match func {
Some(f) => f,
None => return,
};
for n in ["pid", "ppid", "procsubstpid"] {
if let Some(v) = fillpmsysparams(n) {
let pm = param {
node: hashnode {
next: None,
nam: n.to_string(),
flags: PM_SCALAR as i32 | PM_READONLY as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(v),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
let node_box = Box::new(pm.node.clone());
f(&node_box, flags); }
}
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
0
}
pub fn cleanup_(m: *const module) -> i32 {
setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0
}
#[cfg(target_os = "linux")]
pub static SYS_ERRNAMES: &[(&str, i32)] = &[
("EPERM", 1),
("ENOENT", 2),
("ESRCH", 3),
("EINTR", 4),
("EIO", 5),
("ENXIO", 6),
("E2BIG", 7),
("ENOEXEC", 8),
("EBADF", 9),
("ECHILD", 10),
("EAGAIN", 11),
("ENOMEM", 12),
("EACCES", 13),
("EFAULT", 14),
("ENOTBLK", 15),
("EBUSY", 16),
("EEXIST", 17),
("EXDEV", 18),
("ENODEV", 19),
("ENOTDIR", 20),
("EISDIR", 21),
("EINVAL", 22),
("ENFILE", 23),
("EMFILE", 24),
("ENOTTY", 25),
("ETXTBSY", 26),
("EFBIG", 27),
("ENOSPC", 28),
("ESPIPE", 29),
("EROFS", 30),
("EMLINK", 31),
("EPIPE", 32),
("EDOM", 33),
("ERANGE", 34),
("EDEADLK", 35),
("ENAMETOOLONG", 36),
("ENOLCK", 37),
("ENOSYS", 38),
("ENOTEMPTY", 39),
("ELOOP", 40),
];
#[cfg(target_os = "macos")]
pub static SYS_ERRNAMES: &[(&str, i32)] = &[
("EPERM", 1),
("ENOENT", 2),
("ESRCH", 3),
("EINTR", 4),
("EIO", 5),
("ENXIO", 6),
("E2BIG", 7),
("ENOEXEC", 8),
("EBADF", 9),
("ECHILD", 10),
("EDEADLK", 11),
("ENOMEM", 12),
("EACCES", 13),
("EFAULT", 14),
("ENOTBLK", 15),
("EBUSY", 16),
("EEXIST", 17),
("EXDEV", 18),
("ENODEV", 19),
("ENOTDIR", 20),
("EISDIR", 21),
("EINVAL", 22),
("ENFILE", 23),
("EMFILE", 24),
("ENOTTY", 25),
("ETXTBSY", 26),
("EFBIG", 27),
("ENOSPC", 28),
("ESPIPE", 29),
("EROFS", 30),
("EMLINK", 31),
("EPIPE", 32),
("EDOM", 33),
("ERANGE", 34),
("EAGAIN", 35),
("EINPROGRESS", 36),
("EALREADY", 37),
("ENOTSOCK", 38),
("EDESTADDRREQ", 39),
("EMSGSIZE", 40),
("EPROTOTYPE", 41),
("ENOPROTOOPT", 42),
("EPROTONOSUPPORT", 43),
("ESOCKTNOSUPPORT", 44),
("ENOTSUP", 45),
("EPFNOSUPPORT", 46),
("EAFNOSUPPORT", 47),
("EADDRINUSE", 48),
("EADDRNOTAVAIL", 49),
("ENETDOWN", 50),
("ENETUNREACH", 51),
("ENETRESET", 52),
("ECONNABORTED", 53),
("ECONNRESET", 54),
("ENOBUFS", 55),
("EISCONN", 56),
("ENOTCONN", 57),
("ESHUTDOWN", 58),
("ETOOMANYREFS", 59),
("ETIMEDOUT", 60),
("ECONNREFUSED", 61),
("ELOOP", 62),
("ENAMETOOLONG", 63),
("EHOSTDOWN", 64),
("EHOSTUNREACH", 65),
("ENOTEMPTY", 66),
("EPROCLIM", 67),
("EUSERS", 68),
("EDQUOT", 69),
("ESTALE", 70),
("EREMOTE", 71),
("EBADRPC", 72),
("ERPCMISMATCH", 73),
("EPROGUNAVAIL", 74),
("EPROGMISMATCH", 75),
("EPROCUNAVAIL", 76),
("ENOLCK", 77),
("ENOSYS", 78),
("EFTYPE", 79),
("EAUTH", 80),
("ENEEDAUTH", 81),
("EPWROFF", 82),
("EDEVERR", 83),
("EOVERFLOW", 84),
("EBADEXEC", 85),
("EBADARCH", 86),
("ESHLIBVERS", 87),
("EBADMACHO", 88),
("ECANCELED", 89),
("EIDRM", 90),
("ENOMSG", 91),
("EILSEQ", 92),
("ENOATTR", 93),
("EBADMSG", 94),
("EMULTIHOP", 95),
("ENODATA", 96),
("ENOLINK", 97),
("ENOSR", 98),
("ENOSTR", 99),
("EPROTO", 100),
("ETIME", 101),
("EOPNOTSUPP", 102),
("ENOPOLICY", 103),
("ENOTRECOVERABLE", 104),
("EOWNERDEAD", 105),
("EQFULL", 106),
];
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub static SYS_ERRNAMES: &[(&str, i32)] = &[
("EPERM", 1),
("ENOENT", 2),
("ESRCH", 3),
("EINTR", 4),
("EIO", 5),
("ENXIO", 6),
("E2BIG", 7),
("ENOEXEC", 8),
("EBADF", 9),
("ECHILD", 10),
("ENOMEM", 12),
("EACCES", 13),
("EFAULT", 14),
("EBUSY", 16),
("EEXIST", 17),
("EXDEV", 18),
("ENODEV", 19),
("ENOTDIR", 20),
("EISDIR", 21),
("EINVAL", 22),
("ENFILE", 23),
("EMFILE", 24),
("ENOTTY", 25),
("EFBIG", 27),
("ENOSPC", 28),
("ESPIPE", 29),
("EROFS", 30),
("EMLINK", 31),
("EPIPE", 32),
("EDOM", 33),
("ERANGE", 34),
];
pub static ERRNO_NAMES: &[(&str, i32)] = SYS_ERRNAMES;
static MODULE_FEATURES: OnceLock<Mutex<crate::ported::zsh_h::features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>) -> Vec<String> {
vec![
"b:syserror".to_string(),
"b:sysread".to_string(),
"b:syswrite".to_string(),
"b:sysopen".to_string(),
"b:sysseek".to_string(),
"b:zsystem".to_string(),
"f:systell".to_string(),
"p:errnos".to_string(),
"p:sysparams".to_string(),
]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 9]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<crate::ported::zsh_h::features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(crate::ported::zsh_h::features {
bn_list: None,
bn_size: 6,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 1,
pd_list: None,
pd_size: 2,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::math::{mnumber, MN_INTEGER};
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
use crate::zsh_h::{options, MAX_OPS};
#[test]
fn getposint_basic() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("42", "test"), 42);
assert_eq!(getposint("0", "test"), 0);
assert_eq!(getposint("-1", "test"), -1); assert_eq!(getposint("abc", "test"), -1); }
#[test]
fn bin_zsystem_supports_self() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
assert_eq!(
bin_zsystem_supports("zsystem", &["supports".to_string()], &ops, 0),
0
);
#[cfg(unix)]
assert_eq!(
bin_zsystem_supports("zsystem", &["flock".to_string()], &ops, 0),
0
);
assert_eq!(
bin_zsystem_supports("zsystem", &["nosuchfeature".to_string()], &ops, 0),
1
);
}
#[test]
fn bin_zsystem_supports_arg_count() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
assert_eq!(bin_zsystem_supports("zsystem", &[], &ops, 0), 255);
assert_eq!(
bin_zsystem_supports("zsystem", &["a".to_string(), "b".to_string()], &ops, 0),
255
);
}
#[test]
fn bin_zsystem_dispatch() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
assert_eq!(
bin_zsystem(
"zsystem",
&["supports".to_string(), "supports".to_string()],
&ops,
0
),
0
);
assert_eq!(bin_zsystem("zsystem", &["unknown".to_string()], &ops, 0), 1);
assert_eq!(bin_zsystem("zsystem", &[], &ops, 0), 1);
}
#[test]
fn errnosgetfn_returns_table() {
let _g = crate::test_util::global_state_lock();
let names = errnosgetfn(std::ptr::null_mut());
assert!(names.contains(&"EPERM".to_string()));
assert!(names.contains(&"ENOENT".to_string()));
assert!(names.contains(&"EINVAL".to_string()));
}
#[test]
fn fillpmsysparams_keys() {
let _g = crate::test_util::global_state_lock();
assert!(fillpmsysparams("pid").is_some());
assert!(fillpmsysparams("ppid").is_some());
assert!(fillpmsysparams("procsubstpid").is_some());
assert!(fillpmsysparams("nonsense").is_none());
}
#[test]
fn getpmsysparams_pid_set() {
let _g = crate::test_util::global_state_lock();
use crate::ported::zsh_h::PM_UNSET;
let pm_pid = getpmsysparams(std::ptr::null_mut(), "pid").expect("pid Param");
assert!(pm_pid.node.flags & PM_UNSET as i32 == 0, "pid must be set");
let pm_bad = getpmsysparams(std::ptr::null_mut(), "nonsense").expect("Param");
assert!(pm_bad.node.flags & PM_UNSET as i32 != 0, "unknown key PM_UNSET");
}
#[test]
fn scanpmsysparams_three_entries() {
let _g = crate::test_util::global_state_lock();
use std::sync::Mutex;
static KEYS: Mutex<Vec<String>> = Mutex::new(Vec::new());
KEYS.lock().unwrap().clear();
fn cb(node: &crate::ported::zsh_h::HashNode, _flags: i32) {
KEYS.lock().unwrap().push(node.nam.clone());
}
scanpmsysparams(std::ptr::null_mut(), Some(cb), 0);
let collected = KEYS.lock().unwrap().clone();
assert!(collected.iter().any(|k| k == "pid"));
assert!(collected.iter().any(|k| k == "ppid"));
assert!(collected.iter().any(|k| k == "procsubstpid"));
}
fn empty_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
fn ops_with(args: &[(u8, &str)]) -> options {
let mut ops = empty_ops();
for (idx, (opt, val)) in args.iter().enumerate() {
ops.ind[*opt as usize] = (((idx + 1) << 2) | 1) as u8;
ops.args.push(val.to_string());
ops.argscount = (idx + 1) as i32;
ops.argsalloc = (idx + 1) as i32;
}
ops
}
#[test]
fn bin_syserror_to_errvar_with_prefix() {
let _g = crate::test_util::global_state_lock();
let ops = ops_with(&[(b'e', "myerr"), (b'p', "PFX:")]);
let r = bin_syserror("syserror", &["ENOENT".to_string()], &ops, 0);
assert_eq!(r, 0);
let val = paramtab()
.read()
.ok()
.and_then(|t| t.get("myerr").and_then(|p| p.u_str.clone()))
.unwrap_or_default();
assert!(
val.starts_with("PFX:"),
"expected PFX: prefix, got {:?}",
val
);
}
#[test]
fn bin_syserror_unknown_name_returns_2() {
let _g = crate::test_util::global_state_lock();
let ops = ops_with(&[(b'e', "myerr2")]);
assert_eq!(
bin_syserror("syserror", &["ENOTAREALERROR".to_string()], &ops, 0),
2
);
}
#[test]
#[cfg(unix)]
fn bin_sysopen_writes_fd_to_var() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let dir = TempDir::new().unwrap();
let p = dir.path().join("a.txt");
let ops = ops_with(&[(b'u', "MYFD"), (b'o', "creat")]);
let mut ops = ops;
ops.ind[b'w' as usize] = 1;
let r = bin_sysopen("sysopen", &[p.to_str().unwrap().to_string()], &ops, 0);
assert_eq!(r, 0);
let fd: i32 = paramtab()
.read()
.ok()
.and_then(|t| t.get("MYFD").map(|p| p.u_val as i32))
.expect("MYFD not set by sysopen");
assert!(fd >= 10, "movefd should lift fd to 10+, got {}", fd);
unsafe {
libc::close(fd);
}
opt_state_set("exec", saved_exec);
}
#[test]
#[cfg(unix)]
fn bin_sysseek_basic() {
let _g = crate::test_util::global_state_lock();
let dir = TempDir::new().unwrap();
let p = dir.path().join("b.txt");
{
let mut f = File::create(&p).unwrap();
f.write_all(b"hello world").unwrap();
}
let path_c = std::ffi::CString::new(p.to_str().unwrap()).unwrap();
let fd = unsafe { libc::open(path_c.as_ptr(), libc::O_RDONLY) };
assert!(fd >= 0);
let ops = ops_with(&[(b'u', &fd.to_string()), (b'w', "start")]);
let r = bin_sysseek("sysseek", &["5".to_string()], &ops, 0);
assert_eq!(r, 0);
unsafe {
libc::close(fd);
}
}
#[test]
fn setiparam_writes_integer_to_paramtab() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let name = "ZSHRS_TEST_SETIPARAM_FD_INT";
let _ = setiparam(name, 12345);
let val = paramtab()
.read()
.ok()
.and_then(|t| t.get(name).map(|p| p.u_val));
opt_state_set("exec", saved_exec);
assert_eq!(
val,
Some(12345),
"setiparam should put the integer in paramtab().get(name).u_val"
);
}
#[test]
#[cfg(unix)]
fn math_systell_returns_lseek_cur() {
let _g = crate::test_util::global_state_lock();
let dir = TempDir::new().unwrap();
let p = dir.path().join("c.txt");
{
let mut f = File::create(&p).unwrap();
f.write_all(b"hello world").unwrap();
}
let path_c = std::ffi::CString::new(p.to_str().unwrap()).unwrap();
let fd = unsafe { libc::open(path_c.as_ptr(), libc::O_RDONLY) };
unsafe {
libc::lseek(fd, 7, libc::SEEK_SET);
}
let argv = vec![mnumber {
l: fd as i64,
d: 0.0,
type_: MN_INTEGER,
}];
let r = math_systell("systell", 1, &argv, 0);
assert_eq!(r.type_, MN_INTEGER);
assert_eq!(r.l, 7);
unsafe {
libc::close(fd);
}
}
#[test]
fn system_corpus_getposint_decimal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("42", "test"), 42);
}
#[test]
fn system_corpus_getposint_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("0", "test"), 0);
}
#[test]
fn system_corpus_getposint_negative_returns_error() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("-5", "test"), -1,
"negative input rejected per c:51");
}
#[test]
fn system_corpus_getposint_non_numeric_returns_error() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("abc", "test"), -1);
}
#[test]
fn system_corpus_getposint_trailing_garbage_returns_error() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("42abc", "test"), -1,
"trailing non-digits rejected per c:51 eptr check");
}
#[test]
fn system_corpus_getposint_empty_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("", "test"), 0,
"empty input: zstrtol returns 0, neither error branch hits");
}
#[test]
fn system_corpus_getposint_large_positive() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getposint("1000000", "test"), 1_000_000);
}
}