diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index a37a34f8df..856455d957 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -487,6 +487,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // fadvise is only informational, we can ignore it. this.write_null(dest)?; } + + "posix_fallocate" => { + // posix_fallocate is not supported by macos. + this.check_target_os( + &[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::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, + 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)?; + } + + "posix_fallocate64" => { + // 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, + 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..9a81077ae2 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -1202,6 +1202,69 @@ 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, + offset: i128, + len: i128, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`posix_fallocate`", reject_with)?; + // 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(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(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(this.eval_libc("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)?, + }; + + interp_ok(result) + } else { + interp_ok(Scalar::from_i32(0)) + } + } + 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..41c3e3a122 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -36,6 +36,10 @@ 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(any(target_os = "linux", target_os = "android"))] + test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range(); test_isatty(); @@ -335,6 +339,74 @@ 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, +) { + // 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_posix_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_posix_fallocate.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;