From 3bf347488e68f29e4b190aeeaa85f59e063a2578 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 15:14:08 +0200 Subject: [PATCH 1/2] add ReadDir::dir to get the directory handle of the underlying directory --- library/std/src/fs.rs | 13 +++++++++++ library/std/src/fs/tests.rs | 13 +++++++++++ library/std/src/sys/fs/hermit.rs | 8 +++++++ library/std/src/sys/fs/motor.rs | 8 +++++++ library/std/src/sys/fs/solid.rs | 8 +++++++ library/std/src/sys/fs/uefi.rs | 8 +++++++ library/std/src/sys/fs/unix.rs | 33 +++++++++++++++++++++++++++ library/std/src/sys/fs/unix/dir.rs | 2 +- library/std/src/sys/fs/unsupported.rs | 6 +++++ library/std/src/sys/fs/vexos.rs | 6 +++++ library/std/src/sys/fs/windows.rs | 9 ++++++++ 11 files changed, 113 insertions(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 139275ed2e1c2..9467b2a786e2d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2797,6 +2797,19 @@ impl AsInner for Permissions { } } +impl ReadDir { + /// Returns a handle for the directory this `ReadDir` is reading. + /// + /// Whenever possible, this will directly reuse the underlying handle. However, this may + /// fall back to opening a new handle based on the path, which is subject to race conditions + /// if the file system has changed since the `ReadDir` was created. + // FIXME: before stabilization, ensure that we have a race-free implementation on Windows! + #[unstable(feature = "dirfd", issue = "120426")] + pub fn dir(&self) -> io::Result { + self.0.dir().map(|d| Dir { inner: d }) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Iterator for ReadDir { type Item = io::Result; diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 14547227744e4..9cc09774eddc6 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3162,6 +3162,19 @@ fn test_dir_metadata() { assert!(metadata.is_symlink()); } +#[test] +fn test_read_dir_dir() { + let tmpdir = tmpdir(); + check!(fs::write(tmpdir.path().join("file.txt"), b"hello")); + + let read_dir = check!(fs::read_dir(tmpdir.path())); + let dir = check!(read_dir.dir()); + let mut f = check!(dir.open_file("file.txt")); + let mut data = vec![]; + check!(f.read_to_end(&mut data)); + assert_eq!(data, b"hello"); +} + fn root_test_dir(what: &str) -> PathBuf { crate::env::current_dir().unwrap().ancestors().last().unwrap().join(what) } diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 89f6989b9adbe..b47920f70cab9 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -221,6 +221,14 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.inner.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 91cd90a6b0cb3..c1b8bb6892ab6 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -406,6 +406,14 @@ pub fn readdir(path: &Path) -> io::Result { }) } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(Path::new(&self.path), &options) + } +} + impl Iterator for ReadDir { type Item = io::Result; diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index dfdcacdcdf277..783a301fe16c2 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -159,6 +159,14 @@ pub fn readdir(p: &Path) -> io::Result { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.inner.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 5d0c219586e0e..25050454d992a 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -147,6 +147,14 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.0.path(), &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("ReadDir"); diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 0adc0b9136f63..e97e0877ed703 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -796,6 +796,39 @@ impl fmt::Debug for FilePermissions { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + + cfg_select! { + // Some targets don't have `dirfd`. Open `Dir` based on path. + any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "qnx", + target_os = "vxworks", + target_os = "l4re", + ) => Dir::open(&self.inner.root, &options), + // Use `dirfd` where possible. + _ => { + let fd = unsafe { libc::dirfd(self.inner.dirp.0) }; + // Make this FD into a directory handle. We don't actually drop it, + // so having an `OwnedFd` is fine. + let dir_handle = + mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) })); + // We don't want to expose `fd` or even the underlying file description + // as the directory stream has state attached to it. So we open a completely + // new file description based on this one. + dir_handle.open_dir(Path::new("."), &options) + } + } + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index dd01db58c600d..2ac5695e3d72a 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -60,7 +60,7 @@ impl Dir { pub fn self_metadata(&self) -> io::Result { // Reuse the implementation for files, which should work for all FDs. let fd = self.0.as_raw_fd(); - let f = core::mem::ManuallyDrop::new(File( + let f = mem::ManuallyDrop::new(File( // SAFETY: we borrowed `self` so the FD will not be closed while this function runs. unsafe { FileDesc::from_raw_fd(fd) }, )); diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 26677c7c7410a..747fc6d470d41 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -138,6 +138,12 @@ impl fmt::Debug for FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + self.0 + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 5043a4daa83b4..3af834964b02b 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -127,6 +127,12 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + self.0 + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index c4e0bb6ecf51f..c74a76337487f 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -113,6 +113,15 @@ impl fmt::Debug for c::FILETIME { #[derive(Debug)] pub struct DirBuilder; +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + // FIXME: use race-free handle-based approach instead. + Dir::open(&self.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. From 3d7b11f383746c7db09e84a7b16dec5f4f7b9d8b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 5 Oct 2026 15:48:36 +0200 Subject: [PATCH 2/2] miri: use new ReadDir::dir support --- src/tools/miri/src/shims/unix/fs.rs | 15 +++++++-------- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index f74631544658a..0a800c50a36fd 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1205,14 +1205,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match result { Ok(read_dir) => { - // Also open the same directory as a directory handle, so we have - // an underlying FD. - // FIXME(https://github.com/rust-lang/miri/issues/5326): This is racy! We can't even - // verify whether there was a race. We just trust that the directory did not change - // in between above and here. One day, the standard library will support converting - // between `Dir` and `ReadDir` (one of the two directions would suffice for our - // needs), then we'll use that. - let Ok(dir) = fs::Dir::open(&name) else { + // Create an underlying FD as well, in case someone calls `dirfd` later. + // (We could do this lazily but that does not seem worth it.) + #[cfg(not(bootstrap))] + let dir = read_dir.dir(); + #[cfg(bootstrap)] + let dir = fs::Dir::open(&name); + let Ok(dir) = dir else { throw_unsup_format!( "cannot `opendir` this directory: failed to create directory handle" ); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 39a7203c52318..d2401f22792f8 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -1373,6 +1373,8 @@ fn test_dirfd() { assert!(!dir.is_null()); let dirfd = unsafe { libc::dirfd(dir) }; + let dirfd2 = unsafe { libc::dirfd(dir) }; + assert_eq!(dirfd, dirfd2); // make sure we always return the same one let mut stat = MaybeUninit::::uninit(); errno_check(unsafe { libc::fstat(dirfd, stat.as_mut_ptr()) });