Skip to content

Commit 3f98c4d

Browse files
committed
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.
1 parent 580d7ab commit 3f98c4d

File tree

6 files changed

+110
-85
lines changed

6 files changed

+110
-85
lines changed

library/std/src/io/mod.rs

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,6 @@
297297
#[cfg(test)]
298298
mod tests;
299299

300-
use core::intrinsics;
301300
#[unstable(feature = "read_buf", issue = "78485")]
302301
pub use core::io::{BorrowedBuf, BorrowedCursor};
303302
use core::slice::memchr;
@@ -1429,25 +1428,6 @@ impl<'a> IoSliceMut<'a> {
14291428
}
14301429
}
14311430

1432-
/// Limits a slice of buffers to at most `n` buffers.
1433-
///
1434-
/// When the slice contains over `n` buffers, ensure that at least one
1435-
/// non-empty buffer is in the truncated slice, if there is one.
1436-
#[allow(dead_code)] // Not used on all platforms
1437-
#[inline]
1438-
pub(crate) fn limit_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize) {
1439-
if intrinsics::unlikely(bufs.len() > n) {
1440-
for (i, buf) in bufs.iter().enumerate() {
1441-
if !buf.is_empty() {
1442-
let len = cmp::min(bufs.len() - i, n);
1443-
*bufs = &mut take(bufs)[i..i + len];
1444-
return;
1445-
}
1446-
}
1447-
*bufs = &mut take(bufs)[..0];
1448-
}
1449-
}
1450-
14511431
/// Get the underlying bytes as a mutable slice with the original lifetime.
14521432
///
14531433
/// # Examples
@@ -1609,25 +1589,6 @@ impl<'a> IoSlice<'a> {
16091589
}
16101590
}
16111591

1612-
/// Limits a slice of buffers to at most `n` buffers.
1613-
///
1614-
/// When the slice contains over `n` buffers, ensure that at least one
1615-
/// non-empty buffer is in the truncated slice, if there is one.
1616-
#[allow(dead_code)] // Not used on all platforms
1617-
#[inline]
1618-
pub(crate) fn limit_slices(bufs: &mut &[IoSlice<'a>], n: usize) {
1619-
if intrinsics::unlikely(bufs.len() > n) {
1620-
for (i, buf) in bufs.iter().enumerate() {
1621-
if !buf.is_empty() {
1622-
let len = cmp::min(bufs.len() - i, n);
1623-
*bufs = &bufs[i..i + len];
1624-
return;
1625-
}
1626-
}
1627-
*bufs = &bufs[..0];
1628-
}
1629-
}
1630-
16311592
/// Get the underlying bytes as a slice with the original lifetime.
16321593
///
16331594
/// This doesn't borrow from `self`, so is less restrictive than calling
@@ -1665,6 +1626,60 @@ impl<'a> Deref for IoSlice<'a> {
16651626
}
16661627
}
16671628

1629+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1630+
/// least one buffer, even if empty.
1631+
///
1632+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1633+
/// buffer is in the truncated slice, if there is one.
1634+
#[allow(unused_macros)] // Not used on all platforms
1635+
pub(crate) macro limit_slices($bufs:expr, $n:expr) {
1636+
'slices: {
1637+
let bufs: &[IoSlice<'_>] = $bufs;
1638+
let n: usize = $n;
1639+
// if bufs.len() > n || bufs.is_empty()
1640+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) {
1641+
for (i, buf) in bufs.iter().enumerate() {
1642+
if !buf.is_empty() {
1643+
let len = cmp::min(bufs.len() - i, n);
1644+
break 'slices &bufs[i..i + len];
1645+
}
1646+
}
1647+
// All buffers are empty. Since POSIX requires at least one buffer
1648+
// for [writev], but possibly bufs.is_empty(), return an empty write.
1649+
// [writev]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/writev.html
1650+
return Ok(0);
1651+
}
1652+
bufs
1653+
}
1654+
}
1655+
1656+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1657+
/// least one buffer, even if empty.
1658+
///
1659+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1660+
/// buffer is in the truncated slice, if there is one.
1661+
#[allow(unused_macros)] // Not used on all platforms
1662+
pub(crate) macro limit_slices_mut($bufs:expr, $n:expr) {
1663+
'slices: {
1664+
let bufs: &mut [IoSliceMut<'_>] = $bufs;
1665+
let n: usize = $n;
1666+
// if bufs.len() > n || bufs.is_empty()
1667+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) {
1668+
for (i, buf) in bufs.iter().enumerate() {
1669+
if !buf.is_empty() {
1670+
let len = cmp::min(bufs.len() - i, n);
1671+
break 'slices &mut bufs[i..i + len];
1672+
}
1673+
}
1674+
// All buffers are empty. Since POSIX requires at least one buffer
1675+
// for [readv], but possibly bufs.is_empty(), return an empty read.
1676+
// [readv]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/readv.html
1677+
return Ok(0);
1678+
}
1679+
bufs
1680+
}
1681+
}
1682+
16681683
/// A trait for objects which are byte-oriented sinks.
16691684
///
16701685
/// Implementors of the `Write` trait are sometimes called 'writers'.

library/std/src/sys/fd/hermit.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ impl FileDesc {
3737
Ok(())
3838
}
3939

40-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
41-
IoSliceMut::limit_slices(&mut bufs, max_iov());
40+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
41+
let bufs = io::limit_slices_mut!(bufs, max_iov());
4242
let ret = cvt(unsafe {
4343
hermit_abi::readv(
4444
self.as_raw_fd(),
@@ -65,8 +65,8 @@ impl FileDesc {
6565
Ok(result as usize)
6666
}
6767

68-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
69-
IoSlice::limit_slices(&mut bufs, max_iov());
68+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
69+
let bufs = io::limit_slices!(bufs, max_iov());
7070
let ret = cvt(unsafe {
7171
hermit_abi::writev(
7272
self.as_raw_fd(),

library/std/src/sys/fd/unix.rs

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ impl FileDesc {
110110
target_os = "vita",
111111
target_os = "nuttx"
112112
)))]
113-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
114-
IoSliceMut::limit_slices(&mut bufs, max_iov());
113+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
114+
let bufs = io::limit_slices_mut!(bufs, max_iov());
115115
let ret = cvt(unsafe {
116116
libc::readv(
117117
self.as_raw_fd(),
@@ -201,12 +201,8 @@ impl FileDesc {
201201
target_os = "netbsd",
202202
target_os = "openbsd", // OpenBSD 2.7
203203
))]
204-
pub fn read_vectored_at(
205-
&self,
206-
mut bufs: &mut [IoSliceMut<'_>],
207-
offset: u64,
208-
) -> io::Result<usize> {
209-
IoSliceMut::limit_slices(&mut bufs, max_iov());
204+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
205+
let bufs = io::limit_slices_mut!(bufs, max_iov());
210206
let ret = cvt(unsafe {
211207
libc::preadv(
212208
self.as_raw_fd(),
@@ -243,11 +239,7 @@ impl FileDesc {
243239
// passing 64-bits parameters to syscalls, so we fallback to the default
244240
// implementation if `preadv` is not available.
245241
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
246-
pub fn read_vectored_at(
247-
&self,
248-
mut bufs: &mut [IoSliceMut<'_>],
249-
offset: u64,
250-
) -> io::Result<usize> {
242+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
251243
syscall!(
252244
fn preadv(
253245
fd: libc::c_int,
@@ -257,7 +249,7 @@ impl FileDesc {
257249
) -> isize;
258250
);
259251

260-
IoSliceMut::limit_slices(&mut bufs, max_iov());
252+
let bufs = io::limit_slices_mut!(bufs, max_iov());
261253
let ret = cvt(unsafe {
262254
preadv(
263255
self.as_raw_fd(),
@@ -272,7 +264,7 @@ impl FileDesc {
272264
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
273265
pub fn read_vectored_at(
274266
&self,
275-
mut bufs: &mut [IoSliceMut<'_>],
267+
bufs: &mut [IoSliceMut<'_>],
276268
offset: u64,
277269
) -> io::Result<usize> {
278270
weak!(
@@ -286,7 +278,7 @@ impl FileDesc {
286278

287279
match preadv64.get() {
288280
Some(preadv) => {
289-
IoSliceMut::limit_slices(&mut bufs, max_iov());
281+
let bufs = io::limit_slices_mut!(bufs, max_iov());
290282
let ret = cvt(unsafe {
291283
preadv(
292284
self.as_raw_fd(),
@@ -311,11 +303,7 @@ impl FileDesc {
311303
// These versions may be newer than the minimum supported versions of OS's we support so we must
312304
// use "weak" linking.
313305
#[cfg(target_vendor = "apple")]
314-
pub fn read_vectored_at(
315-
&self,
316-
mut bufs: &mut [IoSliceMut<'_>],
317-
offset: u64,
318-
) -> io::Result<usize> {
306+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
319307
weak!(
320308
fn preadv(
321309
fd: libc::c_int,
@@ -327,7 +315,7 @@ impl FileDesc {
327315

328316
match preadv.get() {
329317
Some(preadv) => {
330-
IoSliceMut::limit_slices(&mut bufs, max_iov());
318+
let bufs = io::limit_slices_mut!(bufs, max_iov());
331319
let ret = cvt(unsafe {
332320
preadv(
333321
self.as_raw_fd(),
@@ -359,8 +347,8 @@ impl FileDesc {
359347
target_os = "vita",
360348
target_os = "nuttx"
361349
)))]
362-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
363-
IoSlice::limit_slices(&mut bufs, max_iov());
350+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
351+
let bufs = io::limit_slices!(bufs, max_iov());
364352
let ret = cvt(unsafe {
365353
libc::writev(
366354
self.as_raw_fd(),
@@ -429,8 +417,8 @@ impl FileDesc {
429417
target_os = "netbsd",
430418
target_os = "openbsd", // OpenBSD 2.7
431419
))]
432-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
433-
IoSlice::limit_slices(&mut bufs, max_iov());
420+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
421+
let bufs = io::limit_slices!(bufs, max_iov());
434422
let ret = cvt(unsafe {
435423
libc::pwritev(
436424
self.as_raw_fd(),
@@ -467,7 +455,7 @@ impl FileDesc {
467455
// passing 64-bits parameters to syscalls, so we fallback to the default
468456
// implementation if `pwritev` is not available.
469457
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
470-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
458+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471459
syscall!(
472460
fn pwritev(
473461
fd: libc::c_int,
@@ -477,7 +465,7 @@ impl FileDesc {
477465
) -> isize;
478466
);
479467

480-
IoSlice::limit_slices(&mut bufs, max_iov());
468+
let bufs = io::limit_slices!(bufs, max_iov());
481469
let ret = cvt(unsafe {
482470
pwritev(
483471
self.as_raw_fd(),
@@ -490,7 +478,7 @@ impl FileDesc {
490478
}
491479

492480
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
493-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
481+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
494482
weak!(
495483
fn pwritev64(
496484
fd: libc::c_int,
@@ -502,7 +490,7 @@ impl FileDesc {
502490

503491
match pwritev64.get() {
504492
Some(pwritev) => {
505-
IoSlice::limit_slices(&mut bufs, max_iov());
493+
let bufs = io::limit_slices!(bufs, max_iov());
506494
let ret = cvt(unsafe {
507495
pwritev(
508496
self.as_raw_fd(),
@@ -527,7 +515,7 @@ impl FileDesc {
527515
// These versions may be newer than the minimum supported versions of OS's we support so we must
528516
// use "weak" linking.
529517
#[cfg(target_vendor = "apple")]
530-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
518+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
531519
weak!(
532520
fn pwritev(
533521
fd: libc::c_int,
@@ -539,7 +527,7 @@ impl FileDesc {
539527

540528
match pwritev.get() {
541529
Some(pwritev) => {
542-
IoSlice::limit_slices(&mut bufs, max_iov());
530+
let bufs = io::limit_slices!(bufs, max_iov());
543531
let ret = cvt(unsafe {
544532
pwritev(
545533
self.as_raw_fd(),

library/std/src/sys/fd/unix/tests.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ fn limit_vector_count() {
2121
bufs[IOV_MAX * 2] = IoSlice::new(b"world!");
2222
assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len())
2323
}
24+
25+
#[test]
26+
fn empty_vector() {
27+
let stdin = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(0) });
28+
assert_eq!(stdin.read_vectored(&mut []).unwrap(), 0);
29+
30+
let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
31+
assert_eq!(stdout.write_vectored(&[]).unwrap(), 0);
32+
}

library/std/src/sys/net/connection/socket/solid.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ impl Socket {
222222
self.recv_with_flags(buf, 0)
223223
}
224224

225-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
226-
IoSliceMut::limit_slices(&mut bufs, max_iov());
225+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
226+
let bufs = io::limit_slices_mut!(bufs, max_iov());
227227
let ret = cvt(unsafe {
228228
netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
229229
})?;
@@ -264,8 +264,8 @@ impl Socket {
264264
self.recv_from_with_flags(buf, MSG_PEEK)
265265
}
266266

267-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
268-
IoSlice::limit_slices(&mut bufs, max_iov());
267+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
268+
let bufs = io::limit_slices!(bufs, max_iov());
269269
let ret = cvt(unsafe {
270270
netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
271271
})?;

0 commit comments

Comments
 (0)