Skip to content

Commit

Permalink
Auto merge of rust-lang#89518 - a1phyr:unix_file_vectored_at, r=worki…
Browse files Browse the repository at this point in the history
…ngjubilee

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: rust-lang#89517
  • Loading branch information
bors committed Mar 4, 2023
2 parents 70adb4e + 92f35b3 commit 0fbfc3e
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 3 deletions.
30 changes: 30 additions & 0 deletions library/std/src/os/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ use crate::sealed::Sealed;
#[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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -155,6 +169,16 @@ pub trait FileExt {
#[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
Expand Down Expand Up @@ -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`].
Expand Down
57 changes: 57 additions & 0 deletions library/std/src/os/unix/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -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);
}
184 changes: 181 additions & 3 deletions library/std/src/sys/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl FileDesc {
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,
)
})?;
Expand All @@ -107,7 +107,7 @@ impl FileDesc {

#[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]
Expand Down Expand Up @@ -153,6 +153,95 @@ impl FileDesc {
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(
Expand All @@ -178,7 +267,7 @@ impl FileDesc {

#[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]
Expand All @@ -203,6 +292,95 @@ impl FileDesc {
}
}

#[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",
Expand Down
8 changes: 8 additions & 0 deletions library/std/src/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,10 @@ impl File {
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)
}
Expand All @@ -1149,6 +1153,10 @@ impl File {
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(())
}
Expand Down

0 comments on commit 0fbfc3e

Please sign in to comment.