#![allow(non_camel_case_types, non_snake_case)]
use crate::ported::utils::zwarnnam;
use std::sync::{Mutex, OnceLock};
use std::io::Read;
use crate::ported::zsh_h::{OPT_ISSET, OPT_ARG};
use std::os::unix::fs::DirBuilderExt;
use std::os::unix::fs::PermissionsExt;
pub fn recursivecmd<P, R, L>( nam: &str, opt_noerr: i32, opt_recurse: i32, opt_safe: i32,
args: &[String], dirpre_func: P, dirpost_func: R, leaf_func: L,
) -> i32 where
P: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
R: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
L: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
{
let _ = opt_noerr;
let reccmd = recursivecmd {
nam, opt_noerr, opt_recurse, opt_safe,
dirpre_func, dirpost_func, leaf_func,
};
let mut err = 0i32;
for arg in args { if (err & 2) != 0 { break; }
let first = if opt_safe != 0 { 0 } else { 1 }; err |= recursivecmd_doone(&reccmd, arg, arg, first); }
if err != 0 { 1 } else { 0 } }
pub struct recursivecmd<'a, P, R, L>
where
P: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
R: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
L: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
{
pub nam: &'a str, pub opt_noerr: i32, pub opt_recurse: i32, pub opt_safe: i32, pub dirpre_func: P, pub dirpost_func: R, pub leaf_func: L, }
pub fn ask() -> i32 { use std::io::Read;
let mut bytes = std::io::stdin().lock().bytes();
let a = bytes.next().and_then(|r| r.ok()).unwrap_or(0); for c in bytes.by_ref() { if matches!(c, Ok(b'\n') | Err(_)) { break; }
}
(a == b'y' || a == b'Y') as i32 }
pub fn bin_sync(_nam: &str, _args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
unsafe { libc::sync(); } 0 }
pub fn bin_mkdir(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let oumask = unsafe { libc::umask(0) }; let mut mode: u32 = 0o777 & !(oumask as u32); let mut err = 0i32;
unsafe { libc::umask(oumask); } if OPT_ISSET(ops, b'm') { let str_arg = OPT_ARG(ops, b'm').unwrap_or(""); match i64::from_str_radix(str_arg, 8) { Ok(m) => mode = m as u32,
Err(_) => {
zwarnnam(nam, &format!("invalid mode `{}'", str_arg));
return 1; }
}
}
let p_flag = if OPT_ISSET(ops, b'p') { 1 } else { 0 }; for arg in args { let trimmed: String = if arg.starts_with('/') { let body = arg.trim_end_matches('/');
if body.is_empty() { "/".to_string() } else { body.to_string() }
} else {
arg.trim_end_matches('/').to_string()
};
if p_flag != 0 { let bytes = trimmed.as_bytes();
let mut i = 0usize;
loop {
while i < bytes.len() && bytes[i] == b'/' { i += 1; } while i < bytes.len() && bytes[i] != b'/' { i += 1; } if i >= bytes.len() { err |= domkdir(nam, &trimmed, mode, 1); break;
}
let prefix = &trimmed[..i]; let e = domkdir(nam, prefix, mode | 0o300, 1); if e != 0 { err = 1; break; }
}
} else {
err |= domkdir(nam, &trimmed, mode, 0); }
}
err }
pub fn domkdir(nam: &str, path: &str, mode: u32, p: i32) -> i32 { let mut n = 8; let mut last_err: i32 = 0;
while n > 0 { n -= 1;
let oumask = unsafe { libc::umask(0) }; let mut builder = std::fs::DirBuilder::new();
builder.mode(mode);
let result = builder.create(path); unsafe { libc::umask(oumask); } match result {
Ok(()) => return 0, Err(e) => last_err = e.raw_os_error().unwrap_or(0),
}
if p == 0 || last_err != libc::EEXIST { break; } match std::fs::metadata(path) { Ok(meta) if meta.is_dir() => return 0, Ok(_) => break, Err(e) => {
last_err = e.raw_os_error().unwrap_or(0);
if last_err == libc::ENOENT { continue; } break; }
}
}
zwarnnam(nam, &format!("cannot make directory `{}': {}",
path, std::io::Error::from_raw_os_error(last_err)));
1 }
pub fn bin_rmdir(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut err = 0i32;
for arg in args { let cpath = match std::ffi::CString::new(arg.as_str()) { Ok(c) => c,
Err(_) => {
zwarnnam(nam, &format!("{}: {}", arg, "name too long"));
err = 1;
continue;
}
};
let r = unsafe { libc::rmdir(cpath.as_ptr()) }; if r != 0 { zwarnnam(nam, &format!("cannot remove directory `{}': {}",
arg, std::io::Error::last_os_error()));
err = 1; }
}
err }
pub const BIN_LN: i32 = 0; pub const BIN_MV: i32 = 1;
pub const MV_NODIRS: i32 = 1 << 0; pub const MV_FORCE: i32 = 1 << 1; pub const MV_INTERACTIVE: i32 = 1 << 2; pub const MV_ASKNW: i32 = 1 << 3; pub const MV_ATOMIC: i32 = 1 << 4; pub const MV_NOCHASETARGET: i32 = 1 << 5;
pub fn bin_ln(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let movefn: MoveFunc;
let mut flags: i32;
let mut err = 0i32;
if func == BIN_MV { movefn = mv_rename; flags = if OPT_ISSET(ops, b'f') { 0 } else { MV_ASKNW }; flags |= MV_ATOMIC; } else {
flags = if OPT_ISSET(ops, b'f') { MV_FORCE } else { 0 }; if OPT_ISSET(ops, b'h') || OPT_ISSET(ops, b'n') { flags |= MV_NOCHASETARGET;
}
if OPT_ISSET(ops, b's') { movefn = mv_symlink; } else {
movefn = mv_link; if !OPT_ISSET(ops, b'd') { flags |= MV_NODIRS;
}
}
}
if OPT_ISSET(ops, b'i') && !OPT_ISSET(ops, b'f') { flags |= MV_INTERACTIVE;
}
if args.is_empty() {
zwarnnam(nam, "missing file argument");
return 1;
}
let last_idx = args.len() - 1; let mut have_dir = false;
if last_idx > 0 { let target = &args[last_idx];
if let Ok(meta) = std::fs::metadata(target) { if meta.is_dir() { have_dir = true;
if (flags & MV_NOCHASETARGET) != 0 { if let Ok(lmeta) = std::fs::symlink_metadata(target) {
if lmeta.file_type().is_symlink() { if last_idx > 1 { zwarnnam(nam, &format!("{}: not a directory", target));
return 1; }
if (flags & MV_FORCE) != 0 { let _ = std::fs::remove_file(target); have_dir = false; } else {
zwarnnam(nam, &format!("{}: file exists", target));
return 1; }
}
}
}
}
}
}
if have_dir { let dir = args[last_idx].trim_end_matches('/').to_string();
for src in &args[..last_idx] { let basename = match src.rsplit_once('/') { Some((_, n)) => n,
None => src.as_str(),
};
let dest = format!("{}/{}", dir, basename); err |= domove(nam, movefn, src, &dest, flags); }
return err; }
if last_idx > 1 { zwarnnam(nam, "last of many arguments must be a directory"); return 1; }
let (src, dest) = if args.len() < 2 { let basename = match args[0].rsplit_once('/') { Some((_, n)) => n,
None => args[0].as_str(),
};
(args[0].clone(), basename.to_string()) } else {
(args[0].clone(), args[1].clone())
};
domove(nam, movefn, &src, &dest, flags) }
pub fn domove(nam: &str, movefn: MoveFunc, p: &str, q: &str, flags: i32) -> i32 { if (flags & MV_NODIRS) != 0 { match std::fs::symlink_metadata(p) { Ok(meta) if meta.is_dir() => { zwarnnam(nam, &format!("{}: is a directory", p)); return 1; }
Err(e) => {
zwarnnam(nam, &format!("{}: {}", p, e)); return 1;
}
_ => {}
}
}
if let Ok(qmeta) = std::fs::symlink_metadata(q) { let mut doit = (flags & MV_FORCE) != 0; if qmeta.is_dir() { zwarnnam(nam, &format!("{}: cannot overwrite directory", q)); return 1; } else if (flags & MV_INTERACTIVE) != 0 { eprint!("{}: replace `{}'? ", nam, q); if ask() == 0 { return 0; }
doit = true; } else if (flags & MV_ASKNW) != 0 && !qmeta.file_type().is_symlink() && unsafe { let cq = std::ffi::CString::new(q).ok();
cq.map(|c| libc::access(c.as_ptr(), libc::W_OK))
.unwrap_or(-1) != 0
} {
let mode = qmeta.permissions().mode() & 0o7777;
eprint!("{}: replace `{}', overriding mode {:04o}? ", nam, q, mode); if ask() == 0 { return 0; }
doit = true; }
if doit && (flags & MV_ATOMIC) == 0 { let _ = std::fs::remove_file(q); }
}
let r = { let cp = std::ffi::CString::new(p).unwrap_or_default();
let cq = std::ffi::CString::new(q).unwrap_or_default();
movefn(&cp, &cq)
};
if r != 0 { let osek = std::io::Error::last_os_error();
let errfile = if osek.raw_os_error() == Some(libc::ENOENT) && std::fs::symlink_metadata(p).is_ok() { q } else { p };
zwarnnam(nam, &format!("`{}': {}", errfile, osek)); return 1; }
0 }
pub fn recursivecmd_doone<P, R, L>( reccmd: &recursivecmd<P, R, L>, arg: &str, rp: &str, first: i32,
) -> i32 where
P: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
R: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
L: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
{
let st = std::fs::symlink_metadata(rp); if reccmd.opt_recurse != 0 { if let Ok(ref meta) = st {
if meta.is_dir() { return recursivecmd_dorec(reccmd, arg, rp, meta, first); }
}
}
let sp = st.as_ref().ok(); (reccmd.leaf_func)(arg, rp, sp) }
pub fn recursivecmd_dorec<P, R, L>( reccmd: &recursivecmd<P, R, L>, arg: &str, rp: &str,
sp: &std::fs::Metadata, _first: i32,
) -> i32 where
P: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
R: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
L: Fn(&str, &str, Option<&std::fs::Metadata>) -> i32,
{
let err1 = (reccmd.dirpre_func)(arg, rp, Some(sp)); if (err1 & 2) != 0 { return 2; } let dir = match std::fs::read_dir(rp) { Ok(d) => d,
Err(e) => {
if reccmd.opt_noerr == 0 { zwarnnam(reccmd.nam, &format!("{}: {}", arg, e)); }
return err1 | 1; }
};
let mut err = err1;
for entry in dir.flatten() { let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str == "." || name_str == ".." { continue; } let narg = format!("{}/{}", arg.trim_end_matches('/'), name_str); let nrp = entry.path();
let nrp_str = nrp.to_string_lossy();
if (err & 2) != 0 { break; } err |= recursivecmd_doone(reccmd, &narg, &nrp_str, 0); }
if (err & 2) != 0 { return 2; } err | (reccmd.dirpost_func)(arg, rp, Some(sp)) }
pub fn recurse_donothing(_arg: &str, _rp: &str, _sp: Option<&std::fs::Metadata>) -> i32 {
0 }
pub struct rmmagic<'a> {
pub nam: &'a str, pub opt_force: i32, pub opt_interact: i32, pub opt_unlinkdir: i32, }
pub fn rm_leaf(arg: &str, rp: &str, sp: Option<&std::fs::Metadata>, rmm: &rmmagic) -> i32 {
if rmm.opt_unlinkdir == 0 || rmm.opt_force == 0 { let owned;
let sp_use = if let Some(s) = sp { Some(s) } else { owned = std::fs::symlink_metadata(rp).ok(); owned.as_ref()
};
if let Some(meta) = sp_use { if rmm.opt_unlinkdir == 0 && meta.is_dir() { if rmm.opt_force != 0 { return 0; } zwarnnam(rmm.nam, &format!("{}: is a directory", arg));
return 1; }
if rmm.opt_interact != 0 { eprint!("{}: remove `{}'? ", rmm.nam, arg); if ask() == 0 { return 0; } } else if rmm.opt_force == 0 && !meta.file_type().is_symlink() && unsafe { let crp = std::ffi::CString::new(rp).ok();
crp.map(|c| libc::access(c.as_ptr(), libc::W_OK))
.unwrap_or(-1) != 0
} {
let mode = meta.permissions().mode() & 0o7777;
eprint!("{}: remove `{}', overriding mode {:04o}? ",
rmm.nam, arg, mode); if ask() == 0 { return 0; } }
}
}
let crp = match std::ffi::CString::new(rp) {
Ok(c) => c,
Err(_) => return 1,
};
if unsafe { libc::unlink(crp.as_ptr()) } != 0 && rmm.opt_force == 0 { zwarnnam(rmm.nam, &format!("{}: {}", arg, std::io::Error::last_os_error()));
return 1; }
0 }
pub fn rm_dirpost(arg: &str, rp: &str, _sp: Option<&std::fs::Metadata>, rmm: &rmmagic) -> i32 {
let crp = match std::ffi::CString::new(rp) {
Ok(c) => c,
Err(_) => return 1,
};
if unsafe { libc::rmdir(crp.as_ptr()) } != 0 && rmm.opt_force == 0 { zwarnnam(rmm.nam, &format!("{}: {}", arg, std::io::Error::last_os_error()));
return 1; }
0 }
pub fn bin_rm(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let rmm = rmmagic {
nam, opt_force: if OPT_ISSET(ops, b'f') { 1 } else { 0 }, opt_interact: if OPT_ISSET(ops, b'i') && !OPT_ISSET(ops, b'f') { 1 } else { 0 },
opt_unlinkdir: if OPT_ISSET(ops, b'd') { 1 } else { 0 }, };
let recurse = if !OPT_ISSET(ops, b'd') && (OPT_ISSET(ops, b'R') || OPT_ISSET(ops, b'r')) { 1 } else { 0 };
let safe = if OPT_ISSET(ops, b's') { 1 } else { 0 }; let err = recursivecmd(nam, rmm.opt_force, recurse, safe, args, |_a, _r, _s| 0, |a, r, s| rm_dirpost(a, r, s, &rmm), |a, r, s| rm_leaf(a, r, s, &rmm)); if rmm.opt_force != 0 { 0 } else { err } }
pub struct chmodmagic<'a> {
pub nam: &'a str, pub mode: u32, }
pub fn chmod_dochmod(arg: &str, rp: &str, _sp: Option<&std::fs::Metadata>, chm: &chmodmagic) -> i32 {
let crp = match std::ffi::CString::new(rp) {
Ok(c) => c,
Err(_) => return 1,
};
if unsafe { libc::chmod(crp.as_ptr(), chm.mode as libc::mode_t) } != 0 { zwarnnam(chm.nam, &format!("{}: {}", arg, std::io::Error::last_os_error()));
return 1; }
0 }
pub fn bin_chmod(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if args.is_empty() {
zwarnnam(nam, "missing mode");
return 1;
}
let mode = match i64::from_str_radix(&args[0], 8) { Ok(m) => m as u32,
Err(_) => {
zwarnnam(nam, &format!("invalid mode `{}'", args[0])); return 1; }
};
let chm = chmodmagic { nam, mode };
let recurse = if OPT_ISSET(ops, b'R') { 1 } else { 0 }; let safe = if OPT_ISSET(ops, b's') { 1 } else { 0 }; recursivecmd(nam, 0, recurse, safe, &args[1..], |a, r, s| chmod_dochmod(a, r, s, &chm), |_a, _r, _s| 0, |a, r, s| chmod_dochmod(a, r, s, &chm)) }
pub struct chownmagic<'a> {
pub nam: &'a str, pub uid: i64, pub gid: i64, }
pub fn chown_dochown(arg: &str, rp: &str, _sp: Option<&std::fs::Metadata>, chm: &chownmagic) -> i32 {
let crp = match std::ffi::CString::new(rp) {
Ok(c) => c,
Err(_) => return 1,
};
let uid = if chm.uid < 0 { libc::uid_t::MAX } else { chm.uid as libc::uid_t };
let gid = if chm.gid < 0 { libc::gid_t::MAX } else { chm.gid as libc::gid_t };
if unsafe { libc::chown(crp.as_ptr(), uid, gid) } != 0 { zwarnnam(chm.nam, &format!("{}: {}", arg, std::io::Error::last_os_error()));
return 1; }
0 }
pub fn chown_dolchown(arg: &str, rp: &str, _sp: Option<&std::fs::Metadata>, chm: &chownmagic) -> i32 {
let crp = match std::ffi::CString::new(rp) {
Ok(c) => c,
Err(_) => return 1,
};
let uid = if chm.uid < 0 { libc::uid_t::MAX } else { chm.uid as libc::uid_t };
let gid = if chm.gid < 0 { libc::gid_t::MAX } else { chm.gid as libc::gid_t };
if unsafe { libc::lchown(crp.as_ptr(), uid, gid) } != 0 { zwarnnam(chm.nam, &format!("{}: {}", arg, std::io::Error::last_os_error()));
return 1; }
0 }
pub fn bin_chown(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
if args.is_empty() {
zwarnnam(nam, "missing argument");
return 1;
}
let uspec = args[0].clone(); let mut chm = chownmagic { nam, uid: -1, gid: -1 };
let mut p_idx = 0usize;
let mut do_group_only = false;
if func == BIN_CHGRP { chm.uid = -1; do_group_only = true; } else {
let end = uspec.find(':').or_else(|| uspec.find('.')); if end == Some(0) { chm.uid = -1; p_idx = 1; do_group_only = true; } else {
let user_part = if let Some(e) = end { &uspec[..e] } else { &uspec[..] };
let cuser = std::ffi::CString::new(user_part).unwrap_or_default();
let pwd = unsafe { libc::getpwnam(cuser.as_ptr()) }; let uid = if !pwd.is_null() {
unsafe { (*pwd).pw_uid as i64 } } else {
let mut errp = 0i32;
let n = getnumeric(user_part, &mut errp); if errp != 0 { zwarnnam(nam, &format!("{}: no such user", user_part));
return 1; }
n as i64
};
chm.uid = uid;
if let Some(e) = end { let group_part = &uspec[e + 1..];
if group_part.is_empty() { let p2 = if !pwd.is_null() { pwd } else { unsafe { libc::getpwuid(uid as libc::uid_t) } };
if p2.is_null() { zwarnnam(nam, &format!("{}: no such user", uspec));
return 1; }
chm.gid = unsafe { (*p2).pw_gid as i64 }; } else if group_part == ":" { chm.gid = -1; } else {
p_idx = 0; let cgrp = std::ffi::CString::new(group_part).unwrap_or_default();
let grp = unsafe { libc::getgrnam(cgrp.as_ptr()) }; if !grp.is_null() {
chm.gid = unsafe { (*grp).gr_gid as i64 }; } else {
let mut errp = 0i32;
let n = getnumeric(group_part, &mut errp); if errp != 0 { zwarnnam(nam, &format!("{}: no such group", group_part));
return 1; }
chm.gid = n as i64;
}
}
}
}
}
if do_group_only { let group_part = &uspec[p_idx..];
let cgrp = std::ffi::CString::new(group_part).unwrap_or_default();
let grp = unsafe { libc::getgrnam(cgrp.as_ptr()) }; if !grp.is_null() {
chm.gid = unsafe { (*grp).gr_gid as i64 }; } else {
let mut errp = 0i32;
let n = getnumeric(group_part, &mut errp); if errp != 0 { zwarnnam(nam, &format!("{}: no such group", group_part));
return 1; }
chm.gid = n as i64;
}
}
let recurse = if OPT_ISSET(ops, b'R') { 1 } else { 0 }; let safe = if OPT_ISSET(ops, b's') { 1 } else { 0 }; let h_flag = OPT_ISSET(ops, b'h'); recursivecmd(nam, 0, recurse, safe, &args[1..], |a, r, s| if h_flag { chown_dolchown(a, r, s, &chm) }
else { chown_dochown(a, r, s, &chm) }, |_a, _r, _s| 0, |a, r, s| if h_flag { chown_dolchown(a, r, s, &chm) }
else { chown_dochown(a, r, s, &chm) }) }
pub const LN_OPTS: &str = "dfhins";
#[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
}
pub const BIN_CHOWN: i32 = 0;
use crate::ported::zsh_h::module;
pub const BIN_CHGRP: i32 = 1;
#[allow(non_camel_case_types)]
pub type MoveFunc = fn(p: &std::ffi::CStr, q: &std::ffi::CStr) -> i32;
pub fn mv_rename(p: &std::ffi::CStr, q: &std::ffi::CStr) -> i32 {
unsafe { libc::rename(p.as_ptr(), q.as_ptr()) }
}
pub fn mv_symlink(p: &std::ffi::CStr, q: &std::ffi::CStr) -> i32 {
unsafe { libc::symlink(p.as_ptr(), q.as_ptr()) }
}
pub fn mv_link(p: &std::ffi::CStr, q: &std::ffi::CStr) -> i32 {
unsafe { libc::link(p.as_ptr(), q.as_ptr()) }
}
pub fn getnumeric(p: &str, errp: &mut i32) -> u64 { if !p.chars().next().is_some_and(|c| c.is_ascii_digit()) { *errp = 1; return 0; }
let end = p.find(|c: char| !c.is_ascii_digit()).unwrap_or(p.len());
let ret = p[..end].parse::<u64>().unwrap_or(0); *errp = if end < p.len() { 1 } else { 0 }; ret }
use crate::ported::zsh_h::features as features_t;
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:chgrp".to_string(), "b:chmod".to_string(), "b:chown".to_string(), "b:ln".to_string(), "b:mkdir".to_string(), "b:mv".to_string(), "b:rm".to_string(), "b:rmdir".to_string(), "b:sync".to_string(), "b:zf_chgrp".to_string(), "b:zf_chmod".to_string(), "b:zf_chown".to_string(), "b:zf_ln".to_string(), "b:zf_mkdir".to_string(), "b:zf_mv".to_string(), "b:zf_rm".to_string(), "b:zf_rmdir".to_string(), "b:zf_sync".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 18]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 18,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_ops() -> crate::ported::zsh_h::options {
crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(), argscount: 0, argsalloc: 0,
}
}
#[test]
fn domkdir_fails_when_path_already_exists() {
let tmp = std::env::temp_dir();
assert_ne!(domkdir("mkdir", tmp.to_str().unwrap(), 0o755, 0), 0);
}
#[test]
fn mkdir_with_invalid_mode_short_circuits_before_filesystem_op() {
let mut ops = empty_ops();
ops.ind[b'm' as usize] = (1 << 2) | 1;
ops.args.push("not-octal".to_string());
let r = bin_mkdir("mkdir", &["/tmp/zshrs_test_invalid_mode".to_string()], &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn domkdir_with_p_flag_on_existing_dir_returns_zero() {
let tmp = std::env::temp_dir();
let r = domkdir("mkdir", tmp.to_str().unwrap(), 0o755, 1);
assert_eq!(r, 0, "mkdir -p /tmp must succeed when /tmp already exists");
}
#[test]
fn domkdir_without_p_on_existing_dir_fails() {
let tmp = std::env::temp_dir();
let r = domkdir("mkdir", tmp.to_str().unwrap(), 0o755, 0);
assert_ne!(r, 0, "mkdir (no -p) on existing dir must fail per POSIX");
}
#[test]
fn bin_mkdir_with_no_args_returns_zero() {
let ops = empty_ops();
let r = bin_mkdir("mkdir", &[], &ops, 0);
assert_eq!(r, 0, "bin_mkdir on empty argv falls through the for loop");
}
#[test]
fn bin_mkdir_p_creates_nested_path() {
let mut ops = empty_ops();
ops.ind[b'p' as usize] = (1 << 2) | 1;
let pid = std::process::id();
let base = format!("/tmp/zshrs_test_mkdir_p_{}", pid);
let nested = format!("{}/a/b/c", base);
let _ = std::fs::remove_dir_all(&base);
let r = bin_mkdir("mkdir", &[nested.clone()], &ops, 0);
assert_eq!(r, 0, "mkdir -p should create the whole chain");
assert!(std::path::Path::new(&nested).is_dir(),
"leaf dir {} should exist after mkdir -p", nested);
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn bin_mkdir_strips_trailing_slashes() {
let pid = std::process::id();
let target = format!("/tmp/zshrs_test_mkdir_trailing_{}/", pid);
let _ = std::fs::remove_dir_all(target.trim_end_matches('/'));
let ops = empty_ops();
let r = bin_mkdir("mkdir", &[target.clone()], &ops, 0);
assert_eq!(r, 0, "trailing slash should not break mkdir");
assert!(std::path::Path::new(target.trim_end_matches('/')).is_dir());
let _ = std::fs::remove_dir_all(target.trim_end_matches('/'));
}
#[test]
fn bin_rmdir_on_nonexistent_path_returns_nonzero() {
let ops = empty_ops();
let r = bin_rmdir(
"rmdir",
&["/__definitely_not_a_dir_xyzzy_zshrs_test__".to_string()],
&ops, 0,
);
assert_ne!(r, 0, "rmdir of nonexistent path must report failure");
}
#[test]
fn bin_rmdir_with_no_args_returns_zero() {
let ops = empty_ops();
let r = bin_rmdir("rmdir", &[], &ops, 0);
assert_eq!(r, 0);
}
#[test]
fn bin_rmdir_removes_an_empty_directory() {
let pid = std::process::id();
let target = format!("/tmp/zshrs_test_rmdir_{}", pid);
let _ = std::fs::remove_dir_all(&target);
std::fs::create_dir(&target).expect("setup mkdir");
let ops = empty_ops();
let r = bin_rmdir("rmdir", &[target.clone()], &ops, 0);
assert_eq!(r, 0, "rmdir on existing empty dir should succeed");
assert!(!std::path::Path::new(&target).exists(),
"directory should be gone after rmdir");
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let null_module = std::ptr::null();
assert_eq!(setup_(null_module), 0);
assert_eq!(boot_(null_module), 0);
assert_eq!(cleanup_(null_module), 0);
assert_eq!(finish_(null_module), 0);
}
#[test]
fn bin_rm_no_args_returns_zero() {
let ops = empty_ops();
let r = bin_rm("rm", &[], &ops, 0);
assert_eq!(r, 0);
}
#[test]
fn bin_rm_nonexistent_path_returns_one() {
let ops = empty_ops();
let r = bin_rm("rm", &["/__zshrs_test_no_such_path__".to_string()], &ops, 0);
assert_ne!(r, 0, "rm on nonexistent path must error");
}
#[test]
fn rm_leaf_removes_existing_file() {
let pid = std::process::id();
let f = format!("/tmp/zshrs_test_rm_leaf_{}.txt", pid);
let _ = std::fs::write(&f, "x");
assert!(std::path::Path::new(&f).exists());
let rmm = rmmagic { nam: "rm", opt_force: 1, opt_interact: 0, opt_unlinkdir: 0 };
let r = rm_leaf(&f, &f, None, &rmm);
assert_eq!(r, 0, "rm_leaf on existing file must succeed");
assert!(!std::path::Path::new(&f).exists(),
"file must be gone after rm_leaf");
}
#[test]
fn rm_leaf_force_silently_succeeds_on_missing() {
let rmm = rmmagic { nam: "rm", opt_force: 1, opt_interact: 0, opt_unlinkdir: 0 };
let r = rm_leaf("/__never__", "/__never__", None, &rmm);
assert_eq!(r, 0, "rm -f on missing path must succeed silently");
}
#[test]
fn bin_chmod_no_args_returns_one() {
let ops = empty_ops();
let r = bin_chmod("chmod", &[], &ops, 0);
assert_eq!(r, 1, "missing mode must surface as error");
}
#[test]
fn bin_chown_no_args_returns_one() {
let ops = empty_ops();
let r = bin_chown("chown", &[], &ops, 0);
assert_eq!(r, 1, "missing owner must surface as error");
}
#[test]
fn bin_ln_one_arg_returns_nonzero() {
let ops = empty_ops();
let r = bin_ln("ln", &["/tmp".to_string()], &ops, 0);
assert_ne!(r, 0, "ln with <2 args must error");
}
#[test]
fn bin_sync_returns_zero_regardless_of_args() {
let ops = empty_ops();
assert_eq!(bin_sync("sync", &[], &ops, 0), 0);
assert_eq!(bin_sync("sync", &["ignored".to_string()], &ops, 0), 0);
assert_eq!(bin_sync("sync",
&["a".to_string(), "b".to_string()], &ops, 0), 0);
}
}