Skip to content

Commit

Permalink
Support preadv2 and pwritev2 on all non-glibc Linux ABIs. (#489)
Browse files Browse the repository at this point in the history
Use `libc::syscall` on ABIs where libc doesn't have bindings for
`preadv2` and `pwritev2`.
  • Loading branch information
sunfishcode committed Dec 27, 2022
1 parent 811e5ea commit 53374d9
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 16 deletions.
34 changes: 28 additions & 6 deletions src/backend/libc/io/syscalls.rs
Expand Up @@ -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")),
Expand All @@ -179,8 +179,19 @@ pub(crate) fn preadv2(
offset: u64,
flags: ReadWriteFlags,
) -> io::Result<usize> {
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::<c::iovec>(),
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"))]
Expand All @@ -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")),
Expand All @@ -217,8 +228,19 @@ pub(crate) fn pwritev2(
offset: u64,
flags: ReadWriteFlags,
) -> io::Result<usize> {
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::<c::iovec>(),
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
Expand Down
15 changes: 5 additions & 10 deletions src/backend/libc/io/types.rs
Expand Up @@ -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;
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/io/read_write.rs
Expand Up @@ -62,6 +62,10 @@ pub fn pread<Fd: AsFd>(fd: Fd, buf: &mut [u8], offset: u64) -> io::Result<usize>

/// `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]
Expand Down Expand Up @@ -121,6 +125,10 @@ pub fn preadv<Fd: AsFd>(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]
///
Expand Down
80 changes: 80 additions & 0 deletions tests/io/read_write.rs
Expand Up @@ -118,3 +118,83 @@ 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(_) => {}
// Skip the rest of the test if we don't have `pwritev2` and `RWF_APPEND`.
Err(rustix::io::Errno::NOSYS) | Err(rustix::io::Errno::NOTSUP) => return,
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");
}

0 comments on commit 53374d9

Please sign in to comment.