Skip to content

Commit

Permalink
Fix timeout error when waiting for fd (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 Jul 31, 2024
1 parent f2e6912 commit 55f5dd0
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 55f5dd0

Please sign in to comment.