#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::ported::init::init_io;
use crate::ported::params::setsparam;
use crate::ported::utils::{unmetafy, zerrnam, zwarnnam};
use crate::ported::zsh_h::{features, module, options, MAX_OPS};
use std::ffi::CString;
use std::os::unix::io::RawFd;
#[cfg(unix)]
#[allow(unused_variables)]
pub fn bin_clone(nam: &str, args: &[String], ops: &options, func: i32) -> i32 {
let ttyfd: RawFd;
let pid: libc::pid_t;
let cttyfd: RawFd;
let arg0_in: &str = match args.first() {
Some(a) => a.as_str(),
None => {
zwarnnam(nam, "terminal required");
return 1;
}
};
let mut arg0_bytes = arg0_in.as_bytes().to_vec();
unmetafy(&mut arg0_bytes);
let arg0: String = String::from_utf8_lossy(&arg0_bytes).into_owned();
let tty_c = match CString::new(arg0.clone()) {
Ok(c) => c,
Err(_) => {
zwarnnam(nam, &format!("{}: invalid tty path", arg0));
return 1;
}
};
ttyfd = unsafe { libc::open(tty_c.as_ptr(), libc::O_RDWR | libc::O_NOCTTY) };
if ttyfd < 0 {
zwarnnam(
nam,
&format!("{}: {}", arg0, std::io::Error::last_os_error()),
); return 1; }
pid = unsafe { libc::fork() };
if pid == 0 {
if let Some(tab) = crate::ported::jobs::JOBTAB.get() {
if let Ok(mut jobs) = tab.lock() {
jobs.clear();
}
}
let mypid = unsafe { libc::getpid() };
if unsafe { libc::setsid() } != mypid {
zwarnnam(
nam,
&format!(
"failed to create new session: {}",
std::io::Error::last_os_error()
), );
}
unsafe {
libc::dup2(ttyfd, 0); libc::dup2(ttyfd, 1); libc::dup2(ttyfd, 2); }
if ttyfd > 2 {
unsafe { libc::close(ttyfd) };
}
unsafe { libc::close(coprocin.load(Ordering::Relaxed)) }; unsafe { libc::close(coprocout.load(Ordering::Relaxed)) }; cttyfd = unsafe { libc::open(tty_c.as_ptr(), libc::O_RDWR) };
if cttyfd == -1 {
zwarnnam(nam, &format!("{}", std::io::Error::last_os_error())); } else {
#[cfg(any(target_os = "linux", target_os = "macos"))]
unsafe {
libc::ioctl(cttyfd, libc::TIOCSCTTY as _, 0);
}
unsafe { libc::close(cttyfd) }; }
let dev_tty = b"/dev/tty\0".as_ptr() as *const libc::c_char;
let cttyfd2 = unsafe { libc::open(dev_tty, libc::O_RDWR) };
if cttyfd2 == -1 {
zwarnnam(
nam,
&format!(
"could not make {} my controlling tty, job control disabled",
arg0
),
);
} else {
unsafe { libc::close(cttyfd2) }; }
mypgrp.store(0, Ordering::Relaxed); init_io(None); let tty_name = ttystrname.lock().unwrap().clone();
setsparam("TTY", &tty_name); } else {
unsafe { libc::close(ttyfd) }; }
if pid < 0 {
zerrnam(
nam,
&format!("fork failed: {}", std::io::Error::last_os_error()),
); return 1; }
lastpid.store(pid as i32, Ordering::Relaxed); 0 }
#[cfg(not(unix))]
#[allow(unused_variables)]
pub fn bin_clone(nam: &str, args: &[String], ops: &options, func: i32) -> i32 {
zwarnnam(nam, "not available on this host");
1
}
#[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 static coprocin: AtomicI32 = AtomicI32::new(-1);
pub static coprocout: AtomicI32 = AtomicI32::new(-1);
pub static mypgrp: AtomicI32 = AtomicI32::new(0);
pub static lastpid: AtomicI32 = AtomicI32::new(0);
pub static ttystrname: Mutex<String> = Mutex::new(String::new());
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec!["b:clone".to_string()]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
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: 1,
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; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
#[test]
#[cfg(unix)]
fn bin_clone_no_args_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
assert_eq!(bin_clone("clone", &[], &ops, 0), 1);
}
#[test]
#[cfg(unix)]
fn bin_clone_invalid_tty_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let rc = bin_clone("clone", &["/nonexistent/tty".to_string()], &ops, 0);
assert_eq!(rc, 1);
}
#[test]
fn module_loaders_return_zero() {
let _g = crate::test_util::global_state_lock();
let mut features: Vec<String> = Vec::new();
let mut enables: Option<Vec<i32>> = None;
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(features_(m, &mut features), 0);
assert_eq!(features, vec!["b:clone"]);
assert_eq!(enables_(m, &mut enables), 0);
assert!(enables.is_some());
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn features_emits_exactly_one_b_clone_string() {
let _g = crate::test_util::global_state_lock();
let mut feats: Vec<String> = Vec::new();
features_(std::ptr::null(), &mut feats);
assert_eq!(feats.len(), 1);
assert_eq!(feats[0], "b:clone");
}
#[test]
fn enables_returns_some_with_at_least_one_entry() {
let _g = crate::test_util::global_state_lock();
let mut enables: Option<Vec<i32>> = None;
enables_(std::ptr::null(), &mut enables);
let e = enables.expect("must return Some");
assert!(
!e.is_empty(),
"enables vec must contain ≥1 entry for the b:clone feature"
);
}
#[test]
#[cfg(unix)]
fn bin_clone_with_extra_arg_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let rc = bin_clone(
"clone",
&["/nonexistent1".to_string(), "/nonexistent2".to_string()],
&ops,
0,
);
assert_eq!(rc, 1, "more than 1 arg must be rejected");
}
#[test]
fn features_string_uses_b_prefix() {
let _g = crate::test_util::global_state_lock();
let mut feats: Vec<String> = Vec::new();
features_(std::ptr::null(), &mut feats);
let f = &feats[0];
assert!(
f.starts_with("b:"),
"feature {} must use 'b:' prefix per zsh module spec",
f
);
assert_eq!(
&f[2..],
"clone",
"feature 'b:<name>' suffix must be 'clone'"
);
}
#[test]
fn module_lifecycle_stubs_accept_null_module() {
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 bin_clone_no_args_returns_one_pin() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone("clone", &[], &ops, 0);
assert_eq!(r, 1, "no args → 1 (terminal required)");
}
#[test]
fn bin_clone_nonexistent_tty_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone(
"clone",
&["/__never_exists_zshrs_tty__".to_string()],
&ops,
0,
);
assert_eq!(r, 1, "open of nonexistent tty → 1");
}
#[test]
#[cfg(unix)]
fn bin_clone_non_tty_path_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone("clone", &["/dev/null".to_string()], &ops, 0);
assert!(r == 0 || r == 1, "rc must be 0 or 1, got {}", r);
}
#[test]
fn clone_setup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn clone_features_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
let mut features: Vec<String> = Vec::new();
let r = features_(std::ptr::null(), &mut features);
assert_eq!(r, 0);
}
#[test]
fn clone_enables_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = None;
let _ = enables_(std::ptr::null(), &mut e);
}
#[test]
fn bin_clone_empty_path_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone("clone", &["".to_string()], &ops, 0);
assert_ne!(r, 0, "empty path → error");
}
#[test]
fn bin_clone_return_in_exit_code_range() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
for args in [
vec![],
vec!["/tmp".to_string()],
vec!["".to_string()],
vec!["/dev/null".to_string()],
] {
let r = bin_clone("clone", &args, &ops, 0);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 range for {:?}",
r,
args
);
}
}
#[test]
fn bin_clone_no_args_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let first = bin_clone("clone", &[], &ops, 0);
for _ in 0..5 {
assert_eq!(bin_clone("clone", &[], &ops, 0), first);
}
}
#[test]
fn bin_clone_multibyte_path_no_panic() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let _ = bin_clone("clone", &["/dev/日本".to_string()], &ops, 0);
let _ = bin_clone("clone", &["包含中文".to_string()], &ops, 0);
}
#[test]
fn clone_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 clone_setup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn clone_finish_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn clone_cleanup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn clone_boot_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(boot_(std::ptr::null()), 0);
}
}
#[test]
fn bin_clone_two_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone("clone", &["/tmp".to_string(), "/etc".to_string()], &ops, 0);
assert_ne!(r, 0, "two args → usage error");
}
#[test]
fn bin_clone_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let _: i32 = bin_clone("clone", &[], &ops, 0);
}
#[test]
fn bin_clone_three_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone("clone", &["a".into(), "b".into(), "c".into()], &ops, 0);
assert_ne!(r, 0, "three args → usage error");
}
#[test]
fn bin_clone_five_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let r = bin_clone(
"clone",
&["a".into(), "b".into(), "c".into(), "d".into(), "e".into()],
&ops,
0,
);
assert_ne!(r, 0, "five args → usage error");
}
#[test]
fn bin_clone_no_args_deterministic() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
let first = bin_clone("clone", &[], &ops, 0);
for _ in 0..5 {
assert_eq!(
bin_clone("clone", &[], &ops, 0),
first,
"bin_clone no-args must be pure"
);
}
}
#[test]
fn bin_clone_exit_code_non_negative_for_usage_errors() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops();
for argv in [vec![], vec!["arg".into()], vec!["a".into(), "b".into()]] {
let r = bin_clone("clone", &argv, &ops, 0);
assert!(
r >= 0,
"exit code must be non-negative, got {} for {:?}",
r,
argv
);
}
}
#[test]
fn clone_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn clone_features_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut v: Vec<String> = Vec::new();
let _: i32 = features_(std::ptr::null(), &mut v);
}
#[test]
fn clone_enables_with_none_returns_i32() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = None;
let _: i32 = enables_(std::ptr::null(), &mut e);
}
#[test]
fn clone_features_replaces_caller_vec_wholesale() {
let _g = crate::test_util::global_state_lock();
let mut v: Vec<String> = vec!["sentinel".to_string()];
let _ = features_(std::ptr::null(), &mut v);
assert!(
!v.iter().any(|s| s == "sentinel"),
"c:130 — features_ MUST overwrite the Vec (`*features = featuresarray(...)`)"
);
}
#[test]
fn clone_enables_deterministic_for_null_in() {
let _g = crate::test_util::global_state_lock();
let mut a: Option<Vec<i32>> = None;
let first = enables_(std::ptr::null(), &mut a);
for _ in 0..5 {
let mut b: Option<Vec<i32>> = None;
assert_eq!(
enables_(std::ptr::null(), &mut b),
first,
"enables_ must be deterministic for null in"
);
}
}
#[test]
fn clone_each_lifecycle_hook_returns_zero_individually() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
let mut v: Vec<String> = Vec::new();
let mut e: Option<Vec<i32>> = None;
assert_eq!(setup_(null), 0, "c:213 setup_");
assert_eq!(features_(null, &mut v), 0, "c:220 features_");
assert_eq!(enables_(null, &mut e), 0, "c:228 enables_");
assert_eq!(boot_(null), 0, "c:235 boot_");
assert_eq!(cleanup_(null), 0, "c:242 cleanup_");
assert_eq!(finish_(null), 0, "c:249 finish_");
}
#[test]
fn clone_setup_idempotent_repeated_calls() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn clone_cleanup_idempotent_repeated_calls() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn clone_finish_idempotent_repeated_calls() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn clone_features_deterministic_on_null_module() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
let mut v1: Vec<String> = Vec::new();
let mut v2: Vec<String> = Vec::new();
assert_eq!(features_(null, &mut v1), 0);
assert_eq!(features_(null, &mut v2), 0);
assert_eq!(
v1, v2,
"features_ must populate identical vec for identical input"
);
}
#[test]
fn bin_clone_empty_args_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,
};
let r = bin_clone("clone", &[], &ops, 0);
assert!(r >= 0, "bin_clone empty args must return ≥ 0, got {}", r);
}
#[test]
fn bin_clone_deterministic_for_two_args() {
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 args = vec!["a".to_string(), "b".to_string()];
let r1 = bin_clone("clone", &args, &ops, 0);
let r2 = bin_clone("clone", &args, &ops, 0);
assert_eq!(r1, r2, "bin_clone must be deterministic for same args");
}
#[test]
fn bin_clone_various_func_values_no_panic() {
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 func in [-1, 0, 1, 100, i32::MAX] {
let _ = bin_clone("clone", &[], &ops, func);
}
}
#[test]
fn clone_setup_then_boot_returns_zero_each() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
assert_eq!(boot_(null), 0);
}
#[test]
fn clone_enables_with_some_non_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = Some(vec![1, 2, 3]);
let _ = enables_(std::ptr::null(), &mut e);
}
#[test]
fn clone_setup_returns_i32_type_compile_pin() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn clone_boot_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = boot_(std::ptr::null());
}
#[test]
fn clone_cleanup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = cleanup_(std::ptr::null());
}
#[test]
fn clone_finish_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = finish_(std::ptr::null());
}
}