Skip to content

Commit 9ae47c9

Browse files
authored
Merge pull request #4812 from enthropy7/master
Support fstat on non-file-backed FDs
2 parents d4ca795 + c91a363 commit 9ae47c9

8 files changed

Lines changed: 217 additions & 32 deletions

File tree

src/tools/miri/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ pub use rustc_const_eval::interpret::*;
105105
// Resolve ambiguity.
106106
#[doc(no_inline)]
107107
pub use rustc_const_eval::interpret::{self, AllocMap, Provenance as _};
108-
use rustc_log::tracing::{self, info, trace};
109-
use rustc_middle::{bug, span_bug};
108+
pub use rustc_data_structures::either::Either;
109+
pub use rustc_log::tracing::{self, info, trace};
110+
pub use rustc_middle::{bug, span_bug};
110111

111112
#[cfg(all(feature = "native-lib", unix))]
112113
pub mod native_lib {

src/tools/miri/src/shims/files.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::Any;
22
use std::collections::BTreeMap;
3-
use std::fs::{File, Metadata};
3+
use std::fs::File;
44
use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
55
use std::marker::CoercePointee;
66
use std::ops::Deref;
@@ -209,7 +209,11 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
209209
throw_unsup_format!("cannot close {}", self.name());
210210
}
211211

212-
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
212+
/// Returns the metadata for this FD, if available.
213+
/// This is either host metadata, or a non-file-backed-FD type.
214+
/// The latter is for new represented as a string storing a `libc` name so we only
215+
/// support that kind of metadata on Unix targets.
216+
fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
213217
throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
214218
}
215219

@@ -432,8 +436,8 @@ impl FileDescription for FileHandle {
432436
}
433437
}
434438

435-
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
436-
interp_ok(self.file.metadata())
439+
fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
440+
interp_ok(Either::Left(self.file.metadata()))
437441
}
438442

439443
fn is_tty(&self, communicate_allowed: bool) -> bool {

src/tools/miri/src/shims/unix/fs.rs

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,22 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
229229
let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
230230
let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
231231
let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
232-
let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
233232

234233
// We do *not* use `deref_pointer_as` here since determining the right pointee type
235234
// is highly non-trivial: it depends on which exact alias of the function was invoked
236235
// (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level
237236
// which can be different between the libc used by std and the libc used by everyone else.
238237
let buf = this.deref_pointer(buf_op)?;
238+
239+
// `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets
240+
// (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target.
241+
let mode_t_size = this.libc_ty_layout("mode_t").size;
242+
let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap();
243+
239244
this.write_int_fields_named(
240245
&[
241246
("st_dev", metadata.dev.into()),
242-
("st_mode", mode.try_into().unwrap()),
247+
("st_mode", mode.into()),
243248
("st_nlink", 0),
244249
("st_ino", 0),
245250
("st_uid", metadata.uid.into()),
@@ -747,13 +752,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
747752
Err(err) => return this.set_last_error_and_return_i32(err),
748753
};
749754

750-
// The `mode` field specifies the type of the file and the permissions over the file for
751-
// the owner, its group and other users. Given that we can only provide the file type
752-
// without using platform specific methods, we only set the bits corresponding to the file
753-
// type. This should be an `__u16` but `libc` provides its values as `u32`.
755+
// `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in
756+
// width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size.
757+
let mode_t_size = this.libc_ty_layout("mode_t").size;
754758
let mode: u16 = metadata
755759
.mode
756-
.to_u32()?
760+
.to_uint(mode_t_size)?
757761
.try_into()
758762
.unwrap_or_else(|_| bug!("libc contains bad value for constant"));
759763

@@ -1630,6 +1634,34 @@ fn extract_sec_and_nsec<'tcx>(
16301634
}
16311635
}
16321636

1637+
fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1638+
#[cfg(unix)]
1639+
use std::os::unix::fs::FileTypeExt;
1640+
1641+
if file_type.is_file() {
1642+
"S_IFREG"
1643+
} else if file_type.is_dir() {
1644+
"S_IFDIR"
1645+
} else if file_type.is_symlink() {
1646+
"S_IFLNK"
1647+
} else {
1648+
// Certain file types are only available when the host is a Unix system.
1649+
#[cfg(unix)]
1650+
{
1651+
if file_type.is_socket() {
1652+
return "S_IFSOCK";
1653+
} else if file_type.is_fifo() {
1654+
return "S_IFIFO";
1655+
} else if file_type.is_char_device() {
1656+
return "S_IFCHR";
1657+
} else if file_type.is_block_device() {
1658+
return "S_IFBLK";
1659+
}
1660+
}
1661+
"S_IFREG"
1662+
}
1663+
}
1664+
16331665
/// Stores a file's metadata in order to avoid code duplication in the different metadata related
16341666
/// shims.
16351667
struct FileMetadata {
@@ -1662,10 +1694,27 @@ impl FileMetadata {
16621694
let Some(fd) = ecx.machine.fds.get(fd_num) else {
16631695
return interp_ok(Err(LibcError("EBADF")));
16641696
};
1697+
match fd.metadata()? {
1698+
Either::Left(host) => Self::from_meta(ecx, host),
1699+
Either::Right(name) => Self::synthetic(ecx, name),
1700+
}
1701+
}
16651702

1666-
let metadata = fd.metadata()?;
1667-
drop(fd);
1668-
FileMetadata::from_meta(ecx, metadata)
1703+
fn synthetic<'tcx>(
1704+
ecx: &mut MiriInterpCx<'tcx>,
1705+
mode_name: &str,
1706+
) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1707+
let mode = ecx.eval_libc(mode_name);
1708+
interp_ok(Ok(FileMetadata {
1709+
mode,
1710+
size: 0,
1711+
created: None,
1712+
accessed: None,
1713+
modified: None,
1714+
dev: 0,
1715+
uid: 0,
1716+
gid: 0,
1717+
}))
16691718
}
16701719

16711720
fn from_meta<'tcx>(
@@ -1680,16 +1729,7 @@ impl FileMetadata {
16801729
};
16811730

16821731
let file_type = metadata.file_type();
1683-
1684-
let mode_name = if file_type.is_file() {
1685-
"S_IFREG"
1686-
} else if file_type.is_dir() {
1687-
"S_IFDIR"
1688-
} else {
1689-
"S_IFLNK"
1690-
};
1691-
1692-
let mode = ecx.eval_libc(mode_name);
1732+
let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
16931733

16941734
let size = metadata.len();
16951735

src/tools/miri/src/shims/unix/linux_like/epoll.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ impl FileDescription for Epoll {
119119
"epoll"
120120
}
121121

122+
fn metadata<'tcx>(
123+
&self,
124+
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
125+
// On Linux, epoll is an "anonymous inode" reported as S_IFREG.
126+
interp_ok(Either::Right("S_IFREG"))
127+
}
128+
122129
fn destroy<'tcx>(
123130
mut self,
124131
self_id: FdId,

src/tools/miri/src/shims/unix/linux_like/eventfd.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ impl FileDescription for EventFd {
3737
"event"
3838
}
3939

40+
fn metadata<'tcx>(
41+
&self,
42+
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
43+
// On Linux, eventfd is an "anonymous inode" reported as S_IFREG.
44+
interp_ok(Either::Right("S_IFREG"))
45+
}
46+
4047
fn destroy<'tcx>(
4148
self,
4249
_self_id: FdId,

src/tools/miri/src/shims/unix/virtual_socket.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ impl FileDescription for VirtualSocket {
8383
}
8484
}
8585

86+
fn metadata<'tcx>(
87+
&self,
88+
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
89+
let mode_name = match self.fd_type {
90+
VirtualSocketType::Socketpair => "S_IFSOCK",
91+
VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO",
92+
};
93+
interp_ok(Either::Right(mode_name))
94+
}
95+
8696
fn destroy<'tcx>(
8797
self,
8898
_self_id: FdId,

src/tools/miri/src/shims/windows/fs.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ impl FileDescription for DirHandle {
2222
"directory"
2323
}
2424

25-
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
26-
interp_ok(self.path.metadata())
25+
fn metadata<'tcx>(
26+
&self,
27+
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
28+
interp_ok(Either::Left(self.path.metadata()))
2729
}
2830

2931
fn destroy<'tcx>(
@@ -49,8 +51,10 @@ impl FileDescription for MetadataHandle {
4951
"metadata-only"
5052
}
5153

52-
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
53-
interp_ok(Ok(self.meta.clone()))
54+
fn metadata<'tcx>(
55+
&self,
56+
) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
57+
interp_ok(Either::Left(Ok(self.meta.clone())))
5458
}
5559

5660
fn destroy<'tcx>(
@@ -329,11 +333,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
329333
};
330334

331335
let metadata = match desc.metadata()? {
332-
Ok(meta) => meta,
333-
Err(e) => {
336+
Either::Left(Ok(meta)) => meta,
337+
Either::Left(Err(e)) => {
334338
this.set_last_error(e)?;
335339
return interp_ok(this.eval_windows("c", "FALSE"));
336340
}
341+
Either::Right(_mode) =>
342+
throw_unsup_format!(
343+
"`GetFileInformationByHandle` is not supported on non-file-backed handles"
344+
),
337345
};
338346

339347
let size = metadata.len();
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//@ignore-target: windows # No libc fstat on non-file FDs on Windows
2+
//@compile-flags: -Zmiri-disable-isolation
3+
4+
use std::mem::MaybeUninit;
5+
6+
#[path = "../../utils/libc.rs"]
7+
mod libc_utils;
8+
use libc_utils::errno_check;
9+
10+
fn main() {
11+
test_fstat_socketpair();
12+
test_fstat_pipe();
13+
#[cfg(target_os = "linux")]
14+
test_fstat_eventfd();
15+
#[cfg(target_os = "linux")]
16+
test_fstat_epoll();
17+
}
18+
19+
/// Calls fstat and returns a reference to the result.
20+
/// We use `assume_init_ref` rather than `assume_init` because not all fields
21+
/// of `libc::stat` may be written by fstat (e.g. `st_lspare` on macOS).
22+
fn do_fstat(fd: i32, buf: &mut MaybeUninit<libc::stat>) -> &libc::stat {
23+
let res = unsafe { libc::fstat(fd, buf.as_mut_ptr()) };
24+
assert_eq!(res, 0, "fstat failed on fd {}", fd);
25+
unsafe { buf.assume_init_ref() }
26+
}
27+
28+
fn assert_stat_fields_are_accessible(stat: &libc::stat) {
29+
let _st_nlink = stat.st_nlink;
30+
let _st_blksize = stat.st_blksize;
31+
let _st_blocks = stat.st_blocks;
32+
let _st_ino = stat.st_ino;
33+
let _st_dev = stat.st_dev;
34+
let _st_uid = stat.st_uid;
35+
let _st_gid = stat.st_gid;
36+
let _st_rdev = stat.st_rdev;
37+
let _st_atime = stat.st_atime;
38+
let _st_mtime = stat.st_mtime;
39+
let _st_ctime = stat.st_ctime;
40+
let _st_atime_nsec = stat.st_atime_nsec;
41+
let _st_mtime_nsec = stat.st_mtime_nsec;
42+
let _st_ctime_nsec = stat.st_ctime_nsec;
43+
}
44+
45+
/// Test fstat on socketpair file descriptors.
46+
fn test_fstat_socketpair() {
47+
let mut fds = [0i32; 2];
48+
errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });
49+
50+
for fd in fds.iter() {
51+
let mut buf = MaybeUninit::uninit();
52+
let stat = do_fstat(*fd, &mut buf);
53+
assert_eq!(
54+
stat.st_mode & libc::S_IFMT,
55+
libc::S_IFSOCK,
56+
"socketpair should have S_IFSOCK mode"
57+
);
58+
assert_eq!(stat.st_size, 0, "socketpair should have size 0");
59+
assert_stat_fields_are_accessible(stat);
60+
}
61+
62+
errno_check(unsafe { libc::close(fds[0]) });
63+
errno_check(unsafe { libc::close(fds[1]) });
64+
}
65+
66+
/// Test fstat on pipe file descriptors.
67+
fn test_fstat_pipe() {
68+
let mut fds = [0i32; 2];
69+
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
70+
71+
for fd in fds.iter() {
72+
let mut buf = MaybeUninit::uninit();
73+
let stat = do_fstat(*fd, &mut buf);
74+
assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO, "pipe should have S_IFIFO mode");
75+
assert_eq!(stat.st_size, 0, "pipe should have size 0");
76+
assert_stat_fields_are_accessible(stat);
77+
}
78+
79+
errno_check(unsafe { libc::close(fds[0]) });
80+
errno_check(unsafe { libc::close(fds[1]) });
81+
}
82+
83+
/// Test fstat on eventfd file descriptors (Linux only).
84+
#[cfg(target_os = "linux")]
85+
fn test_fstat_eventfd() {
86+
let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK;
87+
let fd = libc_utils::errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();
88+
89+
let mut buf = MaybeUninit::uninit();
90+
let stat = do_fstat(fd, &mut buf);
91+
assert_eq!(stat.st_size, 0, "eventfd should have size 0");
92+
assert_stat_fields_are_accessible(stat);
93+
94+
errno_check(unsafe { libc::close(fd) });
95+
}
96+
97+
/// Test fstat on epoll file descriptors (Linux only).
98+
#[cfg(target_os = "linux")]
99+
fn test_fstat_epoll() {
100+
let fd = libc_utils::errno_result(unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) }).unwrap();
101+
102+
let mut buf = MaybeUninit::uninit();
103+
let stat = do_fstat(fd, &mut buf);
104+
assert_eq!(stat.st_size, 0, "epoll should have size 0");
105+
assert_stat_fields_are_accessible(stat);
106+
107+
errno_check(unsafe { libc::close(fd) });
108+
}

0 commit comments

Comments
 (0)