Skip to content

Commit ada55f8

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 ada55f8

File tree

6 files changed

+110
-89
lines changed

6 files changed

+110
-89
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 & 36 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(),
@@ -270,11 +262,7 @@ impl FileDesc {
270262
}
271263

272264
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
273-
pub fn read_vectored_at(
274-
&self,
275-
mut bufs: &mut [IoSliceMut<'_>],
276-
offset: u64,
277-
) -> io::Result<usize> {
265+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
278266
weak!(
279267
fn preadv64(
280268
fd: libc::c_int,
@@ -286,7 +274,7 @@ impl FileDesc {
286274

287275
match preadv64.get() {
288276
Some(preadv) => {
289-
IoSliceMut::limit_slices(&mut bufs, max_iov());
277+
let bufs = io::limit_slices_mut!(bufs, max_iov());
290278
let ret = cvt(unsafe {
291279
preadv(
292280
self.as_raw_fd(),
@@ -311,11 +299,7 @@ impl FileDesc {
311299
// These versions may be newer than the minimum supported versions of OS's we support so we must
312300
// use "weak" linking.
313301
#[cfg(target_vendor = "apple")]
314-
pub fn read_vectored_at(
315-
&self,
316-
mut bufs: &mut [IoSliceMut<'_>],
317-
offset: u64,
318-
) -> io::Result<usize> {
302+
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
319303
weak!(
320304
fn preadv(
321305
fd: libc::c_int,
@@ -327,7 +311,7 @@ impl FileDesc {
327311

328312
match preadv.get() {
329313
Some(preadv) => {
330-
IoSliceMut::limit_slices(&mut bufs, max_iov());
314+
let bufs = io::limit_slices_mut!(bufs, max_iov());
331315
let ret = cvt(unsafe {
332316
preadv(
333317
self.as_raw_fd(),
@@ -359,8 +343,8 @@ impl FileDesc {
359343
target_os = "vita",
360344
target_os = "nuttx"
361345
)))]
362-
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
363-
IoSlice::limit_slices(&mut bufs, max_iov());
346+
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
347+
let bufs = io::limit_slices!(bufs, max_iov());
364348
let ret = cvt(unsafe {
365349
libc::writev(
366350
self.as_raw_fd(),
@@ -429,8 +413,8 @@ impl FileDesc {
429413
target_os = "netbsd",
430414
target_os = "openbsd", // OpenBSD 2.7
431415
))]
432-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
433-
IoSlice::limit_slices(&mut bufs, max_iov());
416+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
417+
let bufs = io::limit_slices!(bufs, max_iov());
434418
let ret = cvt(unsafe {
435419
libc::pwritev(
436420
self.as_raw_fd(),
@@ -467,7 +451,7 @@ impl FileDesc {
467451
// passing 64-bits parameters to syscalls, so we fallback to the default
468452
// implementation if `pwritev` is not available.
469453
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
470-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
454+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471455
syscall!(
472456
fn pwritev(
473457
fd: libc::c_int,
@@ -477,7 +461,7 @@ impl FileDesc {
477461
) -> isize;
478462
);
479463

480-
IoSlice::limit_slices(&mut bufs, max_iov());
464+
let bufs = io::limit_slices!(bufs, max_iov());
481465
let ret = cvt(unsafe {
482466
pwritev(
483467
self.as_raw_fd(),
@@ -490,7 +474,7 @@ impl FileDesc {
490474
}
491475

492476
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
493-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
477+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
494478
weak!(
495479
fn pwritev64(
496480
fd: libc::c_int,
@@ -502,7 +486,7 @@ impl FileDesc {
502486

503487
match pwritev64.get() {
504488
Some(pwritev) => {
505-
IoSlice::limit_slices(&mut bufs, max_iov());
489+
let bufs = io::limit_slices!(bufs, max_iov());
506490
let ret = cvt(unsafe {
507491
pwritev(
508492
self.as_raw_fd(),
@@ -527,7 +511,7 @@ impl FileDesc {
527511
// These versions may be newer than the minimum supported versions of OS's we support so we must
528512
// use "weak" linking.
529513
#[cfg(target_vendor = "apple")]
530-
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
514+
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
531515
weak!(
532516
fn pwritev(
533517
fd: libc::c_int,
@@ -539,7 +523,7 @@ impl FileDesc {
539523

540524
match pwritev.get() {
541525
Some(pwritev) => {
542-
IoSlice::limit_slices(&mut bufs, max_iov());
526+
let bufs = io::limit_slices!(bufs, max_iov());
543527
let ret = cvt(unsafe {
544528
pwritev(
545529
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)