Skip to content

Commit 9d3dc92

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 d3fe1fd commit 9d3dc92

File tree

6 files changed

+95
-87
lines changed

6 files changed

+95
-87
lines changed

library/std/src/io/mod.rs

+54-39
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;
@@ -1389,25 +1388,6 @@ impl<'a> IoSliceMut<'a> {
13891388
}
13901389
}
13911390

1392-
/// Limits a slice of buffers to at most `n` buffers.
1393-
///
1394-
/// When the slice contains over `n` buffers, ensure that at least one
1395-
/// non-empty buffer is in the truncated slice, if there is one.
1396-
#[allow(dead_code)] // Not used on all platforms
1397-
#[inline]
1398-
pub(crate) fn limit_slices(bufs: &mut &mut [IoSliceMut<'a>], n: usize) {
1399-
if intrinsics::unlikely(bufs.len() > n) {
1400-
for (i, buf) in bufs.iter().enumerate() {
1401-
if !buf.is_empty() {
1402-
let len = cmp::min(bufs.len() - i, n);
1403-
*bufs = &mut take(bufs)[i..i + len];
1404-
return;
1405-
}
1406-
}
1407-
*bufs = &mut take(bufs)[..0];
1408-
}
1409-
}
1410-
14111391
/// Get the underlying bytes as a mutable slice with the original lifetime.
14121392
///
14131393
/// # Examples
@@ -1569,25 +1549,6 @@ impl<'a> IoSlice<'a> {
15691549
}
15701550
}
15711551

1572-
/// Limits a slice of buffers to at most `n` buffers.
1573-
///
1574-
/// When the slice contains over `n` buffers, ensure that at least one
1575-
/// non-empty buffer is in the truncated slice, if there is one.
1576-
#[allow(dead_code)] // Not used on all platforms
1577-
#[inline]
1578-
pub(crate) fn limit_slices(bufs: &mut &[IoSlice<'a>], n: usize) {
1579-
if intrinsics::unlikely(bufs.len() > n) {
1580-
for (i, buf) in bufs.iter().enumerate() {
1581-
if !buf.is_empty() {
1582-
let len = cmp::min(bufs.len() - i, n);
1583-
*bufs = &bufs[i..i + len];
1584-
return;
1585-
}
1586-
}
1587-
*bufs = &bufs[..0];
1588-
}
1589-
}
1590-
15911552
/// Get the underlying bytes as a slice with the original lifetime.
15921553
///
15931554
/// This doesn't borrow from `self`, so is less restrictive than calling
@@ -1625,6 +1586,60 @@ impl<'a> Deref for IoSlice<'a> {
16251586
}
16261587
}
16271588

1589+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1590+
/// least one buffer, even if empty.
1591+
///
1592+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1593+
/// buffer is in the truncated slice, if there is one.
1594+
#[allow(dead_code)] // Not used on all platforms
1595+
pub(crate) macro limit_slices($bufs:expr, $n:expr) {
1596+
'slices: {
1597+
let bufs: &[IoSlice<'_>] = $bufs;
1598+
let n: usize = $n;
1599+
// if bufs.len() > n || bufs.is_empty()
1600+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) > n - 1) {
1601+
for (i, buf) in bufs.iter().enumerate() {
1602+
if !buf.is_empty() {
1603+
let len = cmp::min(bufs.len() - i, n);
1604+
break 'slices &bufs[i..i + len];
1605+
}
1606+
}
1607+
// All buffers are empty. Since POSIX requires at least one buffer
1608+
// for [writev], but possibly bufs.is_empty(), return an empty write.
1609+
// [writev]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/writev.html
1610+
return Ok(0);
1611+
}
1612+
bufs
1613+
}
1614+
}
1615+
1616+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1617+
/// least one buffer, even if empty.
1618+
///
1619+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1620+
/// buffer is in the truncated slice, if there is one.
1621+
#[allow(dead_code)] // Not used on all platforms
1622+
pub(crate) macro limit_slices_mut($bufs:expr, $n:expr) {
1623+
'slices: {
1624+
let bufs: &mut [IoSliceMut<'_>] = $bufs;
1625+
let n: usize = $n;
1626+
// if bufs.len() > n || bufs.is_empty()
1627+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) > n - 1) {
1628+
for (i, buf) in bufs.iter().enumerate() {
1629+
if !buf.is_empty() {
1630+
let len = cmp::min(bufs.len() - i, n);
1631+
break 'slices &mut bufs[i..i + len];
1632+
}
1633+
}
1634+
// All buffers are empty. Since POSIX requires at least one buffer
1635+
// for [readv], but possibly bufs.is_empty(), return an empty read.
1636+
// [readv]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/readv.html
1637+
return Ok(0);
1638+
}
1639+
bufs
1640+
}
1641+
}
1642+
16281643
/// A trait for objects which are byte-oriented sinks.
16291644
///
16301645
/// Implementors of the `Write` trait are sometimes called 'writers'.

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

+4-4
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
})?;

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ impl Socket {
333333
self.recv_with_flags(buf, 0)
334334
}
335335

336-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
337-
IoSliceMut::limit_slices(&mut bufs, u32::MAX as usize);
336+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
337+
let bufs = io::limit_slices_mut!(bufs, u32::MAX as usize);
338338
let mut nread = 0;
339339
let mut flags = 0;
340340
let result = unsafe {
@@ -422,8 +422,8 @@ impl Socket {
422422
self.recv_from_with_flags(buf, c::MSG_PEEK)
423423
}
424424

425-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
426-
IoSlice::limit_slices(&mut bufs, u32::MAX as usize);
425+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
426+
let bufs = io::limit_slices!(bufs, u32::MAX as usize);
427427
let mut nwritten = 0;
428428
let result = unsafe {
429429
c::WSASend(

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

+4-4
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/pal/unix/fd.rs

+20-36
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ impl FileDesc {
104104
target_os = "vita",
105105
target_os = "nuttx"
106106
)))]
107-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
108-
IoSliceMut::limit_slices(&mut bufs, max_iov());
107+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
108+
let bufs = io::limit_slices_mut!(bufs, max_iov());
109109
let ret = cvt(unsafe {
110110
libc::readv(
111111
self.as_raw_fd(),
@@ -195,12 +195,8 @@ impl FileDesc {
195195
target_os = "netbsd",
196196
target_os = "openbsd", // OpenBSD 2.7
197197
))]
198-
pub fn read_vectored_at(
199-
&self,
200-
mut bufs: &mut [IoSliceMut<'_>],
201-
offset: u64,
202-
) -> io::Result<usize> {
203-
IoSliceMut::limit_slices(&mut bufs, max_iov());
198+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
199+
let bufs = io::limit_slices_mut!(bufs, max_iov());
204200
let ret = cvt(unsafe {
205201
libc::preadv(
206202
self.as_raw_fd(),
@@ -237,11 +233,7 @@ impl FileDesc {
237233
// passing 64-bits parameters to syscalls, so we fallback to the default
238234
// implementation if `preadv` is not available.
239235
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
240-
pub fn read_vectored_at(
241-
&self,
242-
mut bufs: &mut [IoSliceMut<'_>],
243-
offset: u64,
244-
) -> io::Result<usize> {
236+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
245237
super::weak::syscall!(
246238
fn preadv(
247239
fd: libc::c_int,
@@ -251,7 +243,7 @@ impl FileDesc {
251243
) -> isize;
252244
);
253245

254-
IoSliceMut::limit_slices(&mut bufs, max_iov());
246+
let bufs = io::limit_slices_mut!(bufs, max_iov());
255247
let ret = cvt(unsafe {
256248
preadv(
257249
self.as_raw_fd(),
@@ -267,11 +259,7 @@ impl FileDesc {
267259
// FIXME(#115199): Rust currently omits weak function definitions
268260
// and its metadata from LLVM IR.
269261
#[no_sanitize(cfi)]
270-
pub fn read_vectored_at(
271-
&self,
272-
mut bufs: &mut [IoSliceMut<'_>],
273-
offset: u64,
274-
) -> io::Result<usize> {
262+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
275263
super::weak::weak!(
276264
fn preadv64(
277265
fd: libc::c_int,
@@ -283,7 +271,7 @@ impl FileDesc {
283271

284272
match preadv64.get() {
285273
Some(preadv) => {
286-
IoSliceMut::limit_slices(&mut bufs, max_iov());
274+
let bufs = io::limit_slices_mut!(bufs, max_iov());
287275
let ret = cvt(unsafe {
288276
preadv(
289277
self.as_raw_fd(),
@@ -308,11 +296,7 @@ impl FileDesc {
308296
// These versions may be newer than the minimum supported versions of OS's we support so we must
309297
// use "weak" linking.
310298
#[cfg(target_vendor = "apple")]
311-
pub fn read_vectored_at(
312-
&self,
313-
mut bufs: &mut [IoSliceMut<'_>],
314-
offset: u64,
315-
) -> io::Result<usize> {
299+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
316300
super::weak::weak!(
317301
fn preadv(
318302
fd: libc::c_int,
@@ -324,7 +308,7 @@ impl FileDesc {
324308

325309
match preadv.get() {
326310
Some(preadv) => {
327-
IoSliceMut::limit_slices(&mut bufs, max_iov());
311+
let bufs = io::limit_slices_mut!(bufs, max_iov());
328312
let ret = cvt(unsafe {
329313
preadv(
330314
self.as_raw_fd(),
@@ -356,8 +340,8 @@ impl FileDesc {
356340
target_os = "vita",
357341
target_os = "nuttx"
358342
)))]
359-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
360-
IoSlice::limit_slices(&mut bufs, max_iov());
343+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
344+
let bufs = io::limit_slices!(bufs, max_iov());
361345
let ret = cvt(unsafe {
362346
libc::writev(
363347
self.as_raw_fd(),
@@ -426,8 +410,8 @@ impl FileDesc {
426410
target_os = "netbsd",
427411
target_os = "openbsd", // OpenBSD 2.7
428412
))]
429-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
430-
IoSlice::limit_slices(&mut bufs, max_iov());
413+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
414+
let bufs = io::limit_slices!(bufs, max_iov());
431415
let ret = cvt(unsafe {
432416
libc::pwritev(
433417
self.as_raw_fd(),
@@ -464,7 +448,7 @@ impl FileDesc {
464448
// passing 64-bits parameters to syscalls, so we fallback to the default
465449
// implementation if `pwritev` is not available.
466450
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
467-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
451+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
468452
super::weak::syscall!(
469453
fn pwritev(
470454
fd: libc::c_int,
@@ -474,7 +458,7 @@ impl FileDesc {
474458
) -> isize;
475459
);
476460

477-
IoSlice::limit_slices(&mut bufs, max_iov());
461+
let bufs = io::limit_slices!(bufs, max_iov());
478462
let ret = cvt(unsafe {
479463
pwritev(
480464
self.as_raw_fd(),
@@ -487,7 +471,7 @@ impl FileDesc {
487471
}
488472

489473
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
490-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
474+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
491475
super::weak::weak!(
492476
fn pwritev64(
493477
fd: libc::c_int,
@@ -499,7 +483,7 @@ impl FileDesc {
499483

500484
match pwritev64.get() {
501485
Some(pwritev) => {
502-
IoSlice::limit_slices(&mut bufs, max_iov());
486+
let bufs = io::limit_slices!(bufs, max_iov());
503487
let ret = cvt(unsafe {
504488
pwritev(
505489
self.as_raw_fd(),
@@ -524,7 +508,7 @@ impl FileDesc {
524508
// These versions may be newer than the minimum supported versions of OS's we support so we must
525509
// use "weak" linking.
526510
#[cfg(target_vendor = "apple")]
527-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
511+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
528512
super::weak::weak!(
529513
fn pwritev(
530514
fd: libc::c_int,
@@ -536,7 +520,7 @@ impl FileDesc {
536520

537521
match pwritev.get() {
538522
Some(pwritev) => {
539-
IoSlice::limit_slices(&mut bufs, max_iov());
523+
let bufs = io::limit_slices!(bufs, max_iov());
540524
let ret = cvt(unsafe {
541525
pwritev(
542526
self.as_raw_fd(),

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

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

0 commit comments

Comments
 (0)