use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
use std::sync::Mutex;
use crate::ported::builtin::{realexit, LASTVAL, RETFLAG, STOPMSG};
use crate::ported::context::zcontext_restore;
use crate::ported::hist::{curhist, curline, hbegin, hend, hist_ring, histlinect, stophist};
use crate::ported::lex::{set_tok, tok, ENDINPUT};
use crate::ported::mem::popheap;
use crate::ported::options::{dosetopt, emulation};
use crate::ported::params::{getsparam, TERMFLAGS};
use crate::ported::signals::{dotrap, install_handler, intr, queue_signals, signal_ignore, sigtrapped, unqueue_signals};
use crate::ported::signals_h::dont_queue_signals;
use crate::ported::text::getpermtext;
use crate::ported::utils::{callhookfunc, errflag, movefd, unmeta, ERRFLAG_ERROR};
use crate::ported::zsh_h::{eprog, hookdef, interact, islogin, isset, jobbing, Eprog, EMULATE_KSH, EMULATE_SH, GLOBALRCS, HISTBEEP, HISTIGNOREDUPS, HIST_DUP, HIST_TMPSTORE, HOOK_SUFFIX, HUP, INTERACTIVE, LEXERR, PRIVILEGED, RCS, SHINSTDIN, SINGLECOMMAND, TERM_UNKNOWN, ZEXIT_NORMAL, ZLE_CMD_POSTEXEC, ZLE_CMD_PREEXEC, HOOKF_ALL};
pub static noexitct: AtomicI32 = AtomicI32::new(0);
pub static zunderscore: Mutex<String> = Mutex::new(String::new());
pub static underscorelen: AtomicUsize = AtomicUsize::new(0);
pub static underscoreused: AtomicI32 = AtomicI32::new(0);
pub static sourcelevel: AtomicI32 = AtomicI32::new(0);
pub static SHTTY: AtomicI32 = AtomicI32::new(-1);
pub static shout: Mutex<usize> = Mutex::new(0);
pub static tcstr: Mutex<[String; 39]> = Mutex::new([
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
]);
pub static tclen: Mutex<[i32; 39]> = Mutex::new([0; 39]);
pub static tclines: AtomicI32 = AtomicI32::new(0);
pub static tccolumns: AtomicI32 = AtomicI32::new(0);
pub static hasam: AtomicI32 = AtomicI32::new(0);
pub static hasxn: AtomicI32 = AtomicI32::new(0);
pub static tccolours: AtomicI32 = AtomicI32::new(0);
pub static zshhooks: once_cell::sync::Lazy<
std::sync::atomic::AtomicPtr<hookdef>,
> = once_cell::sync::Lazy::new(|| {
let arr: Box<[hookdef; 4]> = Box::new([
hookdef {
next: std::ptr::null_mut(),
name: "exit".to_string(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
hookdef {
next: std::ptr::null_mut(),
name: "before_trap".to_string(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
hookdef {
next: std::ptr::null_mut(),
name: "after_trap".to_string(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
hookdef {
next: std::ptr::null_mut(),
name: "get_color_attr".to_string(),
def: None,
flags: HOOKF_ALL,
funcs: std::ptr::null_mut(),
},
]);
let base = Box::into_raw(arr) as *mut hookdef;
std::sync::atomic::AtomicPtr::new(base)
});
static argv0: Mutex<String> = Mutex::new(String::new());
pub static zle_entry_ptr: AtomicUsize = AtomicUsize::new(0);
pub static zle_load_state: AtomicI32 = AtomicI32::new(0);
pub static compctlreadptr: AtomicUsize = AtomicUsize::new(0);
pub static use_exit_printed: AtomicI32 = AtomicI32::new(0);
const tccapnams: [&str; 39] = [
"cl", "le", "LE", "nd", "RI", "up", "UP", "do", "DO", "dc", "DC", "ic", "IC", "cd", "ce", "al",
"dl", "ta", "md", "mh", "so", "us", "ZH", "me", "se", "ue", "ZR", "ch", "ku", "kd", "kl", "kr",
"sc", "rc", "bc", "AF", "AB", "vi", "ve",
];
fn parseargs(
zsh_name: &str,
argv: &mut Vec<String>, runscript: &mut Option<String>,
cmdptr: &mut Option<String>,
) {
let mut idx: usize = 0; let flags: i32 = 1;
let flags = if argv.first().map(|s| s.starts_with('-')).unwrap_or(false)
{
flags | 2
} else {
flags
};
*argv0.lock().unwrap() = argv[idx].clone(); idx += 1;
let _ = parseopts(zsh_name, argv, &mut idx, cmdptr, flags);
let mut paramlist: Vec<String> = Vec::new();
if idx < argv.len() {
if cmdptr.is_none() {
*runscript = Some(argv[idx].clone());
}
idx += 1;
while idx < argv.len() {
paramlist.push(argv[idx].clone());
idx += 1;
}
} else if cmdptr.is_none() { }
let _ = paramlist;
}
fn parseopts_insert(optlist: &mut Vec<usize>, base: usize, optno: i32) {
let ptr = base + (if optno < 0 { -optno } else { optno }) as usize; for (i, &node) in optlist.iter().enumerate() {
if ptr < node {
optlist.insert(i, ptr); return; }
}
optlist.push(ptr); }
pub fn parseopts(
_nam: &str,
argv: &mut Vec<String>,
idx: &mut usize, cmdp: &mut Option<String>,
flags: i32,
) -> i32 {
let toplevel = (flags & 1) != 0; let mut emulate_required = toplevel; *cmdp = None;
while *idx < argv.len() {
let arg = argv[*idx].clone();
if !(arg.starts_with('-') || arg.starts_with('+')) {
break;
}
if arg == "--version" {
println!("zshrs (C-port)"); if toplevel {
std::process::exit(0);
} }
if arg == "--help" {
printhelp(); if toplevel {
std::process::exit(0);
} }
if arg == "-c" {
if emulate_required {
parseopts_setemulate(_nam, flags); emulate_required = false; }
*idx += 1;
*cmdp = argv.get(*idx).cloned(); *idx += 1;
continue;
}
*idx += 1;
}
if emulate_required {
parseopts_setemulate(_nam, flags); }
0 }
fn printhelp() {
let argz = argv0.lock().unwrap().clone(); println!("Usage: {} [<options>] [<argument> ...]", argz); println!(); println!("Special options:"); println!(" --help show this message, then exit"); println!(" --version show zsh version number, then exit"); println!(" -b end option processing, like --"); println!(" -c take first argument as a command to execute"); println!(" -o OPTION set an option by name (see below)"); println!(); println!("Normal options are named. An option may be turned on by"); println!("`-o OPTION', `--OPTION', `+o no_OPTION' or `+-no-OPTION'. An"); println!("option may be turned off by `-o no_OPTION', `--no-OPTION',"); println!("`+o OPTION' or `+-OPTION'. Options are listed below only in"); println!("`--OPTION' or `--no-OPTION' form."); }
pub fn init_io(_cmd: Option<&str>) {
*shout.lock().unwrap() = 0;
if SHTTY.load(Ordering::SeqCst) != -1 {
unsafe {
libc::close(SHTTY.load(Ordering::SeqCst));
} SHTTY.store(-1, Ordering::SeqCst); }
#[cfg(unix)]
unsafe {
if libc::isatty(0) != 0 {
let name_ptr = libc::ttyname(0); if !name_ptr.is_null() {
let name = std::ffi::CStr::from_ptr(name_ptr);
let cstr = std::ffi::CString::new(name.to_bytes()).unwrap();
let fd = libc::open(
cstr.as_ptr(), libc::O_RDWR | libc::O_NOCTTY,
);
SHTTY.store(movefd(fd), Ordering::SeqCst);
}
if SHTTY.load(Ordering::SeqCst) == -1 {
SHTTY.store(movefd(libc::dup(0)), Ordering::SeqCst);
}
}
if SHTTY.load(Ordering::SeqCst) == -1 && libc::isatty(1) != 0 {
SHTTY.store(movefd(libc::dup(1)), Ordering::SeqCst);
}
if SHTTY.load(Ordering::SeqCst) == -1 {
let dev_tty = std::ffi::CString::new("/dev/tty").unwrap();
let fd = libc::open(dev_tty.as_ptr(), libc::O_RDWR | libc::O_NOCTTY); SHTTY.store(movefd(fd), Ordering::SeqCst);
}
if SHTTY.load(Ordering::SeqCst) != -1 {
let fdflags = libc::fcntl(SHTTY.load(Ordering::SeqCst), libc::F_GETFD, 0); if fdflags != -1 {
libc::fcntl(
SHTTY.load(Ordering::SeqCst),
libc::F_SETFD, fdflags | libc::FD_CLOEXEC,
);
}
}
}
init_shout();
let _ = crate::ported::jobs::acquire_pgrp();
}
pub fn init_shout() {
if SHTTY.load(Ordering::SeqCst) == -1 {
return;
}
let _ = crate::ported::utils::gettyinfo(); }
pub fn tccap_get_name(cap: usize) -> &'static str {
if cap >= 39
{
return ""; }
tccapnams[cap] }
pub fn init_term() -> i32 {
let term = getsparam("TERM").unwrap_or_default();
if term.is_empty() {
TERMFLAGS.fetch_or(TERM_UNKNOWN, Ordering::SeqCst);
return 0;
}
if term == "emacs" {
TERMFLAGS.fetch_or(TERM_UNKNOWN, Ordering::SeqCst);
return 0;
}
if term == "dumb" {
TERMFLAGS.fetch_or(TERM_UNKNOWN, Ordering::SeqCst);
return 0;
}
use crate::ported::zsh_h::{
TCALLATTRSOFF, TCBACKSPACE, TCBGCOLOUR, TCBOLDFACEBEG, TCCLEAREOD, TCCLEAREOL,
TCCLEARSCREEN, TCCURINV, TCCURVIS, TCDEL, TCDELLINE, TCDOWN, TCDOWNCURSOR, TCFAINTBEG,
TCFGCOLOUR, TCHORIZPOS, TCINS, TCINSLINE, TCITALICSBEG, TCITALICSEND, TCLEFT, TCLEFTCURSOR,
TCMULTDEL, TCMULTDOWN, TCMULTINS, TCMULTLEFT, TCMULTRIGHT, TCMULTUP, TCNEXTTAB,
TCRESTRCURSOR, TCRIGHT, TCRIGHTCURSOR, TCSAVECURSOR, TCSTANDOUTBEG, TCSTANDOUTEND,
TCUNDERLINEBEG, TCUNDERLINEEND, TCUP, TCUPCURSOR,
};
let mut s = tcstr.lock().unwrap();
let mut l = tclen.lock().unwrap();
let mut set = |idx: i32, esc: &str| {
let i = idx as usize;
s[i] = esc.to_string();
l[i] = esc.len() as i32;
};
set(TCLEFT, "\x08");
set(TCMULTLEFT, "\x1b[%dD");
set(TCRIGHT, "\x1b[C");
set(TCMULTRIGHT, "\x1b[%dC");
set(TCUP, "\x1b[A");
set(TCMULTUP, "\x1b[%dA");
set(TCDOWN, "\x1b[B");
set(TCMULTDOWN, "\x1b[%dB");
set(TCBACKSPACE, "\x08");
set(TCHORIZPOS, "\x1b[%dG");
set(TCUPCURSOR, "\x1b[A");
set(TCDOWNCURSOR, "\x1b[B");
set(TCRIGHTCURSOR, "\x1b[C");
set(TCLEFTCURSOR, "\x1b[D");
set(TCCLEARSCREEN, "\x1b[H\x1b[2J");
set(TCCLEAREOD, "\x1b[J");
set(TCCLEAREOL, "\x1b[K");
set(TCDEL, "\x1b[P");
set(TCMULTDEL, "\x1b[%dP");
set(TCINS, "\x1b[@");
set(TCMULTINS, "\x1b[%d@");
set(TCINSLINE, "\x1b[L");
set(TCDELLINE, "\x1b[M");
set(TCNEXTTAB, "\t");
set(TCBOLDFACEBEG, "\x1b[1m");
set(TCFAINTBEG, "\x1b[2m");
set(TCSTANDOUTBEG, "\x1b[7m");
set(TCSTANDOUTEND, "\x1b[27m");
set(TCUNDERLINEBEG, "\x1b[4m");
set(TCUNDERLINEEND, "\x1b[24m");
set(TCITALICSBEG, "\x1b[3m");
set(TCITALICSEND, "\x1b[23m");
set(TCALLATTRSOFF, "\x1b[m");
set(TCSAVECURSOR, "\x1b7");
set(TCRESTRCURSOR, "\x1b8");
set(TCCURINV, "\x1b[?25l");
set(TCCURVIS, "\x1b[?25h");
set(TCFGCOLOUR, "\x1b[3%dm");
set(TCBGCOLOUR, "\x1b[4%dm");
1 }
fn getmypath(name: Option<&str>, cwd: Option<&str>) -> Option<String> {
#[cfg(target_os = "macos")]
unsafe {
let mut buf = vec![0u8; libc::PATH_MAX as usize]; let mut n: u32 = libc::PATH_MAX as u32; let ret = libc::_NSGetExecutablePath(
buf.as_mut_ptr() as *mut i8, &mut n,
);
if ret < 0 {
buf.resize(n as usize, 0); let ret2 = libc::_NSGetExecutablePath(buf.as_mut_ptr() as *mut i8, &mut n); if ret2 == 0 {
let s = std::ffi::CStr::from_ptr(buf.as_ptr() as *const i8);
let lossy = s.to_string_lossy().into_owned();
if !lossy.is_empty() {
return Some(lossy);
}
}
} else if ret == 0 {
let s = std::ffi::CStr::from_ptr(buf.as_ptr() as *const i8);
let lossy = s.to_string_lossy().into_owned();
if !lossy.is_empty() {
return Some(lossy);
} }
}
#[cfg(target_os = "linux")]
{
if let Ok(p) = std::fs::read_link("/proc/self/exe") {
return Some(p.to_string_lossy().into_owned()); }
}
let name = name?; let name = if name.starts_with('-') {
&name[1..]
} else {
name
}; let namelen = name.len(); if namelen == 0 {
return None;
} if name.ends_with('/') {
return None;
} if name.starts_with('/') {
return Some(name.to_string()); }
if name.contains('/') {
let cwd = cwd?; return Some(format!("{}/{}", cwd, name)); }
let path = std::env::var("PATH").ok()?; if path.is_empty() {
return None;
} for dir in path.split(':') {
let candidate = if dir.is_empty() {
std::path::PathBuf::from(name)
} else {
std::path::PathBuf::from(format!("{}/{}", dir, name))
};
if let Ok(real) = std::fs::canonicalize(&candidate) {
if real.is_file() {
return Some(real.to_string_lossy().into_owned());
}
}
}
None }
pub fn setupvals(cmd: Option<&str>, runscript: Option<&str>, zsh_name: &str) {
let mut close_fds = [0i32; 10]; let mut tmppipe = [-1i32; 2];
#[cfg(unix)]
unsafe {
if libc::pipe(tmppipe.as_mut_ptr()) == 0 {
let mut i: i32 = -1; while i < 9 {
let j: i32;
if i < tmppipe[0] {
j = tmppipe[0]; } else if i < tmppipe[1] {
j = tmppipe[1]; } else {
j = libc::dup(0); if j == -1 {
break;
} }
if j < 10 {
close_fds[j as usize] = 1; } else {
libc::close(j); }
if i < j {
i = j;
} }
if i < tmppipe[0] {
libc::close(tmppipe[0]);
} if i < tmppipe[1] {
libc::close(tmppipe[1]);
} }
}
{
let base = zshhooks.load(std::sync::atomic::Ordering::SeqCst);
let _ = crate::ported::module::addhookdefs(std::ptr::null(), base, 4);
}
let _ = crate::ported::hist::inithist();
TERMFLAGS.store(1, Ordering::SeqCst);
#[cfg(unix)]
unsafe {
let mut ts: libc::timespec = std::mem::zeroed();
libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
libc::srand((ts.tv_sec as u32).wrapping_add(ts.tv_nsec as u32));
}
std::env::set_var(
"PATH", std::env::var("PATH")
.unwrap_or_else(|_| "/bin:/usr/bin:/usr/ucb:/usr/local/bin".to_string()),
);
let underscore_val = std::env::var("_").unwrap_or_default(); let metafied = crate::ported::utils::metafy(&underscore_val); *zunderscore.lock().unwrap() = metafied.clone(); underscoreused.store((metafied.len() + 1) as i32, Ordering::SeqCst); let ulen = (metafied.len() + 1 + 31) & !31; underscorelen.store(ulen, Ordering::SeqCst);
if let Ok(home) = std::env::var("HOME") {
let _ = home;
}
let cwd = crate::ported::compat::zgetcwd(); std::env::set_var("PWD", &cwd); if std::env::var("OLDPWD").is_err() {
std::env::set_var("OLDPWD", &cwd); }
crate::ported::utils::inittyptab(); crate::ported::lex::initlextabs();
crate::ported::hashtable::createreswdtable(); crate::ported::hashtable::createaliastables(); crate::ported::hashtable::createcmdnamtable(); crate::ported::hashtable::createshfunctable(); let _ = crate::ported::builtin::createbuiltintable(); crate::ported::hashnameddir::createnameddirtable(); crate::ported::params::createparamtable();
let (_cols, _lines) = crate::ported::utils::adjustwinsize(0);
sourcelevel.store(0, Ordering::SeqCst);
#[cfg(unix)]
for i in 0..10 {
if close_fds[i] != 0 {
unsafe {
libc::close(i as i32);
} }
}
let _ = crate::ported::prompt::set_default_colour_sequences();
{
let exename = argv0.lock().unwrap().clone(); let exename = unmeta(&exename); let cwd = getsparam("PWD").map(|s| unmeta(&s));
let mypath = getmypath(
Some(&exename), cwd.as_deref(),
);
if let Some(mp) = mypath {
std::env::set_var("ZSH_EXEPATH", &mp); }
}
if let Some(cmd) = cmd {
std::env::set_var("ZSH_EXECUTION_STRING", cmd); }
if let Some(rs) = runscript {
std::env::set_var("ZSH_SCRIPT", rs); }
std::env::set_var("ZSH_NAME", zsh_name); }
fn setupshin(runscript: Option<&str>) {
if let Some(script) = runscript {
let funmeta = unmeta(script); let mut sfname: Option<String> = None; if std::path::Path::new(&funmeta).is_file() {
sfname = Some(script.to_string()); }
if sfname.is_none() {
crate::ported::utils::zerr(&format!(
"can't open input file: {}",
script
));
std::process::exit(127); }
}
}
pub fn init_signals() {
#[cfg(unix)]
if interact() {
let empty = crate::ported::signals::signal_mask(0);
let _ = crate::ported::signals::signal_setmask(&empty);
for i in 1..=crate::ported::signals_h::SIGCOUNT {
let _ = crate::ported::signals::signal_default(i);
}
}
intr();
#[cfg(unix)]
unsafe {
let mut act: libc::sigaction = std::mem::zeroed();
if libc::sigaction(libc::SIGQUIT, std::ptr::null(), &mut act) == 0
&& act.sa_sigaction == libc::SIG_IGN
{
}
signal_ignore(libc::SIGQUIT);
if signal_ignore(libc::SIGHUP) == libc::SIG_IGN {
dosetopt(HUP, 0, 0); } else {
install_handler(libc::SIGHUP); }
install_handler(libc::SIGCHLD); #[cfg(not(target_os = "haiku"))]
{
install_handler(libc::SIGWINCH); }
if interact() {
install_handler(libc::SIGPIPE); install_handler(libc::SIGALRM); signal_ignore(libc::SIGTERM); }
if jobbing() {
signal_ignore(libc::SIGTTOU); signal_ignore(libc::SIGTSTP); signal_ignore(libc::SIGTTIN); }
}
}
pub fn run_init_scripts() {
let emul = emulation.load(Ordering::SeqCst);
let is_posix = (emul & (EMULATE_KSH | EMULATE_SH) as i32) != 0;
let is_login = islogin();
let interact = isset(INTERACTIVE);
let privileged = isset(PRIVILEGED);
if is_posix {
if is_login {
let _ = source("/etc/profile");
}
if !privileged {
if is_login {
sourcehome(".profile");
}
if interact {
if let Some(s) = getsparam("ENV") {
let _ = source(&s);
}
}
} else {
let _ = source("/etc/suid_profile");
}
} else {
let _ = source(crate::ported::config_h::GLOBAL_ZSHENV);
if isset(RCS) && !privileged {
sourcehome(".zshenv");
}
if is_login {
if isset(RCS) && isset(GLOBALRCS) {
let _ = source(crate::ported::config_h::GLOBAL_ZPROFILE);
}
if isset(RCS) && !privileged {
sourcehome(".zprofile");
}
}
if interact {
if isset(RCS) && isset(GLOBALRCS) {
let _ = source(crate::ported::config_h::GLOBAL_ZSHRC);
}
if isset(RCS) && !privileged {
sourcehome(".zshrc");
}
}
if is_login {
if isset(RCS) && isset(GLOBALRCS) {
let _ = source(crate::ported::config_h::GLOBAL_ZLOGIN);
}
if isset(RCS) && !privileged {
sourcehome(".zlogin");
}
}
}
}
pub fn init_misc(cmd: Option<&str>, zsh_name: &str) {
if zsh_name.starts_with('r') {
crate::ported::utils::zerrnam(
zsh_name, "no support for restricted mode",
);
std::process::exit(1); }
if let Some(cmdstr) = cmd {
let _ = cmdstr;
std::process::exit(0);
}
}
pub fn source(s: &str) -> i32 {
let us = unmeta(s); let path = std::path::Path::new(&us);
if !path.exists() {
return 1;
}
let old_scriptname = crate::ported::utils::scriptname_get(); let old_scriptfilename = crate::ported::utils::scriptfilename_get(); crate::ported::utils::set_scriptname(Some(us.clone())); crate::ported::utils::set_scriptfilename(Some(us.clone()));
sourcelevel.fetch_add(1, Ordering::SeqCst);
let contents = std::fs::read_to_string(path);
if let Ok(body) = contents {
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(&body);
}
sourcelevel.fetch_sub(1, Ordering::SeqCst);
crate::ported::utils::set_scriptname(old_scriptname);
crate::ported::utils::set_scriptfilename(old_scriptfilename);
0 }
pub fn sourcehome(s: &str) {
queue_signals(); let emul = emulation.load(Ordering::SeqCst);
let is_posix = (emul & 6) != 0;
let h = if is_posix {
getsparam("HOME")
} else {
getsparam("ZDOTDIR")
.or_else(|| getsparam("HOME"))
};
let h = match h {
Some(h) => h,
None => {
unqueue_signals();
return;
}
};
let buf = format!("{}/{}", h, s); unqueue_signals(); source(&buf); }
pub fn init_bltinmods() { }
pub fn noop_function() { }
pub fn noop_function_int(_nothing: i32) { }
pub fn zleentry(cmd: i32) -> Option<String> {
let mut cmd = cmd;
match zle_load_state.load(Ordering::SeqCst) {
0 => {
if cmd != 1 && cmd != 2 && cmd != 3 {
zle_load_state.store(2, Ordering::SeqCst); }
}
1 => {
cmd = -1; }
2 => { } _ => {}
}
match cmd {
4 => {
let _line = String::new(); return Some(String::new());
}
5 => {
return Some(String::new()); }
_ => {}
}
None }
pub fn fallback_compctlread(name: &str) -> i32 {
crate::ported::utils::zwarnnam(
name, "no loaded module provides read for completion context",
);
1 }
pub fn zsh_main(_argc: i32, argv: &[String]) -> i32 {
#[cfg(unix)]
unsafe {
let empty = std::ffi::CString::new("").unwrap();
libc::setlocale(libc::LC_ALL, empty.as_ptr()); }
let env: Vec<String> = std::env::vars()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
let _ = crate::ported::jobs::init_jobs(argv, &env);
let mut zsh_name = argv.first().cloned().unwrap_or_default(); loop {
let arg0 = zsh_name.clone(); zsh_name = match arg0.rfind('/') {
None => arg0.clone(), Some(i) => arg0[i + 1..].to_string(), };
if zsh_name.starts_with('-') {
zsh_name = zsh_name[1..].to_string(); }
if zsh_name == "su" {
if let Ok(sh) = std::env::var("SHELL") {
if !sh.is_empty() && arg0 != sh {
zsh_name = sh; continue; }
}
break; }
break; }
let _ = crate::ported::compat::zopenmax();
crate::ported::options::createoptiontable();
let mut argv_v = argv.to_vec();
let mut runscript: Option<String> = None; let mut cmd: Option<String> = None; parseargs(&zsh_name, &mut argv_v, &mut runscript, &mut cmd);
SHTTY.store(-1, Ordering::SeqCst); init_io(cmd.as_deref()); setupvals(cmd.as_deref(), runscript.as_deref(), &zsh_name);
init_signals(); init_bltinmods(); crate::ported::builtin::init_builtins(); run_init_scripts(); setupshin(runscript.as_deref()); init_misc(cmd.as_deref(), &zsh_name);
loop {
let mut errexit = 0; loop {
let _ = r#loop(1, 0); errexit = 1;
break;
}
if errexit != 0 {
std::process::exit(0);
}
std::process::exit(0);
}
}
pub fn r#loop(toplevel: i32, justonce: i32) -> i32 {
let subsh: i32 = crate::ported::exec::subsh.load(Ordering::Relaxed);
let mut prog: Option<crate::ported::parse::ZshProgram>; let err: i32; let mut non_empty: i32 = 0;
queue_signals(); crate::ported::mem::pushheap(); if toplevel == 0 {
crate::ported::context::zcontext_save(); }
loop {
crate::ported::mem::freeheap(); if stophist.load(Ordering::SeqCst) == 3 {
hend(None); }
hbegin(1); if isset(SHINSTDIN) {
crate::ported::utils::setblock_stdin(); if isset(INTERACTIVE) && toplevel != 0 {
let hstop = stophist.load(Ordering::SeqCst); stophist.store(3, Ordering::SeqCst); errflag.store(0, Ordering::SeqCst); crate::ported::utils::preprompt(); if stophist.load(Ordering::SeqCst) != 3 {
hbegin(1); } else {
stophist.store(hstop, Ordering::SeqCst); }
errflag.store(0, Ordering::SeqCst); }
}
use_exit_printed.store(0, Ordering::SeqCst); intr(); crate::ported::lex::lexinit(); prog = crate::ported::parse::parse_event(ENDINPUT as i32); if prog.is_none() {
hend(None); let tok_v = tok(); let errflag_v = errflag.load(Ordering::SeqCst);
let lexerr_break = tok_v == LEXERR && (!isset(SHINSTDIN) || toplevel == 0);
let endinput_break = tok_v == ENDINPUT && errflag_v == 0;
if endinput_break || lexerr_break || justonce != 0 {
break; }
if crate::ported::hist::exit_pending.load(Ordering::SeqCst) {
STOPMSG.store(1, Ordering::SeqCst); crate::ported::builtin::zexit(
crate::ported::builtin::EXIT_VAL.load(Ordering::SeqCst),
ZEXIT_NORMAL,
);
}
if tok_v == LEXERR && LASTVAL.load(Ordering::SeqCst) == 0
{
LASTVAL.store(1, Ordering::SeqCst); }
continue; }
let prog_inner = prog.take().unwrap();
let prog_bytes: Vec<u8> = format!("{:?}", prog_inner).into_bytes();
let hend_ret = hend(Some(&prog_bytes)); if hend_ret != 0 {
let _toksav = tok(); non_empty = 1; if toplevel != 0 {
let preexec_fn = crate::ported::utils::getshfunc("preexec"); let preexec_hook = crate::ported::params::paramtab().read().ok().and_then(|t| {
t.get(&format!("preexec{}", HOOK_SUFFIX))
.map(|_| ())
}); if preexec_fn.is_some() || preexec_hook.is_some() {
let mut args: Vec<String> = Vec::new(); args.push("preexec".to_string()); let hr = hist_ring.lock().unwrap();
let cl = curline.lock().unwrap();
let same = !hr.is_empty()
&& cl.as_ref().map(|c| c.histnum).unwrap_or(0)
== curhist.load(Ordering::SeqCst);
if same {
args.push(hr.first().map(|h| h.node.nam.clone()).unwrap_or_default());
} else {
args.push(String::new()); }
drop(hr);
drop(cl);
let placeholder: Eprog =
Box::new(eprog {
flags: 0,
len: 0,
npats: 0,
nref: 0,
pats: Vec::new(),
prog: Vec::new(),
strs: None,
shf: None,
dump: None,
});
let placeholder2: Eprog =
Box::new(eprog {
flags: 0,
len: 0,
npats: 0,
nref: 0,
pats: Vec::new(),
prog: Vec::new(),
strs: None,
shf: None,
dump: None,
});
let job_text = crate::ported::text::getjobtext(placeholder, None); args.push(crate::ported::mem::dupstring(&job_text));
let cmdstr = getpermtext(placeholder2, None, 0); args.push(cmdstr.clone());
callhookfunc(
"preexec",
Some(&args),
1,
std::ptr::null_mut(),
);
crate::ported::mem::zsfree(cmdstr); errflag.fetch_and(
!ERRFLAG_ERROR,
Ordering::SeqCst,
);
}
}
if toplevel != 0 && zle_load_state.load(Ordering::SeqCst) == 1
{
let _ = zleentry(
ZLE_CMD_PREEXEC,
);
}
if STOPMSG.load(Ordering::SeqCst) != 0 {
STOPMSG.fetch_sub(1, Ordering::SeqCst); }
let _exec_label = if toplevel != 0 { "toplevel" } else { "file" };
let _ = prog_inner;
set_tok(_toksav);
if toplevel != 0 {
noexitct.store(0, Ordering::SeqCst); if zle_load_state.load(Ordering::SeqCst) == 1 {
let _ = zleentry(
ZLE_CMD_POSTEXEC,
);
}
}
}
if subsh != 0 {
realexit(); }
let errflag_v = errflag.load(Ordering::SeqCst);
let interact_v = isset(INTERACTIVE);
let srclvl = sourcelevel.load(Ordering::SeqCst);
let retflag_v = RETFLAG.load(Ordering::SeqCst);
if ((!interact_v || srclvl != 0) && errflag_v != 0) || retflag_v != 0 {
break; }
if isset(SINGLECOMMAND) && toplevel != 0 {
dont_queue_signals(); let tr = sigtrapped.lock().unwrap();
let sigexit = libc::SIGINT as usize; if tr.get(sigexit).copied().unwrap_or(0) != 0 {
drop(tr);
let _ = dotrap(0 ); }
realexit(); }
if justonce != 0 {
break; }
let _ = histlinect.load(Ordering::SeqCst); }
err = errflag.load(Ordering::SeqCst); if toplevel == 0 {
zcontext_restore(); }
popheap(); unqueue_signals();
if err != 0 {
return 2;
} if non_empty == 0 {
return 1;
} 0 }
fn parseopts_setemulate(nam: &str, flags: i32) {
let bare = nam.trim_start_matches('-');
let basename = std::path::Path::new(bare)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(bare);
crate::ported::options::emulate(basename, true);
const PARSEARGS_LOGIN: i32 = 2;
crate::ported::options::opt_state_set(
"loginshell",
(flags & PARSEARGS_LOGIN) != 0,
);
let priv_on = unsafe { libc::getuid() != libc::geteuid() || libc::getgid() != libc::getegid() };
crate::ported::options::opt_state_set("privileged", priv_on);
let interactive_default = unsafe { libc::isatty(0) != 0 };
crate::ported::options::opt_state_set("interactive", interactive_default);
crate::ported::options::opt_state_set("monitor", true);
crate::ported::options::opt_state_set("hashdirs", true);
crate::ported::options::opt_state_set("usezle", true);
crate::ported::options::opt_state_set("shinstdin", false);
crate::ported::options::opt_state_set("singlecommand", false);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fallback_compctlread_signals_failure() {
let _g = crate::test_util::global_state_lock();
assert_eq!(fallback_compctlread("test"), 1);
}
#[test]
fn parseopts_dash_c_captures_command_string() {
let _g = crate::test_util::global_state_lock();
let mut argv = vec!["-c".to_string(), "echo hi".to_string()];
let mut idx = 0usize;
let mut cmd: Option<String> = None;
let r = parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0);
assert_eq!(r, 0);
assert_eq!(
cmd.as_deref(),
Some("echo hi"),
"-c must capture the next arg as the command"
);
assert_eq!(idx, 2, "idx must advance past both -c AND its arg");
}
#[test]
fn parseopts_stops_at_first_positional() {
let _g = crate::test_util::global_state_lock();
let mut argv = vec!["script.sh".to_string(), "arg1".to_string()];
let mut idx = 0usize;
let mut cmd: Option<String> = None;
parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0);
assert_eq!(idx, 0, "must stop AT the first bareword (no advance)");
assert!(cmd.is_none(), "no -c → cmd stays None");
}
#[test]
fn parseopts_empty_argv_is_safe() {
let _g = crate::test_util::global_state_lock();
let mut argv: Vec<String> = Vec::new();
let mut idx = 0usize;
let mut cmd: Option<String> = None;
assert_eq!(parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0), 0);
assert_eq!(idx, 0);
assert!(cmd.is_none());
}
#[test]
fn tccap_get_name_out_of_range_returns_empty() {
let _g = crate::test_util::global_state_lock();
let n = tccap_get_name(9999);
assert!(
n.is_empty(),
"out-of-range index must return empty (got {n:?})"
);
}
#[test]
fn parseopts_dash_x_is_single_slot_flag() {
let _g = crate::test_util::global_state_lock();
let mut argv = vec!["-x".to_string()];
let mut idx = 0usize;
let mut cmd: Option<String> = None;
let r = parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0);
assert_eq!(r, 0);
assert_eq!(idx, 1, "-x consumes exactly 1 arg slot");
assert!(cmd.is_none(), "-x must NOT capture a command");
}
#[test]
fn parseopts_dash_c_without_argument_does_not_capture_command() {
let _g = crate::test_util::global_state_lock();
let mut argv = vec!["-c".to_string()];
let mut idx = 0usize;
let mut cmd: Option<String> = None;
let _ = parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0);
assert!(
cmd.is_none() || cmd.as_deref() == Some(""),
"-c without arg must NOT capture a usable command"
);
}
#[test]
fn parseopts_consumes_multiple_flags() {
let _g = crate::test_util::global_state_lock();
let mut argv = vec!["-x".to_string(), "-v".to_string()];
let mut idx = 0usize;
let mut cmd: Option<String> = None;
let r = parseopts("zsh", &mut argv, &mut idx, &mut cmd, 0);
assert_eq!(r, 0);
assert_eq!(idx, 2, "both flags must be consumed");
}
#[test]
fn tccap_get_name_index_zero_is_nonempty() {
let _g = crate::test_util::global_state_lock();
let n = tccap_get_name(0);
assert!(
!n.is_empty(),
"index 0 must yield a real cap name; got {:?}",
n
);
for c in n.chars() {
assert!(
c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_',
"cap name {:?} contains non-termcap char {:?}",
n,
c
);
}
}
#[test]
fn noop_function_is_safe_to_call_multiple_times() {
let _g = crate::test_util::global_state_lock();
noop_function();
noop_function();
noop_function();
}
#[test]
fn noop_function_int_ignores_argument() {
let _g = crate::test_util::global_state_lock();
noop_function_int(0);
noop_function_int(42);
noop_function_int(-1);
noop_function_int(i32::MAX);
noop_function_int(i32::MIN);
}
#[test]
fn zleentry_unknown_command_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(
zleentry(99999).is_none(),
"unknown zle command must return None, not a default string"
);
}
#[test]
fn source_empty_path_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let r = source("");
assert_ne!(r, 0, "source of empty path must report failure");
}
#[test]
fn init_corpus_tccap_index_zero_nonempty() {
let _g = crate::test_util::global_state_lock();
let name = tccap_get_name(0);
assert!(!name.is_empty(), "cap[0] has a name, got {name:?}");
}
#[test]
fn init_corpus_tccap_out_of_range_returns_empty() {
let _g = crate::test_util::global_state_lock();
assert_eq!(tccap_get_name(39), "", "TC_COUNT = empty");
assert_eq!(tccap_get_name(999), "", "way out of range = empty");
}
#[test]
fn init_corpus_tccap_indexes_all_named() {
let _g = crate::test_util::global_state_lock();
for i in 0..39 {
let n = tccap_get_name(i);
assert!(!n.is_empty(), "cap {i} should have a name");
}
}
#[test]
fn init_corpus_source_nonexistent_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
assert_ne!(source("/nonexistent/path/zshrs_test_xyz_zzz"), 0);
}
#[test]
fn init_corpus_noop_function_does_not_panic() {
let _g = crate::test_util::global_state_lock();
noop_function();
}
#[test]
fn init_corpus_noop_function_int_zero_no_panic() {
let _g = crate::test_util::global_state_lock();
noop_function_int(0);
}
#[test]
fn init_corpus_zleentry_negative_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(zleentry(-1).is_none());
}
}