#![allow(non_camel_case_types, non_snake_case)]
use crate::ported::utils::zwarnnam;
use crate::ported::zsh_h::{OPT_ARG, OPT_ISSET, module, options};
use std::io::Read;
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
use std::sync::{Mutex, OnceLock};
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 {
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: &options,
_func: i32,
) -> i32 {
unsafe {
libc::sync();
} 0 }
pub fn bin_mkdir(
nam: &str,
args: &[String], ops: &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: &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: &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: &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: &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: &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;
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 }
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: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<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 18]);
}
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: 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() -> options {
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_rmdir("rmdir", &[], &ops, 0);
assert_eq!(r, 0);
}
#[test]
fn bin_rmdir_removes_an_empty_directory() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_rm("rm", &[], &ops, 0);
assert_eq!(r, 0);
}
#[test]
fn bin_rm_nonexistent_path_returns_one() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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
);
}
#[test]
#[ignore = "ZSHRS BUG: bin_mkdir with no args returns 0 instead of usage error per POSIX"]
fn files_corpus_bin_mkdir_no_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_mkdir("mkdir", &[], &ops, 0);
assert_ne!(r, 0, "mkdir with no args = usage error");
}
#[test]
#[ignore = "ZSHRS BUG: bin_rmdir with no args returns 0 instead of usage error per POSIX"]
fn files_corpus_bin_rmdir_no_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_rmdir("rmdir", &[], &ops, 0);
assert_ne!(r, 0, "rmdir with no args = usage error");
}
#[test]
#[ignore = "ZSHRS BUG: bin_rm with no args returns 0 instead of usage error per POSIX"]
fn files_corpus_bin_rm_no_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_rm("rm", &[], &ops, 0);
assert_ne!(r, 0, "rm with no args = usage error");
}
#[test]
fn files_corpus_bin_mkdir_creates_directory() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("newdir");
let target_str = target.to_str().unwrap().to_string();
let r = bin_mkdir("mkdir", &[target_str], &ops, 0);
assert_eq!(r, 0, "mkdir on new path succeeds");
assert!(target.exists(), "new dir actually created");
assert!(target.is_dir(), "new path is a directory");
}
#[test]
fn files_corpus_bin_rmdir_removes_empty_dir() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("toremove");
std::fs::create_dir(&target).unwrap();
assert!(target.exists());
let r = bin_rmdir("rmdir", &[target.to_str().unwrap().to_string()], &ops, 0);
assert_eq!(r, 0, "rmdir on empty dir succeeds");
assert!(!target.exists(), "dir removed");
}
#[test]
fn files_corpus_domkdir_creates_with_mode() {
let _g = crate::test_util::global_state_lock();
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("modedir");
let r = domkdir("mkdir", target.to_str().unwrap(), 0o755, 0);
assert_eq!(r, 0);
assert!(target.is_dir());
}
#[test]
fn files_corpus_domkdir_existing_path_fails_without_p() {
let _g = crate::test_util::global_state_lock();
let dir = tempfile::tempdir().unwrap();
let r = domkdir("mkdir", dir.path().to_str().unwrap(), 0o755, 0);
assert_ne!(r, 0, "mkdir on existing dir without -p = error");
}
}