Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support preadv2 and pwritev2 on all non-glibc Linux ABIs. #489

Merged
merged 1 commit into from Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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");
}