mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-30 21:16:27 +03:00
Merge pull request #5019 from WhySoBad/file-description-readv-writev
Add vectored read and vectored write shims for Unix targets
This commit is contained in:
@@ -44,6 +44,12 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VisitProvenance> VisitProvenance for Vec<T> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
self.iter().for_each(|el| el.visit_provenance(visit));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VisitProvenance> VisitProvenance for std::cell::RefCell<T> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
self.borrow().visit_provenance(visit)
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use rand::RngExt;
|
||||
use rustc_abi::Size;
|
||||
use rustc_abi::{Align, Size};
|
||||
use rustc_target::spec::Os;
|
||||
|
||||
use crate::shims::files::FileDescription;
|
||||
use crate::shims::files::{DynFileDescriptionRef, FileDescription};
|
||||
use crate::shims::sig::check_min_vararg_count;
|
||||
use crate::shims::unix::linux_like::epoll::EpollReadiness;
|
||||
use crate::shims::unix::*;
|
||||
@@ -316,7 +316,6 @@ fn read(
|
||||
.min(u64::try_from(this.target_isize_max()).unwrap())
|
||||
.min(u64::try_from(isize::MAX).unwrap());
|
||||
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
|
||||
let communicate = this.machine.communicate();
|
||||
|
||||
// Get the FD.
|
||||
let Some(fd) = this.machine.fds.get(fd_num) else {
|
||||
@@ -324,33 +323,17 @@ fn read(
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
// Handle the zero-sized case. The man page says:
|
||||
// > If count is zero, read() may detect the errors described below. In the absence of any
|
||||
// > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
|
||||
// > and has no other effects.
|
||||
if count == 0 {
|
||||
this.write_null(dest)?;
|
||||
return interp_ok(());
|
||||
}
|
||||
// Non-deterministically decide to further reduce the count, simulating a partial read (but
|
||||
// never to 0, that would indicate EOF).
|
||||
let count = if this.machine.short_fd_operations
|
||||
&& fd.short_fd_operations()
|
||||
&& count >= 2
|
||||
&& this.machine.rng.get_mut().random()
|
||||
{
|
||||
count / 2 // since `count` is at least 2, the result is still at least 1
|
||||
} else {
|
||||
count
|
||||
};
|
||||
|
||||
trace!("read: FD mapped to {fd:?}");
|
||||
// We want to read at most `count` bytes. We are sure that `count` is not negative
|
||||
// because it was a target's `usize`. Also we are sure that it's smaller than
|
||||
// `usize::MAX` because it is bounded by the host's `isize`.
|
||||
|
||||
let finish = {
|
||||
let dest = dest.clone();
|
||||
let dest = dest.clone();
|
||||
this.read_from_fd(
|
||||
fd,
|
||||
buf,
|
||||
count,
|
||||
offset,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
count: usize,
|
||||
@@ -363,22 +346,10 @@ fn read(
|
||||
// This must fit since `count` fits.
|
||||
this.write_int(u64::try_from(read_size).unwrap(), &dest)
|
||||
}
|
||||
Err(e) => {
|
||||
this.set_last_error_and_return(e, &dest)
|
||||
}
|
||||
Err(e) => this.set_last_error_and_return(e, &dest)
|
||||
}}
|
||||
)
|
||||
};
|
||||
match offset {
|
||||
None => fd.read(communicate, buf, count, this, finish)?,
|
||||
Some(offset) => {
|
||||
let Ok(offset) = u64::try_from(offset) else {
|
||||
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
|
||||
};
|
||||
fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
|
||||
}
|
||||
};
|
||||
interp_ok(())
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn write(
|
||||
@@ -402,39 +373,18 @@ fn write(
|
||||
.min(u64::try_from(this.target_isize_max()).unwrap())
|
||||
.min(u64::try_from(isize::MAX).unwrap());
|
||||
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
|
||||
let communicate = this.machine.communicate();
|
||||
|
||||
// We temporarily dup the FD to be able to retain mutable access to `this`.
|
||||
let Some(fd) = this.machine.fds.get(fd_num) else {
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
// Handle the zero-sized case. The man page says:
|
||||
// > If count is zero and fd refers to a regular file, then write() may return a failure
|
||||
// > status if one of the errors below is detected. If no errors are detected, or error
|
||||
// > detection is not performed, 0 is returned without causing any other effect. If count
|
||||
// > is zero and fd refers to a file other than a regular file, the results are not
|
||||
// > specified.
|
||||
if count == 0 {
|
||||
// For now let's not open the can of worms of what exactly "not specified" could mean...
|
||||
this.write_null(dest)?;
|
||||
return interp_ok(());
|
||||
}
|
||||
// Non-deterministically decide to further reduce the count, simulating a partial write.
|
||||
// We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
|
||||
// but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
|
||||
let count = if this.machine.short_fd_operations
|
||||
&& fd.short_fd_operations()
|
||||
&& count >= 2
|
||||
&& this.machine.rng.get_mut().random()
|
||||
{
|
||||
count / 2
|
||||
} else {
|
||||
count
|
||||
};
|
||||
|
||||
let finish = {
|
||||
let dest = dest.clone();
|
||||
let dest = dest.clone();
|
||||
this.write_to_fd(
|
||||
fd,
|
||||
buf,
|
||||
count,
|
||||
offset,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
count: usize,
|
||||
@@ -447,19 +397,355 @@ fn write(
|
||||
// This must fit since `count` fits.
|
||||
this.write_int(u64::try_from(write_size).unwrap(), &dest)
|
||||
}
|
||||
Err(e) => {
|
||||
this.set_last_error_and_return(e, &dest)
|
||||
}
|
||||
Err(e) => this.set_last_error_and_return(e, &dest)
|
||||
|
||||
}}
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Vectored reads are implemented by first reading bytes from `fd`
|
||||
/// into a temporary buffer which has the combined size of all buffers in
|
||||
/// `iov`. After that we split the bytes of the combined buffer into the
|
||||
/// buffers of `iov`. This ensures that the vectored read occurs atomically.
|
||||
fn readv(
|
||||
&mut self,
|
||||
fd: &OpTy<'tcx>,
|
||||
iov: &OpTy<'tcx>,
|
||||
iovcnt: &OpTy<'tcx>,
|
||||
offset: Option<&OpTy<'tcx>>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let iov_ptr = this.read_pointer(iov)?;
|
||||
let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
|
||||
// `readv` is the same as `preadv` without an offset.
|
||||
let offset = if let Some(offset) = offset {
|
||||
if matches!(this.tcx.sess.target.os, Os::Solaris) {
|
||||
throw_unsup_format!(
|
||||
"preadv: vectored reads with offsets aren't supported on Solaris"
|
||||
)
|
||||
}
|
||||
Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check that the FD exists.
|
||||
let Some(fd) = this.machine.fds.get(fd) else {
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
|
||||
let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
|
||||
|
||||
// Read list of buffers from `iov`.
|
||||
let mut buffers = Vec::new();
|
||||
|
||||
let mut array = this.project_array_fields(&iov_ptr_mplace)?;
|
||||
while let Some((_idx, iovec)) = array.next(this)? {
|
||||
let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
|
||||
let iov_len: u64 = this
|
||||
.read_scalar(&iov_len_field)?
|
||||
.to_int(iov_len_field.layout.size)?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
|
||||
let iov_base_ptr = this.read_pointer(&iov_base_field)?;
|
||||
|
||||
buffers.push((iov_base_ptr, iov_len));
|
||||
}
|
||||
|
||||
let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
|
||||
|
||||
// Allocate a temporary buffer which has the combined size of all buffers provided in `iov`.
|
||||
let tmp_ptr: Pointer = this
|
||||
.allocate_ptr(
|
||||
Size::from_bytes(total_bytes),
|
||||
Align::ONE,
|
||||
MemoryKind::Stack,
|
||||
AllocInit::Uninit,
|
||||
)?
|
||||
.into();
|
||||
|
||||
let dest = dest.clone();
|
||||
this.read_from_fd(
|
||||
fd,
|
||||
tmp_ptr,
|
||||
usize::try_from(total_bytes).unwrap(),
|
||||
offset,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
tmp_ptr: Pointer,
|
||||
buffers: Vec<(Pointer, u64)>,
|
||||
dest: MPlaceTy<'tcx>
|
||||
} |this, result: Result<usize, IoError>| {
|
||||
let bytes_read = match result {
|
||||
Ok(size) => {
|
||||
this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
|
||||
u64::try_from(size).unwrap()
|
||||
},
|
||||
Err(e) => return this.set_last_error_and_return(e, &dest)
|
||||
};
|
||||
let mut remaining_bytes = bytes_read;
|
||||
|
||||
// Split the bytes from the temporary buffer into the buffers provided in `iov`.
|
||||
// We start at the first buffer and fill them in order, until we reach the end of the
|
||||
// initialized bytes in the temporary buffer.
|
||||
for (buffer_ptr, buffer_len) in buffers {
|
||||
// Offset temporary buffer by the amount of bytes we already copied into previous buffers.
|
||||
let tmp_ptr_with_offset =
|
||||
this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
|
||||
|
||||
// Copy at most as many bytes as the buffer fits but without reading
|
||||
// any uninitialized bytes from the temporary buffer.
|
||||
let copy_amount = buffer_len.min(remaining_bytes);
|
||||
this.mem_copy(
|
||||
tmp_ptr_with_offset,
|
||||
buffer_ptr,
|
||||
Size::from_bytes(copy_amount),
|
||||
// The buffers are guaranteed to not overlap because we just newly allocated
|
||||
// the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be
|
||||
// within those boundaries.
|
||||
true,
|
||||
)?;
|
||||
|
||||
remaining_bytes = remaining_bytes.strict_sub(copy_amount);
|
||||
if remaining_bytes == 0 {
|
||||
// We don't have anything left to copy; exit the loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Vectored writes are implemented by first writing the bytes from all
|
||||
/// buffers of `iov` into a combined temporary buffer and then writing this
|
||||
/// combined buffer into `fd`. This ensures that the vectored write occurs atomically.
|
||||
fn writev(
|
||||
&mut self,
|
||||
fd: &OpTy<'tcx>,
|
||||
iov: &OpTy<'tcx>,
|
||||
iovcnt: &OpTy<'tcx>,
|
||||
offset: Option<&OpTy<'tcx>>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let iov_ptr = this.read_pointer(iov)?;
|
||||
let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
|
||||
// `writev` is the same as `pwritev` without an offset.
|
||||
let offset = if let Some(offset) = offset {
|
||||
if matches!(this.tcx.sess.target.os, Os::Solaris) {
|
||||
throw_unsup_format!(
|
||||
"pwritev: vectored writes with offsets aren't supported on Solaris"
|
||||
)
|
||||
}
|
||||
Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check that the FD exists.
|
||||
let Some(fd) = this.machine.fds.get(fd) else {
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
|
||||
let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
|
||||
|
||||
// Read list of buffers from `iov`.
|
||||
let mut buffers = Vec::new();
|
||||
|
||||
let mut array = this.project_array_fields(&iov_ptr_mplace)?;
|
||||
while let Some((_idx, iovec)) = array.next(this)? {
|
||||
let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
|
||||
let iov_len: u64 = this
|
||||
.read_scalar(&iov_len_field)?
|
||||
.to_int(iov_len_field.layout.size)?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
|
||||
let iov_base_ptr = this.read_pointer(&iov_base_field)?;
|
||||
|
||||
buffers.push((iov_base_ptr, iov_len));
|
||||
}
|
||||
|
||||
let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
|
||||
|
||||
// Allocate a temporary buffer which has the combined size of all buffers provided in `iov`.
|
||||
let tmp_ptr: Pointer = this
|
||||
.allocate_ptr(
|
||||
Size::from_bytes(total_bytes),
|
||||
Align::ONE,
|
||||
MemoryKind::Stack,
|
||||
AllocInit::Uninit,
|
||||
)?
|
||||
.into();
|
||||
|
||||
// Copy the bytes from all buffers provided in `iov` into the temporary buffer.
|
||||
// We start at the first buffer and then continue buffer by buffer.
|
||||
let mut bytes_copied: u64 = 0;
|
||||
for (buffer_ptr, buffer_len) in buffers {
|
||||
// Offset temporary buffer by the amount of bytes we already copied from previous buffers.
|
||||
let tmp_ptr_with_offset =
|
||||
this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
|
||||
|
||||
this.mem_copy(
|
||||
buffer_ptr,
|
||||
tmp_ptr_with_offset,
|
||||
Size::from_bytes(buffer_len),
|
||||
// The buffers are guaranteed to not overlap because we just newly allocated
|
||||
// the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be
|
||||
// within those boundaries.
|
||||
true,
|
||||
)?;
|
||||
|
||||
bytes_copied = bytes_copied.strict_add(buffer_len);
|
||||
}
|
||||
|
||||
let dest = dest.clone();
|
||||
// Write bytes from the temporary buffer. This ensures the write is atomic.
|
||||
this.write_to_fd(
|
||||
fd,
|
||||
tmp_ptr,
|
||||
usize::try_from(total_bytes).unwrap(),
|
||||
offset,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
tmp_ptr: Pointer,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
|this, result: Result<usize, IoError>| {
|
||||
this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
|
||||
match result {
|
||||
Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
|
||||
Err(e) => this.set_last_error_and_return(e, &dest)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
/// Read `len` bytes from the `fd` file description at `offset` into the buffer
|
||||
/// pointed to by `ptr`.
|
||||
/// If `offset` is [`Some`], the read occurs at the given absolute position rather
|
||||
/// than the current file position (`read_at` semantics rather than `read`).
|
||||
/// `finish` will be invoked when the read is done (which might be way after
|
||||
/// this function returns as the read may block).
|
||||
fn read_from_fd(
|
||||
&mut self,
|
||||
fd: DynFileDescriptionRef,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
offset: Option<i128>,
|
||||
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// Handle the zero-sized case. The man page says:
|
||||
// > If count is zero, read() may detect the errors described below. In the absence of any
|
||||
// > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
|
||||
// > and has no other effects.
|
||||
if len == 0 {
|
||||
return finish.call(this, Ok(0));
|
||||
}
|
||||
|
||||
// Non-deterministically decide to further reduce the length, simulating a partial read (but
|
||||
// never to 0, that would indicate EOF).
|
||||
let len = if this.machine.short_fd_operations
|
||||
&& fd.short_fd_operations()
|
||||
&& len >= 2
|
||||
&& this.machine.rng.get_mut().random()
|
||||
{
|
||||
len / 2 // since `len` is at least 2, the result is still at least 1
|
||||
} else {
|
||||
len
|
||||
};
|
||||
|
||||
match offset {
|
||||
None => fd.write(communicate, buf, count, this, finish)?,
|
||||
None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
|
||||
Some(offset) => {
|
||||
let Ok(offset) = u64::try_from(offset) else {
|
||||
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
|
||||
return finish.call(this, Err(LibcError("EINVAL")));
|
||||
};
|
||||
fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
|
||||
fd.as_unix(this).pread(
|
||||
this.machine.communicate(),
|
||||
offset,
|
||||
ptr,
|
||||
len,
|
||||
this,
|
||||
finish,
|
||||
)?
|
||||
}
|
||||
};
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Write `len` bytes at `offset` from the buffer pointed to by `ptr` into the `fd`
|
||||
/// file description.
|
||||
/// If `offset` is [`Some`], the write occurs at the given absolute position rather
|
||||
/// than the current file position (`write_at` semantics rather than `write`).
|
||||
/// `finish` will be invoked when the write is done (which might be way after
|
||||
/// this function returns as the write may block).
|
||||
fn write_to_fd(
|
||||
&mut self,
|
||||
fd: DynFileDescriptionRef,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
offset: Option<i128>,
|
||||
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// Handle the zero-sized case. The man page says:
|
||||
// > If count is zero and fd refers to a regular file, then write() may return a failure
|
||||
// > status if one of the errors below is detected. If no errors are detected, or error
|
||||
// > detection is not performed, 0 is returned without causing any other effect. If count
|
||||
// > is zero and fd refers to a file other than a regular file, the results are not
|
||||
// > specified.
|
||||
if len == 0 {
|
||||
// For now let's not open the can of worms of what exactly "not specified" could mean...
|
||||
return finish.call(this, Ok(0));
|
||||
}
|
||||
|
||||
// Non-deterministically decide to further reduce the length, simulating a partial write.
|
||||
// We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
|
||||
// but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
|
||||
let len = if this.machine.short_fd_operations
|
||||
&& fd.short_fd_operations()
|
||||
&& len >= 2
|
||||
&& this.machine.rng.get_mut().random()
|
||||
{
|
||||
len / 2
|
||||
} else {
|
||||
len
|
||||
};
|
||||
|
||||
match offset {
|
||||
None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
|
||||
Some(offset) => {
|
||||
let Ok(offset) = u64::try_from(offset) else {
|
||||
return finish.call(this, Err(LibcError("EINVAL")));
|
||||
};
|
||||
fd.as_unix(this).pwrite(
|
||||
this.machine.communicate(),
|
||||
ptr,
|
||||
len,
|
||||
offset,
|
||||
this,
|
||||
finish,
|
||||
)?
|
||||
}
|
||||
};
|
||||
interp_ok(())
|
||||
|
||||
@@ -226,8 +226,25 @@ fn emulate_foreign_item_inner(
|
||||
trace!("Called write({:?}, {:?}, {:?})", fd, buf, count);
|
||||
this.write(fd, buf, count, None, dest)?;
|
||||
}
|
||||
"readv" => {
|
||||
let [fd, iov, iovcnt] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *const _, i32) -> isize),
|
||||
link_name,
|
||||
abi,
|
||||
args,
|
||||
)?;
|
||||
this.readv(fd, iov, iovcnt, None, dest)?;
|
||||
}
|
||||
"writev" => {
|
||||
let [fd, iov, iovcnt] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *const _, i32) -> isize),
|
||||
link_name,
|
||||
abi,
|
||||
args,
|
||||
)?;
|
||||
this.writev(fd, iov, iovcnt, None, dest)?;
|
||||
}
|
||||
"pread" => {
|
||||
// FIXME: This does not have a direct test (#3179).
|
||||
let [fd, buf, count, offset] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off_t) -> isize),
|
||||
link_name,
|
||||
@@ -241,7 +258,6 @@ fn emulate_foreign_item_inner(
|
||||
this.read(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"pwrite" => {
|
||||
// FIXME: This does not have a direct test (#3179).
|
||||
let [fd, buf, n, offset] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *const _, usize, libc::off_t) -> isize),
|
||||
link_name,
|
||||
@@ -255,6 +271,25 @@ fn emulate_foreign_item_inner(
|
||||
trace!("Called pwrite({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
|
||||
this.write(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"preadv" => {
|
||||
let [fd, iov, iovcnt, offset] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *const _, i32, libc::off_t) -> isize),
|
||||
link_name,
|
||||
abi,
|
||||
args,
|
||||
)?;
|
||||
this.readv(fd, iov, iovcnt, Some(offset), dest)?;
|
||||
}
|
||||
"pwritev" => {
|
||||
let [fd, iov, iovcnt, offset] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32, *const _, i32, libc::off_t) -> isize),
|
||||
link_name,
|
||||
abi,
|
||||
args,
|
||||
)?;
|
||||
this.writev(fd, iov, iovcnt, Some(offset), dest)?;
|
||||
}
|
||||
|
||||
"close" => {
|
||||
let [fd] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(i32) -> i32),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::ptr;
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
@@ -14,6 +15,8 @@
|
||||
#[path = "../../utils/libc.rs"]
|
||||
mod libc_utils;
|
||||
|
||||
use libc_utils::errno_result;
|
||||
|
||||
fn main() {
|
||||
test_dup();
|
||||
test_dup_stdout_stderr();
|
||||
@@ -55,6 +58,16 @@ fn main() {
|
||||
test_statx_on_file_descriptor();
|
||||
#[cfg(target_os = "linux")]
|
||||
test_statx_empty_path_on_pipe();
|
||||
test_readv();
|
||||
test_readv_empty_bufs();
|
||||
#[cfg(not(target_os = "solaris"))]
|
||||
test_preadv();
|
||||
test_pread();
|
||||
test_writev();
|
||||
test_writev_empty_bufs();
|
||||
#[cfg(not(target_os = "solaris"))]
|
||||
test_pwritev();
|
||||
test_pwrite();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -852,3 +865,277 @@ pub fn check_stat_fields(stat: &libc::stat) {
|
||||
let _st_mtime_nsec = stat.st_mtime_nsec;
|
||||
let _st_ctime_nsec = stat.st_ctime_nsec;
|
||||
}
|
||||
|
||||
/// Test vectored reads with multiple buffers.
|
||||
fn test_readv() {
|
||||
let file_contents = [1u8, 2, 3, 4, 5, 6];
|
||||
let path = utils::prepare_with_content("pass-libc-readv.txt", &file_contents);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut buffer = [0u8; 4];
|
||||
let (buffer1, buffer2) = buffer.split_at_mut(2);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
let bytes_read = unsafe {
|
||||
errno_result(libc::readv(fd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() as usize
|
||||
};
|
||||
|
||||
// The vectored read should read at least one byte.
|
||||
assert!(bytes_read > 0);
|
||||
assert_eq!(&buffer[0..bytes_read], &file_contents[0..bytes_read]);
|
||||
}
|
||||
|
||||
/// Test that vectored reads without any buffers return zero.
|
||||
fn test_readv_empty_bufs() {
|
||||
let path = utils::prepare_with_content("pass-libc-readv-empty-bufs.txt", &[1u8, 2, 3]);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
unsafe { assert_eq!(errno_result(libc::readv(fd, ptr::null::<libc::iovec>(), 0)).unwrap(), 0) };
|
||||
}
|
||||
|
||||
/// Test vectored reads with multiple buffers and a byte offset.
|
||||
///
|
||||
/// **Note**: We skip this test on Solaris targets because Solaris
|
||||
/// doesn't have `preadv`.
|
||||
#[cfg(not(target_os = "solaris"))]
|
||||
fn test_preadv() {
|
||||
let file_contents = [1u8, 2, 3, 4, 5, 6];
|
||||
let path = utils::prepare_with_content("pass-libc-preadv.txt", &file_contents);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut buffer = [0u8; 4];
|
||||
let (buffer1, buffer2) = buffer.split_at_mut(2);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
// Read with a 2 byte offset.
|
||||
const OFFSET: usize = 2;
|
||||
let bytes_read = unsafe {
|
||||
errno_result(libc::preadv(
|
||||
fd,
|
||||
iov.as_ptr(),
|
||||
iov.len() as libc::c_int,
|
||||
OFFSET as libc::off_t,
|
||||
))
|
||||
.unwrap() as usize
|
||||
};
|
||||
|
||||
// The vectored read should read at least one byte.
|
||||
assert!(bytes_read > 0);
|
||||
// The vectored read should start at the provided byte offset.
|
||||
assert_eq!(&buffer[0..bytes_read], &file_contents[OFFSET..(bytes_read + OFFSET)]);
|
||||
}
|
||||
|
||||
/// Test reading with an offset.
|
||||
fn test_pread() {
|
||||
let file_contents = [1u8, 2, 3, 4, 5, 6];
|
||||
let path = utils::prepare_with_content("pass-libc-pread.txt", &file_contents);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut buffer = [0u8; 2];
|
||||
|
||||
// Read with a 2 byte offset.
|
||||
const OFFSET: usize = 2;
|
||||
let bytes_read = unsafe {
|
||||
errno_result(libc::pread(
|
||||
fd,
|
||||
buffer.as_mut_ptr().cast(),
|
||||
buffer.len() as libc::size_t,
|
||||
OFFSET as libc::off_t,
|
||||
))
|
||||
.unwrap() as usize
|
||||
};
|
||||
|
||||
// We should read at least one byte.
|
||||
assert!(bytes_read > 0);
|
||||
// The read should start at the provided byte offset.
|
||||
assert_eq!(&buffer[0..bytes_read], &file_contents[OFFSET..(bytes_read + OFFSET)]);
|
||||
}
|
||||
|
||||
/// Test vectored writes with multiple buffers.
|
||||
fn test_writev() {
|
||||
let path = utils::prepare_with_content("pass-libc-writev.txt", &[]);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut write_buffer = [1u8, 2, 3, 4, 5, 6];
|
||||
let (buffer1, buffer2) = write_buffer.split_at_mut(3);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
let bytes_written = unsafe {
|
||||
errno_result(libc::writev(fd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() as usize
|
||||
};
|
||||
// The vectored write should write at least one byte.
|
||||
assert!(bytes_written > 0);
|
||||
|
||||
// Open the FD again in readonly mode and with an unadvanced pointer.
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut read_buffer = [0u8; 16];
|
||||
unsafe {
|
||||
libc_utils::read_exact_generic(
|
||||
read_buffer.as_mut_ptr().cast(),
|
||||
bytes_written as libc::size_t,
|
||||
libc_utils::Retry::NoRetry,
|
||||
|buf, count| libc::read(fd, buf, count),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(&write_buffer[0..bytes_written], &read_buffer[0..bytes_written]);
|
||||
}
|
||||
|
||||
/// Test that vectored writes without any buffers return zero.
|
||||
fn test_writev_empty_bufs() {
|
||||
let path = utils::prepare_with_content("pass-libc-writev-empty-bufs.txt", &[1u8, 2, 3]);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
unsafe {
|
||||
assert_eq!(errno_result(libc::writev(fd, ptr::null::<libc::iovec>(), 0)).unwrap(), 0)
|
||||
};
|
||||
}
|
||||
|
||||
/// Test vectored writes with multiple buffers and a byte offset.
|
||||
///
|
||||
/// **Note**: We skip this test on Solaris targets because Solaris
|
||||
/// doesn't have `pwritev`.
|
||||
#[cfg(not(target_os = "solaris"))]
|
||||
fn test_pwritev() {
|
||||
let path = utils::prepare_with_content("pass-libc-pwritev.txt", &[]);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut write_buffer = [1u8, 2, 3, 4, 5, 6];
|
||||
let (buffer1, buffer2) = write_buffer.split_at_mut(3);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
// Write with a 2 byte offset.
|
||||
const OFFSET: usize = 2;
|
||||
let bytes_written = unsafe {
|
||||
errno_result(libc::pwritev(
|
||||
fd,
|
||||
iov.as_ptr(),
|
||||
iov.len() as libc::c_int,
|
||||
OFFSET as libc::off_t,
|
||||
))
|
||||
.unwrap() as usize
|
||||
};
|
||||
// The vectored write should write at least one byte.
|
||||
assert!(bytes_written > 0);
|
||||
|
||||
// Open the FD again in readonly mode and with an unadvanced pointer.
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut read_buffer = [0u8; 16];
|
||||
// Read offset + bytes written.
|
||||
unsafe {
|
||||
libc_utils::read_exact_generic(
|
||||
read_buffer.as_mut_ptr().cast(),
|
||||
(bytes_written + OFFSET) as libc::size_t,
|
||||
libc_utils::Retry::NoRetry,
|
||||
|buf, count| libc::read(fd, buf, count),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// The vectored write should start at the provided byte offset.
|
||||
assert_eq!(&write_buffer[0..bytes_written], &read_buffer[OFFSET..(bytes_written + OFFSET)]);
|
||||
}
|
||||
|
||||
/// Test writing with an offset.
|
||||
fn test_pwrite() {
|
||||
let path = utils::prepare_with_content("pass-libc-pwritev.txt", &[]);
|
||||
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let write_buffer = [1u8, 2, 3, 4, 5, 6];
|
||||
|
||||
// Write with a 2 byte offset.
|
||||
const OFFSET: usize = 2;
|
||||
let bytes_written = unsafe {
|
||||
errno_result(libc::pwrite(
|
||||
fd,
|
||||
write_buffer.as_ptr().cast(),
|
||||
write_buffer.len() as libc::size_t,
|
||||
OFFSET as libc::off_t,
|
||||
))
|
||||
.unwrap() as usize
|
||||
};
|
||||
// We should write at least one byte.
|
||||
assert!(bytes_written > 0);
|
||||
|
||||
// Open the FD again in readonly mode and with an unadvanced pointer.
|
||||
let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) };
|
||||
assert_ne!(fd, -1);
|
||||
|
||||
let mut read_buffer = [0u8; 16];
|
||||
// Read offset + bytes written.
|
||||
unsafe {
|
||||
libc_utils::read_exact_generic(
|
||||
read_buffer.as_mut_ptr().cast(),
|
||||
(bytes_written + OFFSET) as libc::size_t,
|
||||
libc_utils::Retry::NoRetry,
|
||||
|buf, count| libc::read(fd, buf, count),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// The write should start at the provided byte offset.
|
||||
assert_eq!(&write_buffer[0..bytes_written], &read_buffer[OFFSET..(bytes_written + OFFSET)]);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
mod utils;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{ptr, thread};
|
||||
|
||||
use libc_utils::*;
|
||||
|
||||
@@ -37,6 +37,8 @@ fn main() {
|
||||
test_accept_connect();
|
||||
test_send_peek_recv();
|
||||
test_write_read();
|
||||
test_readv();
|
||||
test_writev();
|
||||
|
||||
test_getsockname_ipv4();
|
||||
test_getsockname_ipv4_random_port();
|
||||
@@ -342,6 +344,77 @@ fn test_write_read() {
|
||||
server_thread.join().unwrap();
|
||||
}
|
||||
|
||||
/// Test vectored reads with multiple buffers on a connected socket.
|
||||
fn test_readv() {
|
||||
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
|
||||
let client_sockfd =
|
||||
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
|
||||
|
||||
net::connect_ipv4(client_sockfd, addr).unwrap();
|
||||
let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap();
|
||||
|
||||
libc_utils::write_all(peerfd, TEST_BYTES).unwrap();
|
||||
|
||||
let mut buffer = [0u8; TEST_BYTES.len()];
|
||||
let (buffer1, buffer2) = buffer.split_at_mut(2);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
let num = unsafe {
|
||||
errno_result(libc::readv(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap()
|
||||
};
|
||||
assert_eq!(num as usize, TEST_BYTES.len());
|
||||
// The vectored read should read the entire buffer because we don't have
|
||||
// short reads on sockets.
|
||||
assert_eq!(&buffer, TEST_BYTES);
|
||||
}
|
||||
|
||||
/// Test vectored writes with multiple buffers on a connected socket.
|
||||
fn test_writev() {
|
||||
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
|
||||
let client_sockfd =
|
||||
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
|
||||
|
||||
net::connect_ipv4(client_sockfd, addr).unwrap();
|
||||
let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap();
|
||||
|
||||
let mut write_buffer = TEST_BYTES.to_owned();
|
||||
let (buffer1, buffer2) = write_buffer.split_at_mut(3);
|
||||
|
||||
let iov = [
|
||||
libc::iovec { iov_base: ptr::null_mut::<libc::c_void>(), iov_len: 0 as libc::size_t },
|
||||
libc::iovec {
|
||||
iov_base: buffer1.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer1.len() as libc::size_t,
|
||||
},
|
||||
libc::iovec {
|
||||
iov_base: buffer2.as_mut_ptr().cast::<libc::c_void>(),
|
||||
iov_len: buffer2.len() as libc::size_t,
|
||||
},
|
||||
];
|
||||
|
||||
let num = unsafe {
|
||||
errno_result(libc::writev(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap()
|
||||
};
|
||||
assert_eq!(num as usize, TEST_BYTES.len());
|
||||
|
||||
let mut buffer = [0u8; TEST_BYTES.len()];
|
||||
libc_utils::read_exact(peerfd, &mut buffer).unwrap();
|
||||
// The vectored write should write the entire buffer because we don't have
|
||||
// short writes on sockets.
|
||||
assert_eq!(&buffer, TEST_BYTES);
|
||||
}
|
||||
|
||||
/// Test the `getsockname` syscall on an IPv4 socket which is bound.
|
||||
/// The `getsockname` syscall should return the same address as to
|
||||
/// which the socket was bound to.
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
#![feature(io_error_more)]
|
||||
#![feature(io_error_uncategorized)]
|
||||
#![cfg_attr(unix, feature(unix_file_vectored_at))]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{
|
||||
self, File, OpenOptions, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename,
|
||||
};
|
||||
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
|
||||
use std::io::{
|
||||
Error, ErrorKind, IoSlice, IoSliceMut, IsTerminal, Read, Result, Seek, SeekFrom, Write,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
@@ -39,6 +42,9 @@ fn main() {
|
||||
test_pread_pwrite();
|
||||
#[cfg(not(any(target_os = "solaris", target_os = "android")))]
|
||||
test_flock();
|
||||
test_readv_writev();
|
||||
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "android"))))]
|
||||
test_preadv_pwritev();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,3 +433,82 @@ fn test_flock() {
|
||||
// Unlock exclusive lock
|
||||
file1.unlock().unwrap();
|
||||
}
|
||||
|
||||
/// Test vectored reads and vectored writes.
|
||||
fn test_readv_writev() {
|
||||
let bytes = b"hello world!";
|
||||
let path = utils::prepare_with_content("miri_test_fs_readv_writev.txt", bytes);
|
||||
let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
|
||||
|
||||
let mut read_buffer = [0u8; 10];
|
||||
let (buffer1, buffer2) = read_buffer.split_at_mut(5);
|
||||
|
||||
let bytes_read =
|
||||
f.read_vectored(&mut [IoSliceMut::new(buffer1), IoSliceMut::new(buffer2)]).unwrap();
|
||||
|
||||
// Vectored read should read at least a byte.
|
||||
assert!(bytes_read > 0);
|
||||
assert_eq!(read_buffer[0..bytes_read], bytes[0..bytes_read]);
|
||||
|
||||
let write_buffer = b"some additional bytes";
|
||||
let (buffer1, buffer2) = write_buffer.split_at(write_buffer.len() / 2);
|
||||
|
||||
let bytes_written = f.write_vectored(&[IoSlice::new(buffer1), IoSlice::new(buffer2)]).unwrap();
|
||||
|
||||
// Vectored write should write at least a byte.
|
||||
assert!(bytes_written > 0);
|
||||
|
||||
// Reset file cursor to read the written bytes.
|
||||
f.seek(SeekFrom::Start(bytes_read as u64)).unwrap();
|
||||
let mut written_bytes = vec![0u8; bytes_written];
|
||||
f.read_exact(&mut written_bytes).unwrap();
|
||||
assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]);
|
||||
}
|
||||
|
||||
/// Test vectored reads and vectored writes with byte offsets.
|
||||
///
|
||||
/// **Note**: We skip this test on Solaris and Android targets. This is
|
||||
/// because Solaris doesn't have `preadv`/`pwritev`, and on Android the
|
||||
/// standard library uses `syscall(...)` for vectored reads/writes with
|
||||
/// offsets because older Android versions also didn't have `preadv`/`pwritev`.
|
||||
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "android"))))]
|
||||
fn test_preadv_pwritev() {
|
||||
use std::os::unix::fs::FileExt;
|
||||
|
||||
let bytes = b"hello world!";
|
||||
let path = utils::prepare_with_content("miri_test_fs_preadv_pwritev.txt", bytes);
|
||||
let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
|
||||
|
||||
const OFFSET: usize = 2;
|
||||
|
||||
let mut read_buffer = [0u8; 10];
|
||||
let (buffer1, buffer2) = read_buffer.split_at_mut(5);
|
||||
|
||||
let bytes_read = f
|
||||
.read_vectored_at(&mut [IoSliceMut::new(buffer1), IoSliceMut::new(buffer2)], OFFSET as u64)
|
||||
.unwrap();
|
||||
|
||||
// Vectored read should read at least a byte at the provided offset.
|
||||
assert!(bytes_read > 0);
|
||||
assert_eq!(read_buffer[0..bytes_read], bytes[OFFSET..(bytes_read + OFFSET)]);
|
||||
|
||||
let write_buffer = b"some additional bytes";
|
||||
let (buffer1, buffer2) = write_buffer.split_at(write_buffer.len() / 2);
|
||||
|
||||
let bytes_written = f
|
||||
.write_vectored_at(
|
||||
&[IoSlice::new(buffer1), IoSlice::new(buffer2)],
|
||||
(bytes.len() + OFFSET) as u64,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Vectored write should write at least a byte at the provided offset.
|
||||
assert!(bytes_written > 0);
|
||||
|
||||
// Reset file cursor to read the written bytes. We move the cursor
|
||||
// to include the offset.
|
||||
f.seek(SeekFrom::Start((bytes.len() + OFFSET) as u64)).unwrap();
|
||||
let mut written_bytes = vec![0u8; bytes_written];
|
||||
f.read_exact(&mut written_bytes).unwrap();
|
||||
assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user