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 5468fd303742..04b84e6f3e67 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; @@ -209,7 +209,11 @@ fn destroy<'tcx>( throw_unsup_format!("cannot close {}", self.name()); } - fn 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"); } @@ -432,8 +436,8 @@ fn destroy<'tcx>( } } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(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 5adc5932883e..0988edaef2b0 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()), @@ -747,13 +752,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")); @@ -1630,6 +1634,34 @@ 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. struct FileMetadata { @@ -1662,10 +1694,27 @@ fn from_fd_num<'tcx>( let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; + match fd.metadata()? { + Either::Left(host) => Self::from_meta(ecx, host), + Either::Right(name) => Self::synthetic(ecx, name), + } + } - let metadata = fd.metadata()?; - drop(fd); - FileMetadata::from_meta(ecx, metadata) + fn synthetic<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + mode_name: &str, + ) -> InterpResult<'tcx, Result> { + let mode = ecx.eval_libc(mode_name); + interp_ok(Ok(FileMetadata { + mode, + size: 0, + created: None, + accessed: None, + modified: None, + dev: 0, + uid: 0, + gid: 0, + })) } fn from_meta<'tcx>( @@ -1680,16 +1729,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..bd07e13d47fb 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -119,6 +119,13 @@ fn name(&self) -> &'static str { "epoll" } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + // On Linux, epoll is an "anonymous inode" reported as S_IFREG. + interp_ok(Either::Right("S_IFREG")) + } + 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..7cccbd0e275c 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -37,6 +37,13 @@ fn name(&self) -> &'static str { "event" } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. + interp_ok(Either::Right("S_IFREG")) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 51092b80c1c6..51bd30840ffb 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -83,6 +83,16 @@ fn name(&self) -> &'static str { } } + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + let mode_name = match self.fd_type { + VirtualSocketType::Socketpair => "S_IFSOCK", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", + }; + interp_ok(Either::Right(mode_name)) + } + 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..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 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 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>( @@ -329,11 +333,15 @@ fn GetFileInformationByHandle( }; let metadata = match desc.metadata()? { - Ok(meta) => meta, - Err(e) => { + 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(); 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) }); +}