From 0cbfdb4c5f3c3aebec5fbc630c129ee872a90371 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 13:58:36 +0200 Subject: [PATCH 1/4] unix DirEntry: make fallback handling more clear --- library/std/src/sys/fs/unix.rs | 129 ++++++++++++++------------------- 1 file changed, 55 insertions(+), 74 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 46d5d33d23ac2..2e297cd10b537 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1016,89 +1016,70 @@ impl DirEntry { self.file_name_os_str().to_os_string() } - #[cfg(all( - any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "fuchsia", - target_os = "hurd", - target_os = "illumos", - target_vendor = "apple", - ), - not(miri) // no dirfd on Miri - ))] pub fn metadata(&self) -> io::Result { - let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; - let name = self.name.as_ptr(); + cfg_select! { + // Use fstatat (or similar) where possible + all( + any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "fuchsia", + target_os = "hurd", + target_os = "illumos", + target_vendor = "apple", + ), + not(miri) // no dirfd on Miri + ) => { + let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; + let name = self.name.as_ptr(); + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + fd, + name, + libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + ) } { + return ret; + } + } - cfg_has_statx! { - if let Some(ret) = unsafe { try_statx( - fd, - name, - libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - ) } { - return ret; + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; + Ok(FileAttr::from_stat64(stat)) } - } - - let mut stat: stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; - Ok(FileAttr::from_stat64(stat)) - } - #[cfg(any( - not(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "fuchsia", - target_os = "hurd", - target_os = "illumos", - target_vendor = "apple", - )), - miri // no dirfd on Miri - ))] - pub fn metadata(&self) -> io::Result { - run_path_with_cstr(&self.path(), &lstat) - } - - #[cfg(any( - target_os = "solaris", - target_os = "illumos", - target_os = "haiku", - target_os = "vxworks", - target_os = "aix", - target_os = "nto", - target_os = "qnx", - target_os = "vita", - target_os = "l4re", - ))] - pub fn file_type(&self) -> io::Result { - self.metadata().map(|m| m.file_type()) + // Fallback based on path + _ => run_path_with_cstr(&self.path(), &lstat), + } } - #[cfg(not(any( - target_os = "solaris", - target_os = "illumos", - target_os = "haiku", - target_os = "vxworks", - target_os = "aix", - target_os = "nto", - target_os = "qnx", - target_os = "vita", - target_os = "l4re", - )))] pub fn file_type(&self) -> io::Result { + // Use `entry.d_type` if available. + #[cfg(not(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks", + target_os = "aix", + target_os = "nto", + target_os = "qnx", + target_os = "vita", + target_os = "l4re", + )))] match self.entry.d_type { - libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), - libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), - libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), - libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), - libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), - libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), - libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), - _ => self.metadata().map(|m| m.file_type()), + libc::DT_CHR => return Ok(FileType { mode: libc::S_IFCHR }), + libc::DT_FIFO => return Ok(FileType { mode: libc::S_IFIFO }), + libc::DT_LNK => return Ok(FileType { mode: libc::S_IFLNK }), + libc::DT_REG => return Ok(FileType { mode: libc::S_IFREG }), + libc::DT_SOCK => return Ok(FileType { mode: libc::S_IFSOCK }), + libc::DT_DIR => return Ok(FileType { mode: libc::S_IFDIR }), + libc::DT_BLK => return Ok(FileType { mode: libc::S_IFBLK }), + _ => {} } + + // Fall back to loading the metadata. + self.metadata().map(|m| m.file_type()) } pub fn ino(&self) -> u64 { From 49c0a85c79241bfbff71ae689492b3802c1a2ac0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 16:14:44 +0200 Subject: [PATCH 2/4] add Dir::(symlink_)metadata_at --- library/std/src/fs.rs | 48 +++++++++++++++++++++++++++ library/std/src/fs/tests.rs | 31 +++++++++++++---- library/std/src/sys/fs/common.rs | 8 +++++ library/std/src/sys/fs/unix/dir.rs | 36 ++++++++++++++++++-- library/std/src/sys/fs/windows.rs | 4 ++- library/std/src/sys/fs/windows/dir.rs | 42 ++++++++++++++++++++++- 6 files changed, 158 insertions(+), 11 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 65b8ed634bc05..e7efa32af6964 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1891,6 +1891,54 @@ impl Dir { pub fn try_clone(&self) -> io::Result { Ok(Dir { inner: self.inner.duplicate()? }) } + + /// Queries the file system to get information about a file, directory, etc. relative to this + /// directory. + /// + /// This function will traverse symbolic links to query information about the destination file. + /// To query metadata about the path itself without following symbolic links, use + /// [`symlink_metadata_at`][Self::symlink_metadata_at]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let metadata = dir.metadata_at("subdir/file.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn metadata_at>(&self, path: P) -> io::Result { + self.inner.metadata_at(path.as_ref()).map(Metadata) + } + + /// Queries the file system to get information about a file, directory, etc. relative to this + /// directory. + /// + /// This function will return the [`Metadata`] of the exact path without traversing symbolic + /// links to a resolved destination file. Using this function on a path that is a file or + /// directory (not a symbolic link) will behave the same as [`metadata_at`][Self::metadata_at]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let metadata = dir.symlink_metadata_at("subdir/file.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn symlink_metadata_at>(&self, path: P) -> io::Result { + self.inner.symlink_metadata_at(path.as_ref()).map(Metadata) + } } impl AsInner for Dir { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..15711652cb05f 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -13,7 +13,7 @@ use crate::os::unix::fs::symlink as symlink_file; use crate::os::unix::fs::symlink as junction_point; #[cfg(windows)] use crate::os::windows::fs::{OpenOptionsExt, junction_point, symlink_dir, symlink_file}; -use crate::path::Path; +use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::test_helpers::{TempDir, tmpdir}; use crate::time::{Duration, Instant, SystemTime}; @@ -655,11 +655,6 @@ fn set_get_permissions_nofollows() { not(any(target_os = "espidf", target_os = "horizon", target_os = "wasi")) ))] fn set_get_permissions_nofollows_symlink() { - #[cfg(not(windows))] - use crate::os::unix::fs::symlink as symlink_file; - #[cfg(windows)] - use crate::os::windows::fs::symlink_file; - let tmpdir = tmpdir(); let filename = tmpdir.join("set_get_unix_permissions_file"); let symlink_name = tmpdir.join("set_get_unix_permissions"); @@ -3106,3 +3101,27 @@ fn test_dir_open_dir() { check!(f.read_exact(&mut buf)); assert_eq!(b"baz", &buf); } + +#[test] +fn test_dir_metadata_at() { + let tmpdir = tmpdir(); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.create_dir("subdir")); + // FIXME: `/` does not work as path separator on Windows. + let barpath = PathBuf::from("subdir").join("bar.txt"); + drop(check!(dir.open_file_with(&barpath, &OpenOptions::new().create(true).write(true)))); + check!(symlink_file(&tmpdir.join("subdir/bar.txt"), &tmpdir.join("link"))); + + let metadata = check!(dir.metadata_at(&barpath)); + assert!(metadata.is_file()); + let metadata = check!(dir.metadata_at("subdir")); + assert!(metadata.is_dir()); + dir.metadata_at("does-not-exist").unwrap_err(); + + let metadata = check!(dir.metadata_at("link")); + assert!(metadata.is_file()); + assert!(!metadata.is_symlink()); + let metadata = check!(dir.symlink_metadata_at("link")); + assert!(!metadata.is_file()); + assert!(metadata.is_symlink()); +} diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index b2472665fe465..70cb57ac2915d 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -108,6 +108,14 @@ impl Dir { pub fn remove_dir(&self, path: &Path) -> io::Result<()> { remove_dir(self.path.join(path)) } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + self.path.join(path).metadata().map(|m| m.into_inner()) + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + self.path.join(path).symlink_metadata().map(|m| m.into_inner()) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index cf0dece265054..a1e336b7fc237 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -26,7 +26,7 @@ use crate::sys::fs::OpenOptions; use crate::sys::fs::unix::{File, FileAttr, debug_path_fd}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r}; -use crate::{fmt, fs, io}; +use crate::{fmt, fs, io, mem}; const TRAVERSE_DIRECTORY: i32 = cfg_select! { @@ -68,7 +68,7 @@ impl Dir { } pub fn remove_file(&self, path: &Path) -> io::Result<()> { - run_path_with_cstr(path, &|path| self.remove_c(path, false)) + run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ false)) } pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> { @@ -86,7 +86,17 @@ impl Dir { } pub fn remove_dir(&self, path: &Path) -> io::Result<()> { - run_path_with_cstr(path, &|path| self.remove_c(path, true)) + run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ true)) + } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| { + self.metadata_at_c(path, /* symlink_nofollow */ false) + }) + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| self.metadata_at_c(path, /* symlink_nofollow */ true)) } fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { @@ -143,6 +153,26 @@ impl Dir { fn create_dir_c(&self, path: &CStr) -> io::Result<()> { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } + + fn metadata_at_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { + let fd = self.0.as_raw_fd(); + let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; + + cfg_has_statx! { + if let Some(ret) = unsafe { super::try_statx( + fd, + path.as_ptr(), + flag | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + ) } { + return ret; + } + } + + let mut stat: super::stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { super::fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; + Ok(FileAttr::from_stat64(stat)) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 399c0749bc21e..473d92aface26 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1454,7 +1454,9 @@ pub fn link(_original: &WCStr, _link: &WCStr) -> io::Result<()> { pub fn stat(path: &WCStr) -> io::Result { match metadata(path, ReparsePoint::Follow) { Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { - if let Ok(attrs) = lstat(path) { + // Fallback to opening reparse points when following fails. Needed for UNIX domain + // sockets. See . + if let Ok(attrs) = metadata(path, ReparsePoint::Open) { if !attrs.file_type().is_symlink() { return Ok(attrs); } diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 6f6d59af31628..7b8cd60fd8d06 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -7,7 +7,7 @@ use crate::os::windows::io::{ }; use crate::path::Path; use crate::sys::api::{UnicodeStrRef, WinError}; -use crate::sys::fs::windows::debug_path_handle; +use crate::sys::fs::windows::{ReparsePoint, debug_path_handle}; use crate::sys::fs::{File, FileAttr, OpenOptions}; use crate::sys::handle::Handle; use crate::sys::path::{WCStr, with_native_path}; @@ -165,6 +165,7 @@ impl Dir { fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16]) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); + // FIXME: custom_flags is ignored by `open_file_native`! opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); let handle = self.open_file_native(from, &opts, /* create_opt */ 0)?; // Calculate the layout of the `FILE_RENAME_INFORMATION` we pass to `NtSetInformationFile` @@ -231,6 +232,45 @@ impl Dir { }); f.file_attr() } + + pub fn metadata_at(&self, path: &Path) -> io::Result { + let path = to_u16s_without_nul(path)?; + // Same as the `stat` logic used for `fs::metadata` + match self.metadata_at_native(&path, ReparsePoint::Follow) { + Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { + // Fallback to opening reparse points when following fails. Needed for UNIX domain + // sockets. See . + if let Ok(attrs) = self.metadata_at_native(&path, ReparsePoint::Open) { + if !attrs.file_type().is_symlink() { + return Ok(attrs); + } + } + Err(err) + } + result => result, + } + } + + pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + let path = to_u16s_without_nul(path)?; + self.metadata_at_native(&path, ReparsePoint::Open) + } + + fn metadata_at_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { + let mut opts = OpenOptions::new(); + // the NT functions need at least c::FILE_READ_ATTRIBUTES + opts.access_mode(c::FILE_READ_ATTRIBUTES); + let create_opt = if reparse == ReparsePoint::Open { c::FILE_OPEN_REPARSE_POINT } else { 0 }; + + let name = UnicodeStrRef::from_slice(path); + let object_attributes = c::OBJECT_ATTRIBUTES { + RootDirectory: self.handle.as_raw_handle(), + ObjectName: name.as_ptr().cast_mut(), + ..c::OBJECT_ATTRIBUTES::with_length() + }; + let handle = unsafe { nt_create_file(&opts, &object_attributes, create_opt)? }; + File { handle }.file_attr() + } } impl fmt::Debug for Dir { From cadaa2e7bf5a4c7ed2f9c4fd1de42d6527f6c8b8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 16:22:23 +0200 Subject: [PATCH 3/4] reuse Dir::metadata_at code for DirEntry::metadata --- library/std/src/sys/fs/unix.rs | 27 +++++++-------------------- library/std/src/sys/fs/unix/dir.rs | 14 +++++++++----- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 2e297cd10b537..988bacd57d1b2 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -17,10 +17,6 @@ use libc::c_char; target_vendor = "apple", ))] use libc::dirfd; -#[cfg(any(target_os = "fuchsia", target_os = "illumos", target_vendor = "apple"))] -use libc::fstatat as fstatat64; -#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] -use libc::fstatat64; use libc::{c_int, mode_t}; #[cfg(target_os = "android")] use libc::{ @@ -1018,7 +1014,7 @@ impl DirEntry { pub fn metadata(&self) -> io::Result { cfg_select! { - // Use fstatat (or similar) where possible + // Use directory handle where possible all( any( all(target_os = "linux", not(target_env = "musl")), @@ -1031,22 +1027,13 @@ impl DirEntry { not(miri) // no dirfd on Miri ) => { let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; - let name = self.name.as_ptr(); - - cfg_has_statx! { - if let Some(ret) = unsafe { try_statx( - fd, - name, - libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - ) } { - return ret; - } - } - let mut stat: stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; - Ok(FileAttr::from_stat64(stat)) + // 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) })); + + dir_handle.metadata_at_c(&self.name, /* symlink_nofollow */ true) } // Fallback based on path diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index a1e336b7fc237..087916fd9ff63 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -7,10 +7,10 @@ cfg_select! { target_os = "android", target_os = "hurd", )) => { - use libc::{open as open64, openat as openat64}; + use libc::{fstatat as fstatat64, open as open64, openat as openat64}; } _ => { - use libc::{open64, openat64}; + use libc::{fstatat64, open64, openat64}; } } @@ -36,7 +36,7 @@ const TRAVERSE_DIRECTORY: i32 = _ => libc::O_RDONLY, }; -pub struct Dir(OwnedFd); +pub struct Dir(pub(super) OwnedFd); impl Dir { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { @@ -154,7 +154,11 @@ impl Dir { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } - fn metadata_at_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { + pub(super) fn metadata_at_c( + &self, + path: &CStr, + symlink_nofollow: bool, + ) -> io::Result { let fd = self.0.as_raw_fd(); let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; @@ -170,7 +174,7 @@ impl Dir { } let mut stat: super::stat64 = unsafe { mem::zeroed() }; - cvt(unsafe { super::fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; + cvt(unsafe { fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; Ok(FileAttr::from_stat64(stat)) } } From 5b172d928a56980fc534b5ff8f363354df804fc2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 23 Sep 2026 08:36:40 +0200 Subject: [PATCH 4/4] =?UTF-8?q?rename=20metadata=5Fat=20=E2=86=92=20metada?= =?UTF-8?q?ta=20(and=20metadata=20=E2=86=92=20self=5Fmetadata)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/std/src/fs.rs | 24 ++++++----- library/std/src/fs/tests.rs | 16 ++++---- library/std/src/sys/fs/common.rs | 6 +-- library/std/src/sys/fs/unix.rs | 57 +++++++++++++++----------- library/std/src/sys/fs/unix/dir.rs | 24 +++++------ library/std/src/sys/fs/windows/dir.rs | 14 +++---- src/tools/miri/src/shims/files.rs | 5 ++- src/tools/miri/src/shims/windows/fs.rs | 6 ++- 8 files changed, 84 insertions(+), 68 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index e7efa32af6964..3f0f276551b45 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1637,6 +1637,8 @@ impl Dir { /// Queries metadata about the underlying directory. /// + /// This is equivalent to `dir.metadata(".")` but can be more efficient. + /// /// # Examples /// /// ```no_run @@ -1645,13 +1647,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.metadata()?; + /// let metadata = dir.self_metadata()?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn metadata(&self) -> io::Result { - self.inner.metadata().map(Metadata) + pub fn self_metadata(&self) -> io::Result { + self.inner.self_metadata().map(Metadata) } /// Attempts to open a file in read-only mode relative to this directory. @@ -1897,7 +1899,7 @@ impl Dir { /// /// This function will traverse symbolic links to query information about the destination file. /// To query metadata about the path itself without following symbolic links, use - /// [`symlink_metadata_at`][Self::symlink_metadata_at]. + /// [`symlink_metadata`][Self::symlink_metadata]. /// /// # Examples /// @@ -1907,13 +1909,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.metadata_at("subdir/file.txt")?; + /// let metadata = dir.metadata("subdir/file.txt")?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn metadata_at>(&self, path: P) -> io::Result { - self.inner.metadata_at(path.as_ref()).map(Metadata) + pub fn metadata>(&self, path: P) -> io::Result { + self.inner.metadata(path.as_ref()).map(Metadata) } /// Queries the file system to get information about a file, directory, etc. relative to this @@ -1921,7 +1923,7 @@ impl Dir { /// /// This function will return the [`Metadata`] of the exact path without traversing symbolic /// links to a resolved destination file. Using this function on a path that is a file or - /// directory (not a symbolic link) will behave the same as [`metadata_at`][Self::metadata_at]. + /// directory (not a symbolic link) will behave the same as [`metadata`][Self::metadata]. /// /// # Examples /// @@ -1931,13 +1933,13 @@ impl Dir { /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::open("foo")?; - /// let metadata = dir.symlink_metadata_at("subdir/file.txt")?; + /// let metadata = dir.symlink_metadata("subdir/file.txt")?; /// Ok(()) /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] - pub fn symlink_metadata_at>(&self, path: P) -> io::Result { - self.inner.symlink_metadata_at(path.as_ref()).map(Metadata) + pub fn symlink_metadata>(&self, path: P) -> io::Result { + self.inner.symlink_metadata(path.as_ref()).map(Metadata) } } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 15711652cb05f..d1d39d144e4b1 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3010,10 +3010,10 @@ fn test_dir_clone() { } #[test] -fn test_dir_metadata() { +fn test_dir_self_metadata() { let tmpdir = tmpdir(); let dir = check!(Dir::open(tmpdir.path())); - let metadata = check!(dir.metadata()); + let metadata = check!(dir.self_metadata()); assert!(metadata.is_dir()); } @@ -3103,7 +3103,7 @@ fn test_dir_open_dir() { } #[test] -fn test_dir_metadata_at() { +fn test_dir_metadata() { let tmpdir = tmpdir(); let dir = check!(Dir::open(tmpdir.path())); check!(dir.create_dir("subdir")); @@ -3112,16 +3112,16 @@ fn test_dir_metadata_at() { drop(check!(dir.open_file_with(&barpath, &OpenOptions::new().create(true).write(true)))); check!(symlink_file(&tmpdir.join("subdir/bar.txt"), &tmpdir.join("link"))); - let metadata = check!(dir.metadata_at(&barpath)); + let metadata = check!(dir.metadata(&barpath)); assert!(metadata.is_file()); - let metadata = check!(dir.metadata_at("subdir")); + let metadata = check!(dir.metadata("subdir")); assert!(metadata.is_dir()); - dir.metadata_at("does-not-exist").unwrap_err(); + dir.metadata("does-not-exist").unwrap_err(); - let metadata = check!(dir.metadata_at("link")); + let metadata = check!(dir.metadata("link")); assert!(metadata.is_file()); assert!(!metadata.is_symlink()); - let metadata = check!(dir.symlink_metadata_at("link")); + let metadata = check!(dir.symlink_metadata("link")); assert!(!metadata.is_file()); assert!(metadata.is_symlink()); } diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index 70cb57ac2915d..1724e6115462e 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -85,7 +85,7 @@ impl Dir { File::open(&self.path.join(path), opts) } - pub fn metadata(&self) -> io::Result { + pub fn self_metadata(&self) -> io::Result { self.path.metadata().map(|m| m.into_inner()) } @@ -109,11 +109,11 @@ impl Dir { remove_dir(self.path.join(path)) } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { self.path.join(path).metadata().map(|m| m.into_inner()) } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + pub fn symlink_metadata(&self, path: &Path) -> io::Result { self.path.join(path).symlink_metadata().map(|m| m.into_inner()) } } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 988bacd57d1b2..6d1c01be6078f 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -6,6 +6,39 @@ #[cfg(test)] mod tests; +// Import the file operations under consistent names. +cfg_select! { + not(any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "hurd", + target_os = "l4re", + )) => { + use libc::{ + dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, + lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, + }; + } + target_os = "android" => { + // Android's `stat`, `dirent`, and related functions are always 64-bit LFS compatible, and + // `open` already implies `O_LARGEFILE`, so all those don't need to follow Linux. + // However, we still need off64_t, ftruncate64, and lseek64. + use libc::{ + dirent as dirent64, fstat as fstat64, ftruncate64, lseek64, lstat as lstat64, off64_t, + open as open64, stat as stat64, + }; + } + target_os = "l4re" => { + use libc::{ + dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, + lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, + }; + } + _ => { + use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; + } +} + #[cfg(all(target_os = "linux", target_env = "gnu"))] use libc::c_char; #[cfg(any( @@ -18,28 +51,6 @@ use libc::c_char; ))] use libc::dirfd; use libc::{c_int, mode_t}; -#[cfg(target_os = "android")] -use libc::{ - dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64, - lstat as lstat64, off64_t, open as open64, stat as stat64, -}; -#[cfg(not(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "hurd", - target_os = "l4re", -)))] -use libc::{ - dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, - lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, -}; -#[cfg(target_os = "l4re")] -use libc::{ - dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, - off_t as off64_t, open as open64, stat as stat64, -}; -#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] -use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; @@ -1033,7 +1044,7 @@ impl DirEntry { let dir_handle = mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) })); - dir_handle.metadata_at_c(&self.name, /* symlink_nofollow */ true) + dir_handle.metadata_c(&self.name, /* symlink_nofollow */ true) } // Fallback based on path diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index 087916fd9ff63..dd01db58c600d 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -4,16 +4,16 @@ cfg_select! { not(any( all(target_os = "linux", not(target_env = "musl")), target_os = "l4re", - target_os = "android", target_os = "hurd", )) => { - use libc::{fstatat as fstatat64, open as open64, openat as openat64}; + use libc::{fstatat as fstatat64, openat as openat64}; } _ => { - use libc::{fstatat64, open64, openat64}; + use libc::{fstatat64, openat64}; } } +use super::{open64, stat64}; use crate::ffi::CStr; use crate::os::fd::{AsFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}; #[cfg(target_family = "unix")] @@ -57,7 +57,7 @@ impl Dir { .map(File) } - pub fn metadata(&self) -> io::Result { + 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( @@ -89,14 +89,14 @@ impl Dir { run_path_with_cstr(path, &|path| self.remove_c(path, /* remove_dir */ true)) } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { run_path_with_cstr(path, &|path| { - self.metadata_at_c(path, /* symlink_nofollow */ false) + self.metadata_c(path, /* symlink_nofollow */ false) }) } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { - run_path_with_cstr(path, &|path| self.metadata_at_c(path, /* symlink_nofollow */ true)) + pub fn symlink_metadata(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| self.metadata_c(path, /* symlink_nofollow */ true)) } fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { @@ -154,11 +154,7 @@ impl Dir { cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) } - pub(super) fn metadata_at_c( - &self, - path: &CStr, - symlink_nofollow: bool, - ) -> io::Result { + pub(super) fn metadata_c(&self, path: &CStr, symlink_nofollow: bool) -> io::Result { let fd = self.0.as_raw_fd(); let flag = if symlink_nofollow { libc::AT_SYMLINK_NOFOLLOW } else { 0 }; @@ -173,7 +169,7 @@ impl Dir { } } - let mut stat: super::stat64 = unsafe { mem::zeroed() }; + let mut stat: stat64 = unsafe { mem::zeroed() }; cvt(unsafe { fstatat64(fd, path.as_ptr(), &mut stat, flag) })?; Ok(FileAttr::from_stat64(stat)) } diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 7b8cd60fd8d06..60f8c2522de26 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -223,7 +223,7 @@ impl Dir { .io_result() } - pub fn metadata(&self) -> io::Result { + pub fn self_metadata(&self) -> io::Result { // Reuse the implementation for files, which should work for all handles. let handle = self.handle.as_raw_handle(); let f = core::mem::ManuallyDrop::new(File { @@ -233,14 +233,14 @@ impl Dir { f.file_attr() } - pub fn metadata_at(&self, path: &Path) -> io::Result { + pub fn metadata(&self, path: &Path) -> io::Result { let path = to_u16s_without_nul(path)?; // Same as the `stat` logic used for `fs::metadata` - match self.metadata_at_native(&path, ReparsePoint::Follow) { + match self.metadata_native(&path, ReparsePoint::Follow) { Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { // Fallback to opening reparse points when following fails. Needed for UNIX domain // sockets. See . - if let Ok(attrs) = self.metadata_at_native(&path, ReparsePoint::Open) { + if let Ok(attrs) = self.metadata_native(&path, ReparsePoint::Open) { if !attrs.file_type().is_symlink() { return Ok(attrs); } @@ -251,12 +251,12 @@ impl Dir { } } - pub fn symlink_metadata_at(&self, path: &Path) -> io::Result { + pub fn symlink_metadata(&self, path: &Path) -> io::Result { let path = to_u16s_without_nul(path)?; - self.metadata_at_native(&path, ReparsePoint::Open) + self.metadata_native(&path, ReparsePoint::Open) } - fn metadata_at_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { + fn metadata_native(&self, path: &[u16], reparse: ReparsePoint) -> io::Result { let mut opts = OpenOptions::new(); // the NT functions need at least c::FILE_READ_ATTRIBUTES opts.access_mode(c::FILE_READ_ATTRIBUTES); diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d73ce57f64f19..cd7a7f4f0250b 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -524,7 +524,10 @@ impl FileDescription for DirHandle { fn metadata<'tcx>( &self, ) -> InterpResult<'tcx, Either, &'static str>> { - interp_ok(Either::Left(self.dir.metadata())) + #[cfg(bootstrap)] + return interp_ok(Either::Left(self.dir.metadata())); + #[cfg(not(bootstrap))] + return interp_ok(Either::Left(self.dir.self_metadata())); } } diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 9aae6677faeb2..bab26d80dee70 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -225,7 +225,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Handle::Invalid); } }; - if !dir.metadata().unwrap().is_dir() { + #[cfg(bootstrap)] + let metadata = dir.metadata(); + #[cfg(not(bootstrap))] + let metadata = dir.self_metadata(); + if !metadata.unwrap().is_dir() { // This changed from a directory to a file. Retry. continue; }