diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f922696298fff..eadf23ec96e89 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2795,6 +2795,20 @@ impl AsInner for Permissions { } } +impl ReadDir { + /// Returns a handle for the directory this `ReadDir` is reading. + /// + /// Whenever possible, the new handle will be created in a race-free manner from the underlying + /// handle used by `ReadDir`. 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 605ad25270a63..758a10b6644ae 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3341,6 +3341,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 650f7e884c41d..32ed0bd76214f 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 06d7356b2a519..269ece5666494 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -410,6 +410,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 849a1657b761d..c43a4794e620e 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 ef902931533d3..945eaeaff1d7d 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 d0cbabbdd4955..28ef1f7877970 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 1421dad97272f..78b4a1a698a33 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -157,6 +157,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 6529159eb49fc..5dbcdfef7c80d 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 548b2d4b6f00f..c7dbc815b3434 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. diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index bee60af61f0fa..4622114dcacd8 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1367,14 +1367,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/fs.rs b/src/tools/miri/tests/pass-dep/libc/fs.rs index 90053d36fd1b1..a5a60de75f6b0 100644 --- a/src/tools/miri/tests/pass-dep/libc/fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/fs.rs @@ -1659,6 +1659,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()) }); diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 683b0f5605b0e..cd95ed21e5dc6 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -501,22 +501,24 @@ fn test_directory() { let dir_1 = dir_path.join("test_dir_1"); create_dir(&dir_1).unwrap(); // Test that read_dir metadata calls succeed + let mut dir_iter = read_dir(&dir_path).unwrap(); assert_eq!( BTreeMap::from([ (OsString::from("test_file_1"), true), (OsString::from("test_file_2"), true), (OsString::from("test_dir_1"), false) ]), - read_dir(&dir_path) - .unwrap() + (&mut dir_iter) .map(|e| { let e = e.unwrap(); (e.file_name(), e.metadata().unwrap().is_file()) }) .collect::>() ); + // We can access the directory handle from this iterator. + let dir_handle = dir_iter.dir().unwrap(); + drop(dir_handle.open_file("test_file_1").unwrap()); // Deleting the directory should fail, since it is not empty. - // Solaris/Illumos `rmdir` call set errno to EEXIST if directory contains // other entries than `.` and `..`. // https://docs.oracle.com/cd/E86824_01/html/E54765/rmdir-2.html