use crate::ported::exec::ShellExecutor;
use crate::ported::utils::zwarnnam;
use std::fs;
use std::os::unix::fs::MetadataExt;
pub const HNAMEKEY: &str = "name";
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",
];
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
}
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 formatted = crate::ported::utils::ztrftime("%a %b %e %k:%M:%S %Z %Y", 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: &crate::ported::zsh_h::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;
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) = crate::ported::utils::zstrtol(args[i], 10);
if !endptr.is_empty() {
zwarnnam(nam, "bad file descriptor");
return 1;
}
fd = val as i32;
break;
}
'F' => {
i += 1;
if i >= args.len() {
zwarnnam(nam, "missing time format");
return 1;
}
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();
crate::ported::params::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;
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) => {
zwarnnam(nam, &format!("{}: {}", path, e));
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 { crate::ported::params::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);
}
crate::ported::params::sethparam(&name, flat); }
0
}
use crate::ported::zsh_h::module;
#[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(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
#[test]
fn statelts_count_matches_st_count() {
assert_eq!(STATELTS.len() as i32, ST_COUNT);
}
#[test]
fn statmodeprint_octal_only() {
let s = statmodeprint(0o100644, STF_RAW | STF_OCTAL);
assert!(s.starts_with('0'));
assert!(s.contains("644"));
}
#[test]
fn statmodeprint_string_only() {
let s = statmodeprint(0o100644, STF_STRING);
assert_eq!(s.len(), 10);
assert_eq!(&s[..1], "-");
}
#[test]
fn statmodeprint_directory() {
let s = statmodeprint(0o040755, STF_STRING);
assert_eq!(&s[..1], "d");
}
#[test]
fn statulprint_decimal() {
assert_eq!(statulprint(12345), "12345");
}
#[test]
fn statprint_size_via_index() {
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");
}
}
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
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,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:stat".to_string(), "b:zstat".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; 2]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}