From 24a15e9937bdd8b0338a85e153aeabc7c52bb02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20=C3=87i=C3=A7ekel?= Date: Tue, 6 Oct 2026 15:57:02 +0300 Subject: [PATCH] Require F_ALLOCATEALL on Apple fallocate. F_PREALLOCATE without that flag can return success after reserving only a few megabytes. The next ftruncate then leaves a sparse file that later writes can hit ENOSPC on. Co-authored-by: Cursor --- src/backend/libc/fs/syscalls.rs | 8 +++++--- tests/fs/fallocate.rs | 31 +++++++++++++++++++++++++++++++ tests/fs/main.rs | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 tests/fs/fallocate.rs diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index c95eaf561..794f9a7cb 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -1808,7 +1808,9 @@ pub(crate) fn fallocate( let new_len = offset.checked_add(len).ok_or(io::Errno::FBIG)?; let mut store = c::fstore_t { - fst_flags: c::F_ALLOCATECONTIG, + // `F_ALLOCATEALL` is required. Without it, `F_PREALLOCATE` may + // succeed after reserving only a fraction of `fst_length`. + fst_flags: c::F_ALLOCATECONTIG | c::F_ALLOCATEALL, fst_posmode: c::F_PEOFPOSMODE, fst_offset: 0, fst_length: new_len, @@ -1817,9 +1819,9 @@ pub(crate) fn fallocate( unsafe { if c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store) == -1 { // Unable to allocate contiguous disk space; attempt to allocate - // non-contiguously. + // non-contiguously, still all-or-nothing. store.fst_flags = c::F_ALLOCATEALL; - let _ = ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?; + ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?; } ret(c::ftruncate(borrowed_fd(fd), new_len)) } diff --git a/tests/fs/fallocate.rs b/tests/fs/fallocate.rs new file mode 100644 index 000000000..b61c5854f --- /dev/null +++ b/tests/fs/fallocate.rs @@ -0,0 +1,31 @@ +//! Apple `F_PREALLOCATE` without `F_ALLOCATEALL` can return success after +//! allocating only a fraction of the requested length. + +#![cfg(apple)] + +use rustix::fs::{fallocate, fstat, fsync, openat, FallocateFlags, Mode, OFlags, CWD}; + +#[test] +fn apple_fallocate_reserves_requested_space() { + let tmp = tempfile::tempdir().unwrap(); + let dir = openat(CWD, tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap(); + let file = openat( + &dir, + "file", + OFlags::RDWR | OFlags::TRUNC | OFlags::CREATE, + Mode::RUSR | Mode::WUSR, + ) + .unwrap(); + + // Large enough that a partial `F_ALLOCATECONTIG` allocation (a few MiB) + // would fall short of the request. + let requested = 16 * 1024 * 1024; + fallocate(&file, FallocateFlags::empty(), 0, requested).unwrap(); + fsync(&file).unwrap(); + + let allocated = fstat(&file).unwrap().st_blocks as u64 * 512; + assert!( + allocated >= requested, + "fallocate reported success after reserving {allocated} of {requested} bytes" + ); +} diff --git a/tests/fs/main.rs b/tests/fs/main.rs index 84ac2aef3..a7257f18e 100644 --- a/tests/fs/main.rs +++ b/tests/fs/main.rs @@ -7,6 +7,8 @@ mod chmodat; #[cfg(not(target_os = "redox"))] mod dir; +#[cfg(apple)] +mod fallocate; mod fcntl; #[cfg(not(any( target_os = "emscripten",