Skip to content

Commit 53374d9

Browse files
authored
Support preadv2 and pwritev2 on all non-glibc Linux ABIs. (#489)
Use `libc::syscall` on ABIs where libc doesn't have bindings for `preadv2` and `pwritev2`.
1 parent 811e5ea commit 53374d9

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

src/backend/libc/io/syscalls.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub(crate) fn preadv2(
167167
}
168168

169169
/// At present, `libc` only has `preadv2` defined for glibc. On other
170-
/// ABIs, `ReadWriteFlags` has no flags defined, and we use plain `preadv`.
170+
/// ABIs, use `libc::syscall`.
171171
#[cfg(any(
172172
target_os = "android",
173173
all(target_os = "linux", not(target_env = "gnu")),
@@ -179,8 +179,19 @@ pub(crate) fn preadv2(
179179
offset: u64,
180180
flags: ReadWriteFlags,
181181
) -> io::Result<usize> {
182-
assert!(flags.is_empty());
183-
preadv(fd, bufs, offset)
182+
// Silently cast; we'll get `EINVAL` if the value is negative.
183+
let offset = offset as i64;
184+
let nread = unsafe {
185+
ret_ssize_t(libc::syscall(
186+
libc::SYS_preadv2,
187+
borrowed_fd(fd),
188+
bufs.as_ptr().cast::<c::iovec>(),
189+
min(bufs.len(), max_iov()) as c::c_int,
190+
offset,
191+
flags.bits(),
192+
) as c::ssize_t)?
193+
};
194+
Ok(nread as usize)
184195
}
185196

186197
#[cfg(all(target_os = "linux", target_env = "gnu"))]
@@ -205,7 +216,7 @@ pub(crate) fn pwritev2(
205216
}
206217

207218
/// At present, `libc` only has `pwritev2` defined for glibc. On other
208-
/// ABIs, `ReadWriteFlags` has no flags defined, and we use plain `pwritev`.
219+
/// ABIs, use `libc::syscall`.
209220
#[cfg(any(
210221
target_os = "android",
211222
all(target_os = "linux", not(target_env = "gnu")),
@@ -217,8 +228,19 @@ pub(crate) fn pwritev2(
217228
offset: u64,
218229
flags: ReadWriteFlags,
219230
) -> io::Result<usize> {
220-
assert!(flags.is_empty());
221-
pwritev(fd, bufs, offset)
231+
// Silently cast; we'll get `EINVAL` if the value is negative.
232+
let offset = offset as i64;
233+
let nwritten = unsafe {
234+
ret_ssize_t(libc::syscall(
235+
libc::SYS_pwritev2,
236+
borrowed_fd(fd),
237+
bufs.as_ptr().cast::<c::iovec>(),
238+
min(bufs.len(), max_iov()) as c::c_int,
239+
offset,
240+
flags.bits(),
241+
) as c::ssize_t)?
242+
};
243+
Ok(nwritten as usize)
222244
}
223245

224246
// These functions are derived from Rust's library/std/src/sys/unix/fd.rs at

src/backend/libc/io/types.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,15 @@ bitflags! {
2222
/// [`pwritev2`]: crate::io::pwritev
2323
pub struct ReadWriteFlags: c::c_int {
2424
/// `RWF_DSYNC` (since Linux 4.7)
25-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
26-
const DSYNC = c::RWF_DSYNC;
25+
const DSYNC = linux_raw_sys::general::RWF_DSYNC as c::c_int;
2726
/// `RWF_HIPRI` (since Linux 4.6)
28-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
29-
const HIPRI = c::RWF_HIPRI;
27+
const HIPRI = linux_raw_sys::general::RWF_HIPRI as c::c_int;
3028
/// `RWF_SYNC` (since Linux 4.7)
31-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
32-
const SYNC = c::RWF_SYNC;
29+
const SYNC = linux_raw_sys::general::RWF_SYNC as c::c_int;
3330
/// `RWF_NOWAIT` (since Linux 4.14)
34-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
35-
const NOWAIT = c::RWF_NOWAIT;
31+
const NOWAIT = linux_raw_sys::general::RWF_NOWAIT as c::c_int;
3632
/// `RWF_APPEND` (since Linux 4.16)
37-
#[cfg(all(target_os = "linux", target_env = "gnu"))]
38-
const APPEND = c::RWF_APPEND;
33+
const APPEND = linux_raw_sys::general::RWF_APPEND as c::c_int;
3934
}
4035
}
4136

src/io/read_write.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ pub fn pread<Fd: AsFd>(fd: Fd, buf: &mut [u8], offset: u64) -> io::Result<usize>
6262

6363
/// `pwrite(fd, bufs)`—Writes to a file at a given position.
6464
///
65+
/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD,
66+
/// if the file is opened in append mode, this ignores the offset appends the
67+
/// data to the end of the file.
68+
///
6569
/// # References
6670
/// - [POSIX]
6771
/// - [Linux]
@@ -121,6 +125,10 @@ pub fn preadv<Fd: AsFd>(fd: Fd, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io:
121125
/// `pwritev(fd, bufs, offset)`—Writes to a file at a given position from
122126
/// multiple buffers.
123127
///
128+
/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD,
129+
/// if the file is opened in append mode, this ignores the offset appends the
130+
/// data to the end of the file.
131+
///
124132
/// # References
125133
/// - [Linux]
126134
///

tests/io/read_write.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,83 @@ fn test_readwrite() {
118118
read(&foo, &mut buf).unwrap();
119119
assert_eq!(&buf, b"world");
120120
}
121+
122+
#[cfg(all(target_os = "linux", target_env = "gnu"))]
123+
#[test]
124+
fn test_rwf_values() {
125+
// We use the kernel's values for these flags; check that libc doesn't
126+
// have different values.
127+
assert_eq!(
128+
rustix::io::ReadWriteFlags::APPEND.bits() as i32,
129+
libc::RWF_APPEND
130+
);
131+
assert_eq!(
132+
rustix::io::ReadWriteFlags::DSYNC.bits() as i32,
133+
libc::RWF_DSYNC
134+
);
135+
assert_eq!(
136+
rustix::io::ReadWriteFlags::HIPRI.bits() as i32,
137+
libc::RWF_HIPRI
138+
);
139+
assert_eq!(
140+
rustix::io::ReadWriteFlags::NOWAIT.bits() as i32,
141+
libc::RWF_NOWAIT
142+
);
143+
assert_eq!(
144+
rustix::io::ReadWriteFlags::SYNC.bits() as i32,
145+
libc::RWF_SYNC
146+
);
147+
}
148+
149+
#[cfg(any(target_os = "android", target_os = "linux"))]
150+
#[cfg(feature = "fs")]
151+
#[test]
152+
fn test_pwritev2() {
153+
use rustix::fs::{cwd, openat, seek, Mode, OFlags};
154+
use rustix::io::{preadv2, pwritev2, writev, ReadWriteFlags, SeekFrom};
155+
156+
let tmp = tempfile::tempdir().unwrap();
157+
let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
158+
let foo = openat(
159+
&dir,
160+
"foo",
161+
OFlags::RDWR | OFlags::CREATE | OFlags::TRUNC,
162+
Mode::RUSR | Mode::WUSR,
163+
)
164+
.unwrap();
165+
166+
writev(&foo, &[IoSlice::new(b"hello")]).unwrap();
167+
seek(&foo, SeekFrom::Start(0)).unwrap();
168+
169+
// pwritev2 to append with a 0 offset: don't update the current position.
170+
match pwritev2(&foo, &[IoSlice::new(b"world")], 0, ReadWriteFlags::APPEND) {
171+
Ok(_) => {}
172+
// Skip the rest of the test if we don't have `pwritev2` and `RWF_APPEND`.
173+
Err(rustix::io::Errno::NOSYS) | Err(rustix::io::Errno::NOTSUP) => return,
174+
Err(err) => Err(err).unwrap(),
175+
}
176+
assert_eq!(seek(&foo, SeekFrom::Current(0)).unwrap(), 0);
177+
178+
// pwritev2 to append with a !0 offset: do update the current position.
179+
pwritev2(&foo, &[IoSlice::new(b"world")], !0, ReadWriteFlags::APPEND).unwrap();
180+
assert_eq!(seek(&foo, SeekFrom::Current(0)).unwrap(), 15);
181+
182+
seek(&foo, SeekFrom::Start(0)).unwrap();
183+
let mut buf = [0_u8; 5];
184+
preadv2(
185+
&foo,
186+
&mut [IoSliceMut::new(&mut buf)],
187+
0,
188+
ReadWriteFlags::empty(),
189+
)
190+
.unwrap();
191+
assert_eq!(&buf, b"hello");
192+
preadv2(
193+
&foo,
194+
&mut [IoSliceMut::new(&mut buf)],
195+
5,
196+
ReadWriteFlags::empty(),
197+
)
198+
.unwrap();
199+
assert_eq!(&buf, b"world");
200+
}

0 commit comments

Comments
 (0)