Skip to content

Commit 40733be

Browse files
authored
Change poll's timeout from c_int to Option<&Timespec>. (#1285)
* Change `poll`'s timeout from `c_int` to `Option<&Timespec>`. This harmonizes the timeout with the rest of rustix, which uses `Timespec` for all time values. And, it eliminates the awkwardness of using `-1` as a sentinel value. On platforms with `ppoll`, the `Timespec` can be passed straight to the OS. On platforms without `ppoll`, we have to do a fallible conversion into `c_int`. * Return `Errno::INVAL` when converting a `Timespec` to `i32`. * Make ppoll weak on NetBSD, as it's not present on NetBSD 9. * Add comments about why the code adds `999_999` before dividing.
1 parent 7b1f638 commit 40733be

File tree

12 files changed

+158
-45
lines changed

12 files changed

+158
-45
lines changed

src/backend/libc/event/syscalls.rs

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::event::port::Event;
1818
use crate::event::EventfdFlags;
1919
#[cfg(any(bsd, linux_kernel, target_os = "wasi"))]
2020
use crate::event::FdSetElement;
21-
use crate::event::PollFd;
21+
use crate::event::{PollFd, Timespec};
2222
use crate::io;
2323
#[cfg(solarish)]
2424
use crate::utils::as_mut_ptr;
@@ -30,7 +30,15 @@ use crate::utils::as_ptr;
3030
all(feature = "alloc", any(linux_kernel, target_os = "redox")),
3131
))]
3232
use core::mem::MaybeUninit;
33-
#[cfg(any(bsd, linux_kernel, target_os = "wasi"))]
33+
#[cfg(any(
34+
bsd,
35+
linux_kernel,
36+
target_os = "fuchsia",
37+
target_os = "haiku",
38+
target_os = "hurd",
39+
target_os = "netbsd",
40+
target_os = "wasi"
41+
))]
3442
use core::ptr::null;
3543
#[cfg(any(bsd, linux_kernel, solarish, target_os = "redox", target_os = "wasi"))]
3644
use core::ptr::null_mut;
@@ -119,14 +127,98 @@ pub(crate) unsafe fn kevent(
119127
}
120128

121129
#[inline]
122-
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
130+
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> {
123131
let nfds = fds
124132
.len()
125133
.try_into()
126134
.map_err(|_convert_err| io::Errno::INVAL)?;
127135

128-
ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
129-
.map(|nready| nready as usize)
136+
// If we have `ppoll`, it supports a `timespec` timeout, so use it.
137+
#[cfg(any(
138+
linux_kernel,
139+
freebsdlike,
140+
target_os = "fuchsia",
141+
target_os = "haiku",
142+
target_os = "hurd",
143+
target_os = "netbsd"
144+
))]
145+
{
146+
// If we don't have to fix y2038 on this platform, `Timespec` is
147+
// the same as `c::timespec` and it's easy.
148+
#[cfg(not(fix_y2038))]
149+
let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
150+
151+
// If we do have to fix y2038 on this platform, convert to
152+
// `c::timespec`.
153+
#[cfg(fix_y2038)]
154+
let converted_timeout;
155+
#[cfg(fix_y2038)]
156+
let timeout = match timeout {
157+
None => null(),
158+
Some(timeout) => {
159+
converted_timeout = c::timespec {
160+
tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
161+
tv_nsec: timeout.tv_nsec as _,
162+
};
163+
&converted_timeout
164+
}
165+
};
166+
167+
#[cfg(not(target_os = "netbsd"))]
168+
{
169+
ret_c_int(unsafe { c::ppoll(fds.as_mut_ptr().cast(), nfds, timeout, null()) })
170+
.map(|nready| nready as usize)
171+
}
172+
173+
// NetBSD 9.x lacks `ppoll`, so use a weak symbol and fall back to
174+
// plain `poll` if needed.
175+
#[cfg(target_os = "netbsd")]
176+
{
177+
weak! {
178+
fn ppoll(
179+
*mut c::pollfd,
180+
c::nfds_t,
181+
*const c::timespec,
182+
*const c::sigset_t
183+
) -> c::c_int
184+
}
185+
if let Some(func) = ppoll.get() {
186+
return ret_c_int(unsafe { func(fds.as_mut_ptr().cast(), nfds, timeout, null()) })
187+
.map(|nready| nready as usize);
188+
}
189+
}
190+
}
191+
192+
// If we don't have `ppoll`, convert the timeout to `c_int` and use `poll`.
193+
#[cfg(not(any(
194+
linux_kernel,
195+
freebsdlike,
196+
target_os = "fuchsia",
197+
target_os = "haiku",
198+
target_os = "hurd"
199+
)))]
200+
{
201+
let timeout = match timeout {
202+
None => -1,
203+
Some(timeout) => {
204+
// Convert from `Timespec` to `c_int` milliseconds.
205+
let secs = timeout.tv_sec;
206+
if secs < 0 {
207+
return Err(io::Errno::INVAL);
208+
}
209+
secs.checked_mul(1000)
210+
.and_then(|millis| {
211+
// Add the nanoseconds, converted to millis, rounding
212+
// up. With Rust 1.73.0 this can use `div_ceil`.
213+
millis.checked_add((i64::from(timeout.tv_nsec) + 999_999) / 1_000_000)
214+
})
215+
.and_then(|millis| c::c_int::try_from(millis).ok())
216+
.ok_or(io::Errno::INVAL)?
217+
}
218+
};
219+
ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
220+
.map(|nready| nready as usize)
221+
}
130222
}
131223

132224
#[cfg(any(bsd, linux_kernel))]
@@ -135,7 +227,7 @@ pub(crate) unsafe fn select(
135227
readfds: Option<&mut [FdSetElement]>,
136228
writefds: Option<&mut [FdSetElement]>,
137229
exceptfds: Option<&mut [FdSetElement]>,
138-
timeout: Option<&crate::timespec::Timespec>,
230+
timeout: Option<&Timespec>,
139231
) -> io::Result<i32> {
140232
let len = crate::event::fd_set_num_elements_for_bitvector(nfds);
141233

@@ -212,7 +304,7 @@ pub(crate) unsafe fn select(
212304
readfds: Option<&mut [FdSetElement]>,
213305
writefds: Option<&mut [FdSetElement]>,
214306
exceptfds: Option<&mut [FdSetElement]>,
215-
timeout: Option<&crate::timespec::Timespec>,
307+
timeout: Option<&Timespec>,
216308
) -> io::Result<i32> {
217309
let len = crate::event::fd_set_num_elements_for_fd_array(nfds as usize);
218310

src/backend/libc/event/windows_syscalls.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@
22
33
use crate::backend::c;
44
use crate::backend::conv::ret_c_int;
5-
use crate::event::{FdSetElement, PollFd};
5+
use crate::event::{FdSetElement, PollFd, Timespec};
66
use crate::io;
77

8-
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
8+
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> {
99
let nfds = fds
1010
.len()
1111
.try_into()
1212
.map_err(|_convert_err| io::Errno::INVAL)?;
1313

14+
let timeout = match timeout {
15+
None => -1,
16+
Some(timeout) => {
17+
// Convert from `Timespec` to `c_int` milliseconds.
18+
let secs = timeout.tv_sec;
19+
if secs < 0 {
20+
return Err(io::Errno::INVAL);
21+
}
22+
secs.checked_mul(1000)
23+
.and_then(|millis| {
24+
// Add the nanoseconds, converted to millis, rounding up.
25+
// With Rust 1.73.0 this can use `div_ceil`.
26+
millis.checked_add((i64::from(timeout.tv_nsec) + 999_999) / 1_000_000)
27+
})
28+
.and_then(|millis| c::c_int::try_from(millis).ok())
29+
.ok_or(io::Errno::INVAL)?
30+
}
31+
};
32+
1433
ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
1534
.map(|nready| nready as usize)
1635
}

src/backend/linux_raw/conv.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ pub(super) fn opt_mut<T: Sized, Num: ArgNumber>(t: Option<&mut T>) -> ArgReg<'_,
232232

233233
/// Convert an optional immutable reference into a `usize` for passing to a
234234
/// syscall.
235-
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
236235
#[inline]
237236
pub(super) fn opt_ref<T: Sized, Num: ArgNumber>(t: Option<&T>) -> ArgReg<'_, Num> {
238237
// This optimizes into the equivalent of `transmute(t)`, and has the

src/backend/linux_raw/event/syscalls.rs

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,28 @@
55
//! See the `rustix::backend` module documentation for details.
66
#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
77

8+
#[cfg(feature = "alloc")]
89
use crate::backend::c;
910
use crate::backend::conv::{
10-
by_ref, c_int, c_uint, ret, ret_c_int, ret_error, ret_owned_fd, ret_usize, slice_mut, zero,
11+
by_ref, c_int, c_uint, opt_ref, ret, ret_c_int, ret_error, ret_owned_fd, ret_usize, size_of,
12+
slice_mut, zero,
1113
};
12-
use crate::event::{epoll, EventfdFlags, FdSetElement, PollFd};
14+
use crate::event::{epoll, EventfdFlags, FdSetElement, PollFd, Timespec};
1315
use crate::fd::{BorrowedFd, OwnedFd};
1416
use crate::io;
15-
use crate::utils::as_mut_ptr;
17+
use crate::utils::{as_mut_ptr, option_as_ptr};
1618
#[cfg(feature = "alloc")]
1719
use core::mem::MaybeUninit;
1820
use core::ptr::null_mut;
19-
use linux_raw_sys::general::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD};
20-
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
21-
use {
22-
crate::backend::conv::{opt_ref, size_of},
23-
linux_raw_sys::general::{__kernel_timespec, kernel_sigset_t},
24-
};
21+
use linux_raw_sys::general::{kernel_sigset_t, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD};
2522

2623
#[inline]
27-
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
24+
pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> {
2825
let (fds_addr_mut, fds_len) = slice_mut(fds);
2926

30-
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
27+
let timeout = option_as_ptr(timeout);
28+
3129
unsafe {
32-
let timeout = if timeout >= 0 {
33-
Some(__kernel_timespec {
34-
tv_sec: (timeout as i64) / 1000,
35-
tv_nsec: (timeout as i64) % 1000 * 1_000_000,
36-
})
37-
} else {
38-
None
39-
};
4030
ret_usize(syscall!(
4131
__NR_ppoll,
4232
fds_addr_mut,
@@ -46,10 +36,6 @@ pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usiz
4636
size_of::<kernel_sigset_t, _>()
4737
))
4838
}
49-
#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
50-
unsafe {
51-
ret_usize(syscall!(__NR_poll, fds_addr_mut, fds_len, c_int(timeout)))
52-
}
5339
}
5440

5541
pub(crate) unsafe fn select(

src/event/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod port;
1919
#[cfg(any(bsd, linux_kernel, windows, target_os = "wasi"))]
2020
mod select;
2121

22+
pub use crate::timespec::{Nsecs, Secs, Timespec};
2223
#[cfg(any(
2324
linux_kernel,
2425
target_os = "freebsd",

src/event/poll.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
use crate::event::Timespec;
12
use crate::{backend, io};
23

34
pub use backend::event::poll_fd::{PollFd, PollFlags};
45

56
/// `poll(self.fds, timeout)`—Wait for events on lists of file descriptors.
67
///
8+
/// Some platforms (those that don't support `ppoll`) don't support timeouts
9+
/// greater than `c_int::MAX` milliseconds; if an unsupported timeout is
10+
/// passed, this function fails with [`io::Errno::INVAL`].
11+
///
712
/// On macOS, `poll` doesn't work on fds for /dev/tty or /dev/null, however
813
/// [`select`] is available and does work on these fds.
914
///
@@ -32,6 +37,6 @@ pub use backend::event::poll_fd::{PollFd, PollFlags};
3237
/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=poll&section=2
3338
/// [illumos]: https://illumos.org/man/2/poll
3439
#[inline]
35-
pub fn poll(fds: &mut [PollFd<'_>], timeout: i32) -> io::Result<usize> {
40+
pub fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> {
3641
backend::event::syscalls::poll(fds, timeout)
3742
}

src/event/select.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77

88
#[cfg(any(linux_like, target_os = "wasi"))]
99
use crate::backend::c;
10+
use crate::event::Timespec;
1011
use crate::fd::RawFd;
1112
use crate::{backend, io};
1213
#[cfg(any(windows, target_os = "wasi"))]
1314
use core::mem::{align_of, size_of};
1415
#[cfg(any(windows, target_os = "wasi"))]
1516
use core::slice;
1617

17-
pub use crate::timespec::{Nsecs, Secs, Timespec};
18-
1918
/// wasi-libc's `fd_set` type. The libc bindings for it have private fields,
2019
/// so we redeclare it for ourselves so that we can access the fields. They're
2120
/// publicly exposed in wasi-libc.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ mod prctl;
359359
mod signal;
360360
#[cfg(any(
361361
feature = "fs",
362+
feature = "event",
362363
feature = "process",
363364
feature = "runtime",
364365
feature = "thread",

src/timespec.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use crate::backend::c;
88
#[allow(unused)]
99
use crate::ffi;
10+
#[cfg(not(fix_y2038))]
11+
use core::ptr::null;
1012

1113
/// `struct timespec`
12-
#[derive(Debug, Clone, Copy)]
14+
#[derive(Debug, Clone, Copy, Default)]
1315
#[repr(C)]
1416
pub struct Timespec {
1517
/// Seconds.
@@ -99,6 +101,14 @@ pub(crate) fn as_libc_timespec_mut_ptr(
99101
timespec.as_mut_ptr().cast::<c::timespec>()
100102
}
101103

104+
#[cfg(not(fix_y2038))]
105+
pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
106+
match timespec {
107+
None => null(),
108+
Some(timespec) => as_libc_timespec_ptr(timespec),
109+
}
110+
}
111+
102112
#[test]
103113
fn test_sizes() {
104114
assert_eq_size!(Secs, u64);

tests/event/poll.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use {rustix::event::poll, rustix::io::retry_on_intr};
88
#[cfg(not(any(windows, target_os = "wasi")))]
99
#[test]
1010
fn test_poll() {
11+
use rustix::event::Timespec;
1112
use rustix::io::{read, write};
1213
use rustix::pipe::pipe;
1314

@@ -17,7 +18,7 @@ fn test_poll() {
1718
assert_eq!(poll_fds[0].as_fd().as_raw_fd(), reader.as_fd().as_raw_fd());
1819

1920
// `poll` should say there's nothing ready to be read from the pipe.
20-
let num = retry_on_intr(|| poll(&mut poll_fds, 0)).unwrap();
21+
let num = retry_on_intr(|| poll(&mut poll_fds, Some(&Timespec::default()))).unwrap();
2122
assert_eq!(num, 0);
2223
assert!(poll_fds[0].revents().is_empty());
2324
assert_eq!(poll_fds[0].as_fd().as_raw_fd(), reader.as_fd().as_raw_fd());
@@ -26,7 +27,7 @@ fn test_poll() {
2627
assert_eq!(retry_on_intr(|| write(&writer, b"a")).unwrap(), 1);
2728

2829
// `poll` should now say there's data to be read.
29-
let num = retry_on_intr(|| poll(&mut poll_fds, -1)).unwrap();
30+
let num = retry_on_intr(|| poll(&mut poll_fds, None)).unwrap();
3031
assert_eq!(num, 1);
3132
assert_eq!(poll_fds[0].revents(), PollFlags::IN);
3233
assert_eq!(poll_fds[0].as_fd().as_raw_fd(), reader.as_fd().as_raw_fd());
@@ -43,7 +44,7 @@ fn test_poll() {
4344
assert_eq!(poll_fds[0].as_fd().as_raw_fd(), reader.as_fd().as_raw_fd());
4445

4546
// Poll should now say there's no more data to be read.
46-
let num = retry_on_intr(|| poll(&mut poll_fds, 0)).unwrap();
47+
let num = retry_on_intr(|| poll(&mut poll_fds, Some(&Timespec::default()))).unwrap();
4748
assert_eq!(num, 0);
4849
assert!(poll_fds[0].revents().is_empty());
4950
assert_eq!(poll_fds[0].as_fd().as_raw_fd(), reader.as_fd().as_raw_fd());

0 commit comments

Comments
 (0)