From bcd769db783188e2ef2366100cfad2c4459443db Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Tue, 8 Apr 2025 01:38:40 -0400 Subject: [PATCH 01/14] dirfd: initial quick and dirty implementation for unix --- library/std/src/fs.rs | 26 ++++ library/std/src/sys/fs/mod.rs | 2 + library/std/src/sys/fs/unix.rs | 230 ++++++++++++++++++++++----------- 3 files changed, 180 insertions(+), 78 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 6cbf8301e01b9..1e1cdc1305427 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -132,6 +132,13 @@ pub enum TryLockError { WouldBlock, } +#[unstable(feature = "dirfd", issue = "120426")] +#[cfg(target_family = "unix")] +/// An object providing access to a directory on the filesystem. +pub struct Dir { + inner: fs_imp::Dir, +} + /// Metadata information about a file. /// /// This structure is returned from the [`metadata`] or @@ -1453,6 +1460,25 @@ impl Seek for Arc { } } +#[unstable(feature = "dirfd", issue = "120426")] +impl Dir { + /// Opens a file relative to this directory. + pub fn open>(&self, path: P) -> io::Result { + self.inner.open(path).map(|f| File { inner: f }) + } + /// Opens a file relative to this directory with the specified options. + pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + self.inner.open_with(path, &opts.0).map(|f| File { inner: f }) + } +} + +#[unstable(feature = "dirfd", issue = "120426")] +impl fmt::Debug for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + impl OpenOptions { /// Creates a blank new set of options ready for configuration. /// diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index d55e28074fe8c..458c097339d90 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -46,6 +46,8 @@ pub fn with_native_path(path: &Path, f: &dyn Fn(&Path) -> io::Result) -> i f(path) } +#[cfg(target_family = "unix")] +pub use imp::Dir; pub use imp::{ DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, ReadDir, diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index dc278274f00f4..a53167aa9fffe 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -54,7 +54,7 @@ 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, + lstat as lstat64, off64_t, open as open64, openat as openat64, stat as stat64, }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), @@ -64,14 +64,14 @@ use libc::{ )))] 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, + lstat as lstat64, off_t as off64_t, open as open64, openat as openat64, stat as stat64, }; #[cfg(any( all(target_os = "linux", not(target_env = "musl")), target_os = "l4re", target_os = "hurd" ))] -use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; +use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, stat64}; use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; @@ -264,7 +264,154 @@ impl ReadDir { } } -struct Dir(*mut libc::DIR); +pub struct Dir(*mut libc::DIR); + +// dirfd isn't supported everywhere +#[cfg(not(any( + miri, + target_os = "redox", + target_os = "nto", + target_os = "vita", + target_os = "hurd", + target_os = "espidf", + target_os = "horizon", + target_os = "vxworks", + target_os = "rtems", + target_os = "nuttx", +)))] +impl Dir { + pub fn open>(&self, path: P) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, &opts)) + } + + pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, opts)) + } + + pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { + let flags = libc::O_CLOEXEC + | opts.get_access_mode()? + | opts.get_creation_mode()? + | (opts.custom_flags as c_int & !libc::O_ACCMODE); + let fd = cvt_r(|| unsafe { + openat64(libc::dirfd(self.0), path.as_ptr(), flags, opts.mode as c_int) + })?; + Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) + } + + // pub fn create_dir>(&self, path: P) -> Result<()> + // pub fn rename, Q: AsRef>(&self, from: P, to_dir: &Self, to: Q) -> Result<()> + // pub fn remove_file>(&self, path: P) -> Result<()> + // pub fn remove_dir>(&self, path: P) -> Result<()> + // pub fn symlink, Q: AsRef>(&self, original: P, link: Q) +} + +fn get_path_from_fd(fd: c_int) -> Option { + #[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))] + fn get_path(fd: c_int) -> Option { + let mut p = PathBuf::from("/proc/self/fd"); + p.push(&fd.to_string()); + run_path_with_cstr(&p, &readlink).ok() + } + + #[cfg(any(target_vendor = "apple", target_os = "netbsd"))] + fn get_path(fd: c_int) -> Option { + // FIXME: The use of PATH_MAX is generally not encouraged, but it + // is inevitable in this case because Apple targets and NetBSD define `fcntl` + // with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no + // alternatives. If a better method is invented, it should be used + // instead. + let mut buf = vec![0; libc::PATH_MAX as usize]; + let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; + if n == -1 { + cfg_if::cfg_if! { + if #[cfg(target_os = "netbsd")] { + // fallback to procfs as last resort + let mut p = PathBuf::from("/proc/self/fd"); + p.push(&fd.to_string()); + return run_path_with_cstr(&p, &readlink).ok() + } else { + return None; + } + } + } + let l = buf.iter().position(|&c| c == 0).unwrap(); + buf.truncate(l as usize); + buf.shrink_to_fit(); + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(target_os = "freebsd")] + fn get_path(fd: c_int) -> Option { + let info = Box::::new_zeroed(); + let mut info = unsafe { info.assume_init() }; + info.kf_structsize = size_of::() as libc::c_int; + let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) }; + if n == -1 { + return None; + } + let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() }; + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(target_os = "vxworks")] + fn get_path(fd: c_int) -> Option { + let mut buf = vec![0; libc::PATH_MAX as usize]; + let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }; + if n == -1 { + return None; + } + let l = buf.iter().position(|&c| c == 0).unwrap(); + buf.truncate(l as usize); + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "vxworks", + target_os = "freebsd", + target_os = "netbsd", + target_os = "illumos", + target_os = "solaris", + target_vendor = "apple", + )))] + fn get_path(_fd: c_int) -> Option { + // FIXME(#24570): implement this for other Unix platforms + None + } + + get_path(fd) +} + +impl fmt::Debug for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn get_mode(fd: c_int) -> Option<(bool, bool)> { + let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + if mode == -1 { + return None; + } + match mode & libc::O_ACCMODE { + libc::O_RDONLY => Some((true, false)), + libc::O_RDWR => Some((true, true)), + libc::O_WRONLY => Some((false, true)), + _ => None, + } + } + + let fd = unsafe { dirfd(self.0) }; + let mut b = f.debug_struct("Dir"); + b.field("fd", &fd); + if let Some(path) = get_path_from_fd(fd) { + b.field("path", &path); + } + if let Some((read, write)) = get_mode(fd) { + b.field("read", &read).field("write", &write); + } + b.finish() + } +} unsafe impl Send for Dir {} unsafe impl Sync for Dir {} @@ -1661,79 +1808,6 @@ impl FromRawFd for File { impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))] - fn get_path(fd: c_int) -> Option { - let mut p = PathBuf::from("/proc/self/fd"); - p.push(&fd.to_string()); - run_path_with_cstr(&p, &readlink).ok() - } - - #[cfg(any(target_vendor = "apple", target_os = "netbsd"))] - fn get_path(fd: c_int) -> Option { - // FIXME: The use of PATH_MAX is generally not encouraged, but it - // is inevitable in this case because Apple targets and NetBSD define `fcntl` - // with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no - // alternatives. If a better method is invented, it should be used - // instead. - let mut buf = vec![0; libc::PATH_MAX as usize]; - let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; - if n == -1 { - cfg_if::cfg_if! { - if #[cfg(target_os = "netbsd")] { - // fallback to procfs as last resort - let mut p = PathBuf::from("/proc/self/fd"); - p.push(&fd.to_string()); - return run_path_with_cstr(&p, &readlink).ok() - } else { - return None; - } - } - } - let l = buf.iter().position(|&c| c == 0).unwrap(); - buf.truncate(l as usize); - buf.shrink_to_fit(); - Some(PathBuf::from(OsString::from_vec(buf))) - } - - #[cfg(target_os = "freebsd")] - fn get_path(fd: c_int) -> Option { - let info = Box::::new_zeroed(); - let mut info = unsafe { info.assume_init() }; - info.kf_structsize = size_of::() as libc::c_int; - let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) }; - if n == -1 { - return None; - } - let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() }; - Some(PathBuf::from(OsString::from_vec(buf))) - } - - #[cfg(target_os = "vxworks")] - fn get_path(fd: c_int) -> Option { - let mut buf = vec![0; libc::PATH_MAX as usize]; - let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }; - if n == -1 { - return None; - } - let l = buf.iter().position(|&c| c == 0).unwrap(); - buf.truncate(l as usize); - Some(PathBuf::from(OsString::from_vec(buf))) - } - - #[cfg(not(any( - target_os = "linux", - target_os = "vxworks", - target_os = "freebsd", - target_os = "netbsd", - target_os = "illumos", - target_os = "solaris", - target_vendor = "apple", - )))] - fn get_path(_fd: c_int) -> Option { - // FIXME(#24570): implement this for other Unix platforms - None - } - fn get_mode(fd: c_int) -> Option<(bool, bool)> { let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) }; if mode == -1 { @@ -1750,7 +1824,7 @@ impl fmt::Debug for File { let fd = self.as_raw_fd(); let mut b = f.debug_struct("File"); b.field("fd", &fd); - if let Some(path) = get_path(fd) { + if let Some(path) = get_path_from_fd(fd) { b.field("path", &path); } if let Some((read, write)) = get_mode(fd) { From 05f87c5efae748cf45f0be0747cc8ba01531446f Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 3 May 2025 11:59:23 -0400 Subject: [PATCH 02/14] add documentation, implementations for unsupported platforms --- library/std/src/fs.rs | 28 ++++++++++++++++++++++-- library/std/src/sys/fs/unix.rs | 39 +++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 1e1cdc1305427..804bbdb2ba0a4 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -133,8 +133,24 @@ pub enum TryLockError { } #[unstable(feature = "dirfd", issue = "120426")] -#[cfg(target_family = "unix")] /// An object providing access to a directory on the filesystem. +/// +/// Files are automatically closed when they go out of scope. Errors detected +/// on closing are ignored by the implementation of `Drop`. +/// +/// # Examples +/// +/// Opens a directory and then a file inside it. +/// +/// ```no_run +/// use std::fs::Dir; +/// +/// fn main() -> std::io::Result<()> { +/// let dir = Dir::new("/home/foo")?; +/// let file = dir.open("bar.txt")?; +/// Ok(()) +/// } +/// ``` pub struct Dir { inner: fs_imp::Dir, } @@ -1460,13 +1476,21 @@ impl Seek for Arc { } } -#[unstable(feature = "dirfd", issue = "120426")] impl Dir { /// Opens a file relative to this directory. + /// + /// # Examples + /// ```no_run + /// use std::fs::Dir; + /// + /// let dir = Dir::new("foo")?; + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] pub fn open>(&self, path: P) -> io::Result { self.inner.open(path).map(|f| File { inner: f }) } /// Opens a file relative to this directory with the specified options. + #[unstable(feature = "dirfd", issue = "120426")] pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { self.inner.open_with(path, &opts.0).map(|f| File { inner: f }) } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index a53167aa9fffe..34bf641fbd0e7 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -300,12 +300,41 @@ impl Dir { })?; Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) } +} - // pub fn create_dir>(&self, path: P) -> Result<()> - // pub fn rename, Q: AsRef>(&self, from: P, to_dir: &Self, to: Q) -> Result<()> - // pub fn remove_file>(&self, path: P) -> Result<()> - // pub fn remove_dir>(&self, path: P) -> Result<()> - // pub fn symlink, Q: AsRef>(&self, original: P, link: Q) +#[cfg(any( + miri, + target_os = "redox", + target_os = "nto", + target_os = "vita", + target_os = "hurd", + target_os = "espidf", + target_os = "horizon", + target_os = "vxworks", + target_os = "rtems", + target_os = "nuttx", +))] +impl Dir { + pub fn open>(&self, path: P) -> io::Result { + Err(io::const_error!( + io::ErrorKind::Unsupported, + "directory handles are not available for this platform", + )) + } + + pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + Err(io::const_error!( + io::ErrorKind::Unsupported, + "directory handles are not available for this platform", + )) + } + + pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { + Err(io::const_error!( + io::ErrorKind::Unsupported, + "directory handles are not available for this platform", + )) + } } fn get_path_from_fd(fd: c_int) -> Option { From bdeb57e71b0c2878246c28f61084e246f19a9b74 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 5 May 2025 03:54:02 -0400 Subject: [PATCH 03/14] add new Dir struct, rename/remove functions, constructors, and documentation --- library/std/src/fs.rs | 182 ++++++++++++++++++++++++++++++++- library/std/src/sys/fs/mod.rs | 4 +- library/std/src/sys/fs/unix.rs | 126 +++++++++++++---------- 3 files changed, 250 insertions(+), 62 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 804bbdb2ba0a4..1683bbc6355ab 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -146,7 +146,7 @@ pub enum TryLockError { /// use std::fs::Dir; /// /// fn main() -> std::io::Result<()> { -/// let dir = Dir::new("/home/foo")?; +/// let dir = Dir::new("foo")?; /// let file = dir.open("bar.txt")?; /// Ok(()) /// } @@ -1477,23 +1477,197 @@ impl Seek for Arc { } impl Dir { - /// Opens a file relative to this directory. + /// Attempts to open a directory at `path` in read-only mode. + /// + /// See [`new_with`] for more options. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a directory + /// * The process doesn't have permission to read the directory + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// let mut f = dir.open("bar.txt")?; + /// let mut data = vec![]; + /// f.read_to_end(&mut data)?; + /// Ok(()) + /// } + /// ``` + /// + /// [`new_with`]: Dir::new_with + #[unstable(feature = "dirfd", issue = "120426")] + pub fn new>(path: P) -> io::Result { + Ok(Self { inner: fs_imp::Dir::new(path)? }) + } + + /// Attempts to open a directory at `path` with the options specified by `opts`. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a directory + /// * The process doesn't have permission to read/write (according to `opts`) the directory /// /// # Examples + /// /// ```no_run /// use std::fs::Dir; /// - /// let dir = Dir::new("foo")?; + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new_with("foo", OpenOptions::new().write(true))?; + /// let mut f = dir.remove_file("bar.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { + Ok(Self { inner: fs_imp::Dir::new_with(path, &opts.0)? }) + } + + /// Attempts to open a file relative to this directory. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a regular file + /// * The process doesn't have permission to read/write (according to `opts`) the directory + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// let mut f = dir.open("bar.txt")?; + /// let mut data = vec![]; + /// f.read_to_end(&mut data)?; + /// Ok(()) + /// } /// ``` #[unstable(feature = "dirfd", issue = "120426")] pub fn open>(&self, path: P) -> io::Result { self.inner.open(path).map(|f| File { inner: f }) } - /// Opens a file relative to this directory with the specified options. + + /// Attempts to open a file relative to this directory with the options specified by `opts`. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a regular file + /// * The process doesn't have permission to read/write (according to `opts`) the directory + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// let mut f = dir.open_with("bar.txt", OpenOptions::new().read(true))?; + /// let mut data = vec![]; + /// f.read_to_end(&mut data)?; + /// Ok(()) + /// } + /// ``` #[unstable(feature = "dirfd", issue = "120426")] pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { self.inner.open_with(path, &opts.0).map(|f| File { inner: f }) } + + /// Attempts to remove a file relative to this directory. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a regular file + /// * The process doesn't have permission to delete the file. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// dir.remove_file("bar.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_file>(&self, path: P) -> io::Result<()> { + self.inner.remove_file(path) + } + + /// Attempts to remove a directory relative to this directory. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path doesn't exist + /// * The path doesn't specify a directory + /// * The directory isn't empty + /// * The process doesn't have permission to delete the directory. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// dir.remove_dir("baz")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + self.inner.remove_dir(path) + } + + /// Attempts to rename a file or directory relative to this directory to a new name, replacing + /// the destination file if present. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The `from` path doesn't exist + /// * The `from` path doesn't specify a directory + /// * `self` and `to_dir` are on different mount points + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// dir.rename("bar.txt", &dir, "quux.txt")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn rename, Q: AsRef>( + &self, + from: P, + to_dir: &Self, + to: Q, + ) -> io::Result<()> { + self.inner.rename(from, &to_dir.inner, to) + } } #[unstable(feature = "dirfd", issue = "120426")] diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 458c097339d90..ceee69d4dd7a6 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -46,10 +46,8 @@ pub fn with_native_path(path: &Path, f: &dyn Fn(&Path) -> io::Result) -> i f(path) } -#[cfg(target_family = "unix")] -pub use imp::Dir; pub use imp::{ - DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, ReadDir, }; diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 34bf641fbd0e7..b8eee69a47713 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -54,7 +54,8 @@ 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, openat as openat64, stat as stat64, + lstat as lstat64, off64_t, open as open64, openat as openat64, renameat, stat as stat64, + unlinkat, }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), @@ -64,14 +65,18 @@ use libc::{ )))] 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, openat as openat64, stat as stat64, + lstat as lstat64, off_t as off64_t, open as open64, openat as openat64, renameat, + stat as stat64, unlinkat, }; #[cfg(any( all(target_os = "linux", not(target_env = "musl")), target_os = "l4re", target_os = "hurd" ))] -use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, stat64}; +use libc::{ + dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, renameat, stat64, + unlinkat, +}; use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; @@ -249,7 +254,7 @@ cfg_has_statx! {{ // all DirEntry's will have a reference to this struct struct InnerReadDir { - dirp: Dir, + dirp: DirStream, root: PathBuf, } @@ -264,22 +269,21 @@ impl ReadDir { } } -pub struct Dir(*mut libc::DIR); +struct DirStream(*mut libc::DIR); + +pub struct Dir(OwnedFd); -// dirfd isn't supported everywhere -#[cfg(not(any( - miri, - target_os = "redox", - target_os = "nto", - target_os = "vita", - target_os = "hurd", - target_os = "espidf", - target_os = "horizon", - target_os = "vxworks", - target_os = "rtems", - target_os = "nuttx", -)))] impl Dir { + pub fn new>(path: P) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + run_path_with_cstr(path.as_ref(), &|path| Self::open_c_dir(path, &opts)) + } + + pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path.as_ref(), &|path| Self::open_c_dir(path, &opts)) + } + pub fn open>(&self, path: P) -> io::Result { let mut opts = OpenOptions::new(); opts.read(true); @@ -290,50 +294,62 @@ impl Dir { run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, opts)) } + pub fn remove_file>(&self, path: P) -> io::Result<()> { + run_path_with_cstr(path.as_ref(), &|path| self.remove_c(path, false)) + } + + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + run_path_with_cstr(path.as_ref(), &|path| self.remove_c(path, true)) + } + + pub fn rename, Q: AsRef>( + &self, + from: P, + to_dir: &Self, + to: Q, + ) -> io::Result<()> { + run_path_with_cstr(from.as_ref(), &|from| { + run_path_with_cstr(to.as_ref(), &|to| self.rename_c(from, to_dir, to)) + }) + } + pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { let flags = libc::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | (opts.custom_flags as c_int & !libc::O_ACCMODE); let fd = cvt_r(|| unsafe { - openat64(libc::dirfd(self.0), path.as_ptr(), flags, opts.mode as c_int) + openat64(self.0.as_raw_fd(), path.as_ptr(), flags, opts.mode as c_int) })?; Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) } -} -#[cfg(any( - miri, - target_os = "redox", - target_os = "nto", - target_os = "vita", - target_os = "hurd", - target_os = "espidf", - target_os = "horizon", - target_os = "vxworks", - target_os = "rtems", - target_os = "nuttx", -))] -impl Dir { - pub fn open>(&self, path: P) -> io::Result { - Err(io::const_error!( - io::ErrorKind::Unsupported, - "directory handles are not available for this platform", - )) + pub fn open_c_dir(path: &CStr, opts: &OpenOptions) -> io::Result { + let flags = libc::O_CLOEXEC + | libc::O_DIRECTORY + | opts.get_access_mode()? + | opts.get_creation_mode()? + | (opts.custom_flags as c_int & !libc::O_ACCMODE); + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) } - pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { - Err(io::const_error!( - io::ErrorKind::Unsupported, - "directory handles are not available for this platform", - )) + pub fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> { + cvt(unsafe { + unlinkat( + self.0.as_raw_fd(), + path.as_ptr(), + if remove_dir { libc::AT_REMOVEDIR } else { 0 }, + ) + }) + .map(|_| ()) } - pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { - Err(io::const_error!( - io::ErrorKind::Unsupported, - "directory handles are not available for this platform", - )) + pub fn rename_c(&self, p1: &CStr, new_dir: &Self, p2: &CStr) -> io::Result<()> { + cvt(unsafe { + renameat(self.0.as_raw_fd(), p1.as_ptr(), new_dir.0.as_raw_fd(), p2.as_ptr()) + }) + .map(|_| ()) } } @@ -429,7 +445,7 @@ impl fmt::Debug for Dir { } } - let fd = unsafe { dirfd(self.0) }; + let fd = self.0.as_raw_fd(); let mut b = f.debug_struct("Dir"); b.field("fd", &fd); if let Some(path) = get_path_from_fd(fd) { @@ -442,8 +458,8 @@ impl fmt::Debug for Dir { } } -unsafe impl Send for Dir {} -unsafe impl Sync for Dir {} +unsafe impl Send for DirStream {} +unsafe impl Sync for DirStream {} #[cfg(any( target_os = "android", @@ -1028,7 +1044,7 @@ pub(crate) fn debug_assert_fd_is_open(fd: RawFd) { } } -impl Drop for Dir { +impl Drop for DirStream { fn drop(&mut self) { // dirfd isn't supported everywhere #[cfg(not(any( @@ -1936,7 +1952,7 @@ pub fn readdir(path: &Path) -> io::Result { Err(Error::last_os_error()) } else { let root = path.to_path_buf(); - let inner = InnerReadDir { dirp: Dir(ptr), root }; + let inner = InnerReadDir { dirp: DirStream(ptr), root }; Ok(ReadDir::new(inner)) } } @@ -2297,7 +2313,7 @@ mod remove_dir_impl { #[cfg(all(target_os = "linux", target_env = "gnu"))] use libc::{fdopendir, openat64 as openat, unlinkat}; - use super::{Dir, DirEntry, InnerReadDir, ReadDir, lstat}; + use super::{DirEntry, DirStream, InnerReadDir, ReadDir, lstat}; use crate::ffi::CStr; use crate::io; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; @@ -2323,7 +2339,7 @@ mod remove_dir_impl { if ptr.is_null() { return Err(io::Error::last_os_error()); } - let dirp = Dir(ptr); + let dirp = DirStream(ptr); // file descriptor is automatically closed by libc::closedir() now, so give up ownership let new_parent_fd = dir_fd.into_raw_fd(); // a valid root is not needed because we do not call any functions involving the full path From 0a82f166ad548cac4c5675bb4af1773ea1813755 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 5 May 2025 05:17:42 -0400 Subject: [PATCH 04/14] fix doctests, add real tests --- library/std/src/fs.rs | 18 +++++--- library/std/src/fs/tests.rs | 85 ++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 1683bbc6355ab..0bffe5d762c11 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -143,6 +143,7 @@ pub enum TryLockError { /// Opens a directory and then a file inside it. /// /// ```no_run +/// #![feature(dirfd)] /// use std::fs::Dir; /// /// fn main() -> std::io::Result<()> { @@ -1491,7 +1492,8 @@ impl Dir { /// # Examples /// /// ```no_run - /// use std::fs::Dir; + /// #![feature(dirfd)] + /// use std::{fs::Dir, io::Read}; /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::new("foo")?; @@ -1520,7 +1522,8 @@ impl Dir { /// # Examples /// /// ```no_run - /// use std::fs::Dir; + /// #![feature(dirfd)] + /// use std::fs::{Dir, OpenOptions}; /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::new_with("foo", OpenOptions::new().write(true))?; @@ -1533,7 +1536,7 @@ impl Dir { Ok(Self { inner: fs_imp::Dir::new_with(path, &opts.0)? }) } - /// Attempts to open a file relative to this directory. + /// Attempts to open a file in read-only mode relative to this directory. /// /// # Errors /// @@ -1545,7 +1548,8 @@ impl Dir { /// # Examples /// /// ```no_run - /// use std::fs::Dir; + /// #![feature(dirfd)] + /// use std::{fs::Dir, io::Read}; /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::new("foo")?; @@ -1572,7 +1576,8 @@ impl Dir { /// # Examples /// /// ```no_run - /// use std::fs::Dir; + /// #![feature(dirfd)] + /// use std::{fs::{Dir, OpenOptions}, io::Read}; /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::new("foo")?; @@ -1599,6 +1604,7 @@ impl Dir { /// # Examples /// /// ```no_run + /// #![feature(dirfd)] /// use std::fs::Dir; /// /// fn main() -> std::io::Result<()> { @@ -1625,6 +1631,7 @@ impl Dir { /// # Examples /// /// ```no_run + /// #![feature(dirfd)] /// use std::fs::Dir; /// /// fn main() -> std::io::Result<()> { @@ -1651,6 +1658,7 @@ impl Dir { /// # Examples /// /// ```no_run + /// #![feature(dirfd)] /// use std::fs::Dir; /// /// fn main() -> std::io::Result<()> { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index c81e3af2f0d4c..09bde6a02c96f 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1,5 +1,6 @@ use rand::RngCore; +use super::Dir; #[cfg(any( windows, target_os = "freebsd", @@ -17,7 +18,7 @@ use crate::char::MAX_LEN_UTF8; target_vendor = "apple", ))] use crate::fs::TryLockError; -use crate::fs::{self, File, FileTimes, OpenOptions}; +use crate::fs::{self, File, FileTimes, OpenOptions, create_dir}; use crate::io::prelude::*; use crate::io::{BorrowedBuf, ErrorKind, SeekFrom}; use crate::mem::MaybeUninit; @@ -2084,3 +2085,85 @@ fn test_rename_junction() { // Junction links are always absolute so we just check the file name is correct. assert_eq!(fs::read_link(&dest).unwrap().file_name(), Some(not_exist.as_os_str())); } + +#[test] +fn test_dir_smoke_test() { + let tmpdir = tmpdir(); + check!(Dir::new(tmpdir.path())); +} + +#[test] +fn test_dir_read_file() { + let tmpdir = tmpdir(); + let mut f = check!(File::create(tmpdir.join("foo.txt"))); + check!(f.write(b"bar")); + check!(f.flush()); + drop(f); + let dir = check!(Dir::new(tmpdir.path())); + let mut f = check!(dir.open("foo.txt")); + let mut buf = [0u8; 3]; + check!(f.read_exact(&mut buf)); + assert_eq!(b"bar", &buf); +} + +#[test] +fn test_dir_write_file() { + let tmpdir = tmpdir(); + let dir = check!(Dir::new(tmpdir.path())); + let mut f = check!(dir.open_with("foo.txt", &OpenOptions::new().write(true).create(true))); + check!(f.write(b"bar")); + check!(f.flush()); + drop(f); + let mut f = check!(File::open(tmpdir.join("foo.txt"))); + let mut buf = [0u8; 3]; + check!(f.read_exact(&mut buf)); + assert_eq!(b"bar", &buf); +} + +#[test] +fn test_dir_remove_file() { + let tmpdir = tmpdir(); + let mut f = check!(File::create(tmpdir.join("foo.txt"))); + check!(f.write(b"bar")); + check!(f.flush()); + drop(f); + let dir = check!(Dir::new(tmpdir.path())); + check!(dir.remove_file("foo.txt")); + let result = File::open(tmpdir.join("foo.txt")); + #[cfg(all(unix, not(target_os = "vxworks")))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn test_dir_remove_dir() { + let tmpdir = tmpdir(); + check!(create_dir(tmpdir.join("foo"))); + let dir = check!(Dir::new(tmpdir.path())); + check!(dir.remove_dir("foo")); + let result = Dir::new(tmpdir.join("foo")); + #[cfg(all(unix, not(target_os = "vxworks")))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn test_dir_rename_file() { + let tmpdir = tmpdir(); + let mut f = check!(File::create(tmpdir.join("foo.txt"))); + check!(f.write(b"bar")); + check!(f.flush()); + drop(f); + let dir = check!(Dir::new(tmpdir.path())); + check!(dir.rename("foo.txt", &dir, "baz.txt")); + let mut f = check!(File::open(tmpdir.join("baz.txt"))); + let mut buf = [0u8; 3]; + check!(f.read_exact(&mut buf)); + assert_eq!(b"bar", &buf); +} From 228d9948d43e8f6cd5fff4ce2becff1a64a7b678 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 5 May 2025 21:47:16 -0400 Subject: [PATCH 05/14] add windows implementation --- library/std/src/sys/fs/windows.rs | 224 +++++++++++++++++++++++++++++- 1 file changed, 220 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index a95709b489143..650cab5bc6606 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -10,6 +10,7 @@ use crate::os::windows::io::{AsHandle, BorrowedHandle}; use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; use crate::sync::Arc; +use crate::sys::api::SetFileInformation; use crate::sys::handle::Handle; use crate::sys::pal::api::{self, WinError, set_file_information_by_handle}; use crate::sys::pal::{IoResult, fill_utf16_buf, to_u16s, truncate_utf16_at_nul}; @@ -26,6 +27,10 @@ pub struct File { handle: Handle, } +pub struct Dir { + handle: Handle, +} + #[derive(Clone)] pub struct FileAttr { attributes: u32, @@ -848,6 +853,217 @@ impl File { } } +unsafe fn nt_create_file( + access: u32, + object_attributes: &c::OBJECT_ATTRIBUTES, + share: u32, + dir: bool, +) -> Result { + let mut handle = ptr::null_mut(); + let mut io_status = c::IO_STATUS_BLOCK::PENDING; + let disposition = match (access & c::GENERIC_READ > 0, access & c::GENERIC_WRITE > 0) { + (true, true) => c::FILE_OPEN_IF, + (true, false) => c::FILE_OPEN, + (false, true) => c::FILE_CREATE, + (false, false) => { + return Err(WinError::new(c::ERROR_INVALID_PARAMETER)); + } + }; + let status = unsafe { + c::NtCreateFile( + &mut handle, + access, + object_attributes, + &mut io_status, + ptr::null(), + c::FILE_ATTRIBUTE_NORMAL, + share, + disposition, + if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE }, + ptr::null(), + 0, + ) + }; + if c::nt_success(status) { + // SAFETY: nt_success guarantees that handle is no longer null + unsafe { Ok(Handle::from_raw_handle(handle)) } + } else { + let win_error = if status == c::STATUS_DELETE_PENDING { + // We make a special exception for `STATUS_DELETE_PENDING` because + // otherwise this will be mapped to `ERROR_ACCESS_DENIED` which is + // very unhelpful because that can also mean a permission error. + WinError::DELETE_PENDING + } else { + WinError::new(unsafe { c::RtlNtStatusToDosError(status) }) + }; + Err(win_error) + } +} + +fn run_path_with_wcstr>( + path: P, + f: &dyn Fn(&WCStr) -> io::Result, +) -> io::Result { + let path = maybe_verbatim(path.as_ref())?; + // SAFETY: maybe_verbatim returns null-terminated strings + let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) }; + f(path) +} + +impl Dir { + pub fn new>(path: P) -> io::Result { + let opts = OpenOptions::new(); + run_path_with_wcstr(path, &|path| Self::new_native(path, &opts)) + } + + pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { + run_path_with_wcstr(path, &|path| Self::new_native(path, &opts)) + } + + pub fn open>(&self, path: P) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + Ok(File { handle: run_path_with_wcstr(path, &|path| self.open_native(path, &opts))? }) + } + + pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + Ok(File { handle: run_path_with_wcstr(path, &|path| self.open_native(path, &opts))? }) + } + + pub fn create_dir>(&self, path: P) -> io::Result<()> { + run_path_with_wcstr(path, &|path| { + self.create_dir_native(path, &OpenOptions::new()).map(|_| ()) + }) + } + + pub fn remove_file>(&self, path: P) -> io::Result<()> { + run_path_with_wcstr(path, &|path| self.remove_native(path, false)) + } + + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + run_path_with_wcstr(path, &|path| self.remove_native(path, true)) + } + + pub fn rename, Q: AsRef>( + &self, + from: P, + to_dir: &Self, + to: Q, + ) -> io::Result<()> { + run_path_with_wcstr(from.as_ref(), &|from| { + run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from, to_dir, to)) + }) + } + + fn new_native(path: &WCStr, opts: &OpenOptions) -> io::Result { + let name = c::UNICODE_STRING { + Length: path.count_bytes() as _, + MaximumLength: path.count_bytes() as _, + Buffer: path.as_ptr() as *mut _, + }; + let object_attributes = c::OBJECT_ATTRIBUTES { + Length: size_of::() as _, + RootDirectory: ptr::null_mut(), + ObjectName: &name, + Attributes: 0, + SecurityDescriptor: ptr::null(), + SecurityQualityOfService: ptr::null(), + }; + let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; + let handle = + unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, true) } + .io_result()?; + Ok(Self { handle }) + } + + fn open_native(&self, path: &WCStr, opts: &OpenOptions) -> io::Result { + let name = c::UNICODE_STRING { + Length: path.count_bytes() as _, + MaximumLength: path.count_bytes() as _, + Buffer: path.as_ptr() as *mut _, + }; + let object_attributes = c::OBJECT_ATTRIBUTES { + Length: size_of::() as _, + RootDirectory: self.handle.as_raw_handle(), + ObjectName: &name, + Attributes: 0, + SecurityDescriptor: ptr::null(), + SecurityQualityOfService: ptr::null(), + }; + let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; + unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, false) } + .io_result() + } + + fn create_dir_native(&self, path: &WCStr, opts: &OpenOptions) -> io::Result { + let name = c::UNICODE_STRING { + Length: path.count_bytes() as _, + MaximumLength: path.count_bytes() as _, + Buffer: path.as_ptr() as *mut _, + }; + let object_attributes = c::OBJECT_ATTRIBUTES { + Length: size_of::() as _, + RootDirectory: self.handle.as_raw_handle(), + ObjectName: &name, + Attributes: 0, + SecurityDescriptor: ptr::null(), + SecurityQualityOfService: ptr::null(), + }; + let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; + unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, true) } + .io_result() + } + + fn remove_native(&self, path: &WCStr, dir: bool) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(c::GENERIC_WRITE); + let handle = + if dir { self.create_dir_native(path, &opts) } else { self.open_native(path, &opts) }?; + let info = c::FILE_DISPOSITION_INFO_EX { Flags: c::FILE_DISPOSITION_FLAG_DELETE }; + let result = unsafe { + c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileDispositionInfoEx, + (&info).as_ptr(), + size_of::() as _, + ) + }; + if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } + } + + fn rename_native(&self, from: &WCStr, to_dir: &Self, to: &WCStr) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(c::GENERIC_WRITE); + let handle = self.open_native(from, &opts)?; + let info = c::FILE_RENAME_INFO { + Anonymous: c::FILE_RENAME_INFO_0 { ReplaceIfExists: true }, + RootDirectory: to_dir.handle.as_raw_handle(), + FileNameLength: to.count_bytes() as _, + FileName: [to.as_ptr() as u16], + }; + let result = unsafe { + c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileRenameInfo, + ptr::addr_of!(info) as _, + size_of::() as _, + ) + }; + if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("Dir"); + b.field("handle", &self.handle.as_raw_handle()); + if let Ok(path) = get_path(self.handle.as_handle()) { + b.field("path", &path); + } + b.finish() + } +} + /// A buffer for holding directory entries. struct DirBuff { buffer: Box; Self::BUFFER_SIZE]>>, @@ -997,7 +1213,7 @@ impl fmt::Debug for File { // FIXME(#24570): add more info here (e.g., mode) let mut b = f.debug_struct("File"); b.field("handle", &self.handle.as_raw_handle()); - if let Ok(path) = get_path(self) { + if let Ok(path) = get_path(self.handle.as_handle()) { b.field("path", &path); } b.finish() @@ -1486,10 +1702,10 @@ pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> { } } -fn get_path(f: &File) -> io::Result { +fn get_path(f: impl AsRawHandle) -> io::Result { fill_utf16_buf( |buf, sz| unsafe { - c::GetFinalPathNameByHandleW(f.handle.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS) + c::GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS) }, |buf| PathBuf::from(OsString::from_wide(buf)), ) @@ -1502,7 +1718,7 @@ pub fn canonicalize(p: &WCStr) -> io::Result { // This flag is so we can open directories too opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); let f = File::open_native(p, &opts)?; - get_path(&f) + get_path(f.handle) } pub fn copy(from: &WCStr, to: &WCStr) -> io::Result { From c58a2d8817c4cbcfba08fd33067e3987c14dd335 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 5 May 2025 22:20:12 -0400 Subject: [PATCH 06/14] add unix implementation for create_dir --- library/std/src/fs.rs | 27 +++++++++++++++++++++++++++ library/std/src/sys/fs/unix.rs | 18 +++++++++++++----- library/std/src/sys/fs/windows.rs | 6 +++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 0bffe5d762c11..22afe1b1aa74b 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1592,6 +1592,33 @@ impl Dir { self.inner.open_with(path, &opts.0).map(|f| File { inner: f }) } + /// Attempts to create a directory relative to this directory. + /// + /// # Errors + /// + /// This function will return an error in these (and other) situations: + /// * The path exists + /// * The process doesn't have permission to create the directory + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::{Dir, OpenOptions}, io::Read}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::new("foo")?; + /// let mut f = dir.open_with("bar.txt", OpenOptions::new().read(true))?; + /// let mut data = vec![]; + /// f.read_to_end(&mut data)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn create_dir>(&self, path: P) -> io::Result<()> { + self.inner.create_dir(path) + } + /// Attempts to remove a file relative to this directory. /// /// # Errors diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index b8eee69a47713..1d2810c6bf298 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -54,8 +54,8 @@ 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, openat as openat64, renameat, stat as stat64, - unlinkat, + lstat as lstat64, mkdirat, off64_t, open as open64, openat as openat64, renameat, + stat as stat64, unlinkat, }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), @@ -65,7 +65,7 @@ use libc::{ )))] 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, openat as openat64, renameat, + lstat as lstat64, mkdirat, off_t as off64_t, open as open64, openat as openat64, renameat, stat as stat64, unlinkat, }; #[cfg(any( @@ -74,8 +74,8 @@ use libc::{ target_os = "hurd" ))] use libc::{ - dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, renameat, stat64, - unlinkat, + dirent64, fstat64, ftruncate64, lseek64, lstat64, mkdirat, off64_t, open64, openat64, renameat, + stat64, unlinkat, }; use crate::ffi::{CStr, OsStr, OsString}; @@ -294,6 +294,10 @@ impl Dir { run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, opts)) } + pub fn create_dir>(&self, path: P) -> io::Result<()> { + run_path_with_cstr(path.as_ref(), &|path| self.create_dir_c(path)) + } + pub fn remove_file>(&self, path: P) -> io::Result<()> { run_path_with_cstr(path.as_ref(), &|path| self.remove_c(path, false)) } @@ -334,6 +338,10 @@ impl Dir { Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) } + pub fn create_dir_c(&self, path: &CStr) -> io::Result<()> { + cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) + } + pub fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> { cvt(unsafe { unlinkat( diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 650cab5bc6606..1c3cfb1e748e6 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -931,9 +931,9 @@ impl Dir { } pub fn create_dir>(&self, path: P) -> io::Result<()> { - run_path_with_wcstr(path, &|path| { - self.create_dir_native(path, &OpenOptions::new()).map(|_| ()) - }) + let mut opts = OpenOptions::new(); + opts.write(true); + run_path_with_wcstr(path, &|path| self.create_dir_native(path, &opts).map(|_| ())) } pub fn remove_file>(&self, path: P) -> io::Result<()> { From c1c4fb1bef2766dfc3f6c304442b87b3be0c1063 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 5 May 2025 23:28:27 -0400 Subject: [PATCH 07/14] add create_dir test --- library/std/src/fs/tests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 09bde6a02c96f..f255fde7e2125 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2167,3 +2167,11 @@ fn test_dir_rename_file() { check!(f.read_exact(&mut buf)); assert_eq!(b"bar", &buf); } + +#[test] +fn test_dir_create_dir() { + let tmpdir = tmpdir(); + let dir = check!(Dir::new(tmpdir.path())); + check!(dir.create_dir("foo")); + check!(Dir::new(tmpdir.join("foo"))); +} From 4890e60c9cc090673c04738b18982f4e44c4ca63 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Fri, 23 May 2025 21:04:38 -0400 Subject: [PATCH 08/14] fix windows functions, documentation --- library/std/src/fs.rs | 3 +- library/std/src/sys/fs/windows.rs | 174 ++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 59 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 22afe1b1aa74b..f413905f3046f 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1568,7 +1568,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: + /// This function may return an error in these (and other) situations, depending on the + /// specified `opts`: /// * The path doesn't exist /// * The path doesn't specify a regular file /// * The process doesn't have permission to read/write (according to `opts`) the directory diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 1c3cfb1e748e6..a30cd759c5738 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -293,6 +293,30 @@ impl OpenOptions { }) } + fn get_disposition(&self) -> io::Result { + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return Err(Error::from_raw_os_error(c::ERROR_INVALID_PARAMETER as i32)); + } + } + (_, true) => { + if self.truncate && !self.create_new { + return Err(Error::from_raw_os_error(c::ERROR_INVALID_PARAMETER as i32)); + } + } + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => c::FILE_OPEN, + (true, false, false) => c::FILE_OPEN_IF, + (false, true, false) => c::FILE_OVERWRITE, + (true, true, false) => c::FILE_OVERWRITE_IF, + (_, _, true) => c::FILE_CREATE, + }) + } + fn get_flags_and_attributes(&self) -> u32 { self.custom_flags | self.attributes @@ -855,20 +879,16 @@ impl File { unsafe fn nt_create_file( access: u32, + disposition: u32, object_attributes: &c::OBJECT_ATTRIBUTES, share: u32, dir: bool, ) -> Result { let mut handle = ptr::null_mut(); let mut io_status = c::IO_STATUS_BLOCK::PENDING; - let disposition = match (access & c::GENERIC_READ > 0, access & c::GENERIC_WRITE > 0) { - (true, true) => c::FILE_OPEN_IF, - (true, false) => c::FILE_OPEN, - (false, true) => c::FILE_CREATE, - (false, false) => { - return Err(WinError::new(c::ERROR_INVALID_PARAMETER)); - } - }; + let access = access | c::SYNCHRONIZE; + let options = if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE } + | c::FILE_SYNCHRONOUS_IO_NONALERT; let status = unsafe { c::NtCreateFile( &mut handle, @@ -879,7 +899,7 @@ unsafe fn nt_create_file( c::FILE_ATTRIBUTE_NORMAL, share, disposition, - if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE }, + options, ptr::null(), 0, ) @@ -910,38 +930,48 @@ fn run_path_with_wcstr>( f(path) } +fn run_path_with_utf16>( + path: P, + f: &dyn Fn(&[u16]) -> io::Result, +) -> io::Result { + let utf16: Vec = path.as_ref().as_os_str().encode_wide().collect(); + f(&utf16) +} + impl Dir { pub fn new>(path: P) -> io::Result { let opts = OpenOptions::new(); - run_path_with_wcstr(path, &|path| Self::new_native(path, &opts)) + Self::new_native(path.as_ref(), &opts) } pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { - run_path_with_wcstr(path, &|path| Self::new_native(path, &opts)) + Self::new_native(path.as_ref(), &opts) } pub fn open>(&self, path: P) -> io::Result { let mut opts = OpenOptions::new(); + let path = path.as_ref().as_os_str().encode_wide().collect::>(); opts.read(true); - Ok(File { handle: run_path_with_wcstr(path, &|path| self.open_native(path, &opts))? }) + Ok(File { handle: self.open_native(&path, &opts)? }) } pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { - Ok(File { handle: run_path_with_wcstr(path, &|path| self.open_native(path, &opts))? }) + let path = path.as_ref().as_os_str().encode_wide().collect::>(); + Ok(File { handle: self.open_native(&path, &opts)? }) } pub fn create_dir>(&self, path: P) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.write(true); - run_path_with_wcstr(path, &|path| self.create_dir_native(path, &opts).map(|_| ())) + run_path_with_utf16(path, &|path| self.create_dir_native(path, &opts).map(|_| ())) } pub fn remove_file>(&self, path: P) -> io::Result<()> { - run_path_with_wcstr(path, &|path| self.remove_native(path, false)) + run_path_with_utf16(path, &|path| self.remove_native(path, false)) } pub fn remove_dir>(&self, path: P) -> io::Result<()> { - run_path_with_wcstr(path, &|path| self.remove_native(path, true)) + run_path_with_utf16(path, &|path| self.remove_native(path, true)) } pub fn rename, Q: AsRef>( @@ -950,36 +980,18 @@ impl Dir { to_dir: &Self, to: Q, ) -> io::Result<()> { - run_path_with_wcstr(from.as_ref(), &|from| { - run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from, to_dir, to)) - }) + run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from.as_ref(), to_dir, to)) } - fn new_native(path: &WCStr, opts: &OpenOptions) -> io::Result { - let name = c::UNICODE_STRING { - Length: path.count_bytes() as _, - MaximumLength: path.count_bytes() as _, - Buffer: path.as_ptr() as *mut _, - }; - let object_attributes = c::OBJECT_ATTRIBUTES { - Length: size_of::() as _, - RootDirectory: ptr::null_mut(), - ObjectName: &name, - Attributes: 0, - SecurityDescriptor: ptr::null(), - SecurityQualityOfService: ptr::null(), - }; - let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; - let handle = - unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, true) } - .io_result()?; + fn new_native(path: &Path, opts: &OpenOptions) -> io::Result { + let handle = File::open(path, opts)?.into_inner(); Ok(Self { handle }) } - fn open_native(&self, path: &WCStr, opts: &OpenOptions) -> io::Result { + fn open_native(&self, path: &[u16], opts: &OpenOptions) -> io::Result { let name = c::UNICODE_STRING { - Length: path.count_bytes() as _, - MaximumLength: path.count_bytes() as _, + Length: path.len() as _, + MaximumLength: path.len() as _, Buffer: path.as_ptr() as *mut _, }; let object_attributes = c::OBJECT_ATTRIBUTES { @@ -991,14 +1003,22 @@ impl Dir { SecurityQualityOfService: ptr::null(), }; let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; - unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, false) } - .io_result() + unsafe { + nt_create_file( + opts.get_access_mode()?, + opts.get_disposition()?, + &object_attributes, + share, + false, + ) + } + .io_result() } - fn create_dir_native(&self, path: &WCStr, opts: &OpenOptions) -> io::Result { + fn create_dir_native(&self, path: &[u16], opts: &OpenOptions) -> io::Result { let name = c::UNICODE_STRING { - Length: path.count_bytes() as _, - MaximumLength: path.count_bytes() as _, + Length: path.len() as _, + MaximumLength: path.len() as _, Buffer: path.as_ptr() as *mut _, }; let object_attributes = c::OBJECT_ATTRIBUTES { @@ -1010,11 +1030,19 @@ impl Dir { SecurityQualityOfService: ptr::null(), }; let share = c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE; - unsafe { nt_create_file(opts.get_access_mode()?, &object_attributes, share, true) } - .io_result() + unsafe { + nt_create_file( + opts.get_access_mode()?, + opts.get_disposition()?, + &object_attributes, + share, + true, + ) + } + .io_result() } - fn remove_native(&self, path: &WCStr, dir: bool) -> io::Result<()> { + fn remove_native(&self, path: &[u16], dir: bool) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::GENERIC_WRITE); let handle = @@ -1031,24 +1059,54 @@ impl Dir { if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } } - fn rename_native(&self, from: &WCStr, to_dir: &Self, to: &WCStr) -> io::Result<()> { + fn rename_native(&self, from: &Path, to_dir: &Self, to: &WCStr) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::GENERIC_WRITE); - let handle = self.open_native(from, &opts)?; - let info = c::FILE_RENAME_INFO { - Anonymous: c::FILE_RENAME_INFO_0 { ReplaceIfExists: true }, - RootDirectory: to_dir.handle.as_raw_handle(), - FileNameLength: to.count_bytes() as _, - FileName: [to.as_ptr() as u16], + let handle = run_path_with_utf16(from, &|u| self.open_native(u, &opts))?; + // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation` + // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. + let Ok(new_len_without_nul_in_bytes): Result = + ((to.count_bytes() - 1) * 2).try_into() + else { + return Err(io::Error::new(io::ErrorKind::InvalidFilename, "Filename too long")); }; + let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap(); + let struct_size = offset + new_len_without_nul_in_bytes + 2; + let layout = + Layout::from_size_align(struct_size as usize, align_of::()) + .unwrap(); + + // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. + let file_rename_info; + unsafe { + file_rename_info = alloc(layout).cast::(); + if file_rename_info.is_null() { + return Err(io::ErrorKind::OutOfMemory.into()); + } + + (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { + Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, + }); + + (&raw mut (*file_rename_info).RootDirectory).write(to_dir.handle.as_raw_handle()); + // Don't include the NULL in the size + (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); + + to.as_ptr().copy_to_nonoverlapping( + (&raw mut (*file_rename_info).FileName).cast::(), + run_path_with_wcstr(from, &|s| Ok(s.count_bytes())).unwrap(), + ); + } + let result = unsafe { c::SetFileInformationByHandle( handle.as_raw_handle(), - c::FileRenameInfo, - ptr::addr_of!(info) as _, - size_of::() as _, + c::FileRenameInfoEx, + file_rename_info.cast::(), + struct_size, ) }; + unsafe { dealloc(file_rename_info.cast::(), layout) }; if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } } } From f4d6ffcb06c42366949b5f5773aa218ac64c6585 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Tue, 10 Jun 2025 12:35:28 -0400 Subject: [PATCH 09/14] requested changes --- library/std/src/fs.rs | 56 +++++++++++-------------------- library/std/src/sys/fs/windows.rs | 28 +++++++++------- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f413905f3046f..43f6223e1708d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -132,7 +132,6 @@ pub enum TryLockError { WouldBlock, } -#[unstable(feature = "dirfd", issue = "120426")] /// An object providing access to a directory on the filesystem. /// /// Files are automatically closed when they go out of scope. Errors detected @@ -144,14 +143,18 @@ pub enum TryLockError { /// /// ```no_run /// #![feature(dirfd)] -/// use std::fs::Dir; +/// use std::{fs::Dir, io::Read}; /// /// fn main() -> std::io::Result<()> { /// let dir = Dir::new("foo")?; -/// let file = dir.open("bar.txt")?; +/// let mut file = dir.open("bar.txt")?; +/// let mut s = String::new(); +/// file.read_to_string(&mut s)?; +/// println!("{}", s); /// Ok(()) /// } /// ``` +#[unstable(feature = "dirfd", issue = "120426")] pub struct Dir { inner: fs_imp::Dir, } @@ -1484,10 +1487,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path doesn't exist - /// * The path doesn't specify a directory - /// * The process doesn't have permission to read the directory + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1514,10 +1515,7 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path doesn't exist - /// * The path doesn't specify a directory - /// * The process doesn't have permission to read/write (according to `opts`) the directory + /// This function may return an error according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1540,10 +1538,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path doesn't exist - /// * The path doesn't specify a regular file - /// * The process doesn't have permission to read/write (according to `opts`) the directory + /// This function will return an error if `path` does not point to an existing file. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1568,11 +1564,7 @@ impl Dir { /// /// # Errors /// - /// This function may return an error in these (and other) situations, depending on the - /// specified `opts`: - /// * The path doesn't exist - /// * The path doesn't specify a regular file - /// * The process doesn't have permission to read/write (according to `opts`) the directory + /// This function may return an error according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1597,9 +1589,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path exists - /// * The process doesn't have permission to create the directory + /// This function will return an error if `path` points to an existing file or directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1624,10 +1615,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path doesn't exist - /// * The path doesn't specify a regular file - /// * The process doesn't have permission to delete the file. + /// This function will return an error if `path` does not point to an existing file. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1650,11 +1639,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The path doesn't exist - /// * The path doesn't specify a directory - /// * The directory isn't empty - /// * The process doesn't have permission to delete the directory. + /// This function will return an error if `path` does not point to an existing, non-empty directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// @@ -1678,10 +1664,8 @@ impl Dir { /// /// # Errors /// - /// This function will return an error in these (and other) situations: - /// * The `from` path doesn't exist - /// * The `from` path doesn't specify a directory - /// * `self` and `to_dir` are on different mount points + /// This function will return an error if `from` does not point to an existing file or directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index a30cd759c5738..f566d44db1926 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1065,19 +1065,21 @@ impl Dir { let handle = run_path_with_utf16(from, &|u| self.open_native(u, &opts))?; // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation` // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. - let Ok(new_len_without_nul_in_bytes): Result = - ((to.count_bytes() - 1) * 2).try_into() - else { - return Err(io::Error::new(io::ErrorKind::InvalidFilename, "Filename too long")); - }; - let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap(); - let struct_size = offset + new_len_without_nul_in_bytes + 2; - let layout = - Layout::from_size_align(struct_size as usize, align_of::()) - .unwrap(); + const too_long_err: io::Error = + io::const_error!(io::ErrorKind::InvalidFilename, "Filename too long"); + let struct_size = to + .count_bytes() + .checked_mul(2) + .and_then(|x| x.checked_add(offset_of!(c::FILE_RENAME_INFO, FileName))) + .ok_or(too_long_err)?; + let layout = Layout::from_size_align(struct_size, align_of::()) + .map_err(|_| too_long_err)?; + let to_byte_len_without_nul = + u32::try_from((to.count_bytes() - 1) * 2).map_err(|_| too_long_err)?; + let struct_size = u32::try_from(struct_size).map_err(|_| too_long_err)?; - // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. let file_rename_info; + // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. unsafe { file_rename_info = alloc(layout).cast::(); if file_rename_info.is_null() { @@ -1090,7 +1092,7 @@ impl Dir { (&raw mut (*file_rename_info).RootDirectory).write(to_dir.handle.as_raw_handle()); // Don't include the NULL in the size - (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); + (&raw mut (*file_rename_info).FileNameLength).write(to_byte_len_without_nul); to.as_ptr().copy_to_nonoverlapping( (&raw mut (*file_rename_info).FileName).cast::(), @@ -1539,8 +1541,8 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { Layout::from_size_align(struct_size as usize, align_of::()) .unwrap(); - // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. let file_rename_info; + // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. unsafe { file_rename_info = alloc(layout).cast::(); if file_rename_info.is_null() { From 02f9073656ada236964a1a18dfac53f60e0445e6 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 11 Jun 2025 00:44:18 -0400 Subject: [PATCH 10/14] add unsupported implementations for missing platforms --- library/std/src/sys/fs/hermit.rs | 48 +++++++++++++++++++++++++++ library/std/src/sys/fs/solid.rs | 47 ++++++++++++++++++++++++++ library/std/src/sys/fs/uefi.rs | 48 +++++++++++++++++++++++++++ library/std/src/sys/fs/unsupported.rs | 47 ++++++++++++++++++++++++++ library/std/src/sys/fs/wasi.rs | 47 ++++++++++++++++++++++++++ 5 files changed, 237 insertions(+) diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 175d919c289dd..0932456dfacb3 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -19,6 +19,54 @@ use crate::{fmt, mem}; #[derive(Debug)] pub struct File(FileDesc); + +pub struct Dir(!); + +impl Dir { + pub fn new>(_path: P) -> io::Result { + unsupported() + } + + pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn open>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn create_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_file>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn rename, Q: AsRef>( + &self, + _from: P, + _to_dir: &Self, + _to: Q, + ) -> io::Result<()> { + self.0 + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + #[derive(Clone)] pub struct FileAttr { stat_val: stat_struct, diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 808a95829114e..8224546505d8a 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -85,6 +85,8 @@ pub struct FileType(c_short); #[derive(Debug)] pub struct DirBuilder {} +pub struct Dir(!); + impl FileAttr { pub fn size(&self) -> u64 { self.stat.st_size as u64 @@ -193,6 +195,51 @@ impl Drop for InnerReadDir { } } +impl Dir { + pub fn new>(_path: P) -> io::Result { + unsupported() + } + + pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn open>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn create_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_file>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn rename, Q: AsRef>( + &self, + _from: P, + _to_dir: &Self, + _to: Q, + ) -> io::Result<()> { + self.0 + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + impl DirEntry { pub fn path(&self) -> PathBuf { self.inner.root.join(OsStr::from_bytes( diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 5763d7862f5ae..ac583fe9eafac 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -20,6 +20,9 @@ pub struct FileAttr { size: u64, } +#[derive(Debug)] +pub struct Dir(!); + pub struct ReadDir(!); pub struct DirEntry(!); @@ -115,6 +118,51 @@ impl FileType { } } +impl Dir { + pub fn new>(_path: P) -> io::Result { + unsupported() + } + + pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn open>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn create_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_file>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn rename, Q: AsRef>( + &self, + _from: P, + _to_dir: &Self, + _to: Q, + ) -> io::Result<()> { + self.0 + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::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/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index efaddb51b3751..38678a655f840 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -13,6 +13,8 @@ pub struct FileAttr(!); pub struct ReadDir(!); +pub struct Dir(!); + pub struct DirEntry(!); #[derive(Clone, Debug)] @@ -151,6 +153,51 @@ impl Iterator for ReadDir { } } +impl Dir { + pub fn new>(_path: P) -> io::Result { + unsupported() + } + + pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn open>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn create_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_file>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn rename, Q: AsRef>( + &self, + _from: P, + _to_dir: &Self, + _to: Q, + ) -> io::Result<()> { + self.0 + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + impl DirEntry { pub fn path(&self) -> PathBuf { self.0 diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index b65d86de12a3d..6a8ed907e1587 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -24,6 +24,8 @@ pub struct FileAttr { meta: wasi::Filestat, } +pub struct Dir(!); + pub struct ReadDir { inner: Arc, state: ReadDirState, @@ -160,6 +162,51 @@ impl FileType { } } +impl Dir { + pub fn new>(_path: P) -> io::Result { + unsupported() + } + + pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn open>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn create_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_file>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn remove_dir>(&self, _path: P) -> io::Result<()> { + self.0 + } + + pub fn rename, Q: AsRef>( + &self, + _from: P, + _to_dir: &Self, + _to: Q, + ) -> io::Result<()> { + self.0 + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + impl ReadDir { fn new(dir: File, root: PathBuf) -> ReadDir { ReadDir { From 9ae1e46118b88635c4da439199cd4aea1a892d9d Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 11 Jun 2025 22:33:01 -0400 Subject: [PATCH 11/14] add pathbuf implementation to common --- library/std/src/sys/fs/common.rs | 54 ++++++++++++++++++++++++++++++-- library/std/src/sys/fs/hermit.rs | 49 +---------------------------- library/std/src/sys/fs/solid.rs | 49 +---------------------------- library/std/src/sys/fs/uefi.rs | 1 - library/std/src/sys/fs/wasi.rs | 49 +---------------------------- 5 files changed, 55 insertions(+), 147 deletions(-) diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index bfd684d295b89..1e311fe7c1ca6 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] // not used on all platforms -use crate::fs; +use crate::fmt; +use crate::fs::{self, File, OpenOptions, create_dir, remove_dir, remove_file, rename}; use crate::io::{self, Error, ErrorKind}; -use crate::path::Path; +use crate::path::{Path, PathBuf}; use crate::sys_common::ignore_notfound; pub(crate) const NOT_FILE_ERROR: Error = io::const_error!( @@ -58,3 +59,52 @@ pub fn exists(path: &Path) -> io::Result { Err(error) => Err(error), } } + +pub struct Dir { + path: PathBuf, +} + +impl Dir { + pub fn new>(path: P) -> io::Result { + Ok(Self { path: path.as_ref().to_path_buf() }) + } + + pub fn new_with>(path: P, _opts: &OpenOptions) -> io::Result { + Ok(Self { path: path.as_ref().to_path_buf() }) + } + + pub fn open>(&self, path: P) -> io::Result { + File::open(self.path.join(path)) + } + + pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + opts.open(self.path.join(path)) + } + + pub fn create_dir>(&self, path: P) -> io::Result<()> { + create_dir(self.path.join(path)) + } + + pub fn remove_file>(&self, path: P) -> io::Result<()> { + remove_file(self.path.join(path)) + } + + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + remove_dir(self.path.join(path)) + } + + pub fn rename, Q: AsRef>( + &self, + from: P, + to_dir: &Self, + to: Q, + ) -> io::Result<()> { + rename(self.path.join(from), to_dir.path.join(to)) + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Dir").field("path", &self.path).finish() + } +} diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 0932456dfacb3..7028ff9ac0c3a 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -11,7 +11,7 @@ use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::FileDesc; -pub use crate::sys::fs::common::{copy, exists}; +pub use crate::sys::fs::common::{Dir, copy, exists}; use crate::sys::time::SystemTime; use crate::sys::{cvt, unsupported, unsupported_err}; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -20,53 +20,6 @@ use crate::{fmt, mem}; #[derive(Debug)] pub struct File(FileDesc); -pub struct Dir(!); - -impl Dir { - pub fn new>(_path: P) -> io::Result { - unsupported() - } - - pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { - unsupported() - } - - pub fn open>(&self, _path: P) -> io::Result { - self.0 - } - - pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { - self.0 - } - - pub fn create_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_file>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn rename, Q: AsRef>( - &self, - _from: P, - _to_dir: &Self, - _to: Q, - ) -> io::Result<()> { - self.0 - } -} - -impl fmt::Debug for Dir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} - #[derive(Clone)] pub struct FileAttr { stat_val: stat_struct, diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 8224546505d8a..fae34123968a8 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -9,7 +9,7 @@ use crate::os::raw::{c_int, c_short}; use crate::os::solid::ffi::OsStrExt; use crate::path::{Path, PathBuf}; use crate::sync::Arc; -pub use crate::sys::fs::common::exists; +pub use crate::sys::fs::common::{Dir, exists}; use crate::sys::pal::{abi, error}; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; @@ -85,8 +85,6 @@ pub struct FileType(c_short); #[derive(Debug)] pub struct DirBuilder {} -pub struct Dir(!); - impl FileAttr { pub fn size(&self) -> u64 { self.stat.st_size as u64 @@ -195,51 +193,6 @@ impl Drop for InnerReadDir { } } -impl Dir { - pub fn new>(_path: P) -> io::Result { - unsupported() - } - - pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { - unsupported() - } - - pub fn open>(&self, _path: P) -> io::Result { - self.0 - } - - pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { - self.0 - } - - pub fn create_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_file>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn rename, Q: AsRef>( - &self, - _from: P, - _to_dir: &Self, - _to: Q, - ) -> io::Result<()> { - self.0 - } -} - -impl fmt::Debug for Dir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} - impl DirEntry { pub fn path(&self) -> PathBuf { self.inner.root.join(OsStr::from_bytes( diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index ac583fe9eafac..963bfe175737a 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -20,7 +20,6 @@ pub struct FileAttr { size: u64, } -#[derive(Debug)] pub struct Dir(!); pub struct ReadDir(!); diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 6a8ed907e1587..9560205c90a99 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -9,7 +9,7 @@ use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::WasiFd; -pub use crate::sys::fs::common::exists; +pub use crate::sys::fs::common::{Dir, exists}; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; use crate::sys_common::{AsInner, FromInner, IntoInner, ignore_notfound}; @@ -24,8 +24,6 @@ pub struct FileAttr { meta: wasi::Filestat, } -pub struct Dir(!); - pub struct ReadDir { inner: Arc, state: ReadDirState, @@ -162,51 +160,6 @@ impl FileType { } } -impl Dir { - pub fn new>(_path: P) -> io::Result { - unsupported() - } - - pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { - unsupported() - } - - pub fn open>(&self, _path: P) -> io::Result { - self.0 - } - - pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { - self.0 - } - - pub fn create_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_file>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn remove_dir>(&self, _path: P) -> io::Result<()> { - self.0 - } - - pub fn rename, Q: AsRef>( - &self, - _from: P, - _to_dir: &Self, - _to: Q, - ) -> io::Result<()> { - self.0 - } -} - -impl fmt::Debug for Dir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} - impl ReadDir { fn new(dir: File, root: PathBuf) -> ReadDir { ReadDir { From f07f7c53bcb09588751c6b3d61fcd4d2790f106b Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Tue, 17 Jun 2025 23:36:01 -0400 Subject: [PATCH 12/14] requested changes --- library/std/src/fs.rs | 81 ++++++++++++++++++++++++++++++- library/std/src/sys/fs/unix.rs | 55 +++++++++++++++++++-- library/std/src/sys/fs/windows.rs | 35 ++++++++++--- 3 files changed, 159 insertions(+), 12 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 43f6223e1708d..86de039476f3d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1508,7 +1508,7 @@ impl Dir { /// [`new_with`]: Dir::new_with #[unstable(feature = "dirfd", issue = "120426")] pub fn new>(path: P) -> io::Result { - Ok(Self { inner: fs_imp::Dir::new(path)? }) + fs_imp::Dir::new(path).map(|inner| Self { inner }) } /// Attempts to open a directory at `path` with the options specified by `opts`. @@ -1531,7 +1531,33 @@ impl Dir { /// ``` #[unstable(feature = "dirfd", issue = "120426")] pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { - Ok(Self { inner: fs_imp::Dir::new_with(path, &opts.0)? }) + fs_imp::Dir::new_with(path, &opts.0).map(|inner| Self { inner }) + } + + /// Attempts to open a directory at `path` with the minimum permissions for traversal. + /// + /// # Errors + /// + /// This function may return an error according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir, io::Read}; + /// + /// fn main() -> std::io::Result<()> { + /// let foo = Dir::new_for_traversal("foo")?; + /// let foobar = foo.open_dir("bar")?; + /// let mut s = String::new(); + /// foobar.open("baz")?.read_to_string(&mut s)?; + /// println!("{s}"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn new_for_traversal>(path: P) -> io::Result { + fs_imp::Dir::new_for_traversal(path).map(|inner| Self { inner }) } /// Attempts to open a file in read-only mode relative to this directory. @@ -1611,6 +1637,57 @@ impl Dir { self.inner.create_dir(path) } + /// Attempts to open a directory relative to this directory in read-only mode. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir, io::Read}; + /// + /// fn main() -> std::io::Result<()> { + /// let foo = Dir::new("foo")?; + /// let foobar = foo.open_dir("bar")?; + /// let mut s = String::new(); + /// foobar.open("baz")?.read_to_string(&mut s)?; + /// println!("{s}"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir>(&self, path: P) -> io::Result { + self.inner.open_dir(path).map(|inner| Self { inner }) + } + + /// Attempts to open a directory relative to this directory in read-only mode. + /// + /// # Errors + /// + /// This function will return an error according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::{Dir, OpenOptions}, io::Write}; + /// + /// fn main() -> std::io::Result<()> { + /// let foo = Dir::new("foo")?; + /// let foobar = foo.open_dir_with("bar", OpenOptions::new().write(true))?; + /// foobar.open("baz")?.write(b"baz")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + self.inner.open_dir_with(path, &opts.0).map(|inner| Self { inner }) + } + /// Attempts to remove a file relative to this directory. /// /// # Errors diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 1d2810c6bf298..bc71b41698815 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -78,6 +78,23 @@ use libc::{ stat64, unlinkat, }; +#[cfg(any(target_os = "freebsd", target_os = "aix"))] +const TRAVERSE_DIRECTORY: i32 = libc::O_EXEC; +#[cfg(any(target_os = "linux", target_os = "android", target_os = "l4re", target_os = "redox"))] +const TRAVERSE_DIRECTORY: i32 = libc::O_PATH; +#[cfg(target_os = "illumos")] +const TRAVERSE_DIRECTORY: i32 = libc::O_SEARCH; +#[cfg(not(any( + target_os = "aix", + target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "l4re", + target_os = "linux", + target_os = "redox", +)))] +const TRAVERSE_DIRECTORY: i32 = libc::O_RDONLY; + use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; use crate::fs::TryLockError; @@ -277,11 +294,15 @@ impl Dir { pub fn new>(path: P) -> io::Result { let mut opts = OpenOptions::new(); opts.read(true); - run_path_with_cstr(path.as_ref(), &|path| Self::open_c_dir(path, &opts)) + run_path_with_cstr(path.as_ref(), &|path| Self::new_with_c(path, &opts)) } pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { - run_path_with_cstr(path.as_ref(), &|path| Self::open_c_dir(path, &opts)) + run_path_with_cstr(path.as_ref(), &|path| Self::new_with_c(path, opts)) + } + + pub fn new_for_traversal>(path: P) -> io::Result { + run_path_with_cstr(path.as_ref(), &|path| Self::new_c(path)) } pub fn open>(&self, path: P) -> io::Result { @@ -298,6 +319,14 @@ impl Dir { run_path_with_cstr(path.as_ref(), &|path| self.create_dir_c(path)) } + pub fn open_dir>(&self, path: P) -> io::Result { + run_path_with_cstr(path.as_ref(), &|path| self.open_c_dir(path, &OpenOptions::new())) + } + + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path.as_ref(), &|path| self.open_c_dir(path, opts)) + } + pub fn remove_file>(&self, path: P) -> io::Result<()> { run_path_with_cstr(path.as_ref(), &|path| self.remove_c(path, false)) } @@ -328,7 +357,25 @@ impl Dir { Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) } - pub fn open_c_dir(path: &CStr, opts: &OpenOptions) -> io::Result { + pub fn open_c_dir(&self, path: &CStr, opts: &OpenOptions) -> io::Result { + let flags = libc::O_CLOEXEC + | libc::O_DIRECTORY + | opts.get_access_mode()? + | opts.get_creation_mode()? + | (opts.custom_flags as c_int & !libc::O_ACCMODE); + let fd = cvt_r(|| unsafe { + openat64(self.0.as_raw_fd(), path.as_ptr(), flags, opts.mode as c_int) + })?; + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) + } + + pub fn new_c(path: &CStr) -> io::Result { + let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | TRAVERSE_DIRECTORY; + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, 0) })?; + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) + } + + pub fn new_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | opts.get_access_mode()? @@ -466,6 +513,8 @@ impl fmt::Debug for Dir { } } +// SAFETY: `int dirfd (DIR *dirstream)` is MT-safe, implying that the pointer +// may be safely sent among threads. unsafe impl Send for DirStream {} unsafe impl Sync for DirStream {} diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index f566d44db1926..0312199b6f1bb 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -941,23 +941,39 @@ fn run_path_with_utf16>( impl Dir { pub fn new>(path: P) -> io::Result { let opts = OpenOptions::new(); - Self::new_native(path.as_ref(), &opts) + Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle }) } pub fn new_with>(path: P, opts: &OpenOptions) -> io::Result { - Self::new_native(path.as_ref(), &opts) + Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle }) + } + + pub fn new_for_traversal>(path: P) -> io::Result { + Self::new_native(path.as_ref()).map(|handle| Self { handle }) } pub fn open>(&self, path: P) -> io::Result { let mut opts = OpenOptions::new(); let path = path.as_ref().as_os_str().encode_wide().collect::>(); opts.read(true); - Ok(File { handle: self.open_native(&path, &opts)? }) + self.open_native(&path, &opts).map(|handle| File { handle }) } pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { let path = path.as_ref().as_os_str().encode_wide().collect::>(); - Ok(File { handle: self.open_native(&path, &opts)? }) + self.open_native(&path, &opts).map(|handle| File { handle }) + } + + pub fn open_dir>(&self, path: P) -> io::Result { + let mut opts = OpenOptions::new(); + let path = path.as_ref().as_os_str().encode_wide().collect::>(); + opts.read(true); + self.open_native(&path, &opts).map(|handle| Self { handle }) + } + + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + let path = path.as_ref().as_os_str().encode_wide().collect::>(); + self.open_native(&path, &opts).map(|handle| Self { handle }) } pub fn create_dir>(&self, path: P) -> io::Result<()> { @@ -983,9 +999,14 @@ impl Dir { run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from.as_ref(), to_dir, to)) } - fn new_native(path: &Path, opts: &OpenOptions) -> io::Result { - let handle = File::open(path, opts)?.into_inner(); - Ok(Self { handle }) + fn new_native(path: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.access_mode(c::FILE_TRAVERSE); + File::open(path, &opts).map(|file| file.into_inner()) + } + + fn new_with_native(path: &Path, opts: &OpenOptions) -> io::Result { + File::open(path, opts).map(|file| file.into_inner()) } fn open_native(&self, path: &[u16], opts: &OpenOptions) -> io::Result { From c8b411f37d0c41d1f9333e29d2d8b253b726054d Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Thu, 19 Jun 2025 22:24:28 -0400 Subject: [PATCH 13/14] add DirEntry API, fixes for other platforms --- library/std/src/fs.rs | 172 ++++++++++++++++++++++ library/std/src/sys/fs/common.rs | 21 ++- library/std/src/sys/fs/hermit.rs | 27 ++++ library/std/src/sys/fs/solid.rs | 27 ++++ library/std/src/sys/fs/uefi.rs | 40 ++++- library/std/src/sys/fs/unix.rs | 203 ++++++++++++++++++++++++++ library/std/src/sys/fs/unsupported.rs | 40 ++++- library/std/src/sys/fs/wasi.rs | 27 ++++ library/std/src/sys/fs/windows.rs | 27 ++++ 9 files changed, 577 insertions(+), 7 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 86de039476f3d..6056254776f21 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2734,6 +2734,178 @@ impl DirEntry { pub fn file_name(&self) -> OsString { self.0.file_name() } + + /// Attempts to open the file represented by `self` in read-only mode. + /// + /// # Errors + /// + /// This function will error whenever [`File::open`] would error, including when `self` + /// represents a directory. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs, io::read_to_string}; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(file) = entry.open_file() && let Ok(text) = read_to_string(file) { + /// println!("{}", text); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_file(&self) -> io::Result { + self.0.open_file().map(|inner| File { inner }) + } + + /// Attempts to open the file represented by `self` with the options specified by `opts`. + /// + /// # Errors + /// + /// This function will error whenever [`OpenOptions::open`] would error, including when `self` + /// represents a directory. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs, io::Write}; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(mut file) = entry.open_file_with(fs::OpenOptions::new().write(true)) { + /// let _ = file.write(b"foo"); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + self.0.open_file_with(&opts.0).map(|inner| File { inner }) + } + + /// Attempts to open the directory represented by `self` in read-only mode. + /// + /// # Errors + /// + /// This function will error whenever [`Dir::new`] would error, including when `self` + /// represents a file. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs, io::read_to_string}; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(dir) = entry.open_dir() && let Ok(file) = dir.open("foo") && let Ok(text) = read_to_string(file) { + /// println!("{}", text); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir(&self) -> io::Result { + self.0.open_dir().map(|inner| Dir { inner }) + } + + /// Attempts to open the directory represented by `self` with the options specified by `opts`. + /// + /// # Errors + /// + /// This function will error whenever [`Dir::new_with`] would error, including when `self` + /// represents a file. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(dir) = entry.open_dir_with(fs::OpenOptions::new().write(true)) { + /// let _ = dir.remove_file("foo"); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + self.0.open_dir_with(&opts.0).map(|inner| Dir { inner }) + } + + /// Attempts to remove the file represented by `self`. + /// + /// # Errors + /// + /// This function will return an error whenever [`remove_file`] would error. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(ty) = entry.file_type() && ty.is_file() { + /// let _ = entry.remove_file(); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_file(&self) -> io::Result<()> { + self.0.remove_file() + } + + /// Attempts to remove the directory represented by `self`. + /// + /// # Errors + /// + /// This function will return an error whenever [`remove_dir`] would error. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs; + /// + /// if let Ok(entries) = fs::read_dir(".") { + /// for entry in entries { + /// if let Ok(entry) = entry { + /// // Here, `entry` is a `DirEntry`. + /// if let Ok(ty) = entry.file_type() && ty.is_dir() { + /// let _ = entry.remove_dir(); + /// } + /// } + /// } + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_dir(&self) -> io::Result<()> { + self.0.remove_dir() + } } #[stable(feature = "dir_entry_debug", since = "1.13.0")] diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index 1e311fe7c1ca6..659d4001c2e4c 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -1,9 +1,10 @@ #![allow(dead_code)] // not used on all platforms use crate::fmt; -use crate::fs::{self, File, OpenOptions, create_dir, remove_dir, remove_file, rename}; +use crate::fs::{self, create_dir, remove_dir, remove_file, rename}; use crate::io::{self, Error, ErrorKind}; use crate::path::{Path, PathBuf}; +use crate::sys::fs::{File, OpenOptions}; use crate::sys_common::ignore_notfound; pub(crate) const NOT_FILE_ERROR: Error = io::const_error!( @@ -73,18 +74,32 @@ impl Dir { Ok(Self { path: path.as_ref().to_path_buf() }) } + pub fn new_for_traversal>(path: P) -> io::Result { + Ok(Self { path: path.as_ref().to_path_buf() }) + } + pub fn open>(&self, path: P) -> io::Result { - File::open(self.path.join(path)) + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path.join(path), &opts) } pub fn open_with>(&self, path: P, opts: &OpenOptions) -> io::Result { - opts.open(self.path.join(path)) + File::open(&self.path.join(path), opts) } pub fn create_dir>(&self, path: P) -> io::Result<()> { create_dir(self.path.join(path)) } + pub fn open_dir>(&self, path: P) -> io::Result { + Self::new(self.path.join(path)) + } + + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + Self::new_with(self.path.join(path), opts) + } + pub fn remove_file>(&self, path: P) -> io::Result<()> { remove_file(self.path.join(path)) } diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 7028ff9ac0c3a..5af353d8f1a3f 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -12,6 +12,7 @@ use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::{Dir, copy, exists}; +use crate::sys::fs::{remove_dir, remove_file}; use crate::sys::time::SystemTime; use crate::sys::{cvt, unsupported, unsupported_err}; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -252,6 +253,32 @@ impl DirEntry { pub fn file_name_os_str(&self) -> &OsStr { self.name.as_os_str() } + + pub fn open_file(&self) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path(), &opts) + } + + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + File::open(&self.path(), opts) + } + + pub fn open_dir(&self) -> io::Result { + Dir::new(self.path()) + } + + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir::new_with(self.path(), opts) + } + + pub fn remove_file(&self) -> io::Result<()> { + remove_file(&self.path()) + } + + pub fn remove_dir(&self) -> io::Result<()> { + remove_dir(&self.path()) + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index fae34123968a8..5505f66faa2a5 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -10,6 +10,7 @@ use crate::os::solid::ffi::OsStrExt; use crate::path::{Path, PathBuf}; use crate::sync::Arc; pub use crate::sys::fs::common::{Dir, exists}; +use crate::sys::fs::{remove_dir, remove_file}; use crate::sys::pal::{abi, error}; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; @@ -219,6 +220,32 @@ impl DirEntry { _ => lstat(&self.path()).map(|m| m.file_type()), } } + + pub fn open_file(&self) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path(), &opts) + } + + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + File::open(&self.path(), opts) + } + + pub fn open_dir(&self) -> io::Result { + Dir::new(self.path()) + } + + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir::new_with(self.path(), opts) + } + + pub fn remove_file(&self) -> io::Result<()> { + remove_file(&self.path()) + } + + pub fn remove_dir(&self) -> io::Result<()> { + remove_dir(&self.path()) + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 963bfe175737a..efa9610231057 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -122,7 +122,11 @@ impl Dir { unsupported() } - pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + pub fn new_with>(_path: P, _opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn new_for_traversal>(_path: P) -> io::Result { unsupported() } @@ -130,7 +134,7 @@ impl Dir { self.0 } - pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + pub fn open_with>(&self, _path: P, _opts: &OpenOptions) -> io::Result { self.0 } @@ -138,6 +142,14 @@ impl Dir { self.0 } + pub fn open_dir>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_dir_with>(&self, _path: P, _opts: &OpenOptions) -> io::Result { + self.0 + } + pub fn remove_file>(&self, _path: P) -> io::Result<()> { self.0 } @@ -192,6 +204,30 @@ impl DirEntry { pub fn file_type(&self) -> io::Result { self.0 } + + pub fn open_file(&self) -> io::Result { + self.0 + } + + pub fn open_file_with(&self, _opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn open_dir(&self) -> io::Result { + self.0 + } + + pub fn open_dir_with(&self, _opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn remove_file(&self) -> io::Result<()> { + self.0 + } + + pub fn remove_dir(&self) -> io::Result<()> { + self.0 + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index bc71b41698815..fb8e342073089 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -288,8 +288,37 @@ impl ReadDir { struct DirStream(*mut libc::DIR); +#[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri +))] +pub use crate::sys::fs::common::Dir; + +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri +)))] pub struct Dir(OwnedFd); +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri +)))] impl Dir { pub fn new>(path: P) -> io::Result { let mut opts = OpenOptions::new(); @@ -485,6 +514,15 @@ fn get_path_from_fd(fd: c_int) -> Option { get_path(fd) } +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri +)))] impl fmt::Debug for Dir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn get_mode(fd: c_int) -> Option<(bool, bool)> { @@ -1320,6 +1358,171 @@ impl DirEntry { pub fn file_name_os_str(&self) -> &OsStr { OsStr::from_bytes(self.name_bytes()) } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn open_file(&self) -> io::Result { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }).open(self.file_name()) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn open_file(&self) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path(), &opts) + } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }) + .open_with(self.file_name(), opts) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + File::open(&self.path(), &opts) + } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn open_dir(&self) -> io::Result { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }) + .open_dir(self.file_name()) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn open_dir(&self) -> io::Result { + Dir::new(self.path()) + } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }) + .open_dir_with(self.file_name(), opts) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir::new_with(self.path(), opts) + } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn remove_file(&self) -> io::Result<()> { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }) + .remove_file(self.file_name()) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn remove_file(&self) -> io::Result<()> { + use crate::sys::fs::remove_file; + remove_file(&self.path()) + } + + #[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + )))] + pub fn remove_dir(&self) -> io::Result<()> { + Dir(unsafe { OwnedFd::from_raw_fd(libc::dirfd(self.dir.dirp.0)) }) + .remove_dir(self.file_name()) + } + + #[cfg(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + miri + ))] + pub fn remove_dir(&self) -> io::Result<()> { + use crate::sys::fs::remove_dir; + remove_dir(&self.path()) + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 38678a655f840..09bdab3cce8f2 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -158,7 +158,11 @@ impl Dir { unsupported() } - pub fn new_with>(_path: P, opts: &OpenOptions) -> io::Result { + pub fn new_with>(_path: P, _opts: &OpenOptions) -> io::Result { + unsupported() + } + + pub fn new_for_traversal>(_path: P) -> io::Result { unsupported() } @@ -166,7 +170,7 @@ impl Dir { self.0 } - pub fn open_with>(&self, _path: P, opts: &OpenOptions) -> io::Result { + pub fn open_with>(&self, _path: P, _opts: &OpenOptions) -> io::Result { self.0 } @@ -174,6 +178,14 @@ impl Dir { self.0 } + pub fn open_dir>(&self, _path: P) -> io::Result { + self.0 + } + + pub fn open_dir_with>(&self, _path: P, _opts: &OpenOptions) -> io::Result { + self.0 + } + pub fn remove_file>(&self, _path: P) -> io::Result<()> { self.0 } @@ -214,6 +226,30 @@ impl DirEntry { pub fn file_type(&self) -> io::Result { self.0 } + + pub fn open_file(&self) -> io::Result { + self.0 + } + + pub fn open_file_with(&self, _opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn open_dir(&self) -> io::Result { + self.0 + } + + pub fn open_dir_with(&self, _opts: &OpenOptions) -> io::Result { + self.0 + } + + pub fn remove_file(&self) -> io::Result<()> { + self.0 + } + + pub fn remove_dir(&self) -> io::Result<()> { + self.0 + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 9560205c90a99..39f993c70f66a 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -10,6 +10,7 @@ use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::WasiFd; pub use crate::sys::fs::common::{Dir, exists}; +use crate::sys::fs::{remove_dir, remove_file}; use crate::sys::time::SystemTime; use crate::sys::{unsupported, unsupported_err}; use crate::sys_common::{AsInner, FromInner, IntoInner, ignore_notfound}; @@ -293,6 +294,32 @@ impl DirEntry { pub fn ino(&self) -> wasi::Inode { self.meta.d_ino } + + pub fn open_file(&self) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path(), &opts) + } + + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + File::open(&self.path(), opts) + } + + pub fn open_dir(&self) -> io::Result { + Dir::new(self.path()) + } + + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir::new_with(self.path(), opts) + } + + pub fn remove_file(&self) -> io::Result<()> { + remove_file(&self.path()) + } + + pub fn remove_dir(&self) -> io::Result<()> { + remove_dir(&self.path()) + } } impl OpenOptions { diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 0312199b6f1bb..f6b8de07ad672 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -11,6 +11,7 @@ use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::api::SetFileInformation; +use crate::sys::fs::{remove_dir, remove_file}; use crate::sys::handle::Handle; use crate::sys::pal::api::{self, WinError, set_file_information_by_handle}; use crate::sys::pal::{IoResult, fill_utf16_buf, to_u16s, truncate_utf16_at_nul}; @@ -189,6 +190,32 @@ impl DirEntry { pub fn metadata(&self) -> io::Result { Ok(self.data.into()) } + + pub fn open_file(&self) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + File::open(&self.path(), &opts) + } + + pub fn open_file_with(&self, opts: &OpenOptions) -> io::Result { + File::open(&self.path(), opts) + } + + pub fn open_dir(&self) -> io::Result { + Dir::new(&self.path()) + } + + pub fn open_dir_with(&self, opts: &OpenOptions) -> io::Result { + Dir::new_with(&self.path(), opts) + } + + pub fn remove_file(&self) -> io::Result<()> { + remove_file(&self.path()) + } + + pub fn remove_dir(&self) -> io::Result<()> { + remove_dir(&self.path()) + } } impl OpenOptions { From 3deaa185cfb0c803499bac49522bfb7e4d291984 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sun, 22 Jun 2025 13:14:45 -0400 Subject: [PATCH 14/14] rework *at imports --- library/std/src/sys/fs/unix.rs | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index fb8e342073089..ea2b7861f3c9c 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -54,8 +54,7 @@ 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, mkdirat, off64_t, open as open64, openat as openat64, renameat, - stat as stat64, unlinkat, + lstat as lstat64, off64_t, open as open64, stat as stat64, }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), @@ -65,18 +64,37 @@ use libc::{ )))] use libc::{ dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, - lstat as lstat64, mkdirat, off_t as off64_t, open as open64, openat as openat64, renameat, - stat as stat64, unlinkat, + 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 = "l4re", target_os = "hurd" ))] -use libc::{ - dirent64, fstat64, ftruncate64, lseek64, lstat64, mkdirat, off64_t, open64, openat64, renameat, - stat64, unlinkat, -}; +use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; +#[cfg(any( + target_os = "aix", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "solaris", + target_vendor = "apple", +))] +use libc::{mkdirat, openat as openat64, renameat, unlinkat}; +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "vxworks", + target_os = "fuchsia", + target_os = "freebsd", + target_os = "solaris", + target_os = "aix", + target_vendor = "apple", + miri +)))] +use libc::{mkdirat, openat64, renameat, unlinkat}; #[cfg(any(target_os = "freebsd", target_os = "aix"))] const TRAVERSE_DIRECTORY: i32 = libc::O_EXEC;