|
| 1 | +//! Timer API via file descriptors. |
| 2 | +//! |
| 3 | +//! Timer FD is a Linux-only API to create timers and get expiration |
| 4 | +//! notifications through file descriptors. |
| 5 | +//! |
| 6 | +//! For more documentation, please read [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html). |
| 7 | +//! |
| 8 | +//! # Examples |
| 9 | +//! |
| 10 | +//! Create a new one-shot timer that expires after 1 second. |
| 11 | +//! ``` |
| 12 | +//! # use std::os::unix::io::AsRawFd; |
| 13 | +//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags, |
| 14 | +//! # Expiration}; |
| 15 | +//! # use nix::sys::time::{TimeSpec, TimeValLike}; |
| 16 | +//! # use nix::unistd::read; |
| 17 | +//! # |
| 18 | +//! // We create a new monotonic timer. |
| 19 | +//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()) |
| 20 | +//! .unwrap(); |
| 21 | +//! |
| 22 | +//! // We set a new one-shot timer in 1 seconds. |
| 23 | +//! timer.set( |
| 24 | +//! Expiration::OneShot(TimeSpec::seconds(1)), |
| 25 | +//! TimerSetTimeFlags::empty() |
| 26 | +//! ).unwrap(); |
| 27 | +//! |
| 28 | +//! // We wait for the timer to expire. |
| 29 | +//! timer.wait().unwrap(); |
| 30 | +//! ``` |
| 31 | +use crate::sys::time::TimeSpec; |
| 32 | +use crate::unistd::read; |
| 33 | +use crate::{errno::Errno, Error, Result}; |
| 34 | +use bitflags::bitflags; |
| 35 | +use libc::c_int; |
| 36 | +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; |
| 37 | + |
| 38 | +/// A timerfd instance. This is also a file descriptor, you can feed it to |
| 39 | +/// other interfaces consuming file descriptors, epoll for example. |
| 40 | +#[derive(Debug, Clone, Copy)] |
| 41 | +pub struct TimerFd { |
| 42 | + fd: RawFd, |
| 43 | +} |
| 44 | + |
| 45 | +impl AsRawFd for TimerFd { |
| 46 | + fn as_raw_fd(&self) -> RawFd { |
| 47 | + self.fd |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +impl FromRawFd for TimerFd { |
| 52 | + unsafe fn from_raw_fd(fd: RawFd) -> Self { |
| 53 | + TimerFd { fd } |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +libc_enum! { |
| 58 | + /// The type of the clock used to mark the progress of the timer. For more |
| 59 | + /// details on each kind of clock, please refer to [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html). |
| 60 | + #[repr(i32)] |
| 61 | + pub enum ClockId { |
| 62 | + CLOCK_REALTIME, |
| 63 | + CLOCK_MONOTONIC, |
| 64 | + CLOCK_BOOTTIME, |
| 65 | + CLOCK_REALTIME_ALARM, |
| 66 | + CLOCK_BOOTTIME_ALARM, |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +libc_bitflags! { |
| 71 | + /// Additional flags to change the behaviour of the file descriptor at the |
| 72 | + /// time of creation. |
| 73 | + pub struct TimerFlags: c_int { |
| 74 | + TFD_NONBLOCK; |
| 75 | + TFD_CLOEXEC; |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +bitflags! { |
| 80 | + /// Flags that are used for arming the timer. |
| 81 | + pub struct TimerSetTimeFlags: libc::c_int { |
| 82 | + const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +#[derive(Debug, Clone, Copy)] |
| 87 | +struct TimerSpec(libc::itimerspec); |
| 88 | + |
| 89 | +impl TimerSpec { |
| 90 | + pub fn none() -> Self { |
| 91 | + Self(libc::itimerspec { |
| 92 | + it_interval: libc::timespec { |
| 93 | + tv_sec: 0, |
| 94 | + tv_nsec: 0, |
| 95 | + }, |
| 96 | + it_value: libc::timespec { |
| 97 | + tv_sec: 0, |
| 98 | + tv_nsec: 0, |
| 99 | + }, |
| 100 | + }) |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +impl AsRef<libc::itimerspec> for TimerSpec { |
| 105 | + fn as_ref(&self) -> &libc::itimerspec { |
| 106 | + &self.0 |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +impl From<Expiration> for TimerSpec { |
| 111 | + fn from(expiration: Expiration) -> TimerSpec { |
| 112 | + match expiration { |
| 113 | + Expiration::OneShot(t) => TimerSpec(libc::itimerspec { |
| 114 | + it_interval: libc::timespec { |
| 115 | + tv_sec: 0, |
| 116 | + tv_nsec: 0, |
| 117 | + }, |
| 118 | + it_value: *t.as_ref(), |
| 119 | + }), |
| 120 | + Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec { |
| 121 | + it_interval: *interval.as_ref(), |
| 122 | + it_value: *start.as_ref(), |
| 123 | + }), |
| 124 | + Expiration::Interval(t) => TimerSpec(libc::itimerspec { |
| 125 | + it_interval: *t.as_ref(), |
| 126 | + it_value: *t.as_ref(), |
| 127 | + }), |
| 128 | + } |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +impl From<TimerSpec> for Expiration { |
| 133 | + fn from(timerspec: TimerSpec) -> Expiration { |
| 134 | + match timerspec { |
| 135 | + TimerSpec(libc::itimerspec { |
| 136 | + it_interval: |
| 137 | + libc::timespec { |
| 138 | + tv_sec: 0, |
| 139 | + tv_nsec: 0, |
| 140 | + }, |
| 141 | + it_value: ts, |
| 142 | + }) => Expiration::OneShot(ts.into()), |
| 143 | + TimerSpec(libc::itimerspec { |
| 144 | + it_interval: int_ts, |
| 145 | + it_value: val_ts, |
| 146 | + }) => { |
| 147 | + if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) { |
| 148 | + Expiration::Interval(int_ts.into()) |
| 149 | + } else { |
| 150 | + Expiration::IntervalDelayed(val_ts.into(), int_ts.into()) |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +/// An enumeration allowing the definition of the expiration time of an alarm, |
| 158 | +/// recurring or not. |
| 159 | +#[derive(Debug, Clone, Copy, PartialEq)] |
| 160 | +pub enum Expiration { |
| 161 | + OneShot(TimeSpec), |
| 162 | + IntervalDelayed(TimeSpec, TimeSpec), |
| 163 | + Interval(TimeSpec), |
| 164 | +} |
| 165 | + |
| 166 | +impl TimerFd { |
| 167 | + /// Creates a new timer based on the clock defined by `clockid`. The |
| 168 | + /// underlying fd can be assigned specific flags with `flags` (CLOEXEC, |
| 169 | + /// NONBLOCK). |
| 170 | + pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> { |
| 171 | + Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) }) |
| 172 | + .map(|fd| Self { fd }) |
| 173 | + } |
| 174 | + |
| 175 | + /// Sets a new alarm on the timer. |
| 176 | + /// |
| 177 | + /// # Types of alarm |
| 178 | + /// |
| 179 | + /// There are 3 types of alarms you can set: |
| 180 | + /// |
| 181 | + /// - one shot: the alarm will trigger once after the specified amount of |
| 182 | + /// time. |
| 183 | + /// Example: I want an alarm to go off in 60s and then disables itself. |
| 184 | + /// |
| 185 | + /// - interval: the alarm will trigger every specified interval of time. |
| 186 | + /// Example: I want an alarm to go off every 60s. The alarm will first |
| 187 | + /// go off 60s after I set it and every 60s after that. The alarm will |
| 188 | + /// not disable itself. |
| 189 | + /// |
| 190 | + /// - interval delayed: the alarm will trigger after a certain amount of |
| 191 | + /// time and then trigger at a specified interval. |
| 192 | + /// Example: I want an alarm to go off every 60s but only start in 1h. |
| 193 | + /// The alarm will first trigger 1h after I set it and then every 60s |
| 194 | + /// after that. The alarm will not disable itself. |
| 195 | + /// |
| 196 | + /// # Relative vs absolute alarm |
| 197 | + /// |
| 198 | + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass |
| 199 | + /// to the `Expiration` you want is relative. If however you want an alarm |
| 200 | + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. |
| 201 | + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed |
| 202 | + /// interval are going to be interpreted as absolute. |
| 203 | + /// |
| 204 | + /// # Disabling alarms |
| 205 | + /// |
| 206 | + /// Note: Only one alarm can be set for any given timer. Setting a new alarm |
| 207 | + /// actually removes the previous one. |
| 208 | + /// |
| 209 | + /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm |
| 210 | + /// altogether. |
| 211 | + pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> { |
| 212 | + let timerspec: TimerSpec = expiration.into(); |
| 213 | + Errno::result(unsafe { |
| 214 | + libc::timerfd_settime( |
| 215 | + self.fd, |
| 216 | + flags.bits(), |
| 217 | + timerspec.as_ref(), |
| 218 | + std::ptr::null_mut(), |
| 219 | + ) |
| 220 | + }) |
| 221 | + .map(drop) |
| 222 | + } |
| 223 | + |
| 224 | + /// Get the parameters for the alarm currently set, if any. |
| 225 | + pub fn get(&self) -> Result<Option<Expiration>> { |
| 226 | + let mut timerspec = TimerSpec::none(); |
| 227 | + let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0; |
| 228 | + |
| 229 | + Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| { |
| 230 | + if timerspec.0.it_interval.tv_sec == 0 |
| 231 | + && timerspec.0.it_interval.tv_nsec == 0 |
| 232 | + && timerspec.0.it_value.tv_sec == 0 |
| 233 | + && timerspec.0.it_value.tv_nsec == 0 |
| 234 | + { |
| 235 | + None |
| 236 | + } else { |
| 237 | + Some(timerspec.into()) |
| 238 | + } |
| 239 | + }) |
| 240 | + } |
| 241 | + |
| 242 | + /// Remove the alarm if any is set. |
| 243 | + pub fn unset(&self) -> Result<()> { |
| 244 | + Errno::result(unsafe { |
| 245 | + libc::timerfd_settime( |
| 246 | + self.fd, |
| 247 | + TimerSetTimeFlags::empty().bits(), |
| 248 | + TimerSpec::none().as_ref(), |
| 249 | + std::ptr::null_mut(), |
| 250 | + ) |
| 251 | + }) |
| 252 | + .map(drop) |
| 253 | + } |
| 254 | + |
| 255 | + /// Wait for the configured alarm to expire. |
| 256 | + /// |
| 257 | + /// Note: If the alarm is unset, then you will wait forever. |
| 258 | + pub fn wait(&self) -> Result<()> { |
| 259 | + loop { |
| 260 | + if let Err(e) = read(self.fd, &mut [0u8; 8]) { |
| 261 | + match e { |
| 262 | + Error::Sys(Errno::EINTR) => continue, |
| 263 | + _ => return Err(e), |
| 264 | + } |
| 265 | + } else { |
| 266 | + break; |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + Ok(()) |
| 271 | + } |
| 272 | +} |
0 commit comments