From 78a748229a190d2e77599b72ccce8a03b8d9fb40 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Tue, 4 Nov 2025 20:20:49 +0100 Subject: [PATCH 1/6] impl posix_fallocate + add tests --- src/shims/unix/foreign_items.rs | 35 ++++++++++++++++ src/shims/unix/fs.rs | 60 +++++++++++++++++++++++++++ tests/pass-dep/libc/libc-fs.rs | 72 +++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index a37a34f8df..0e6b3fe2a4 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -487,6 +487,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // fadvise is only informational, we can ignore it. this.write_null(dest)?; } + + // only macos doesn't support `posix_fallocate` + "posix_fallocate" if &*this.tcx.sess.target.os != "macos" => { + let [fd, offset, len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t) -> i32), + link_name, + abi, + args, + )?; + + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; + let len = this.read_scalar(len)?.to_int(len.layout.size)?; + + let result = this.posix_fallocate(fd, offset, len)?; + this.write_scalar(result, dest)?; + } + + // only macos doesn't support `posix_fallocate` + "posix_fallocate64" if &*this.tcx.sess.target.os != "macos" => { + let [fd, offset, len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32), + link_name, + abi, + args, + )?; + + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; + let len = this.read_scalar(len)?.to_int(len.layout.size)?; + + let result = this.posix_fallocate(fd, offset, len)?; + this.write_scalar(result, dest)?; + } + "realpath" => { let [path, resolved_path] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _), diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 137e60aaba..127be9d6be 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -6,6 +6,7 @@ use std::fs::{ remove_file, rename, }; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; +use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::time::SystemTime; @@ -1202,6 +1203,65 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } + fn posix_fallocate( + &mut self, + fd_num: i32, + offset: i128, + len: i128, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + // According to the man page of `possix_fallocate`, it returns the error code instead + // of setting `errno`. + let ebadf = Scalar::from_i32(this.eval_libc_i32("EBADF")); + let einval = Scalar::from_i32(this.eval_libc_i32("EINVAL")); + let enodev = Scalar::from_i32(this.eval_libc_i32("ENODEV")); + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`posix_fallocate`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return interp_ok(ebadf); + } + + let Some(fd) = this.machine.fds.get(fd_num) else { + return interp_ok(ebadf); + }; + + let file = match fd.downcast::() { + Some(file_handle) => file_handle, + // Man page specifies to return ENODEV if `fd` is not a regular file. + None => return interp_ok(enodev), + }; + + // EINVAL is returned when: "offset was less than 0, or len was less than or equal to 0". + if offset < 0 || len <= 0 { + return interp_ok(einval); + } + + if file.writable { + let current_size = match file.file.metadata() { + Ok(metadata) => metadata.size(), + Err(err) => return this.io_error_to_errnum(err), + }; + let new_size = match offset.checked_add(len) { + Some(size) => size.try_into().unwrap(), // We just checked negative `offset` and `len`. + None => return interp_ok(einval), + }; + // `posix_fallocate` only specifies increasing the file size. + if current_size < new_size { + let result = file.file.set_len(new_size); + let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; + interp_ok(Scalar::from_i32(result)) + } else { + interp_ok(Scalar::from_i32(0)) + } + } else { + // The file is not writable. + interp_ok(ebadf) + } + } + fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the // underlying disk to finish writing. In the interest of host compatibility, diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index 86cf2a041f..1d951d08a8 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -37,6 +37,10 @@ fn main() { #[cfg(target_os = "linux")] test_posix_fadvise(); #[cfg(target_os = "linux")] + test_posix_fallocate::(libc::posix_fallocate); + #[cfg(target_os = "linux")] + test_posix_fallocate::(libc::posix_fallocate64); + #[cfg(target_os = "linux")] test_sync_file_range(); test_isatty(); test_read_and_uninit(); @@ -335,6 +339,74 @@ fn test_posix_fadvise() { assert_eq!(result, 0); } +#[cfg(target_os = "linux")] +fn test_posix_fallocate>( + posix_fallocate: unsafe extern "C" fn(fd: libc::c_int, offset: T, len: T) -> libc::c_int, +) { + // libc::off_t is i32 in target i686-unknown-linux-gnu + // https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/type.off_t.html + + let test_errors = || { + // invalid fd + let ret = unsafe { posix_fallocate(42, T::from(0), T::from(10)) }; + assert_eq!(ret, libc::EBADF); + + let path = utils::prepare("miri_test_libc_possix_fallocate_errors.txt"); + let file = File::create(&path).unwrap(); + + // invalid offset + let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(-10), T::from(10)) }; + assert_eq!(ret, libc::EINVAL); + + // invalid len + let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(0), T::from(-10)) }; + assert_eq!(ret, libc::EINVAL); + + // fd not writable + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY) }; + let ret = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) }; + assert_eq!(ret, libc::EBADF); + }; + + let test = || { + let bytes = b"hello"; + let path = utils::prepare("miri_test_libc_fs_ftruncate.txt"); + let mut file = File::create(&path).unwrap(); + file.write_all(bytes).unwrap(); + file.sync_all().unwrap(); + assert_eq!(file.metadata().unwrap().len(), 5); + + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDWR) }; + + // Allocate to a bigger size from offset 0 + let mut res = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) }; + assert_eq!(res, 0); + assert_eq!(file.metadata().unwrap().len(), 10); + + // Write after allocation + file.write(b"dup").unwrap(); + file.sync_all().unwrap(); + assert_eq!(file.metadata().unwrap().len(), 10); + + // Can't truncate to a smaller size with possix_fallocate + res = unsafe { posix_fallocate(fd, T::from(0), T::from(3)) }; + assert_eq!(res, 0); + assert_eq!(file.metadata().unwrap().len(), 10); + + // Allocate from offset + res = unsafe { posix_fallocate(fd, T::from(7), T::from(7)) }; + assert_eq!(res, 0); + assert_eq!(file.metadata().unwrap().len(), 14); + + remove_file(&path).unwrap(); + }; + + test_errors(); + test(); +} + #[cfg(target_os = "linux")] fn test_sync_file_range() { use std::io::Write; From 41dcec7bf608f8ee336e78f475086e25ea8a2790 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Tue, 4 Nov 2025 20:41:09 +0100 Subject: [PATCH 2/6] correct way to get file size --- src/shims/unix/fs.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 127be9d6be..06a4659c07 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -6,7 +6,6 @@ use std::fs::{ remove_file, rename, }; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::time::SystemTime; @@ -1241,7 +1240,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if file.writable { let current_size = match file.file.metadata() { - Ok(metadata) => metadata.size(), + Ok(metadata) => metadata.len(), Err(err) => return this.io_error_to_errnum(err), }; let new_size = match offset.checked_add(len) { From 749e9d4be9e55b5c541734d62208dad213356246 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Wed, 12 Nov 2025 10:11:42 +0100 Subject: [PATCH 3/6] address review --- src/shims/unix/foreign_items.rs | 16 ++++++--- src/shims/unix/fs.rs | 62 ++++++++++++++++++--------------- tests/pass-dep/libc/libc-fs.rs | 7 ++-- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index 0e6b3fe2a4..3f6502c4d7 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -488,8 +488,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - // only macos doesn't support `posix_fallocate` - "posix_fallocate" if &*this.tcx.sess.target.os != "macos" => { + "posix_fallocate" => { + // posix_fallocate is not supported by macos. + this.check_target_os( + &["linux", "freebsd", "solaris", "illumos", "android"], + link_name, + )?; let [fd, offset, len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t) -> i32), link_name, @@ -505,8 +509,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } - // only macos doesn't support `posix_fallocate` - "posix_fallocate64" if &*this.tcx.sess.target.os != "macos" => { + "posix_fallocate64" => { + // posix_fallocate is not supported by macos. + this.check_target_os( + &["linux", "freebsd", "solaris", "illumos", "android"], + link_name, + )?; let [fd, offset, len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32), link_name, diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 06a4659c07..9a81077ae2 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -1202,6 +1202,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } + /// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead + /// of setting `errno`. fn posix_fallocate( &mut self, fd_num: i32, @@ -1210,54 +1212,56 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - // According to the man page of `possix_fallocate`, it returns the error code instead - // of setting `errno`. - let ebadf = Scalar::from_i32(this.eval_libc_i32("EBADF")); - let einval = Scalar::from_i32(this.eval_libc_i32("EINVAL")); - let enodev = Scalar::from_i32(this.eval_libc_i32("ENODEV")); - // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`posix_fallocate`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return interp_ok(ebadf); + // Return error code "EBADF" (bad fd). + return interp_ok(this.eval_libc("EBADF")); } let Some(fd) = this.machine.fds.get(fd_num) else { - return interp_ok(ebadf); + return interp_ok(this.eval_libc("EBADF")); }; let file = match fd.downcast::() { Some(file_handle) => file_handle, // Man page specifies to return ENODEV if `fd` is not a regular file. - None => return interp_ok(enodev), + None => return interp_ok(this.eval_libc("ENODEV")), }; // EINVAL is returned when: "offset was less than 0, or len was less than or equal to 0". if offset < 0 || len <= 0 { - return interp_ok(einval); + return interp_ok(this.eval_libc("EINVAL")); } - if file.writable { - let current_size = match file.file.metadata() { - Ok(metadata) => metadata.len(), - Err(err) => return this.io_error_to_errnum(err), - }; - let new_size = match offset.checked_add(len) { - Some(size) => size.try_into().unwrap(), // We just checked negative `offset` and `len`. - None => return interp_ok(einval), + if !file.writable { + // The file is not writable. + return interp_ok(this.eval_libc("EBADF")); + } + + let current_size = match file.file.metadata() { + Ok(metadata) => metadata.len(), + Err(err) => return this.io_error_to_errnum(err), + }; + let new_size = match offset.checked_add(len) { + // u64::from(i128) can fail if: + // - the value is negative, but we checed this above with `offset < 0 || len <= 0` + // - the value is too big/small to fit in u64, this should not happen since the callers + // check if the value is a `i32` or `i64`. + // So this unwrap is safe to do. + Some(size) => u64::try_from(size).unwrap(), + None => return interp_ok(this.eval_libc("EFBIG")), // Size to big + }; + // `posix_fallocate` only specifies increasing the file size. + if current_size < new_size { + let result = match file.file.set_len(new_size) { + Ok(()) => Scalar::from_i32(0), + Err(e) => this.io_error_to_errnum(e)?, }; - // `posix_fallocate` only specifies increasing the file size. - if current_size < new_size { - let result = file.file.set_len(new_size); - let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; - interp_ok(Scalar::from_i32(result)) - } else { - interp_ok(Scalar::from_i32(0)) - } + + interp_ok(result) } else { - // The file is not writable. - interp_ok(ebadf) + interp_ok(Scalar::from_i32(0)) } } diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index 1d951d08a8..cf2190dfef 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -36,9 +36,7 @@ fn main() { test_posix_realpath_errors(); #[cfg(target_os = "linux")] test_posix_fadvise(); - #[cfg(target_os = "linux")] test_posix_fallocate::(libc::posix_fallocate); - #[cfg(target_os = "linux")] test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range(); @@ -339,7 +337,6 @@ fn test_posix_fadvise() { assert_eq!(result, 0); } -#[cfg(target_os = "linux")] fn test_posix_fallocate>( posix_fallocate: unsafe extern "C" fn(fd: libc::c_int, offset: T, len: T) -> libc::c_int, ) { @@ -351,7 +348,7 @@ fn test_posix_fallocate>( let ret = unsafe { posix_fallocate(42, T::from(0), T::from(10)) }; assert_eq!(ret, libc::EBADF); - let path = utils::prepare("miri_test_libc_possix_fallocate_errors.txt"); + let path = utils::prepare("miri_test_libc_posix_fallocate_errors.txt"); let file = File::create(&path).unwrap(); // invalid offset @@ -371,7 +368,7 @@ fn test_posix_fallocate>( let test = || { let bytes = b"hello"; - let path = utils::prepare("miri_test_libc_fs_ftruncate.txt"); + let path = utils::prepare("miri_test_libc_posix_fallocate.txt"); let mut file = File::create(&path).unwrap(); file.write_all(bytes).unwrap(); file.sync_all().unwrap(); From 21651ee21bb967a8fb2b2df523b13f6d978071f7 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Wed, 12 Nov 2025 13:29:11 +0100 Subject: [PATCH 4/6] uses new Os enum instead of strings --- src/shims/unix/foreign_items.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index 3f6502c4d7..e31ec5a60e 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -491,7 +491,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "posix_fallocate" => { // posix_fallocate is not supported by macos. this.check_target_os( - &["linux", "freebsd", "solaris", "illumos", "android"], + &[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::Android], link_name, )?; let [fd, offset, len] = this.check_shim_sig( @@ -512,7 +512,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "posix_fallocate64" => { // posix_fallocate is not supported by macos. this.check_target_os( - &["linux", "freebsd", "solaris", "illumos", "android"], + &[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::Android], link_name, )?; let [fd, offset, len] = this.check_shim_sig( From 66de73e2becb3017264596d501959c8658045f61 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Wed, 12 Nov 2025 14:23:53 +0100 Subject: [PATCH 5/6] only test when not in macos. --- tests/pass-dep/libc/libc-fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index cf2190dfef..89506fe578 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -36,7 +36,9 @@ fn main() { test_posix_realpath_errors(); #[cfg(target_os = "linux")] test_posix_fadvise(); + #[cfg(not(target_os = "macos"))] test_posix_fallocate::(libc::posix_fallocate); + #[cfg(not(target_os = "macos"))] test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range(); @@ -337,6 +339,7 @@ fn test_posix_fadvise() { assert_eq!(result, 0); } +#[cfg(not(target_os = "macos"))] fn test_posix_fallocate>( posix_fallocate: unsafe extern "C" fn(fd: libc::c_int, offset: T, len: T) -> libc::c_int, ) { From be13e9480554527edd2dc76865882f22427a9534 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Wed, 12 Nov 2025 15:07:21 +0100 Subject: [PATCH 6/6] posix_fallocate64 is only supported on linux and android --- src/shims/unix/foreign_items.rs | 7 ++----- tests/pass-dep/libc/libc-fs.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index e31ec5a60e..856455d957 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -510,11 +510,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "posix_fallocate64" => { - // posix_fallocate is not supported by macos. - this.check_target_os( - &[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::Android], - link_name, - )?; + // posix_fallocate64 is only supported on Linux and Android + this.check_target_os(&[Os::Linux, Os::Android], link_name)?; let [fd, offset, len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32), link_name, diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index 89506fe578..41c3e3a122 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -38,7 +38,7 @@ fn main() { test_posix_fadvise(); #[cfg(not(target_os = "macos"))] test_posix_fallocate::(libc::posix_fallocate); - #[cfg(not(target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "android"))] test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range();