From ab97e9ca2b3c3ddc45eab3984017adc8b133918f Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Sun, 23 Mar 2025 23:02:42 -0700 Subject: [PATCH 1/2] Ensure non-empty buffers for large vectored I/O `readv` and `writev` are constrained by a platform-specific upper bound on the number of buffers which can be passed. Currently, `read_vectored` and `write_vectored` implementations simply truncate to this limit when larger. However, when the only non-empty buffers are at indices above this limit, they will erroneously return `Ok(0)`. Instead, slice the buffers starting at the first non-empty buffer. This trades a conditional move for a branch, so it's barely a penalty in the common case. The new method `limit_slices` on `IoSlice` and `IoSliceMut` may be generally useful to users like `advance_slices` is, but I have left it as `pub(crate)` for now. --- library/std/src/io/mod.rs | 39 +++++++++++ library/std/src/sys/fd/hermit.rs | 11 ++-- library/std/src/sys/fd/unix.rs | 66 +++++++++++++------ library/std/src/sys/fd/unix/tests.rs | 17 ++++- .../src/sys/net/connection/socket/solid.rs | 20 ++---- 5 files changed, 112 insertions(+), 41 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 314cbb45d49e2..fd885c19f9298 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -297,6 +297,7 @@ #[cfg(test)] mod tests; +use core::intrinsics; #[unstable(feature = "read_buf", issue = "78485")] pub use core::io::{BorrowedBuf, BorrowedCursor}; use core::slice::memchr; @@ -1388,6 +1389,25 @@ impl<'a> IoSliceMut<'a> { } } + /// Limits a slice of buffers to at most `n` buffers. + /// + /// When the slice contains over `n` buffers, ensure that at least one + /// non-empty buffer is in the truncated slice, if there is one. + #[allow(dead_code)] // Not used on all platforms + #[inline] + pub(crate) fn limit_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize) { + if intrinsics::unlikely(bufs.len() > n) { + for (i, buf) in bufs.iter().enumerate() { + if !buf.is_empty() { + let len = cmp::min(bufs.len() - i, n); + *bufs = &mut take(bufs)[i..i + len]; + return; + } + } + *bufs = &mut take(bufs)[..0]; + } + } + /// Get the underlying bytes as a mutable slice with the original lifetime. /// /// # Examples @@ -1549,6 +1569,25 @@ impl<'a> IoSlice<'a> { } } + /// Limits a slice of buffers to at most `n` buffers. + /// + /// When the slice contains over `n` buffers, ensure that at least one + /// non-empty buffer is in the truncated slice, if there is one. + #[allow(dead_code)] // Not used on all platforms + #[inline] + pub(crate) fn limit_slices(bufs: &mut &[IoSlice<'a>], n: usize) { + if intrinsics::unlikely(bufs.len() > n) { + for (i, buf) in bufs.iter().enumerate() { + if !buf.is_empty() { + let len = cmp::min(bufs.len() - i, n); + *bufs = &bufs[i..i + len]; + return; + } + } + *bufs = &bufs[..0]; + } + } + /// Get the underlying bytes as a slice with the original lifetime. /// /// This doesn't borrow from `self`, so is less restrictive than calling diff --git a/library/std/src/sys/fd/hermit.rs b/library/std/src/sys/fd/hermit.rs index 7e8ba065f1b96..7d9dc51014102 100644 --- a/library/std/src/sys/fd/hermit.rs +++ b/library/std/src/sys/fd/hermit.rs @@ -1,6 +1,5 @@ #![unstable(reason = "not public", issue = "none", feature = "fd")] -use crate::cmp; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, SeekFrom}; use crate::os::hermit::hermit_abi; use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; @@ -38,12 +37,13 @@ impl FileDesc { Ok(()) } - pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { hermit_abi::readv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut hermit_abi::iovec as *const hermit_abi::iovec, - cmp::min(bufs.len(), max_iov()), + bufs.len(), ) })?; Ok(ret as usize) @@ -65,12 +65,13 @@ impl FileDesc { Ok(result as usize) } - pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { hermit_abi::writev( self.as_raw_fd(), bufs.as_ptr() as *const hermit_abi::iovec, - cmp::min(bufs.len(), max_iov()), + bufs.len(), ) })?; Ok(ret as usize) diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs index 2042ea2c73d00..3b00b5013e15e 100644 --- a/library/std/src/sys/fd/unix.rs +++ b/library/std/src/sys/fd/unix.rs @@ -108,12 +108,13 @@ impl FileDesc { target_os = "vita", target_os = "nuttx" )))] - pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { libc::readv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, ) })?; Ok(ret as usize) @@ -198,12 +199,17 @@ impl FileDesc { target_os = "netbsd", target_os = "openbsd", // OpenBSD 2.7 ))] - pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + pub fn read_vectored_at( + &self, + mut bufs: &mut [IoSliceMut<'_>], + offset: u64, + ) -> io::Result { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { libc::preadv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -235,7 +241,11 @@ impl FileDesc { // passing 64-bits parameters to syscalls, so we fallback to the default // implementation if `preadv` is not available. #[cfg(all(target_os = "android", target_pointer_width = "64"))] - pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + pub fn read_vectored_at( + &self, + mut bufs: &mut [IoSliceMut<'_>], + offset: u64, + ) -> io::Result { syscall!( fn preadv( fd: libc::c_int, @@ -245,11 +255,12 @@ impl FileDesc { ) -> isize; ); + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -260,7 +271,11 @@ impl FileDesc { // FIXME(#115199): Rust currently omits weak function definitions // and its metadata from LLVM IR. #[no_sanitize(cfi)] - pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + pub fn read_vectored_at( + &self, + mut bufs: &mut [IoSliceMut<'_>], + offset: u64, + ) -> io::Result { weak!( fn preadv64( fd: libc::c_int, @@ -272,11 +287,12 @@ impl FileDesc { match preadv64.get() { Some(preadv) => { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -296,7 +312,11 @@ impl FileDesc { // These versions may be newer than the minimum supported versions of OS's we support so we must // use "weak" linking. #[cfg(target_vendor = "apple")] - pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + pub fn read_vectored_at( + &self, + mut bufs: &mut [IoSliceMut<'_>], + offset: u64, + ) -> io::Result { weak!( fn preadv( fd: libc::c_int, @@ -308,11 +328,12 @@ impl FileDesc { match preadv.get() { Some(preadv) => { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -339,12 +360,13 @@ impl FileDesc { target_os = "vita", target_os = "nuttx" )))] - pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { libc::writev( self.as_raw_fd(), bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, ) })?; Ok(ret as usize) @@ -408,12 +430,13 @@ impl FileDesc { target_os = "netbsd", target_os = "openbsd", // OpenBSD 2.7 ))] - pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { libc::pwritev( self.as_raw_fd(), bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -445,7 +468,7 @@ impl FileDesc { // passing 64-bits parameters to syscalls, so we fallback to the default // implementation if `pwritev` is not available. #[cfg(all(target_os = "android", target_pointer_width = "64"))] - pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { syscall!( fn pwritev( fd: libc::c_int, @@ -455,11 +478,12 @@ impl FileDesc { ) -> isize; ); + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -467,7 +491,7 @@ impl FileDesc { } #[cfg(all(target_os = "android", target_pointer_width = "32"))] - pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { weak!( fn pwritev64( fd: libc::c_int, @@ -479,11 +503,12 @@ impl FileDesc { match pwritev64.get() { Some(pwritev) => { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; @@ -503,7 +528,7 @@ impl FileDesc { // These versions may be newer than the minimum supported versions of OS's we support so we must // use "weak" linking. #[cfg(target_vendor = "apple")] - pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { weak!( fn pwritev( fd: libc::c_int, @@ -515,11 +540,12 @@ impl FileDesc { match pwritev.get() { Some(pwritev) => { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), bufs.as_ptr() as *const libc::iovec, - cmp::min(bufs.len(), max_iov()) as libc::c_int, + bufs.len() as libc::c_int, offset as _, ) })?; diff --git a/library/std/src/sys/fd/unix/tests.rs b/library/std/src/sys/fd/unix/tests.rs index fcd66c71707d9..b51f2b620d1d9 100644 --- a/library/std/src/sys/fd/unix/tests.rs +++ b/library/std/src/sys/fd/unix/tests.rs @@ -1,12 +1,23 @@ use core::mem::ManuallyDrop; -use super::FileDesc; +use super::{FileDesc, max_iov}; use crate::io::IoSlice; use crate::os::unix::io::FromRawFd; #[test] fn limit_vector_count() { + const IOV_MAX: usize = max_iov(); + let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) }); - let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::>(); - assert!(stdout.write_vectored(&bufs).is_ok()); + let mut bufs = vec![IoSlice::new(&[]); IOV_MAX * 2 + 1]; + assert_eq!(stdout.write_vectored(&bufs).unwrap(), 0); + + // The slice of buffers is truncated to IOV_MAX buffers. However, since the + // first IOV_MAX buffers are all empty, it is sliced starting at the first + // non-empty buffer to avoid erroneously returning Ok(0). In this case, that + // starts with the b"hello" buffer and ends just before the b"world!" + // buffer. + bufs[IOV_MAX] = IoSlice::new(b"hello"); + bufs[IOV_MAX * 2] = IoSlice::new(b"world!"); + assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len()) } diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index 94bb605c1007c..3b5dfb5d0aabd 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -9,7 +9,7 @@ use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Owne use crate::sys::abi; use crate::sys_common::{FromInner, IntoInner}; use crate::time::Duration; -use crate::{cmp, mem, ptr, str}; +use crate::{mem, ptr, str}; pub(super) mod netc { pub use crate::sys::abi::sockets::*; @@ -222,13 +222,10 @@ impl Socket { self.recv_with_flags(buf, 0) } - pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { + IoSliceMut::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { - netc::readv( - self.as_raw_fd(), - bufs.as_ptr() as *const netc::iovec, - cmp::min(bufs.len(), max_iov()) as c_int, - ) + netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int) })?; Ok(ret as usize) } @@ -267,13 +264,10 @@ impl Socket { self.recv_from_with_flags(buf, MSG_PEEK) } - pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { + IoSlice::limit_slices(&mut bufs, max_iov()); let ret = cvt(unsafe { - netc::writev( - self.as_raw_fd(), - bufs.as_ptr() as *const netc::iovec, - cmp::min(bufs.len(), max_iov()) as c_int, - ) + netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int) })?; Ok(ret as usize) } From 60ddaa69dc129e9636bdac8417afee32c46b3a5a Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Sat, 29 Mar 2025 21:31:44 -0700 Subject: [PATCH 2/2] Ensure at least one buffer for vectored I/O POSIX requires at least one buffer passed to readv and writev, but we allow the user to pass an empty slice of buffers. In this case, return a zero-length read or write. --- library/std/src/io/mod.rs | 93 +++++++++++-------- library/std/src/sys/fd/hermit.rs | 8 +- library/std/src/sys/fd/unix.rs | 56 ++++------- library/std/src/sys/fd/unix/tests.rs | 9 ++ .../src/sys/net/connection/socket/solid.rs | 8 +- .../src/sys/net/connection/socket/windows.rs | 25 +++-- 6 files changed, 110 insertions(+), 89 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index fd885c19f9298..5cf0ea8d5c90e 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -297,7 +297,6 @@ #[cfg(test)] mod tests; -use core::intrinsics; #[unstable(feature = "read_buf", issue = "78485")] pub use core::io::{BorrowedBuf, BorrowedCursor}; use core::slice::memchr; @@ -1389,25 +1388,6 @@ impl<'a> IoSliceMut<'a> { } } - /// Limits a slice of buffers to at most `n` buffers. - /// - /// When the slice contains over `n` buffers, ensure that at least one - /// non-empty buffer is in the truncated slice, if there is one. - #[allow(dead_code)] // Not used on all platforms - #[inline] - pub(crate) fn limit_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize) { - if intrinsics::unlikely(bufs.len() > n) { - for (i, buf) in bufs.iter().enumerate() { - if !buf.is_empty() { - let len = cmp::min(bufs.len() - i, n); - *bufs = &mut take(bufs)[i..i + len]; - return; - } - } - *bufs = &mut take(bufs)[..0]; - } - } - /// Get the underlying bytes as a mutable slice with the original lifetime. /// /// # Examples @@ -1569,25 +1549,6 @@ impl<'a> IoSlice<'a> { } } - /// Limits a slice of buffers to at most `n` buffers. - /// - /// When the slice contains over `n` buffers, ensure that at least one - /// non-empty buffer is in the truncated slice, if there is one. - #[allow(dead_code)] // Not used on all platforms - #[inline] - pub(crate) fn limit_slices(bufs: &mut &[IoSlice<'a>], n: usize) { - if intrinsics::unlikely(bufs.len() > n) { - for (i, buf) in bufs.iter().enumerate() { - if !buf.is_empty() { - let len = cmp::min(bufs.len() - i, n); - *bufs = &bufs[i..i + len]; - return; - } - } - *bufs = &bufs[..0]; - } - } - /// Get the underlying bytes as a slice with the original lifetime. /// /// This doesn't borrow from `self`, so is less restrictive than calling @@ -1625,6 +1586,60 @@ impl<'a> Deref for IoSlice<'a> { } } +/// Limits a slice of buffers to at most `n` buffers and ensures that it has at +/// least one buffer, even if empty. +/// +/// When the slice contains over `n` buffers, ensure that at least one non-empty +/// buffer is in the truncated slice, if there is one. +#[allow(unused_macros)] // Not used on all platforms +pub(crate) macro limit_slices($bufs:expr, $n:expr) { + 'slices: { + let bufs: &[IoSlice<'_>] = $bufs; + let n: usize = $n; + // if bufs.len() > n || bufs.is_empty() + if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) { + for (i, buf) in bufs.iter().enumerate() { + if !buf.is_empty() { + let len = cmp::min(bufs.len() - i, n); + break 'slices &bufs[i..i + len]; + } + } + // All buffers are empty. Since POSIX requires at least one buffer + // for [writev], but possibly bufs.is_empty(), return an empty write. + // [writev]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/writev.html + return Ok(0); + } + bufs + } +} + +/// Limits a slice of buffers to at most `n` buffers and ensures that it has at +/// least one buffer, even if empty. +/// +/// When the slice contains over `n` buffers, ensure that at least one non-empty +/// buffer is in the truncated slice, if there is one. +#[allow(unused_macros)] // Not used on all platforms +pub(crate) macro limit_slices_mut($bufs:expr, $n:expr) { + 'slices: { + let bufs: &mut [IoSliceMut<'_>] = $bufs; + let n: usize = $n; + // if bufs.len() > n || bufs.is_empty() + if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) { + for (i, buf) in bufs.iter().enumerate() { + if !buf.is_empty() { + let len = cmp::min(bufs.len() - i, n); + break 'slices &mut bufs[i..i + len]; + } + } + // All buffers are empty. Since POSIX requires at least one buffer + // for [readv], but possibly bufs.is_empty(), return an empty read. + // [readv]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/readv.html + return Ok(0); + } + bufs + } +} + /// A trait for objects which are byte-oriented sinks. /// /// Implementors of the `Write` trait are sometimes called 'writers'. diff --git a/library/std/src/sys/fd/hermit.rs b/library/std/src/sys/fd/hermit.rs index 7d9dc51014102..793b2bd644885 100644 --- a/library/std/src/sys/fd/hermit.rs +++ b/library/std/src/sys/fd/hermit.rs @@ -37,8 +37,8 @@ impl FileDesc { Ok(()) } - pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { hermit_abi::readv( self.as_raw_fd(), @@ -65,8 +65,8 @@ impl FileDesc { Ok(result as usize) } - pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { - IoSlice::limit_slices(&mut bufs, max_iov()); + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { hermit_abi::writev( self.as_raw_fd(), diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs index 3b00b5013e15e..26b322b597ac4 100644 --- a/library/std/src/sys/fd/unix.rs +++ b/library/std/src/sys/fd/unix.rs @@ -108,8 +108,8 @@ impl FileDesc { target_os = "vita", target_os = "nuttx" )))] - pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { libc::readv( self.as_raw_fd(), @@ -199,12 +199,8 @@ impl FileDesc { target_os = "netbsd", target_os = "openbsd", // OpenBSD 2.7 ))] - pub fn read_vectored_at( - &self, - mut bufs: &mut [IoSliceMut<'_>], - offset: u64, - ) -> io::Result { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { libc::preadv( self.as_raw_fd(), @@ -241,11 +237,7 @@ impl FileDesc { // passing 64-bits parameters to syscalls, so we fallback to the default // implementation if `preadv` is not available. #[cfg(all(target_os = "android", target_pointer_width = "64"))] - pub fn read_vectored_at( - &self, - mut bufs: &mut [IoSliceMut<'_>], - offset: u64, - ) -> io::Result { + pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { syscall!( fn preadv( fd: libc::c_int, @@ -255,7 +247,7 @@ impl FileDesc { ) -> isize; ); - IoSliceMut::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), @@ -271,11 +263,7 @@ impl FileDesc { // FIXME(#115199): Rust currently omits weak function definitions // and its metadata from LLVM IR. #[no_sanitize(cfi)] - pub fn read_vectored_at( - &self, - mut bufs: &mut [IoSliceMut<'_>], - offset: u64, - ) -> io::Result { + pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { weak!( fn preadv64( fd: libc::c_int, @@ -287,7 +275,7 @@ impl FileDesc { match preadv64.get() { Some(preadv) => { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), @@ -312,11 +300,7 @@ impl FileDesc { // These versions may be newer than the minimum supported versions of OS's we support so we must // use "weak" linking. #[cfg(target_vendor = "apple")] - pub fn read_vectored_at( - &self, - mut bufs: &mut [IoSliceMut<'_>], - offset: u64, - ) -> io::Result { + pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { weak!( fn preadv( fd: libc::c_int, @@ -328,7 +312,7 @@ impl FileDesc { match preadv.get() { Some(preadv) => { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { preadv( self.as_raw_fd(), @@ -360,8 +344,8 @@ impl FileDesc { target_os = "vita", target_os = "nuttx" )))] - pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { - IoSlice::limit_slices(&mut bufs, max_iov()); + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { libc::writev( self.as_raw_fd(), @@ -430,8 +414,8 @@ impl FileDesc { target_os = "netbsd", target_os = "openbsd", // OpenBSD 2.7 ))] - pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { - IoSlice::limit_slices(&mut bufs, max_iov()); + pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { libc::pwritev( self.as_raw_fd(), @@ -468,7 +452,7 @@ impl FileDesc { // passing 64-bits parameters to syscalls, so we fallback to the default // implementation if `pwritev` is not available. #[cfg(all(target_os = "android", target_pointer_width = "64"))] - pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { syscall!( fn pwritev( fd: libc::c_int, @@ -478,7 +462,7 @@ impl FileDesc { ) -> isize; ); - IoSlice::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), @@ -491,7 +475,7 @@ impl FileDesc { } #[cfg(all(target_os = "android", target_pointer_width = "32"))] - pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { weak!( fn pwritev64( fd: libc::c_int, @@ -503,7 +487,7 @@ impl FileDesc { match pwritev64.get() { Some(pwritev) => { - IoSlice::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), @@ -528,7 +512,7 @@ impl FileDesc { // These versions may be newer than the minimum supported versions of OS's we support so we must // use "weak" linking. #[cfg(target_vendor = "apple")] - pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { weak!( fn pwritev( fd: libc::c_int, @@ -540,7 +524,7 @@ impl FileDesc { match pwritev.get() { Some(pwritev) => { - IoSlice::limit_slices(&mut bufs, max_iov()); + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { pwritev( self.as_raw_fd(), diff --git a/library/std/src/sys/fd/unix/tests.rs b/library/std/src/sys/fd/unix/tests.rs index b51f2b620d1d9..9f82b65c00556 100644 --- a/library/std/src/sys/fd/unix/tests.rs +++ b/library/std/src/sys/fd/unix/tests.rs @@ -21,3 +21,12 @@ fn limit_vector_count() { bufs[IOV_MAX * 2] = IoSlice::new(b"world!"); assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len()) } + +#[test] +fn empty_vector() { + let stdin = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(0) }); + assert_eq!(stdin.read_vectored(&mut []).unwrap(), 0); + + let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) }); + assert_eq!(stdout.write_vectored(&[]).unwrap(), 0); +} diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index 3b5dfb5d0aabd..0b979b3c04052 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -222,8 +222,8 @@ impl Socket { self.recv_with_flags(buf, 0) } - pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result { - IoSliceMut::limit_slices(&mut bufs, max_iov()); + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let bufs = io::limit_slices_mut!(bufs, max_iov()); let ret = cvt(unsafe { netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int) })?; @@ -264,8 +264,8 @@ impl Socket { self.recv_from_with_flags(buf, MSG_PEEK) } - pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result { - IoSlice::limit_slices(&mut bufs, max_iov()); + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + let bufs = io::limit_slices!(bufs, max_iov()); let ret = cvt(unsafe { netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int) })?; diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs index ce975bb2289c2..d6dafb63e38cb 100644 --- a/library/std/src/sys/net/connection/socket/windows.rs +++ b/library/std/src/sys/net/connection/socket/windows.rs @@ -299,8 +299,6 @@ impl Socket { } fn recv_with_flags(&self, mut buf: BorrowedCursor<'_>, flags: c_int) -> io::Result<()> { - // On unix when a socket is shut down all further reads return 0, so we - // do the same on windows to map a shut down socket to returning EOF. let length = cmp::min(buf.capacity(), i32::MAX as usize) as i32; let result = unsafe { c::recv(self.as_raw(), buf.as_mut().as_mut_ptr() as *mut _, length, flags) }; @@ -309,6 +307,9 @@ impl Socket { c::SOCKET_ERROR => { let error = unsafe { c::WSAGetLastError() }; + // On Unix when a socket is shut down, all further reads return + // 0, so we do the same on Windows to map a shut down socket to + // returning EOF. if error == c::WSAESHUTDOWN { Ok(()) } else { @@ -333,8 +334,11 @@ impl Socket { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - // On unix when a socket is shut down all further reads return 0, so we - // do the same on windows to map a shut down socket to returning EOF. + // WSARecv requires at least one buffer. + if bufs.is_empty() { + return Ok(0); + } + let length = cmp::min(bufs.len(), u32::MAX as usize) as u32; let mut nread = 0; let mut flags = 0; @@ -355,6 +359,9 @@ impl Socket { _ => { let error = unsafe { c::WSAGetLastError() }; + // On Unix when a socket is shut down, all further reads return + // 0, so we do the same on Windows to map a shut down socket to + // returning EOF. if error == c::WSAESHUTDOWN { Ok(0) } else { @@ -384,8 +391,6 @@ impl Socket { let mut addrlen = size_of_val(&storage) as netc::socklen_t; let length = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; - // On unix when a socket is shut down all further reads return 0, so we - // do the same on windows to map a shut down socket to returning EOF. let result = unsafe { c::recvfrom( self.as_raw(), @@ -401,6 +406,9 @@ impl Socket { c::SOCKET_ERROR => { let error = unsafe { c::WSAGetLastError() }; + // On Unix when a socket is shut down, all further reads return + // 0, so we do the same on Windows to map a shut down socket to + // returning EOF. if error == c::WSAESHUTDOWN { Ok((0, unsafe { socket_addr_from_c(&storage, addrlen as usize)? })) } else { @@ -420,6 +428,11 @@ impl Socket { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + // WSASend requires at least one buffer. + if bufs.is_empty() { + return Ok(0); + } + let length = cmp::min(bufs.len(), u32::MAX as usize) as u32; let mut nwritten = 0; let result = unsafe {