use std::env;
use std::process::Child;
use std::time::{Duration, Instant};
use crate::ported::utils::zwarnnam;
use std::os::unix::process::ExitStatusExt;
use crate::ported::signals::{signal_block, signal_setmask};
use std::sync::atomic::Ordering;
use crate::ported::zsh_h::OPT_ISSET;
use crate::ported::hashtable_h::{BIN_FG, BIN_BG, BIN_JOBS};
use crate::ported::signals_h::{signal_default, signal_ignore};
pub mod stat {
pub const STOPPED: i32 = 1 << 0; pub const DONE: i32 = 1 << 1; pub const SUBJOB: i32 = 1 << 2; pub const CURSH: i32 = 1 << 3; pub const SUPERJOB: i32 = 1 << 4; pub const WASSUPER: i32 = 1 << 5; pub const INUSE: i32 = 1 << 6; pub const BUILTIN: i32 = 1 << 7; pub const DISOWN: i32 = 1 << 8; pub const NOTIFY: i32 = 1 << 9; pub const ATTACH: i32 = 1 << 10; }
pub const SP_RUNNING: i32 = -1;
pub const MAX_PIPESTATS: usize = 256;
pub const MAXJOBS_ALLOC: usize = 50;
pub const MAX_MAXJOBS: usize = 1000;
pub use crate::ported::zsh_h::timeinfo;
pub use crate::ported::zsh_h::process as Process;
pub use crate::ported::zsh_h::job as Job;
impl Process {
pub fn new(pid: i32) -> Self {
Process {
pid,
status: SP_RUNNING,
text: String::new(),
ti: timeinfo::default(),
bgtime: Some(std::time::Instant::now()),
endtime: None,
}
}
pub fn is_running(&self) -> bool { self.status == SP_RUNNING }
pub fn is_stopped(&self) -> bool { self.status & 0xff == 0x7f }
pub fn is_signaled(&self) -> bool {
(self.status & 0x7f) > 0 && (self.status & 0x7f) < 0x7f
}
pub fn exit_status(&self) -> i32 { (self.status >> 8) & 0xff }
pub fn term_sig(&self) -> i32 { self.status & 0x7f }
pub fn stop_sig(&self) -> i32 { (self.status >> 8) & 0xff }
}
impl Job {
pub fn new() -> Self { Self::default() }
pub fn has_procs(&self) -> bool {
!self.procs.is_empty() || !self.auxprocs.is_empty()
}
pub fn is_running(&self) -> bool {
self.procs.iter().any(|p| p.is_running())
}
pub fn is_done(&self) -> bool {
!self.procs.is_empty()
&& self.procs.iter().all(|p| !p.is_running() && !p.is_stopped())
}
pub fn is_stopped(&self) -> bool {
(self.stat & stat::STOPPED) != 0
|| self.procs.iter().any(|p| p.is_stopped())
}
pub fn is_inuse(&self) -> bool { (self.stat & stat::INUSE) != 0 }
pub fn make_running(&mut self) {
for p in &mut self.procs {
if p.is_stopped() {
p.status = SP_RUNNING;
}
}
self.stat &= !stat::STOPPED;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_new() {
let proc = Process::new(1234);
assert_eq!(proc.pid, 1234);
assert!(proc.is_running());
}
#[test]
fn test_job_new() {
let job = Job::new();
assert_eq!(job.stat, 0);
assert!(!job.is_done());
assert!(!job.is_stopped());
}
#[test]
fn test_job_make_running() {
let mut job = Job::new();
job.stat |= stat::STOPPED;
job.procs.push(Process {
status: 0x007f,
..Process::new(1234)
});
job.make_running();
assert!(!job.is_stopped());
assert!(job.procs[0].is_running());
}
#[test]
fn test_format_job() {
let mut job = Job::new();
job.text = "vim file.txt".to_string();
job.stat |= stat::STOPPED;
let formatted = printjob(&job, 1, false, Some(1), None);
assert!(formatted.contains("[1]"));
assert!(formatted.contains("+"));
assert!(formatted.contains("suspended") || formatted.contains("Stopped"));
assert!(formatted.contains("vim file.txt"));
}
#[test]
fn test_isanum_handles_minus() {
assert!(isanum("123"));
assert!(isanum("-1")); assert!(isanum("---")); assert!(isanum("12-34")); assert!(!isanum("")); assert!(!isanum("abc")); assert!(!isanum("1a")); }
#[test]
fn test_havefiles_walks_table() {
let mut tab = vec![Job::new(), Job::new(), Job::new()];
tab[1].stat = stat::INUSE;
tab[1].filelist = vec!["/tmp/foo".to_string()];
assert!(havefiles(&tab));
tab[1].filelist.clear();
assert!(!havefiles(&tab));
tab[2].stat = 0;
tab[2].filelist = vec!["/tmp/bar".to_string()];
assert!(!havefiles(&tab));
}
#[test]
fn test_storepipestats_decodes_status() {
let mut job = Job::new();
let mut p1 = Process::new(100);
p1.status = 0;
let mut p2 = Process::new(101);
p2.status = 0x0100;
let mut p3 = Process::new(102);
p3.status = 0x09;
job.procs = vec![p1, p2, p3];
let (stats, pipefail) = storepipestats(&job);
assert_eq!(stats.len(), 3);
assert_eq!(stats[0], 0); assert_eq!(stats[1], 1); assert_eq!(stats[2], 0o200 | 9); assert_eq!(pipefail, 0o200 | 9); }
#[test]
fn test_expandjobtab_respects_max() {
let mut tab = vec![Job::new(); 950];
assert!(expandjobtab(&mut tab, 0));
assert_eq!(tab.len(), 1000);
assert!(!expandjobtab(&mut tab, 0));
assert_eq!(tab.len(), 1000);
}
#[test]
fn test_addfilelist_fd_vs_name() {
let mut job = Job::new();
addfilelist(&mut job, Some("/tmp/zshrs-test.X"), -1);
addfilelist(&mut job, None, 7);
assert_eq!(job.filelist.len(), 2);
assert_eq!(job.filelist[0], "/tmp/zshrs-test.X");
assert_eq!(job.filelist[1], "<fd:7>");
}
#[test]
fn test_hasprocs_index_bounded() {
let mut tab = vec![Job::new(), Job::new()];
tab[0].procs.push(Process::new(1));
assert!(hasprocs(&tab, 0));
assert!(!hasprocs(&tab, 1));
assert!(!hasprocs(&tab, 99));
}
#[test]
fn test_makerunning_clears_stopped() {
let mut tab = vec![Job::new(), Job::new()];
tab[0].stat = stat::STOPPED;
let mut p = Process::new(42);
p.status = 0x7f; tab[0].procs.push(p);
makerunning(&mut tab, 0);
assert_eq!(tab[0].stat & stat::STOPPED, 0);
assert_eq!(tab[0].procs[0].status, SP_RUNNING);
}
}
use std::sync::{Mutex, OnceLock};
use crate::zsh_h::{isset, POSIXBUILTINS};
pub static ORIGPGRP: OnceLock<Mutex<i32>> = OnceLock::new();
pub static MYPGRP: OnceLock<Mutex<i32>> = OnceLock::new();
pub static LAST_ATTACHED_PGRP: OnceLock<Mutex<i32>> = OnceLock::new();
pub static THISJOB: OnceLock<Mutex<i32>> = OnceLock::new();
pub static CURJOB: OnceLock<Mutex<i32>> = OnceLock::new();
pub static PREVJOB: OnceLock<Mutex<i32>> = OnceLock::new();
pub static JOBTAB: OnceLock<Mutex<Vec<Job>>> = OnceLock::new();
pub static JOBTABSIZE: OnceLock<Mutex<usize>> = OnceLock::new();
pub static MAXJOB: OnceLock<Mutex<usize>> = OnceLock::new();
static OLDJOBTAB: OnceLock<Mutex<Vec<Job>>> = OnceLock::new();
static OLDMAXJOB: OnceLock<Mutex<usize>> = OnceLock::new();
pub static TTYFROZEN: OnceLock<Mutex<i32>> = OnceLock::new();
pub static NUMPIPESTATS: OnceLock<Mutex<usize>> = OnceLock::new();
pub static PIPESTATS: OnceLock<Mutex<[i32; MAX_PIPESTATS]>> = OnceLock::new();
pub fn get_clktck() -> i64 { #[cfg(unix)]
{
static CLKTCK: OnceLock<i64> = OnceLock::new(); *CLKTCK.get_or_init(|| unsafe { libc::sysconf(libc::_SC_CLK_TCK) as i64 }) }
#[cfg(not(unix))]
{
100 }
}
pub fn printhhmmss(secs: f64) -> String { let mins = (secs / 60.0) as i32;
let hours = mins / 60;
let secs = secs - (mins * 60) as f64;
let mins = mins - (hours * 60);
if hours > 0 {
format!("{}:{:02}:{:05.2}", hours, mins, secs)
} else if mins > 0 {
format!("{}:{:05.2}", mins, secs)
} else {
format!("{:.3}", secs)
}
}
pub fn printtime( elapsed_secs: f64,
user_secs: f64,
system_secs: f64,
format: &str,
job_name: &str,
) -> String {
let mut result = String::new();
let total_time = user_secs + system_secs;
let percent = if elapsed_secs > 0.0 {
(100.0 * total_time / elapsed_secs) as i32
} else {
0
};
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
match chars.next() {
Some('E') => result.push_str(&format!("{:.2}s", elapsed_secs)),
Some('U') => result.push_str(&format!("{:.2}s", user_secs)),
Some('S') => result.push_str(&format!("{:.2}s", system_secs)),
Some('P') => result.push_str(&format!("{}%", percent)),
Some('J') => result.push_str(job_name),
Some('m') => match chars.next() {
Some('E') => result.push_str(&format!("{:.0}ms", elapsed_secs * 1000.0)),
Some('U') => result.push_str(&format!("{:.0}ms", user_secs * 1000.0)),
Some('S') => result.push_str(&format!("{:.0}ms", system_secs * 1000.0)),
_ => result.push_str("%m"),
},
Some('u') => match chars.next() {
Some('E') => result.push_str(&format!("{:.0}us", elapsed_secs * 1_000_000.0)),
Some('U') => result.push_str(&format!("{:.0}us", user_secs * 1_000_000.0)),
Some('S') => result.push_str(&format!("{:.0}us", system_secs * 1_000_000.0)),
_ => result.push_str("%u"),
},
Some('n') => match chars.next() {
Some('E') => {
result.push_str(&format!("{:.0}ns", elapsed_secs * 1_000_000_000.0))
}
Some('U') => result.push_str(&format!("{:.0}ns", user_secs * 1_000_000_000.0)),
Some('S') => {
result.push_str(&format!("{:.0}ns", system_secs * 1_000_000_000.0))
}
_ => result.push_str("%n"),
},
Some('*') => match chars.next() {
Some('E') => result.push_str(&printhhmmss(elapsed_secs)),
Some('U') => result.push_str(&printhhmmss(user_secs)),
Some('S') => result.push_str(&printhhmmss(system_secs)),
_ => result.push_str("%*"),
},
Some('%') => result.push('%'),
Some(other) => {
result.push('%');
result.push(other);
}
None => result.push('%'),
}
} else {
result.push(c);
}
}
result
}
pub const DEFAULT_TIMEFMT: &str = "%J %U user %S system %P cpu %*E total";
pub fn sigmsg(sig: i32) -> &'static str { match sig {
libc::SIGHUP => "hangup",
libc::SIGINT => "interrupt",
libc::SIGQUIT => "quit",
libc::SIGILL => "illegal instruction",
libc::SIGTRAP => "trace trap",
libc::SIGABRT => "abort",
libc::SIGBUS => "bus error",
libc::SIGFPE => "floating point exception",
libc::SIGKILL => "killed",
libc::SIGUSR1 => "user-defined signal 1",
libc::SIGSEGV => "segmentation fault",
libc::SIGUSR2 => "user-defined signal 2",
libc::SIGPIPE => "broken pipe",
libc::SIGALRM => "alarm",
libc::SIGTERM => "terminated",
libc::SIGCHLD => "child exited",
libc::SIGCONT => "continued",
libc::SIGSTOP => "stopped (signal)",
libc::SIGTSTP => "stopped",
libc::SIGTTIN => "stopped (tty input)",
libc::SIGTTOU => "stopped (tty output)",
libc::SIGURG => "urgent I/O condition",
libc::SIGXCPU => "CPU time exceeded",
libc::SIGXFSZ => "file size exceeded",
libc::SIGVTALRM => "virtual timer expired",
libc::SIGPROF => "profiling timer expired",
libc::SIGWINCH => "window changed",
libc::SIGIO => "I/O ready",
libc::SIGSYS => "bad system call",
_ => "unknown signal",
}
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub struct bgstatus { pub pid: i32, pub status: i32, }
pub type Bgstatus = Box<bgstatus>;
pub static bgstatus_list: std::sync::Mutex<Vec<bgstatus>> = std::sync::Mutex::new(Vec::new());
pub static bgstatus_count: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
pub fn waitforpid(pid: i32) -> Option<i32> { #[cfg(unix)]
{
loop {
let mut status: i32 = 0;
let result = unsafe { libc::waitpid(pid, &mut status, 0) };
if result == pid {
if libc::WIFEXITED(status) {
return Some(libc::WEXITSTATUS(status));
} else if libc::WIFSIGNALED(status) {
return Some(128 + libc::WTERMSIG(status));
} else if libc::WIFSTOPPED(status) {
return None;
}
} else if result == -1 {
return None;
}
}
}
#[cfg(not(unix))]
{
let _ = pid;
None
}
}
pub fn zwaitjob(job: &mut Job) -> Option<i32> { if job.procs.is_empty() {
return Some(0);
}
let mut last_status = 0;
for proc in &mut job.procs {
if proc.is_running() {
if let Some(status) = waitforpid(proc.pid) {
proc.status = status << 8;
last_status = status;
}
} else {
last_status = proc.exit_status();
}
}
job.stat |= stat::DONE;
Some(last_status)
}
pub fn havefiles(jobtab: &[Job]) -> bool { jobtab
.iter()
.any(|j| j.stat != 0 && !j.filelist.is_empty())
}
pub fn deletejob(jn: &mut Job, disowning: bool) { if !disowning {
jn.filelist.clear();
}
jn.procs.clear();
jn.auxprocs.clear();
jn.stat = 0;
}
pub fn freejob(jn: &mut Job, deleting: bool) { let _ = deleting;
jn.procs.clear();
jn.auxprocs.clear();
jn.filelist.clear();
jn.stat = 0;
jn.gleader = 0;
jn.text.clear();
}
pub fn addproc(job: &mut Job, pid: i32, text: &str, aux: bool) { let proc = Process::new(pid);
let proc = Process {
pid,
status: SP_RUNNING,
text: text.to_string(),
..proc
};
if aux {
job.auxprocs.push(proc);
} else {
if job.gleader == 0 {
job.gleader = pid;
}
job.procs.push(proc);
}
job.stat &= !stat::DONE;
}
pub fn super_job(jobtab: &[Job], job_idx: usize) -> Option<usize> { for (i, job) in jobtab.iter().enumerate() {
if (job.stat & stat::SUPERJOB) != 0 && job.other as usize == job_idx {
return Some(i);
}
}
None
}
pub fn getjob(s: &str, prog: &str) -> i32 { let mut jobnum: i32; let mymaxjob: i32; let myjobtab: Vec<Job>;
let (tab, max) = selectjobtab(); myjobtab = tab;
mymaxjob = max as i32;
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1)) .lock().expect("curjob poisoned");
let prevjob = *PREVJOB.get_or_init(|| Mutex::new(-1)) .lock().expect("prevjob poisoned");
let thisjob = *THISJOB.get_or_init(|| Mutex::new(-1))
.lock().expect("thisjob poisoned");
let posixbuiltins = isset( POSIXBUILTINS);
let s_bytes = s.as_bytes();
let mut idx = 0usize;
if s_bytes.is_empty() || s_bytes[0] != b'%' {
if let Some(jn) = findjobnam(s, &myjobtab, mymaxjob, thisjob) { return jn;
}
if !posixbuiltins && !prog.is_empty() { zwarnnam(prog, &format!("job not found: {}", s)); }
return -1; }
idx += 1;
if idx >= s_bytes.len() || s_bytes[idx] == b'%' || s_bytes[idx] == b'+' { if curjob == -1 { if !prog.is_empty() && !posixbuiltins { zwarnnam(prog, "no current job"); }
return -1; }
return curjob; }
if s_bytes[idx] == b'-' { if prevjob == -1 { if !prog.is_empty() && !posixbuiltins { zwarnnam(prog, "no previous job"); }
return -1; }
return prevjob; }
if s_bytes[idx].is_ascii_digit() { let rest = &s[idx..];
jobnum = rest.parse::<i32>().unwrap_or(0); if jobnum > 0 && jobnum <= mymaxjob { let ju = jobnum as usize;
if ju < myjobtab.len()
&& myjobtab[ju].stat != 0
&& (myjobtab[ju].stat & stat::SUBJOB) == 0 && jobnum != thisjob {
return jobnum; }
}
if !prog.is_empty() && !posixbuiltins { zwarnnam(prog, &format!("%{}: no such job", rest)); }
return -1; }
if s_bytes[idx] == b'?' { let search = &s[idx + 1..]; jobnum = mymaxjob; while jobnum >= 0 { let ju = jobnum as usize;
if ju < myjobtab.len()
&& myjobtab[ju].stat != 0 && (myjobtab[ju].stat & stat::SUBJOB) == 0 && jobnum != thisjob {
for pn in &myjobtab[ju].procs { if pn.text.contains(search) { return jobnum; }
}
}
jobnum -= 1;
}
if !prog.is_empty() && !posixbuiltins { zwarnnam(prog, &format!("job not found: {}", s)); }
return -1; }
let rest = &s[idx..];
if let Some(jn) = findjobnam(rest, &myjobtab, mymaxjob, thisjob) { return jn; }
if !posixbuiltins && !prog.is_empty() { zwarnnam(prog, &format!("job not found: {}", s)); }
-1 }
fn findjobnam(s: &str, jobtab: &[Job], maxjob: i32, thisjob: i32) -> Option<i32> {
let mut jobnum = maxjob; while jobnum >= 0 { let ju = jobnum as usize;
if ju < jobtab.len()
&& jobtab[ju].stat != 0 && (jobtab[ju].stat & stat::SUBJOB) == 0 && jobnum != thisjob {
if let Some(first_proc) = jobtab[ju].procs.first() {
if first_proc.text.starts_with(s) {
return Some(jobnum); }
}
}
jobnum -= 1;
}
None }
pub fn isanum(s: &str) -> bool { !s.is_empty()
&& s.bytes().all(|b| b == b'-' || b.is_ascii_digit())
}
pub fn init_jobs(argv: &[String], envp: &[String]) -> crate::exec_jobs::JobTable { let table = crate::exec_jobs::JobTable::new(); if !argv.is_empty() { let zero = argv[0].as_str();
let mut hackspace = zero.len(); for entry in argv.iter().skip(1).chain(envp.iter()) { hackspace += 1 + entry.len(); }
std::env::set_var("__zshrs_hackspace", hackspace.to_string()); }
table }
#[cfg(unix)]
pub fn acquire_pgrp() -> bool { let mypid = unsafe { libc::getpid() };
let mut mypgrp = unsafe { libc::getpgrp() }; if mypgrp < 0 {
crate::ported::options::opt_state_set("monitor", false); return false;
}
let mut lastpgrp = mypgrp; let mut blockset: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigemptyset(&mut blockset);
libc::sigaddset(&mut blockset, libc::SIGTTIN); libc::sigaddset(&mut blockset, libc::SIGTTOU); libc::sigaddset(&mut blockset, libc::SIGTSTP); }
let oldset = signal_block(&blockset); let mut loop_count = 0i32; let interact = crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE);
loop {
let ttpgrp = unsafe { libc::tcgetpgrp(0) }; if ttpgrp == -1 || ttpgrp == mypgrp { break; }
mypgrp = unsafe { libc::getpgrp() }; if mypgrp == mypid { if !interact { break; } signal_setmask(&oldset); unsafe { libc::tcsetpgrp(0, mypgrp); } signal_block(&blockset); }
if mypgrp == unsafe { libc::tcgetpgrp(0) } { break; } signal_setmask(&oldset); let mut buf: [u8; 0] = [];
let _ = unsafe { libc::read(0, buf.as_mut_ptr() as *mut _, 0) }; signal_block(&blockset); mypgrp = unsafe { libc::getpgrp() }; if mypgrp == lastpgrp { if !interact { break; } loop_count += 1;
if loop_count == 100 { break; }
}
lastpgrp = mypgrp; }
let mut acquired = mypgrp == mypid; if !acquired {
if unsafe { libc::setpgid(0, 0) } == 0 { mypgrp = mypid; unsafe { libc::tcsetpgrp(0, mypgrp); } acquired = true;
} else {
crate::ported::options::opt_state_set("monitor", false); }
}
signal_setmask(&oldset); acquired }
pub fn storepipestats(job: &Job) -> (Vec<i32>, i32) {
let mut stats = Vec::with_capacity(job.procs.len().min(MAX_PIPESTATS));
let mut pipefail = 0;
for p in job.procs.iter().take(MAX_PIPESTATS) {
let st = p.status;
let entry = if st == SP_RUNNING {
0
} else if (st & 0x7f) > 0 && (st & 0x7f) < 0x7f {
0o200 | (st & 0x7f)
} else if (st & 0xff) == 0x7f {
0o200 | ((st >> 8) & 0xff)
} else {
(st >> 8) & 0xff
};
stats.push(entry);
if entry != 0 {
pipefail = entry;
}
}
(stats, pipefail)
}
pub fn clearjobtab(table: &mut crate::exec_jobs::JobTable, monitor: i32) { let _ = (table, monitor);
}
pub fn scanjobs(table: &crate::exec_jobs::JobTable) -> Vec<String> { let mut output = Vec::new();
for (id, job) in table.iter() {
let state_str = match job.state {
crate::exec_jobs::JobState::Running => "running",
crate::exec_jobs::JobState::Done => "done",
crate::exec_jobs::JobState::Stopped => "stopped",
};
output.push(format!("[{}] {} {}", id, state_str, job.command));
}
output
}
pub fn shelltime() -> timeinfo {
#[cfg(unix)]
{
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
if unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut usage) } == 0 {
return timeinfo {
ut: usage.ru_utime.tv_sec as i64 * 1_000_000
+ usage.ru_utime.tv_usec as i64,
st: usage.ru_stime.tv_sec as i64 * 1_000_000
+ usage.ru_stime.tv_usec as i64,
};
}
}
timeinfo::default()
}
pub fn get_usage() -> timeinfo {
#[cfg(unix)]
{
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
if unsafe { libc::getrusage(libc::RUSAGE_CHILDREN, &mut usage) } == 0 {
return timeinfo {
ut: usage.ru_utime.tv_sec as i64 * 1_000_000
+ usage.ru_utime.tv_usec as i64,
st: usage.ru_stime.tv_sec as i64 * 1_000_000
+ usage.ru_stime.tv_usec as i64,
};
}
}
timeinfo::default()
}
pub fn update_process(pn: &mut Process, status: i32) {
let prev = get_usage();
let now = get_usage();
pn.endtime = Some(Instant::now());
pn.status = status;
pn.ti.ut = (now.ut - prev.ut).max(0);
pn.ti.st = (now.st - prev.st).max(0);
}
pub fn findproc(jobtab: &[Job], pid: i32) -> Option<(usize, usize, bool)> {
for (ji, job) in jobtab.iter().enumerate() {
for (pi, proc) in job.procs.iter().enumerate() {
if proc.pid == pid {
return Some((ji, pi, false));
}
}
for (pi, proc) in job.auxprocs.iter().enumerate() {
if proc.pid == pid {
return Some((ji, pi, true));
}
}
}
None
}
pub fn update_job(job: &mut Job) -> bool { for proc in &job.auxprocs {
if proc.is_running() {
return false;
}
}
let all_done = true;
let mut some_stopped = false;
let mut last_status = 0;
for proc in &job.procs {
if proc.is_running() {
return false; }
if proc.is_stopped() {
some_stopped = true;
}
}
if let Some(last) = job.procs.last() {
if last.is_signaled() {
last_status = 0x80 | last.term_sig();
} else if last.is_stopped() {
last_status = 0x80 | last.stop_sig();
} else {
last_status = last.exit_status();
}
}
if some_stopped {
job.stat |= stat::STOPPED;
job.stat &= !stat::DONE;
} else {
job.stat |= stat::DONE;
job.stat &= !stat::STOPPED;
}
true
}
pub fn update_bg_job(jn: &mut [Job], pid: i32, status: i32) -> bool {
if let Some((ji, pi, is_aux)) = findproc(jn, pid) {
if is_aux {
jn[ji].auxprocs[pi].status = status;
jn[ji].auxprocs[pi].endtime = Some(Instant::now());
} else {
jn[ji].procs[pi].status = status;
jn[ji].procs[pi].endtime = Some(Instant::now());
}
update_job(&mut jn[ji]);
return true;
}
false
}
pub fn handle_sub(jobtab: &mut [Job], super_idx: usize, fg: bool) {
let sub_idx = jobtab[super_idx].other as usize;
if sub_idx >= jobtab.len() {
return;
}
if jobtab[sub_idx].is_done() {
if fg {
}
jobtab[super_idx].stat &= !stat::SUPERJOB;
jobtab[super_idx].stat |= stat::WASSUPER;
}
}
pub fn setprevjob() { let tab = JOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("jobtab poisoned");
let maxjob = *MAXJOB.get_or_init(|| Mutex::new(0))
.lock().expect("maxjob poisoned");
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1))
.lock().expect("curjob poisoned");
let thisjob = *THISJOB.get_or_init(|| Mutex::new(-1))
.lock().expect("thisjob poisoned");
for i in (1..=maxjob).rev() {
if i >= tab.len() { continue; }
let j = &tab[i];
if (j.stat & (stat::INUSE | stat::STOPPED)) == (stat::INUSE | stat::STOPPED)
&& (j.stat & stat::SUBJOB) == 0
&& i as i32 != curjob && i as i32 != thisjob
{
*PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = i as i32;
return;
}
}
for i in (1..=maxjob).rev() {
if i >= tab.len() { continue; }
let j = &tab[i];
if (j.stat & stat::INUSE) != 0
&& (j.stat & stat::SUBJOB) == 0
&& i as i32 != curjob && i as i32 != thisjob
{
*PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = i as i32;
return;
}
}
*PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = -1;
}
pub fn setcurjob() { let tab = JOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("jobtab poisoned");
let maxjob = *MAXJOB.get_or_init(|| Mutex::new(0))
.lock().expect("maxjob poisoned");
let mut found: i32 = -1;
for i in (1..=maxjob).rev() {
if i >= tab.len() { continue; }
if (tab[i].stat & (stat::INUSE | stat::STOPPED))
== (stat::INUSE | stat::STOPPED)
{
found = i as i32;
break;
}
}
if found < 0 {
for i in (1..=maxjob).rev() {
if i >= tab.len() { continue; }
if (tab[i].stat & stat::INUSE) != 0 {
found = i as i32;
break;
}
}
}
*CURJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = found;
drop(tab);
setprevjob();
}
pub fn should_report_time(job: &Job, reporttime: f64) -> bool {
if reporttime < 0.0 {
return false;
}
if let Some(first) = job.procs.first() {
if let (Some(start), Some(end)) =
(first.bgtime, job.procs.last().and_then(|p| p.endtime))
{
let elapsed = end.duration_since(start).as_secs_f64();
return elapsed >= reporttime;
}
}
false
}
pub fn dumptime(job: &Job, format: &str) -> Option<String> {
let first_start = job.procs.first()?.bgtime?;
let last_end = job.procs.last()?.endtime?;
let elapsed = last_end.duration_since(first_start).as_secs_f64();
let mut total_user = 0.0;
let mut total_sys = 0.0;
for proc in &job.procs {
total_user += proc.ti.ut as f64 / 1_000_000.0;
total_sys += proc.ti.st as f64 / 1_000_000.0;
}
Some(printtime(
elapsed,
total_user,
total_sys,
format,
&if !job.text.is_empty() { job.text.clone() } else { job.procs.iter().map(|p| p.text.as_str()).collect::<Vec<_>>().join(" | ") },
))
}
pub fn waitjobs(jobtab: &mut [Job], thisjob: usize) { if thisjob < jobtab.len() {
while !jobtab[thisjob].is_done() && !jobtab[thisjob].is_stopped() {
#[cfg(unix)]
{
let mut status: i32 = 0;
let pid = unsafe { libc::waitpid(-1, &mut status, libc::WUNTRACED) };
if pid > 0 {
update_bg_job(jobtab, pid, status);
} else {
break;
}
}
#[cfg(not(unix))]
{
break;
}
}
}
}
pub fn waitonejob(job: &mut Job) {
for proc in &mut job.procs {
if proc.is_running() {
if let Some(_status) = waitforpid(proc.pid) {
}
}
}
}
pub fn initjob(jobtab: &mut Vec<Job>) -> usize { for (i, job) in jobtab.iter().enumerate() {
if (job.stat & stat::INUSE) == 0 {
jobtab[i] = Job::new();
jobtab[i].stat = stat::INUSE;
return i;
}
}
let idx = jobtab.len();
let mut job = Job::new();
job.stat = stat::INUSE;
jobtab.push(job);
idx
}
pub fn setjobpwd(job: &mut Job) {
if let Ok(cwd) = std::env::current_dir() {
let _ = cwd;
}
}
pub fn spawnjob(job: &mut Job, fg: bool) { job.stat |= stat::INUSE;
if !fg {
job.stat &= !stat::CURSH;
}
}
pub fn selectjobtab() -> (Vec<Job>, usize) {
let oldtab = OLDJOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("oldjobtab poisoned");
if !oldtab.is_empty() { let oldmax = *OLDMAXJOB.get_or_init(|| Mutex::new(0))
.lock().expect("oldmaxjob poisoned");
(oldtab.clone(), oldmax) } else {
drop(oldtab); let jobtab = JOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("jobtab poisoned");
let maxjob = *MAXJOB.get_or_init(|| Mutex::new(0))
.lock().expect("maxjob poisoned");
(jobtab.clone(), maxjob) }
}
pub fn expandjobtab(jobtab: &mut Vec<Job>, _needed: usize) -> bool {
let newsize = jobtab.len() + MAXJOBS_ALLOC;
if newsize > MAX_MAXJOBS {
return false;
}
jobtab.resize_with(newsize, Job::new);
true
}
pub fn maybeshrinkjobtab(jobtab: &mut Vec<Job>) {
while jobtab
.last()
.map(|j| (j.stat & stat::INUSE) == 0)
.unwrap_or(false)
{
jobtab.pop();
}
}
pub fn addfilelist(job: &mut Job, name: Option<&str>, fd: i32) {
match name {
Some(n) => job.filelist.push(n.to_string()),
None => job.filelist.push(format!("<fd:{}>", fd)),
}
}
pub fn pipecleanfilelist(filelist: &mut Job, proc_subst_only: bool) { if proc_subst_only { filelist.filelist.retain(|f| {
!f.starts_with("/dev/fd/")
&& !f.starts_with("/proc/")
&& !f.starts_with("<fd:")
});
} else {
for entry in &filelist.filelist {
if let Some(rest) = entry.strip_prefix("<fd:") {
if let Some(num_str) = rest.strip_suffix('>') {
if let Ok(fd) = num_str.parse::<i32>() {
#[cfg(unix)]
unsafe { libc::close(fd); } }
}
} else {
let _ = std::fs::remove_file(entry); }
}
filelist.filelist.clear();
}
}
pub fn deletefilelist(file_list: &mut Job, disowning: bool) { if !disowning { for entry in &file_list.filelist {
if let Some(rest) = entry.strip_prefix("<fd:") {
if let Some(num_str) = rest.strip_suffix('>') {
if let Ok(fd) = num_str.parse::<i32>() {
#[cfg(unix)]
unsafe { libc::close(fd); } }
}
} else {
let _ = std::fs::remove_file(entry); }
}
}
file_list.filelist.clear();
}
pub fn printjob(
job: &Job,
job_num: usize,
long_format: bool,
cur_job: Option<usize>,
prev_job: Option<usize>,
) -> String {
let fmt_proc_status = |status: i32| -> String {
if status == SP_RUNNING {
"running".to_string()
} else if (status & 0x7f) == 0 {
let code = (status >> 8) & 0xff;
if code == 0 {
"done".to_string()
} else {
format!("exit {}", code)
}
} else if (status & 0xff) == 0x7f {
let sig = (status >> 8) & 0xff;
format!("suspended ({})", sigmsg(sig))
} else {
let sig = status & 0x7f;
let core = (status >> 7) & 1;
if core != 0 {
format!("{} (core dumped)", sigmsg(sig))
} else {
sigmsg(sig).to_string()
}
}
};
let marker = if Some(job_num) == cur_job {
'+'
} else if Some(job_num) == prev_job {
'-'
} else {
' '
};
let status_str = if job.is_done() {
if let Some(last) = job.procs.last() {
fmt_proc_status(last.status)
} else {
"done".to_string()
}
} else if job.is_stopped() {
"suspended".to_string()
} else {
"running".to_string()
};
if long_format {
let mut lines = Vec::new();
for (i, proc) in job.procs.iter().enumerate() {
let pstatus = fmt_proc_status(proc.status);
if i == 0 {
lines.push(format!(
"[{}] {} {:>5} {:16} {}",
job_num, marker, proc.pid, pstatus, proc.text
));
} else {
lines.push(format!(
" {:>5} {:16} | {}",
proc.pid, pstatus, proc.text
));
}
}
lines.join("\n")
} else {
format!(
"[{}] {} {:16} {}",
job_num,
marker,
status_str,
if !job.text.is_empty() { job.text.clone() } else { job.procs.iter().map(|p| p.text.as_str()).collect::<Vec<_>>().join(" | ") }
)
}
}
pub fn getsigname(sig: i32) -> String {
match sig {
0 => "EXIT".to_string(),
libc::SIGHUP => "HUP".to_string(),
libc::SIGINT => "INT".to_string(),
libc::SIGQUIT => "QUIT".to_string(),
libc::SIGILL => "ILL".to_string(),
libc::SIGTRAP => "TRAP".to_string(),
libc::SIGABRT => "ABRT".to_string(),
libc::SIGBUS => "BUS".to_string(),
libc::SIGFPE => "FPE".to_string(),
libc::SIGKILL => "KILL".to_string(),
libc::SIGUSR1 => "USR1".to_string(),
libc::SIGSEGV => "SEGV".to_string(),
libc::SIGUSR2 => "USR2".to_string(),
libc::SIGPIPE => "PIPE".to_string(),
libc::SIGALRM => "ALRM".to_string(),
libc::SIGTERM => "TERM".to_string(),
libc::SIGCHLD => "CHLD".to_string(),
libc::SIGCONT => "CONT".to_string(),
libc::SIGSTOP => "STOP".to_string(),
libc::SIGTSTP => "TSTP".to_string(),
libc::SIGTTIN => "TTIN".to_string(),
libc::SIGTTOU => "TTOU".to_string(),
libc::SIGURG => "URG".to_string(),
libc::SIGXCPU => "XCPU".to_string(),
libc::SIGXFSZ => "XFSZ".to_string(),
libc::SIGVTALRM => "VTALRM".to_string(),
libc::SIGPROF => "PROF".to_string(),
libc::SIGWINCH => "WINCH".to_string(),
libc::SIGIO => "IO".to_string(),
libc::SIGSYS => "SYS".to_string(),
_ => format!("SIG{}", sig),
}
}
pub fn dtime_tv(dt: &mut Duration, t1: &Duration, t2: &Duration) -> Duration {
if *t2 > *t1 {
*dt = *t2 - *t1;
} else {
*dt = Duration::ZERO;
}
*dt
}
pub fn dtime_ts(t1: &Instant, t2: &Instant) -> Duration {
if *t2 > *t1 {
t2.duration_since(*t1)
} else {
Duration::ZERO
}
}
pub fn makerunning(jobtab: &mut [Job], idx: usize) {
if idx >= jobtab.len() {
return;
}
let other = jobtab[idx].other as usize;
let is_super = (jobtab[idx].stat & stat::SUPERJOB) != 0;
{
let job = &mut jobtab[idx];
job.stat &= !stat::STOPPED;
for proc in &mut job.procs {
if proc.is_stopped() {
proc.status = SP_RUNNING;
}
}
}
if is_super && other != idx && other < jobtab.len() {
makerunning(jobtab, other);
}
}
pub fn hasprocs(jobtab: &[Job], job: usize) -> bool {
jobtab
.get(job)
.map(|j| !j.procs.is_empty() || !j.auxprocs.is_empty())
.unwrap_or(false)
}
#[cfg(unix)]
pub fn check_cursh_sig(jobtab: &[Job], sig: i32) {
for job in jobtab {
if (job.stat & stat::CURSH) != 0 && !job.is_done() {
for proc in &job.procs {
if proc.is_running() {
unsafe {
libc::kill(proc.pid, sig);
}
}
}
}
}
}
pub fn cleanfilelists(jobtab: &mut [Job]) {
for job in jobtab.iter_mut().skip(1) {
deletefilelist(job, false);
}
}
pub fn clearoldjobtab() {
*OLDJOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("oldjobtab poisoned") = Vec::new();
*OLDMAXJOB.get_or_init(|| Mutex::new(0))
.lock().expect("oldmaxjob poisoned") = 0;
}
pub fn addbgstatus(pid: i32, status_val: i32) { let max_child = unsafe { libc::sysconf(libc::_SC_CHILD_MAX) };
let cap = if max_child > 0 { max_child as i64 } else { 1024 };
if let Ok(mut list) = bgstatus_list.lock() {
if bgstatus_count.load(Ordering::Relaxed) >= cap { if !list.is_empty() {
list.remove(0);
bgstatus_count.fetch_sub(1, Ordering::Relaxed);
}
}
list.push(bgstatus { pid, status: status_val }); bgstatus_count.fetch_add(1, Ordering::Relaxed); }
}
pub fn getbgstatus(pid: i32) -> Option<i32> { if let Ok(mut list) = bgstatus_list.lock() {
if let Some(idx) = list.iter().position(|b| b.pid == pid) { let status = list[idx].status;
list.remove(idx); bgstatus_count.fetch_sub(1, Ordering::Relaxed);
return Some(status);
}
}
None
}
pub fn gettrapnode(sig: i32) -> Option<String> {
let name = format!("TRAP{}", getsigname(sig));
let tab = crate::ported::hashtable::shfunctab_lock()
.read()
.expect("shfunctab poisoned");
tab.get_including_disabled(&name)
.and_then(|f| f.body.clone())
}
pub fn removetrapnode(sig: i32) {
let name = format!("TRAP{}", getsigname(sig));
crate::ported::hashtable::removeshfuncnode(&name);
}
#[cfg(unix)]
pub fn release_pgrp() { let origpgrp = *ORIGPGRP.get_or_init(|| Mutex::new(0))
.lock().expect("origpgrp poisoned");
let mypgrp = *MYPGRP.get_or_init(|| Mutex::new(0))
.lock().expect("mypgrp poisoned");
if origpgrp != mypgrp { if origpgrp != 0 { unsafe {
libc::tcsetpgrp(0, origpgrp);
libc::setpgid(0, origpgrp); }
}
*MYPGRP.get_or_init(|| Mutex::new(0)) .lock().expect("mypgrp poisoned") = origpgrp;
}
}
pub fn bin_fg(name: &str, argv: &[String], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let _ofunc = func;
if OPT_ISSET(ops, b'Z') { if argv.is_empty() || argv.len() > 1 { zwarnnam(name, "-Z requires one argument"); return 1; }
crate::ported::mem::queue_signals(); let title = &argv[0];
#[cfg(target_os = "linux")]
unsafe {
let cs = std::ffi::CString::new(title.as_str()).unwrap_or_default();
libc::prctl(15 , cs.as_ptr() as libc::c_ulong, 0, 0, 0); }
#[cfg(target_os = "macos")]
unsafe {
extern "C" {
fn pthread_setname_np(name: *const libc::c_char) -> libc::c_int;
}
let cs = std::ffi::CString::new(title.as_str()).unwrap_or_default();
pthread_setname_np(cs.as_ptr());
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = title;
}
crate::ported::mem::unqueue_signals(); return 0; }
let mut lng = 0i32; if func == BIN_JOBS { lng = if OPT_ISSET(ops, b'l') { 1 } else if OPT_ISSET(ops, b'p') { 2 } else { 0 };
if OPT_ISSET(ops, b'd') { lng |= 4; } } else {
lng = if crate::ported::zsh_h::isset(crate::ported::zsh_h::LONGLISTJOBS) {
1
} else {
0
};
}
let _ = lng;
let jobbing = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR);
if (func == BIN_FG || func == BIN_BG) && !jobbing { zwarnnam(name, "no job control in this shell."); return 1; }
crate::ported::signals::queue_signals();
crate::ported::signals::wait_for_processes();
let table = JOBTAB.get_or_init(|| Mutex::new(Vec::new()));
if func != BIN_JOBS || jobbing
|| *OLDMAXJOB.get_or_init(|| Mutex::new(0)).lock().unwrap() == 0
{
setcurjob();
}
if func == BIN_JOBS {
crate::ported::builtin::STOPMSG
.store(2, std::sync::atomic::Ordering::Relaxed); }
let mut returnval: i32 = 0;
if argv.is_empty() { if func == BIN_JOBS {
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1))
.lock().unwrap();
let t = table.lock().expect("jobtab poisoned");
let curmaxjob = t.len();
let r_only = OPT_ISSET(ops, b'r');
let s_only = OPT_ISSET(ops, b's');
for job in 0..curmaxjob { if job as i32 == curjob { continue;
}
let j = &t[job];
if !j.is_inuse() { continue;
}
let stopped = j.is_stopped();
if (!r_only && !s_only)
|| (r_only && s_only)
|| (r_only && !stopped)
|| (s_only && stopped)
{
let curjob_opt = if curjob >= 0 {
Some(curjob as usize)
} else {
None
};
let prevjob = *PREVJOB
.get_or_init(|| Mutex::new(-1)).lock().unwrap();
let prevjob_opt = if prevjob >= 0 {
Some(prevjob as usize)
} else {
None
};
print!("{}", printjob(j, job, (lng & 1) != 0,
curjob_opt, prevjob_opt));
}
}
crate::ported::signals::unqueue_signals(); return 0; }
if func == BIN_FG || func == BIN_BG {
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1))
.lock().unwrap();
if curjob < 0 {
zwarnnam(name, "no current job"); crate::ported::signals::unqueue_signals();
return 1; }
let gleader = table.lock().expect("jobtab poisoned")
.get(curjob as usize).map(|j| j.gleader).unwrap_or(0);
if gleader > 0 {
let _ = crate::ported::signals::killjb(gleader, libc::SIGCONT);
}
crate::ported::signals::unqueue_signals();
return 0;
}
crate::ported::signals::unqueue_signals();
return 0;
}
for arg in argv {
let p = if arg.starts_with('%') {
getjob(arg, name) } else if let Ok(n) = arg.parse::<i32>() {
if n >= 0 { n } else { -1 }
} else {
zwarnnam(name, &format!("{}: no such job", arg));
returnval = 1;
continue;
};
if p < 0 {
returnval = 1;
continue;
}
let gleader = table.lock().expect("jobtab poisoned")
.get(p as usize).map(|j| j.gleader).unwrap_or(0);
if func == BIN_FG || func == BIN_BG {
if gleader > 0 {
if crate::ported::signals::killjb(gleader, libc::SIGCONT) == -1 {
zwarnnam(name, &format!("{}: kill failed: {}", arg,
std::io::Error::last_os_error()));
returnval = 1;
}
}
} else if func == BIN_JOBS {
let t = table.lock().expect("jobtab poisoned");
if let Some(j) = t.get(p as usize) {
let curjob = *CURJOB.get_or_init(|| Mutex::new(-1))
.lock().unwrap();
let prevjob = *PREVJOB.get_or_init(|| Mutex::new(-1))
.lock().unwrap();
print!("{}", printjob(j, p as usize, (lng & 1) != 0,
if curjob >= 0 { Some(curjob as usize) } else { None },
if prevjob >= 0 { Some(prevjob as usize) } else { None }));
}
}
}
crate::ported::signals::unqueue_signals(); returnval }
pub fn bin_kill(nam: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut sig: i32 = libc::SIGTERM; let mut returnval: i32 = 0; let mut got_sig = false; let mut idx = 0usize;
while idx < argv.len() && argv[idx].starts_with('-') {
let arg = argv[idx].clone();
let body = &arg[1..];
if body == "-" { idx += 1;
break;
}
if got_sig { break; }
if body.chars().next().is_some_and(|c| c.is_ascii_digit()) { match body.parse::<i32>() {
Ok(n) => sig = n, Err(_) => {
zwarnnam(nam, &format!("invalid signal number: -{}", body));
return 1; }
}
got_sig = true;
idx += 1;
continue;
}
if body == "l" { idx += 1;
if idx < argv.len() { while idx < argv.len() {
let token = &argv[idx];
idx += 1;
if let Ok(n) = token.parse::<i32>() { let s = (n & !0o200) as i32; if let Some(name) = crate::ported::signals_h::sigs_name(s) { println!("{}", name);
} else {
println!("{}", n); }
} else {
let upper = token.to_ascii_uppercase();
let bare = upper.strip_prefix("SIG").unwrap_or(&upper);
if let Some(n) = crate::ported::signals_h::sigs_number(bare) { println!("{}", n); } else {
zwarnnam(nam,
&format!("unknown signal: SIG{}", bare)); returnval += 1;
}
}
}
return returnval; }
print!("{}", crate::ported::signals_h::sigs_name(1).unwrap_or("HUP"));
for s in 2..=crate::ported::signals_h::SIGCOUNT {
if let Some(n) = crate::ported::signals_h::sigs_name(s) { print!(" {}", n); }
}
println!();
return 0; }
if body == "L" { let cols = 4usize;
let mut col = 0usize;
for s in 1..=crate::ported::signals_h::SIGCOUNT {
if let Some(n) = crate::ported::signals_h::sigs_name(s) {
print!("{:>2} {:<10}", s, n);
col += 1;
if col % cols == 0 { println!(); }
else { print!(" "); }
}
}
if col % cols != 0 { println!(); }
return 0; }
if body == "n" { idx += 1;
if idx >= argv.len() { zwarnnam(nam, "-n: argument expected"); return 1; }
match argv[idx].parse::<i32>() { Ok(n) => { sig = n; }
Err(_) => {
zwarnnam(nam,
&format!("invalid signal number: {}", argv[idx])); return 1;
}
}
got_sig = true;
idx += 1;
continue;
}
if body == "s" { idx += 1;
if idx >= argv.len() { zwarnnam(nam, "-s: argument expected"); return 1;
}
let name = argv[idx].as_str();
let upper = name.to_ascii_uppercase();
let bare = upper.strip_prefix("SIG").unwrap_or(&upper);
match crate::ported::signals_h::sigs_number(bare) {
Some(n) => sig = n,
None => {
zwarnnam(nam,
&format!("unknown signal: SIG{}", bare)); return 1;
}
}
got_sig = true;
idx += 1;
continue;
}
if body == "q" { idx += 1;
if idx >= argv.len() { zwarnnam(nam, "-q: argument expected"); return 1;
}
if argv[idx].parse::<i32>().is_err() { zwarnnam(nam,
&format!("invalid number: {}", argv[idx])); return 1;
}
idx += 1; continue; }
let upper = body.to_ascii_uppercase();
let bare = upper.strip_prefix("SIG").unwrap_or(&upper);
match crate::ported::signals_h::sigs_number(bare) {
Some(n) => { sig = n; got_sig = true; idx += 1; }
None => {
zwarnnam(nam, &format!("unknown signal: SIG{}", bare)); return 1;
}
}
}
if idx >= argv.len() { zwarnnam(nam, "not enough arguments"); return 1;
}
for arg in &argv[idx..] {
if let Some(num) = arg.strip_prefix('-') { match num.parse::<i32>() {
Ok(pgid) => {
let r = unsafe { libc::killpg(pgid, sig) }; if r != 0 {
zwarnnam(nam, &format!("kill {}: {}", arg,
std::io::Error::last_os_error()));
returnval = 1;
}
}
Err(_) => {
zwarnnam(nam, &format!("illegal pid: {}", arg));
returnval = 1;
}
}
} else if arg.starts_with('%') { let p = crate::ported::jobs::getjob(arg, nam);
if p < 0 { returnval += 1; continue;
}
let gleader = JOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("jobtab poisoned")
.get(p as usize).map(|j| j.gleader).unwrap_or(0);
if crate::ported::signals::killjb(gleader, sig) == -1 { zwarnnam("kill", &format!("kill {} failed: {}", arg, std::io::Error::last_os_error()));
returnval += 1; continue;
}
let stopped = JOBTAB.get_or_init(|| Mutex::new(Vec::new()))
.lock().expect("jobtab poisoned")
.get(p as usize).map(|j| j.is_stopped()).unwrap_or(false);
if stopped
&& sig != libc::SIGKILL && sig != libc::SIGCONT
&& sig != libc::SIGTSTP && sig != libc::SIGTTOU
&& sig != libc::SIGTTIN && sig != libc::SIGSTOP
{
let _ = crate::ported::signals::killjb(gleader, libc::SIGCONT); }
} else {
match arg.parse::<i32>() { Ok(pid) => {
let r = unsafe { libc::kill(pid, sig) }; if r != 0 {
zwarnnam(nam, &format!("kill {}: {}", arg,
std::io::Error::last_os_error())); returnval = 1;
}
}
Err(_) => {
zwarnnam(nam, &format!("illegal pid: {}", arg));
returnval = 1;
}
}
}
}
returnval }
pub fn bin_suspend(name: &str, _argv: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let islogin = crate::ported::params::getsparam("0")
.map(|s| s.starts_with('-'))
.unwrap_or(false);
if islogin && !OPT_ISSET(ops, b'f') { zwarnnam(name, "can't suspend login shell"); return 1; }
let jobbing = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR);
if jobbing { signal_default(libc::SIGTTIN); signal_default(libc::SIGTSTP); signal_default(libc::SIGTTOU); release_pgrp(); }
let origpgrp = ORIGPGRP.get_or_init(|| Mutex::new(0))
.lock().map(|g| *g).unwrap_or(0);
unsafe { libc::killpg(origpgrp, libc::SIGTSTP); }
if jobbing { let _ = acquire_pgrp(); signal_ignore(libc::SIGTTOU); signal_ignore(libc::SIGTSTP); signal_ignore(libc::SIGTTIN); }
0 }
pub fn getsigidx(s: &str) -> Option<i32> {
let s = s.strip_prefix("SIG").unwrap_or(s);
match s.to_uppercase().as_str() {
"EXIT" => Some(0),
"HUP" => Some(libc::SIGHUP),
"INT" => Some(libc::SIGINT),
"QUIT" => Some(libc::SIGQUIT),
"ILL" => Some(libc::SIGILL),
"TRAP" => Some(libc::SIGTRAP),
"ABRT" | "IOT" => Some(libc::SIGABRT),
"BUS" => Some(libc::SIGBUS),
"FPE" => Some(libc::SIGFPE),
"KILL" => Some(libc::SIGKILL),
"USR1" => Some(libc::SIGUSR1),
"SEGV" => Some(libc::SIGSEGV),
"USR2" => Some(libc::SIGUSR2),
"PIPE" => Some(libc::SIGPIPE),
"ALRM" => Some(libc::SIGALRM),
"TERM" => Some(libc::SIGTERM),
"CHLD" | "CLD" => Some(libc::SIGCHLD),
"CONT" => Some(libc::SIGCONT),
"STOP" => Some(libc::SIGSTOP),
"TSTP" => Some(libc::SIGTSTP),
"TTIN" => Some(libc::SIGTTIN),
"TTOU" => Some(libc::SIGTTOU),
"URG" => Some(libc::SIGURG),
"XCPU" => Some(libc::SIGXCPU),
"XFSZ" => Some(libc::SIGXFSZ),
"VTALRM" => Some(libc::SIGVTALRM),
"PROF" => Some(libc::SIGPROF),
"WINCH" => Some(libc::SIGWINCH),
"IO" | "POLL" => Some(libc::SIGIO),
"SYS" => Some(libc::SIGSYS),
_ => s.parse().ok(),
}
}