diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c4516ae0a..3b097d6a11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1619](https://github.com/nix-rust/nix/pull/1619)) - Added the `TxTime` sockopt and control message. (#[1564](https://github.com/nix-rust/nix/pull/1564)) +- Added POSIX per-process timer support + (#[1622](https://github.com/nix-rust/nix/pull/1622)) ### Changed ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 03941aa5f1..6550db11c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,9 +76,6 @@ uio = [] users = ["features"] zerocopy = ["fs", "uio"] -[target.'cfg(target_os = "dragonfly")'.build-dependencies] -cc = "1" - [dev-dependencies] assert-impl = "0.1" lazy_static = "1.2" diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 16ba9e0ab8..654a4d87bf 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -201,3 +201,18 @@ feature! { #[allow(missing_docs)] pub mod timerfd; } + +#[cfg(all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +feature! { + #![feature = "time"] + pub mod timer; +} diff --git a/src/sys/signal.rs b/src/sys/signal.rs index b655604dd4..9750d89080 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -1085,6 +1085,11 @@ mod sigevent { pub fn sigevent(&self) -> libc::sigevent { self.sigevent } + + /// Returns a mutable pointer to the `sigevent` wrapped by `self` + pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent { + &mut self.sigevent + } } impl<'a> From<&'a libc::sigevent> for SigEvent { diff --git a/src/sys/time.rs b/src/sys/time.rs index ac4247180d..1e62b76a90 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -5,6 +5,129 @@ use libc::{timespec, timeval}; #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 pub use libc::{time_t, suseconds_t}; +#[cfg(any( + all(feature = "time", any(target_os = "android", target_os = "linux")), + all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" + ) +))] +pub(crate) mod timer { + use crate::sys::time::TimeSpec; + use bitflags::bitflags; + + #[derive(Debug, Clone, Copy)] + pub(crate) struct TimerSpec(libc::itimerspec); + + impl TimerSpec { + pub const fn none() -> Self { + Self(libc::itimerspec { + it_interval: libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + it_value: libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + }) + } + } + + impl AsMut for TimerSpec { + fn as_mut(&mut self) -> &mut libc::itimerspec { + &mut self.0 + } + } + + impl AsRef for TimerSpec { + fn as_ref(&self) -> &libc::itimerspec { + &self.0 + } + } + + impl From for TimerSpec { + fn from(expiration: Expiration) -> TimerSpec { + match expiration { + Expiration::OneShot(t) => TimerSpec(libc::itimerspec { + it_interval: libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + it_value: *t.as_ref(), + }), + Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec { + it_interval: *interval.as_ref(), + it_value: *start.as_ref(), + }), + Expiration::Interval(t) => TimerSpec(libc::itimerspec { + it_interval: *t.as_ref(), + it_value: *t.as_ref(), + }), + } + } + } + + /// An enumeration allowing the definition of the expiration time of an alarm, + /// recurring or not. + #[derive(Debug, Clone, Copy, PartialEq)] + pub enum Expiration { + /// Alarm will trigger once after the time given in `TimeSpec` + OneShot(TimeSpec), + /// Alarm will trigger after a specified delay and then every interval of + /// time. + IntervalDelayed(TimeSpec, TimeSpec), + /// Alarm will trigger every specified interval of time. + Interval(TimeSpec), + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; + } + } + #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly", target_os = "illumos"))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME; + } + } + + impl From for Expiration { + fn from(timerspec: TimerSpec) -> Expiration { + match timerspec { + TimerSpec(libc::itimerspec { + it_interval: + libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }, + it_value: ts, + }) => Expiration::OneShot(ts.into()), + TimerSpec(libc::itimerspec { + it_interval: int_ts, + it_value: val_ts, + }) => { + if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) { + Expiration::Interval(int_ts.into()) + } else { + Expiration::IntervalDelayed(val_ts.into(), int_ts.into()) + } + } + } + } + } +} + pub trait TimeValLike: Sized { #[inline] fn zero() -> Self { diff --git a/src/sys/timer.rs b/src/sys/timer.rs new file mode 100644 index 0000000000..349346bb10 --- /dev/null +++ b/src/sys/timer.rs @@ -0,0 +1,175 @@ +//! Timer API via signals. +//! +//! Timer is a POSIX API to create timers and get expiration notifications +//! through queued Unix signals, for the current process. This is similar to +//! Linux's timerfd mechanism, except that API is specific to Linux and makes +//! use of file polling. +//! +//! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html). +//! +//! # Examples +//! +//! Create an interval timer that signals SIGALARM every 250 milliseconds. +//! +//! ```no_run +//! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal}; +//! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; +//! use nix::time::ClockId; +//! use std::convert::TryFrom; +//! use std::sync::atomic::{AtomicU64, Ordering}; +//! use std::thread::yield_now; +//! use std::time::Duration; +//! +//! const SIG: Signal = Signal::SIGALRM; +//! static ALARMS: AtomicU64 = AtomicU64::new(0); +//! +//! extern "C" fn handle_alarm(signal: libc::c_int) { +//! let signal = Signal::try_from(signal).unwrap(); +//! if signal == SIG { +//! ALARMS.fetch_add(1, Ordering::Relaxed); +//! } +//! } +//! +//! fn main() { +//! let clockid = ClockId::CLOCK_MONOTONIC; +//! let sigevent = SigEvent::new(SigevNotify::SigevSignal { +//! signal: SIG, +//! si_value: 0, +//! }); +//! +//! let mut timer = Timer::new(clockid, sigevent).unwrap(); +//! let expiration = Expiration::Interval(Duration::from_millis(250).into()); +//! let flags = TimerSetTimeFlags::empty(); +//! timer.set(expiration, flags).expect("could not set timer"); +//! +//! let handler = SigHandler::Handler(handle_alarm); +//! unsafe { signal::signal(SIG, handler) }.unwrap(); +//! +//! loop { +//! let alarms = ALARMS.load(Ordering::Relaxed); +//! if alarms >= 10 { +//! println!("total alarms handled: {}", alarms); +//! break; +//! } +//! yield_now() +//! } +//! } +//! ``` +use crate::sys::signal::SigEvent; +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; +use crate::time::ClockId; +use crate::{errno::Errno, Result}; +use core::mem; + +/// A Unix signal per-process timer. +#[derive(Debug)] +#[repr(transparent)] +pub struct Timer(libc::timer_t); + +impl Timer { + /// Creates a new timer based on the clock defined by `clockid`. The details + /// of the signal and its handler are defined by the passed `sigevent`. + pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result { + let mut timer_id: mem::MaybeUninit = mem::MaybeUninit::uninit(); + Errno::result(unsafe { + libc::timer_create( + clockid.as_raw(), + sigevent.as_mut_ptr(), + timer_id.as_mut_ptr(), + ) + }) + .map(|_| { + // SAFETY: libc::timer_create is responsible for initializing + // timer_id. + unsafe { Self(timer_id.assume_init()) } + }) + } + + /// Set a new alarm on the timer. + /// + /// # Types of alarm + /// + /// There are 3 types of alarms you can set: + /// + /// - one shot: the alarm will trigger once after the specified amount of + /// time. + /// Example: I want an alarm to go off in 60s and then disable itself. + /// + /// - interval: the alarm will trigger every specified interval of time. + /// Example: I want an alarm to go off every 60s. The alarm will first + /// go off 60s after I set it and every 60s after that. The alarm will + /// not disable itself. + /// + /// - interval delayed: the alarm will trigger after a certain amount of + /// time and then trigger at a specified interval. + /// Example: I want an alarm to go off every 60s but only start in 1h. + /// The alarm will first trigger 1h after I set it and then every 60s + /// after that. The alarm will not disable itself. + /// + /// # Relative vs absolute alarm + /// + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass + /// to the `Expiration` you want is relative. If however you want an alarm + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed + /// interval are going to be interpreted as absolute. + /// + /// # Disabling alarms + /// + /// Note: Only one alarm can be set for any given timer. Setting a new alarm + /// actually removes the previous one. + /// + /// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm + /// altogether. + pub fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> { + let timerspec: TimerSpec = expiration.into(); + Errno::result(unsafe { + libc::timer_settime( + self.0, + flags.bits(), + timerspec.as_ref(), + core::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Get the parameters for the alarm currently set, if any. + pub fn get(&self) -> Result> { + let mut timerspec = TimerSpec::none(); + Errno::result(unsafe { libc::timer_gettime(self.0, timerspec.as_mut()) }).map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 + { + None + } else { + Some(timerspec.into()) + } + }) + } + + /// Return the number of timers that have overrun + /// + /// Each timer is able to queue one signal to the process at a time, meaning + /// if the signal is not handled before the next expiration the timer has + /// 'overrun'. This function returns how many times that has happened to + /// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum + /// number of overruns have happened the return is capped to the maximum. + pub fn overruns(&self) -> i32 { + unsafe { libc::timer_getoverrun(self.0) } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::timer_delete(self.0) }); + if let Err(Errno::EINVAL) = result { + panic!("close of Timer encountered EINVAL"); + } + } + } +} diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs index 705a3c4d65..bc5a75d4ce 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -28,10 +28,10 @@ //! // We wait for the timer to expire. //! timer.wait().unwrap(); //! ``` -use crate::sys::time::TimeSpec; +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; use crate::unistd::read; use crate::{errno::Errno, Result}; -use bitflags::bitflags; use libc::c_int; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; @@ -77,93 +77,6 @@ libc_bitflags! { } } -bitflags! { - /// Flags that are used for arming the timer. - pub struct TimerSetTimeFlags: libc::c_int { - const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; - } -} - -#[derive(Debug, Clone, Copy)] -struct TimerSpec(libc::itimerspec); - -impl TimerSpec { - pub const fn none() -> Self { - Self(libc::itimerspec { - it_interval: libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - it_value: libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - }) - } -} - -impl AsRef for TimerSpec { - fn as_ref(&self) -> &libc::itimerspec { - &self.0 - } -} - -impl From for TimerSpec { - fn from(expiration: Expiration) -> TimerSpec { - match expiration { - Expiration::OneShot(t) => TimerSpec(libc::itimerspec { - it_interval: libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - it_value: *t.as_ref(), - }), - Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec { - it_interval: *interval.as_ref(), - it_value: *start.as_ref(), - }), - Expiration::Interval(t) => TimerSpec(libc::itimerspec { - it_interval: *t.as_ref(), - it_value: *t.as_ref(), - }), - } - } -} - -impl From for Expiration { - fn from(timerspec: TimerSpec) -> Expiration { - match timerspec { - TimerSpec(libc::itimerspec { - it_interval: - libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - it_value: ts, - }) => Expiration::OneShot(ts.into()), - TimerSpec(libc::itimerspec { - it_interval: int_ts, - it_value: val_ts, - }) => { - if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) { - Expiration::Interval(int_ts.into()) - } else { - Expiration::IntervalDelayed(val_ts.into(), int_ts.into()) - } - } - } - } -} - -/// An enumeration allowing the definition of the expiration time of an alarm, -/// recurring or not. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Expiration { - OneShot(TimeSpec), - IntervalDelayed(TimeSpec, TimeSpec), - Interval(TimeSpec), -} - impl TimerFd { /// Creates a new timer based on the clock defined by `clockid`. The /// underlying fd can be assigned specific flags with `flags` (CLOEXEC, @@ -181,7 +94,7 @@ impl TimerFd { /// /// - one shot: the alarm will trigger once after the specified amount of /// time. - /// Example: I want an alarm to go off in 60s and then disables itself. + /// Example: I want an alarm to go off in 60s and then disable itself. /// /// - interval: the alarm will trigger every specified interval of time. /// Example: I want an alarm to go off every 60s. The alarm will first @@ -225,13 +138,11 @@ impl TimerFd { /// Get the parameters for the alarm currently set, if any. pub fn get(&self) -> Result> { let mut timerspec = TimerSpec::none(); - let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0; - - Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| { - if timerspec.0.it_interval.tv_sec == 0 - && timerspec.0.it_interval.tv_nsec == 0 - && timerspec.0.it_value.tv_sec == 0 - && timerspec.0.it_value.tv_nsec == 0 + Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec.as_mut()) }).map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 { None } else { @@ -259,7 +170,7 @@ impl TimerFd { pub fn wait(&self) -> Result<()> { while let Err(e) = read(self.fd, &mut [0u8; 8]) { if e != Errno::EINTR { - return Err(e) + return Err(e); } } @@ -270,9 +181,7 @@ impl TimerFd { impl Drop for TimerFd { fn drop(&mut self) { if !std::thread::panicking() { - let result = Errno::result(unsafe { - libc::close(self.fd) - }); + let result = Errno::result(unsafe { libc::close(self.fd) }); if let Err(Errno::EBADF) = result { panic!("close of TimerFd encountered EBADF"); } diff --git a/test/test.rs b/test/test.rs index 922caa5f63..83cb46450e 100644 --- a/test/test.rs +++ b/test/test.rs @@ -41,6 +41,17 @@ mod test_sendfile; mod test_stat; mod test_time; mod test_unistd; +#[cfg(all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +mod test_timer; use std::os::unix::io::RawFd; use std::path::PathBuf; diff --git a/test/test_timer.rs b/test/test_timer.rs new file mode 100644 index 0000000000..31a740b0d6 --- /dev/null +++ b/test/test_timer.rs @@ -0,0 +1,91 @@ +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify, Signal, +}; +use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; +use nix::time::ClockId; +use std::convert::TryFrom; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::{Duration, Instant}; + +const SIG: Signal = Signal::SIGALRM; +static ALARM_CALLED: AtomicBool = AtomicBool::new(false); + +pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) { + let signal = Signal::try_from(raw_signal).unwrap(); + if signal == SIG { + ALARM_CALLED.store(true, Ordering::Release); + } +} + +#[test] +fn alarm_fires() { + // Avoid interfering with other signal using tests by taking a mutex shared + // among other tests in this crate. + let _m = crate::SIGNAL_MTX.lock(); + + // + // Setup + // + + // Create a handler for the test signal, `SIG`. The handler is responsible + // for flipping `ALARM_CALLED`. + let handler = SigHandler::Handler(handle_sigalarm); + let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); + let old_handler = + unsafe { sigaction(SIG, &signal_action).expect("unable to set signal handler for alarm") }; + + // Create the timer. We use the monotonic clock here, though any would do + // really. The timer is set to fire every 250 milliseconds with no delay for + // the initial firing. + let clockid = ClockId::CLOCK_MONOTONIC; + let sigevent = SigEvent::new(SigevNotify::SigevSignal { + signal: SIG, + si_value: 0, + }); + let mut timer = Timer::new(clockid, sigevent).expect("failed to create timer"); + let expiration = Expiration::Interval(Duration::from_millis(250).into()); + let flags = TimerSetTimeFlags::empty(); + timer.set(expiration, flags).expect("could not set timer"); + + // + // Test + // + + // Determine that there's still an expiration tracked by the + // timer. Depending on when this runs either an `Expiration::Interval` or + // `Expiration::IntervalDelayed` will be present. That is, if the timer has + // not fired yet we'll get our original `expiration`, else the one that + // represents a delay to the next expiration. We're only interested in the + // timer still being extant. + match timer.get() { + Ok(Some(exp)) => { + assert!(matches!( + exp, + Expiration::Interval(..) | Expiration::IntervalDelayed(..) + )) + } + _ => panic!("timer lost its expiration"), + } + + // Wait for 2 firings of the alarm before checking that it has fired and + // been handled at least the once. If we wait for 3 seconds and the handler + // is never called something has gone sideways and the test fails. + let starttime = Instant::now(); + loop { + thread::sleep(Duration::from_millis(500)); + if ALARM_CALLED.load(Ordering::Acquire) { + break; + } + if starttime.elapsed() > Duration::from_secs(3) { + panic!("Timeout waiting for SIGALRM"); + } + } + + // Replace the old signal handler now that we've completed the test. If the + // test fails this process panics, so the fact we might not get here is + // okay. + unsafe { + sigaction(SIG, &old_handler).expect("unable to reset signal handler"); + } +}