Auto merge of #89518 - a1phyr:unix_file_vectored_at, r=workingjubilee

Add vectored positioned I/O on Unix

Add methods for vectored I/O with an offset on `File` for `unix` under `#![feature(unix_file_vectored_at)]`.

The new methods are wrappers around `preadv` and `pwritev`.

Tracking issue: #89517
This commit is contained in:
bors 2023-03-04 05:26:35 +00:00
commit 0fbfc3e769
4 changed files with 276 additions and 3 deletions

View File

@ -17,6 +17,10 @@
#[allow(unused_imports)]
use io::{Read, Write};
// Tests for this module
#[cfg(test)]
mod tests;
/// Unix-specific extensions to [`fs::File`].
#[stable(feature = "file_offset", since = "1.15.0")]
pub trait FileExt {
@ -54,6 +58,16 @@ pub trait FileExt {
#[stable(feature = "file_offset", since = "1.15.0")]
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
/// Like `read_at`, except that it reads into a slice of buffers.
///
/// Data is copied to fill each buffer in order, with the final buffer
/// written to possibly being only partially filled. This method must behave
/// equivalently to a single call to read with concatenated buffers.
#[unstable(feature = "unix_file_vectored_at", issue = "89517")]
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
io::default_read_vectored(|b| self.read_at(b, offset), bufs)
}
/// Reads the exact number of byte required to fill `buf` from the given offset.
///
/// The offset is relative to the start of the file and thus independent
@ -155,6 +169,16 @@ fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
#[stable(feature = "file_offset", since = "1.15.0")]
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
/// Like `write_at`, except that it writes from a slice of buffers.
///
/// Data is copied from each buffer in order, with the final buffer read
/// from possibly being only partially consumed. This method must behave as
/// a call to `write_at` with the buffers concatenated would.
#[unstable(feature = "unix_file_vectored_at", issue = "89517")]
fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result<usize> {
io::default_write_vectored(|b| self.write_at(b, offset), bufs)
}
/// Attempts to write an entire buffer starting from a given offset.
///
/// The offset is relative to the start of the file and thus independent
@ -218,9 +242,15 @@ impl FileExt for fs::File {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
self.as_inner().read_at(buf, offset)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
self.as_inner().read_vectored_at(bufs, offset)
}
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
self.as_inner().write_at(buf, offset)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result<usize> {
self.as_inner().write_vectored_at(bufs, offset)
}
}
/// Unix-specific extensions to [`fs::Permissions`].

View File

@ -0,0 +1,57 @@
use super::*;
#[test]
fn read_vectored_at() {
let msg = b"preadv is working!";
let dir = crate::sys_common::io::test::tmpdir();
let filename = dir.join("preadv.txt");
{
let mut file = fs::File::create(&filename).unwrap();
file.write_all(msg).unwrap();
}
{
let file = fs::File::open(&filename).unwrap();
let mut buf0 = [0; 4];
let mut buf1 = [0; 3];
let mut iovec = [io::IoSliceMut::new(&mut buf0), io::IoSliceMut::new(&mut buf1)];
let n = file.read_vectored_at(&mut iovec, 4).unwrap();
assert!(n == 4 || n == 7);
assert_eq!(&buf0, b"dv i");
if n == 7 {
assert_eq!(&buf1, b"s w");
}
}
}
#[test]
fn write_vectored_at() {
let msg = b"pwritev is not working!";
let dir = crate::sys_common::io::test::tmpdir();
let filename = dir.join("preadv.txt");
{
let mut file = fs::File::create(&filename).unwrap();
file.write_all(msg).unwrap();
}
let expected = {
let file = fs::File::options().write(true).open(&filename).unwrap();
let buf0 = b" ";
let buf1 = b"great ";
let iovec = [io::IoSlice::new(buf0), io::IoSlice::new(buf1)];
let n = file.write_vectored_at(&iovec, 11).unwrap();
assert!(n == 4 || n == 11);
if n == 4 { b"pwritev is working!" } else { b"pwritev is great !" }
};
let content = fs::read(&filename).unwrap();
assert_eq!(&content, expected);
}

View File

@ -98,7 +98,7 @@ pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::readv(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
)
})?;
@ -107,7 +107,7 @@ pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
#[cfg(any(target_os = "espidf", target_os = "horizon"))]
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
return crate::io::default_read_vectored(|b| self.read(b), bufs);
io::default_read_vectored(|b| self.read(b), bufs)
}
#[inline]
@ -153,6 +153,95 @@ pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
Ok(())
}
#[cfg(any(
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
)))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
io::default_read_vectored(|b| self.read_at(b, offset), bufs)
}
// We support some old Android versions that do not have `preadv` in libc,
// so we use weak linkage and fallback to a direct syscall if not available.
//
// On 32-bit targets, we don't want to deal with weird ABI issues around
// passing 64-bits parameters to syscalls, so we fallback to the default
// implementation if `preadv` is not available.
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
super::weak::syscall! {
fn preadv(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t
) -> isize
}
let ret = cvt(unsafe {
preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
// We support old MacOS and iOS versions that do not have `preadv`. There is
// no `syscall` possible in these platform.
#[cfg(any(
all(target_os = "android", target_pointer_width = "32"),
target_os = "ios",
target_os = "macos",
))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
super::weak::weak!(fn preadv64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize);
match preadv64.get() {
Some(preadv) => {
let ret = cvt(unsafe {
preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
}
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::write(
@ -178,7 +267,7 @@ pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
#[cfg(any(target_os = "espidf", target_os = "horizon"))]
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
return crate::io::default_write_vectored(|b| self.write(b), bufs);
io::default_write_vectored(|b| self.write(b), bufs)
}
#[inline]
@ -203,6 +292,95 @@ pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
}
}
#[cfg(any(
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
)))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
io::default_write_vectored(|b| self.write_at(b, offset), bufs)
}
// We support some old Android versions that do not have `pwritev` in libc,
// so we use weak linkage and fallback to a direct syscall if not available.
//
// On 32-bit targets, we don't want to deal with weird ABI issues around
// passing 64-bits parameters to syscalls, so we fallback to the default
// implementation if `pwritev` is not available.
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
super::weak::syscall! {
fn pwritev(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t
) -> isize
}
let ret = cvt(unsafe {
pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
// We support old MacOS and iOS versions that do not have `pwritev`. There is
// no `syscall` possible in these platform.
#[cfg(any(
all(target_os = "android", target_pointer_width = "32"),
target_os = "ios",
target_os = "macos",
))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
super::weak::weak!(fn pwritev64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize);
match pwritev64.get() {
Some(pwritev) => {
let ret = cvt(unsafe {
pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
}
}
#[cfg(not(any(
target_env = "newlib",
target_os = "solaris",

View File

@ -1132,6 +1132,10 @@ pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
self.0.read_buf(cursor)
}
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
self.0.read_vectored_at(bufs, offset)
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
@ -1149,6 +1153,10 @@ pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
self.0.write_at(buf, offset)
}
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
self.0.write_vectored_at(bufs, offset)
}
pub fn flush(&self) -> io::Result<()> {
Ok(())
}