mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Cover the empty iovec noop, the fast path where iovecs go straight to preadv or pwritev, and misaligned O_DIRECT scatter and gather across multiple iovecs including a partial block read-modify-write. Assisted-by: Claude:Opus-4.8 Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
574 lines
19 KiB
Rust
574 lines
19 KiB
Rust
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use std::fs::{File, Metadata};
|
|
use std::os::fd::{AsFd, BorrowedFd};
|
|
use std::os::unix::fs::FileExt;
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
use std::{io, slice};
|
|
|
|
use vmm_sys_util::file_traits::FileSync;
|
|
use vmm_sys_util::seek_hole::SeekHole;
|
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
|
|
use crate::aligned_buffer::AlignedBuffer;
|
|
use crate::{SECTOR_SIZE, probe_direct_alignment};
|
|
|
|
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
|
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
|
fn is_aligned(alignment: usize, buf_ptr: usize, len: usize, offset: u64) -> bool {
|
|
alignment == 0
|
|
|| (buf_ptr.is_multiple_of(alignment)
|
|
&& len.is_multiple_of(alignment)
|
|
&& offset.is_multiple_of(alignment as u64))
|
|
}
|
|
|
|
/// True when `offset` and every iovec base/length satisfy `alignment`
|
|
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
|
fn iovecs_are_aligned(alignment: usize, iovecs: &[libc::iovec], offset: u64) -> bool {
|
|
alignment == 0
|
|
|| (offset.is_multiple_of(alignment as u64)
|
|
&& iovecs.iter().all(|iov| {
|
|
(iov.iov_base as usize).is_multiple_of(alignment)
|
|
&& iov.iov_len.is_multiple_of(alignment)
|
|
}))
|
|
}
|
|
|
|
/// A `File` that transparently satisfies O_DIRECT alignment requirements.
|
|
///
|
|
/// `alignment == 0` means no O_DIRECT (all I/O passes straight through).
|
|
/// For unaligned requests under O_DIRECT, I/O is bounced through an
|
|
/// `AlignedBuffer` (read-modify-write for writes).
|
|
#[derive(Debug)]
|
|
pub struct AlignedFile {
|
|
file: File,
|
|
alignment: usize,
|
|
}
|
|
|
|
impl AlignedFile {
|
|
/// Wrap `file`, querying the O_DIRECT block alignment when `direct_io`.
|
|
pub fn new(file: File, direct_io: bool) -> Self {
|
|
let alignment = if direct_io {
|
|
probe_direct_alignment(file.as_raw_fd()).unwrap_or(SECTOR_SIZE) as usize
|
|
} else {
|
|
0
|
|
};
|
|
AlignedFile { file, alignment }
|
|
}
|
|
|
|
pub fn alignment(&self) -> usize {
|
|
self.alignment
|
|
}
|
|
|
|
pub fn file(&self) -> &File {
|
|
&self.file
|
|
}
|
|
|
|
pub fn file_mut(&mut self) -> &mut File {
|
|
&mut self.file
|
|
}
|
|
|
|
pub fn try_clone(&self) -> io::Result<Self> {
|
|
Ok(AlignedFile {
|
|
file: self.file.try_clone()?,
|
|
alignment: self.alignment,
|
|
})
|
|
}
|
|
|
|
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
|
self.file.set_len(size)
|
|
}
|
|
|
|
pub fn metadata(&self) -> io::Result<Metadata> {
|
|
self.file.metadata()
|
|
}
|
|
|
|
pub fn sync_all(&self) -> io::Result<()> {
|
|
self.file.sync_all()
|
|
}
|
|
|
|
pub fn sync_data(&self) -> io::Result<()> {
|
|
self.file.sync_data()
|
|
}
|
|
|
|
pub fn is_direct(&self) -> bool {
|
|
self.alignment != 0
|
|
}
|
|
|
|
pub fn is_writable(&self) -> bool {
|
|
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
|
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
|
|
if flags < 0 {
|
|
return false;
|
|
}
|
|
let access_mode = flags & libc::O_ACCMODE;
|
|
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
|
}
|
|
|
|
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
|
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
|
#[cfg(test)]
|
|
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
|
AlignedFile { file, alignment }
|
|
}
|
|
|
|
/// Read `len` bytes at `offset` through an aligned bounce buffer.
|
|
pub(crate) fn read_unaligned(
|
|
&self,
|
|
offset: u64,
|
|
len: usize,
|
|
scatter: impl FnOnce(&[u8]) -> io::Result<()>,
|
|
) -> io::Result<usize> {
|
|
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
|
|
let n = abuf.read_from(&self.file)?;
|
|
scatter(&abuf.as_slice()[..n])?;
|
|
Ok(n)
|
|
}
|
|
|
|
/// Write `len` bytes at `offset` through an aligned bounce buffer.
|
|
pub(crate) fn write_unaligned(
|
|
&self,
|
|
offset: u64,
|
|
len: usize,
|
|
gather: impl FnOnce(&mut [u8]) -> io::Result<()>,
|
|
) -> io::Result<usize> {
|
|
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
|
|
abuf.read_from(&self.file)?; // RMW: preserve head/tail padding
|
|
gather(abuf.as_mut_slice())?;
|
|
abuf.write_to(&self.file)?;
|
|
Ok(len)
|
|
}
|
|
|
|
/// Read into the buffers described by `iovecs`, starting at `offset`.
|
|
///
|
|
/// # Safety
|
|
/// Every iovec must describe valid, writable memory of `iov_len` bytes.
|
|
pub(crate) unsafe fn read_vectored_at(
|
|
&self,
|
|
iovecs: &[libc::iovec],
|
|
offset: u64,
|
|
) -> io::Result<usize> {
|
|
if iovecs.is_empty() {
|
|
return Ok(0);
|
|
}
|
|
if iovecs_are_aligned(self.alignment, iovecs, offset) {
|
|
// SAFETY: upheld by this fn contract.
|
|
let ret = unsafe {
|
|
libc::preadv(
|
|
self.file.as_raw_fd(),
|
|
iovecs.as_ptr(),
|
|
iovecs.len() as libc::c_int,
|
|
offset as libc::off_t,
|
|
)
|
|
};
|
|
if ret < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
return Ok(ret as usize);
|
|
}
|
|
let total_len = iovecs.iter().map(|iov| iov.iov_len).sum();
|
|
self.read_unaligned(offset, total_len, |mut data| {
|
|
for iov in iovecs {
|
|
if data.is_empty() {
|
|
break;
|
|
}
|
|
let n = data.len().min(iov.iov_len);
|
|
// SAFETY: upheld by this fn contract.
|
|
let dst = unsafe { slice::from_raw_parts_mut(iov.iov_base as *mut u8, n) };
|
|
dst.copy_from_slice(&data[..n]);
|
|
data = &data[n..];
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Write the buffers described by `iovecs`, starting at `offset`.
|
|
///
|
|
/// # Safety
|
|
/// Every iovec must describe valid, readable memory of `iov_len` bytes.
|
|
pub(crate) unsafe fn write_vectored_at(
|
|
&self,
|
|
iovecs: &[libc::iovec],
|
|
offset: u64,
|
|
) -> io::Result<usize> {
|
|
if iovecs.is_empty() {
|
|
return Ok(0);
|
|
}
|
|
if iovecs_are_aligned(self.alignment, iovecs, offset) {
|
|
// SAFETY: upheld by this fn contract.
|
|
let ret = unsafe {
|
|
libc::pwritev(
|
|
self.file.as_raw_fd(),
|
|
iovecs.as_ptr(),
|
|
iovecs.len() as libc::c_int,
|
|
offset as libc::off_t,
|
|
)
|
|
};
|
|
if ret < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
return Ok(ret as usize);
|
|
}
|
|
let total_len = iovecs.iter().map(|iov| iov.iov_len).sum();
|
|
self.write_unaligned(offset, total_len, |mut dst| {
|
|
for iov in iovecs {
|
|
if dst.is_empty() {
|
|
break;
|
|
}
|
|
let n = dst.len().min(iov.iov_len);
|
|
// SAFETY: upheld by this fn contract.
|
|
let src = unsafe { slice::from_raw_parts(iov.iov_base as *const u8, n) };
|
|
dst[..n].copy_from_slice(src);
|
|
dst = &mut dst[n..];
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
|
|
impl FileExt for AlignedFile {
|
|
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
if buf.is_empty() {
|
|
return Ok(0);
|
|
}
|
|
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
|
|
return self.file.read_at(buf, offset);
|
|
}
|
|
self.read_unaligned(offset, buf.len(), |data| {
|
|
buf[..data.len()].copy_from_slice(data);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
|
|
if buf.is_empty() {
|
|
return Ok(0);
|
|
}
|
|
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
|
|
return self.file.write_at(buf, offset);
|
|
}
|
|
self.write_unaligned(offset, buf.len(), |dst| {
|
|
dst.copy_from_slice(buf);
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
|
|
impl WriteZeroesAt for AlignedFile {
|
|
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
|
|
self.file.write_zeroes_at(offset, length)
|
|
}
|
|
}
|
|
|
|
impl PunchHole for AlignedFile {
|
|
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
|
self.file.punch_hole(offset, length)
|
|
}
|
|
}
|
|
|
|
impl FileSync for AlignedFile {
|
|
fn fsync(&mut self) -> io::Result<()> {
|
|
self.file.fsync()
|
|
}
|
|
}
|
|
|
|
impl SeekHole for AlignedFile {
|
|
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
self.file.seek_hole(offset)
|
|
}
|
|
|
|
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
self.file.seek_data(offset)
|
|
}
|
|
}
|
|
|
|
impl Clone for AlignedFile {
|
|
fn clone(&self) -> Self {
|
|
self.try_clone().expect("AlignedFile cloning failed")
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for AlignedFile {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.file.as_raw_fd()
|
|
}
|
|
}
|
|
|
|
impl AsFd for AlignedFile {
|
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
self.file.as_fd()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::io::Write;
|
|
use std::os::unix::fs::FileExt;
|
|
|
|
use vmm_sys_util::tempfile::TempFile;
|
|
|
|
use super::*;
|
|
|
|
fn pattern_file(size: usize) -> TempFile {
|
|
let tf = TempFile::new().unwrap();
|
|
let p: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
|
|
tf.as_file().write_all(&p).unwrap();
|
|
tf.as_file().sync_all().unwrap();
|
|
tf
|
|
}
|
|
|
|
fn forced(file: File, alignment: usize) -> AlignedFile {
|
|
AlignedFile { file, alignment }
|
|
}
|
|
|
|
#[test]
|
|
fn new_probes_alignment_and_accessors() {
|
|
let tf = pattern_file(8192);
|
|
// Not O_DIRECT, so new() falls back to SECTOR_SIZE (512).
|
|
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
|
assert_eq!(af.alignment(), 512);
|
|
let _ = af.file();
|
|
let _ = af.file_mut();
|
|
let _ = af.try_clone().unwrap();
|
|
|
|
let plain = AlignedFile::new(tf.as_file().try_clone().unwrap(), false);
|
|
assert_eq!(plain.alignment(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn read_unaligned_offset_matches_contents() {
|
|
let tf = pattern_file(8192);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let mut buf = vec![0u8; 200];
|
|
assert_eq!(af.read_at(&mut buf, 100).unwrap(), 200);
|
|
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(buf, want);
|
|
}
|
|
|
|
#[test]
|
|
fn read_unaligned_short_at_eof() {
|
|
let tf = pattern_file(100);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let mut buf = vec![0u8; 200];
|
|
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 90);
|
|
}
|
|
|
|
#[test]
|
|
fn write_unaligned_offset_is_rmw() {
|
|
let tf = pattern_file(8192);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
|
|
assert_eq!(af.write_at(&data, 100).unwrap(), 200);
|
|
|
|
let mut whole = vec![0u8; 8192];
|
|
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(&whole[..100], &before[..]);
|
|
assert_eq!(&whole[100..300], &data[..]);
|
|
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(&whole[300..], &after[..]);
|
|
}
|
|
|
|
#[test]
|
|
fn aligned_passthrough_roundtrip() {
|
|
let tf = pattern_file(4096);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let mut buf = vec![0u8; 512];
|
|
assert_eq!(af.read_at(&mut buf, 512).unwrap(), 512);
|
|
let want: Vec<u8> = (512..1024).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(buf, want);
|
|
}
|
|
|
|
#[test]
|
|
fn no_alignment_is_plain_passthrough() {
|
|
let tf = pattern_file(100);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 0);
|
|
let mut buf = vec![0u8; 50];
|
|
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
|
|
}
|
|
|
|
#[test]
|
|
fn test_unaligned_read_beyond_eof_returns_zero() {
|
|
let tf = pattern_file(100);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let mut buf = vec![0u8; 16];
|
|
assert_eq!(af.read_at(&mut buf, 200).unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_unaligned_write_extends_at_eof() {
|
|
let file_size = 100usize;
|
|
let tf = pattern_file(file_size);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let data = b"xyz";
|
|
assert_eq!(af.write_at(data, file_size as u64).unwrap(), data.len());
|
|
|
|
let mut readback = vec![0u8; file_size + data.len()];
|
|
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
|
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(&readback[..file_size], &expected_prefix[..]);
|
|
assert_eq!(&readback[file_size..], data);
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_unaligned_io_is_noop() {
|
|
let tf = pattern_file(100);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
let mut read_buf = [];
|
|
assert_eq!(af.read_at(&mut read_buf, 1).unwrap(), 0);
|
|
assert_eq!(af.write_at(&[], 1).unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn read_unaligned_scatters_in_a_single_copy() {
|
|
let file = pattern_file(8192);
|
|
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
let mut out = vec![0u8; 200];
|
|
let n = aligned_file
|
|
.read_unaligned(100, 200, |data| {
|
|
out.copy_from_slice(data);
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
assert_eq!(n, 200);
|
|
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(out, want);
|
|
}
|
|
|
|
#[test]
|
|
fn read_unaligned_closure_short_at_eof() {
|
|
let file = pattern_file(100);
|
|
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
let mut seen = 0usize;
|
|
let n = aligned_file
|
|
.read_unaligned(10, 200, |data| {
|
|
seen = data.len();
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
assert_eq!(n, 90);
|
|
assert_eq!(seen, 90);
|
|
}
|
|
|
|
#[test]
|
|
fn write_unaligned_gather_is_rmw() {
|
|
let file = pattern_file(8192);
|
|
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
|
|
let n = aligned_file
|
|
.write_unaligned(100, 200, |buf| {
|
|
buf.copy_from_slice(&data);
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
assert_eq!(n, 200);
|
|
|
|
let mut whole = vec![0u8; 8192];
|
|
file.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(&whole[..100], &before[..]);
|
|
assert_eq!(&whole[100..300], &data[..]);
|
|
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
|
|
assert_eq!(&whole[300..], &after[..]);
|
|
}
|
|
|
|
#[test]
|
|
fn read_unaligned_propagates_closure_error() {
|
|
let file = pattern_file(8192);
|
|
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
let err = aligned_file
|
|
.read_unaligned(100, 200, |_| {
|
|
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
|
|
})
|
|
.unwrap_err();
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
}
|
|
|
|
#[test]
|
|
fn write_unaligned_propagates_closure_error() {
|
|
let file = pattern_file(8192);
|
|
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
let err = aligned_file
|
|
.write_unaligned(100, 200, |_| {
|
|
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
|
|
})
|
|
.unwrap_err();
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
}
|
|
|
|
/// Build an iovec over `buf`, which must outlive the iovec.
|
|
fn iovec_of(buf: &mut [u8]) -> libc::iovec {
|
|
libc::iovec {
|
|
iov_base: buf.as_mut_ptr() as *mut libc::c_void,
|
|
iov_len: buf.len(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vectored_empty_is_noop() {
|
|
let tf = pattern_file(100);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
// SAFETY: empty iovec slices point to no memory.
|
|
assert_eq!(unsafe { af.read_vectored_at(&[], 0) }.unwrap(), 0);
|
|
// SAFETY: empty iovec slices point to no memory.
|
|
assert_eq!(unsafe { af.write_vectored_at(&[], 0) }.unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn vectored_fast_path_roundtrip() {
|
|
let tf = pattern_file(4096);
|
|
// alignment 0 sends any iovecs through the single preadv/pwritev path.
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 0);
|
|
|
|
let mut w0: Vec<u8> = (0..30).map(|i| ((i + 7) % 239) as u8).collect();
|
|
let mut w1: Vec<u8> = (0..70).map(|i| ((i + 37) % 239) as u8).collect();
|
|
let wiovecs = [iovec_of(&mut w0), iovec_of(&mut w1)];
|
|
// SAFETY: the iovecs describe the live w0/w1 buffers.
|
|
assert_eq!(unsafe { af.write_vectored_at(&wiovecs, 10) }.unwrap(), 100);
|
|
let data = [w0.as_slice(), &w1].concat();
|
|
|
|
let mut plain = vec![0u8; 100];
|
|
tf.as_file().read_exact_at(&mut plain, 10).unwrap();
|
|
assert_eq!(plain, data);
|
|
|
|
let mut r0 = vec![0u8; 30];
|
|
let mut r1 = vec![0u8; 70];
|
|
let riovecs = [iovec_of(&mut r0), iovec_of(&mut r1)];
|
|
// SAFETY: the iovecs describe the live r0/r1 buffers.
|
|
assert_eq!(unsafe { af.read_vectored_at(&riovecs, 10) }.unwrap(), 100);
|
|
assert_eq!([r0.as_slice(), &r1].concat(), data);
|
|
}
|
|
|
|
#[test]
|
|
fn vectored_unaligned_scatter_gather_roundtrip() {
|
|
let tf = pattern_file(8192);
|
|
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
|
|
// Gather three iovecs into an unaligned read-modify-write.
|
|
let mut w0: Vec<u8> = (0..50).map(|i| ((i + 1) % 239) as u8).collect();
|
|
let mut w1: Vec<u8> = (0..100).map(|i| ((i + 51) % 239) as u8).collect();
|
|
let mut w2: Vec<u8> = (0..50).map(|i| ((i + 151) % 239) as u8).collect();
|
|
let wiovecs = [iovec_of(&mut w0), iovec_of(&mut w1), iovec_of(&mut w2)];
|
|
// SAFETY: the iovecs describe the live w0/w1/w2 buffers.
|
|
assert_eq!(unsafe { af.write_vectored_at(&wiovecs, 100) }.unwrap(), 200);
|
|
let data = [w0.as_slice(), &w1, &w2].concat();
|
|
|
|
// Independently confirm the region and the untouched neighbors.
|
|
let mut expected: Vec<u8> = (0..8192).map(|i| (i % 251) as u8).collect();
|
|
expected[100..300].copy_from_slice(&data);
|
|
let mut whole = vec![0u8; 8192];
|
|
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
assert_eq!(whole, expected);
|
|
|
|
// Scatter the same region back across three iovecs.
|
|
let mut r0 = vec![0u8; 50];
|
|
let mut r1 = vec![0u8; 100];
|
|
let mut r2 = vec![0u8; 50];
|
|
let riovecs = [iovec_of(&mut r0), iovec_of(&mut r1), iovec_of(&mut r2)];
|
|
// SAFETY: the iovecs describe the live r0/r1/r2 buffers.
|
|
assert_eq!(unsafe { af.read_vectored_at(&riovecs, 100) }.unwrap(), 200);
|
|
assert_eq!([r0.as_slice(), &r1, &r2].concat(), data);
|
|
}
|
|
}
|