Skip to content

Commit 7c63be2

Browse files
Safer poll timeout
1 parent df5877c commit 7c63be2

File tree

3 files changed

+199
-10
lines changed

3 files changed

+199
-10
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2121
([#1921](https://github.com/nix-rust/nix/pull/1921))
2222
- The `fd` argument to `sys::signalfd::signalfd` is now of type `Option<impl AsFd>`.
2323
([#1874](https://github.com/nix-rust/nix/pull/1874))
24+
- The `timeout` argument of `poll::poll` is now of type `poll::PollTimeout`.
25+
([#1876](https://github.com/nix-rust/nix/pull/1876))
2426

2527
### Fixed
2628
### Removed

src/poll.rs

+194-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Wait for events to trigger on specific file descriptors
2+
use std::convert::TryFrom;
23
use std::os::unix::io::{AsRawFd, RawFd};
4+
use std::time::Duration;
35

46
use crate::errno::Errno;
57
use crate::Result;
6-
78
/// This is a wrapper around `libc::pollfd`.
89
///
910
/// It's meant to be used as an argument to the [`poll`](fn.poll.html) and
@@ -132,6 +133,192 @@ libc_bitflags! {
132133
}
133134
}
134135

136+
/// Timeout argument for [`poll`].
137+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
138+
pub struct PollTimeout(i32);
139+
140+
impl PollTimeout {
141+
/// Blocks indefinitely.
142+
pub const NONE: Self = Self(-1);
143+
/// Returns immediately.
144+
pub const ZERO: Self = Self(0);
145+
/// Blocks for at most [`std::i32::MAX`] milliseconds.
146+
pub const MAX: Self = Self(i32::MAX);
147+
/// Returns if `self` equals [`PollTimeout::NONE`].
148+
pub fn is_none(&self) -> bool {
149+
// > Specifying a negative value in timeout means an infinite timeout.
150+
*self <= Self::NONE
151+
}
152+
/// Returns if `self` does not equal [`PollTimeout::NONE`].
153+
pub fn is_some(&self) -> bool {
154+
!self.is_none()
155+
}
156+
/// Returns the timeout in milliseconds if there is some, otherwise returns `None`.
157+
pub fn timeout(&self) -> Option<i32> {
158+
self.is_some().then_some(self.0)
159+
}
160+
}
161+
162+
impl TryFrom<Duration> for PollTimeout {
163+
type Error = <i32 as TryFrom<u128>>::Error;
164+
fn try_from(x: Duration) -> std::result::Result<Self, Self::Error> {
165+
Ok(Self(i32::try_from(x.as_millis())?))
166+
}
167+
}
168+
impl TryFrom<u128> for PollTimeout {
169+
type Error = <i32 as TryFrom<u128>>::Error;
170+
fn try_from(x: u128) -> std::result::Result<Self, Self::Error> {
171+
Ok(Self(i32::try_from(x)?))
172+
}
173+
}
174+
impl TryFrom<u64> for PollTimeout {
175+
type Error = <i32 as TryFrom<u64>>::Error;
176+
fn try_from(x: u64) -> std::result::Result<Self, Self::Error> {
177+
Ok(Self(i32::try_from(x)?))
178+
}
179+
}
180+
impl TryFrom<u32> for PollTimeout {
181+
type Error = <i32 as TryFrom<u32>>::Error;
182+
fn try_from(x: u32) -> std::result::Result<Self, Self::Error> {
183+
Ok(Self(i32::try_from(x)?))
184+
}
185+
}
186+
impl From<u16> for PollTimeout {
187+
fn from(x: u16) -> Self {
188+
Self(i32::from(x))
189+
}
190+
}
191+
impl From<u8> for PollTimeout {
192+
fn from(x: u8) -> Self {
193+
Self(i32::from(x))
194+
}
195+
}
196+
impl TryFrom<i128> for PollTimeout {
197+
type Error = <i32 as TryFrom<i128>>::Error;
198+
fn try_from(x: i128) -> std::result::Result<Self, Self::Error> {
199+
match x {
200+
// > Specifying a negative value in timeout means an infinite timeout.
201+
i128::MIN..=-1 => Ok(Self::NONE),
202+
millis @ 0.. => Ok(Self(
203+
i32::try_from(millis)?,
204+
)),
205+
}
206+
}
207+
}
208+
impl TryFrom<i64> for PollTimeout {
209+
type Error = <i32 as TryFrom<i64>>::Error;
210+
fn try_from(x: i64) -> std::result::Result<Self, Self::Error> {
211+
match x {
212+
i64::MIN..=-1 => Ok(Self::NONE),
213+
millis @ 0.. => Ok(Self(
214+
i32::try_from(millis)?,
215+
)),
216+
}
217+
}
218+
}
219+
impl From<i32> for PollTimeout {
220+
fn from(millis: i32) -> Self {
221+
Self(millis)
222+
}
223+
}
224+
impl TryFrom<i16> for PollTimeout {
225+
type Error = Errno;
226+
fn try_from(x: i16) -> Result<Self> {
227+
match x {
228+
-1 => Ok(Self::NONE),
229+
millis @ 0.. => Ok(Self(millis.into())),
230+
// EINVAL (ppoll()) The timeout value expressed in *ip is invalid (negative).
231+
_ => Err(Errno::EINVAL),
232+
}
233+
}
234+
}
235+
impl TryFrom<i8> for PollTimeout {
236+
type Error = Errno;
237+
fn try_from(x: i8) -> Result<Self> {
238+
match x {
239+
-1 => Ok(Self::NONE),
240+
millis @ 0.. => Ok(Self(millis.into())),
241+
// EINVAL (ppoll()) The timeout value expressed in *ip is invalid (negative).
242+
_ => Err(Errno::EINVAL),
243+
}
244+
}
245+
}
246+
impl TryFrom<PollTimeout> for Duration {
247+
type Error = ();
248+
fn try_from(x: PollTimeout) -> std::result::Result<Self, ()> {
249+
match x.timeout() {
250+
// SAFETY: When `x.timeout()` returns `Some(a)`, `a` is always non-negative.
251+
Some(millis) => Ok(Duration::from_millis(unsafe {
252+
u64::try_from(millis).unwrap_unchecked()
253+
})),
254+
None => Err(()),
255+
}
256+
}
257+
}
258+
impl TryFrom<PollTimeout> for u128 {
259+
type Error = <Self as TryFrom<i32>>::Error;
260+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
261+
Self::try_from(x.0)
262+
}
263+
}
264+
impl TryFrom<PollTimeout> for u64 {
265+
type Error = <Self as TryFrom<i32>>::Error;
266+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
267+
Self::try_from(x.0)
268+
}
269+
}
270+
impl TryFrom<PollTimeout> for u32 {
271+
type Error = <Self as TryFrom<i32>>::Error;
272+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
273+
Self::try_from(x.0)
274+
}
275+
}
276+
impl TryFrom<PollTimeout> for u16 {
277+
type Error = Option<<Self as TryFrom<i32>>::Error>;
278+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
279+
match x.timeout() {
280+
Some(millis) => Ok(Self::try_from(millis).map_err(Some)?),
281+
None => Err(None),
282+
}
283+
}
284+
}
285+
impl TryFrom<PollTimeout> for u8 {
286+
type Error = Option<<Self as TryFrom<i32>>::Error>;
287+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
288+
match x.timeout() {
289+
Some(millis) => Ok(Self::try_from(millis).map_err(Some)?),
290+
None => Err(None),
291+
}
292+
}
293+
}
294+
impl From<PollTimeout> for i128 {
295+
fn from(x: PollTimeout) -> Self {
296+
x.timeout().unwrap_or(-1).into()
297+
}
298+
}
299+
impl From<PollTimeout> for i64 {
300+
fn from(x: PollTimeout) -> Self {
301+
x.timeout().unwrap_or(-1).into()
302+
}
303+
}
304+
impl From<PollTimeout> for i32 {
305+
fn from(x: PollTimeout) -> Self {
306+
x.timeout().unwrap_or(-1)
307+
}
308+
}
309+
impl TryFrom<PollTimeout> for i16 {
310+
type Error = <Self as TryFrom<i32>>::Error;
311+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
312+
Self::try_from(x.timeout().unwrap_or(-1))
313+
}
314+
}
315+
impl TryFrom<PollTimeout> for i8 {
316+
type Error = <Self as TryFrom<i32>>::Error;
317+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
318+
Self::try_from(x.timeout().unwrap_or(-1))
319+
}
320+
}
321+
135322
/// `poll` waits for one of a set of file descriptors to become ready to perform I/O.
136323
/// ([`poll(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html))
137324
///
@@ -148,16 +335,16 @@ libc_bitflags! {
148335
///
149336
/// Note that the timeout interval will be rounded up to the system clock
150337
/// granularity, and kernel scheduling delays mean that the blocking
151-
/// interval may overrun by a small amount. Specifying a negative value
152-
/// in timeout means an infinite timeout. Specifying a timeout of zero
153-
/// causes `poll()` to return immediately, even if no file descriptors are
154-
/// ready.
155-
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<libc::c_int> {
338+
/// interval may overrun by a small amount. Specifying a [`PollTimeout::NONE`]
339+
/// in timeout means an infinite timeout. Specifying a timeout of
340+
/// [`PollTimeout::ZERO`] causes `poll()` to return immediately, even if no file
341+
/// descriptors are ready.
342+
pub fn poll<T: Into<PollTimeout>>(fds: &mut [PollFd], timeout: T) -> Result<libc::c_int> {
156343
let res = unsafe {
157344
libc::poll(
158345
fds.as_mut_ptr() as *mut libc::pollfd,
159346
fds.len() as libc::nfds_t,
160-
timeout,
347+
i32::from(timeout.into()),
161348
)
162349
};
163350

test/test_poll.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use nix::{
22
errno::Errno,
3-
poll::{poll, PollFd, PollFlags},
3+
poll::{poll, PollFd, PollFlags, PollTimeout},
44
unistd::{pipe, write},
55
};
66

@@ -22,14 +22,14 @@ fn test_poll() {
2222
let mut fds = [PollFd::new(r, PollFlags::POLLIN)];
2323

2424
// Poll an idle pipe. Should timeout
25-
let nfds = loop_while_eintr!(poll(&mut fds, 100));
25+
let nfds = loop_while_eintr!(poll(&mut fds, PollTimeout::from(100u8)));
2626
assert_eq!(nfds, 0);
2727
assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN));
2828

2929
write(w, b".").unwrap();
3030

3131
// Poll a readable pipe. Should return an event.
32-
let nfds = poll(&mut fds, 100).unwrap();
32+
let nfds = poll(&mut fds, PollTimeout::from(100u8)).unwrap();
3333
assert_eq!(nfds, 1);
3434
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
3535
}

0 commit comments

Comments
 (0)