From c4167dcc0a19d4c0e4f0c5e011023547ff9f92e7 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 26 Dec 2022 20:08:41 -0800 Subject: [PATCH] Support `preadv2` and `pwritev2` on all non-glibc Linux ABIs. Use `libc::syscall` on ABIs where libc doesn't have bindings for `preadv2` and `pwritev2`. --- src/backend/libc/io/syscalls.rs | 34 +++++++++++--- src/backend/libc/io/types.rs | 15 +++---- src/io/read_write.rs | 8 ++++ tests/io/read_write.rs | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/backend/libc/io/syscalls.rs b/src/backend/libc/io/syscalls.rs index 3774e700a..d083fc520 100644 --- a/src/backend/libc/io/syscalls.rs +++ b/src/backend/libc/io/syscalls.rs @@ -167,7 +167,7 @@ pub(crate) fn preadv2( } /// At present, `libc` only has `preadv2` defined for glibc. On other -/// ABIs, `ReadWriteFlags` has no flags defined, and we use plain `preadv`. +/// ABIs, use `libc::syscall`. #[cfg(any( target_os = "android", all(target_os = "linux", not(target_env = "gnu")), @@ -179,8 +179,19 @@ pub(crate) fn preadv2( offset: u64, flags: ReadWriteFlags, ) -> io::Result { - assert!(flags.is_empty()); - preadv(fd, bufs, offset) + // Silently cast; we'll get `EINVAL` if the value is negative. + let offset = offset as i64; + let nread = unsafe { + ret_ssize_t(libc::syscall( + libc::SYS_preadv2, + borrowed_fd(fd), + bufs.as_ptr().cast::(), + min(bufs.len(), max_iov()) as c::c_int, + offset, + flags.bits(), + ) as c::ssize_t)? + }; + Ok(nread as usize) } #[cfg(all(target_os = "linux", target_env = "gnu"))] @@ -205,7 +216,7 @@ pub(crate) fn pwritev2( } /// At present, `libc` only has `pwritev2` defined for glibc. On other -/// ABIs, `ReadWriteFlags` has no flags defined, and we use plain `pwritev`. +/// ABIs, use `libc::syscall`. #[cfg(any( target_os = "android", all(target_os = "linux", not(target_env = "gnu")), @@ -217,8 +228,19 @@ pub(crate) fn pwritev2( offset: u64, flags: ReadWriteFlags, ) -> io::Result { - assert!(flags.is_empty()); - pwritev(fd, bufs, offset) + // Silently cast; we'll get `EINVAL` if the value is negative. + let offset = offset as i64; + let nwritten = unsafe { + ret_ssize_t(libc::syscall( + libc::SYS_pwritev2, + borrowed_fd(fd), + bufs.as_ptr().cast::(), + min(bufs.len(), max_iov()) as c::c_int, + offset, + flags.bits(), + ) as c::ssize_t)? + }; + Ok(nwritten as usize) } // These functions are derived from Rust's library/std/src/sys/unix/fd.rs at diff --git a/src/backend/libc/io/types.rs b/src/backend/libc/io/types.rs index 46d5f6332..9716b27df 100644 --- a/src/backend/libc/io/types.rs +++ b/src/backend/libc/io/types.rs @@ -22,20 +22,15 @@ bitflags! { /// [`pwritev2`]: crate::io::pwritev pub struct ReadWriteFlags: c::c_int { /// `RWF_DSYNC` (since Linux 4.7) - #[cfg(all(target_os = "linux", target_env = "gnu"))] - const DSYNC = c::RWF_DSYNC; + const DSYNC = linux_raw_sys::general::RWF_DSYNC as c::c_int; /// `RWF_HIPRI` (since Linux 4.6) - #[cfg(all(target_os = "linux", target_env = "gnu"))] - const HIPRI = c::RWF_HIPRI; + const HIPRI = linux_raw_sys::general::RWF_HIPRI as c::c_int; /// `RWF_SYNC` (since Linux 4.7) - #[cfg(all(target_os = "linux", target_env = "gnu"))] - const SYNC = c::RWF_SYNC; + const SYNC = linux_raw_sys::general::RWF_SYNC as c::c_int; /// `RWF_NOWAIT` (since Linux 4.14) - #[cfg(all(target_os = "linux", target_env = "gnu"))] - const NOWAIT = c::RWF_NOWAIT; + const NOWAIT = linux_raw_sys::general::RWF_NOWAIT as c::c_int; /// `RWF_APPEND` (since Linux 4.16) - #[cfg(all(target_os = "linux", target_env = "gnu"))] - const APPEND = c::RWF_APPEND; + const APPEND = linux_raw_sys::general::RWF_APPEND as c::c_int; } } diff --git a/src/io/read_write.rs b/src/io/read_write.rs index 1a4d37b65..45b11cd50 100644 --- a/src/io/read_write.rs +++ b/src/io/read_write.rs @@ -62,6 +62,10 @@ pub fn pread(fd: Fd, buf: &mut [u8], offset: u64) -> io::Result /// `pwrite(fd, bufs)`—Writes to a file at a given position. /// +/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD, +/// if the file is opened in append mode, this ignores the offset appends the +/// data to the end of the file. +/// /// # References /// - [POSIX] /// - [Linux] @@ -121,6 +125,10 @@ pub fn preadv(fd: Fd, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io: /// `pwritev(fd, bufs, offset)`—Writes to a file at a given position from /// multiple buffers. /// +/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD, +/// if the file is opened in append mode, this ignores the offset appends the +/// data to the end of the file. +/// /// # References /// - [Linux] /// diff --git a/tests/io/read_write.rs b/tests/io/read_write.rs index b5b343aa9..a891f5619 100644 --- a/tests/io/read_write.rs +++ b/tests/io/read_write.rs @@ -118,3 +118,82 @@ fn test_readwrite() { read(&foo, &mut buf).unwrap(); assert_eq!(&buf, b"world"); } + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[test] +fn test_rwf_values() { + // We use the kernel's values for these flags; check that libc doesn't + // have different values. + assert_eq!( + rustix::io::ReadWriteFlags::APPEND.bits() as i32, + libc::RWF_APPEND + ); + assert_eq!( + rustix::io::ReadWriteFlags::DSYNC.bits() as i32, + libc::RWF_DSYNC + ); + assert_eq!( + rustix::io::ReadWriteFlags::HIPRI.bits() as i32, + libc::RWF_HIPRI + ); + assert_eq!( + rustix::io::ReadWriteFlags::NOWAIT.bits() as i32, + libc::RWF_NOWAIT + ); + assert_eq!( + rustix::io::ReadWriteFlags::SYNC.bits() as i32, + libc::RWF_SYNC + ); +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "fs")] +#[test] +fn test_pwritev2() { + use rustix::fs::{cwd, openat, seek, Mode, OFlags}; + use rustix::io::{preadv2, pwritev2, writev, ReadWriteFlags, SeekFrom}; + + let tmp = tempfile::tempdir().unwrap(); + let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap(); + let foo = openat( + &dir, + "foo", + OFlags::RDWR | OFlags::CREATE | OFlags::TRUNC, + Mode::RUSR | Mode::WUSR, + ) + .unwrap(); + + writev(&foo, &[IoSlice::new(b"hello")]).unwrap(); + seek(&foo, SeekFrom::Start(0)).unwrap(); + + // pwritev2 to append with a 0 offset: don't update the current position. + match pwritev2(&foo, &[IoSlice::new(b"world")], 0, ReadWriteFlags::APPEND) { + Ok(_) => {} + Err(rustix::io::Errno::NOSYS) => return, // skip the rest of the test if we don't have `pwritev2` + Err(err) => Err(err).unwrap(), + } + assert_eq!(seek(&foo, SeekFrom::Current(0)).unwrap(), 0); + + // pwritev2 to append with a !0 offset: do update the current position. + pwritev2(&foo, &[IoSlice::new(b"world")], !0, ReadWriteFlags::APPEND).unwrap(); + assert_eq!(seek(&foo, SeekFrom::Current(0)).unwrap(), 15); + + seek(&foo, SeekFrom::Start(0)).unwrap(); + let mut buf = [0_u8; 5]; + preadv2( + &foo, + &mut [IoSliceMut::new(&mut buf)], + 0, + ReadWriteFlags::empty(), + ) + .unwrap(); + assert_eq!(&buf, b"hello"); + preadv2( + &foo, + &mut [IoSliceMut::new(&mut buf)], + 5, + ReadWriteFlags::empty(), + ) + .unwrap(); + assert_eq!(&buf, b"world"); +}