use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use crate::ported::zsh_system_h::timespec;
use std::os::unix::fs::MetadataExt;
pub fn zgettime(ts: &mut timespec) -> i32 { let mut ret: i32 = -1; unsafe {
let mut dts: timespec = std::mem::zeroed();
if libc::clock_gettime(libc::CLOCK_REALTIME, &mut dts) < 0 { crate::ported::utils::zwarn(&format!(
"unable to retrieve time: {}",
std::io::Error::last_os_error()
));
ret -= 1; } else { ret += 1; ts.tv_sec = dts.tv_sec; ts.tv_nsec = dts.tv_nsec; }
if ret != 0 { let mut dtv: libc::timeval = std::mem::zeroed(); libc::gettimeofday(&mut dtv, std::ptr::null_mut()); ret += 1; ts.tv_sec = dtv.tv_sec; ts.tv_nsec = (dtv.tv_usec as libc::c_long) * 1000; }
}
ret }
pub fn zgettime_monotonic_if_available(ts: &mut timespec) -> i32 { let mut ret: i32 = -1; unsafe {
let mut dts: timespec = std::mem::zeroed(); #[cfg(target_os = "macos")]
let clk = libc::CLOCK_MONOTONIC_RAW;
#[cfg(not(target_os = "macos"))]
let clk = libc::CLOCK_MONOTONIC;
if libc::clock_gettime(clk, &mut dts) < 0 { crate::ported::utils::zwarn(&format!(
"unable to retrieve CLOCK_MONOTONIC time: {}",
std::io::Error::last_os_error()
));
ret -= 1; } else {
ret += 1; ts.tv_sec = dts.tv_sec; ts.tv_nsec = dts.tv_nsec; }
}
if ret != 0 { ret = zgettime(ts); }
ret }
pub fn difftime(t2: i64, t1: i64) -> f64 { (t2 - t1) as f64
}
pub fn strerror(errnum: i32) -> String { std::io::Error::from_raw_os_error(errnum).to_string()
}
pub fn zopenmax() -> i64 { const ZSH_INITIAL_OPEN_MAX: i64 =
crate::ported::zsh_system_h::ZSH_INITIAL_OPEN_MAX as i64; const OPEN_MAX: i64 =
crate::ported::zsh_system_h::OPEN_MAX as i64;
#[cfg(unix)]
{
unsafe {
let mut openmax = libc::sysconf(libc::_SC_OPEN_MAX);
if openmax < 1 {
openmax = OPEN_MAX;
} else if openmax > OPEN_MAX {
if openmax > ZSH_INITIAL_OPEN_MAX {
openmax = ZSH_INITIAL_OPEN_MAX;
}
let mut j = OPEN_MAX;
let mut i = j;
while i < openmax {
let r = libc::fcntl(i as i32, libc::F_GETFL, 0);
if r < 0 {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if e == libc::EBADF || e == libc::EINTR {
if e != libc::EINTR {
i += 1;
}
continue;
}
}
j = i;
i += 1;
}
openmax = j;
}
openmax
}
}
#[cfg(not(unix))]
{
OPEN_MAX
}
}
pub fn zgetdir(d: Option<&mut crate::ported::zsh_h::dirsav>) -> Option<String> { let cwd = env::current_dir().ok()?;
let cwd_str = cwd.to_str()?.to_string();
#[cfg(unix)]
if let Some(dirsav) = d {
if let Ok(meta) = fs::metadata(&cwd) {
dirsav.ino = meta.ino();
dirsav.dev = meta.dev();
}
dirsav.dirname = Some(cwd_str.clone());
}
#[cfg(not(unix))]
if let Some(dirsav) = d {
dirsav.dirname = Some(cwd_str.clone());
}
Some(cwd_str)
}
pub fn zgetcwd() -> Option<String> { env::current_dir()
.ok()
.and_then(|p| p.to_str().map(|s| s.to_string()))
}
pub fn zchdir(dir: &str) -> i32 { if dir.is_empty() {
return 0;
}
if env::set_current_dir(dir).is_ok() {
return 0;
}
let path = Path::new(dir);
if !path.is_absolute() {
return -1;
}
let saved_dir = env::current_dir().ok();
let mut current = PathBuf::from("/");
for component in path.components().skip(1) {
current.push(component);
if env::set_current_dir(¤t).is_err() {
if let Some(ref saved) = saved_dir {
if env::set_current_dir(saved).is_err() {
return -2; }
}
return -1;
}
}
0
}
pub fn output64(val: i64) -> String { val.to_string()
}
pub fn u9_wcwidth(ucs: char) -> i32 { unicode_width::UnicodeWidthChar::width(ucs)
.map(|w| w as i32)
.unwrap_or(if ucs.is_control() { -1 } else { 1 })
}
pub fn u9_iswprint(ucs: char) -> bool { !ucs.is_control() && u9_wcwidth(ucs) >= 0
}
pub fn isprint_ascii(c: char) -> bool { let b = c as u32;
(0x20..=0x7e).contains(&b)
}
pub fn strstr(s: &str, t: &str) -> Option<usize> { s.find(t) }
pub fn gettimeofday() -> (i64, i64) { #[cfg(unix)] {
let mut tv: libc::timeval = unsafe { std::mem::zeroed() };
unsafe { libc::gettimeofday(&mut tv, std::ptr::null_mut()); } (tv.tv_sec as i64, tv.tv_usec as i64)
}
#[cfg(not(unix))] { (0, 0) }
}
pub fn strtoul(nptr: &str, base: u32) -> (u64, usize) { let bytes = nptr.as_bytes();
let mut i = 0;
while i < bytes.len() && bytes[i].is_ascii_whitespace() { i += 1; } let neg = i < bytes.len() && bytes[i] == b'-'; if neg || (i < bytes.len() && bytes[i] == b'+') { i += 1; } let (radix, start) = if (base == 0 || base == 16) && bytes.get(i).copied() == Some(b'0')
&& bytes.get(i+1).map(|b| b.eq_ignore_ascii_case(&b'x')).unwrap_or(false) {
(16u32, i + 2) } else if base == 0 {
(if bytes.get(i).copied() == Some(b'0') { 8 } else { 10 }, i) } else {
(base, i)
};
let mut acc: u64 = 0;
let mut consumed = start;
for &b in &bytes[start..] {
let digit = if b.is_ascii_digit() { (b - b'0') as u32 }
else if b.is_ascii_uppercase() { (b - b'A' + 10) as u32 }
else if b.is_ascii_lowercase() { (b - b'a' + 10) as u32 }
else { break };
if digit >= radix { break; }
acc = acc.saturating_mul(radix as u64).saturating_add(digit as u64);
consumed += 1;
}
(if neg { acc.wrapping_neg() } else { acc }, consumed)
}
pub fn zpathmax(dir: &str) -> i64 { #[cfg(unix)] unsafe {
let mut buf: Vec<u8> = dir.as_bytes().to_vec(); #[cfg(target_os = "macos")]
let errno_loc: *mut libc::c_int = libc::__error();
#[cfg(target_os = "linux")]
let errno_loc: *mut libc::c_int = libc::__errno_location();
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let errno_loc: *mut libc::c_int = std::ptr::null_mut();
if errno_loc.is_null() {
let dirlen = buf.len() as i64;
let path_max = 4096i64;
return if dirlen >= path_max { -1 } else { path_max - dirlen };
}
let mut accumulated_taillen: libc::c_long = 0; loop {
let cs = match std::ffi::CString::new(buf.clone()) {
Ok(c) => c,
Err(_) => return -1,
};
*errno_loc = 0; let pathmax = libc::pathconf(cs.as_ptr(), libc::_PC_PATH_MAX); if pathmax >= 0 { if accumulated_taillen == 0 {
return pathmax as i64; }
if accumulated_taillen < pathmax {
return (pathmax - accumulated_taillen) as i64; } else {
*errno_loc = libc::ENAMETOOLONG; return -1;
}
}
let err = *errno_loc;
if err != libc::EINVAL && err != libc::ENOENT && err != libc::ENOTDIR {
return if *errno_loc != 0 { -1 } else { 0 }; }
let tail_pos: Option<usize> = buf.iter().rposition(|&b| b == b'/');
let mut tail = match tail_pos { Some(t) => t, None => {
*errno_loc = 0;
let dot = std::ffi::CString::new(".").unwrap();
let pm = libc::pathconf(dot.as_ptr(), libc::_PC_PATH_MAX);
let taillen = (buf.len() + 1) as libc::c_long;
if pm > 0 && taillen < pm {
return (pm - taillen) as i64; }
if pm > 0 { *errno_loc = libc::ENAMETOOLONG; } return if *errno_loc != 0 { -1 } else { 0 }; }};
while tail > 0 && buf[tail - 1] == b'/' { tail -= 1; } let taillen_now = (buf.len() - tail) as libc::c_long; accumulated_taillen += taillen_now;
if tail > 0 { buf.truncate(tail); continue;
} else {
*errno_loc = 0;
let root = std::ffi::CString::new("/").unwrap();
let pm = libc::pathconf(root.as_ptr(), libc::_PC_PATH_MAX);
if pm > 0 && accumulated_taillen < pm {
return (pm - accumulated_taillen) as i64; }
if pm > 0 { *errno_loc = libc::ENAMETOOLONG; } return if *errno_loc != 0 { -1 } else { 0 }; }
}
}
#[cfg(not(unix))] {
let dirlen = dir.len() as i64;
let path_max = 4096i64;
if dirlen >= path_max { -1 } else { path_max - dirlen }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zgettime() {
let mut ts: timespec = unsafe { std::mem::zeroed() };
let r = zgettime(&mut ts);
assert!(r >= 0);
assert!(ts.tv_sec > 0);
}
#[test]
fn test_zgettime_monotonic() {
let mut t1: timespec = unsafe { std::mem::zeroed() };
let mut t2: timespec = unsafe { std::mem::zeroed() };
let r1 = zgettime_monotonic_if_available(&mut t1);
std::thread::sleep(std::time::Duration::from_millis(10));
let r2 = zgettime_monotonic_if_available(&mut t2);
assert!(r1 >= 0 && r2 >= 0);
let elapsed_ns = (t2.tv_sec - t1.tv_sec) * 1_000_000_000
+ (t2.tv_nsec - t1.tv_nsec) as i64;
assert!(elapsed_ns > 0);
}
#[test]
fn test_zgetcwd() {
let cwd = zgetcwd();
assert!(cwd.is_some());
assert!(!cwd.unwrap().is_empty());
}
#[test]
fn test_zopenmax() {
let max = zopenmax();
assert!(max > 0);
}
#[test]
fn test_isprint_safe() {
assert!(isprint_ascii('a'));
assert!(isprint_ascii('Z'));
assert!(isprint_ascii(' '));
assert!(!isprint_ascii('\x00'));
assert!(!isprint_ascii('\x1f'));
}
#[test]
fn test_wcwidth() {
assert_eq!(u9_wcwidth('a'), 1);
assert_eq!(u9_wcwidth('中'), 2);
assert!(u9_wcwidth('\x00') <= 0);
}
#[test]
fn strstr_substring_hit_returns_byte_offset() {
assert_eq!(strstr("hello world", "world"), Some(6));
assert_eq!(strstr("hello world", "hello"), Some(0));
assert_eq!(strstr("hello world", "xyz"), None);
assert_eq!(strstr("", "x"), None);
assert_eq!(strstr("anything", ""), Some(0));
}
#[cfg(unix)]
#[test]
fn gettimeofday_returns_positive_secs() {
let (sec, _usec) = gettimeofday();
assert!(sec > 1_000_000_000, "epoch seconds should be past 2001");
}
#[test]
fn strtoul_parses_decimal() {
let (v, n) = strtoul("12345", 10);
assert_eq!(v, 12345);
assert_eq!(n, 5);
}
#[test]
fn strtoul_parses_hex_with_0x_prefix_when_base_zero() {
let (v, n) = strtoul("0xff", 0);
assert_eq!(v, 255);
assert_eq!(n, 4);
}
#[test]
fn strtoul_parses_octal_when_base_zero_with_leading_zero() {
let (v, _n) = strtoul("0777", 0);
assert_eq!(v, 511);
}
#[test]
fn strtoul_skips_leading_whitespace() {
let (v, _) = strtoul(" 42", 10);
assert_eq!(v, 42);
}
#[test]
fn strtoul_stops_at_first_non_digit() {
let (v, n) = strtoul("100abc", 10);
assert_eq!(v, 100);
assert_eq!(n, 3);
}
#[test]
fn difftime_returns_signed_double_difference() {
assert_eq!(difftime(1_700_000_010, 1_700_000_000), 10.0);
assert_eq!(difftime(1_700_000_000, 1_700_000_010), -10.0,
"c:178 — signed cast; t1 > t2 must be negative");
assert_eq!(difftime(42, 42), 0.0);
}
#[test]
fn isprint_ascii_matches_strict_ascii_printable_range() {
assert!(isprint_ascii(' '), "c:786 — 0x20 is printable");
assert!(isprint_ascii('~'), "c:786 — 0x7e is printable");
assert!(!isprint_ascii('\x1f'), "c:786 — 0x1f is NOT printable");
assert!(!isprint_ascii('\x7f'), "c:786 — DEL is NOT printable");
assert!(isprint_ascii('A'));
assert!(isprint_ascii('0'));
assert!(isprint_ascii('!'));
assert!(!isprint_ascii('\t'));
assert!(!isprint_ascii('\n'));
assert!(!isprint_ascii('\0'));
assert!(!isprint_ascii('é'), "c:786 — non-ASCII outside range");
assert!(!isprint_ascii('字'), "c:786 — wide char outside range");
}
#[test]
fn output64_formats_i64_boundaries_and_zero() {
assert_eq!(output64(0), "0");
assert_eq!(output64(42), "42");
assert_eq!(output64(-1), "-1");
assert_eq!(output64(i64::MAX), "9223372036854775807");
assert_eq!(output64(i64::MIN), "-9223372036854775808");
}
#[test]
fn u9_iswprint_accepts_printable_rejects_controls() {
assert!(u9_iswprint('a'));
assert!(u9_iswprint(' '));
assert!(u9_iswprint('é'), "Latin-1 letter is printable");
assert!(u9_iswprint('字'), "CJK ideograph is printable");
assert!(!u9_iswprint('\0'));
assert!(!u9_iswprint('\t'));
assert!(!u9_iswprint('\n'));
assert!(!u9_iswprint('\x07'));
assert!(!u9_iswprint('\x1b'));
assert!(!u9_iswprint('\x7f'), "DEL is a C0 control");
}
#[test]
fn u9_wcwidth_returns_canonical_widths() {
assert_eq!(u9_wcwidth('\x07'), -1);
assert_eq!(u9_wcwidth('a'), 1);
assert_eq!(u9_wcwidth(' '), 1);
assert_eq!(u9_wcwidth('字'), 2);
assert_eq!(u9_wcwidth('\u{0301}'), 0);
}
#[test]
fn strerror_returns_non_empty_string_for_known_errno() {
let s = strerror(2 );
assert!(!s.is_empty(), "c:194 — strerror must return non-empty for ENOENT");
}
#[test]
fn zopenmax_caps_within_canonical_ladder() {
assert_eq!(crate::ported::zsh_system_h::ZSH_INITIAL_OPEN_MAX, 64,
"Src/zsh_system.h:307 — ZSH_INITIAL_OPEN_MAX must be 64");
let m = zopenmax();
assert!(m > 0, "c:307 — zopenmax must report a positive ceiling");
}
}