Skip to content
Open
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
25 changes: 13 additions & 12 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 Expand Up @@ -652,7 +653,7 @@ pub(crate) fn set_ip_add_membership_with_ifindex(
setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreqn)
}

#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
#[cfg(any(apple, target_os = "freebsd", linux_like, solarish, target_os = "aix"))]
#[inline]
pub(crate) fn set_ip_add_source_membership(
fd: BorrowedFd<'_>,
Expand All @@ -664,7 +665,7 @@ pub(crate) fn set_ip_add_source_membership(
setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_SOURCE_MEMBERSHIP, mreq_source)
}

#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
#[cfg(any(apple, target_os = "freebsd", linux_like, solarish, target_os = "aix"))]
#[inline]
pub(crate) fn set_ip_drop_source_membership(
fd: BorrowedFd<'_>,
Expand Down Expand Up @@ -1324,7 +1325,7 @@ fn to_ip_mreqn(multiaddr: &Ipv4Addr, address: &Ipv4Addr, ifindex: i32) -> c::ip_
}
}

#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
#[cfg(any(apple, target_os = "freebsd", linux_like, solarish, target_os = "aix"))]
#[inline]
fn to_imr_source(
multiaddr: &Ipv4Addr,
Expand Down
29 changes: 21 additions & 8 deletions src/backend/linux_raw/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,18 @@ fn duration_to_linux_sock_timeval(timeout: Option<Duration>) -> io::Result<__ker
if timeout == Duration::ZERO {
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_sock_timeval {
tv_sec: timeout.as_secs().try_into().unwrap_or(i64::MAX),
tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
tv_sec: sec.try_into().unwrap_or(i64::MAX),
tv_usec: usec as _,
};
if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
timeout.tv_usec = 1;
Expand All @@ -312,11 +319,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
4 changes: 2 additions & 2 deletions src/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ pub fn set_ip_add_membership_with_ifindex<Fd: AsFd>(
/// See the [module-level documentation] for more.
///
/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
#[cfg(any(apple, target_os = "freebsd", linux_like, solarish, target_os = "aix"))]
#[inline]
#[doc(alias = "IP_ADD_SOURCE_MEMBERSHIP")]
pub fn set_ip_add_source_membership<Fd: AsFd>(
Expand All @@ -1101,7 +1101,7 @@ pub fn set_ip_add_source_membership<Fd: AsFd>(
/// See the [module-level documentation] for more.
///
/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
#[cfg(any(apple, target_os = "freebsd", linux_like, solarish, target_os = "aix"))]
#[inline]
#[doc(alias = "IP_DROP_SOURCE_MEMBERSHIP")]
pub fn set_ip_drop_source_membership<Fd: AsFd>(
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(all(any(libc, target_os = "wasi"), not(windows)))]
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
75 changes: 75 additions & 0 deletions tests/event/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,78 @@ 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);
}

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

let (reader, _writer) = pipe().unwrap();
let nfds = reader.as_raw_fd() + 1;

// 999_999_000 nanoseconds -> (999_999_000 + 999)/1000 = 999_999 microseconds (valid)
let mut readfds1 = vec![FdSetElement::default(); fd_set_num_elements(1, nfds)];
fd_set_insert(&mut readfds1, reader.as_raw_fd());
let res1 = retry_on_intr(|| unsafe {
select(
nfds,
Some(&mut readfds1),
None,
None,
Some(&Timespec {
tv_sec: 0,
tv_nsec: 999_999_000,
}),
)
});
assert_eq!(res1.unwrap(), 0);

// 999_999_123 nanoseconds -> (999_999_123 + 999)/1000 = 1_000_000 microseconds.
// Before our fix, this produced tv_usec = 1_000_000, which failed with EINVAL (os error 22).
let mut readfds2 = vec![FdSetElement::default(); fd_set_num_elements(1, nfds)];
fd_set_insert(&mut readfds2, reader.as_raw_fd());
let res2 = retry_on_intr(|| unsafe {
select(
nfds,
Some(&mut readfds2),
None,
None,
Some(&Timespec {
tv_sec: 0,
tv_nsec: 999_999_123,
}),
)
});
assert_eq!(res2.unwrap(), 0);
}
Loading