Skip to content

Commit 580d7ab

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 dddd7ab commit 580d7ab

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;
@@ -1428,6 +1429,25 @@ impl<'a> IoSliceMut<'a> {
14281429
}
14291430
}
14301431

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+
14311451
/// Get the underlying bytes as a mutable slice with the original lifetime.
14321452
///
14331453
/// # Examples
@@ -1589,6 +1609,25 @@ impl<'a> IoSlice<'a> {
15891609
}
15901610
}
15911611

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+
15921631
/// Get the underlying bytes as a slice with the original lifetime.
15931632
///
15941633
/// 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
@@ -110,12 +110,13 @@ impl FileDesc {
110110
target_os = "vita",
111111
target_os = "nuttx"
112112
)))]
113-
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
113+
pub fn read_vectored(&self, mut bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
114+
IoSliceMut::limit_slices(&mut bufs, max_iov());
114115
let ret = cvt(unsafe {
115116
libc::readv(
116117
self.as_raw_fd(),
117118
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
118-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
119+
bufs.len() as libc::c_int,
119120
)
120121
})?;
121122
Ok(ret as usize)
@@ -200,12 +201,17 @@ impl FileDesc {
200201
target_os = "netbsd",
201202
target_os = "openbsd", // OpenBSD 2.7
202203
))]
203-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
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());
204210
let ret = cvt(unsafe {
205211
libc::preadv(
206212
self.as_raw_fd(),
207213
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
208-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
214+
bufs.len() as libc::c_int,
209215
offset as _,
210216
)
211217
})?;
@@ -237,7 +243,11 @@ impl FileDesc {
237243
// passing 64-bits parameters to syscalls, so we fallback to the default
238244
// implementation if `preadv` is not available.
239245
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
240-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
246+
pub fn read_vectored_at(
247+
&self,
248+
mut bufs: &mut [IoSliceMut<'_>],
249+
offset: u64,
250+
) -> io::Result<usize> {
241251
syscall!(
242252
fn preadv(
243253
fd: libc::c_int,
@@ -247,19 +257,24 @@ impl FileDesc {
247257
) -> isize;
248258
);
249259

260+
IoSliceMut::limit_slices(&mut bufs, max_iov());
250261
let ret = cvt(unsafe {
251262
preadv(
252263
self.as_raw_fd(),
253264
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
254-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
265+
bufs.len() as libc::c_int,
255266
offset as _,
256267
)
257268
})?;
258269
Ok(ret as usize)
259270
}
260271

261272
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
262-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
273+
pub fn read_vectored_at(
274+
&self,
275+
mut bufs: &mut [IoSliceMut<'_>],
276+
offset: u64,
277+
) -> io::Result<usize> {
263278
weak!(
264279
fn preadv64(
265280
fd: libc::c_int,
@@ -271,11 +286,12 @@ impl FileDesc {
271286

272287
match preadv64.get() {
273288
Some(preadv) => {
289+
IoSliceMut::limit_slices(&mut bufs, max_iov());
274290
let ret = cvt(unsafe {
275291
preadv(
276292
self.as_raw_fd(),
277293
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
278-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
294+
bufs.len() as libc::c_int,
279295
offset as _,
280296
)
281297
})?;
@@ -295,7 +311,11 @@ impl FileDesc {
295311
// These versions may be newer than the minimum supported versions of OS's we support so we must
296312
// use "weak" linking.
297313
#[cfg(target_vendor = "apple")]
298-
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
314+
pub fn read_vectored_at(
315+
&self,
316+
mut bufs: &mut [IoSliceMut<'_>],
317+
offset: u64,
318+
) -> io::Result<usize> {
299319
weak!(
300320
fn preadv(
301321
fd: libc::c_int,
@@ -307,11 +327,12 @@ impl FileDesc {
307327

308328
match preadv.get() {
309329
Some(preadv) => {
330+
IoSliceMut::limit_slices(&mut bufs, max_iov());
310331
let ret = cvt(unsafe {
311332
preadv(
312333
self.as_raw_fd(),
313334
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
314-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
335+
bufs.len() as libc::c_int,
315336
offset as _,
316337
)
317338
})?;
@@ -338,12 +359,13 @@ impl FileDesc {
338359
target_os = "vita",
339360
target_os = "nuttx"
340361
)))]
341-
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
362+
pub fn write_vectored(&self, mut bufs: &[IoSlice<'_>]) -> io::Result<usize> {
363+
IoSlice::limit_slices(&mut bufs, max_iov());
342364
let ret = cvt(unsafe {
343365
libc::writev(
344366
self.as_raw_fd(),
345367
bufs.as_ptr() as *const libc::iovec,
346-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
368+
bufs.len() as libc::c_int,
347369
)
348370
})?;
349371
Ok(ret as usize)
@@ -407,12 +429,13 @@ impl FileDesc {
407429
target_os = "netbsd",
408430
target_os = "openbsd", // OpenBSD 2.7
409431
))]
410-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
432+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
433+
IoSlice::limit_slices(&mut bufs, max_iov());
411434
let ret = cvt(unsafe {
412435
libc::pwritev(
413436
self.as_raw_fd(),
414437
bufs.as_ptr() as *const libc::iovec,
415-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
438+
bufs.len() as libc::c_int,
416439
offset as _,
417440
)
418441
})?;
@@ -444,7 +467,7 @@ impl FileDesc {
444467
// passing 64-bits parameters to syscalls, so we fallback to the default
445468
// implementation if `pwritev` is not available.
446469
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
447-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
470+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
448471
syscall!(
449472
fn pwritev(
450473
fd: libc::c_int,
@@ -454,19 +477,20 @@ impl FileDesc {
454477
) -> isize;
455478
);
456479

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

468492
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
469-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
493+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
470494
weak!(
471495
fn pwritev64(
472496
fd: libc::c_int,
@@ -478,11 +502,12 @@ impl FileDesc {
478502

479503
match pwritev64.get() {
480504
Some(pwritev) => {
505+
IoSlice::limit_slices(&mut bufs, max_iov());
481506
let ret = cvt(unsafe {
482507
pwritev(
483508
self.as_raw_fd(),
484509
bufs.as_ptr() as *const libc::iovec,
485-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
510+
bufs.len() as libc::c_int,
486511
offset as _,
487512
)
488513
})?;
@@ -502,7 +527,7 @@ impl FileDesc {
502527
// These versions may be newer than the minimum supported versions of OS's we support so we must
503528
// use "weak" linking.
504529
#[cfg(target_vendor = "apple")]
505-
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
530+
pub fn write_vectored_at(&self, mut bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
506531
weak!(
507532
fn pwritev(
508533
fd: libc::c_int,
@@ -514,11 +539,12 @@ impl FileDesc {
514539

515540
match pwritev.get() {
516541
Some(pwritev) => {
542+
IoSlice::limit_slices(&mut bufs, max_iov());
517543
let ret = cvt(unsafe {
518544
pwritev(
519545
self.as_raw_fd(),
520546
bufs.as_ptr() as *const libc::iovec,
521-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
547+
bufs.len() as libc::c_int,
522548
offset as _,
523549
)
524550
})?;

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)