Skip to content

Commit ab97e9c

Browse files
committed
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.
1 parent 0c478fd commit ab97e9c

File tree

5 files changed

+112
-41
lines changed

5 files changed

+112
-41
lines changed

library/std/src/io/mod.rs

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

300+
use core::intrinsics;
300301
#[unstable(feature = "read_buf", issue = "78485")]
301302
pub use core::io::{BorrowedBuf, BorrowedCursor};
302303
use core::slice::memchr;
@@ -1388,6 +1389,25 @@ impl<'a> IoSliceMut<'a> {
13881389
}
13891390
}
13901391

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+
13911411
/// Get the underlying bytes as a mutable slice with the original lifetime.
13921412
///
13931413
/// # Examples
@@ -1549,6 +1569,25 @@ impl<'a> IoSlice<'a> {
15491569
}
15501570
}
15511571

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+
15521591
/// Get the underlying bytes as a slice with the original lifetime.
15531592
///
15541593
/// This doesn't borrow from `self`, so is less restrictive than calling

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#![unstable(reason = "not public", issue = "none", feature = "fd")]
22

3-
use crate::cmp;
43
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, SeekFrom};
54
use crate::os::hermit::hermit_abi;
65
use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
@@ -38,12 +37,13 @@ impl FileDesc {
3837
Ok(())
3938
}
4039

41-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
40+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
41+
IoSliceMut::limit_slices(&mut bufs, max_iov());
4242
let ret = cvt(unsafe {
4343
hermit_abi::readv(
4444
self.as_raw_fd(),
4545
bufs.as_mut_ptr() as *mut hermit_abi::iovec as *const hermit_abi::iovec,
46-
cmp::min(bufs.len(), max_iov()),
46+
bufs.len(),
4747
)
4848
})?;
4949
Ok(ret as usize)
@@ -65,12 +65,13 @@ impl FileDesc {
6565
Ok(result as usize)
6666
}
6767

68-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
68+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
69+
IoSlice::limit_slices(&mut bufs, max_iov());
6970
let ret = cvt(unsafe {
7071
hermit_abi::writev(
7172
self.as_raw_fd(),
7273
bufs.as_ptr() as *const hermit_abi::iovec,
73-
cmp::min(bufs.len(), max_iov()),
74+
bufs.len(),
7475
)
7576
})?;
7677
Ok(ret as usize)

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

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,13 @@ impl FileDesc {
108108
target_os = "vita",
109109
target_os = "nuttx"
110110
)))]
111-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
111+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
112+
IoSliceMut::limit_slices(&mut bufs, max_iov());
112113
let ret = cvt(unsafe {
113114
libc::readv(
114115
self.as_raw_fd(),
115116
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
116-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
117+
bufs.len() as libc::c_int,
117118
)
118119
})?;
119120
Ok(ret as usize)
@@ -198,12 +199,17 @@ impl FileDesc {
198199
target_os = "netbsd",
199200
target_os = "openbsd", // OpenBSD 2.7
200201
))]
201-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
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());
202208
let ret = cvt(unsafe {
203209
libc::preadv(
204210
self.as_raw_fd(),
205211
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
206-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
212+
bufs.len() as libc::c_int,
207213
offset as _,
208214
)
209215
})?;
@@ -235,7 +241,11 @@ impl FileDesc {
235241
// passing 64-bits parameters to syscalls, so we fallback to the default
236242
// implementation if `preadv` is not available.
237243
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
238-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
244+
pub fn read_vectored_at(
245+
&self,
246+
mut bufs: &mut [IoSliceMut<'_>],
247+
offset: u64,
248+
) -> io::Result<usize> {
239249
syscall!(
240250
fn preadv(
241251
fd: libc::c_int,
@@ -245,11 +255,12 @@ impl FileDesc {
245255
) -> isize;
246256
);
247257

258+
IoSliceMut::limit_slices(&mut bufs, max_iov());
248259
let ret = cvt(unsafe {
249260
preadv(
250261
self.as_raw_fd(),
251262
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
252-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
263+
bufs.len() as libc::c_int,
253264
offset as _,
254265
)
255266
})?;
@@ -260,7 +271,11 @@ impl FileDesc {
260271
// FIXME(#115199): Rust currently omits weak function definitions
261272
// and its metadata from LLVM IR.
262273
#[no_sanitize(cfi)]
263-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
274+
pub fn read_vectored_at(
275+
&self,
276+
mut bufs: &mut [IoSliceMut<'_>],
277+
offset: u64,
278+
) -> io::Result<usize> {
264279
weak!(
265280
fn preadv64(
266281
fd: libc::c_int,
@@ -272,11 +287,12 @@ impl FileDesc {
272287

273288
match preadv64.get() {
274289
Some(preadv) => {
290+
IoSliceMut::limit_slices(&mut bufs, max_iov());
275291
let ret = cvt(unsafe {
276292
preadv(
277293
self.as_raw_fd(),
278294
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
279-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
295+
bufs.len() as libc::c_int,
280296
offset as _,
281297
)
282298
})?;
@@ -296,7 +312,11 @@ impl FileDesc {
296312
// These versions may be newer than the minimum supported versions of OS's we support so we must
297313
// use "weak" linking.
298314
#[cfg(target_vendor = "apple")]
299-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
315+
pub fn read_vectored_at(
316+
&self,
317+
mut bufs: &mut [IoSliceMut<'_>],
318+
offset: u64,
319+
) -> io::Result<usize> {
300320
weak!(
301321
fn preadv(
302322
fd: libc::c_int,
@@ -308,11 +328,12 @@ impl FileDesc {
308328

309329
match preadv.get() {
310330
Some(preadv) => {
331+
IoSliceMut::limit_slices(&mut bufs, max_iov());
311332
let ret = cvt(unsafe {
312333
preadv(
313334
self.as_raw_fd(),
314335
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
315-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
336+
bufs.len() as libc::c_int,
316337
offset as _,
317338
)
318339
})?;
@@ -339,12 +360,13 @@ impl FileDesc {
339360
target_os = "vita",
340361
target_os = "nuttx"
341362
)))]
342-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
363+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
364+
IoSlice::limit_slices(&mut bufs, max_iov());
343365
let ret = cvt(unsafe {
344366
libc::writev(
345367
self.as_raw_fd(),
346368
bufs.as_ptr() as *const libc::iovec,
347-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
369+
bufs.len() as libc::c_int,
348370
)
349371
})?;
350372
Ok(ret as usize)
@@ -408,12 +430,13 @@ impl FileDesc {
408430
target_os = "netbsd",
409431
target_os = "openbsd", // OpenBSD 2.7
410432
))]
411-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
433+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
434+
IoSlice::limit_slices(&mut bufs, max_iov());
412435
let ret = cvt(unsafe {
413436
libc::pwritev(
414437
self.as_raw_fd(),
415438
bufs.as_ptr() as *const libc::iovec,
416-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
439+
bufs.len() as libc::c_int,
417440
offset as _,
418441
)
419442
})?;
@@ -445,7 +468,7 @@ impl FileDesc {
445468
// passing 64-bits parameters to syscalls, so we fallback to the default
446469
// implementation if `pwritev` is not available.
447470
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
448-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
449472
syscall!(
450473
fn pwritev(
451474
fd: libc::c_int,
@@ -455,19 +478,20 @@ impl FileDesc {
455478
) -> isize;
456479
);
457480

481+
IoSlice::limit_slices(&mut bufs, max_iov());
458482
let ret = cvt(unsafe {
459483
pwritev(
460484
self.as_raw_fd(),
461485
bufs.as_ptr() as *const libc::iovec,
462-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
486+
bufs.len() as libc::c_int,
463487
offset as _,
464488
)
465489
})?;
466490
Ok(ret as usize)
467491
}
468492

469493
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
470-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
494+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
471495
weak!(
472496
fn pwritev64(
473497
fd: libc::c_int,
@@ -479,11 +503,12 @@ impl FileDesc {
479503

480504
match pwritev64.get() {
481505
Some(pwritev) => {
506+
IoSlice::limit_slices(&mut bufs, max_iov());
482507
let ret = cvt(unsafe {
483508
pwritev(
484509
self.as_raw_fd(),
485510
bufs.as_ptr() as *const libc::iovec,
486-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
511+
bufs.len() as libc::c_int,
487512
offset as _,
488513
)
489514
})?;
@@ -503,7 +528,7 @@ impl FileDesc {
503528
// These versions may be newer than the minimum supported versions of OS's we support so we must
504529
// use "weak" linking.
505530
#[cfg(target_vendor = "apple")]
506-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
531+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
507532
weak!(
508533
fn pwritev(
509534
fd: libc::c_int,
@@ -515,11 +540,12 @@ impl FileDesc {
515540

516541
match pwritev.get() {
517542
Some(pwritev) => {
543+
IoSlice::limit_slices(&mut bufs, max_iov());
518544
let ret = cvt(unsafe {
519545
pwritev(
520546
self.as_raw_fd(),
521547
bufs.as_ptr() as *const libc::iovec,
522-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
548+
bufs.len() as libc::c_int,
523549
offset as _,
524550
)
525551
})?;

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
use core::mem::ManuallyDrop;
22

3-
use super::FileDesc;
3+
use super::{FileDesc, max_iov};
44
use crate::io::IoSlice;
55
use crate::os::unix::io::FromRawFd;
66

77
#[test]
88
fn limit_vector_count() {
9+
const IOV_MAX: usize = max_iov();
10+
911
let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
10-
let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
11-
assert!(stdout.write_vectored(&bufs).is_ok());
12+
let mut bufs = vec![IoSlice::new(&[]); IOV_MAX * 2 + 1];
13+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), 0);
14+
15+
// The slice of buffers is truncated to IOV_MAX buffers. However, since the
16+
// first IOV_MAX buffers are all empty, it is sliced starting at the first
17+
// non-empty buffer to avoid erroneously returning Ok(0). In this case, that
18+
// starts with the b"hello" buffer and ends just before the b"world!"
19+
// buffer.
20+
bufs[IOV_MAX] = IoSlice::new(b"hello");
21+
bufs[IOV_MAX * 2] = IoSlice::new(b"world!");
22+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len())
1223
}

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

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Owne
99
use crate::sys::abi;
1010
use crate::sys_common::{FromInner, IntoInner};
1111
use crate::time::Duration;
12-
use crate::{cmp, mem, ptr, str};
12+
use crate::{mem, ptr, str};
1313

1414
pub(super) mod netc {
1515
pub use crate::sys::abi::sockets::*;
@@ -222,13 +222,10 @@ impl Socket {
222222
self.recv_with_flags(buf, 0)
223223
}
224224

225-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
225+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
226+
IoSliceMut::limit_slices(&mut bufs, max_iov());
226227
let ret = cvt(unsafe {
227-
netc::readv(
228-
self.as_raw_fd(),
229-
bufs.as_ptr() as *const netc::iovec,
230-
cmp::min(bufs.len(), max_iov()) as c_int,
231-
)
228+
netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
232229
})?;
233230
Ok(ret as usize)
234231
}
@@ -267,13 +264,10 @@ impl Socket {
267264
self.recv_from_with_flags(buf, MSG_PEEK)
268265
}
269266

270-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
267+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
268+
IoSlice::limit_slices(&mut bufs, max_iov());
271269
let ret = cvt(unsafe {
272-
netc::writev(
273-
self.as_raw_fd(),
274-
bufs.as_ptr() as *const netc::iovec,
275-
cmp::min(bufs.len(), max_iov()) as c_int,
276-
)
270+
netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
277271
})?;
278272
Ok(ret as usize)
279273
}

0 commit comments

Comments
 (0)