Skip to content

Commit 3db46c2

Browse files
committed
support 64-bit file offsets on systems with 32-bit off_t
Adds large file support (64-bit file positions) to the existing Nix API when targetting systems that have separate 32-bit and 64-bit APIs. On many platforms that support 64-bit file positions, this support is present in the standard I/O functions. On others, there have historically been two APIs: the standard API, which limits file size to 2**31-1 bytes, and a separate API that suffixes function names with "64" and supports 64-bit file sizes. This "transitional interface for 64-bit file offsets" is not present on every platform. As a result, using the *64 API will make programs non-portable, whereas using the standard API will result in programs that cannot handle large file offsets on some systems, even if those systems actually do have a way to handle large file offsets. This change enables 64-bit file offsets on the following platforms where the standard API only provides 32-bit offsets: - 32-bit Linux with glibc. - 32-bit Android. To support large files with a consistent API across platforms, this change makes Nix functions call the 64-bit capable equivalents where necessary. Other C libraries may not provide the *64 API, so we continue to call the standard functions on those. Broadly, the change consists of 5 parts: 1. Uses of libc::off_t have are replaced by i64. This provides a consistent API across platforms and allows 64-bit offsets to be used even when libc::off_t is 32-bit. This is similar to std::fs, which uses u64 for file positions. Nix uses i64 because off_t is a signed type. Into<i64> is used where appropriate so that existing code that uses libc::off_t will continue to work without changes. 2. A largefile_fn macro that is used to select the large-file-capable version of a function. E.g. largefile_fn![pwrite] is equivalent to libc::pwrite64 on glibc/Linux and plain libc::pwrite on systems that don't have or need libc::pwrite64. 3. A new require_largefile macro that is used to skip tests that require libc::off_t to be larger than 32 bits. 4. Changes to fallocate, ftruncate, lseek, mmap, open, openat, posix_fadvise, posix_fallocate, pread, preadv, pwrite, pwritev, sendfile, and truncate, making them support large files. 5. A set of test_*_largefile tests to verify that each of the previously mentioned functions now works with files whose size requires more than 32 bits to represent. A few functions are still limited to 32-bit file sizes after this change. This includes the aio* functions, the *stat* functions, and mkstemp(). Adding large file support to those requires a bit more work than simply calling a different function and is therefore left for later.
1 parent 25b437c commit 3db46c2

14 files changed

+437
-100
lines changed

src/fcntl.rs

+49-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use crate::errno::Errno;
33
#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
44
use core::slice;
5-
use libc::{c_int, c_uint, size_t, ssize_t};
5+
use libc::{self, c_int, c_uint, size_t, ssize_t};
66
#[cfg(any(
77
target_os = "netbsd",
88
apple_targets,
@@ -163,8 +163,12 @@ libc_bitflags!(
163163
all(target_os = "linux", not(target_env = "musl"), not(target_env = "ohos")),
164164
target_os = "redox"))]
165165
O_FSYNC;
166-
/// Allow files whose sizes can't be represented in an `off_t` to be opened.
166+
/// On 32-bit Linux, O_LARGEFILE allows the use of file
167+
/// offsets greater than 32 bits. Nix accepts the flag for
168+
/// compatibility, but always opens files in large file mode
169+
/// even if it isn't specified.
167170
#[cfg(linux_android)]
171+
#[cfg_attr(docsrs, doc(cfg(all())))]
168172
O_LARGEFILE;
169173
/// Do not update the file last access time during `read(2)`s.
170174
#[cfg(linux_android)]
@@ -249,7 +253,7 @@ pub fn open<P: ?Sized + NixPath>(
249253
use std::os::fd::FromRawFd;
250254

251255
let fd = path.with_nix_path(|cstr| unsafe {
252-
libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
256+
largefile_fn![open](cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
253257
})?;
254258
Errno::result(fd)?;
255259

@@ -280,7 +284,12 @@ pub fn openat<P: ?Sized + NixPath, Fd: std::os::fd::AsFd>(
280284
use std::os::fd::FromRawFd;
281285

282286
let fd = path.with_nix_path(|cstr| unsafe {
283-
libc::openat(dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
287+
largefile_fn![openat](
288+
dirfd.as_fd().as_raw_fd(),
289+
cstr.as_ptr(),
290+
oflag.bits(),
291+
mode.bits() as c_uint,
292+
)
284293
})?;
285294
Errno::result(fd)?;
286295

@@ -1303,23 +1312,26 @@ feature! {
13031312
/// file referred to by fd.
13041313
#[cfg(target_os = "linux")]
13051314
#[cfg(feature = "fs")]
1306-
pub fn fallocate<Fd: std::os::fd::AsFd>(
1315+
pub fn fallocate<Fd: std::os::fd::AsFd, Off: Into<i64>>(
13071316
fd: Fd,
13081317
mode: FallocateFlags,
1309-
offset: libc::off_t,
1310-
len: libc::off_t,
1318+
offset: Off,
1319+
len: Off,
13111320
) -> Result<()> {
13121321
use std::os::fd::AsRawFd;
13131322

1314-
let res = unsafe { libc::fallocate(fd.as_fd().as_raw_fd(), mode.bits(), offset, len) };
1323+
let res = unsafe {
1324+
largefile_fn![fallocate](
1325+
fd.as_fd().as_raw_fd(), mode.bits(), offset.into(), len.into())
1326+
};
13151327
Errno::result(res).map(drop)
13161328
}
13171329

13181330
/// Argument to [`fspacectl`] describing the range to zero. The first member is
13191331
/// the file offset, and the second is the length of the region.
13201332
#[cfg(any(target_os = "freebsd"))]
13211333
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1322-
pub struct SpacectlRange(pub libc::off_t, pub libc::off_t);
1334+
pub struct SpacectlRange(pub i64, pub i64);
13231335

13241336
#[cfg(any(target_os = "freebsd"))]
13251337
impl SpacectlRange {
@@ -1334,13 +1346,13 @@ impl SpacectlRange {
13341346

13351347
/// Remaining length of the range
13361348
#[inline]
1337-
pub fn len(&self) -> libc::off_t {
1349+
pub fn len(&self) -> i64 {
13381350
self.1
13391351
}
13401352

13411353
/// Next file offset to operate on
13421354
#[inline]
1343-
pub fn offset(&self) -> libc::off_t {
1355+
pub fn offset(&self) -> i64 {
13441356
self.0
13451357
}
13461358
}
@@ -1437,16 +1449,16 @@ pub fn fspacectl<Fd: std::os::fd::AsFd>(fd: Fd, range: SpacectlRange) -> Result<
14371449
/// ```
14381450
#[cfg(target_os = "freebsd")]
14391451
#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined
1440-
pub fn fspacectl_all<Fd: std::os::fd::AsFd>(
1452+
pub fn fspacectl_all<Fd: std::os::fd::AsFd, Off: Into<i64>>(
14411453
fd: Fd,
1442-
offset: libc::off_t,
1443-
len: libc::off_t,
1454+
offset: Off,
1455+
len: Off,
14441456
) -> Result<()> {
14451457
use std::os::fd::AsRawFd;
14461458

14471459
let mut rqsr = libc::spacectl_range {
1448-
r_offset: offset,
1449-
r_len: len,
1460+
r_offset: offset.into(),
1461+
r_len: len.into(),
14501462
};
14511463
while rqsr.r_len > 0 {
14521464
let res = unsafe {
@@ -1505,15 +1517,22 @@ mod posix_fadvise {
15051517
///
15061518
/// # See Also
15071519
/// * [`posix_fadvise`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fadvise.html)
1508-
pub fn posix_fadvise<Fd: std::os::fd::AsFd>(
1520+
pub fn posix_fadvise<Fd: std::os::fd::AsFd, Off: Into<i64>>(
15091521
fd: Fd,
1510-
offset: libc::off_t,
1511-
len: libc::off_t,
1522+
offset: Off,
1523+
len: Off,
15121524
advice: PosixFadviseAdvice,
15131525
) -> Result<()> {
15141526
use std::os::fd::AsRawFd;
15151527

1516-
let res = unsafe { libc::posix_fadvise(fd.as_fd().as_raw_fd(), offset, len, advice as libc::c_int) };
1528+
let res = unsafe {
1529+
largefile_fn![posix_fadvise](
1530+
fd.as_fd().as_raw_fd(),
1531+
offset.into(),
1532+
len.into(),
1533+
advice as libc::c_int,
1534+
)
1535+
};
15171536

15181537
if res == 0 {
15191538
Ok(())
@@ -1535,14 +1554,20 @@ mod posix_fadvise {
15351554
target_os = "fuchsia",
15361555
target_os = "wasi",
15371556
))]
1538-
pub fn posix_fallocate<Fd: std::os::fd::AsFd>(
1557+
pub fn posix_fallocate<Fd: std::os::fd::AsFd, Off: Into<i64>>(
15391558
fd: Fd,
1540-
offset: libc::off_t,
1541-
len: libc::off_t,
1559+
offset: Off,
1560+
len: Off,
15421561
) -> Result<()> {
15431562
use std::os::fd::AsRawFd;
15441563

1545-
let res = unsafe { libc::posix_fallocate(fd.as_fd().as_raw_fd(), offset, len) };
1564+
let res = unsafe {
1565+
largefile_fn![posix_fallocate](
1566+
fd.as_fd().as_raw_fd(),
1567+
offset.into(),
1568+
len.into(),
1569+
)
1570+
};
15461571
match Errno::result(res) {
15471572
Err(err) => Err(err),
15481573
Ok(0) => Ok(()),

src/macros.rs

+47
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use cfg_if::cfg_if;
2+
13
// Thanks to Tokio for this macro
24
macro_rules! feature {
35
(
@@ -329,3 +331,48 @@ macro_rules! libc_enum {
329331
}
330332
};
331333
}
334+
335+
cfg_if! {
336+
if #[cfg(any(
337+
all(target_os = "linux", target_env = "gnu"),
338+
target_os = "android",
339+
))] {
340+
/// Function variant that supports large file positions.
341+
///
342+
/// On some platforms, the standard I/O functions support a limited
343+
/// range of file positions, and there is an alternate set of
344+
/// functions that support larger file positions. This macro takes
345+
/// the identifier of a standard I/O function and returns the
346+
/// identifier of the corresponding I/O function with large file
347+
/// support.
348+
#[allow(unused_macro_rules)]
349+
macro_rules! largefile_fn {
350+
[fallocate] => (libc::fallocate64);
351+
[ftruncate] => (libc::ftruncate64);
352+
[lseek] => (libc::lseek64);
353+
[mmap] => (libc::mmap64);
354+
[open] => (libc::open64);
355+
[openat] => (libc::openat64);
356+
[posix_fadvise] => (libc::posix_fadvise64);
357+
[posix_fallocate] => (libc::posix_fallocate64);
358+
[pread] => (libc::pread64);
359+
[preadv] => (libc::preadv64);
360+
[pwrite] => (libc::pwrite64);
361+
[pwritev] => (libc::pwritev64);
362+
[sendfile] => (libc::sendfile64);
363+
[truncate] => (libc::truncate64);
364+
}
365+
} else {
366+
/// Function variant that supports large file positions.
367+
///
368+
/// On some platforms, the standard I/O functions support a limited
369+
/// range of file positions, and there is an alternate set of
370+
/// functions that support larger file positions. This macro takes
371+
/// the identifier of a standard I/O function and returns the
372+
/// identifier of the corresponding I/O function with large file
373+
/// support.
374+
macro_rules! largefile_fn {
375+
[$id:ident] => (libc::$id);
376+
}
377+
}
378+
}

src/sys/mman.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::Result;
77
#[cfg(not(target_os = "android"))]
88
#[cfg(feature = "fs")]
99
use crate::{fcntl::OFlag, sys::stat::Mode};
10-
use libc::{self, c_int, c_void, off_t, size_t};
10+
use libc::{self, c_int, c_void, size_t};
1111
use std::ptr::NonNull;
1212
use std::{
1313
num::NonZeroUsize,
@@ -392,19 +392,20 @@ pub fn munlockall() -> Result<()> {
392392
/// See the [`mmap(2)`] man page for detailed requirements.
393393
///
394394
/// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html
395-
pub unsafe fn mmap<F: AsFd>(
395+
pub unsafe fn mmap<F: AsFd, Off: Into<i64>>(
396396
addr: Option<NonZeroUsize>,
397397
length: NonZeroUsize,
398398
prot: ProtFlags,
399399
flags: MapFlags,
400400
f: F,
401-
offset: off_t,
401+
offset: Off,
402402
) -> Result<NonNull<c_void>> {
403403
let ptr = addr.map_or(std::ptr::null_mut(), |a| a.get() as *mut c_void);
404404

405405
let fd = f.as_fd().as_raw_fd();
406406
let ret = unsafe {
407-
libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset)
407+
largefile_fn![mmap](
408+
ptr, length.into(), prot.bits(), flags.bits(), fd, offset.into())
408409
};
409410

410411
if ret == libc::MAP_FAILED {

src/sys/sendfile.rs

+21-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cfg_if::cfg_if;
44
use std::os::unix::io::{AsFd, AsRawFd};
55
use std::ptr;
66

7-
use libc::{self, off_t};
7+
use libc;
88

99
use crate::errno::Errno;
1010
use crate::Result;
@@ -26,14 +26,14 @@ use crate::Result;
2626
pub fn sendfile<F1: AsFd, F2: AsFd>(
2727
out_fd: F1,
2828
in_fd: F2,
29-
offset: Option<&mut off_t>,
29+
offset: Option<&mut i64>,
3030
count: usize,
3131
) -> Result<usize> {
3232
let offset = offset
3333
.map(|offset| offset as *mut _)
3434
.unwrap_or(ptr::null_mut());
3535
let ret = unsafe {
36-
libc::sendfile(
36+
largefile_fn![sendfile](
3737
out_fd.as_fd().as_raw_fd(),
3838
in_fd.as_fd().as_raw_fd(),
3939
offset,
@@ -133,20 +133,20 @@ cfg_if! {
133133
impl<'fd> SendfileVec<'fd> {
134134
/// initialises SendfileVec to send data directly from the process's address space
135135
/// same in C with sfv_fd set to SFV_FD_SELF.
136-
pub fn newself(
137-
off: off_t,
136+
pub fn newself<Off: Into<i64>>(
137+
off: Off,
138138
len: usize
139139
) -> Self {
140-
Self{raw: libc::sendfilevec_t{sfv_fd: libc::SFV_FD_SELF, sfv_flag: 0, sfv_off: off, sfv_len: len}, phantom: PhantomData}
140+
Self{raw: libc::sendfilevec_t{sfv_fd: libc::SFV_FD_SELF, sfv_flag: 0, sfv_off: off.into(), sfv_len: len}, phantom: PhantomData}
141141
}
142142

143143
/// initialises SendfileVec to send data from `fd`.
144-
pub fn new(
144+
pub fn new<Off: Into<i64>>(
145145
fd: BorrowedFd<'fd>,
146-
off: off_t,
146+
off: Off,
147147
len: usize
148148
) -> SendfileVec<'fd> {
149-
Self{raw: libc::sendfilevec_t{sfv_fd: fd.as_raw_fd(), sfv_flag: 0, sfv_off:off, sfv_len: len}, phantom: PhantomData}
149+
Self{raw: libc::sendfilevec_t{sfv_fd: fd.as_raw_fd(), sfv_flag: 0, sfv_off:off.into(), sfv_len: len}, phantom: PhantomData}
150150
}
151151
}
152152

@@ -208,19 +208,19 @@ cfg_if! {
208208
pub fn sendfile<F1: AsFd, F2: AsFd>(
209209
in_fd: F1,
210210
out_sock: F2,
211-
offset: off_t,
211+
offset: i64,
212212
count: Option<usize>,
213213
headers: Option<&[&[u8]]>,
214214
trailers: Option<&[&[u8]]>,
215215
flags: SfFlags,
216216
readahead: u16
217-
) -> (Result<()>, off_t) {
217+
) -> (Result<()>, i64) {
218218
// Readahead goes in upper 16 bits
219219
// Flags goes in lower 16 bits
220220
// see `man 2 sendfile`
221221
let ra32 = u32::from(readahead);
222222
let flags: u32 = (ra32 << 16) | (flags.bits() as u32);
223-
let mut bytes_sent: off_t = 0;
223+
let mut bytes_sent: i64 = 0;
224224
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
225225
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
226226
let return_code = unsafe {
@@ -229,7 +229,7 @@ cfg_if! {
229229
offset,
230230
count.unwrap_or(0),
231231
hdtr_ptr as *mut libc::sf_hdtr,
232-
&mut bytes_sent as *mut off_t,
232+
&mut bytes_sent as *mut i64,
233233
flags as c_int)
234234
};
235235
(Errno::result(return_code).and(Ok(())), bytes_sent)
@@ -258,12 +258,12 @@ cfg_if! {
258258
pub fn sendfile<F1: AsFd, F2: AsFd>(
259259
in_fd: F1,
260260
out_sock: F2,
261-
offset: off_t,
261+
offset: i64,
262262
count: Option<usize>,
263263
headers: Option<&[&[u8]]>,
264264
trailers: Option<&[&[u8]]>,
265-
) -> (Result<()>, off_t) {
266-
let mut bytes_sent: off_t = 0;
265+
) -> (Result<()>, i64) {
266+
let mut bytes_sent: i64 = 0;
267267
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
268268
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
269269
let return_code = unsafe {
@@ -272,7 +272,7 @@ cfg_if! {
272272
offset,
273273
count.unwrap_or(0),
274274
hdtr_ptr as *mut libc::sf_hdtr,
275-
&mut bytes_sent as *mut off_t,
275+
&mut bytes_sent as *mut i64,
276276
0)
277277
};
278278
(Errno::result(return_code).and(Ok(())), bytes_sent)
@@ -304,19 +304,19 @@ cfg_if! {
304304
pub fn sendfile<F1: AsFd, F2: AsFd>(
305305
in_fd: F1,
306306
out_sock: F2,
307-
offset: off_t,
308-
count: Option<off_t>,
307+
offset: i64,
308+
count: Option<i64>,
309309
headers: Option<&[&[u8]]>,
310310
trailers: Option<&[&[u8]]>
311-
) -> (Result<()>, off_t) {
311+
) -> (Result<()>, i64) {
312312
let mut len = count.unwrap_or(0);
313313
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
314314
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
315315
let return_code = unsafe {
316316
libc::sendfile(in_fd.as_fd().as_raw_fd(),
317317
out_sock.as_fd().as_raw_fd(),
318318
offset,
319-
&mut len as *mut off_t,
319+
&mut len as *mut i64,
320320
hdtr_ptr as *mut libc::sf_hdtr,
321321
0)
322322
};

0 commit comments

Comments
 (0)