Skip to content

Commit

Permalink
support 64-bit file offsets on 32-bit glibc/Linux
Browse files Browse the repository at this point in the history
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 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
    everywhere else.

 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.
  • Loading branch information
inglorion committed Jul 25, 2024
1 parent 25b437c commit b654094
Show file tree
Hide file tree
Showing 14 changed files with 415 additions and 84 deletions.
65 changes: 44 additions & 21 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::errno::Errno;
#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
use core::slice;
use libc::{c_int, c_uint, size_t, ssize_t};
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
#[cfg(any(
target_os = "netbsd",
apple_targets,
Expand Down Expand Up @@ -163,8 +163,12 @@ libc_bitflags!(
all(target_os = "linux", not(target_env = "musl"), not(target_env = "ohos")),
target_os = "redox"))]
O_FSYNC;
/// Allow files whose sizes can't be represented in an `off_t` to be opened.
/// On 32-bit Linux, O_LARGEFILE allows the use of file
/// offsets greater than 32 bits. Nix accepts the flag for
/// compatibility, but always opens files in large file mode
/// even if it isn't specified.
#[cfg(linux_android)]
#[cfg_attr(docsrs, doc(cfg(all())))]
O_LARGEFILE;
/// Do not update the file last access time during `read(2)`s.
#[cfg(linux_android)]
Expand Down Expand Up @@ -249,7 +253,7 @@ pub fn open<P: ?Sized + NixPath>(
use std::os::fd::FromRawFd;

let fd = path.with_nix_path(|cstr| unsafe {
libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
largefile_fn![open](cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
})?;
Errno::result(fd)?;

Expand Down Expand Up @@ -280,7 +284,11 @@ pub fn openat<P: ?Sized + NixPath, Fd: std::os::fd::AsFd>(
use std::os::fd::FromRawFd;

let fd = path.with_nix_path(|cstr| unsafe {
libc::openat(dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint)
largefile_fn![openat](
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
oflag.bits(),
mode.bits() as c_uint)
})?;
Errno::result(fd)?;

Expand Down Expand Up @@ -1303,23 +1311,26 @@ feature! {
/// file referred to by fd.
#[cfg(target_os = "linux")]
#[cfg(feature = "fs")]
pub fn fallocate<Fd: std::os::fd::AsFd>(
pub fn fallocate<Fd: std::os::fd::AsFd, Off: Into<i64>>(
fd: Fd,
mode: FallocateFlags,
offset: libc::off_t,
len: libc::off_t,
offset: Off,
len: Off,
) -> Result<()> {
use std::os::fd::AsRawFd;

let res = unsafe { libc::fallocate(fd.as_fd().as_raw_fd(), mode.bits(), offset, len) };
let res = unsafe {
largefile_fn![fallocate](
fd.as_fd().as_raw_fd(), mode.bits(), offset.into(), len.into())
};
Errno::result(res).map(drop)
}

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

#[cfg(any(target_os = "freebsd"))]
impl SpacectlRange {
Expand All @@ -1334,13 +1345,13 @@ impl SpacectlRange {

/// Remaining length of the range
#[inline]
pub fn len(&self) -> libc::off_t {
pub fn len(&self) -> off_t {
self.1
}

/// Next file offset to operate on
#[inline]
pub fn offset(&self) -> libc::off_t {
pub fn offset(&self) -> off_t {
self.0
}
}
Expand Down Expand Up @@ -1439,8 +1450,8 @@ pub fn fspacectl<Fd: std::os::fd::AsFd>(fd: Fd, range: SpacectlRange) -> Result<
#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined
pub fn fspacectl_all<Fd: std::os::fd::AsFd>(
fd: Fd,
offset: libc::off_t,
len: libc::off_t,
offset: off_t,
len: off_t,
) -> Result<()> {
use std::os::fd::AsRawFd;

Expand Down Expand Up @@ -1474,6 +1485,7 @@ pub fn fspacectl_all<Fd: std::os::fd::AsFd>(
mod posix_fadvise {
use crate::errno::Errno;
use crate::Result;
use std::os::unix::io::RawFd;

#[cfg(feature = "fs")]
libc_enum! {
Expand Down Expand Up @@ -1505,15 +1517,21 @@ mod posix_fadvise {
///
/// # See Also
/// * [`posix_fadvise`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fadvise.html)
pub fn posix_fadvise<Fd: std::os::fd::AsFd>(
pub fn posix_fadvise<Fd: std::os::fd::AsFd, Off: Into<i64>>(
fd: Fd,
offset: libc::off_t,
len: libc::off_t,
offset: Off,
len: Off,
advice: PosixFadviseAdvice,
) -> Result<()> {
use std::os::fd::AsRawFd;

let res = unsafe { libc::posix_fadvise(fd.as_fd().as_raw_fd(), offset, len, advice as libc::c_int) };
let res = unsafe {
largefile_fn![posix_fadvise](
fd.as_fd().as_raw_fd(),
offset.into(),
len.into(),
advice as libc::c_int)
};

if res == 0 {
Ok(())
Expand All @@ -1535,14 +1553,19 @@ mod posix_fadvise {
target_os = "fuchsia",
target_os = "wasi",
))]
pub fn posix_fallocate<Fd: std::os::fd::AsFd>(
pub fn posix_fallocate<Fd: std::os::fd::AsFd, Off: Into<i64>>(
fd: Fd,
offset: libc::off_t,
len: libc::off_t,
offset: Off,
len: Off,
) -> Result<()> {
use std::os::fd::AsRawFd;

let res = unsafe { libc::posix_fallocate(fd.as_fd().as_raw_fd(), offset, len) };
let res = unsafe {
largefile_fn![posix_fallocate](
fd.as_fd().as_raw_fd(),
offset.into(),
len.into())
};
match Errno::result(res) {
Err(err) => Err(err),
Ok(0) => Ok(()),
Expand Down
44 changes: 44 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use cfg_if::cfg_if;

// Thanks to Tokio for this macro
macro_rules! feature {
(
Expand Down Expand Up @@ -329,3 +331,45 @@ macro_rules! libc_enum {
}
};
}

cfg_if! {
if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
/// Function variant that supports large file positions.
///
/// On some platforms, the standard I/O functions support a limited
/// range of file positions, and there is an alternate set of
/// functions that support larger file positions. This macro takes
/// the identifier of a standard I/O function and returns the
/// identifier of the corresponding I/O function with large file
/// support.
#[allow(unused_macro_rules)]
macro_rules! largefile_fn {
[fallocate] => (libc::fallocate64);
[ftruncate] => (libc::ftruncate64);
[lseek] => (libc::lseek64);
[mmap] => (libc::mmap64);
[open] => (libc::open64);
[openat] => (libc::openat64);
[posix_fadvise] => (libc::posix_fadvise64);
[posix_fallocate] => (libc::posix_fallocate64);
[pread] => (libc::pread64);
[preadv] => (libc::preadv64);
[pwrite] => (libc::pwrite64);
[pwritev] => (libc::pwritev64);
[sendfile] => (libc::sendfile64);
[truncate] => (libc::truncate64);
}
} else {
/// Function variant that supports large file positions.
///
/// On some platforms, the standard I/O functions support a limited
/// range of file positions, and there is an alternate set of
/// functions that support larger file positions. This macro takes
/// the identifier of a standard I/O function and returns the
/// identifier of the corresponding I/O function with large file
/// support.
macro_rules! largefile_fn {
[$id:ident] => (libc::$id);
}
}
}
7 changes: 4 additions & 3 deletions src/sys/mman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::Result;
#[cfg(not(target_os = "android"))]
#[cfg(feature = "fs")]
use crate::{fcntl::OFlag, sys::stat::Mode};
use libc::{self, c_int, c_void, off_t, size_t};
use libc::{self, c_int, c_void, size_t};
use std::ptr::NonNull;
use std::{
num::NonZeroUsize,
Expand Down Expand Up @@ -398,13 +398,14 @@ pub unsafe fn mmap<F: AsFd>(
prot: ProtFlags,
flags: MapFlags,
f: F,
offset: off_t,
offset: i64,
) -> Result<NonNull<c_void>> {
let ptr = addr.map_or(std::ptr::null_mut(), |a| a.get() as *mut c_void);

let fd = f.as_fd().as_raw_fd();
let ret = unsafe {
libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset)
largefile_fn![mmap](
ptr, length.into(), prot.bits(), flags.bits(), fd, offset)
};

if ret == libc::MAP_FAILED {
Expand Down
30 changes: 15 additions & 15 deletions src/sys/sendfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use cfg_if::cfg_if;
use std::os::unix::io::{AsFd, AsRawFd};
use std::ptr;

use libc::{self, off_t};
use libc;

use crate::errno::Errno;
use crate::Result;
Expand All @@ -26,14 +26,14 @@ use crate::Result;
pub fn sendfile<F1: AsFd, F2: AsFd>(
out_fd: F1,
in_fd: F2,
offset: Option<&mut off_t>,
offset: Option<&mut i64>,
count: usize,
) -> Result<usize> {
let offset = offset
.map(|offset| offset as *mut _)
.unwrap_or(ptr::null_mut());
let ret = unsafe {
libc::sendfile(
largefile_fn![sendfile](
out_fd.as_fd().as_raw_fd(),
in_fd.as_fd().as_raw_fd(),
offset,
Expand Down Expand Up @@ -208,19 +208,19 @@ cfg_if! {
pub fn sendfile<F1: AsFd, F2: AsFd>(
in_fd: F1,
out_sock: F2,
offset: off_t,
offset: i64,
count: Option<usize>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>,
flags: SfFlags,
readahead: u16
) -> (Result<()>, off_t) {
) -> (Result<()>, i64) {
// Readahead goes in upper 16 bits
// Flags goes in lower 16 bits
// see `man 2 sendfile`
let ra32 = u32::from(readahead);
let flags: u32 = (ra32 << 16) | (flags.bits() as u32);
let mut bytes_sent: off_t = 0;
let mut bytes_sent: i64 = 0;
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
let return_code = unsafe {
Expand All @@ -229,7 +229,7 @@ cfg_if! {
offset,
count.unwrap_or(0),
hdtr_ptr as *mut libc::sf_hdtr,
&mut bytes_sent as *mut off_t,
&mut bytes_sent as *mut i64,
flags as c_int)
};
(Errno::result(return_code).and(Ok(())), bytes_sent)
Expand Down Expand Up @@ -258,12 +258,12 @@ cfg_if! {
pub fn sendfile<F1: AsFd, F2: AsFd>(
in_fd: F1,
out_sock: F2,
offset: off_t,
offset: i64,
count: Option<usize>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>,
) -> (Result<()>, off_t) {
let mut bytes_sent: off_t = 0;
) -> (Result<()>, i64) {
let mut bytes_sent: i64 = 0;
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
let return_code = unsafe {
Expand All @@ -272,7 +272,7 @@ cfg_if! {
offset,
count.unwrap_or(0),
hdtr_ptr as *mut libc::sf_hdtr,
&mut bytes_sent as *mut off_t,
&mut bytes_sent as *mut i64,
0)
};
(Errno::result(return_code).and(Ok(())), bytes_sent)
Expand Down Expand Up @@ -304,19 +304,19 @@ cfg_if! {
pub fn sendfile<F1: AsFd, F2: AsFd>(
in_fd: F1,
out_sock: F2,
offset: off_t,
count: Option<off_t>,
offset: i64,
count: Option<i64>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>
) -> (Result<()>, off_t) {
) -> (Result<()>, i64) {
let mut len = count.unwrap_or(0);
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw as *const libc::sf_hdtr);
let return_code = unsafe {
libc::sendfile(in_fd.as_fd().as_raw_fd(),
out_sock.as_fd().as_raw_fd(),
offset,
&mut len as *mut off_t,
&mut len as *mut i64,
hdtr_ptr as *mut libc::sf_hdtr,
0)
};
Expand Down
Loading

0 comments on commit b654094

Please sign in to comment.