Skip to content

Commit e6b1a07

Browse files
feat: Safer poll timeout
1 parent 582e773 commit e6b1a07

File tree

3 files changed

+250
-10
lines changed

3 files changed

+250
-10
lines changed

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,61 @@ All notable changes to this project will be documented in this file.
22
This project adheres to [Semantic Versioning](https://semver.org/).
33

44
# Change Log
5+
## [Unreleased] - ReleaseDate
6+
7+
### Fixed
8+
- Fix `SigSet` incorrect implementation of `Eq`, `PartialEq` and `Hash`
9+
([#1946](https://github.com/nix-rust/nix/pull/1946))
10+
11+
- Fixed the function signature of `recvmmsg`, potentially causing UB
12+
([#2119](https://github.com/nix-rust/nix/issues/2119))
13+
14+
- Fix `SignalFd::set_mask`. In 0.27.0 it would actually close the file
15+
descriptor.
16+
([#2141](https://github.com/nix-rust/nix/pull/2141))
17+
18+
### Changed
19+
20+
- The MSRV is now 1.69
21+
([#2144](https://github.com/nix-rust/nix/pull/2144))
22+
23+
- The following APIs now take an implementation of `AsFd` rather than a
24+
`RawFd`:
25+
26+
- `unistd::tcgetpgrp`
27+
- `unistd::tcsetpgrp`
28+
- `unistd::fpathconf`
29+
- `unistd::ttyname`
30+
- `unistd::getpeereid`
31+
32+
([#2137](https://github.com/nix-rust/nix/pull/2137))
33+
34+
- Changed `openat()` and `Dir::openat()`, now take optional `dirfd`s
35+
([#2139](https://github.com/nix-rust/nix/pull/2139))
36+
37+
- `PollFd::new` now takes a `BorrowedFd` argument, with relaxed lifetime
38+
requirements relative to the previous version.
39+
([#2134](https://github.com/nix-rust/nix/pull/2134))
40+
41+
- `FdSet::{insert, remove, contains}` now take `BorrowedFd` arguments, and have
42+
relaxed lifetime requirements relative to 0.27.1.
43+
([#2136](https://github.com/nix-rust/nix/pull/2136))
44+
45+
- Simplified the function signatures of `recvmmsg` and `sendmmsg`
46+
47+
- The `timeout` argument of `poll::poll` is now of type `poll::PollTimeout`.
48+
([#1876](https://github.com/nix-rust/nix/pull/1876))
49+
50+
### Added
51+
- Added `Icmp` and `IcmpV6` to `SockProtocol`.
52+
(#[2103](https://github.com/nix-rust/nix/pull/2103))
53+
54+
- Added `F_GETPATH` FcntlFlags entry on Apple/NetBSD/DragonflyBSD for `::nix::fcntl`.
55+
([#2142](https://github.com/nix-rust/nix/pull/2142))
56+
57+
- Added `Ipv6HopLimit` to `::nix::sys::socket::ControlMessage` for Linux,
58+
MacOS, FreeBSD, DragonflyBSD, Android, iOS and Haiku.
59+
([#2074](https://github.com/nix-rust/nix/pull/2074))
560

661
## [0.27.1] - 2023-08-28
762

src/poll.rs

Lines changed: 192 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Wait for events to trigger on specific file descriptors
22
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
3+
use std::time::Duration;
34

45
use crate::errno::Errno;
56
use crate::Result;
6-
77
/// This is a wrapper around `libc::pollfd`.
88
///
99
/// It's meant to be used as an argument to the [`poll`](fn.poll.html) and
@@ -179,6 +179,184 @@ libc_bitflags! {
179179
}
180180
}
181181

182+
/// Timeout argument for [`poll`].
183+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
184+
pub struct PollTimeout(i32);
185+
186+
impl PollTimeout {
187+
/// Blocks indefinitely.
188+
///
189+
/// > Specifying a negative value in timeout means an infinite timeout.
190+
pub const NONE: Self = Self(-1);
191+
/// Returns immediately.
192+
///
193+
/// > Specifying a timeout of zero causes poll() to return immediately, even if no file
194+
/// > descriptors are ready.
195+
pub const ZERO: Self = Self(0);
196+
/// Blocks for at most [`std::i32::MAX`] milliseconds.
197+
pub const MAX: Self = Self(i32::MAX);
198+
/// Returns if `self` equals [`PollTimeout::NONE`].
199+
pub fn is_none(&self) -> bool {
200+
// > Specifying a negative value in timeout means an infinite timeout.
201+
*self <= Self::NONE
202+
}
203+
/// Returns if `self` does not equal [`PollTimeout::NONE`].
204+
pub fn is_some(&self) -> bool {
205+
!self.is_none()
206+
}
207+
/// Returns the timeout in milliseconds if there is some, otherwise returns `None`.
208+
pub fn as_millis(&self) -> Option<i32> {
209+
self.is_some().then_some(self.0)
210+
}
211+
/// Returns the timeout as a `Duration` if there is some, otherwise returns `None`.
212+
pub fn timeout(&self) -> Option<Duration> {
213+
self.as_millis().map(|x|Duration::from_millis(u64::try_from(x).unwrap()))
214+
}
215+
}
216+
217+
impl<T: Into<PollTimeout>> From<Option<T>> for PollTimeout {
218+
fn from(x: Option<T>) -> Self {
219+
x.map_or(Self::NONE, |x| x.into())
220+
}
221+
}
222+
impl TryFrom<Duration> for PollTimeout {
223+
type Error = <i32 as TryFrom<u128>>::Error;
224+
fn try_from(x: Duration) -> std::result::Result<Self, Self::Error> {
225+
Ok(Self(i32::try_from(x.as_millis())?))
226+
}
227+
}
228+
impl TryFrom<u128> for PollTimeout {
229+
type Error = <i32 as TryFrom<u128>>::Error;
230+
fn try_from(x: u128) -> std::result::Result<Self, Self::Error> {
231+
Ok(Self(i32::try_from(x)?))
232+
}
233+
}
234+
impl TryFrom<u64> for PollTimeout {
235+
type Error = <i32 as TryFrom<u64>>::Error;
236+
fn try_from(x: u64) -> std::result::Result<Self, Self::Error> {
237+
Ok(Self(i32::try_from(x)?))
238+
}
239+
}
240+
impl TryFrom<u32> for PollTimeout {
241+
type Error = <i32 as TryFrom<u32>>::Error;
242+
fn try_from(x: u32) -> std::result::Result<Self, Self::Error> {
243+
Ok(Self(i32::try_from(x)?))
244+
}
245+
}
246+
impl From<u16> for PollTimeout {
247+
fn from(x: u16) -> Self {
248+
Self(i32::from(x))
249+
}
250+
}
251+
impl From<u8> for PollTimeout {
252+
fn from(x: u8) -> Self {
253+
Self(i32::from(x))
254+
}
255+
}
256+
impl TryFrom<i128> for PollTimeout {
257+
type Error = <i32 as TryFrom<i128>>::Error;
258+
fn try_from(x: i128) -> std::result::Result<Self, Self::Error> {
259+
match x {
260+
// > Specifying a negative value in timeout means an infinite timeout.
261+
i128::MIN..=-1 => Ok(Self::NONE),
262+
millis @ 0.. => Ok(Self(i32::try_from(millis)?)),
263+
}
264+
}
265+
}
266+
impl TryFrom<i64> for PollTimeout {
267+
type Error = <i32 as TryFrom<i64>>::Error;
268+
fn try_from(x: i64) -> std::result::Result<Self, Self::Error> {
269+
match x {
270+
i64::MIN..=-1 => Ok(Self::NONE),
271+
millis @ 0.. => Ok(Self(i32::try_from(millis)?)),
272+
}
273+
}
274+
}
275+
impl From<i32> for PollTimeout {
276+
fn from(x: i32) -> Self {
277+
Self(x)
278+
}
279+
}
280+
impl From<i16> for PollTimeout {
281+
fn from(x: i16) -> Self {
282+
Self(i32::from(x))
283+
}
284+
}
285+
impl From<i8> for PollTimeout {
286+
fn from(x: i8) -> Self {
287+
Self(i32::from(x))
288+
}
289+
}
290+
impl TryFrom<PollTimeout> for Duration {
291+
type Error = ();
292+
fn try_from(x: PollTimeout) -> std::result::Result<Self, ()> {
293+
match x.timeout() {
294+
// SAFETY: When `x.timeout()` returns `Some(a)`, `a` is always non-negative.
295+
Some(millis) => Ok(Duration::from_millis(unsafe {
296+
u64::try_from(millis).unwrap_unchecked()
297+
})),
298+
None => Err(()),
299+
}
300+
}
301+
}
302+
impl TryFrom<PollTimeout> for u128 {
303+
type Error = <Self as TryFrom<i32>>::Error;
304+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
305+
Self::try_from(x.0)
306+
}
307+
}
308+
impl TryFrom<PollTimeout> for u64 {
309+
type Error = <Self as TryFrom<i32>>::Error;
310+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
311+
Self::try_from(x.0)
312+
}
313+
}
314+
impl TryFrom<PollTimeout> for u32 {
315+
type Error = <Self as TryFrom<i32>>::Error;
316+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
317+
Self::try_from(x.0)
318+
}
319+
}
320+
impl TryFrom<PollTimeout> for u16 {
321+
type Error = <Self as TryFrom<i32>>::Error;
322+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
323+
Self::try_from(x.0)
324+
}
325+
}
326+
impl TryFrom<PollTimeout> for u8 {
327+
type Error = <Self as TryFrom<i32>>::Error;
328+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
329+
Self::try_from(x.0)
330+
}
331+
}
332+
impl From<PollTimeout> for i128 {
333+
fn from(x: PollTimeout) -> Self {
334+
Self::from(x.0)
335+
}
336+
}
337+
impl From<PollTimeout> for i64 {
338+
fn from(x: PollTimeout) -> Self {
339+
Self::from(x.0)
340+
}
341+
}
342+
impl From<PollTimeout> for i32 {
343+
fn from(x: PollTimeout) -> Self {
344+
x.0
345+
}
346+
}
347+
impl TryFrom<PollTimeout> for i16 {
348+
type Error = <Self as TryFrom<i32>>::Error;
349+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
350+
Self::try_from(x.0)
351+
}
352+
}
353+
impl TryFrom<PollTimeout> for i8 {
354+
type Error = <Self as TryFrom<i32>>::Error;
355+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
356+
Self::try_from(x.0)
357+
}
358+
}
359+
182360
/// `poll` waits for one of a set of file descriptors to become ready to perform I/O.
183361
/// ([`poll(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html))
184362
///
@@ -195,13 +373,20 @@ libc_bitflags! {
195373
///
196374
/// Note that the timeout interval will be rounded up to the system clock
197375
/// granularity, and kernel scheduling delays mean that the blocking
198-
/// interval may overrun by a small amount. Specifying a negative value
199-
/// in timeout means an infinite timeout. Specifying a timeout of zero
200-
/// causes `poll()` to return immediately, even if no file descriptors are
201-
/// ready.
202-
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<libc::c_int> {
376+
/// interval may overrun by a small amount. Specifying a [`PollTimeout::NONE`]
377+
/// in timeout means an infinite timeout. Specifying a timeout of
378+
/// [`PollTimeout::ZERO`] causes `poll()` to return immediately, even if no file
379+
/// descriptors are ready.
380+
pub fn poll<T: Into<PollTimeout>>(
381+
fds: &mut [PollFd],
382+
timeout: T,
383+
) -> Result<libc::c_int> {
203384
let res = unsafe {
204-
libc::poll(fds.as_mut_ptr().cast(), fds.len() as libc::nfds_t, timeout)
385+
libc::poll(
386+
fds.as_mut_ptr().cast(),
387+
fds.len() as libc::nfds_t,
388+
i32::from(timeout.into()),
389+
)
205390
};
206391

207392
Errno::result(res)

test/test_poll.rs

Lines changed: 3 additions & 3 deletions
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
use std::os::unix::io::{AsFd, BorrowedFd};
@@ -23,14 +23,14 @@ fn test_poll() {
2323
let mut fds = [PollFd::new(r.as_fd(), PollFlags::POLLIN)];
2424

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

3030
write(&w, b".").unwrap();
3131

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

0 commit comments

Comments
 (0)