From e0c9b0ed035eaae35ab15a0bd6eb00f893fec5d6 Mon Sep 17 00:00:00 2001 From: Zero <188656344+k5602@users.noreply.github.com> Date: Tue, 24 Mar 2026 01:39:00 +0200 Subject: [PATCH 1/4] feat(signal): Implement alarm() and timer_settime logic Adds the implementation for the `alarm()` function and its underlying `alarm_timespec` helper. This involves managing a process-global POSIX timer to deliver SIGALRM signals. --- src/platform/redox/mod.rs | 1 + src/platform/redox/signal.rs | 111 ++++++++++++++++++++++++++++++++++- src/platform/redox/timer.rs | 13 ++-- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index bb963d07b4..44414fa512 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -1298,6 +1298,7 @@ impl Pal for Sys { timer_st.next_wake_time = itimerspec::default(); timer_st.thread = ptr::null_mut(); timer_st.caller_thread = caller_thread; + timer_st.process_pid = 0; timer_buf }; diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs index ed21900a8c..dd47fa6c80 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -8,11 +8,15 @@ use crate::{ bits_time::timespec, errno::{EINVAL, ENOSYS}, signal::{ - SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SS_DISABLE, SS_ONSTACK, - sigaction, siginfo_t, sigset_t, sigval, stack_t, ucontext_t, + SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SIGALRM, SIGEV_SIGNAL, + SS_DISABLE, SS_ONSTACK, sigaction, sigevent, siginfo_t, sigset_t, sigval, stack_t, + ucontext_t, }, sys_time::{ITIMER_REAL, itimerval}, + time::{itimerspec, timer_internal_t}, }, + out::Out, + sync::Mutex, }; use core::mem::offset_of; use redox_protocols::protocol::ProcKillTarget; @@ -20,6 +24,17 @@ 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) { @@ -245,4 +260,96 @@ 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. +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_st = unsafe { &mut *(timer_id as *mut timer_internal_t) }; + timer_st.process_pid = Sys::getpid(); + + *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/platform/redox/timer.rs b/src/platform/redox/timer.rs index 8a46f8a550..f97a9cab29 100644 --- a/src/platform/redox/timer.rs +++ b/src/platform/redox/timer.rs @@ -9,7 +9,7 @@ use crate::{ time::timer_internal_t, }, out::Out, - platform::{Pal, Sys, sys::event, types::c_void}, + platform::{Pal, PalSignal, Sys, sys::event, types::c_void}, }; use core::{ mem::{MaybeUninit, size_of}, @@ -42,9 +42,14 @@ pub extern "C" fn timer_routine(arg: *mut c_void) -> *mut c_void { fun(timer_st.evp.sigev_value); } } else if timer_st.evp.sigev_notify == SIGEV_SIGNAL { - if unsafe { Sys::rlct_kill(timer_st.caller_thread, timer_st.evp.sigev_signo as _) } - .is_err() - { + // process_pid != 0 => process-wide delivery (used by alarm()) + // process_pid == 0 => thread-specific delivery (used by timer_create) + let result = if timer_st.process_pid != 0 { + Sys::kill(timer_st.process_pid, timer_st.evp.sigev_signo) + } else { + unsafe { Sys::rlct_kill(timer_st.caller_thread, timer_st.evp.sigev_signo as _) } + }; + if result.is_err() { break; } } From 5cbb4ba0cc97fd64fa7c8e504e774c1620cd43b7 Mon Sep 17 00:00:00 2001 From: Zero Date: Tue, 24 Mar 2026 01:40:20 +0200 Subject: [PATCH 2/4] test(unistd): Add alarm(2) test Introduce a new test for the alarm(2) system call. This test verifies the behavior of setting, cancelling, and re-arming alarms, ensuring correct signal delivery and return values. --- .../expected/bins_dynamic/unistd/alarm.stderr | 0 .../expected/bins_dynamic/unistd/alarm.stdout | 4 + .../expected/bins_static/unistd/alarm.stderr | 0 .../expected/bins_static/unistd/alarm.stdout | 4 + tests/unistd/alarm.c | 80 +++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 tests/expected/bins_dynamic/unistd/alarm.stderr create mode 100644 tests/expected/bins_dynamic/unistd/alarm.stdout create mode 100644 tests/expected/bins_static/unistd/alarm.stderr create mode 100644 tests/expected/bins_static/unistd/alarm.stdout create mode 100644 tests/unistd/alarm.c diff --git a/tests/expected/bins_dynamic/unistd/alarm.stderr b/tests/expected/bins_dynamic/unistd/alarm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/alarm.stdout b/tests/expected/bins_dynamic/unistd/alarm.stdout new file mode 100644 index 0000000000..4ed7f83631 --- /dev/null +++ b/tests/expected/bins_dynamic/unistd/alarm.stdout @@ -0,0 +1,4 @@ +alarm(0) baseline: ok +alarm(1) fires: ok +alarm(0) cancel: ok +alarm re-arm returns remaining: ok diff --git a/tests/expected/bins_static/unistd/alarm.stderr b/tests/expected/bins_static/unistd/alarm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/alarm.stdout b/tests/expected/bins_static/unistd/alarm.stdout new file mode 100644 index 0000000000..4ed7f83631 --- /dev/null +++ b/tests/expected/bins_static/unistd/alarm.stdout @@ -0,0 +1,4 @@ +alarm(0) baseline: ok +alarm(1) fires: ok +alarm(0) cancel: ok +alarm re-arm returns remaining: ok diff --git a/tests/unistd/alarm.c b/tests/unistd/alarm.c new file mode 100644 index 0000000000..4b0b2f96d6 --- /dev/null +++ b/tests/unistd/alarm.c @@ -0,0 +1,80 @@ +/* + * Tests for alarm(2) - POSIX: https://pubs.opengroup.org/onlinepubs/9799919799/functions/alarm.html + * + * Verifies: + * 1. alarm(0) with no pending alarm returns 0 + * 2. alarm(1) delivers SIGALRM after ~1 second (tested via pause()) + * 3. alarm(0) cancels a pending alarm + * 4. re-arming alarm returns the remaining seconds from the previous alarm + */ + +#include +#include +#include +#include + +#include "test_helpers.h" + +static volatile sig_atomic_t alarm_count = 0; + +static void handler(int sig) { + (void)sig; + alarm_count++; +} + +int main(void) { + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + int r = sigaction(SIGALRM, &sa, NULL); + ERROR_IF(sigaction, r, == -1); + + /* alarm(0) with no existing alarm must return 0 */ + unsigned prev = alarm(0); + UNEXP_IF(alarm, (int)prev, != 0); + puts("alarm(0) baseline: ok"); + + /* alarm(1): SIGALRM must fire; pause() blocks until the signal arrives */ + alarm_count = 0; + alarm(1); + pause(); + if (alarm_count != 1) { + fprintf(stderr, "SIGALRM did not fire (count=%d)\n", (int)alarm_count); + return EXIT_FAILURE; + } + puts("alarm(1) fires: ok"); + + /* alarm(0) must cancel a pending alarm and return remaining secs > 0 */ + alarm_count = 0; + alarm(10); + unsigned remaining = alarm(0); + if (remaining == 0) { + fprintf(stderr, "alarm(0) cancel: expected remaining > 0\n"); + return EXIT_FAILURE; + } + /* Wait longer than original alarm; SIGALRM must NOT fire */ + sleep(2); + if (alarm_count != 0) { + fprintf(stderr, "SIGALRM fired after alarm(0) cancel\n"); + return EXIT_FAILURE; + } + puts("alarm(0) cancel: ok"); + + /* + * Re-arming: arm with 10s, sleep 1s, then re-arm with 3s. + * The return value must be the remaining time (~9s) which is > 0. + */ + alarm_count = 0; + alarm(10); + sleep(1); + remaining = alarm(3); + if (remaining == 0) { + fprintf(stderr, "re-arm: expected remaining > 0, got 0\n"); + return EXIT_FAILURE; + } + alarm(0); /* disarm so SIGALRM doesn't fire after the test */ + puts("alarm re-arm returns remaining: ok"); + + return EXIT_SUCCESS; +} From aca4df91cf7527b241b60929d38b11e2b71e3741 Mon Sep 17 00:00:00 2001 From: Zero Date: Tue, 24 Mar 2026 01:40:58 +0200 Subject: [PATCH 3/4] feat(time): Add process_pid to timer_internal_t Add `process_pid` field to `timer_internal_t` struct. This field is used by the `alarm()` function to specify the PID of the process to which the `SIGALRM` signal should be delivered. --- src/header/time/mod.rs | 3 +++ src/header/unistd/mod.rs | 25 ++----------------------- src/platform/pal/signal.rs | 29 ++++++++++++++++++++++++++++- tests/Makefile.tests.mk | 1 + 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs index fd0ba15035..b534dc5274 100644 --- a/src/header/time/mod.rs +++ b/src/header/time/mod.rs @@ -66,6 +66,9 @@ pub(crate) struct timer_internal_t { pub caller_thread: crate::pthread::OsTid, // relibc handles it_interval, not the kernel pub next_wake_time: itimerspec, + // When non-zero, timer_routine delivers SIGALRM via kill(process_pid, sig) + // instead of rlct_kill (thread-specific). Used by alarm(). + pub process_pid: platform::types::pid_t, } /// See . diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index aa31284d81..c4d5f17b00 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -24,7 +24,7 @@ use crate::{ }, out::Out, platform::{ - self, ERRNO, Pal, Sys, + self, ERRNO, Pal, PalSignal, 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, useconds_t, @@ -137,28 +137,7 @@ pub unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> c_int { /// See . #[unsafe(no_mangle)] pub extern "C" fn alarm(seconds: c_uint) -> c_uint { - // TODO setitimer is unimplemented on Redox and obsolete - let timer = sys_time::itimerval { - it_value: timeval { - tv_sec: time_t::from(seconds), - tv_usec: 0, - }, - ..Default::default() - }; - let mut otimer = sys_time::itimerval::default(); - - let errno_backup = platform::ERRNO.get(); - let secs = - if unsafe { sys_time::setitimer(sys_time::ITIMER_REAL, &raw const timer, &raw mut otimer) } - < 0 - { - 0 - } else { - otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 } - }; - platform::ERRNO.set(errno_backup); - - secs + Sys::alarm(seconds) } /// See . diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs index 52670daa6f..b8ed26acea 100644 --- a/src/platform/pal/signal.rs +++ b/src/platform/pal/signal.rs @@ -4,8 +4,9 @@ use crate::{ header::{ bits_time::timespec, signal::{sigaction, siginfo_t, sigset_t, sigval, stack_t}, - sys_time::itimerval, + sys_time::{self, itimerval}, }, + platform, }; pub trait PalSignal: Pal { @@ -21,6 +22,32 @@ pub trait PalSignal: Pal { fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()>; + /// See . + /// + /// Default implementation uses setitimer(ITIMER_REAL). Platforms that do not + /// support setitimer (e.g. Redox) must override this. + fn alarm(seconds: c_uint) -> c_uint { + use crate::header::sys_select::timeval; + + let timer = itimerval { + it_value: timeval { + tv_sec: time_t::from(seconds), + tv_usec: 0, + }, + ..Default::default() + }; + let mut otimer = itimerval::default(); + + let errno_backup = platform::ERRNO.get(); + let secs = if Self::setitimer(sys_time::ITIMER_REAL, &timer, Some(&mut otimer)).is_err() { + 0 + } else { + otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 } + }; + platform::ERRNO.set(errno_backup); + secs + } + fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> Result<()>; unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<()>; diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk index 1f591dabc2..7ace459d3a 100644 --- a/tests/Makefile.tests.mk +++ b/tests/Makefile.tests.mk @@ -159,6 +159,7 @@ EXPECT_NAMES=\ time/timegm \ time/tzset \ unistd/access \ + unistd/alarm \ unistd/constants \ unistd/confstr \ unistd/dup \ From caa5ef8afbb38bbd5db0a23116262f90f6c08f07 Mon Sep 17 00:00:00 2001 From: Zero Date: Wed, 25 Mar 2026 07:40:42 +0200 Subject: [PATCH 4/4] chore(signal): Move alarm function and adding the todo --- src/platform/linux/signal.rs | 26 ++++++++++++++++++++++++-- src/platform/pal/signal.rs | 29 ++--------------------------- src/platform/redox/signal.rs | 4 ++++ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/platform/linux/signal.rs b/src/platform/linux/signal.rs index da2119d47b..3459ef62ee 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, pid_t}, + types::{c_int, c_uint, pid_t, time_t}, }, Sys, e_raw, }; @@ -15,8 +15,10 @@ use crate::{ header::{ bits_time::timespec, signal::{SA_RESTORER, SI_QUEUE, sigaction, siginfo_t, sigset_t, sigval, stack_t}, - sys_time::itimerval, + sys_select::timeval, + sys_time::{self, itimerval}, }, + platform, }; impl PalSignal for Sys { @@ -68,6 +70,26 @@ impl PalSignal for Sys { Ok(()) } + /// See . + fn alarm(seconds: c_uint) -> c_uint { + let timer = itimerval { + it_value: timeval { + tv_sec: time_t::from(seconds), + tv_usec: 0, + }, + ..Default::default() + }; + let mut otimer = itimerval::default(); + let errno_backup = platform::ERRNO.get(); + let secs = if Self::setitimer(sys_time::ITIMER_REAL, &timer, Some(&mut otimer)).is_err() { + 0 + } else { + otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 } + }; + platform::ERRNO.set(errno_backup); + secs + } + fn sigaction( sig: c_int, act: Option<&sigaction>, diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs index b8ed26acea..e703358d72 100644 --- a/src/platform/pal/signal.rs +++ b/src/platform/pal/signal.rs @@ -4,9 +4,8 @@ use crate::{ header::{ bits_time::timespec, signal::{sigaction, siginfo_t, sigset_t, sigval, stack_t}, - sys_time::{self, itimerval}, + sys_time::itimerval, }, - platform, }; pub trait PalSignal: Pal { @@ -22,31 +21,7 @@ pub trait PalSignal: Pal { fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()>; - /// See . - /// - /// Default implementation uses setitimer(ITIMER_REAL). Platforms that do not - /// support setitimer (e.g. Redox) must override this. - fn alarm(seconds: c_uint) -> c_uint { - use crate::header::sys_select::timeval; - - let timer = itimerval { - it_value: timeval { - tv_sec: time_t::from(seconds), - tv_usec: 0, - }, - ..Default::default() - }; - let mut otimer = itimerval::default(); - - let errno_backup = platform::ERRNO.get(); - let secs = if Self::setitimer(sys_time::ITIMER_REAL, &timer, Some(&mut otimer)).is_err() { - 0 - } else { - otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 } - }; - platform::ERRNO.set(errno_backup); - secs - } + fn alarm(seconds: c_uint) -> c_uint; 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 dd47fa6c80..c7a36819cb 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -278,6 +278,10 @@ impl PalSignal for Sys { /// 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();