#![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 libc::time_t;
use crate::ported::utils::{
addprepromptfn, addtimedfn, delprepromptfn, deltimedfn,
unmeta, zjoin, zstrtol, ztrftime, zwarnnam,
};
use crate::ported::ztype_h::idigit;
use crate::ported::zsh_h::options;
pub type Schedcmd = Option<Box<schedcmd>>;
pub const SCHEDFLAG_TRASH_ZLE: i32 = 1;
#[derive(Debug)]
pub struct schedcmd {
pub next: Option<Box<schedcmd>>, pub cmd: String, pub time: time_t, pub flags: i32, }
static schedcmds: OnceLock<Mutex<Option<Box<schedcmd>>>> = OnceLock::new();
static schedcmdtimed: OnceLock<Mutex<i32>> = OnceLock::new();
fn schedcmds_lock() -> &'static Mutex<Option<Box<schedcmd>>> {
schedcmds.get_or_init(|| Mutex::new(None))
}
fn schedcmdtimed_lock() -> &'static Mutex<i32> {
schedcmdtimed.get_or_init(|| Mutex::new(0))
}
pub(crate) fn schedaddtimed() { if *schedcmdtimed_lock().lock().unwrap() != 0 { scheddeltimed(); }
*schedcmdtimed_lock().lock().unwrap() = 1; let head_time: time_t = schedcmds_lock() .lock()
.unwrap()
.as_ref()
.map(|h| h.time)
.unwrap_or(0);
addtimedfn(checksched_thunk, head_time as i64); }
fn checksched_thunk() {
let _ = checksched();
}
pub(crate) fn scheddeltimed() { if *schedcmdtimed_lock().lock().unwrap() != 0 { deltimedfn(checksched_thunk); *schedcmdtimed_lock().lock().unwrap() = 0; }
}
pub(crate) fn checksched() -> i32 { let t: time_t; if schedcmds_lock().lock().unwrap().is_none() { return 0; }
t = unsafe { libc::time(std::ptr::null_mut()) }; loop { let due = {
let head = schedcmds_lock().lock().unwrap();
match head.as_ref() {
Some(h) if h.time <= t => true,
_ => false,
}
};
if !due { break; }
let sch: Box<schedcmd> = { let mut head = schedcmds_lock().lock().unwrap();
let mut h = head.take().unwrap();
*head = h.next.take(); h
};
scheddeltimed();
if (sch.flags & SCHEDFLAG_TRASH_ZLE) != 0
&& zleactive.load(Ordering::Relaxed) != 0 {
crate::ported::init::zleentry(ZLE_CMD_TRASH); }
execstring(&sch.cmd, 0, 0, "sched"); drop(sch);
let need_arm = {
let head = schedcmds_lock().lock().unwrap();
head.is_some() && *schedcmdtimed_lock().lock().unwrap() == 0
};
if need_arm { schedaddtimed(); }
}
0
}
pub(crate) fn bin_sched(nam: &str, argv: &[String], _ops: &options, _func: i32) -> i32 { let s: &str;
let mut argptr: usize;
let mut t: time_t;
let mut h: i64;
let m: i64;
let sec: i64;
let mut sn: i32;
let mut flags: i32 = 0;
argptr = 0; while argptr < argv.len() && argv[argptr].as_bytes().first().copied() == Some(b'-') {
let arg = &argv[argptr][1..]; let arg_b = arg.as_bytes();
if !arg_b.is_empty() && idigit(arg_b[0]) { sn = zstrtol(arg, 10).0 as i32;
if sn == 0 { zwarnnam("sched", "usage for delete: sched -<item#>."); return 1;
}
let mut head = schedcmds_lock().lock().unwrap();
let mut steps = sn - 1;
let mut slot: &mut Option<Box<schedcmd>> = &mut *head;
while steps > 0 {
if slot.is_none() { break; }
slot = &mut slot.as_mut().unwrap().next;
steps -= 1;
}
if slot.is_none() { drop(head);
zwarnnam("sched", "not that many entries"); return 1;
}
let head_slot = sn == 1;
let mut removed = slot.take().unwrap();
*slot = removed.next.take();
if head_slot { drop(head);
scheddeltimed();
let still_have = schedcmds_lock().lock().unwrap().is_some();
if still_have { schedaddtimed(); }
} else {
drop(head);
}
drop(removed); return 0; } else if arg == "-" { argptr += 1; break; } else if arg == "o" { flags |= SCHEDFLAG_TRASH_ZLE; } else {
if !arg.is_empty() { zwarnnam(nam, &format!("bad option: -{}", arg.chars().next().unwrap())); } else {
zwarnnam(nam, "option expected"); }
return 1; }
argptr += 1;
}
if argptr >= argv.len() { sn = 1;
let head = schedcmds_lock().lock().unwrap();
let mut cur: &Option<Box<schedcmd>> = &*head;
while let Some(sch) = cur.as_ref() {
let t_local: time_t = sch.time; let tbuf = ztrftime( "%a %b %e %k:%M:%S",
std::time::UNIX_EPOCH + std::time::Duration::from_secs(t_local as u64),
);
let flagstr = if (sch.flags & SCHEDFLAG_TRASH_ZLE) != 0 { "-o " } else {
"" };
let endstr = if sch.cmd.starts_with('-') { "-- " } else {
"" };
println!("{:3} {} {}{}{}", sn, tbuf, flagstr, endstr, unmeta(&sch.cmd)); cur = &sch.next;
sn += 1;
}
return 0; } else if argptr + 1 >= argv.len() { zwarnnam("sched", "not enough arguments"); return 1; }
s = &argv[argptr]; argptr += 1;
let s_b = s.as_bytes();
if !s_b.is_empty() && s_b[0] == b'+' { let (zl, rest) = zstrtol(&s[1..], 10); let rb = rest.as_bytes();
if !rb.is_empty() && rb[0] == b':' { let (mv, rest2) = zstrtol(&rest[1..], 10); m = mv;
let rb2 = rest2.as_bytes();
let rest3: &str;
if !rb2.is_empty() && rb2[0] == b':' { let (sv, r3) = zstrtol(&rest2[1..], 10); sec = sv;
rest3 = r3;
} else {
sec = 0; rest3 = rest2;
}
if !rest3.is_empty() { zwarnnam("sched", "bad time specifier"); return 1;
}
t = unsafe { libc::time(std::ptr::null_mut()) } + (zl as time_t) * 3600 + (m as time_t) * 60 + (sec as time_t);
} else if rest.is_empty() { t = unsafe { libc::time(std::ptr::null_mut()) } + zl as time_t; } else {
zwarnnam("sched", "bad time specifier"); return 1;
}
} else {
let (zl, rest) = zstrtol(s, 10); let rb = rest.as_bytes();
if !rb.is_empty() && rb[0] == b':' { h = zl; let (mv, rest2) = zstrtol(&rest[1..], 10); m = mv;
let rb2 = rest2.as_bytes();
let rest3: &str;
if !rb2.is_empty() && rb2[0] == b':' { let (sv, r3) = zstrtol(&rest2[1..], 10); sec = sv;
rest3 = r3;
} else {
sec = 0; rest3 = rest2;
}
let rb3 = rest3.as_bytes();
if !rb3.is_empty()
&& rb3[0] != b'a' && rb3[0] != b'A'
&& rb3[0] != b'p' && rb3[0] != b'P' {
zwarnnam("sched", "bad time specifier"); return 1;
}
t = unsafe { libc::time(std::ptr::null_mut()) }; unsafe {
let tm_ptr = libc::localtime(&t as *const time_t); if !tm_ptr.is_null() {
let tm = *tm_ptr;
t -= (tm.tm_sec as time_t)
+ (tm.tm_min as time_t) * 60
+ (tm.tm_hour as time_t) * 3600; }
}
if !rb3.is_empty() && (rb3[0] == b'p' || rb3[0] == b'P') { h += 12; }
t += (h as time_t) * 3600 + (m as time_t) * 60 + (sec as time_t); if t < unsafe { libc::time(std::ptr::null_mut()) } { t += 3600 * 24; }
} else if rest.is_empty() { t = zl as time_t; } else {
zwarnnam("sched", "bad time specifier"); return 1;
}
}
let mut sch_new: Box<schedcmd> = Box::new(schedcmd {
next: None,
cmd: zjoin(&argv[argptr..], ' '), time: t, flags, });
let mut head = schedcmds_lock().lock().unwrap();
if head.is_some() { if sch_new.time < head.as_ref().unwrap().time { drop(head);
scheddeltimed(); let mut head2 = schedcmds_lock().lock().unwrap();
sch_new.next = head2.take(); *head2 = Some(sch_new); drop(head2);
schedaddtimed(); } else {
let mut cur = head.as_mut().unwrap();
while cur.next.is_some() && cur.next.as_ref().unwrap().time < sch_new.time {
cur = cur.next.as_mut().unwrap();
}
sch_new.next = cur.next.take(); cur.next = Some(sch_new); }
} else {
sch_new.next = None; *head = Some(sch_new); drop(head);
schedaddtimed(); }
0 }
pub(crate) fn schedgetfn(_pm: *const crate::ported::zsh_h::param) -> Vec<String> {
let mut i: usize; let head = schedcmds_lock().lock().unwrap();
i = 0;
{
let mut cur: &Option<Box<schedcmd>> = &*head;
while let Some(s) = cur.as_ref() {
cur = &s.next;
i += 1;
}
}
let mut ret: Vec<String> = Vec::with_capacity(i);
let mut cur: &Option<Box<schedcmd>> = &*head;
while let Some(sch) = cur.as_ref() {
let t: time_t = sch.time; let tbuf = format!("{}", t as i64); let flagstr = if (sch.flags & SCHEDFLAG_TRASH_ZLE) != 0 { "-o" } else {
"" };
ret.push(format!("{}:{}:{}", tbuf, flagstr, sch.cmd));
cur = &sch.next;
}
ret }
use crate::ported::zsh_h::features as features_t;
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: 1, n_abstract: 0, }))
}
#[allow(unused_variables)]
pub fn setup_(m: *const crate::ported::zsh_h::module) -> i32 { 0 }
pub fn features_(m: *const crate::ported::zsh_h::module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features()); 0 }
pub fn enables_(m: *const crate::ported::zsh_h::module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const crate::ported::zsh_h::module) -> i32 { addprepromptfn(checksched_thunk); 0 }
pub fn cleanup_(m: *const crate::ported::zsh_h::module) -> i32 { if schedcmds_lock().lock().unwrap().is_some() { scheddeltimed(); }
let mut head = schedcmds_lock().lock().unwrap();
*head = None;
drop(head);
delprepromptfn(checksched_thunk); setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const crate::ported::zsh_h::module) -> i32 { 0 }
fn featuresarray(_m: *const crate::ported::zsh_h::module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:sched".to_string(), "p:zsh_scheduled_events".to_string()]
}
fn handlefeatures(m: *const crate::ported::zsh_h::module, f: &Mutex<features_t>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(getfeatureenables(m, f));
} else if let Some(e) = enables.as_ref() {
return setfeatureenables(m, f, Some(e));
}
0
}
fn getfeatureenables(_m: *const crate::ported::zsh_h::module, f: &Mutex<features_t>) -> Vec<i32> {
let g = f.lock().unwrap();
let total = g.bn_size + g.cd_size + g.mf_size + g.pd_size + g.n_abstract;
vec![0; total as usize]
}
fn setfeatureenables(_m: *const crate::ported::zsh_h::module, _f: &Mutex<features_t>, _e: Option<&Vec<i32>>) -> i32 {
0
}
const ZLE_CMD_TRASH: i32 = 3;
pub static zleactive: AtomicI32 = AtomicI32::new(0);
fn execstring(_cmd: &str, _exiting: i32, _dont_change_job: i32, _context: &str) {}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
use crate::ported::zsh_h::{options, MAX_OPS};
static TEST_SERIAL: StdMutex<()> = StdMutex::new(());
fn empty_ops() -> options {
options { ind: [0u8; MAX_OPS], args: Vec::new(), argscount: 0, argsalloc: 0 }
}
fn reset_with_lock() -> std::sync::MutexGuard<'static, ()> {
let guard = TEST_SERIAL.lock().unwrap_or_else(|e| {
TEST_SERIAL.clear_poison();
e.into_inner()
});
crate::ported::utils::inittyptab();
let mut head = schedcmds_lock().lock().unwrap_or_else(|e| {
schedcmds_lock().clear_poison();
e.into_inner()
});
*head = None;
drop(head);
let mut t = schedcmdtimed_lock().lock().unwrap_or_else(|e| {
schedcmdtimed_lock().clear_poison();
e.into_inner()
});
*t = 0;
drop(t);
guard
}
fn s(x: &str) -> String { x.to_string() }
#[test]
fn list_empty_returns_zero() {
let _serial = reset_with_lock();
let ops = empty_ops();
assert_eq!(bin_sched("sched", &[], &ops, 0), 0);
}
#[test]
fn add_relative_seconds_pushes_one_entry() {
let _serial = reset_with_lock();
let ops = empty_ops();
let args = vec![s("+60"), s("echo"), s("hello")];
assert_eq!(bin_sched("sched", &args, &ops, 0), 0);
let head = schedcmds_lock().lock().unwrap();
assert_eq!(head.as_ref().unwrap().cmd, "echo hello");
assert!(head.as_ref().unwrap().next.is_none());
}
#[test]
fn add_then_delete_first_clears_list() {
let _serial = reset_with_lock();
let ops = empty_ops();
bin_sched("sched", &[s("+60"), s("echo"), s("hello")], &ops, 0);
assert!(schedcmds_lock().lock().unwrap().is_some());
assert_eq!(bin_sched("sched", &[s("-1")], &ops, 0), 0);
assert!(schedcmds_lock().lock().unwrap().is_none());
}
#[test]
fn not_enough_args_returns_one() {
let _serial = reset_with_lock();
let ops = empty_ops();
assert_eq!(bin_sched("sched", &[s("+60")], &ops, 0), 1);
}
#[test]
fn dash_o_flag_sets_trash_zle() {
let _serial = reset_with_lock();
let ops = empty_ops();
let args = vec![s("-o"), s("+60"), s("cmd")];
assert_eq!(bin_sched("sched", &args, &ops, 0), 0);
let head = schedcmds_lock().lock().unwrap();
assert_eq!(head.as_ref().unwrap().flags & SCHEDFLAG_TRASH_ZLE, SCHEDFLAG_TRASH_ZLE);
}
#[test]
fn insert_keeps_time_order() {
let _serial = reset_with_lock();
let ops = empty_ops();
bin_sched("sched", &[s("+200"), s("third")], &ops, 0);
bin_sched("sched", &[s("+50"), s("first")], &ops, 0);
bin_sched("sched", &[s("+100"), s("second")], &ops, 0);
let head = schedcmds_lock().lock().unwrap();
let n0 = head.as_ref().unwrap();
let n1 = n0.next.as_ref().unwrap();
let n2 = n1.next.as_ref().unwrap();
assert_eq!(n0.cmd, "first");
assert_eq!(n1.cmd, "second");
assert_eq!(n2.cmd, "third");
assert!(n2.next.is_none());
}
#[test]
fn schedgetfn_serializes_entries() {
let _serial = reset_with_lock();
let mut head = schedcmds_lock().lock().unwrap();
*head = Some(Box::new(schedcmd {
next: Some(Box::new(schedcmd {
next: None,
cmd: "echo zle".to_string(),
time: 1700001000,
flags: SCHEDFLAG_TRASH_ZLE,
})),
cmd: "echo test".to_string(),
time: 1700000000,
flags: 0,
}));
drop(head);
let arr = schedgetfn(std::ptr::null());
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], "1700000000::echo test");
assert_eq!(arr[1], "1700001000:-o:echo zle");
}
}