Skip to content

Commit 60ddaa6

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 ab97e9c commit 60ddaa6

File tree

6 files changed

+110
-89
lines changed

6 files changed

+110
-89
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(unused_macros)] // 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) {
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(unused_macros)] // 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) {
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/fd/hermit.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/fd/unix.rs

+20-36
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ impl FileDesc {
108108
target_os = "vita",
109109
target_os = "nuttx"
110110
)))]
111-
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
112-
IoSliceMut::limit_slices(&mut bufs, max_iov());
111+
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
112+
let bufs = io::limit_slices_mut!(bufs, max_iov());
113113
let ret = cvt(unsafe {
114114
libc::readv(
115115
self.as_raw_fd(),
@@ -199,12 +199,8 @@ impl FileDesc {
199199
target_os = "netbsd",
200200
target_os = "openbsd", // OpenBSD 2.7
201201
))]
202-
pub fn read_vectored_at(
203-
&self,
204-
mut bufs: &mut [IoSliceMut<'_>],
205-
offset: u64,
206-
) -> io::Result<usize> {
207-
IoSliceMut::limit_slices(&mut bufs, max_iov());
202+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
203+
let bufs = io::limit_slices_mut!(bufs, max_iov());
208204
let ret = cvt(unsafe {
209205
libc::preadv(
210206
self.as_raw_fd(),
@@ -241,11 +237,7 @@ impl FileDesc {
241237
// passing 64-bits parameters to syscalls, so we fallback to the default
242238
// implementation if `preadv` is not available.
243239
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
244-
pub fn read_vectored_at(
245-
&self,
246-
mut bufs: &mut [IoSliceMut<'_>],
247-
offset: u64,
248-
) -> io::Result<usize> {
240+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
249241
syscall!(
250242
fn preadv(
251243
fd: libc::c_int,
@@ -255,7 +247,7 @@ impl FileDesc {
255247
) -> isize;
256248
);
257249

258-
IoSliceMut::limit_slices(&mut bufs, max_iov());
250+
let bufs = io::limit_slices_mut!(bufs, max_iov());
259251
let ret = cvt(unsafe {
260252
preadv(
261253
self.as_raw_fd(),
@@ -271,11 +263,7 @@ impl FileDesc {
271263
// FIXME(#115199): Rust currently omits weak function definitions
272264
// and its metadata from LLVM IR.
273265
#[no_sanitize(cfi)]
274-
pub fn read_vectored_at(
275-
&self,
276-
mut bufs: &mut [IoSliceMut<'_>],
277-
offset: u64,
278-
) -> io::Result<usize> {
266+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
279267
weak!(
280268
fn preadv64(
281269
fd: libc::c_int,
@@ -287,7 +275,7 @@ impl FileDesc {
287275

288276
match preadv64.get() {
289277
Some(preadv) => {
290-
IoSliceMut::limit_slices(&mut bufs, max_iov());
278+
let bufs = io::limit_slices_mut!(bufs, max_iov());
291279
let ret = cvt(unsafe {
292280
preadv(
293281
self.as_raw_fd(),
@@ -312,11 +300,7 @@ impl FileDesc {
312300
// These versions may be newer than the minimum supported versions of OS's we support so we must
313301
// use "weak" linking.
314302
#[cfg(target_vendor = "apple")]
315-
pub fn read_vectored_at(
316-
&self,
317-
mut bufs: &mut [IoSliceMut<'_>],
318-
offset: u64,
319-
) -> io::Result<usize> {
303+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
320304
weak!(
321305
fn preadv(
322306
fd: libc::c_int,
@@ -328,7 +312,7 @@ impl FileDesc {
328312

329313
match preadv.get() {
330314
Some(preadv) => {
331-
IoSliceMut::limit_slices(&mut bufs, max_iov());
315+
let bufs = io::limit_slices_mut!(bufs, max_iov());
332316
let ret = cvt(unsafe {
333317
preadv(
334318
self.as_raw_fd(),
@@ -360,8 +344,8 @@ impl FileDesc {
360344
target_os = "vita",
361345
target_os = "nuttx"
362346
)))]
363-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
364-
IoSlice::limit_slices(&mut bufs, max_iov());
347+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
348+
let bufs = io::limit_slices!(bufs, max_iov());
365349
let ret = cvt(unsafe {
366350
libc::writev(
367351
self.as_raw_fd(),
@@ -430,8 +414,8 @@ impl FileDesc {
430414
target_os = "netbsd",
431415
target_os = "openbsd", // OpenBSD 2.7
432416
))]
433-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
434-
IoSlice::limit_slices(&mut bufs, max_iov());
417+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
418+
let bufs = io::limit_slices!(bufs, max_iov());
435419
let ret = cvt(unsafe {
436420
libc::pwritev(
437421
self.as_raw_fd(),
@@ -468,7 +452,7 @@ impl FileDesc {
468452
// passing 64-bits parameters to syscalls, so we fallback to the default
469453
// implementation if `pwritev` is not available.
470454
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
471-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
455+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
472456
syscall!(
473457
fn pwritev(
474458
fd: libc::c_int,
@@ -478,7 +462,7 @@ impl FileDesc {
478462
) -> isize;
479463
);
480464

481-
IoSlice::limit_slices(&mut bufs, max_iov());
465+
let bufs = io::limit_slices!(bufs, max_iov());
482466
let ret = cvt(unsafe {
483467
pwritev(
484468
self.as_raw_fd(),
@@ -491,7 +475,7 @@ impl FileDesc {
491475
}
492476

493477
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
494-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
478+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
495479
weak!(
496480
fn pwritev64(
497481
fd: libc::c_int,
@@ -503,7 +487,7 @@ impl FileDesc {
503487

504488
match pwritev64.get() {
505489
Some(pwritev) => {
506-
IoSlice::limit_slices(&mut bufs, max_iov());
490+
let bufs = io::limit_slices!(bufs, max_iov());
507491
let ret = cvt(unsafe {
508492
pwritev(
509493
self.as_raw_fd(),
@@ -528,7 +512,7 @@ impl FileDesc {
528512
// These versions may be newer than the minimum supported versions of OS's we support so we must
529513
// use "weak" linking.
530514
#[cfg(target_vendor = "apple")]
531-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
515+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
532516
weak!(
533517
fn pwritev(
534518
fd: libc::c_int,
@@ -540,7 +524,7 @@ impl FileDesc {
540524

541525
match pwritev.get() {
542526
Some(pwritev) => {
543-
IoSlice::limit_slices(&mut bufs, max_iov());
527+
let bufs = io::limit_slices!(bufs, max_iov());
544528
let ret = cvt(unsafe {
545529
pwritev(
546530
self.as_raw_fd(),

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

+9
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

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

0 commit comments

Comments
 (0)