summaryrefslogtreecommitdiff
path: root/src/sys/timerfd.rs
diff options
context:
space:
mode:
authorVincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>2020-03-03 16:30:30 +0100
committerVincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>2020-07-07 14:29:11 +0200
commitf3bd6ed8116b89cbe6dfb3c942048dae09375685 (patch)
treeca502b7e7b760e241f2daab16ccb28c9a5d61d7d /src/sys/timerfd.rs
parent4b6b14a0c2fabb0cdedf415aa0eece2752adc47e (diff)
downloadnix-f3bd6ed8116b89cbe6dfb3c942048dae09375685.zip
Adding an implementation and some basic tests for timerfd.
Removed support for timerfd on Android as it seems to have been deprecated? See https://android.googlesource.com/platform/development/+/73a5a3b/ndk/platforms/android-20/include/sys/timerfd.h or https://github.com/rust-lang/libc/issues/1589 Removed the public status of `TimerSpec`, as it should not be exposed to the user. Implemented `FromRawFd` for `TimerFd` as it already implements `AsRawFd`. Addressed comments from the latest code review: - Removed upper bound assertions on timer expirations in tests. - Made the main example runnable and added code to show how to wait for the timer. - Refactored `ClockId` to use `libc_enum`. - Added comments for all public parts of the module. - Wrapped to 80 cols. - Changed the size of the buffer in the tests to the minimum required. * Ran rustfmt. * Added a `From` implementation for `libc::timespec` -> `TimeSpec`. * Reworked the example with the new changes and changed the timer from 5 to 1 second. * Added a constructor for a 0-initialized `TimerSpec`. * Added a new method to get the timer configured expiration (based on timerfd_gettime). * Added an helper method to unset the expiration of the timer. * Added a `wait` method to actually read from the timer. * Renamed `settime` into just `set`. * Refactored the tests and added a new one that tests both the `unset` and the `get` method. Modified CHANGELOG.
Diffstat (limited to 'src/sys/timerfd.rs')
-rw-r--r--src/sys/timerfd.rs272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs
new file mode 100644
index 00000000..3086309e
--- /dev/null
+++ b/src/sys/timerfd.rs
@@ -0,0 +1,272 @@
+//! Timer API via file descriptors.
+//!
+//! Timer FD is a Linux-only API to create timers and get expiration
+//! notifications through file descriptors.
+//!
+//! For more documentation, please read [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
+//!
+//! # Examples
+//!
+//! Create a new one-shot timer that expires after 1 second.
+//! ```
+//! # use std::os::unix::io::AsRawFd;
+//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags,
+//! # Expiration};
+//! # use nix::sys::time::{TimeSpec, TimeValLike};
+//! # use nix::unistd::read;
+//! #
+//! // We create a new monotonic timer.
+//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
+//! .unwrap();
+//!
+//! // We set a new one-shot timer in 1 seconds.
+//! timer.set(
+//! Expiration::OneShot(TimeSpec::seconds(1)),
+//! TimerSetTimeFlags::empty()
+//! ).unwrap();
+//!
+//! // We wait for the timer to expire.
+//! timer.wait().unwrap();
+//! ```
+use crate::sys::time::TimeSpec;
+use crate::unistd::read;
+use crate::{errno::Errno, Error, Result};
+use bitflags::bitflags;
+use libc::c_int;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+
+/// A timerfd instance. This is also a file descriptor, you can feed it to
+/// other interfaces consuming file descriptors, epoll for example.
+#[derive(Debug, Clone, Copy)]
+pub struct TimerFd {
+ fd: RawFd,
+}
+
+impl AsRawFd for TimerFd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl FromRawFd for TimerFd {
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ TimerFd { fd }
+ }
+}
+
+libc_enum! {
+ /// The type of the clock used to mark the progress of the timer. For more
+ /// details on each kind of clock, please refer to [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
+ #[repr(i32)]
+ pub enum ClockId {
+ CLOCK_REALTIME,
+ CLOCK_MONOTONIC,
+ CLOCK_BOOTTIME,
+ CLOCK_REALTIME_ALARM,
+ CLOCK_BOOTTIME_ALARM,
+ }
+}
+
+libc_bitflags! {
+ /// Additional flags to change the behaviour of the file descriptor at the
+ /// time of creation.
+ pub struct TimerFlags: c_int {
+ TFD_NONBLOCK;
+ TFD_CLOEXEC;
+ }
+}
+
+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 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<libc::itimerspec> for TimerSpec {
+ fn as_ref(&self) -> &libc::itimerspec {
+ &self.0
+ }
+}
+
+impl From<Expiration> 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<TimerSpec> 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,
+ /// NONBLOCK).
+ pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
+ Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
+ .map(|fd| Self { fd })
+ }
+
+ /// Sets 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(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
+ let timerspec: TimerSpec = expiration.into();
+ Errno::result(unsafe {
+ libc::timerfd_settime(
+ self.fd,
+ flags.bits(),
+ timerspec.as_ref(),
+ std::ptr::null_mut(),
+ )
+ })
+ .map(drop)
+ }
+
+ /// Get the parameters for the alarm currently set, if any.
+ pub fn get(&self) -> Result<Option<Expiration>> {
+ 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
+ {
+ None
+ } else {
+ Some(timerspec.into())
+ }
+ })
+ }
+
+ /// Remove the alarm if any is set.
+ pub fn unset(&self) -> Result<()> {
+ Errno::result(unsafe {
+ libc::timerfd_settime(
+ self.fd,
+ TimerSetTimeFlags::empty().bits(),
+ TimerSpec::none().as_ref(),
+ std::ptr::null_mut(),
+ )
+ })
+ .map(drop)
+ }
+
+ /// Wait for the configured alarm to expire.
+ ///
+ /// Note: If the alarm is unset, then you will wait forever.
+ pub fn wait(&self) -> Result<()> {
+ loop {
+ if let Err(e) = read(self.fd, &mut [0u8; 8]) {
+ match e {
+ Error::Sys(Errno::EINTR) => continue,
+ _ => return Err(e),
+ }
+ } else {
+ break;
+ }
+ }
+
+ Ok(())
+ }
+}