Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions src/backend/libc/event/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,7 @@ pub(crate) unsafe fn select(
let timeout_data;
let timeout_ptr = match timeout {
Some(timeout) => {
// Convert from `Timespec` to `c::timeval`.
timeout_data = c::timeval {
tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
tv_usec: ((timeout.tv_nsec + 999) / 1000) as _,
};
timeout_data = timeout.to_timeval()?;
&timeout_data
}
None => null(),
Expand Down Expand Up @@ -324,11 +320,7 @@ pub(crate) unsafe fn select(
let timeout_data;
let timeout_ptr = match timeout {
Some(timeout) => {
// Convert from `Timespec` to `c::timeval`.
timeout_data = c::timeval {
tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
tv_usec: ((timeout.tv_nsec + 999) / 1000) as _,
};
timeout_data = timeout.to_timeval()?;
&timeout_data
}
None => null(),
Expand Down
9 changes: 1 addition & 8 deletions src/backend/libc/event/windows_syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,7 @@ pub(crate) fn select(
let timeout_data;
let timeout_ptr = match timeout {
Some(timeout) => {
// Convert from `Timespec` to `TIMEVAL`.
timeout_data = c::TIMEVAL {
tv_sec: timeout
.tv_sec
.try_into()
.map_err(|_| io::Errno::OPNOTSUPP)?,
tv_usec: ((timeout.tv_nsec + 999) / 1000) as _,
};
timeout_data = timeout.to_timeval()?;
&timeout_data
}
None => null(),
Expand Down
19 changes: 10 additions & 9 deletions src/backend/libc/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,19 @@ pub(crate) fn set_socket_timeout(
return Err(io::Errno::INVAL);
}

// Rust's musl libc bindings deprecated `time_t` while they
// transition to 64-bit `time_t`. What we want here is just
// “whatever type `timeval`'s `tv_sec` is”, so we're ok using
// the deprecated type.
let ts = crate::timespec::Timespec {
tv_sec: timeout
.as_secs()
.try_into()
.unwrap_or(crate::timespec::Secs::MAX),
tv_nsec: timeout.subsec_nanos() as _,
};
let (sec, usec) = ts.to_sec_usec().unwrap_or((crate::timespec::Secs::MAX, 0));
#[allow(deprecated)]
let tv_sec = timeout.as_secs().try_into().unwrap_or(c::time_t::MAX);

// `subsec_micros` rounds down, so we use `subsec_nanos` and
// manually round up.
let tv_sec = sec.try_into().unwrap_or(c::time_t::MAX);
let mut timeout = c::timeval {
tv_sec,
tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
tv_usec: usec as _,
};
if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
timeout.tv_usec = 1;
Expand Down
14 changes: 10 additions & 4 deletions src/backend/linux_raw/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,17 @@ fn duration_to_linux_old_timeval(timeout: Option<Duration>) -> io::Result<__kern
return Err(io::Errno::INVAL);
}

// `subsec_micros` rounds down, so we use `subsec_nanos` and
// manually round up.
let ts = crate::timespec::Timespec {
tv_sec: timeout
.as_secs()
.try_into()
.unwrap_or(crate::timespec::Secs::MAX),
tv_nsec: timeout.subsec_nanos() as _,
};
let (sec, usec) = ts.to_sec_usec().unwrap_or((crate::timespec::Secs::MAX, 0));
let mut timeout = __kernel_old_timeval {
tv_sec: timeout.as_secs().try_into().unwrap_or(c::c_long::MAX),
tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
tv_sec: sec.try_into().unwrap_or(c::c_long::MAX),
tv_usec: usec as _,
};
if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
timeout.tv_usec = 1;
Expand Down
55 changes: 55 additions & 0 deletions src/timespec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,41 @@ impl Timespec {
})
.and_then(|millis| c::c_int::try_from(millis).ok())
}

/// Convert `Timespec` to seconds and microseconds, rounding up fractional
/// microseconds and carrying any overflow into seconds.
#[inline]
pub(crate) fn to_sec_usec(&self) -> Option<(Secs, u32)> {
let mut sec = self.tv_sec;
let mut usec = (self.tv_nsec + 999) / 1000;
if usec >= 1_000_000 {
sec = sec.checked_add(1)?;
usec -= 1_000_000;
}
Some((sec, usec as u32))
}

/// Convert from `Timespec` to `c::timeval`, rounding up fractional
/// microseconds and carrying any overflow into seconds.
#[cfg(any(libc, target_os = "wasi"))]
pub(crate) fn to_timeval(&self) -> crate::io::Result<c::timeval> {
let (sec, usec) = self.to_sec_usec().ok_or(crate::io::Errno::INVAL)?;
Ok(c::timeval {
tv_sec: sec.try_into().map_err(|_| crate::io::Errno::INVAL)?,
tv_usec: usec as _,
})
}

/// Convert from `Timespec` to `c::TIMEVAL`, rounding up fractional
/// microseconds and carrying any overflow into seconds.
#[cfg(windows)]
pub(crate) fn to_timeval(&self) -> crate::io::Result<c::TIMEVAL> {
let (sec, usec) = self.to_sec_usec().ok_or(crate::io::Errno::OPNOTSUPP)?;
Ok(c::TIMEVAL {
tv_sec: sec.try_into().map_err(|_| crate::io::Errno::OPNOTSUPP)?,
tv_usec: usec as _,
})
}
}

impl TryFrom<Timespec> for Duration {
Expand Down Expand Up @@ -396,6 +431,26 @@ mod tests {
assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64);
}

#[cfg(any(libc, target_os = "wasi", windows))]
#[test]
fn test_to_timeval() {
let ts = Timespec {
tv_sec: 4,
tv_nsec: 999_999_500,
};
let tv = ts.to_timeval().unwrap();
assert_eq!(tv.tv_sec, 5);
assert_eq!(tv.tv_usec, 0);

let ts2 = Timespec {
tv_sec: 2,
tv_nsec: 500_000_000,
};
let tv2 = ts2.to_timeval().unwrap();
assert_eq!(tv2.tv_sec, 2);
assert_eq!(tv2.tv_usec, 500_000);
}

// Test that our workarounds are needed.
#[cfg(fix_y2038)]
#[test]
Expand Down
30 changes: 30 additions & 0 deletions tests/event/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,33 @@ fn test_select_iter() {
fn fd_set_contains(fds: &[FdSetElement], fd: RawFd) -> bool {
FdSetIter::new(fds).any(|x| x == fd)
}

#[cfg(feature = "pipe")]
#[cfg(not(windows))]
#[test]
fn test_select_near_second_boundary_timeout() {
use rustix::pipe::pipe;

let (reader, _writer) = pipe().unwrap();
let nfds = reader.as_raw_fd() + 1;
let mut readfds = vec![FdSetElement::default(); fd_set_num_elements(1, nfds)];
fd_set_insert(&mut readfds, reader.as_raw_fd());

// 999_999_500 ns will ceiling-divide to 1_000_000 microseconds.
// Ensure that it rolls over to tv_sec = 1, tv_usec = 0 instead of tv_usec = 1_000_000
// which fails on macOS with EINVAL.
let num = retry_on_intr(|| unsafe {
select(
nfds,
Some(&mut readfds),
None,
None,
Some(&Timespec {
tv_sec: 0,
tv_nsec: 999_999_500,
}),
)
})
.unwrap();
assert_eq!(num, 0);
}
Loading