Skip to content

Commit

Permalink
Clamp timeouts for POSIX
Browse files Browse the repository at this point in the history
The previous timeout calculation overflowed at a cast and resulted in
negative timeouts. These in turn resulted in errors from ppoll.
  • Loading branch information
sirhcel committed Aug 1, 2024
1 parent f2e6912 commit bd2bf99
Showing 1 changed file with 43 additions and 17 deletions.
60 changes: 43 additions & 17 deletions src/posix/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ use std::os::unix::io::RawFd;
use std::slice;
use std::time::Duration;

use nix::libc::c_int;
use nix::poll::{PollFd, PollFlags};
#[cfg(target_os = "linux")]
use nix::sys::signal::SigSet;
#[cfg(target_os = "linux")]
use nix::sys::time::{TimeSpec, TimeValLike};
use nix::sys::time::TimeSpec;

pub fn wait_read_fd(fd: RawFd, timeout: Duration) -> io::Result<()> {
wait_fd(fd, PollFlags::POLLIN, timeout)
Expand All @@ -24,23 +25,12 @@ fn wait_fd(fd: RawFd, events: PollFlags, timeout: Duration) -> io::Result<()> {

let mut fd = PollFd::new(fd, events);

let milliseconds =
timeout.as_secs() as i64 * 1000 + i64::from(timeout.subsec_nanos()) / 1_000_000;
#[cfg(target_os = "linux")]
let wait_res = {
let timespec = TimeSpec::milliseconds(milliseconds);
nix::poll::ppoll(
slice::from_mut(&mut fd),
Some(timespec),
Some(SigSet::empty()),
)
};
#[cfg(not(target_os = "linux"))]
let wait_res = nix::poll::poll(slice::from_mut(&mut fd), milliseconds as nix::libc::c_int);

let wait = match wait_res {
let wait = match poll_clamped(&mut fd, timeout) {
Ok(r) => r,
Err(e) => return Err(io::Error::from(crate::Error::from(e))),
Err(e) => {
dbg!(e);
return Err(io::Error::from(crate::Error::from(e)));
}
};
// All errors generated by poll or ppoll are already caught by the nix wrapper around libc, so
// here we only need to check if there's at least 1 event
Expand All @@ -63,3 +53,39 @@ fn wait_fd(fd: RawFd, events: PollFlags, timeout: Duration) -> io::Result<()> {

Err(io::Error::new(io::ErrorKind::Other, EIO.desc()))
}

/// Poll with a duration clamped to the maximum value representable by the `TimeSpec` used by
/// `ppoll`.
#[cfg(target_os = "linux")]
fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int> {
use nix::libc::c_long;
use nix::sys::time::time_t;

// We need to clamp manually as TimeSpec::from_duration translates durations with more than
// i64::MAX seconds to negative timespans. This happens due to casting to i64 and is still the
// case as of nix 0.29.
let secs_limit = time_t::MAX as u64;
let secs = timeout.as_secs();
let spec = if secs <= secs_limit {
TimeSpec::new(secs as time_t, timeout.subsec_nanos() as c_long)
} else {
TimeSpec::new(time_t::MAX, 999_999_999)
};

nix::poll::ppoll(slice::from_mut(fd), Some(spec), Some(SigSet::empty()))
}

// Poll with a duration clamped to the maximum millisecond value representable by the `c_int` used
// by `poll`.
#[cfg(not(target_os = "linux"))]
fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int> {
let secs_limit = (c_int::MAX as u64) / 1000;
let secs = timeout.as_secs();
let millis = if secs <= secs_limit {
secs as c_int * 1000 + timeout.subsec_millis() as c_int
} else {
c_int::MAX
};

nix::poll::poll(slice::from_mut(fd), millis)
}

0 comments on commit bd2bf99

Please sign in to comment.