From 8f3b31cd2717415aa51070c302e256a64257a784 Mon Sep 17 00:00:00 2001 From: hulxv Date: Fri, 21 Nov 2025 15:44:14 +0200 Subject: [PATCH 1/2] feat: Support fstat in linux refactor: rename `macos_fbsd_solarish_write_stat_buf` to `write_stat_buf` refactor: rename `macos_fbsd_solarish_fstat` to `fstat` feat: support `fstat` in linux test: testing support of `fstat` in linux fix: missed add `Os::Linux` for supported OSs in `fstat` feat: add nanosecond fields to file metadata in `EvalContextExtPrivate` add `fstat` to foreign items in unix enhance test of `fstat` fix the test --- src/shims/unix/foreign_items.rs | 5 ++++ src/shims/unix/freebsd/foreign_items.rs | 2 +- src/shims/unix/fs.rs | 28 +++++++++---------- src/shims/unix/linux/foreign_items.rs | 6 ++++- src/shims/unix/macos/foreign_items.rs | 4 +-- src/shims/unix/solarish/foreign_items.rs | 2 +- tests/pass-dep/libc/libc-fs.rs | 34 ++++++++++++++++++++++++ 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index 114f1a321f..95a0a063bf 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -345,6 +345,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.symlink(target, linkpath)?; this.write_scalar(result, dest)?; } + "fstat" => { + let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.fstat(fd, buf)?; + this.write_scalar(result, dest)?; + } "rename" => { let [oldpath, newpath] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _, *const _) -> i32), diff --git a/src/shims/unix/freebsd/foreign_items.rs b/src/shims/unix/freebsd/foreign_items.rs index 413df85ee3..2a1969049c 100644 --- a/src/shims/unix/freebsd/foreign_items.rs +++ b/src/shims/unix/freebsd/foreign_items.rs @@ -150,7 +150,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "fstat" | "fstat@FBSD_1.0" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; + let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir_r" | "readdir_r@FBSD_1.0" => { diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 16214d7ef1..0b98ab3491 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -118,7 +118,7 @@ impl UnixFileDescription for FileHandle { impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn macos_fbsd_solarish_write_stat_buf( + fn write_stat_buf( &mut self, metadata: FileMetadata, buf_op: &OpTy<'tcx>, @@ -141,8 +141,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { ("st_gid", metadata.gid.into()), ("st_rdev", 0), ("st_atime", access_sec.into()), + ("st_atime_nsec", access_nsec.into()), ("st_mtime", modified_sec.into()), + ("st_mtime_nsec", modified_nsec.into()), ("st_ctime", 0), + ("st_ctime_nsec", 0), ("st_size", metadata.size.into()), ("st_blocks", 0), ("st_blksize", 0), @@ -550,7 +553,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } // `lstat` is used to get symlink metadata. @@ -583,22 +586,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } - fn macos_fbsd_solarish_fstat( - &mut self, - fd_op: &OpTy<'tcx>, - buf_op: &OpTy<'tcx>, - ) -> InterpResult<'tcx, Scalar> { + fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos) - { - panic!( - "`macos_fbsd_solaris_fstat` should not be called on {}", - this.tcx.sess.target.os - ); + if !matches!( + &this.tcx.sess.target.os, + Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux + ) { + panic!("`fstat` should not be called on {}", this.tcx.sess.target.os); } let fd = this.read_scalar(fd_op)?.to_i32()?; @@ -614,7 +612,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Ok(metadata) => metadata, Err(err) => return this.set_last_error_and_return_i32(err), }; - interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?)) + interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } fn linux_statx( diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index 79052698f4..5ced0b7f32 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -53,7 +53,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } - + "fstat" => { + let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.fstat(fd, buf)?; + this.write_scalar(result, dest)?; + } // epoll, eventfd "epoll_create1" => { let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index 1b273593de..731b9aa5d5 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -56,9 +56,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat64" | "fstat$INODE64" => { + "fstat$INODE64" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; + let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } "opendir$INODE64" => { diff --git a/src/shims/unix/solarish/foreign_items.rs b/src/shims/unix/solarish/foreign_items.rs index 6335e6bc96..d081a1f2d0 100644 --- a/src/shims/unix/solarish/foreign_items.rs +++ b/src/shims/unix/solarish/foreign_items.rs @@ -102,7 +102,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "fstat" | "fstat64" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_fbsd_solarish_fstat(fd, buf)?; + let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir" => { diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index 41c3e3a122..b1872e8076 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -42,6 +42,7 @@ fn main() { test_posix_fallocate::(libc::posix_fallocate64); #[cfg(target_os = "linux")] test_sync_file_range(); + test_fstat(); test_isatty(); test_read_and_uninit(); test_nofollow_not_symlink(); @@ -452,6 +453,39 @@ fn test_sync_file_range() { assert_eq!(result_2, 0); } +fn test_fstat() { + use std::mem::MaybeUninit; + use std::os::unix::io::AsRawFd; + + let path = utils::prepare_with_content("miri_test_libc_fstat.txt", b"hello"); + let file = File::open(&path).unwrap(); + let fd = file.as_raw_fd(); + + let mut stat: libc::stat = unsafe { MaybeUninit::zeroed().assume_init() }; + let res = unsafe { libc::fstat(fd, &mut stat) }; + assert_eq!(res, 0); + + assert_eq!(stat.st_size, 5); + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + + let _st_nlink = stat.st_nlink; + let _st_blksize = stat.st_blksize; + let _st_blocks = stat.st_blocks; + let _st_ino = stat.st_ino; + let _st_dev = stat.st_dev; + let _st_uid = stat.st_uid; + let _st_gid = stat.st_gid; + let _st_rdev = stat.st_rdev; + let _st_atime = stat.st_atime; + let _st_mtime = stat.st_mtime; + let _st_ctime = stat.st_ctime; + let _st_atime_nsec = stat.st_atime_nsec; + let _st_mtime_nsec = stat.st_mtime_nsec; + let _st_ctime_nsec = stat.st_ctime_nsec; + + remove_file(&path).unwrap(); +} + fn test_isatty() { // Testing whether our isatty shim returns the right value would require controlling whether // these streams are actually TTYs, which is hard. From 8fd353c770710d75c96cfe22f041dfb9ada1101e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 6 Dec 2025 11:23:47 +0100 Subject: [PATCH 2/2] use correct stat type and fix some redundancy --- src/shims/unix/freebsd/foreign_items.rs | 2 +- src/shims/unix/fs.rs | 9 +++++---- src/shims/unix/linux/foreign_items.rs | 5 ----- src/shims/unix/solarish/foreign_items.rs | 5 ----- tests/pass-dep/libc/libc-fs.rs | 6 ++++-- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/shims/unix/freebsd/foreign_items.rs b/src/shims/unix/freebsd/foreign_items.rs index 2a1969049c..9f7a19269c 100644 --- a/src/shims/unix/freebsd/foreign_items.rs +++ b/src/shims/unix/freebsd/foreign_items.rs @@ -148,7 +148,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat@FBSD_1.0" => { + "fstat@FBSD_1.0" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 0b98ab3491..8329cd47bb 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -130,7 +130,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?; - let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?; + // We do *not* use `deref_pointer_as` here since determining the right pointee type + // is highly non-trivial: it depends on which exact alias of the function was invoked + // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level + // which can be different between the libc used by std and the libc used by everyone else. + let buf = this.deref_pointer(buf_op)?; this.write_int_fields_named( &[ ("st_dev", metadata.dev.into()), @@ -156,9 +160,6 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) { this.write_int_fields_named( &[ - ("st_atime_nsec", access_nsec.into()), - ("st_mtime_nsec", modified_nsec.into()), - ("st_ctime_nsec", 0), ("st_birthtime", created_sec.into()), ("st_birthtime_nsec", created_nsec.into()), ("st_flags", 0), diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index 5ced0b7f32..4c76b08a45 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -53,11 +53,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } - "fstat" => { - let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.fstat(fd, buf)?; - this.write_scalar(result, dest)?; - } // epoll, eventfd "epoll_create1" => { let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/shims/unix/solarish/foreign_items.rs b/src/shims/unix/solarish/foreign_items.rs index d081a1f2d0..b5eca70c3b 100644 --- a/src/shims/unix/solarish/foreign_items.rs +++ b/src/shims/unix/solarish/foreign_items.rs @@ -100,11 +100,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } - "fstat" | "fstat64" => { - let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.fstat(fd, buf)?; - this.write_scalar(result, dest)?; - } "readdir" => { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.readdir64("dirent", dirp)?; diff --git a/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index b1872e8076..3a4ac2a1b3 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/tests/pass-dep/libc/libc-fs.rs @@ -461,13 +461,15 @@ fn test_fstat() { let file = File::open(&path).unwrap(); let fd = file.as_raw_fd(); - let mut stat: libc::stat = unsafe { MaybeUninit::zeroed().assume_init() }; - let res = unsafe { libc::fstat(fd, &mut stat) }; + let mut stat = MaybeUninit::::uninit(); + let res = unsafe { libc::fstat(fd, stat.as_mut_ptr()) }; assert_eq!(res, 0); + let stat = unsafe { stat.assume_init_ref() }; assert_eq!(stat.st_size, 5); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + // Check that all fields are initialized. let _st_nlink = stat.st_nlink; let _st_blksize = stat.st_blksize; let _st_blocks = stat.st_blocks;