use crate::ported::utils::zwarnnam;
pub fn handle_digits( nam: &str,
argptr: &str,
fdset: &mut libc::fd_set,
fdmax: &mut libc::c_int,
) -> i32 {
let first = argptr.chars().next();
if !matches!(first, Some(c) if c.is_ascii_digit()) { zwarnnam(nam, &format!("expecting file descriptor: {}", argptr));
return 1; }
let (fd_val, endptr) = crate::ported::utils::zstrtol(argptr, 10);
let fd = fd_val as libc::c_int;
if !endptr.is_empty() { zwarnnam(nam, &format!("garbage after file descriptor: {}", endptr));
return 1; }
unsafe { libc::FD_SET(fd, fdset); } if fd + 1 > *fdmax { *fdmax = fd + 1; }
0 }
pub fn bin_zselect(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let args = &args[..];
let mut fdset: [libc::fd_set; 3] = unsafe { std::mem::zeroed() };
for s in &mut fdset { unsafe { libc::FD_ZERO(s); }
}
let fdchar: [u8; 3] = *b"rwe"; let mut fdmax: libc::c_int = 0; let mut fdsetind: usize = 0; let mut tv: libc::timeval = libc::timeval { tv_sec: 0, tv_usec: 0 };
let mut have_timeout = false; let mut outarray: String = "reply".to_string(); let mut outhash: Option<String> = None;
let mut i = 0;
while i < args.len() { let arg = args[i];
if let Some(rest) = arg.strip_prefix('-') { let mut chars: Vec<char> = rest.chars().collect();
let mut j = 0;
while j < chars.len() { let c = chars[j];
match c {
'a' | 'A' => { let arg_str: String = if j + 1 < chars.len() {
j += 1; chars[j..].iter().collect()
} else if i + 1 < args.len() {
i += 1; args[i].to_string()
} else {
zwarnnam(nam, &format!("argument expected after -{}", c));
return 1; };
if arg_str.is_empty()
|| arg_str.chars().next().unwrap().is_ascii_digit()
|| !is_ident(&arg_str)
{
zwarnnam(nam, &format!("invalid array name: {}", arg_str));
return 1;
}
if c == 'a' { outarray = arg_str; } else { outhash = Some(arg_str); }
break;
}
'r' => fdsetind = 0, 'w' => fdsetind = 1, 'e' => fdsetind = 2, 't' => { let arg_str: String = if j + 1 < chars.len() {
j += 1;
chars[j..].iter().collect()
} else if i + 1 < args.len() {
i += 1;
args[i].to_string()
} else {
zwarnnam(nam, &format!("argument expected after -{}", c));
return 1;
};
let first = arg_str.chars().next();
if !matches!(first, Some(d) if d.is_ascii_digit()) { zwarnnam(nam, "number expected after -t");
return 1;
}
let (tempnum, endptr) =
crate::ported::utils::zstrtol(&arg_str, 10);
if !endptr.is_empty() { zwarnnam(nam, &format!("garbage after -t argument: {}", endptr));
return 1; }
have_timeout = true;
tv.tv_sec = (tempnum / 100) as libc::time_t;
tv.tv_usec = ((tempnum % 100) * 10000) as libc::suseconds_t;
break; }
_ => { let argptr_rest: String = chars[j..].iter().collect();
if handle_digits(nam, &argptr_rest, &mut fdset[fdsetind], &mut fdmax) != 0 {
return 1; }
break; }
}
j += 1;
}
} else if handle_digits(nam, arg, &mut fdset[fdsetind], &mut fdmax) != 0 { return 1; }
i += 1;
}
let tvptr: *mut libc::timeval = if have_timeout { &mut tv } else { std::ptr::null_mut() };
let mut sel: libc::c_int;
loop {
sel = unsafe {
libc::select(
fdmax,
&mut fdset[0],
&mut fdset[1],
&mut fdset[2],
tvptr,
)
};
if sel >= 0 { break; }
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINTR) { continue; } break;
}
if sel <= 0 { if sel < 0 { zwarnnam(nam, &format!(
"error on select: {}",
std::io::Error::last_os_error()
)); }
return 1; }
if let Some(hash_name) = &outhash { let mut hash: indexmap::IndexMap<String, String> = indexmap::IndexMap::new();
for ii in 0..3 { for fd in 0..fdmax { if unsafe { libc::FD_ISSET(fd, &fdset[ii]) } { let key = fd.to_string();
let mask_char = fdchar[ii] as char;
hash.entry(key.clone())
.and_modify(|v| {
if !v.contains(mask_char) { v.push(mask_char); }
})
.or_insert_with(|| mask_char.to_string());
}
}
}
let pairs: Vec<String> = hash.into_iter()
.map(|(k, v)| format!("{}={}", k, v)).collect();
crate::ported::params::setsparam(hash_name, &pairs.join("\t"));
} else {
let mut out: Vec<String> = Vec::new();
for ii in 0..3 { let mut emitted_flag = false; for fd in 0..fdmax { if unsafe { libc::FD_ISSET(fd, &fdset[ii]) } { if !emitted_flag { out.push(format!("-{}", fdchar[ii] as char)); emitted_flag = true; }
out.push(fd.to_string()); }
}
}
crate::ported::params::setsparam(&outarray, &out.join(":"));
}
0 }
fn is_ident(s: &str) -> bool {
if s.is_empty() { return false; }
let mut chars = s.chars();
let first = chars.next().unwrap();
if first.is_ascii_digit() { return false; }
if !(first.is_alphanumeric() || first == '_') { return false; }
chars.all(|c| c.is_alphanumeric() || c == '_')
}
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::*;
fn empty_ops_zs() -> crate::ported::zsh_h::options {
use crate::ported::zsh_h::{options, MAX_OPS};
options { ind: [0u8; MAX_OPS], args: Vec::new(),
argscount: 0, argsalloc: 0 }
}
fn s(args: &[&str]) -> Vec<String> {
args.iter().map(|a| a.to_string()).collect()
}
#[test]
fn empty_args_with_zero_timeout_returns_one() {
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-t", "0"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn invalid_array_name_returns_one() {
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-a", "1bad"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn timeout_garbage_returns_one() {
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-t", "100x"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn no_arg_after_a_returns_one() {
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-a"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn handle_digits_invalid_input() {
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe { libc::FD_ZERO(&mut fdset); }
let mut fdmax: libc::c_int = 0;
assert_eq!(handle_digits("zselect", "abc", &mut fdset, &mut fdmax), 1);
assert_eq!(handle_digits("zselect", "12abc", &mut fdset, &mut fdmax), 1);
}
#[test]
fn handle_digits_sets_fd_and_fdmax() {
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe { libc::FD_ZERO(&mut fdset); }
let mut fdmax: libc::c_int = 0;
assert_eq!(handle_digits("zselect", "5", &mut fdset, &mut fdmax), 0);
assert_eq!(fdmax, 6);
assert!(unsafe { libc::FD_ISSET(5, &fdset) });
}
}
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: 1,
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:zselect".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; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}