Skip to content

Commit fbc8b68

Browse files
committed
support 64-bit file offsets on 32-bit glibc/Linux
Adds large file support (64-bit file positions) to the existing Nix API when used with glibc on 32-bit Linux. On many platforms that support 64-bit file positions, this support is present in the standard I/O functions. On 32-bit Linux, 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. Other platforms do not provide this API. 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 files on 32-bit Linux, even when the system is actually capable of handling such files. To support large files with a consistent API across platforms, this change makes Nix functions call the 64-bit capable equivalents on Linux when using glibc. Other C libraries may not provide the *64 API, so we continue to call the standard functions on those. Broadly, the change consists of five parts: 1. A new nix::off_t type to represent file positions in arguments to Nix functions. This is intended to be large enough to support all file positions supported by the platform, and therefore differs from libc::off_t on platforms where libc::off_t is too small to support all file sizes (notably 32-bit Linux with glibc). 2. A new require_largefile macro that is used to skip tests that require nix::off_t to be larger than 32 bits. 3. A largefile_fn macro that is used to select the large-file-capable version of a function. E.g. largefile_fn![libc::pwrite] is equivalent to libc::pwrite64 on glibc/Linux and plain libc::pwrite everywhere else. 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 b13b7d1 commit fbc8b68

File tree

14 files changed

+336
-47
lines changed

14 files changed

+336
-47
lines changed

src/fcntl.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::errno::Errno;
2+
use crate::off_t;
23
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
34
use std::ffi::OsString;
45
#[cfg(not(target_os = "redox"))]
@@ -199,7 +200,7 @@ pub fn open<P: ?Sized + NixPath>(
199200
mode: Mode,
200201
) -> Result<RawFd> {
201202
let fd = path.with_nix_path(|cstr| unsafe {
202-
libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
203+
largefile_fn![libc::open](cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
203204
})?;
204205

205206
Errno::result(fd)
@@ -215,7 +216,7 @@ pub fn openat<P: ?Sized + NixPath>(
215216
mode: Mode,
216217
) -> Result<RawFd> {
217218
let fd = path.with_nix_path(|cstr| unsafe {
218-
libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
219+
largefile_fn![libc::openat](dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
219220
})?;
220221
Errno::result(fd)
221222
}
@@ -746,10 +747,10 @@ feature! {
746747
pub fn fallocate(
747748
fd: RawFd,
748749
mode: FallocateFlags,
749-
offset: libc::off_t,
750-
len: libc::off_t,
750+
offset: off_t,
751+
len: off_t,
751752
) -> Result<()> {
752-
let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) };
753+
let res = unsafe { largefile_fn![libc::fallocate](fd, mode.bits(), offset, len) };
753754
Errno::result(res).map(drop)
754755
}
755756

@@ -901,6 +902,7 @@ pub fn fspacectl_all(
901902
mod posix_fadvise {
902903
use crate::errno::Errno;
903904
use crate::Result;
905+
use crate::off_t;
904906
use std::os::unix::io::RawFd;
905907

906908
#[cfg(feature = "fs")]
@@ -922,11 +924,13 @@ mod posix_fadvise {
922924
#![feature = "fs"]
923925
pub fn posix_fadvise(
924926
fd: RawFd,
925-
offset: libc::off_t,
926-
len: libc::off_t,
927+
offset: off_t,
928+
len: off_t,
927929
advice: PosixFadviseAdvice,
928930
) -> Result<()> {
929-
let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) };
931+
let res = unsafe {
932+
largefile_fn![libc::posix_fadvise](fd, offset, len, advice as libc::c_int)
933+
};
930934

931935
if res == 0 {
932936
Ok(())
@@ -948,10 +952,10 @@ mod posix_fadvise {
948952
))]
949953
pub fn posix_fallocate(
950954
fd: RawFd,
951-
offset: libc::off_t,
952-
len: libc::off_t,
955+
offset: off_t,
956+
len: off_t,
953957
) -> Result<()> {
954-
let res = unsafe { libc::posix_fallocate(fd, offset, len) };
958+
let res = unsafe { largefile_fn![libc::posix_fallocate](fd, offset, len) };
955959
match Errno::result(res) {
956960
Err(err) => Err(err),
957961
Ok(0) => Ok(()),

src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,33 @@
5555
#![cfg_attr(docsrs, feature(doc_cfg))]
5656
#![deny(clippy::cast_ptr_alignment)]
5757

58+
use cfg_if::cfg_if;
59+
5860
// Re-exported external crates
5961
pub use libc;
6062

6163
// Private internal modules
6264
#[macro_use]
6365
mod macros;
6466

67+
// On some platforms, libc::off_t is not large enough to represent all file
68+
// offsets the platform supports, but the platform provides a different
69+
// type that allows larger offsets to be used. We define our own off_t type
70+
// that is large enough to represent all file offsets the platform supports.
71+
cfg_if! {
72+
if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
73+
/// Used to represent offsets in files. May differ from libc::off_t
74+
/// on platforms where libc::off_t cannot represent the full range
75+
/// of file offsets.
76+
pub type off_t = libc::off64_t;
77+
} else {
78+
/// Used to represent offsets in files. May differ from libc::off_t
79+
/// on platforms where libc::off_t cannot represent the full range
80+
/// of file offsets.
81+
pub type off_t = libc::off_t;
82+
}
83+
}
84+
6585
// Public crates
6686
#[cfg(not(target_os = "redox"))]
6787
feature! {

src/macros.rs

Lines changed: 43 additions & 0 deletions
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
(
@@ -327,3 +329,44 @@ macro_rules! libc_enum {
327329
}
328330
};
329331
}
332+
333+
cfg_if! {
334+
if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
335+
/// Function variant that supports large file positions.
336+
///
337+
/// On some platforms, the standard I/O functions support a limited
338+
/// range of file positions, and there is an alternate set of
339+
/// functions that support larger file positions. This macro takes
340+
/// the identifier of a standard I/O function and returns the
341+
/// identifier of the corresponding I/O function with large file
342+
/// support.
343+
macro_rules! largefile_fn {
344+
[libc::fallocate] => (libc::fallocate64);
345+
[libc::ftruncate] => (libc::ftruncate64);
346+
[libc::lseek] => (libc::lseek64);
347+
[libc::mmap] => (libc::mmap64);
348+
[libc::open] => (libc::open64);
349+
[libc::openat] => (libc::openat64);
350+
[libc::posix_fadvise] => (libc::posix_fadvise64);
351+
[libc::posix_fallocate] => (libc::posix_fallocate64);
352+
[libc::pread] => (libc::pread64);
353+
[libc::preadv] => (libc::preadv64);
354+
[libc::pwrite] => (libc::pwrite64);
355+
[libc::pwritev] => (libc::pwritev64);
356+
[libc::sendfile] => (libc::sendfile64);
357+
[libc::truncate] => (libc::truncate64);
358+
}
359+
} else {
360+
/// Function variant that supports large file positions.
361+
///
362+
/// On some platforms, the standard I/O functions support a limited
363+
/// range of file positions, and there is an alternate set of
364+
/// functions that support larger file positions. This macro takes
365+
/// the identifier of a standard I/O function and returns the
366+
/// identifier of the corresponding I/O function with large file
367+
/// support.
368+
macro_rules! largefile_fn {
369+
[$id:ident] => ($id);
370+
}
371+
}
372+
}

src/sys/mman.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! Memory management declarations.
22
33
use crate::errno::Errno;
4+
use crate::off_t;
45
#[cfg(not(target_os = "android"))]
56
use crate::NixPath;
67
use crate::Result;
78
#[cfg(not(target_os = "android"))]
89
#[cfg(feature = "fs")]
910
use crate::{fcntl::OFlag, sys::stat::Mode};
10-
use libc::{self, c_int, c_void, off_t, size_t};
11+
use libc::{self, c_int, c_void, size_t};
1112
use std::{num::NonZeroUsize, os::unix::io::{AsRawFd, AsFd}};
1213

1314
libc_bitflags! {
@@ -428,8 +429,13 @@ pub unsafe fn mmap<F: AsFd>(
428429
addr.map_or(std::ptr::null_mut(), |a| usize::from(a) as *mut c_void);
429430

430431
let fd = f.map(|f| f.as_fd().as_raw_fd()).unwrap_or(-1);
431-
let ret =
432-
libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset);
432+
let ret = largefile_fn![libc::mmap](
433+
ptr, length.into(),
434+
prot.bits(),
435+
flags.bits(),
436+
fd,
437+
offset,
438+
);
433439

434440
if ret == libc::MAP_FAILED {
435441
Err(Errno::last())

src/sys/sendfile.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ 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;
10+
use crate::off_t;
1011
use crate::Result;
1112

1213
/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
@@ -33,7 +34,7 @@ pub fn sendfile<F1: AsFd, F2: AsFd>(
3334
.map(|offset| offset as *mut _)
3435
.unwrap_or(ptr::null_mut());
3536
let ret = unsafe {
36-
libc::sendfile(
37+
largefile_fn![libc::sendfile](
3738
out_fd.as_fd().as_raw_fd(),
3839
in_fd.as_fd().as_raw_fd(),
3940
offset,
@@ -125,8 +126,6 @@ cfg_if! {
125126

126127
cfg_if! {
127128
if #[cfg(target_os = "freebsd")] {
128-
use libc::c_int;
129-
130129
libc_bitflags!{
131130
/// Configuration options for [`sendfile`.](fn.sendfile.html)
132131
pub struct SfFlags: c_int {

src/sys/uio.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Vectored I/O
22
33
use crate::errno::Errno;
4+
use crate::off_t;
45
use crate::Result;
5-
use libc::{self, c_int, c_void, off_t, size_t};
6+
use libc::{self, c_int, c_void, size_t};
67
use std::io::{IoSlice, IoSliceMut};
78
use std::os::unix::io::{AsFd, AsRawFd};
89

@@ -50,7 +51,7 @@ pub fn pwritev<Fd: AsFd>(fd: Fd, iov: &[IoSlice<'_>], offset: off_t) -> Result<u
5051

5152
// SAFETY: same as in writev()
5253
let res = unsafe {
53-
libc::pwritev(
54+
largefile_fn![libc::pwritev](
5455
fd.as_fd().as_raw_fd(),
5556
iov.as_ptr() as *const libc::iovec,
5657
iov.len() as c_int,
@@ -80,7 +81,7 @@ pub fn preadv<Fd: AsFd>(
8081

8182
// SAFETY: same as in readv()
8283
let res = unsafe {
83-
libc::preadv(
84+
largefile_fn![libc::preadv](
8485
fd.as_fd().as_raw_fd(),
8586
iov.as_ptr() as *const libc::iovec,
8687
iov.len() as c_int,
@@ -97,7 +98,7 @@ pub fn preadv<Fd: AsFd>(
9798
// TODO: move to unistd
9899
pub fn pwrite<Fd: AsFd>(fd: Fd, buf: &[u8], offset: off_t) -> Result<usize> {
99100
let res = unsafe {
100-
libc::pwrite(
101+
largefile_fn![libc::pwrite](
101102
fd.as_fd().as_raw_fd(),
102103
buf.as_ptr() as *const c_void,
103104
buf.len() as size_t,
@@ -114,7 +115,7 @@ pub fn pwrite<Fd: AsFd>(fd: Fd, buf: &[u8], offset: off_t) -> Result<usize> {
114115
// TODO: move to unistd
115116
pub fn pread<Fd: AsFd>(fd: Fd, buf: &mut [u8], offset: off_t) -> Result<usize> {
116117
let res = unsafe {
117-
libc::pread(
118+
largefile_fn![libc::pread](
118119
fd.as_fd().as_raw_fd(),
119120
buf.as_mut_ptr() as *mut c_void,
120121
buf.len() as size_t,

src/unistd.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::errno::{self, Errno};
66
use crate::fcntl::{at_rawfd, AtFlags};
77
#[cfg(feature = "fs")]
88
use crate::fcntl::{fcntl, FcntlArg::F_SETFD, FdFlag, OFlag};
9+
use crate::off_t;
910
#[cfg(all(
1011
feature = "fs",
1112
any(
@@ -24,8 +25,8 @@ use crate::{Error, NixPath, Result};
2425
#[cfg(not(target_os = "redox"))]
2526
use cfg_if::cfg_if;
2627
use libc::{
27-
self, c_char, c_int, c_long, c_uint, c_void, gid_t, mode_t, off_t, pid_t,
28-
size_t, uid_t, PATH_MAX,
28+
self, c_char, c_int, c_long, c_uint, c_void, gid_t, mode_t, pid_t, size_t,
29+
uid_t, PATH_MAX,
2930
};
3031
use std::convert::Infallible;
3132
use std::ffi::{CStr, OsString};
@@ -1168,7 +1169,7 @@ pub enum Whence {
11681169
///
11691170
/// See also [lseek(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html)
11701171
pub fn lseek(fd: RawFd, offset: off_t, whence: Whence) -> Result<off_t> {
1171-
let res = unsafe { libc::lseek(fd, offset, whence as i32) };
1172+
let res = unsafe { largefile_fn![libc::lseek](fd, offset, whence as i32) };
11721173

11731174
Errno::result(res).map(|r| r as off_t)
11741175
}
@@ -1247,7 +1248,9 @@ pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> {
12471248
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
12481249
pub fn truncate<P: ?Sized + NixPath>(path: &P, len: off_t) -> Result<()> {
12491250
let res = path
1250-
.with_nix_path(|cstr| unsafe { libc::truncate(cstr.as_ptr(), len) })?;
1251+
.with_nix_path(|cstr| unsafe {
1252+
largefile_fn![libc::truncate](cstr.as_ptr(), len)
1253+
})?;
12511254

12521255
Errno::result(res).map(drop)
12531256
}
@@ -1257,7 +1260,9 @@ pub fn truncate<P: ?Sized + NixPath>(path: &P, len: off_t) -> Result<()> {
12571260
/// See also
12581261
/// [ftruncate(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html)
12591262
pub fn ftruncate<Fd: AsFd>(fd: Fd, len: off_t) -> Result<()> {
1260-
Errno::result(unsafe { libc::ftruncate(fd.as_fd().as_raw_fd(), len) }).map(drop)
1263+
Errno::result(unsafe {
1264+
largefile_fn![libc::ftruncate](fd.as_fd().as_raw_fd(), len)
1265+
}).map(drop)
12611266
}
12621267

12631268
pub fn isatty(fd: RawFd) -> Result<bool> {

test/common/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ cfg_if! {
3232
}
3333
}
3434

35+
/// Skip the test if we cannot handle offsets larger than 32 bits.
36+
#[macro_export]
37+
macro_rules! require_largefile {
38+
($name:expr) => {
39+
if (nix::off_t::MAX >> 31) <= 1 {
40+
crate::skip!(
41+
"{} requires file offsets \
42+
larger than 32 bits. Skipping test.",
43+
$name
44+
);
45+
}
46+
};
47+
}
48+
3549
/// Skip the test if we don't have the ability to mount file systems.
3650
#[cfg(target_os = "freebsd")]
3751
#[macro_export]

test/sys/test_mman.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
2-
use std::{num::NonZeroUsize, os::unix::io::BorrowedFd};
2+
use nix::unistd::{lseek, sysconf, write, SysconfVar, Whence};
3+
use std::num::NonZeroUsize;
4+
use std::os::unix::io::{AsRawFd, BorrowedFd};
5+
use tempfile::tempfile;
6+
7+
use crate::{require_largefile, skip};
38

49
#[test]
510
fn test_mmap_anonymous() {
@@ -19,6 +24,41 @@ fn test_mmap_anonymous() {
1924
}
2025
}
2126

27+
#[test]
28+
fn test_mmap_largefile() {
29+
require_largefile!("test_mmap_largefile");
30+
31+
// Calculate an offset that requires more than 32 bits to represent
32+
// and is a multiple of the page size.
33+
let pagesize = match sysconf(SysconfVar::PAGE_SIZE) {
34+
Ok(Some(x)) => x,
35+
_ => {
36+
skip!("test_mmap_largefile: Could not determine page size. Skipping test.");
37+
}
38+
}
39+
as i64;
40+
let pages_in_32bits = 1_0000_0000i64 / pagesize;
41+
let offset = ((pages_in_32bits + 2) * pagesize).try_into().unwrap();
42+
// Create a file, seek to offset, and write some bytes.
43+
let file = tempfile().unwrap();
44+
lseek(file.as_raw_fd(), offset, Whence::SeekSet).unwrap();
45+
write(file.as_raw_fd(), b"xyzzy").unwrap();
46+
// Check that we can map it and see the bytes.
47+
let slice = unsafe {
48+
let res = mmap(
49+
None,
50+
NonZeroUsize::new(5).unwrap(),
51+
ProtFlags::PROT_READ,
52+
MapFlags::MAP_PRIVATE,
53+
Some(&file),
54+
offset,
55+
);
56+
assert!(res.is_ok());
57+
std::slice::from_raw_parts(res.unwrap() as *const u8, 5)
58+
};
59+
assert_eq!(slice, b"xyzzy");
60+
}
61+
2262
#[test]
2363
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
2464
fn test_mremap_grow() {

0 commit comments

Comments
 (0)