Merge pull request #4812 from enthropy7/master

Support fstat on non-file-backed FDs
This commit is contained in:
Ralf Jung
2026-04-26 11:23:07 +00:00
committed by GitHub
8 changed files with 217 additions and 32 deletions
+3 -2
View File
@@ -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 {
+8 -4
View File
@@ -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<fs::Metadata>> {
/// 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<io::Result<fs::Metadata>, &'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<Metadata>> {
interp_ok(self.file.metadata())
fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
interp_ok(Either::Left(self.file.metadata()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
+60 -20
View File
@@ -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<FileMetadata, IoError>> {
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();
@@ -119,6 +119,13 @@ fn name(&self) -> &'static str {
"epoll"
}
fn metadata<'tcx>(
&self,
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'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,
@@ -37,6 +37,13 @@ fn name(&self) -> &'static str {
"event"
}
fn metadata<'tcx>(
&self,
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'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,
@@ -83,6 +83,16 @@ fn name(&self) -> &'static str {
}
}
fn metadata<'tcx>(
&self,
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'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,
+14 -6
View File
@@ -22,8 +22,10 @@ fn name(&self) -> &'static str {
"directory"
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
interp_ok(self.path.metadata())
fn metadata<'tcx>(
&self,
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'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<Metadata>> {
interp_ok(Ok(self.meta.clone()))
fn metadata<'tcx>(
&self,
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'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();
@@ -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>) -> &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) });
}