Skip to content

Commit 6edeef3

Browse files
committed
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 <brian@troutwine.us>
1 parent c77a872 commit 6edeef3

File tree

7 files changed

+263
-106
lines changed

7 files changed

+263
-106
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2525
(#[1547](https://github.com/nix-rust/nix/pull/1547))
2626
- Added getter methods to `MqAttr` struct
2727
(#[1619](https://github.com/nix-rust/nix/pull/1619))
28+
- Added `timer` support
29+
(#[1620](https://github.com/nix-rust/nix/pull/1620))
2830

2931
### Changed
3032
### Fixed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ default = [
3939
"acct", "aio", "dir", "env", "event", "features", "fs",
4040
"hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue",
4141
"net", "personality", "poll", "process", "pthread", "ptrace", "quota",
42-
"reboot", "resource", "sched", "signal", "socket", "term", "time",
42+
"reboot", "resource", "sched", "signal", "socket", "term", "time", "timer",
4343
"ucontext", "uio", "users", "zerocopy",
4444
]
4545

@@ -71,6 +71,7 @@ signal = ["process"]
7171
socket = []
7272
term = []
7373
time = []
74+
timer = ["signal", "time"]
7475
ucontext = ["signal"]
7576
uio = []
7677
users = ["features"]

src/sys/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,12 @@ feature! {
201201
#[allow(missing_docs)]
202202
pub mod timerfd;
203203
}
204+
205+
#[cfg(all(
206+
any(target_os = "freebsd", target_os = "netbsd", target_os = "illumos", target_os = "linux"),
207+
feature = "timer"
208+
))]
209+
feature! {
210+
#![feature = "timer"]
211+
pub mod timer;
212+
}

src/sys/signal.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,11 @@ mod sigevent {
10851085
pub fn sigevent(&self) -> libc::sigevent {
10861086
self.sigevent
10871087
}
1088+
1089+
/// Returns a mutable pointer to the `sigevent` wrapped by `self`
1090+
pub fn as_raw_mut(&mut self) -> *mut libc::sigevent {
1091+
&mut self.sigevent
1092+
}
10881093
}
10891094

10901095
impl<'a> From<&'a libc::sigevent> for SigEvent {

src/sys/time.rs

+120-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,124 @@
1-
use std::{cmp, fmt, ops};
2-
use std::time::Duration;
3-
use std::convert::From;
1+
#[cfg_attr(target_env = "musl", allow(deprecated))]
2+
// https://github.com/rust-lang/libc/issues/1848
3+
pub use libc::{suseconds_t, time_t};
44
use libc::{timespec, timeval};
5-
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
6-
pub use libc::{time_t, suseconds_t};
5+
use std::convert::From;
6+
use std::time::Duration;
7+
use std::{cmp, fmt, ops};
8+
9+
#[cfg(all(
10+
any(target_os = "android", target_os = "freebsd", target_os = "netbsd", target_os = "linux", target_os = "dragonfly"),
11+
any(feature = "timer", feature = "time")
12+
))]
13+
pub(crate) mod timer {
14+
use crate::sys::time::TimeSpec;
15+
use bitflags::bitflags;
16+
17+
#[derive(Debug, Clone, Copy)]
18+
pub(crate) struct TimerSpec(libc::itimerspec);
19+
20+
impl TimerSpec {
21+
pub const fn none() -> Self {
22+
Self(libc::itimerspec {
23+
it_interval: libc::timespec {
24+
tv_sec: 0,
25+
tv_nsec: 0,
26+
},
27+
it_value: libc::timespec {
28+
tv_sec: 0,
29+
tv_nsec: 0,
30+
},
31+
})
32+
}
33+
}
34+
35+
impl AsMut<libc::itimerspec> for TimerSpec {
36+
fn as_mut(&mut self) -> &mut libc::itimerspec {
37+
&mut self.0
38+
}
39+
}
40+
41+
impl AsRef<libc::itimerspec> for TimerSpec {
42+
fn as_ref(&self) -> &libc::itimerspec {
43+
&self.0
44+
}
45+
}
46+
47+
impl From<Expiration> for TimerSpec {
48+
fn from(expiration: Expiration) -> TimerSpec {
49+
match expiration {
50+
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
51+
it_interval: libc::timespec {
52+
tv_sec: 0,
53+
tv_nsec: 0,
54+
},
55+
it_value: *t.as_ref(),
56+
}),
57+
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
58+
it_interval: *interval.as_ref(),
59+
it_value: *start.as_ref(),
60+
}),
61+
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
62+
it_interval: *t.as_ref(),
63+
it_value: *t.as_ref(),
64+
}),
65+
}
66+
}
67+
}
68+
69+
/// An enumeration allowing the definition of the expiration time of an alarm,
70+
/// recurring or not.
71+
#[derive(Debug, Clone, Copy, PartialEq)]
72+
pub enum Expiration {
73+
/// Alarm will trigger once after the time given in `TimeSpec`
74+
OneShot(TimeSpec),
75+
/// Alarm will trigger after a specified delay and then every interval of
76+
/// time.
77+
IntervalDelayed(TimeSpec, TimeSpec),
78+
/// Alarm will trigger every specified interval of time.
79+
Interval(TimeSpec),
80+
}
81+
82+
#[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly")))]
83+
bitflags! {
84+
/// Flags that are used for arming the timer.
85+
pub struct TimerSetTimeFlags: libc::c_int {
86+
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
87+
}
88+
}
89+
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly"))]
90+
bitflags! {
91+
/// Flags that are used for arming the timer.
92+
pub struct TimerSetTimeFlags: libc::c_int {
93+
const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME;
94+
}
95+
}
96+
97+
impl From<TimerSpec> for Expiration {
98+
fn from(timerspec: TimerSpec) -> Expiration {
99+
match timerspec {
100+
TimerSpec(libc::itimerspec {
101+
it_interval:
102+
libc::timespec {
103+
tv_sec: 0,
104+
tv_nsec: 0,
105+
},
106+
it_value: ts,
107+
}) => Expiration::OneShot(ts.into()),
108+
TimerSpec(libc::itimerspec {
109+
it_interval: int_ts,
110+
it_value: val_ts,
111+
}) => {
112+
if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
113+
Expiration::Interval(int_ts.into())
114+
} else {
115+
Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
7122

8123
pub trait TimeValLike: Sized {
9124
#[inline]

src/sys/timer.rs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//! Timer API via signals.
2+
//!
3+
//! Timer is a POSIX API to create timers and get expiration notifications
4+
//! through signals.
5+
//!
6+
//! For more documentation, please read [timer_create(3p)](https://man7.org/linux/man-pages/man3/timer_create.3p.html).
7+
use crate::sys::signal::SigEvent;
8+
use crate::sys::time::timer::{Expiration, TimerSetTimeFlags, TimerSpec};
9+
use crate::time::ClockId;
10+
use crate::{errno::Errno, Result};
11+
use core::mem;
12+
13+
/// The maximum value that [`Timer::overruns`] will return.
14+
pub const DELAYTIMER_MAX: i32 = libc::_SC_DELAYTIMER_MAX as i32;
15+
16+
/// A per-process timer
17+
#[derive(Debug)]
18+
pub struct Timer {
19+
timer_id: libc::timer_t,
20+
}
21+
22+
impl Timer {
23+
/// Creates a new timer based on the clock defined by `clockid`. The details
24+
/// of the signal and its handler are defined by the passed `sigevent`.
25+
pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result<Self> {
26+
let mut timer_id: libc::timer_t = unsafe { mem::zeroed::<libc::timer_t>() };
27+
Errno::result(unsafe {
28+
libc::timer_create(clockid.as_raw(), sigevent.as_raw_mut(), &mut timer_id)
29+
})
30+
.map(|_| Self { timer_id })
31+
}
32+
33+
/// Set a new alarm on the timer.
34+
///
35+
/// # Types of alarm
36+
///
37+
/// There are 3 types of alarms you can set:
38+
///
39+
/// - one shot: the alarm will trigger once after the specified amount of
40+
/// time.
41+
/// Example: I want an alarm to go off in 60s and then disables itself.
42+
///
43+
/// - interval: the alarm will trigger every specified interval of time.
44+
/// Example: I want an alarm to go off every 60s. The alarm will first
45+
/// go off 60s after I set it and every 60s after that. The alarm will
46+
/// not disable itself.
47+
///
48+
/// - interval delayed: the alarm will trigger after a certain amount of
49+
/// time and then trigger at a specified interval.
50+
/// Example: I want an alarm to go off every 60s but only start in 1h.
51+
/// The alarm will first trigger 1h after I set it and then every 60s
52+
/// after that. The alarm will not disable itself.
53+
///
54+
/// # Relative vs absolute alarm
55+
///
56+
/// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
57+
/// to the `Expiration` you want is relative. If however you want an alarm
58+
/// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
59+
/// Then the one shot TimeSpec and the delay TimeSpec of the delayed
60+
/// interval are going to be interpreted as absolute.
61+
///
62+
/// # Disabling alarms
63+
///
64+
/// Note: Only one alarm can be set for any given timer. Setting a new alarm
65+
/// actually removes the previous one.
66+
///
67+
/// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm
68+
/// altogether.
69+
pub fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
70+
let timerspec: TimerSpec = expiration.into();
71+
Errno::result(unsafe {
72+
libc::timer_settime(
73+
self.timer_id,
74+
flags.bits(),
75+
timerspec.as_ref(),
76+
core::ptr::null_mut(),
77+
)
78+
})
79+
.map(drop)
80+
}
81+
82+
/// Get the parameters for the alarm currently set, if any.
83+
pub fn get(&self) -> Result<Option<Expiration>> {
84+
let mut timerspec = TimerSpec::none();
85+
Errno::result(unsafe { libc::timer_gettime(self.timer_id, timerspec.as_mut()) }).map(|_| {
86+
if timerspec.as_ref().it_interval.tv_sec == 0
87+
&& timerspec.as_ref().it_interval.tv_nsec == 0
88+
&& timerspec.as_ref().it_value.tv_sec == 0
89+
&& timerspec.as_ref().it_value.tv_nsec == 0
90+
{
91+
None
92+
} else {
93+
Some(timerspec.into())
94+
}
95+
})
96+
}
97+
98+
/// Return the number of timers that have overrun
99+
///
100+
/// An overrun timer is one which expires while its related signal is still
101+
/// queued. TODO explain better
102+
pub fn overruns(&self) -> i32 {
103+
unsafe { libc::timer_getoverrun(self.timer_id) }
104+
}
105+
}
106+
107+
impl Drop for Timer {
108+
fn drop(&mut self) {
109+
if !std::thread::panicking() {
110+
let result = Errno::result(unsafe { libc::timer_delete(self.timer_id) });
111+
if let Err(Errno::EINVAL) = result {
112+
panic!("close of Timer encountered EINVAL");
113+
}
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)