use crate::ported::params::{setaparam, sethparam, setsparam};
use crate::ported::utils::{zstrtol, ztrftime, zwarnnam};
use crate::ported::zsh_h::{features, module, options};
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::sync::{Mutex, OnceLock};
pub const HNAMEKEY: &str = "name";
pub fn statmodeprint(mode: u32, flags: i32) -> String {
let mut out = String::new();
if (flags & STF_RAW) != 0 {
if (flags & STF_OCTAL) != 0 {
out.push_str(&format!("0{:o}", mode));
} else {
out.push_str(&format!("{}", mode));
}
if (flags & STF_STRING) != 0 {
out.push_str(" ("); }
}
if (flags & STF_STRING) != 0 {
let modes = b"?rwxrwxrwx";
let mut pm = [b'-'; 10];
let ifmt = mode & 0o170_000; pm[0] = match ifmt {
0o020_000 => b'c', 0o040_000 => b'd', 0o060_000 => b'b', 0o100_000 => b'-', 0o120_000 => b'l', 0o140_000 => b's', 0o010_000 => b'p', _ => b'?',
};
let bits = [
0o0400, 0o0200, 0o0100, 0o0040, 0o0020, 0o0010, 0o0004, 0o0002, 0o0001,
];
for i in 0..9 {
pm[i + 1] = if (mode & bits[i]) != 0 {
modes[i + 1]
} else {
b'-'
};
}
if (mode & 0o4000) != 0 {
pm[3] = if (mode & 0o0100) != 0 { b's' } else { b'S' };
}
if (mode & 0o2000) != 0 {
pm[6] = if (mode & 0o0010) != 0 { b's' } else { b'S' };
}
if (mode & 0o1000) != 0 {
pm[9] = if (mode & 0o0001) != 0 { b't' } else { b'T' };
}
out.push_str(std::str::from_utf8(&pm).unwrap_or(""));
if (flags & STF_RAW) != 0 {
out.push(')'); }
}
out
}
pub fn statuidprint(uid: u32, flags: i32) -> String {
let mut out = String::new();
if (flags & STF_RAW) != 0 {
out.push_str(&format!("{}", uid));
if (flags & STF_STRING) != 0 {
out.push_str(" (");
}
}
if (flags & STF_STRING) != 0 {
let name = unsafe {
let p = libc::getpwuid(uid);
if p.is_null() {
String::new()
} else {
let nm = (*p).pw_name;
if nm.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(nm).to_string_lossy().into_owned()
}
}
};
if name.is_empty() {
out.push_str(&format!("{}", uid));
} else {
out.push_str(&name); }
if (flags & STF_RAW) != 0 {
out.push(')');
}
}
out
}
pub fn statgidprint(gid: u32, flags: i32) -> String {
let mut out = String::new();
if (flags & STF_RAW) != 0 {
out.push_str(&format!("{}", gid));
if (flags & STF_STRING) != 0 {
out.push_str(" (");
}
}
if (flags & STF_STRING) != 0 {
let name = unsafe {
let g = libc::getgrgid(gid); if g.is_null() {
String::new()
} else {
let nm = (*g).gr_name;
if nm.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(nm).to_string_lossy().into_owned()
}
}
};
if name.is_empty() {
out.push_str(&format!("{}", gid)); } else {
out.push_str(&name); }
if (flags & STF_RAW) != 0 {
out.push(')');
}
}
out
}
const TIMEFMT_DEFAULT: &str = "%a %b %e %k:%M:%S %Z %Y";
static TIMEFMT: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new();
pub fn stattimeprint(tim: i64, _nsecs: i64, flags: i32) -> String {
let mut out = String::new();
if (flags & STF_RAW) != 0 {
out.push_str(&format!("{}", tim));
if (flags & STF_STRING) != 0 {
out.push_str(" (");
}
}
if (flags & STF_STRING) != 0 {
let st = std::time::UNIX_EPOCH + std::time::Duration::from_secs(tim.max(0) as u64);
let fmt: String = TIMEFMT
.get_or_init(|| std::sync::Mutex::new(TIMEFMT_DEFAULT.to_string()))
.lock()
.map(|g| g.clone())
.unwrap_or_else(|_| TIMEFMT_DEFAULT.to_string());
let formatted = ztrftime(&fmt, st);
out.push_str(&formatted);
if (flags & STF_RAW) != 0 {
out.push(')');
}
}
out
}
pub fn statulprint(num: u64) -> String {
format!("{}", num) }
pub fn statlinkprint(sbuf_mode: u32, fname: &str) -> String {
if (sbuf_mode & 0o170_000) != 0o120_000 {
return String::new();
}
fs::read_link(fname) .map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default()
}
pub fn statprint(meta: &fs::Metadata, fname: &str, iwhich: i32, flags: i32) -> String {
let name_prefix = if (flags & STF_NAME) != 0 {
let n = STATELTS.get(iwhich as usize).copied().unwrap_or("");
if (flags & (STF_PICK | STF_ARRAY)) != 0 {
format!("{} ", n) } else {
format!("{:<8}", n) }
} else {
String::new()
};
let val = match iwhich {
ST_DEV => format!("{}", meta.dev()), ST_INO => format!("{}", meta.ino()), ST_MODE => statmodeprint(meta.mode(), flags), ST_NLINK => format!("{}", meta.nlink()), ST_UID => statuidprint(meta.uid(), flags), ST_GID => statgidprint(meta.gid(), flags), ST_RDEV => format!("{}", meta.rdev()), ST_SIZE => statulprint(meta.size()), ST_ATIM => stattimeprint(meta.atime(), 0, flags), ST_MTIM => stattimeprint(meta.mtime(), 0, flags), ST_CTIM => stattimeprint(meta.ctime(), 0, flags), ST_BLKSIZE => statulprint(meta.blksize()), ST_BLOCKS => statulprint(meta.blocks()), ST_READLINK => statlinkprint(meta.mode(), fname), _ => String::new(),
};
format!("{}{}", name_prefix, val)
}
pub fn bin_stat(
nam: &str,
args: &[String], _ops_unused: &options,
_func: i32,
) -> i32 {
let mut iwhich: i32 = -1; let mut flags: i32 = 0;
let mut found = 0i32; let mut arrnam: Option<String> = None;
let mut hashnam: Option<String> = None;
let mut fd: i32 = 0;
if let Ok(mut g) = TIMEFMT
.get_or_init(|| std::sync::Mutex::new(TIMEFMT_DEFAULT.to_string()))
.lock()
{
g.clear();
g.push_str(TIMEFMT_DEFAULT);
}
let mut ops = [false; 256];
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let mut argv: Vec<&str> = Vec::with_capacity(args.len());
let mut i = 0;
while i < args.len() && (args[i].starts_with('+') || args[i].starts_with('-')) {
let arg = &args[i][1..];
if arg.is_empty() || arg.starts_with('-') || arg.starts_with('+') {
i += 1;
break; }
if args[i].starts_with('+') {
if found != 0 {
break;
}
for (idx, name) in STATELTS.iter().enumerate() {
if name.starts_with(arg) {
found += 1;
iwhich = idx as i32;
}
}
if found > 1 {
zwarnnam(nam, &format!("{}: ambiguous stat element", arg));
return 1;
} else if found == 0 {
zwarnnam(nam, &format!("{}: no such stat element", arg));
return 1;
}
if iwhich == ST_READLINK {
ops[b'L' as usize] = true;
}
flags |= STF_PICK; } else {
for ch in arg.chars() {
match ch {
'g' | 'l' | 'L' | 'n' | 'N' | 'o' | 'r' | 's' | 't' | 'T' => {
ops[ch as u8 as usize] = true; }
'A' => {
i += 1;
if i >= args.len() {
zwarnnam(nam, "missing parameter name");
return 1;
}
arrnam = Some(args[i].to_string());
flags |= STF_ARRAY;
break;
}
'H' => {
i += 1;
if i >= args.len() {
zwarnnam(nam, "missing parameter name");
return 1;
}
hashnam = Some(args[i].to_string());
flags |= STF_HASH;
break;
}
'f' => {
ops[b'f' as usize] = true;
i += 1;
if i >= args.len() {
zwarnnam(nam, "missing file descriptor");
return 1;
}
let (val, endptr) = zstrtol(args[i], 10);
if !endptr.is_empty() {
zwarnnam(nam, "bad file descriptor");
return 1;
}
fd = val as i32;
break;
}
'F' => {
let inline: &str = &arg[arg.find('F').unwrap() + 1..];
let fmt: &str = if !inline.is_empty() {
inline
} else {
i += 1;
if i >= args.len() {
zwarnnam(nam, "missing time format");
return 1;
}
args[i]
};
if let Ok(mut g) = TIMEFMT
.get_or_init(|| std::sync::Mutex::new(TIMEFMT_DEFAULT.to_string()))
.lock()
{
g.clear();
g.push_str(fmt);
}
ops[b's' as usize] = true; break;
}
_ => {
zwarnnam(nam, &format!("bad option: -{}", ch));
return 1;
}
}
}
}
i += 1;
}
while i < args.len() {
argv.push(args[i]);
i += 1;
}
let _ = fd;
if (flags & STF_ARRAY) != 0 && (flags & STF_HASH) != 0 {
zwarnnam(nam, "both array and hash requested");
return 1;
}
if ops[b'l' as usize] {
if let Some(ref name) = arrnam {
let joined: Vec<&str> = STATELTS.iter().copied().collect();
setsparam(name, &joined.join(":"));
} else {
let joined: Vec<&str> = STATELTS.iter().copied().collect();
println!("{}", joined.join(" ")); }
return 0; }
if argv.is_empty() && !ops[b'f' as usize] {
zwarnnam(nam, "no files given");
return 1;
} else if !argv.is_empty() && ops[b'f' as usize] {
zwarnnam(nam, "no files allowed with -f");
return 1;
}
let use_lstat = ops[b'L' as usize];
let mut hash_out: Vec<(String, String)> = Vec::new();
let mut array_out: Vec<String> = Vec::new();
let show_type = ops[b't' as usize]; let mut local_flags = flags;
if ops[b's' as usize] {
local_flags |= STF_STRING;
} if ops[b'r' as usize] || !ops[b's' as usize] {
local_flags |= STF_RAW;
}
if ops[b'n' as usize] {
local_flags |= STF_FILE;
} if ops[b'o' as usize] {
local_flags |= STF_OCTAL;
} if ops[b't' as usize] {
local_flags |= STF_NAME;
} if arrnam.is_none() && hashnam.is_none() {
if argv.len() > 1 {
local_flags |= STF_FILE;
} if (local_flags & STF_PICK) == 0 {
local_flags |= STF_NAME; }
}
if ops[b'N' as usize] || ops[b'f' as usize] {
local_flags &= !STF_FILE;
}
if ops[b'T' as usize] || ops[b'H' as usize] {
local_flags &= !STF_NAME;
}
let _ = show_type;
let mut rc: i32 = 0;
for path in &argv {
let meta = if use_lstat {
fs::symlink_metadata(path)
} else {
fs::metadata(path)
};
let meta = match meta {
Ok(m) => m,
Err(e) => {
let msg = crate::ported::compat::strerror(e.raw_os_error().unwrap_or(0));
zwarnnam(nam, &format!("{}: {}", path, msg));
rc = 1;
continue;
}
};
if (local_flags & STF_FILE) != 0 && arrnam.is_none() && hashnam.is_none() {
if (local_flags & STF_PICK) != 0 {
print!("{} ", path); } else {
println!("{}:", path);
}
}
if iwhich >= 0 {
let val = statprint(&meta, path, iwhich, local_flags);
if let Some(ref aname) = arrnam {
array_out.push(val);
let _ = aname;
} else if let Some(ref hname) = hashnam {
hash_out.push((STATELTS[iwhich as usize].to_string(), val));
let _ = hname;
} else {
println!("{}", val); }
} else {
for idx in 0..STATELTS.len() {
let val = statprint(&meta, path, idx as i32, local_flags);
if let Some(_) = &arrnam {
array_out.push(val);
} else if let Some(_) = &hashnam {
hash_out.push((STATELTS[idx].to_string(), val));
} else {
println!("{}", val); }
}
}
}
if let Some(name) = arrnam {
setaparam(&name, array_out); }
if let Some(name) = hashnam {
let mut flat: Vec<String> = Vec::with_capacity(hash_out.len() * 2);
for (k, v) in hash_out {
flat.push(k);
flat.push(v);
}
sethparam(&name, flat); }
rc
}
#[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 ST_DEV: i32 = 0; pub const ST_INO: i32 = 1;
pub const ST_MODE: i32 = 2;
pub const ST_NLINK: i32 = 3;
pub const ST_UID: i32 = 4;
pub const ST_GID: i32 = 5;
pub const ST_RDEV: i32 = 6;
pub const ST_SIZE: i32 = 7;
pub const ST_ATIM: i32 = 8;
pub const ST_MTIM: i32 = 9;
pub const ST_CTIM: i32 = 10;
pub const ST_BLKSIZE: i32 = 11;
pub const ST_BLOCKS: i32 = 12;
pub const ST_READLINK: i32 = 13;
pub const ST_COUNT: i32 = 14;
pub const STF_NAME: i32 = 1; pub const STF_FILE: i32 = 2;
pub const STF_STRING: i32 = 4;
pub const STF_RAW: i32 = 8;
pub const STF_PICK: i32 = 16;
pub const STF_ARRAY: i32 = 32;
pub const STF_GMT: i32 = 64;
pub const STF_HASH: i32 = 128;
pub const STF_OCTAL: i32 = 256;
pub static STATELTS: &[&str] = &[
"device", "inode", "mode", "nlink", "uid", "gid", "rdev", "size", "atime", "mtime", "ctime",
"blksize", "blocks", "link",
];
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec!["b:stat".to_string(), "b:zstat".to_string()]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 2]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
bn_list: None,
bn_size: 2,
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::*;
use std::fs::File;
use std::io::Write;
#[test]
fn statelts_count_matches_st_count() {
let _g = crate::test_util::global_state_lock();
assert_eq!(STATELTS.len() as i32, ST_COUNT);
}
#[test]
fn statmodeprint_octal_only() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0o100644, STF_RAW | STF_OCTAL);
assert!(s.starts_with('0'));
assert!(s.contains("644"));
}
#[test]
fn statmodeprint_string_only() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0o100644, STF_STRING);
assert_eq!(s.len(), 10);
assert_eq!(&s[..1], "-");
}
#[test]
fn statmodeprint_directory() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0o040755, STF_STRING);
assert_eq!(&s[..1], "d");
}
#[test]
fn statulprint_decimal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(statulprint(12345), "12345");
}
#[test]
fn statprint_size_via_index() {
let _g = crate::test_util::global_state_lock();
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("x.txt");
File::create(&path).unwrap().write_all(b"hello").unwrap();
let meta = fs::metadata(&path).unwrap();
let s = statprint(&meta, path.to_str().unwrap(), ST_SIZE, 0);
assert_eq!(s, "5");
}
#[test]
fn statmodeprint_file_type_chars_match_ls_output() {
let _g = crate::test_util::global_state_lock();
assert_eq!(&statmodeprint(0o100_644, STF_STRING)[..1], "-");
assert_eq!(&statmodeprint(0o040_755, STF_STRING)[..1], "d");
assert_eq!(&statmodeprint(0o120_777, STF_STRING)[..1], "l");
assert_eq!(&statmodeprint(0o020_644, STF_STRING)[..1], "c");
assert_eq!(&statmodeprint(0o060_644, STF_STRING)[..1], "b");
assert_eq!(&statmodeprint(0o010_644, STF_STRING)[..1], "p");
assert_eq!(&statmodeprint(0o140_644, STF_STRING)[..1], "s");
}
#[test]
fn statmodeprint_setuid_setgid_sticky_render_correctly() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0o104_755, STF_STRING);
assert_eq!(
s.chars().nth(3),
Some('s'),
"setuid+x must render as 's' in user-execute slot"
);
let s = statmodeprint(0o104_644, STF_STRING);
assert_eq!(
s.chars().nth(3),
Some('S'),
"setuid without x must render as 'S' (uppercase)"
);
let s = statmodeprint(0o102_755, STF_STRING);
assert_eq!(
s.chars().nth(6),
Some('s'),
"setgid+gx must render as 's' in group-execute slot"
);
let s = statmodeprint(0o102_644, STF_STRING);
assert_eq!(
s.chars().nth(6),
Some('S'),
"setgid without gx must render as 'S'"
);
let s = statmodeprint(0o101_755, STF_STRING);
assert_eq!(
s.chars().nth(9),
Some('t'),
"sticky+ox must render as 't' in other-execute slot"
);
let s = statmodeprint(0o101_644, STF_STRING);
assert_eq!(
s.chars().nth(9),
Some('T'),
"sticky without ox must render as 'T'"
);
}
#[test]
fn statmodeprint_raw_and_string_renders_with_parens() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0o100_644, STF_RAW | STF_STRING);
assert!(s.contains(" ("), "missing ' (' separator: {}", s);
assert!(s.ends_with(')'), "missing closing ')': {}", s);
let open = s.find('(').unwrap();
let close = s.rfind(')').unwrap();
assert_eq!(
close - open - 1,
10,
"expected 10-char ls-mode between parens, got: {:?}",
&s[open + 1..close]
);
}
#[test]
fn statmodeprint_zero_mode_renders_unknown_type_no_perms() {
let _g = crate::test_util::global_state_lock();
let s = statmodeprint(0, STF_STRING);
assert_eq!(s.len(), 10);
assert_eq!(&s[..1], "?", "mode with no S_IFMT bits → unknown");
assert_eq!(&s[1..], "---------", "no permission bits → all dashes");
}
#[test]
fn statuidprint_raw_is_decimal() {
let _g = crate::test_util::global_state_lock();
let s = statuidprint(1000, STF_RAW);
assert_eq!(s, "1000");
}
#[test]
fn statuidprint_uid_zero_resolves_to_root() {
let _g = crate::test_util::global_state_lock();
let s = statuidprint(0, STF_STRING);
assert!(
!s.parse::<u32>().is_ok(),
"uid 0 fell back to numeric form: {}",
s
);
assert!(!s.is_empty());
}
#[test]
fn statgidprint_raw_is_decimal() {
let _g = crate::test_util::global_state_lock();
let s = statgidprint(100, STF_RAW);
assert_eq!(s, "100");
}
#[test]
fn statulprint_zero_renders_as_zero_digit() {
let _g = crate::test_util::global_state_lock();
assert_eq!(statulprint(0), "0");
}
#[test]
fn statulprint_u64_max_renders_full_digits() {
let _g = crate::test_util::global_state_lock();
let s = statulprint(u64::MAX);
assert_eq!(s, "18446744073709551615");
}
#[test]
fn stf_flag_values_are_distinct_single_bits() {
let _g = crate::test_util::global_state_lock();
for f in [
STF_NAME, STF_FILE, STF_STRING, STF_RAW, STF_PICK, STF_ARRAY, STF_GMT, STF_HASH,
STF_OCTAL,
] {
assert!(f > 0, "STF_* flag {} must be positive", f);
assert_eq!(
(f as u32).count_ones(),
1,
"STF_* flag {} = 0b{:b} must be a single bit",
f,
f
);
}
let flags = [
STF_NAME, STF_FILE, STF_STRING, STF_RAW, STF_PICK, STF_ARRAY, STF_GMT, STF_HASH,
STF_OCTAL,
];
for (i, &a) in flags.iter().enumerate() {
for &b in &flags[i + 1..] {
assert_eq!(a & b, 0, "STF flags {} and {} overlap", a, b);
}
}
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn stat_corpus_modeprint_regular_644() {
let mode = 0o100_644; let r = statmodeprint(mode, STF_STRING);
assert_eq!(r, "-rw-r--r--", "regular 0644 → -rw-r--r--, got {r:?}");
}
#[test]
fn stat_corpus_modeprint_regular_755() {
let mode = 0o100_755;
let r = statmodeprint(mode, STF_STRING);
assert_eq!(r, "-rwxr-xr-x");
}
#[test]
fn stat_corpus_modeprint_directory() {
let mode = 0o040_755; let r = statmodeprint(mode, STF_STRING);
assert_eq!(r, "drwxr-xr-x");
}
#[test]
fn stat_corpus_modeprint_symlink_prefix() {
let mode = 0o120_777; let r = statmodeprint(mode, STF_STRING);
assert!(
r.starts_with('l'),
"symlink mode starts with 'l', got {r:?}"
);
}
#[test]
fn stat_corpus_modeprint_fifo_prefix() {
let mode = 0o010_644;
let r = statmodeprint(mode, STF_STRING);
assert!(r.starts_with('p'), "FIFO mode starts with 'p', got {r:?}");
}
#[test]
fn stat_corpus_modeprint_socket_prefix() {
let mode = 0o140_644;
let r = statmodeprint(mode, STF_STRING);
assert!(r.starts_with('s'));
}
#[test]
fn stat_corpus_modeprint_raw_octal() {
let mode = 0o100_644;
let r = statmodeprint(mode, STF_RAW | STF_OCTAL);
assert!(
r.starts_with('0'),
"octal raw form starts with leading 0, got {r:?}"
);
assert!(
r.contains("100644") || r.contains("644"),
"octal repr contains 100644 or 644, got {r:?}"
);
}
#[test]
fn stat_corpus_modeprint_raw_decimal() {
let mode = 0o100_644;
let r = statmodeprint(mode, STF_RAW);
assert_eq!(r, "33188", "raw decimal form, got {r:?}");
}
#[test]
fn stat_corpus_modeprint_no_flags_empty() {
let r = statmodeprint(0o100_644, 0);
assert_eq!(r, "", "no flags → empty output");
}
#[test]
fn statmodeprint_regular_file_starts_with_dash() {
let r = statmodeprint(0o100_644, STF_STRING);
assert!(r.starts_with('-'), "regular file → '-', got {:?}", r);
}
#[test]
fn statmodeprint_directory_starts_with_d() {
let r = statmodeprint(0o040_755, STF_STRING);
assert!(r.starts_with('d'), "directory → 'd', got {:?}", r);
}
#[test]
fn statmodeprint_symlink_starts_with_l() {
let r = statmodeprint(0o120_777, STF_STRING);
assert!(r.starts_with('l'), "symlink → 'l', got {:?}", r);
}
#[test]
fn statmodeprint_char_device_starts_with_c() {
let r = statmodeprint(0o020_644, STF_STRING);
assert!(r.starts_with('c'), "char device → 'c', got {:?}", r);
}
#[test]
fn statmodeprint_block_device_starts_with_b() {
let r = statmodeprint(0o060_644, STF_STRING);
assert!(r.starts_with('b'), "block device → 'b', got {:?}", r);
}
#[test]
fn statmodeprint_socket_starts_with_s() {
let r = statmodeprint(0o140_777, STF_STRING);
assert!(r.starts_with('s'), "socket → 's', got {:?}", r);
}
#[test]
fn statmodeprint_fifo_starts_with_p() {
let r = statmodeprint(0o010_644, STF_STRING);
assert!(r.starts_with('p'), "FIFO → 'p', got {:?}", r);
}
#[test]
fn statmodeprint_unknown_ifmt_returns_question_mark() {
let r = statmodeprint(0o070_000, STF_STRING);
assert!(r.starts_with('?'), "unknown ifmt → '?', got {:?}", r);
}
#[test]
fn statmodeprint_setuid_with_exec_shows_lowercase_s() {
let r = statmodeprint(0o104_755, STF_STRING);
assert_eq!(r.as_bytes()[3], b's', "setuid+x → 's', got {:?}", r);
}
#[test]
fn statmodeprint_setuid_without_exec_shows_uppercase_S() {
let r = statmodeprint(0o104_644, STF_STRING);
assert_eq!(r.as_bytes()[3], b'S', "setuid-no-x → 'S', got {:?}", r);
}
#[test]
fn statmodeprint_sticky_bit_dispatch() {
let r1 = statmodeprint(0o101_777, STF_STRING);
assert_eq!(r1.as_bytes()[9], b't', "sticky+other-x → 't'");
let r2 = statmodeprint(0o101_644, STF_STRING);
assert_eq!(r2.as_bytes()[9], b'T', "sticky no other-x → 'T'");
}
#[test]
fn statulprint_decimal_format() {
assert_eq!(statulprint(0), "0");
assert_eq!(statulprint(42), "42");
assert_eq!(statulprint(u64::MAX), format!("{}", u64::MAX));
}
#[test]
fn statulprint_is_pure() {
for v in [0u64, 1, 42, 1_000_000, u64::MAX] {
let first = statulprint(v);
for _ in 0..5 {
assert_eq!(statulprint(v), first, "statulprint({}) must be pure", v);
}
}
}
#[test]
fn statulprint_output_is_ascii_digits() {
for v in [0u64, 1, 42, u32::MAX as u64, u64::MAX] {
let s = statulprint(v);
assert!(!s.is_empty(), "non-empty");
assert!(
s.chars().all(|c| c.is_ascii_digit()),
"all chars must be digits: {:?}",
s
);
}
}
#[test]
fn statmodeprint_is_pure() {
let mode = 0o100644u32;
let first = statmodeprint(mode, 0);
for _ in 0..5 {
assert_eq!(statmodeprint(mode, 0), first, "statmodeprint must be pure");
}
}
#[test]
fn statmodeprint_no_flags_returns_empty() {
assert_eq!(statmodeprint(0o100644, 0), "");
}
#[test]
fn stattimeprint_is_pure() {
let t = 1_700_000_000i64;
let first = stattimeprint(t, 0, 0);
for _ in 0..5 {
assert_eq!(stattimeprint(t, 0, 0), first, "stattimeprint must be pure");
}
}
#[test]
fn statlinkprint_non_symlink_returns_empty() {
let r = statlinkprint(0o100644, "/tmp/__not_symlink__");
assert!(
r.is_empty() || r == "" || r.starts_with(" "),
"non-symlink should return empty or marker, got {:?}",
r
);
}
#[test]
fn statlinkprint_empty_fname_no_panic() {
let _ = statlinkprint(0o100644, "");
}
#[test]
fn statuidprint_is_pure() {
for uid in [0u32, 1, 1000, u32::MAX] {
let first = statuidprint(uid, 0);
for _ in 0..5 {
assert_eq!(
statuidprint(uid, 0),
first,
"statuidprint({}, 0) must be pure",
uid
);
}
}
}
#[test]
fn statgidprint_is_pure() {
for gid in [0u32, 1, 1000, u32::MAX] {
let first = statgidprint(gid, 0);
for _ in 0..5 {
assert_eq!(
statgidprint(gid, 0),
first,
"statgidprint({}, 0) must be pure",
gid
);
}
}
}
#[test]
fn statmodeprint_returns_string_type() {
let _: String = statmodeprint(0o100644, 0);
}
#[test]
fn statuidprint_returns_string_type() {
let _: String = statuidprint(0, 0);
}
#[test]
fn statgidprint_returns_string_type() {
let _: String = statgidprint(0, 0);
}
#[test]
fn stattimeprint_returns_string_type() {
let _: String = stattimeprint(0, 0, 0);
}
#[test]
fn statulprint_returns_string_type() {
let _: String = statulprint(0);
}
#[test]
fn statlinkprint_returns_string_type() {
let _: String = statlinkprint(0o100644, "");
}
#[test]
fn statulprint_zero_returns_zero_digit() {
assert_eq!(statulprint(0), "0", "0 → \"0\" canonical");
}
#[test]
fn statulprint_u64_max_returns_canonical_decimal() {
let s = statulprint(u64::MAX);
assert_eq!(s, u64::MAX.to_string(), "u64::MAX matches std decimal");
}
#[test]
fn bin_stat_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_stat("stat", &[], &ops, 0);
}
#[test]
fn stat_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn stat_features_nonempty() {
let _g = crate::test_util::global_state_lock();
let mut feats = Vec::new();
features_(std::ptr::null(), &mut feats);
assert!(!feats.is_empty(), "stat must advertise ≥1 feature");
}
#[test]
fn stat_features_use_canonical_prefix() {
let _g = crate::test_util::global_state_lock();
let mut feats = Vec::new();
features_(std::ptr::null(), &mut feats);
for f in &feats {
assert!(
f.starts_with("b:") || f.starts_with("p:"),
"feature {:?} must use b:/p: prefix",
f
);
}
}
#[test]
fn statulprint_length_grows_with_magnitude() {
let a = statulprint(10).len();
let b = statulprint(100).len();
let c = statulprint(1000).len();
assert!(a <= b, "len(10) ≤ len(100), got {} vs {}", a, b);
assert!(b <= c, "len(100) ≤ len(1000), got {} vs {}", b, c);
}
#[test]
fn stat_full_lifecycle_returns_zero_for_all() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
let mut feats = Vec::new();
let _ = features_(null, &mut feats);
let mut enables: Option<Vec<i32>> = None;
let _ = enables_(null, &mut enables);
assert_eq!(boot_(null), 0);
assert_eq!(cleanup_(null), 0);
assert_eq!(finish_(null), 0);
}
#[test]
fn statmodeprint_returns_string_pin_alt() {
let _: String = statmodeprint(0o644, 0);
}
#[test]
fn statmodeprint_deterministic_for_common_modes() {
for mode in [0o644u32, 0o755, 0o600, 0o777, 0o000] {
let a = statmodeprint(mode, 0);
let b = statmodeprint(mode, 0);
assert_eq!(a, b, "statmodeprint({:o}) must be pure", mode);
}
}
#[test]
fn statuidprint_returns_string_pin_alt() {
let _: String = statuidprint(0, 0);
}
#[test]
fn statuidprint_root_with_raw_flag_returns_zero_digit() {
let s = statuidprint(0, STF_RAW);
assert_eq!(s, "0", "uid 0 + STF_RAW must produce '0'");
}
#[test]
fn statgidprint_returns_string_pin_alt() {
let _: String = statgidprint(0, 0);
}
#[test]
fn statgidprint_zero_with_raw_flag_returns_zero_digit() {
let s = statgidprint(0, STF_RAW);
assert_eq!(s, "0", "gid 0 + STF_RAW must produce '0'");
}
#[test]
fn stattimeprint_returns_string_pin_alt() {
let _: String = stattimeprint(0, 0, 0);
}
#[test]
fn stattimeprint_epoch_zero_with_raw_flag_returns_zero() {
let s = stattimeprint(0, 0, STF_RAW);
assert_eq!(s, "0", "epoch 0 + STF_RAW must produce '0'");
}
#[test]
fn bin_stat_exit_code_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
for argv in [
vec![],
vec!["/tmp".into()],
vec!["/dev/null".into()],
vec!["/__nonexistent_xyz__".into()],
] {
let r = bin_stat("stat", &argv, &ops, 0);
assert!(
r >= 0,
"exit code must be non-negative, got {} for {:?}",
r,
argv
);
}
}
#[test]
fn bin_stat_nonexistent_path_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_stat("stat", &["/__definitely_no_such_xyz__".into()], &ops, 0);
assert_ne!(r, 0, "nonexistent path → nonzero");
}
#[test]
fn statulprint_deterministic_for_powers_of_two() {
for shift in 0..64 {
let v = 1u64 << shift;
let a = statulprint(v);
let b = statulprint(v);
assert_eq!(a, b, "statulprint({}) must be pure", v);
}
}
}