From 058a8b9fa1a9a79422787cb3b1cab101dc0f4e14 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:40:13 +0300 Subject: [PATCH 1/2] Support fstat on non-file-backed FDs --- src/tools/miri/src/shims/files.rs | 28 ++++- src/tools/miri/src/shims/unix/fs.rs | 84 ++++++++++---- .../miri/src/shims/unix/linux_like/epoll.rs | 10 +- .../miri/src/shims/unix/linux_like/eventfd.rs | 10 +- src/tools/miri/src/shims/unix/mod.rs | 2 +- .../miri/src/shims/unix/virtual_socket.rs | 13 ++- src/tools/miri/src/shims/windows/fs.rs | 6 +- .../pass-dep/libc/libc-fstat-non-file.rs | 108 ++++++++++++++++++ 8 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 5468fd303742..d007853ead2f 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::collections::BTreeMap; -use std::fs::{File, Metadata}; +use std::fs::File; use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write}; use std::marker::CoercePointee; use std::ops::Deref; @@ -9,7 +9,7 @@ use rustc_abi::Size; -use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// A unique id for file descriptions. While we could use the address, considering that @@ -209,10 +209,23 @@ fn destroy<'tcx>( throw_unsup_format!("cannot close {}", self.name()); } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + /// Returns the host `fs::Metadata` for this FD, if available. + /// Used by host-aware shims like Windows's `GetFileInformationByHandle`. + /// Unrelated to Unix `fstat`, which goes through `fstat()`. + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors"); } + /// Return the metadata describing this FD for the `fstat`/`statx` family of syscalls. + /// File-backed FDs should call `FileMetadata::from_meta` with their host metadata. + /// Non-file-backed FDs should call `FileMetadata::synthetic` with an appropriate mode. + fn fstat<'tcx>( + &self, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + throw_unsup_format!("fstat is not supported on {}", self.name()); + } + fn is_tty(&self, _communicate_allowed: bool) -> bool { // Most FDs are not tty's and the consequence of a wrong `false` are minor, // so we use a default impl here. @@ -432,10 +445,17 @@ fn destroy<'tcx>( } } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(self.file.metadata()) } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + FileMetadata::from_meta(ecx, self.file.metadata()) + } + fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.file.is_terminal() } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 5adc5932883e..d318d3cecdb5 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -229,17 +229,22 @@ fn write_stat_buf( let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0)); let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); - let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?; // 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)?; + + // `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets + // (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target. + let mode_t_size = this.libc_ty_layout("mode_t").size; + let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap(); + this.write_int_fields_named( &[ ("st_dev", metadata.dev.into()), - ("st_mode", mode.try_into().unwrap()), + ("st_mode", mode.into()), ("st_nlink", 0), ("st_ino", 0), ("st_uid", metadata.uid.into()), @@ -343,6 +348,34 @@ fn dir_entry_fields( } } +fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else if file_type.is_symlink() { + "S_IFLNK" + } else { + // Certain file types are only available when the host is a Unix system. + #[cfg(unix)] + { + if file_type.is_socket() { + return "S_IFSOCK"; + } else if file_type.is_fifo() { + return "S_IFIFO"; + } else if file_type.is_char_device() { + return "S_IFCHR"; + } else if file_type.is_block_device() { + return "S_IFBLK"; + } + } + "S_IFREG" + } +} + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn open( @@ -747,13 +780,12 @@ fn linux_statx( Err(err) => return this.set_last_error_and_return_i32(err), }; - // The `mode` field specifies the type of the file and the permissions over the file for - // the owner, its group and other users. Given that we can only provide the file type - // without using platform specific methods, we only set the bits corresponding to the file - // type. This should be an `__u16` but `libc` provides its values as `u32`. + // `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in + // width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size. + let mode_t_size = this.libc_ty_layout("mode_t").size; let mode: u16 = metadata .mode - .to_u32()? + .to_uint(mode_t_size)? .try_into() .unwrap_or_else(|_| bug!("libc contains bad value for constant")); @@ -1632,7 +1664,7 @@ fn extract_sec_and_nsec<'tcx>( /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. -struct FileMetadata { +pub struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, @@ -1662,13 +1694,28 @@ fn from_fd_num<'tcx>( let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; - - let metadata = fd.metadata()?; - drop(fd); - FileMetadata::from_meta(ecx, metadata) + fd.fstat(ecx) } - fn from_meta<'tcx>( + pub(crate) fn synthetic<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + mode_name: &str, + size: u64, + ) -> InterpResult<'tcx, Result> { + let mode = ecx.eval_libc(mode_name); + interp_ok(Ok(FileMetadata { + mode, + size, + created: None, + accessed: None, + modified: None, + dev: 0, + uid: 0, + gid: 0, + })) + } + + pub(crate) fn from_meta<'tcx>( ecx: &mut MiriInterpCx<'tcx>, metadata: Result, ) -> InterpResult<'tcx, Result> { @@ -1680,16 +1727,7 @@ fn from_meta<'tcx>( }; let file_type = metadata.file_type(); - - let mode_name = if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else { - "S_IFLNK" - }; - - let mode = ecx.eval_libc(mode_name); + let mode = ecx.eval_libc(file_type_to_mode_name(file_type)); let size = metadata.len(); diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 7480db00d6ed..48085fa6ae26 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -10,7 +10,7 @@ use crate::shims::files::{ DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; type EpollEventKey = (FdId, FdNum); @@ -119,6 +119,14 @@ fn name(&self) -> &'static str { "epoll" } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + // On Linux, epoll is an "anonymous inode" reported as S_IFREG. + FileMetadata::synthetic(ecx, "S_IFREG", 0) + } + fn destroy<'tcx>( mut self, self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index d374a1e75f72..03bac1e7270d 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -5,8 +5,8 @@ use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; -use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -37,6 +37,14 @@ fn name(&self) -> &'static str { "event" } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. + FileMetadata::synthetic(ecx, "S_IFREG", 0) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index c55a28bfa7b2..9e8fa2a12d49 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -19,7 +19,7 @@ // All the Unix-specific extension traits pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; -pub use self::fs::{DirTable, EvalContextExt as _}; +pub use self::fs::{DirTable, EvalContextExt as _, FileMetadata}; pub use self::linux_like::epoll::EpollInterestTable; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 51092b80c1c6..16eba61c56ba 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -12,8 +12,8 @@ use crate::shims::files::{ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -83,6 +83,17 @@ fn name(&self) -> &'static str { } } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + let mode_name = match self.fd_type { + VirtualSocketType::Socketpair => "S_IFSOCK", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", + }; + FileMetadata::synthetic(ecx, mode_name, 0) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 1ee93cf911c5..ddbb2b1de018 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -22,7 +22,7 @@ fn name(&self) -> &'static str { "directory" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(self.path.metadata()) } @@ -49,7 +49,7 @@ fn name(&self) -> &'static str { "metadata-only" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(Ok(self.meta.clone())) } @@ -328,7 +328,7 @@ fn GetFileInformationByHandle( this.invalid_handle("GetFileInformationByHandle")? }; - let metadata = match desc.metadata()? { + let metadata = match desc.host_metadata()? { Ok(meta) => meta, Err(e) => { this.set_last_error(e)?; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs new file mode 100644 index 000000000000..c29c8ceaa1df --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs @@ -0,0 +1,108 @@ +//@ignore-target: windows # No libc fstat on non-file FDs on Windows +//@compile-flags: -Zmiri-disable-isolation + +use std::mem::MaybeUninit; + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::errno_check; + +fn main() { + test_fstat_socketpair(); + test_fstat_pipe(); + #[cfg(target_os = "linux")] + test_fstat_eventfd(); + #[cfg(target_os = "linux")] + test_fstat_epoll(); +} + +/// Calls fstat and returns a reference to the result. +/// We use `assume_init_ref` rather than `assume_init` because not all fields +/// of `libc::stat` may be written by fstat (e.g. `st_lspare` on macOS). +fn do_fstat(fd: i32, buf: &mut MaybeUninit) -> &libc::stat { + let res = unsafe { libc::fstat(fd, buf.as_mut_ptr()) }; + assert_eq!(res, 0, "fstat failed on fd {}", fd); + unsafe { buf.assume_init_ref() } +} + +fn assert_stat_fields_are_accessible(stat: &libc::stat) { + 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; +} + +/// Test fstat on socketpair file descriptors. +fn test_fstat_socketpair() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!( + stat.st_mode & libc::S_IFMT, + libc::S_IFSOCK, + "socketpair should have S_IFSOCK mode" + ); + assert_eq!(stat.st_size, 0, "socketpair should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on pipe file descriptors. +fn test_fstat_pipe() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO, "pipe should have S_IFIFO mode"); + assert_eq!(stat.st_size, 0, "pipe should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on eventfd file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_eventfd() { + let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK; + let fd = libc_utils::errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "eventfd should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} + +/// Test fstat on epoll file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_epoll() { + let fd = libc_utils::errno_result(unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "epoll should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} From c91a363db10281bf55e0e4a0dd88e5b04f924fb7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 26 Apr 2026 12:56:45 +0200 Subject: [PATCH 2/2] merge fstat and metadata functions --- src/tools/miri/src/lib.rs | 5 +- src/tools/miri/src/shims/files.rs | 32 +++------ src/tools/miri/src/shims/unix/fs.rs | 70 ++++++++++--------- .../miri/src/shims/unix/linux_like/epoll.rs | 9 ++- .../miri/src/shims/unix/linux_like/eventfd.rs | 9 ++- src/tools/miri/src/shims/unix/mod.rs | 2 +- .../miri/src/shims/unix/virtual_socket.rs | 9 ++- src/tools/miri/src/shims/windows/fs.rs | 22 ++++-- 8 files changed, 75 insertions(+), 83 deletions(-) diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index f41e3c20a7d5..5f3bf4a9e9e3 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -105,8 +105,9 @@ // Resolve ambiguity. #[doc(no_inline)] pub use rustc_const_eval::interpret::{self, AllocMap, Provenance as _}; -use rustc_log::tracing::{self, info, trace}; -use rustc_middle::{bug, span_bug}; +pub use rustc_data_structures::either::Either; +pub use rustc_log::tracing::{self, info, trace}; +pub use rustc_middle::{bug, span_bug}; #[cfg(all(feature = "native-lib", unix))] pub mod native_lib { diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d007853ead2f..04b84e6f3e67 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -9,7 +9,7 @@ use rustc_abi::Size; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; +use crate::shims::unix::UnixFileDescription; use crate::*; /// A unique id for file descriptions. While we could use the address, considering that @@ -209,23 +209,14 @@ fn destroy<'tcx>( throw_unsup_format!("cannot close {}", self.name()); } - /// Returns the host `fs::Metadata` for this FD, if available. - /// Used by host-aware shims like Windows's `GetFileInformationByHandle`. - /// Unrelated to Unix `fstat`, which goes through `fstat()`. - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + /// Returns the metadata for this FD, if available. + /// This is either host metadata, or a non-file-backed-FD type. + /// The latter is for new represented as a string storing a `libc` name so we only + /// support that kind of metadata on Unix targets. + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors"); } - /// Return the metadata describing this FD for the `fstat`/`statx` family of syscalls. - /// File-backed FDs should call `FileMetadata::from_meta` with their host metadata. - /// Non-file-backed FDs should call `FileMetadata::synthetic` with an appropriate mode. - fn fstat<'tcx>( - &self, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { - throw_unsup_format!("fstat is not supported on {}", self.name()); - } - fn is_tty(&self, _communicate_allowed: bool) -> bool { // Most FDs are not tty's and the consequence of a wrong `false` are minor, // so we use a default impl here. @@ -445,15 +436,8 @@ fn destroy<'tcx>( } } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.file.metadata()) - } - - fn fstat<'tcx>( - &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { - FileMetadata::from_meta(ecx, self.file.metadata()) + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.file.metadata())) } fn is_tty(&self, communicate_allowed: bool) -> bool { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index d318d3cecdb5..0988edaef2b0 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -348,34 +348,6 @@ fn dir_entry_fields( } } -fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { - #[cfg(unix)] - use std::os::unix::fs::FileTypeExt; - - if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else if file_type.is_symlink() { - "S_IFLNK" - } else { - // Certain file types are only available when the host is a Unix system. - #[cfg(unix)] - { - if file_type.is_socket() { - return "S_IFSOCK"; - } else if file_type.is_fifo() { - return "S_IFIFO"; - } else if file_type.is_char_device() { - return "S_IFCHR"; - } else if file_type.is_block_device() { - return "S_IFBLK"; - } - } - "S_IFREG" - } -} - impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn open( @@ -1662,9 +1634,37 @@ fn extract_sec_and_nsec<'tcx>( } } +fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else if file_type.is_symlink() { + "S_IFLNK" + } else { + // Certain file types are only available when the host is a Unix system. + #[cfg(unix)] + { + if file_type.is_socket() { + return "S_IFSOCK"; + } else if file_type.is_fifo() { + return "S_IFIFO"; + } else if file_type.is_char_device() { + return "S_IFCHR"; + } else if file_type.is_block_device() { + return "S_IFBLK"; + } + } + "S_IFREG" + } +} + /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. -pub struct FileMetadata { +struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, @@ -1694,18 +1694,20 @@ fn from_fd_num<'tcx>( let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; - fd.fstat(ecx) + match fd.metadata()? { + Either::Left(host) => Self::from_meta(ecx, host), + Either::Right(name) => Self::synthetic(ecx, name), + } } - pub(crate) fn synthetic<'tcx>( + fn synthetic<'tcx>( ecx: &mut MiriInterpCx<'tcx>, mode_name: &str, - size: u64, ) -> InterpResult<'tcx, Result> { let mode = ecx.eval_libc(mode_name); interp_ok(Ok(FileMetadata { mode, - size, + size: 0, created: None, accessed: None, modified: None, @@ -1715,7 +1717,7 @@ pub(crate) fn synthetic<'tcx>( })) } - pub(crate) fn from_meta<'tcx>( + fn from_meta<'tcx>( ecx: &mut MiriInterpCx<'tcx>, metadata: Result, ) -> InterpResult<'tcx, Result> { diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 48085fa6ae26..bd07e13d47fb 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -10,7 +10,7 @@ use crate::shims::files::{ DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; +use crate::shims::unix::UnixFileDescription; use crate::*; type EpollEventKey = (FdId, FdNum); @@ -119,12 +119,11 @@ fn name(&self) -> &'static str { "epoll" } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { // On Linux, epoll is an "anonymous inode" reported as S_IFREG. - FileMetadata::synthetic(ecx, "S_IFREG", 0) + interp_ok(Either::Right("S_IFREG")) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index 03bac1e7270d..7cccbd0e275c 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -5,8 +5,8 @@ use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; +use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -37,12 +37,11 @@ fn name(&self) -> &'static str { "event" } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. - FileMetadata::synthetic(ecx, "S_IFREG", 0) + interp_ok(Either::Right("S_IFREG")) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 9e8fa2a12d49..c55a28bfa7b2 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -19,7 +19,7 @@ // All the Unix-specific extension traits pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; -pub use self::fs::{DirTable, EvalContextExt as _, FileMetadata}; +pub use self::fs::{DirTable, EvalContextExt as _}; pub use self::linux_like::epoll::EpollInterestTable; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 16eba61c56ba..51bd30840ffb 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -12,8 +12,8 @@ use crate::shims::files::{ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; +use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -83,15 +83,14 @@ fn name(&self) -> &'static str { } } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { let mode_name = match self.fd_type { VirtualSocketType::Socketpair => "S_IFSOCK", VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", }; - FileMetadata::synthetic(ecx, mode_name, 0) + interp_ok(Either::Right(mode_name)) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index ddbb2b1de018..a6efc36d75f6 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -22,8 +22,10 @@ fn name(&self) -> &'static str { "directory" } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.path.metadata()) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.path.metadata())) } fn destroy<'tcx>( @@ -49,8 +51,10 @@ fn name(&self) -> &'static str { "metadata-only" } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(Ok(self.meta.clone())) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(Ok(self.meta.clone()))) } fn destroy<'tcx>( @@ -328,12 +332,16 @@ fn GetFileInformationByHandle( this.invalid_handle("GetFileInformationByHandle")? }; - let metadata = match desc.host_metadata()? { - Ok(meta) => meta, - Err(e) => { + let metadata = match desc.metadata()? { + Either::Left(Ok(meta)) => meta, + Either::Left(Err(e)) => { this.set_last_error(e)?; return interp_ok(this.eval_windows("c", "FALSE")); } + Either::Right(_mode) => + throw_unsup_format!( + "`GetFileInformationByHandle` is not supported on non-file-backed handles" + ), }; let size = metadata.len();