From 90cb143523ef9a3241da7fccc6f4e60188e1ea51 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 5 May 2026 09:28:53 +0700 Subject: [PATCH] Unify alarm implementation --- src/header/signal/mod.rs | 25 +++++++- src/header/unistd/alarm.rs | 108 ++++++++++++++++++++++++++++++++ src/header/unistd/mod.rs | 9 ++- src/platform/linux/mod.rs | 4 +- src/platform/linux/signal.rs | 49 +-------------- src/platform/pal/signal.rs | 5 +- src/platform/redox/signal.rs | 117 +---------------------------------- 7 files changed, 146 insertions(+), 171 deletions(-) create mode 100644 src/header/unistd/alarm.rs diff --git a/src/header/signal/mod.rs b/src/header/signal/mod.rs index d95eabc093..584c5f4270 100644 --- a/src/header/signal/mod.rs +++ b/src/header/signal/mod.rs @@ -6,14 +6,14 @@ use core::{mem, ptr}; use cbitset::BitSet; +#[cfg(target_os = "redox")] +use crate::platform::types::pthread_attr_t; use crate::{ error::{Errno, ResultExt}, header::{bits_sigset_t::sigset_t, errno, setjmp, time::timespec}, platform::{ self, ERRNO, Pal, PalSignal, Sys, - types::{ - c_char, c_int, c_ulonglong, c_void, pid_t, pthread_attr_t, pthread_t, size_t, uid_t, - }, + types::{c_char, c_int, c_ulonglong, c_void, pid_t, pthread_t, size_t, uid_t}, }, }; @@ -69,6 +69,7 @@ pub struct sigaltstack { /// See . #[repr(C)] #[derive(Clone)] +#[cfg(not(target_os = "linux"))] pub struct sigevent { pub sigev_value: sigval, pub sigev_signo: c_int, @@ -77,6 +78,24 @@ pub struct sigevent { pub sigev_notify_attributes: *mut pthread_attr_t, } +// must match with signature from libc +// https://docs.rs/libc/0.2.186/src/libc/unix/linux_like/mod.rs.html#300-322 +#[repr(C)] +#[derive(Clone)] +#[cfg(target_os = "linux")] +pub struct sigevent { + pub sigev_value: sigval, + pub sigev_signo: c_int, + pub sigev_notify: c_int, + // Actually a union. We only expose sigev_notify_thread_id because it's + // the most useful member + pub sigev_notify_thread_id: c_int, + #[cfg(target_pointer_width = "64")] + __unused1: [c_int; 11], + #[cfg(target_pointer_width = "32")] + __unused1: [c_int; 12], +} + // FIXME: This struct is wrong on Linux #[repr(C)] #[derive(Clone, Copy)] diff --git a/src/header/unistd/alarm.rs b/src/header/unistd/alarm.rs new file mode 100644 index 0000000000..8a999b5724 --- /dev/null +++ b/src/header/unistd/alarm.rs @@ -0,0 +1,108 @@ +#[cfg(target_os = "redox")] +use crate::header::time::timer_internal_t; +use crate::{ + header::{ + bits_timespec::timespec, + signal::{SIGALRM, SIGEV_SIGNAL, sigevent}, + time::itimerspec, + }, + out::Out, + platform::{ + Pal, Sys, + types::{c_int, c_uint, timer_t}, + }, + sync::Mutex, +}; + +/// Wrapper for timer_t that implements Send (the timer_t pointer is a process- +/// wide mmap'd allocation that outlives any single thread). +struct AlarmTimer(timer_t); +// SAFETY: The timer_t pointer refers to an mmap'd timer_internal_t that is +// only accessed under the ALARM_TIMER mutex lock. +unsafe impl Send for AlarmTimer {} + +/// Process-global singleton timer used by alarm(). Protected by a mutex to +/// ensure only one alarm is active at a time (POSIX requirement). +static ALARM_TIMER: Mutex> = Mutex::new(None); + +/// Internal helper that arms/disarms the process-global alarm timer. +/// Accepts a full timespec so sub-second timers (ualarm) can reuse this later. +/// Returns the number of seconds remaining on the previous alarm (rounded up), +/// or 0 if there was no previous alarm. +/// +/// TODO: This implementation does not survive `exec()`. POSIX requires that a +/// pending alarm be preserved across exec (the timer continues counting down +/// in the new process image as i understand). +pub fn alarm_timespec(duration: timespec) -> c_uint { + let mut guard = ALARM_TIMER.lock(); + + // Determine remaining time on any existing alarm + let remaining = if let Some(ref alarm) = *guard { + let mut cur = itimerspec::default(); + if Sys::timer_gettime(alarm.0, Out::from_mut(&mut cur)).is_ok() { + let secs = cur.it_value.tv_sec as c_uint; + if cur.it_value.tv_nsec > 0 { + secs + 1 // POSIX: round up + } else { + secs + } + } else { + 0 + } + } else { + 0 + }; + + let disarm = duration.tv_sec == 0 && duration.tv_nsec == 0; + + if disarm { + // alarm(0): cancel any pending alarm + if let Some(ref alarm) = *guard { + let zero = itimerspec::default(); + let _ = Sys::timer_settime(alarm.0, 0, &zero, None); + } + return remaining; + } + + // Lazily create the singleton timer if it doesn't exist yet + if guard.is_none() { + let mut evp = unsafe { core::mem::zeroed::() }; + evp.sigev_notify = SIGEV_SIGNAL; + evp.sigev_signo = SIGALRM as c_int; + let mut timer_id: timer_t = core::ptr::null_mut(); + if Sys::timer_create( + crate::header::time::CLOCK_REALTIME, + &evp, + Out::from_mut(&mut timer_id), + ) + .is_err() + { + return remaining; + } + + // Enable process-wide signal delivery instead of thread-specific + #[cfg(target_os = "redox")] + { + let timer_ptr = unsafe { timer_internal_t::from_raw(timer_id) }; + let mut timer_st = timer_ptr.lock(); + timer_st.process_pid = Sys::getpid(); + drop(timer_st); + } + + *guard = Some(AlarmTimer(timer_id)); + } + + let timer_id = guard + .as_ref() + .expect("alarm timer must exist after lazy init") + .0; + + // Arm the timer as a one-shot (no interval) + let spec = itimerspec { + it_value: duration, + it_interval: timespec::default(), + }; + let _ = Sys::timer_settime(timer_id, 0, &spec, None); + + remaining +} diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index fdd1ff0dd3..6171273dfa 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -25,10 +25,11 @@ use crate::{ sys_select::timeval, sys_time, sys_utsname, termios, time::timespec, + unistd::alarm::alarm_timespec, }, out::Out, platform::{ - self, ERRNO, Pal, PalSignal, Sys, + self, ERRNO, Pal, Sys, types::{ c_char, c_int, c_long, c_short, c_uint, c_ulonglong, c_void, gid_t, off_t, pid_t, size_t, ssize_t, suseconds_t, time_t, uid_t, @@ -52,6 +53,7 @@ use super::{ stdio::snprintf, }; +mod alarm; mod brk; mod getopt; mod getpass; @@ -155,7 +157,10 @@ pub unsafe extern "C" fn faccessat( /// See . #[unsafe(no_mangle)] pub extern "C" fn alarm(seconds: c_uint) -> c_uint { - Sys::alarm(seconds) + alarm_timespec(timespec { + tv_sec: seconds.into(), + tv_nsec: 0, + }) } /// See . diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index cd316a9953..a5326029c4 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -783,7 +783,7 @@ impl Pal for Sys { syscall!( TIMER_CREATE, clock_id, - ptr::addr_of!(evp), + evp as *const _, timerid.as_mut_ptr() ) }) @@ -809,7 +809,7 @@ impl Pal for Sys { TIMER_SETTIME, timerid, flags, - ptr::addr_of!(value), + value as *const _, match ovalue { None => ptr::null_mut(), Some(mut o) => o.as_mut_ptr(), diff --git a/src/platform/linux/signal.rs b/src/platform/linux/signal.rs index 5e590c506f..584c4bff59 100644 --- a/src/platform/linux/signal.rs +++ b/src/platform/linux/signal.rs @@ -6,7 +6,7 @@ use core::{ use super::{ super::{ PalSignal, - types::{c_int, c_uint, pid_t}, + types::{c_int, pid_t}, }, Sys, e_raw, }; @@ -16,14 +16,9 @@ use crate::{ error::{Errno, Result}, header::{ bits_sigset_t::sigset_t, - signal::{ - SA_RESTORER, SI_QUEUE, SIGALRM, SIGEV_SIGNAL, sigaction, sigevent, siginfo_t, sigval, - stack_t, - }, - time::{self, timespec}, + bits_timespec::timespec, + signal::{SA_RESTORER, SI_QUEUE, sigaction, siginfo_t, sigval, stack_t}, }, - out::Out, - platform::{self, Pal, types::timer_t}, }; impl PalSignal for Sys { @@ -77,44 +72,6 @@ impl PalSignal for Sys { Ok(()) } - /// See . - fn alarm(seconds: c_uint) -> c_uint { - let mut signal_event = sigevent { - sigev_value: sigval { sival_int: 0 }, - sigev_signo: SIGALRM as c_int, - sigev_notify: SIGEV_SIGNAL, - sigev_notify_attributes: ptr::null_mut(), - sigev_notify_function: None, - }; - let mut timerid: timer_t = Default::default(); - let timer = unsafe { - time::timer_create( - time::CLOCK_REALTIME, - ptr::from_mut(&mut signal_event), - ptr::from_mut(&mut timerid), - ) - }; - let value = time::itimerspec { - it_value: timespec { - tv_sec: i64::from(seconds), - tv_nsec: 0, - }, - it_interval: timespec { - tv_sec: 0, - tv_nsec: 0, - }, - }; - let mut ovalue = Default::default(); - - let errno_backup = platform::ERRNO.get(); - if Sys::timer_settime(timerid, 0, &value, Some(Out::from_mut(&mut ovalue))).is_err() { - platform::ERRNO.set(errno_backup); - 0 - } else { - ovalue.it_value.tv_sec as c_uint + if ovalue.it_value.tv_nsec > 0 { 1 } else { 0 } - } - } - fn sigaction( sig: c_int, act: Option<&sigaction>, diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs index a3ad8df46a..d7a7d24bfd 100644 --- a/src/platform/pal/signal.rs +++ b/src/platform/pal/signal.rs @@ -1,6 +1,6 @@ use super::super::{ Pal, - types::{c_int, c_uint, pid_t}, + types::{c_int, pid_t}, }; #[allow(deprecated)] use crate::header::sys_time::itimerval; @@ -35,9 +35,6 @@ pub trait PalSignal: Pal { #[allow(deprecated)] fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()>; - /// Platform implementation of [`alarm()`](crate::header::unistd::alarm) from [`unistd.h`](crate::header::unistd). - fn alarm(seconds: c_uint) -> c_uint; - /// Platform implementation of [`sigaction()`](crate::header::signal::sigaction()) from [`signal.h`](crate::header::signal). fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> Result<()>; diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs index 597c4fa791..c33fb554e2 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -10,13 +10,11 @@ use crate::{ bits_sigset_t::sigset_t, errno::{EINVAL, ENOSYS}, signal::{ - SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SIGALRM, SIGEV_SIGNAL, - SS_DISABLE, SS_ONSTACK, sigaction, sigevent, siginfo_t, sigval, stack_t, ucontext_t, + SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SS_DISABLE, SS_ONSTACK, + sigaction, siginfo_t, sigval, stack_t, ucontext_t, }, - time::{itimerspec, timer_internal_t, timespec}, + time::timespec, }, - out::Out, - sync::Mutex, }; use core::mem::offset_of; use redox_protocols::protocol::ProcKillTarget; @@ -24,17 +22,6 @@ use redox_rt::signal::{ PosixStackt, SigStack, Sigaction, SigactionFlags, SigactionKind, Sigaltstack, SignalHandler, }; -/// Wrapper for timer_t that implements Send (the timer_t pointer is a process- -/// wide mmap'd allocation that outlives any single thread). -struct AlarmTimer(timer_t); -// SAFETY: The timer_t pointer refers to an mmap'd timer_internal_t that is -// only accessed under the ALARM_TIMER mutex lock. -unsafe impl Send for AlarmTimer {} - -/// Process-global singleton timer used by alarm(). Protected by a mutex to -/// ensure only one alarm is active at a time (POSIX requirement). -static ALARM_TIMER: Mutex> = Mutex::new(None); - const _: () = { #[track_caller] const fn assert_eq(a: usize, b: usize) { @@ -262,102 +249,4 @@ impl PalSignal for Sys { } Ok(info.si_signo) } - - /// Thread-based alarm() that Recycles the existing POSIX timer machinery - /// (timerfd + eventfd + pthread) with process-level signal delivery. - /// - /// Internally works with a timespec to allow sub-second timers in the - /// future (e.g. ualarm) as i've been asked, though the public API only exposes whole seconds. - fn alarm(seconds: c_uint) -> c_uint { - alarm_timespec(timespec { - tv_sec: seconds as time_t, - tv_nsec: 0, - }) - } -} - -/// Internal helper that arms/disarms the process-global alarm timer. -/// Accepts a full timespec so sub-second timers (ualarm) can reuse this later. -/// Returns the number of seconds remaining on the previous alarm (rounded up), -/// or 0 if there was no previous alarm. -/// -/// TODO: This implementation does not survive `exec()`. POSIX requires that a -/// pending alarm be preserved across exec (the timer continues counting down -/// in the new process image as i understand). -fn alarm_timespec(duration: timespec) -> c_uint { - let mut guard = ALARM_TIMER.lock(); - - // Determine remaining time on any existing alarm - let remaining = if let Some(ref alarm) = *guard { - let mut cur = itimerspec::default(); - if Sys::timer_gettime(alarm.0, Out::from_mut(&mut cur)).is_ok() { - let secs = cur.it_value.tv_sec as c_uint; - if cur.it_value.tv_nsec > 0 { - secs + 1 // POSIX: round up - } else { - secs - } - } else { - 0 - } - } else { - 0 - }; - - let disarm = duration.tv_sec == 0 && duration.tv_nsec == 0; - - if disarm { - // alarm(0): cancel any pending alarm - if let Some(ref alarm) = *guard { - let zero = itimerspec::default(); - let _ = Sys::timer_settime(alarm.0, 0, &zero, None); - } - return remaining; - } - - // Lazily create the singleton timer if it doesn't exist yet - if guard.is_none() { - let evp = sigevent { - sigev_value: sigval { - sival_ptr: core::ptr::null_mut(), - }, - sigev_signo: SIGALRM as c_int, - sigev_notify: SIGEV_SIGNAL, - sigev_notify_function: None, - sigev_notify_attributes: core::ptr::null_mut(), - }; - - let mut timer_id: timer_t = core::ptr::null_mut(); - if Sys::timer_create( - crate::header::time::CLOCK_REALTIME, - &evp, - Out::from_mut(&mut timer_id), - ) - .is_err() - { - return remaining; - } - - // Enable process-wide signal delivery instead of thread-specific - let timer_ptr = unsafe { timer_internal_t::from_raw(timer_id) }; - let mut timer_st = timer_ptr.lock(); - timer_st.process_pid = Sys::getpid(); - drop(timer_st); - - *guard = Some(AlarmTimer(timer_id)); - } - - let timer_id = guard - .as_ref() - .expect("alarm timer must exist after lazy init") - .0; - - // Arm the timer as a one-shot (no interval) - let spec = itimerspec { - it_value: duration, - it_interval: timespec::default(), - }; - let _ = Sys::timer_settime(timer_id, 0, &spec, None); - - remaining }