#![warn(missing_debug_implementations)]
extern crate rustix;
use std::os::unix::prelude::*;
use std::time::Duration;
use std::io::Result as IoResult;
use std::fmt;
use rustix::fd::{AsFd, BorrowedFd, OwnedFd};
use rustix::time::{Itimerspec, TimerfdClockId};
#[derive(Clone, PartialEq, Eq)]
pub enum ClockId {
Realtime = TimerfdClockId::Realtime as isize,
RealtimeAlarm = TimerfdClockId::RealtimeAlarm as isize,
Monotonic = TimerfdClockId::Monotonic as isize,
Boottime = TimerfdClockId::Boottime as isize,
BoottimeAlarm = TimerfdClockId::BoottimeAlarm as isize,
}
fn clock_name (clock: &ClockId) -> &'static str {
match *clock {
ClockId::Realtime => "CLOCK_REALTIME",
ClockId::RealtimeAlarm => "CLOCK_REALTIME_ALARM",
ClockId::Monotonic => "CLOCK_MONOTONIC",
ClockId::Boottime => "CLOCK_BOOTTIME",
ClockId::BoottimeAlarm => "CLOCK_BOOTTIME_ALARM",
}
}
impl fmt::Display for ClockId {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", clock_name(self))
}
}
impl fmt::Debug for ClockId {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({})", self.clone() as isize, clock_name(self))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetTimeFlags {
Default,
Abstime,
TimerCancelOnSet,
}
use rustix::time::{TimerfdFlags, TimerfdTimerFlags};
mod structs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimerState {
Disarmed,
Oneshot(Duration),
Periodic {
current: Duration,
interval: Duration,
}
}
#[derive(Debug)]
pub struct TimerFd(OwnedFd);
impl TimerFd {
pub fn new_custom(clock: ClockId, nonblocking: bool, cloexec: bool) -> IoResult<TimerFd> {
let mut flags = TimerfdFlags::empty();
if nonblocking {
flags |= TimerfdFlags::NONBLOCK;
}
if cloexec {
flags |= TimerfdFlags::CLOEXEC;
}
let clock = match clock {
ClockId::Realtime => TimerfdClockId::Realtime,
ClockId::RealtimeAlarm => TimerfdClockId::RealtimeAlarm,
ClockId::Monotonic => TimerfdClockId::Monotonic,
ClockId::Boottime => TimerfdClockId::Boottime,
ClockId::BoottimeAlarm => TimerfdClockId::BoottimeAlarm,
};
let fd = rustix::time::timerfd_create(clock, flags)?;
Ok(TimerFd(fd))
}
pub fn new() -> IoResult<TimerFd> {
TimerFd::new_custom(ClockId::Monotonic, false, false)
}
pub fn set_state(&mut self, state: TimerState, sflags: SetTimeFlags) -> TimerState {
let flags = match sflags {
SetTimeFlags::Default => TimerfdTimerFlags::empty(),
SetTimeFlags::Abstime => TimerfdTimerFlags::ABSTIME,
SetTimeFlags::TimerCancelOnSet => {
TimerfdTimerFlags::ABSTIME | TimerfdTimerFlags::CANCEL_ON_SET
}
};
let new: Itimerspec = state.into();
let old = rustix::time::timerfd_settime(&self.0, flags, &new)
.expect("Looks like timerfd_settime failed in some undocumented way");
old.into()
}
pub fn get_state(&self) -> TimerState {
let state = rustix::time::timerfd_gettime(&self.0)
.expect("Looks like timerfd_gettime failed in some undocumented way");
state.into()
}
pub fn read(&self) -> u64 {
let mut buffer = [0_u8; 8];
loop {
match rustix::io::read(&self.0, &mut buffer) {
Ok(8) => {
let value = u64::from_ne_bytes(buffer);
assert_ne!(value, 0);
return value;
}
Err(rustix::io::Errno::WOULDBLOCK) => return 0,
Err(rustix::io::Errno::INTR) => (),
Err(e) => panic!("Unexpected read error: {}", e),
_ => unreachable!(),
}
}
}
}
impl AsRawFd for TimerFd {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl FromRawFd for TimerFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
TimerFd(FromRawFd::from_raw_fd(fd))
}
}
impl AsFd for TimerFd {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl From<TimerFd> for OwnedFd {
fn from(fd: TimerFd) -> OwnedFd {
fd.0
}
}
#[cfg(test)]
mod tests {
extern crate rustix;
use super::{ClockId, Duration, SetTimeFlags, TimerFd, TimerState};
#[test]
fn clockid_new_custom () {
fn __test_clockid (clockid: ClockId) {
let tfd = TimerFd::new_custom(clockid, true, false).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
}
__test_clockid(ClockId::Realtime);
__test_clockid(ClockId::Monotonic);
__test_clockid(ClockId::Boottime);
}
const TEST_TIMER_OFFSET: u64 = 100;
#[test]
fn timerfd_settime_flags_default () {
let mut tfd = TimerFd::new().unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
tfd.set_state(TimerState::Oneshot(Duration::new(TEST_TIMER_OFFSET, 0)),
SetTimeFlags::Default);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
#[test]
fn timerfd_settime_flags_abstime () {
let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
let now = rustix::time::clock_gettime(rustix::time::ClockId::Realtime);
tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
SetTimeFlags::Abstime);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
#[test]
fn timerfd_settime_flags_abstime_cancel () {
let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
let now = rustix::time::clock_gettime(rustix::time::ClockId::Realtime);
tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
SetTimeFlags::TimerCancelOnSet);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
}