From 7f9da7d3918aa996d07e033aa2f151358ba0e7c1 Mon Sep 17 00:00:00 2001 From: "Brian L. Troutwine" Date: Mon, 27 Dec 2021 15:37:30 -0800 Subject: [PATCH] Introduce timer_* support This commit adds support for the signal timer mechanism in POSIX, the mirror to timerfd on Linux. I wasn't _quite_ sure of how to fit into the project organization but hopefully this patch isn't too far off. Resolves #1424 Signed-off-by: Brian L. Troutwine --- CHANGELOG.md | 2 + Cargo.toml | 3 +- src/sys/mod.rs | 9 ++++ src/sys/signal.rs | 5 ++ src/sys/time.rs | 125 +++++++++++++++++++++++++++++++++++++++++++-- src/sys/timer.rs | 116 +++++++++++++++++++++++++++++++++++++++++ src/sys/timerfd.rs | 109 ++++----------------------------------- 7 files changed, 263 insertions(+), 106 deletions(-) create mode 100644 src/sys/timer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c5c0887c..d1bab615d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1547](https://github.com/nix-rust/nix/pull/1547)) - Added getter methods to `MqAttr` struct (#[1619](https://github.com/nix-rust/nix/pull/1619)) +- Added `timer` support + (#[1620](https://github.com/nix-rust/nix/pull/1620)) ### Changed ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 03941aa5f1..ac4200878c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ default = [ "acct", "aio", "dir", "env", "event", "features", "fs", "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", "net", "personality", "poll", "process", "pthread", "ptrace", "quota", - "reboot", "resource", "sched", "signal", "socket", "term", "time", + "reboot", "resource", "sched", "signal", "socket", "term", "time", "timer", "ucontext", "uio", "users", "zerocopy", ] @@ -71,6 +71,7 @@ signal = ["process"] socket = [] term = [] time = [] +timer = ["signal", "time"] ucontext = ["signal"] uio = [] users = ["features"] diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 16ba9e0ab8..5df7b52717 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -201,3 +201,12 @@ feature! { #[allow(missing_docs)] pub mod timerfd; } + +#[cfg(all( + any(target_os = "linux"), + feature = "timer" +))] +feature! { + #![feature = "timer"] + pub mod timer; +} diff --git a/src/sys/signal.rs b/src/sys/signal.rs index b655604dd4..28b0766e32 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_raw_mut(&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..34c34854d6 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,9 +1,124 @@ -use std::{cmp, fmt, ops}; -use std::time::Duration; -use std::convert::From; +#[cfg_attr(target_env = "musl", allow(deprecated))] +// https://github.com/rust-lang/libc/issues/1848 +pub use libc::{suseconds_t, time_t}; 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}; +use std::convert::From; +use std::time::Duration; +use std::{cmp, fmt, ops}; + +#[cfg(all( + any(target_os = "android", target_os = "linux"), + any(feature = "timer", feature = "time") +))] +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(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly")))] + 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"))] + 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] diff --git a/src/sys/timer.rs b/src/sys/timer.rs new file mode 100644 index 0000000000..3b56be6161 --- /dev/null +++ b/src/sys/timer.rs @@ -0,0 +1,116 @@ +//! Timer API via signals. +//! +//! Timer is a POSIX API to create timers and get expiration notifications +//! through signals. +//! +//! For more documentation, please read [timer_create(3p)](https://man7.org/linux/man-pages/man3/timer_create.3p.html). +use crate::sys::signal::SigEvent; +use crate::sys::time::timer::{Expiration, TimerSetTimeFlags, TimerSpec}; +use crate::time::ClockId; +use crate::{errno::Errno, Result}; +use core::mem; + +/// The maximum value that [`Timer::overruns`] will return. +pub const DELAYTIMER_MAX: i32 = libc::_SC_DELAYTIMER_MAX as i32; + +/// A per-process timer +#[derive(Debug)] +pub struct Timer { + timer_id: 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: libc::timer_t = unsafe { mem::zeroed::() }; + Errno::result(unsafe { + libc::timer_create(clockid.as_raw(), sigevent.as_raw_mut(), &mut timer_id) + }) + .map(|_| Self { timer_id }) + } + + /// 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 disables 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 disables 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.timer_id, + 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.timer_id, 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 + /// + /// An overrun timer is one which expires while its related signal is still + /// queued. TODO explain better + pub fn overruns(&self) -> i32 { + unsafe { libc::timer_getoverrun(self.timer_id) } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::timer_delete(self.timer_id) }); + 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..a30625c922 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, @@ -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"); }