Skip to content

Commit a2d9c8e

Browse files
authored
Fix p{read,write}v{,v2}'s encoding of the offset argument on Linux. (#896) (#897)
Unlike with `p{read,write}`, Linux's `p{read,write}v` syscall's offset argument is not passed in an endian-specific order. And, the expectation is for syscall wrappers to always pass both the high and low halves of the offset as separate arguments, even though on 64-bit architectures the low half is passed throgh as a 64-bit value containing the full offset and the kernel doesn't mask it. And `p{read,write}v2` follow the behavior of `p{read,write}`.
1 parent dce2777 commit a2d9c8e

File tree

5 files changed

+292
-154
lines changed

5 files changed

+292
-154
lines changed

src/backend/libc/offset.rs

Lines changed: 34 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -154,25 +154,6 @@ pub(super) use c::{preadv64 as libc_preadv, pwritev64 as libc_pwritev};
154154
mod readwrite_pv64 {
155155
use super::c;
156156

157-
// 64-bit offsets on 32-bit platforms are passed in endianness-specific
158-
// lo/hi pairs. See src/backend/linux_raw/conv.rs for details.
159-
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
160-
fn lo(x: u64) -> usize {
161-
(x >> 32) as usize
162-
}
163-
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
164-
fn hi(x: u64) -> usize {
165-
(x & 0xffff_ffff) as usize
166-
}
167-
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
168-
fn lo(x: u64) -> usize {
169-
(x & 0xffff_ffff) as usize
170-
}
171-
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
172-
fn hi(x: u64) -> usize {
173-
(x >> 32) as usize
174-
}
175-
176157
pub(in super::super) unsafe fn preadv64(
177158
fd: c::c_int,
178159
iov: *const c::iovec,
@@ -189,21 +170,14 @@ mod readwrite_pv64 {
189170
if let Some(fun) = preadv64.get() {
190171
fun(fd, iov, iovcnt, offset)
191172
} else {
192-
#[cfg(target_pointer_width = "32")]
193-
{
194-
c::syscall(
195-
c::SYS_preadv,
196-
fd,
197-
iov,
198-
iovcnt,
199-
hi(offset as u64),
200-
lo(offset as u64),
201-
) as c::ssize_t
202-
}
203-
#[cfg(target_pointer_width = "64")]
204-
{
205-
c::syscall(c::SYS_preadv, fd, iov, iovcnt, offset) as c::ssize_t
206-
}
173+
c::syscall(
174+
c::SYS_preadv,
175+
fd,
176+
iov,
177+
iovcnt,
178+
offset as usize,
179+
(offset >> 32) as usize,
180+
) as c::ssize_t
207181
}
208182
}
209183
pub(in super::super) unsafe fn pwritev64(
@@ -219,21 +193,14 @@ mod readwrite_pv64 {
219193
if let Some(fun) = pwritev64.get() {
220194
fun(fd, iov, iovcnt, offset)
221195
} else {
222-
#[cfg(target_pointer_width = "32")]
223-
{
224-
c::syscall(
225-
c::SYS_pwritev,
226-
fd,
227-
iov,
228-
iovcnt,
229-
hi(offset as u64),
230-
lo(offset as u64),
231-
) as c::ssize_t
232-
}
233-
#[cfg(target_pointer_width = "64")]
234-
{
235-
c::syscall(c::SYS_pwritev, fd, iov, iovcnt, offset) as c::ssize_t
236-
}
196+
c::syscall(
197+
c::SYS_pwritev,
198+
fd,
199+
iov,
200+
iovcnt,
201+
offset as usize,
202+
(offset >> 32) as usize,
203+
) as c::ssize_t
237204
}
238205
}
239206
}
@@ -269,25 +236,6 @@ pub(super) use readwrite_pv::{preadv as libc_preadv, pwritev as libc_pwritev};
269236
mod readwrite_pv64v2 {
270237
use super::c;
271238

272-
// 64-bit offsets on 32-bit platforms are passed in endianness-specific
273-
// lo/hi pairs. See src/backend/linux_raw/conv.rs for details.
274-
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
275-
fn lo(x: u64) -> usize {
276-
(x >> 32) as usize
277-
}
278-
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
279-
fn hi(x: u64) -> usize {
280-
(x & 0xffff_ffff) as usize
281-
}
282-
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
283-
fn lo(x: u64) -> usize {
284-
(x & 0xffff_ffff) as usize
285-
}
286-
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
287-
fn hi(x: u64) -> usize {
288-
(x >> 32) as usize
289-
}
290-
291239
pub(in super::super) unsafe fn preadv64v2(
292240
fd: c::c_int,
293241
iov: *const c::iovec,
@@ -305,22 +253,15 @@ mod readwrite_pv64v2 {
305253
if let Some(fun) = preadv64v2.get() {
306254
fun(fd, iov, iovcnt, offset, flags)
307255
} else {
308-
#[cfg(target_pointer_width = "32")]
309-
{
310-
c::syscall(
311-
c::SYS_preadv,
312-
fd,
313-
iov,
314-
iovcnt,
315-
hi(offset as u64),
316-
lo(offset as u64),
317-
flags,
318-
) as c::ssize_t
319-
}
320-
#[cfg(target_pointer_width = "64")]
321-
{
322-
c::syscall(c::SYS_preadv2, fd, iov, iovcnt, offset, flags) as c::ssize_t
323-
}
256+
c::syscall(
257+
c::SYS_preadv,
258+
fd,
259+
iov,
260+
iovcnt,
261+
offset as usize,
262+
(offset >> 32) as usize,
263+
flags,
264+
) as c::ssize_t
324265
}
325266
}
326267
pub(in super::super) unsafe fn pwritev64v2(
@@ -337,22 +278,15 @@ mod readwrite_pv64v2 {
337278
if let Some(fun) = pwritev64v2.get() {
338279
fun(fd, iov, iovcnt, offset, flags)
339280
} else {
340-
#[cfg(target_pointer_width = "32")]
341-
{
342-
c::syscall(
343-
c::SYS_pwritev,
344-
fd,
345-
iov,
346-
iovcnt,
347-
hi(offset as u64),
348-
lo(offset as u64),
349-
flags,
350-
) as c::ssize_t
351-
}
352-
#[cfg(target_pointer_width = "64")]
353-
{
354-
c::syscall(c::SYS_pwritev2, fd, iov, iovcnt, offset, flags) as c::ssize_t
355-
}
281+
c::syscall(
282+
c::SYS_pwritev,
283+
fd,
284+
iov,
285+
iovcnt,
286+
offset as usize,
287+
(offset >> 32) as usize,
288+
flags,
289+
) as c::ssize_t
356290
}
357291
}
358292
}

src/backend/linux_raw/io/syscalls.rs

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -109,25 +109,16 @@ pub(crate) fn preadv(
109109
) -> io::Result<usize> {
110110
let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]);
111111

112-
#[cfg(target_pointer_width = "32")]
112+
// Unlike the plain "p" functions, the "pv" functions pass their offset in
113+
// an endian-independent way, and always in two registers.
113114
unsafe {
114115
ret_usize(syscall!(
115116
__NR_preadv,
116117
fd,
117118
bufs_addr,
118119
bufs_len,
119-
hi(pos),
120-
lo(pos)
121-
))
122-
}
123-
#[cfg(target_pointer_width = "64")]
124-
unsafe {
125-
ret_usize(syscall!(
126-
__NR_preadv,
127-
fd,
128-
bufs_addr,
129-
bufs_len,
130-
loff_t_from_u64(pos)
120+
pass_usize(pos as usize),
121+
pass_usize((pos >> 32) as usize)
131122
))
132123
}
133124
}
@@ -141,26 +132,16 @@ pub(crate) fn preadv2(
141132
) -> io::Result<usize> {
142133
let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]);
143134

144-
#[cfg(target_pointer_width = "32")]
145-
unsafe {
146-
ret_usize(syscall!(
147-
__NR_preadv2,
148-
fd,
149-
bufs_addr,
150-
bufs_len,
151-
hi(pos),
152-
lo(pos),
153-
flags
154-
))
155-
}
156-
#[cfg(target_pointer_width = "64")]
135+
// Unlike the plain "p" functions, the "pv" functions pass their offset in
136+
// an endian-independent way, and always in two registers.
157137
unsafe {
158138
ret_usize(syscall!(
159139
__NR_preadv2,
160140
fd,
161141
bufs_addr,
162142
bufs_len,
163-
loff_t_from_u64(pos),
143+
pass_usize(pos as usize),
144+
pass_usize((pos >> 32) as usize),
164145
flags
165146
))
166147
}
@@ -230,25 +211,16 @@ pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result<usi
230211
pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], pos: u64) -> io::Result<usize> {
231212
let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]);
232213

233-
#[cfg(target_pointer_width = "32")]
214+
// Unlike the plain "p" functions, the "pv" functions pass their offset in
215+
// an endian-independent way, and always in two registers.
234216
unsafe {
235217
ret_usize(syscall_readonly!(
236218
__NR_pwritev,
237219
fd,
238220
bufs_addr,
239221
bufs_len,
240-
hi(pos),
241-
lo(pos)
242-
))
243-
}
244-
#[cfg(target_pointer_width = "64")]
245-
unsafe {
246-
ret_usize(syscall_readonly!(
247-
__NR_pwritev,
248-
fd,
249-
bufs_addr,
250-
bufs_len,
251-
loff_t_from_u64(pos)
222+
pass_usize(pos as usize),
223+
pass_usize((pos >> 32) as usize)
252224
))
253225
}
254226
}
@@ -262,26 +234,16 @@ pub(crate) fn pwritev2(
262234
) -> io::Result<usize> {
263235
let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]);
264236

265-
#[cfg(target_pointer_width = "32")]
266-
unsafe {
267-
ret_usize(syscall_readonly!(
268-
__NR_pwritev2,
269-
fd,
270-
bufs_addr,
271-
bufs_len,
272-
hi(pos),
273-
lo(pos),
274-
flags
275-
))
276-
}
277-
#[cfg(target_pointer_width = "64")]
237+
// Unlike the plain "p" functions, the "pv" functions pass their offset in
238+
// an endian-independent way, and always in two registers.
278239
unsafe {
279240
ret_usize(syscall_readonly!(
280241
__NR_pwritev2,
281242
fd,
282243
bufs_addr,
283244
bufs_len,
284-
loff_t_from_u64(pos),
245+
pass_usize(pos as usize),
246+
pass_usize((pos >> 32) as usize),
285247
flags
286248
))
287249
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
// Redox and WASI have enough differences that it isn't worth
128128
// precisely conditionallizing all the `use`s for them.
129129
#![cfg_attr(any(target_os = "redox", target_os = "wasi"), allow(unused_imports))]
130+
// On the release branch, don't worry about unused-import warnings.
131+
#![allow(unused_imports)]
130132

131133
#[cfg(not(feature = "rustc-dep-of-std"))]
132134
extern crate alloc;

tests/fs/seek.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/// Test seek positions related to file "holes".
2+
#[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
3+
#[test]
4+
fn test_seek_holes() {
5+
use rustix::fs::{fstat, openat, seek, Mode, OFlags, SeekFrom, CWD};
6+
use std::io::Write;
7+
8+
let tmp = tempfile::tempdir().unwrap();
9+
let dir = openat(CWD, tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
10+
let foo = openat(
11+
&dir,
12+
"foo",
13+
OFlags::RDWR | OFlags::CREATE | OFlags::TRUNC,
14+
Mode::RUSR | Mode::WUSR,
15+
)
16+
.unwrap();
17+
let mut foo = std::fs::File::from(foo);
18+
19+
let stat = fstat(&foo).unwrap();
20+
let hole_size = stat.st_blksize as u64;
21+
22+
#[cfg(any(solarish, freebsdlike, netbsdlike))]
23+
let hole_size = unsafe {
24+
use std::os::unix::io::AsRawFd;
25+
26+
let r = libc::fpathconf(foo.as_raw_fd(), libc::_PC_MIN_HOLE_SIZE);
27+
28+
if r < 0 {
29+
// Holes not supported.
30+
return;
31+
}
32+
33+
// Holes are supported.
34+
core::cmp::max(hole_size, r as u64)
35+
};
36+
37+
foo.write_all(b"prefix").unwrap();
38+
assert_eq!(
39+
seek(&foo, SeekFrom::Start(hole_size * 2)),
40+
Ok(hole_size * 2)
41+
);
42+
foo.write_all(b"suffix").unwrap();
43+
assert_eq!(seek(&foo, SeekFrom::Start(0)), Ok(0));
44+
assert_eq!(seek(&foo, SeekFrom::Current(0)), Ok(0));
45+
assert_eq!(seek(&foo, SeekFrom::Hole(0)), Ok(hole_size));
46+
assert_eq!(seek(&foo, SeekFrom::Hole(hole_size as i64)), Ok(hole_size));
47+
assert_eq!(
48+
seek(&foo, SeekFrom::Hole(hole_size as i64 * 2)),
49+
Ok(hole_size * 2 + 6)
50+
);
51+
assert_eq!(seek(&foo, SeekFrom::Data(0)), Ok(0));
52+
assert_eq!(
53+
seek(&foo, SeekFrom::Data(hole_size as i64)),
54+
Ok(hole_size * 2)
55+
);
56+
assert_eq!(
57+
seek(&foo, SeekFrom::Data(hole_size as i64 * 2)),
58+
Ok(hole_size * 2)
59+
);
60+
}
61+
62+
#[test]
63+
fn test_seek_offsets() {
64+
use rustix::fs::{openat, seek, Mode, OFlags, SeekFrom, CWD};
65+
66+
let f = openat(CWD, "Cargo.toml", OFlags::RDONLY, Mode::empty()).unwrap();
67+
68+
match seek(&f, SeekFrom::Start(0)) {
69+
Ok(_) => {}
70+
Err(e) => panic!("seek failed with an unexpected error: {:?}", e),
71+
}
72+
for invalid_offset in &[i32::MIN as u64, !1 as u64, i64::MIN as u64] {
73+
let invalid_offset = *invalid_offset;
74+
match seek(&f, SeekFrom::Start(invalid_offset)) {
75+
Err(rustix::io::Errno::INVAL) => {}
76+
Ok(_) => panic!("seek unexpectedly succeeded"),
77+
Err(e) => panic!("seek failed with an unexpected error: {:?}", e),
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)