mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Merge pull request #4812 from enthropy7/master
Support fstat on non-file-backed FDs
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
Reference in New Issue
Block a user