From 59c426c870db1610860f6df834ca6628a71ad4ba Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Wed, 17 May 2023 18:31:52 +0100 Subject: [PATCH 1/6] Add `NativePath` And replace `AsRef` with the `AsPath` trait. --- library/std/src/fs.rs | 118 ++++++++++++++++++++-------------------- library/std/src/path.rs | 53 +++++++++++++++++- 2 files changed, 112 insertions(+), 59 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 694cc34cdc171..bffb9296e1156 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -14,10 +14,11 @@ mod tests; use crate::ffi::OsString; use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; -use crate::path::{Path, PathBuf}; +use crate::path::{AsPath, Path, PathBuf}; use crate::sealed::Sealed; use crate::sync::Arc; -use crate::sys::fs as fs_imp; +use crate::sys; +use crate::sys::fs::fs_imp; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; @@ -256,16 +257,16 @@ pub struct DirBuilder { /// } /// ``` #[stable(feature = "fs_read_write_bytes", since = "1.26.0")] -pub fn read>(path: P) -> io::Result> { - fn inner(path: &Path) -> io::Result> { - let mut file = File::open(path)?; +pub fn read(path: P) -> io::Result> { + fn inner(mut file: File) -> io::Result> { let size = file.metadata().map(|m| m.len() as usize).ok(); let mut bytes = Vec::new(); bytes.try_reserve_exact(size.unwrap_or(0)).map_err(|_| io::ErrorKind::OutOfMemory)?; io::default_read_to_end(&mut file, &mut bytes, size)?; Ok(bytes) } - inner(path.as_ref()) + let file = File::open(path)?; + inner(file) } /// Read the entire contents of a file into a string. @@ -299,16 +300,16 @@ pub fn read>(path: P) -> io::Result> { /// } /// ``` #[stable(feature = "fs_read_write", since = "1.26.0")] -pub fn read_to_string>(path: P) -> io::Result { - fn inner(path: &Path) -> io::Result { - let mut file = File::open(path)?; +pub fn read_to_string(path: P) -> io::Result { + fn inner(mut file: File) -> io::Result { let size = file.metadata().map(|m| m.len() as usize).ok(); let mut string = String::new(); string.try_reserve_exact(size.unwrap_or(0)).map_err(|_| io::ErrorKind::OutOfMemory)?; io::default_read_to_string(&mut file, &mut string, size)?; Ok(string) } - inner(path.as_ref()) + let file = File::open(path)?; + inner(file) } /// Write a slice as the entire contents of a file. @@ -336,11 +337,12 @@ pub fn read_to_string>(path: P) -> io::Result { /// } /// ``` #[stable(feature = "fs_read_write_bytes", since = "1.26.0")] -pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { - fn inner(path: &Path, contents: &[u8]) -> io::Result<()> { - File::create(path)?.write_all(contents) +pub fn write>(path: P, contents: C) -> io::Result<()> { + fn inner(mut file: File, contents: &[u8]) -> io::Result<()> { + file.write_all(contents) } - inner(path.as_ref(), contents.as_ref()) + let file = File::create(path)?; + inner(file, contents.as_ref()) } impl File { @@ -371,8 +373,8 @@ impl File { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] - pub fn open>(path: P) -> io::Result { - OpenOptions::new().read(true).open(path.as_ref()) + pub fn open(path: P) -> io::Result { + OpenOptions::new().read(true).open(path) } /// Opens a file in write-only mode. @@ -400,8 +402,8 @@ impl File { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] - pub fn create>(path: P) -> io::Result { - OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref()) + pub fn create(path: P) -> io::Result { + OpenOptions::new().write(true).create(true).truncate(true).open(path) } /// Creates a new file in read-write mode; error if the file exists. @@ -429,8 +431,8 @@ impl File { /// } /// ``` #[stable(feature = "file_create_new", since = "1.77.0")] - pub fn create_new>(path: P) -> io::Result { - OpenOptions::new().read(true).write(true).create_new(true).open(path.as_ref()) + pub fn create_new(path: P) -> io::Result { + OpenOptions::new().read(true).write(true).create_new(true).open(path) } /// Returns a new OpenOptions object. @@ -1127,12 +1129,12 @@ impl OpenOptions { /// [`NotFound`]: io::ErrorKind::NotFound /// [`PermissionDenied`]: io::ErrorKind::PermissionDenied #[stable(feature = "rust1", since = "1.0.0")] - pub fn open>(&self, path: P) -> io::Result { - self._open(path.as_ref()) + pub fn open(&self, path: P) -> io::Result { + path.with_native_path(|path| self._open(path)) } - fn _open(&self, path: &Path) -> io::Result { - fs_imp::File::open(path, &self.0).map(|inner| File { inner }) + fn _open(&self, path: &sys::path::NativePath) -> io::Result { + fs_imp::File::open_native(path, &self.0).map(|inner| File { inner }) } } @@ -1884,8 +1886,8 @@ impl AsInner for DirEntry { /// ``` #[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn remove_file>(path: P) -> io::Result<()> { - fs_imp::unlink(path.as_ref()) +pub fn remove_file(path: P) -> io::Result<()> { + fs_imp::remove_file(path) } /// Given a path, query the file system to get information about a file, @@ -1923,8 +1925,8 @@ pub fn remove_file>(path: P) -> io::Result<()> { /// ``` #[doc(alias = "stat")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn metadata>(path: P) -> io::Result { - fs_imp::stat(path.as_ref()).map(Metadata) +pub fn metadata(path: P) -> io::Result { + fs_imp::metadata(path).map(Metadata) } /// Query the metadata about a file without following symlinks. @@ -1958,8 +1960,8 @@ pub fn metadata>(path: P) -> io::Result { /// ``` #[doc(alias = "lstat")] #[stable(feature = "symlink_metadata", since = "1.1.0")] -pub fn symlink_metadata>(path: P) -> io::Result { - fs_imp::lstat(path.as_ref()).map(Metadata) +pub fn symlink_metadata(path: P) -> io::Result { + fs_imp::symlink_metadata(path).map(Metadata) } /// Rename a file or directory to a new name, replacing the original file if @@ -2002,8 +2004,8 @@ pub fn symlink_metadata>(path: P) -> io::Result { /// ``` #[doc(alias = "mv", alias = "MoveFile", alias = "MoveFileEx")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> { - fs_imp::rename(from.as_ref(), to.as_ref()) +pub fn rename(from: P, to: Q) -> io::Result<()> { + fs_imp::rename(from, to) } /// Copies the contents of one file to another. This function will also @@ -2063,8 +2065,8 @@ pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> #[doc(alias = "CopyFile", alias = "CopyFileEx")] #[doc(alias = "fclonefileat", alias = "fcopyfile")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn copy, Q: AsRef>(from: P, to: Q) -> io::Result { - fs_imp::copy(from.as_ref(), to.as_ref()) +pub fn copy(from: P, to: Q) -> io::Result { + fs_imp::copy(from, to) } /// Creates a new hard link on the filesystem. @@ -2108,8 +2110,8 @@ pub fn copy, Q: AsRef>(from: P, to: Q) -> io::Result { /// ``` #[doc(alias = "CreateHardLink", alias = "linkat")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn hard_link, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - fs_imp::link(original.as_ref(), link.as_ref()) +pub fn hard_link(original: P, link: Q) -> io::Result<()> { + fs_imp::hard_link(original, link) } /// Creates a new symbolic link on the filesystem. @@ -2140,8 +2142,8 @@ pub fn hard_link, Q: AsRef>(original: P, link: Q) -> io::Re note = "replaced with std::os::unix::fs::symlink and \ std::os::windows::fs::{symlink_file, symlink_dir}" )] -pub fn soft_link, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - fs_imp::symlink(original.as_ref(), link.as_ref()) +pub fn soft_link(original: P, link: Q) -> io::Result<()> { + fs_imp::soft_link(original, link) } /// Reads a symbolic link, returning the file that the link points to. @@ -2174,8 +2176,8 @@ pub fn soft_link, Q: AsRef>(original: P, link: Q) -> io::Re /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] -pub fn read_link>(path: P) -> io::Result { - fs_imp::readlink(path.as_ref()) +pub fn read_link(path: P) -> io::Result { + fs_imp::read_link(path) } /// Returns the canonical, absolute form of a path with all intermediate @@ -2217,8 +2219,8 @@ pub fn read_link>(path: P) -> io::Result { #[doc(alias = "realpath")] #[doc(alias = "GetFinalPathNameByHandle")] #[stable(feature = "fs_canonicalize", since = "1.5.0")] -pub fn canonicalize>(path: P) -> io::Result { - fs_imp::canonicalize(path.as_ref()) +pub fn canonicalize(path: P) -> io::Result { + fs_imp::canonicalize(path) } /// Creates a new, empty directory at the provided path @@ -2259,8 +2261,8 @@ pub fn canonicalize>(path: P) -> io::Result { #[doc(alias = "mkdir", alias = "CreateDirectory")] #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_create_dir")] -pub fn create_dir>(path: P) -> io::Result<()> { - DirBuilder::new().create(path.as_ref()) +pub fn create_dir(path: P) -> io::Result<()> { + DirBuilder::new().create(path) } /// Recursively create a directory and all of its parent components if they @@ -2303,8 +2305,8 @@ pub fn create_dir>(path: P) -> io::Result<()> { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] -pub fn create_dir_all>(path: P) -> io::Result<()> { - DirBuilder::new().recursive(true).create(path.as_ref()) +pub fn create_dir_all(path: P) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) } /// Removes an empty directory. @@ -2339,8 +2341,8 @@ pub fn create_dir_all>(path: P) -> io::Result<()> { /// ``` #[doc(alias = "rmdir", alias = "RemoveDirectory")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn remove_dir>(path: P) -> io::Result<()> { - fs_imp::rmdir(path.as_ref()) +pub fn remove_dir(path: P) -> io::Result<()> { + fs_imp::remove_dir(path) } /// Removes a directory at this path, after removing all its contents. Use @@ -2386,8 +2388,8 @@ pub fn remove_dir>(path: P) -> io::Result<()> { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] -pub fn remove_dir_all>(path: P) -> io::Result<()> { - fs_imp::remove_dir_all(path.as_ref()) +pub fn remove_dir_all(path: P) -> io::Result<()> { + fs_imp::remove_dir_all(path) } /// Returns an iterator over the entries within a directory. @@ -2462,8 +2464,8 @@ pub fn remove_dir_all>(path: P) -> io::Result<()> { /// ``` #[doc(alias = "ls", alias = "opendir", alias = "FindFirstFile", alias = "FindNextFile")] #[stable(feature = "rust1", since = "1.0.0")] -pub fn read_dir>(path: P) -> io::Result { - fs_imp::readdir(path.as_ref()).map(ReadDir) +pub fn read_dir(path: P) -> io::Result { + fs_imp::read_dir(path).map(ReadDir) } /// Changes the permissions found on a file or a directory. @@ -2498,8 +2500,8 @@ pub fn read_dir>(path: P) -> io::Result { /// ``` #[doc(alias = "chmod", alias = "SetFileAttributes")] #[stable(feature = "set_permissions", since = "1.1.0")] -pub fn set_permissions>(path: P, perm: Permissions) -> io::Result<()> { - fs_imp::set_perm(path.as_ref(), perm.0) +pub fn set_permissions(path: P, perm: Permissions) -> io::Result<()> { + fs_imp::set_permissions(path, perm.0) } impl DirBuilder { @@ -2558,8 +2560,8 @@ impl DirBuilder { /// assert!(fs::metadata(path).unwrap().is_dir()); /// ``` #[stable(feature = "dir_builder", since = "1.6.0")] - pub fn create>(&self, path: P) -> io::Result<()> { - self._create(path.as_ref()) + pub fn create(&self, path: P) -> io::Result<()> { + path.with_path(|path| self._create(path)) } fn _create(&self, path: &Path) -> io::Result<()> { @@ -2630,6 +2632,6 @@ impl AsInnerMut for DirBuilder { // instead. #[unstable(feature = "fs_try_exists", issue = "83186")] #[inline] -pub fn try_exists>(path: P) -> io::Result { - fs_imp::try_exists(path.as_ref()) +pub fn try_exists(path: P) -> io::Result { + fs_imp::try_exists(path) } diff --git a/library/std/src/path.rs b/library/std/src/path.rs index 56ea51226f9d5..f05f05a4b75a1 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -86,7 +86,9 @@ use crate::sync::Arc; use crate::ffi::{os_str, OsStr, OsString}; use crate::sys; -use crate::sys::path::{is_sep_byte, is_verbatim_sep, parse_prefix, MAIN_SEP_STR}; +use crate::sys::path::{ + is_sep_byte, is_verbatim_sep, parse_prefix, NativePath as NativePathImpl, MAIN_SEP_STR, +}; //////////////////////////////////////////////////////////////////////////////// // GENERAL NOTES @@ -1108,6 +1110,55 @@ impl FusedIterator for Ancestors<'_> {} // Basic types and traits //////////////////////////////////////////////////////////////////////////////// +/// Represents a path in a form native to the OS. +#[derive(Debug)] +#[repr(transparent)] +#[unstable(feature = "fs_native_path", issue = "108979")] +pub struct NativePath(pub(crate) NativePathImpl); + +#[unstable(feature = "sealed", issue = "none")] +impl crate::sealed::Sealed for NativePath {} +#[unstable(feature = "sealed", issue = "none")] +impl crate::sealed::Sealed for &NativePath {} + +/// # Stable use +/// +/// Functions that need a filesystem path will often accept any type that implements [`AsRef`]. +/// +/// These types include [`Path`], [`OsStr`] and [`str`] as well as their owned +/// counterparts [`PathBuf`], [`OsString`] and [`String`]. +/// +/// You can also implement you own [`AsRef`] for your own types and they'll +/// automatically work for filesystem functions. +/// +/// ## Example +/// +/// ```no_run +/// use std::ffi::OsStr; +/// use std::path::Path; +/// use std::fs; +/// +/// // These are all equivalent. +/// let metadata = fs::metadata("path/to/file")?; +/// let metadata = fs::metadata(Path::new("path/to/file"))?; +/// let metadata = fs::metadata(OsStr::new("path/to/file"))?; +/// # Ok::<(), std::io::Error>(()) +/// ``` +/// +/// # Unstable use +/// +/// The `AsPath` trait can also be used with [`NativePath`] to pass platform +/// native paths more directly to system APIs. +#[unstable(feature = "fs_native_path", issue = "108979")] +pub trait AsPath { + #[doc(hidden)] + #[unstable(feature = "fs_native_path_internals", issue = "none")] + fn with_path io::Result>(self, f: F) -> io::Result; + #[doc(hidden)] + #[unstable(feature = "fs_native_path_internals", issue = "none")] + fn with_native_path io::Result>(self, f: F) -> io::Result; +} + /// An owned, mutable path (akin to [`String`]). /// /// This type provides methods like [`push`] and [`set_extension`] that mutate From a8b37dfbbe10625cbfd93c2751343b9180b81309 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Wed, 13 Dec 2023 05:53:51 +0000 Subject: [PATCH 2/6] Windows implementation of `NativePath` --- library/std/src/os/windows/ffi.rs | 36 +++++++++ library/std/src/os/windows/fs.rs | 12 +-- library/std/src/sys/pal/windows/fs.rs | 88 +++++++++++++++++----- library/std/src/sys/pal/windows/pipe.rs | 5 +- library/std/src/sys/pal/windows/process.rs | 5 +- library/std/src/sys/path/windows.rs | 59 ++++++++++++++- library/std/src/sys/path/windows/tests.rs | 2 +- 7 files changed, 176 insertions(+), 31 deletions(-) diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs index 96bab59d3f8d7..1e20f1cbededf 100644 --- a/library/std/src/os/windows/ffi.rs +++ b/library/std/src/os/windows/ffi.rs @@ -54,6 +54,8 @@ #![stable(feature = "rust1", since = "1.0.0")] use crate::ffi::{OsStr, OsString}; +#[cfg(not(target_os = "uefi"))] +use crate::path::NativePath; use crate::sealed::Sealed; use crate::sys::os_str::Buf; use crate::sys_common::wtf8::Wtf8Buf; @@ -134,3 +136,37 @@ impl OsStrExt for OsStr { self.as_inner().inner.encode_wide() } } + +/// On Windows `NativePath` wraps a wide string for use in filesystem function calls. +/// These strings are `&[u16]` slices. +/// +/// # Wide strings +/// +/// Filesystem paths in Windows are encoded as UTF-16 strings. +/// However, because the kernel does not verify validity this may contain invalid UTF-16. +/// Therefore we use the term "wide string" for potentially invalid UTF-16. +#[cfg(windows)] +#[unstable(feature = "fs_native_path", issue = "108979")] +pub trait NativePathExt: Sealed { + /// Wrap a Windows wide string as a `NativePath`. + /// The `wide` string must be null terminated and must not otherwise contain nulls. + fn from_wide(wide: &[u16]) -> &NativePath; + /// Wrap a Windows wide string as a `NativePath` without checking for null termination or internal nulls. + unsafe fn from_wide_unchecked(wide: &[u16]) -> &NativePath; + /// Unwrap the `NativePath` to return the inner wide string. + fn into_wide(&self) -> &[u16]; +} +#[cfg(windows)] +#[unstable(feature = "fs_native_path", issue = "108979")] +impl NativePathExt for NativePath { + fn from_wide(wide: &[u16]) -> &NativePath { + assert_eq!(crate::sys::unrolled_find_u16s(0, wide), Some(wide.len().saturating_sub(1))); + unsafe { Self::from_wide_unchecked(wide) } + } + unsafe fn from_wide_unchecked(wide: &[u16]) -> &NativePath { + &*(wide as *const [u16] as *const Self) + } + fn into_wide(&self) -> &[u16] { + unsafe { &*(self as *const Self as *const [u16]) } + } +} diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index e9d7a13e9d5b2..cff92e0944d2b 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -6,7 +6,7 @@ use crate::fs::{self, Metadata, OpenOptions}; use crate::io; -use crate::path::Path; +use crate::path::AsPath; use crate::sealed::Sealed; use crate::sys; use crate::sys_common::{AsInner, AsInnerMut, IntoInner}; @@ -578,8 +578,9 @@ impl FileTimesExt for fs::FileTimes { /// /// [symlink-security]: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links #[stable(feature = "symlink", since = "1.1.0")] -pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) +pub fn symlink_file(original: P, link: Q) -> io::Result<()> { + original + .with_path(|original| link.with_path(|link| sys::fs::symlink_inner(original, link, false))) } /// Creates a new symlink to a directory on the filesystem. @@ -617,6 +618,7 @@ pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io: /// /// [symlink-security]: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links #[stable(feature = "symlink", since = "1.1.0")] -pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) +pub fn symlink_dir(original: P, link: Q) -> io::Result<()> { + original + .with_path(|original| link.with_path(|link| sys::fs::symlink_inner(original, link, true))) } diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index 3a9e7b4660b36..fbac36f2edf23 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -11,6 +11,7 @@ use crate::ptr; use crate::slice; use crate::sync::Arc; use crate::sys::handle::Handle; +use crate::sys::path::NativePath; use crate::sys::time::SystemTime; use crate::sys::{c, cvt, Align8}; use crate::sys_common::{AsInner, FromInner, IntoInner}; @@ -19,6 +20,60 @@ use crate::thread; use super::{api, to_u16s, IoResult}; use crate::sys::path::maybe_verbatim; +/// The crate-public interface +pub(crate) mod fs_imp { + use crate::io; + use crate::path::AsPath; + use crate::path::PathBuf; + use crate::sys::fs; + pub(crate) use crate::sys::fs::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + pub(crate) fn remove_file(path: P) -> io::Result<()> { + path.with_path(fs::unlink) + } + pub(crate) fn symlink_metadata(path: P) -> io::Result { + path.with_native_path(fs::lstat) + } + pub(crate) fn metadata(path: P) -> io::Result { + path.with_native_path(fs::stat) + } + pub(crate) fn rename(from: P, to: Q) -> io::Result<()> { + from.with_path(|from| to.with_path(|to| fs::rename(from, to))) + } + pub(crate) fn hard_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| fs::link(original, link))) + } + pub(crate) fn soft_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| fs::symlink(original, link))) + } + pub(crate) fn remove_dir(path: P) -> io::Result<()> { + path.with_path(fs::rmdir) + } + pub(crate) fn read_dir(path: P) -> io::Result { + path.with_path(fs::readdir) + } + pub(crate) fn set_permissions(path: P, perms: FilePermissions) -> io::Result<()> { + path.with_path(|path| fs::set_perm(path, perms)) + } + pub(crate) fn copy(from: P, to: Q) -> io::Result { + from.with_path(|from| to.with_path(|to| fs::copy(from, to))) + } + pub(crate) fn canonicalize(path: P) -> io::Result { + path.with_native_path(fs::canonicalize) + } + pub(crate) fn remove_dir_all(path: P) -> io::Result<()> { + path.with_native_path(fs::remove_dir_all) + } + pub(crate) fn read_link(path: P) -> io::Result { + path.with_native_path(fs::readlink) + } + pub(crate) fn try_exists(path: P) -> io::Result { + path.with_native_path(fs::try_exists) + } +} + pub struct File { handle: Handle, } @@ -294,8 +349,7 @@ impl OpenOptions { } impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - let path = maybe_verbatim(path)?; + pub fn open_native(path: &NativePath, opts: &OpenOptions) -> io::Result { let creation = opts.get_creation_mode()?; let handle = unsafe { c::CreateFileW( @@ -1132,16 +1186,16 @@ pub fn rmdir(p: &Path) -> io::Result<()> { } /// Open a file or directory without following symlinks. -fn open_link(path: &Path, access_mode: u32) -> io::Result { +fn open_link(path: &NativePath, access_mode: u32) -> io::Result { let mut opts = OpenOptions::new(); opts.access_mode(access_mode); // `FILE_FLAG_BACKUP_SEMANTICS` allows opening directories. // `FILE_FLAG_OPEN_REPARSE_POINT` opens a link instead of its target. opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT); - File::open(path, &opts) + File::open_native(path, &opts) } -pub fn remove_dir_all(path: &Path) -> io::Result<()> { +pub fn remove_dir_all(path: &NativePath) -> io::Result<()> { let file = open_link(path, c::DELETE | c::FILE_LIST_DIRECTORY)?; // Test if the file is not a directory or a symlink to a directory. @@ -1241,14 +1295,14 @@ fn remove_dir_all_iterative(f: &File, delete: fn(&File) -> io::Result<()>) -> io Ok(()) } -pub fn readlink(path: &Path) -> io::Result { +pub fn readlink(path: &NativePath) -> io::Result { // Open the link with no access mode, instead of generic read. // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and Settings", so // this is needed for a common case. let mut opts = OpenOptions::new(); opts.access_mode(0); opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); - let file = File::open(path, &opts)?; + let file = File::open_native(path, &opts)?; file.readlink() } @@ -1301,7 +1355,7 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> { )); } -pub fn stat(path: &Path) -> io::Result { +pub fn stat(path: &NativePath) -> io::Result { match metadata(path, ReparsePoint::Follow) { Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => { if let Ok(attrs) = lstat(path) { @@ -1315,7 +1369,7 @@ pub fn stat(path: &Path) -> io::Result { } } -pub fn lstat(path: &Path) -> io::Result { +pub fn lstat(path: &NativePath) -> io::Result { metadata(path, ReparsePoint::Open) } @@ -1331,7 +1385,7 @@ impl ReparsePoint { } } -fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result { +fn metadata(path: &NativePath, reparse: ReparsePoint) -> io::Result { let mut opts = OpenOptions::new(); // No read or write permissions are necessary opts.access_mode(0); @@ -1340,7 +1394,7 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result { // Attempt to open the file normally. // If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileW`. // If the fallback fails for any reason we return the original error. - match File::open(path, &opts) { + match File::open_native(path, &opts) { Ok(file) => file.file_attr(), Err(e) if [Some(c::ERROR_SHARING_VIOLATION as _), Some(c::ERROR_ACCESS_DENIED as _)] @@ -1353,8 +1407,6 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result { // However, there are special system files, such as // `C:\hiberfil.sys`, that are locked in a way that denies even that. unsafe { - let path = maybe_verbatim(path)?; - // `FindFirstFileW` accepts wildcard file names. // Fortunately wildcards are not valid file names and // `ERROR_SHARING_VIOLATION` means the file exists (but is locked) @@ -1403,13 +1455,13 @@ fn get_path(f: &File) -> io::Result { ) } -pub fn canonicalize(p: &Path) -> io::Result { +pub fn canonicalize(p: &NativePath) -> io::Result { let mut opts = OpenOptions::new(); // No read or write permissions are necessary opts.access_mode(0); // This flag is so we can open directories too opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); - let f = File::open(p, &opts)?; + let f = File::open_native(p, &opts)?; get_path(&f) } @@ -1467,7 +1519,7 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.write(true); opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); - let f = File::open(junction, &opts)?; + let f = File::open_native(&maybe_verbatim(junction)?, &opts)?; let h = f.as_inner().as_raw_handle(); unsafe { let mut data = @@ -1525,14 +1577,14 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> { } // Try to see if a file exists but, unlike `exists`, report I/O errors. -pub fn try_exists(path: &Path) -> io::Result { +pub fn try_exists(path: &NativePath) -> io::Result { // Open the file to ensure any symlinks are followed to their target. let mut opts = OpenOptions::new(); // No read, write, etc access rights are needed. opts.access_mode(0); // Backup semantics enables opening directories as well as files. opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); - match File::open(path, &opts) { + match File::open_native(path, &opts) { Err(e) => match e.kind() { // The file definitely does not exist io::ErrorKind::NotFound => Ok(false), diff --git a/library/std/src/sys/pal/windows/pipe.rs b/library/std/src/sys/pal/windows/pipe.rs index 013f588676ae8..99b5a91960ac8 100644 --- a/library/std/src/sys/pal/windows/pipe.rs +++ b/library/std/src/sys/pal/windows/pipe.rs @@ -3,7 +3,6 @@ use crate::os::windows::prelude::*; use crate::ffi::OsStr; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; use crate::mem; -use crate::path::Path; use crate::ptr; use crate::slice; use crate::sync::atomic::AtomicUsize; @@ -12,6 +11,7 @@ use crate::sys::c; use crate::sys::fs::{File, OpenOptions}; use crate::sys::handle::Handle; use crate::sys::hashmap_random_keys; +use crate::sys::path::NativePathBuf; use crate::sys_common::{FromInner, IntoInner}; //////////////////////////////////////////////////////////////////////////////// @@ -161,7 +161,8 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res bInheritHandle: their_handle_inheritable as i32, }; opts.security_attributes(&mut sa); - let theirs = File::open(Path::new(&name), &opts)?; + let path = NativePathBuf::from_os_str(&name)?; + let theirs = File::open_native(&path, &opts)?; let theirs = AnonPipe { inner: theirs.into_inner() }; Ok(Pipes { diff --git a/library/std/src/sys/pal/windows/process.rs b/library/std/src/sys/pal/windows/process.rs index e4ab2ca7da1ce..c3ecf87024290 100644 --- a/library/std/src/sys/pal/windows/process.rs +++ b/library/std/src/sys/pal/windows/process.rs @@ -23,7 +23,7 @@ use crate::sys::c::{self, NonZeroDWORD, EXIT_FAILURE, EXIT_SUCCESS}; use crate::sys::cvt; use crate::sys::fs::{File, OpenOptions}; use crate::sys::handle::Handle; -use crate::sys::path; +use crate::sys::path::{self, NativePathBuf}; use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; use crate::sys_common::process::{CommandEnv, CommandEnvs}; @@ -597,7 +597,8 @@ impl Stdio { opts.read(stdio_id == c::STD_INPUT_HANDLE); opts.write(stdio_id != c::STD_INPUT_HANDLE); opts.security_attributes(&mut sa); - File::open(Path::new(r"\\.\NUL"), &opts).map(|file| file.into_inner()) + File::open_native(&NativePathBuf::from_os_str(r"\\.\NUL")?, &opts) + .map(|file| file.into_inner()) } } } diff --git a/library/std/src/sys/path/windows.rs b/library/std/src/sys/path/windows.rs index cebc791023115..91269057cb51b 100644 --- a/library/std/src/sys/path/windows.rs +++ b/library/std/src/sys/path/windows.rs @@ -1,6 +1,6 @@ use crate::ffi::{OsStr, OsString}; use crate::io; -use crate::path::{Path, PathBuf, Prefix}; +use crate::path::{AsPath, Path, PathBuf, Prefix}; use crate::ptr; use crate::sys::pal::{c, fill_utf16_buf, os2path, to_u16s}; @@ -10,6 +10,59 @@ mod tests; pub const MAIN_SEP_STR: &str = "\\"; pub const MAIN_SEP: char = '\\'; +pub(crate) struct NativePathBuf(pub Vec); +impl NativePathBuf { + pub fn from_os_str>(path: S) -> io::Result { + to_u16s(path).map(Self) + } +} +impl core::ops::Deref for NativePathBuf { + type Target = NativePath; + fn deref(&self) -> &Self::Target { + unsafe { NativePath::new_unchecked(&self.0) } + } +} + +/// A null-terminated `[u16]` string. +#[unstable(feature = "fs_native_path_internals", issue = "none")] +#[repr(transparent)] +#[derive(Debug)] +pub struct NativePath(pub [u16]); +impl NativePath { + pub(crate) unsafe fn new_unchecked(native: &[u16]) -> &Self { + unsafe { &*(native as *const [u16] as *const Self) } + } + pub(crate) fn as_ptr(&self) -> *const u16 { + self.0.as_ptr() + } +} + +#[unstable(feature = "fs_native_path_internals", issue = "none")] +impl> AsPath for P { + #[doc(hidden)] + fn with_path io::Result>(self, f: F) -> io::Result { + f(self.as_ref()) + } + #[doc(hidden)] + fn with_native_path io::Result>(self, f: F) -> io::Result { + let path = maybe_verbatim(self.as_ref())?; + f(&path) + } +} + +#[unstable(feature = "fs_native_path_internals", issue = "none")] +impl AsPath for &crate::path::NativePath { + #[doc(hidden)] + fn with_path io::Result>(self, f: F) -> io::Result { + let path = os2path(&self.0.0[..self.0.0.len().saturating_sub(1)]); + f(&path) + } + #[doc(hidden)] + fn with_native_path io::Result>(self, f: F) -> io::Result { + f(&self.0) + } +} + #[inline] pub fn is_sep_byte(b: u8) -> bool { b == b'/' || b == b'\\' @@ -213,9 +266,9 @@ fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) { /// Returns a UTF-16 encoded path capable of bypassing the legacy `MAX_PATH` limits. /// /// This path may or may not have a verbatim prefix. -pub(crate) fn maybe_verbatim(path: &Path) -> io::Result> { +pub(crate) fn maybe_verbatim(path: &Path) -> io::Result { let path = to_u16s(path)?; - get_long_path(path, true) + get_long_path(path, true).map(NativePathBuf) } /// Get a normalized absolute path that can bypass path length limits. diff --git a/library/std/src/sys/path/windows/tests.rs b/library/std/src/sys/path/windows/tests.rs index 623c6236166da..05c459536d636 100644 --- a/library/std/src/sys/path/windows/tests.rs +++ b/library/std/src/sys/path/windows/tests.rs @@ -38,7 +38,7 @@ fn verbatim() { use crate::path::Path; fn check(path: &str, expected: &str) { let verbatim = maybe_verbatim(Path::new(path)).unwrap(); - let verbatim = String::from_utf16_lossy(verbatim.strip_suffix(&[0]).unwrap()); + let verbatim = String::from_utf16_lossy(verbatim.0.strip_suffix(&[0]).unwrap()); assert_eq!(&verbatim, expected, "{}", path); } From abc0c6360a589e6e9488c740437635206beb6525 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 10 Mar 2023 10:25:19 +0000 Subject: [PATCH 3/6] Unix implementation of `NativePath` --- library/std/src/os/unix/ffi/mod.rs | 19 ++++++ library/std/src/os/unix/fs.rs | 6 +- library/std/src/sys/pal/unix/fs.rs | 60 +++++++++++++++++-- .../sys/pal/unix/process/process_common.rs | 2 +- library/std/src/sys/path/cstr_native.rs | 35 +++++++++++ library/std/src/sys/path/mod.rs | 7 +++ 6 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 library/std/src/sys/path/cstr_native.rs diff --git a/library/std/src/os/unix/ffi/mod.rs b/library/std/src/os/unix/ffi/mod.rs index 5b49f50763d74..45d07d2a98347 100644 --- a/library/std/src/os/unix/ffi/mod.rs +++ b/library/std/src/os/unix/ffi/mod.rs @@ -38,5 +38,24 @@ mod os_str; +use crate::ffi::CStr; +use crate::path::NativePath; + #[stable(feature = "rust1", since = "1.0.0")] pub use self::os_str::{OsStrExt, OsStringExt}; + +#[unstable(feature = "fs_native_path", issue = "108979")] +pub trait NativePathExt: crate::sealed::Sealed { + fn from_cstr(cstr: &CStr) -> &NativePath; + fn into_cstr(&self) -> &CStr; +} + +#[unstable(feature = "fs_native_path", issue = "108979")] +impl NativePathExt for NativePath { + fn from_cstr(cstr: &CStr) -> &NativePath { + unsafe { &*(cstr as *const CStr as *const NativePath) } + } + fn into_cstr(&self) -> &CStr { + unsafe { &*(self as *const Self as *const CStr) } + } +} diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index 058e9b90cc7a1..a065ed60ce13a 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -8,7 +8,7 @@ use super::platform::fs::MetadataExt as _; use crate::fs::{self, OpenOptions, Permissions}; use crate::io; use crate::os::unix::io::{AsFd, AsRawFd}; -use crate::path::Path; +use crate::path::{AsPath, Path}; use crate::sys; use crate::sys_common::{AsInner, AsInnerMut, FromInner}; // Used for `File::read` on intra-doc links @@ -954,8 +954,8 @@ impl DirEntryExt2 for fs::DirEntry { /// } /// ``` #[stable(feature = "symlink", since = "1.1.0")] -pub fn symlink, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::symlink(original.as_ref(), link.as_ref()) +pub fn symlink(original: P, link: Q) -> io::Result<()> { + sys::fs::fs_imp::soft_link(original, link) } /// Unix-specific extensions to [`fs::DirBuilder`]. diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs index 422a99380cc59..efdd41865e9c8 100644 --- a/library/std/src/sys/pal/unix/fs.rs +++ b/library/std/src/sys/pal/unix/fs.rs @@ -109,6 +109,60 @@ use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, st pub use crate::sys_common::fs::try_exists; +pub(crate) mod fs_imp { + use crate::io; + use crate::path::AsPath; + use crate::path::PathBuf; + use crate::sys::fs; + pub(crate) use crate::sys::fs::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + + pub(crate) fn remove_file(path: P) -> io::Result<()> { + path.with_path(fs::unlink) + } + pub(crate) fn symlink_metadata(path: P) -> io::Result { + path.with_path(|path| fs::lstat(path)) + } + pub(crate) fn metadata(path: P) -> io::Result { + path.with_path(|path| fs::stat(path)) + } + pub(crate) fn rename(from: P, to: Q) -> io::Result<()> { + from.with_path(|from| to.with_path(|to| fs::rename(from, to))) + } + pub(crate) fn hard_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| fs::link(original, link))) + } + pub(crate) fn soft_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| fs::symlink(original, link))) + } + pub(crate) fn remove_dir(path: P) -> io::Result<()> { + path.with_path(fs::rmdir) + } + pub(crate) fn read_dir(path: P) -> io::Result { + path.with_path(fs::readdir) + } + pub(crate) fn set_permissions(path: P, perms: FilePermissions) -> io::Result<()> { + path.with_path(|path| fs::set_perm(path, perms)) + } + pub(crate) fn copy(from: P, to: Q) -> io::Result { + from.with_path(|from| to.with_path(|to| fs::copy(from, to))) + } + pub(crate) fn canonicalize(path: P) -> io::Result { + path.with_path(fs::canonicalize) + } + pub(crate) fn remove_dir_all(path: P) -> io::Result<()> { + path.with_path(fs::remove_dir_all) + } + pub(crate) fn read_link(path: P) -> io::Result { + path.with_path(fs::readlink) + } + pub(crate) fn try_exists(path: P) -> io::Result { + path.with_path(fs::try_exists) + } +} + pub struct File(FileDesc); // FIXME: This should be available on Linux with all `target_env`. @@ -1117,11 +1171,7 @@ impl OpenOptions { } impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - run_path_with_cstr(path, &|path| File::open_c(path, opts)) - } - - pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { + pub fn open_native(path: &CStr, opts: &OpenOptions) -> io::Result { let flags = libc::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? diff --git a/library/std/src/sys/pal/unix/process/process_common.rs b/library/std/src/sys/pal/unix/process/process_common.rs index f615e8086dccf..7b43ad66589a9 100644 --- a/library/std/src/sys/pal/unix/process/process_common.rs +++ b/library/std/src/sys/pal/unix/process/process_common.rs @@ -481,7 +481,7 @@ impl Stdio { let mut opts = OpenOptions::new(); opts.read(readable); opts.write(!readable); - let fd = File::open_c(DEV_NULL, &opts)?; + let fd = File::open_native(DEV_NULL, &opts)?; Ok((ChildStdio::Owned(fd.into_inner()), None)) } diff --git a/library/std/src/sys/path/cstr_native.rs b/library/std/src/sys/path/cstr_native.rs new file mode 100644 index 0000000000000..f983bd1434277 --- /dev/null +++ b/library/std/src/sys/path/cstr_native.rs @@ -0,0 +1,35 @@ +use crate::ffi::CStr; +use crate::io; +use crate::mem; +use crate::path::{AsPath, Path}; +use crate::sys::common::small_c_string::run_path_with_cstr; + +pub type NativePath = CStr; + +#[unstable(feature = "fs_native_path_internals", issue = "none")] +impl> AsPath for P { + #[doc(hidden)] + fn with_path io::Result>(self, f: F) -> io::Result { + f(self.as_ref()) + } + #[doc(hidden)] + fn with_native_path io::Result>(self, f: F) -> io::Result { + run_path_with_cstr(self.as_ref(), &f) + } +} + +#[unstable(feature = "fs_native_path_internals", issue = "none")] +impl AsPath for &crate::path::NativePath { + #[doc(hidden)] + fn with_path io::Result>(self, f: F) -> io::Result { + // SAFETY: OsStr is a byte slice on platforms with CStr paths. + // Note: We can't use os::unix::OsStrExt because that isn't necessarily + // available for all platforms that use CStr paths. + let osstr: &Path = unsafe { mem::transmute(self.0.to_bytes()) }; + f(osstr) + } + #[doc(hidden)] + fn with_native_path io::Result>(self, f: F) -> io::Result { + f(&self.0) + } +} diff --git a/library/std/src/sys/path/mod.rs b/library/std/src/sys/path/mod.rs index 24a94ec782824..000e706db70f9 100644 --- a/library/std/src/sys/path/mod.rs +++ b/library/std/src/sys/path/mod.rs @@ -1,3 +1,10 @@ +cfg_if::cfg_if! { + if #[cfg(not(target_os = "windows"))] { + mod cstr_native; + pub use cstr_native::NativePath; + } +} + cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { mod windows; From 64ff8e29bf2468fbacda4e40f60b11193c87c2e1 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Mon, 11 Dec 2023 00:36:25 +0000 Subject: [PATCH 4/6] Various OS implementations of `NativePath` --- library/std/src/sys/pal/hermit/fs.rs | 110 ++++++++++----------- library/std/src/sys/pal/solid/fs.rs | 56 ++++++++++- library/std/src/sys/pal/unsupported/fs.rs | 114 +++++++++++----------- 3 files changed, 164 insertions(+), 116 deletions(-) diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index d4da53fd3df9e..ead14ed4cbb38 100644 --- a/library/std/src/sys/pal/hermit/fs.rs +++ b/library/std/src/sys/pal/hermit/fs.rs @@ -7,15 +7,66 @@ use crate::io::{self, Error, ErrorKind}; use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::{Path, PathBuf}; -use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::cvt; use crate::sys::time::SystemTime; use crate::sys::unsupported; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; -pub use crate::sys_common::fs::{copy, try_exists}; //pub use crate::sys_common::fs::remove_dir_all; +pub(crate) mod fs_imp { + pub(crate) use super::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + use crate::io; + use crate::path::{AsPath, PathBuf}; + use crate::sys::unsupported; + + pub(crate) fn remove_file(_path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn symlink_metadata(_path: P) -> io::Result { + unsupported() + } + pub(crate) fn metadata(_path: P) -> io::Result { + unsupported() + } + pub(crate) fn rename(_from: P, _to: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn hard_link(_original: P, _link: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn soft_link(_original: P, _link: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn remove_dir(_path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn read_dir(_path: P) -> io::Result { + unsupported() + } + pub(crate) fn set_permissions(_path: P, _perms: FilePermissions) -> io::Result<()> { + unsupported() + } + pub(crate) fn copy(_from: P, _to: Q) -> io::Result { + unsupported() + } + pub(crate) fn canonicalize(_path: P) -> io::Result { + unsupported() + } + pub(crate) fn remove_dir_all(_path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn read_link(_path: P) -> io::Result { + unsupported() + } + pub(crate) fn try_exists(_path: P) -> io::Result { + unsupported() + } +} + #[derive(Debug)] pub struct File(FileDesc); @@ -268,11 +319,7 @@ impl OpenOptions { } impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - run_path_with_cstr(path, &|path| File::open_c(&path, opts)) - } - - pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { + pub fn open_native(path: &CStr, opts: &OpenOptions) -> io::Result { let mut flags = opts.get_access_mode()?; flags = flags | opts.get_creation_mode()?; @@ -415,52 +462,3 @@ impl FromRawFd for File { Self(FromRawFd::from_raw_fd(raw_fd)) } } - -pub fn readdir(_p: &Path) -> io::Result { - unsupported() -} - -pub fn unlink(path: &Path) -> io::Result<()> { - run_path_with_cstr(path, &|path| cvt(unsafe { abi::unlink(path.as_ptr()) }).map(|_| ())) -} - -pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { - unsupported() -} - -pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { - match perm.0 {} -} - -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() -} - -pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - //unsupported() - Ok(()) -} - -pub fn readlink(_p: &Path) -> io::Result { - unsupported() -} - -pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> { - unsupported() -} - -pub fn link(_original: &Path, _link: &Path) -> io::Result<()> { - unsupported() -} - -pub fn stat(_p: &Path) -> io::Result { - unsupported() -} - -pub fn lstat(_p: &Path) -> io::Result { - unsupported() -} - -pub fn canonicalize(_p: &Path) -> io::Result { - unsupported() -} diff --git a/library/std/src/sys/pal/solid/fs.rs b/library/std/src/sys/pal/solid/fs.rs index a6c1336109ad7..92ec2f024c6a7 100644 --- a/library/std/src/sys/pal/solid/fs.rs +++ b/library/std/src/sys/pal/solid/fs.rs @@ -14,6 +14,58 @@ use crate::{ pub use crate::sys_common::fs::try_exists; +pub(crate) mod fs_imp { + pub(crate) use super::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + use crate::io; + use crate::path::{AsPath, PathBuf}; + + pub(crate) fn remove_file(path: P) -> io::Result<()> { + path.with_path(super::unlink) + } + pub(crate) fn symlink_metadata(path: P) -> io::Result { + path.with_path(|path| super::lstat(path)) + } + pub(crate) fn metadata(path: P) -> io::Result { + path.with_path(|path| super::stat(path)) + } + pub(crate) fn rename(from: P, to: Q) -> io::Result<()> { + from.with_path(|from| to.with_path(|to| super::rename(from, to))) + } + pub(crate) fn hard_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| super::link(original, link))) + } + pub(crate) fn soft_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_path(|link| super::symlink(original, link))) + } + pub(crate) fn remove_dir(path: P) -> io::Result<()> { + path.with_path(super::rmdir) + } + pub(crate) fn read_dir(path: P) -> io::Result { + path.with_path(super::readdir) + } + pub(crate) fn set_permissions(path: P, perms: FilePermissions) -> io::Result<()> { + path.with_path(|path| super::set_perm(path, perms)) + } + pub(crate) fn copy(from: P, to: Q) -> io::Result { + from.with_path(|from| to.with_path(|to| super::copy(from, to))) + } + pub(crate) fn canonicalize(path: P) -> io::Result { + path.with_path(super::canonicalize) + } + pub(crate) fn remove_dir_all(path: P) -> io::Result<()> { + path.with_path(super::remove_dir_all) + } + pub(crate) fn read_link(path: P) -> io::Result { + path.with_path(super::readlink) + } + pub(crate) fn try_exists(path: P) -> io::Result { + path.with_path(super::try_exists) + } +} + /// A file descriptor. #[derive(Clone, Copy)] #[rustc_layout_scalar_valid_range_start(0)] @@ -324,7 +376,7 @@ fn cstr(path: &Path) -> io::Result { } impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + pub fn open_native(path: &CStr, opts: &OpenOptions) -> io::Result { let flags = opts.get_access_mode()? | opts.get_creation_mode()? | (opts.custom_flags as c_int & !abi::O_ACCMODE); @@ -332,7 +384,7 @@ impl File { let mut fd = MaybeUninit::uninit(); error::SolidError::err_if_negative(abi::SOLID_FS_Open( fd.as_mut_ptr(), - cstr(path)?.as_ptr(), + path.as_ptr(), flags, )) .map_err(|e| e.as_io_error())?; diff --git a/library/std/src/sys/pal/unsupported/fs.rs b/library/std/src/sys/pal/unsupported/fs.rs index 6ac1b5d2bcfca..f0ec12c394516 100644 --- a/library/std/src/sys/pal/unsupported/fs.rs +++ b/library/std/src/sys/pal/unsupported/fs.rs @@ -1,4 +1,4 @@ -use crate::ffi::OsString; +use crate::ffi::{CStr, OsString}; use crate::fmt; use crate::hash::{Hash, Hasher}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; @@ -14,6 +14,60 @@ pub struct ReadDir(!); pub struct DirEntry(!); +#[allow(unused_variables)] +pub(crate) mod fs_imp { + use super::unsupported; + pub(crate) use super::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + use crate::io; + use crate::path::{AsPath, PathBuf}; + + pub(crate) fn remove_file(path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn symlink_metadata(path: P) -> io::Result { + unsupported() + } + pub(crate) fn metadata(path: P) -> io::Result { + unsupported() + } + pub(crate) fn rename(from: P, to: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn hard_link(original: P, link: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn soft_link(original: P, link: Q) -> io::Result<()> { + unsupported() + } + pub(crate) fn remove_dir(path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn read_dir(path: P) -> io::Result { + unsupported() + } + pub(crate) fn set_permissions(path: P, perms: FilePermissions) -> io::Result<()> { + unsupported() + } + pub(crate) fn copy(from: P, to: Q) -> io::Result { + unsupported() + } + pub(crate) fn canonicalize(path: P) -> io::Result { + unsupported() + } + pub(crate) fn remove_dir_all(path: P) -> io::Result<()> { + unsupported() + } + pub(crate) fn read_link(path: P) -> io::Result { + unsupported() + } + pub(crate) fn try_exists(path: P) -> io::Result { + unsupported() + } +} + #[derive(Clone, Debug)] pub struct OpenOptions {} @@ -182,7 +236,7 @@ impl OpenOptions { } impl File { - pub fn open(_path: &Path, _opts: &OpenOptions) -> io::Result { + pub fn open_native(_path: &CStr, _opts: &OpenOptions) -> io::Result { unsupported() } @@ -266,59 +320,3 @@ impl fmt::Debug for File { self.0 } } - -pub fn readdir(_p: &Path) -> io::Result { - unsupported() -} - -pub fn unlink(_p: &Path) -> io::Result<()> { - unsupported() -} - -pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { - unsupported() -} - -pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { - match perm.0 {} -} - -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() -} - -pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - unsupported() -} - -pub fn try_exists(_path: &Path) -> io::Result { - unsupported() -} - -pub fn readlink(_p: &Path) -> io::Result { - unsupported() -} - -pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> { - unsupported() -} - -pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> { - unsupported() -} - -pub fn stat(_p: &Path) -> io::Result { - unsupported() -} - -pub fn lstat(_p: &Path) -> io::Result { - unsupported() -} - -pub fn canonicalize(_p: &Path) -> io::Result { - unsupported() -} - -pub fn copy(_from: &Path, _to: &Path) -> io::Result { - unsupported() -} From bd3ca776d7671e81722faeb32e7865f1457911f5 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 8 Mar 2024 05:48:56 +0000 Subject: [PATCH 5/6] WASI implementation of `NativePath` --- library/std/src/os/wasi/fs.rs | 4 +- library/std/src/sys/pal/wasi/fs.rs | 169 +++++++++++++++++++---------- 2 files changed, 113 insertions(+), 60 deletions(-) diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 4525c3aa91477..e8e79f4193743 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -563,7 +563,9 @@ pub fn symlink, U: AsRef>( /// This is a convenience API similar to `std::os::unix::fs::symlink` and /// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`. pub fn symlink_path, U: AsRef>(old_path: P, new_path: U) -> io::Result<()> { - crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref()) + crate::sys::common::small_c_string::run_path_with_cstr(new_path.as_ref(), &|new_path| { + crate::sys::fs::symlink(old_path.as_ref(), new_path) + }) } fn osstr2str(f: &OsStr) -> io::Result<&str> { diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs index 529b82e019893..3db22df9021dd 100644 --- a/library/std/src/sys/pal/wasi/fs.rs +++ b/library/std/src/sys/pal/wasi/fs.rs @@ -17,7 +17,58 @@ use crate::sys::time::SystemTime; use crate::sys::unsupported; use crate::sys_common::{AsInner, FromInner, IntoInner}; -pub use crate::sys_common::fs::try_exists; +pub(crate) mod fs_imp { + pub(crate) use super::{ + DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, + ReadDir, + }; + use crate::io; + use crate::path::AsPath; + use crate::path::PathBuf; + + pub(crate) fn remove_file(path: P) -> io::Result<()> { + path.with_native_path(super::unlink) + } + pub(crate) fn symlink_metadata(path: P) -> io::Result { + path.with_native_path(|path| super::lstat(path)) + } + pub(crate) fn metadata(path: P) -> io::Result { + path.with_native_path(|path| super::stat(path)) + } + pub(crate) fn rename(from: P, to: Q) -> io::Result<()> { + to.with_path(|to| from.with_native_path(|from| super::rename(from, to))) + } + pub(crate) fn hard_link(original: P, link: Q) -> io::Result<()> { + link.with_path(|link| original.with_native_path(|original| super::link(original, link))) + } + pub(crate) fn soft_link(original: P, link: Q) -> io::Result<()> { + original.with_path(|original| link.with_native_path(|link| super::symlink(original, link))) + } + pub(crate) fn remove_dir(path: P) -> io::Result<()> { + path.with_native_path(super::rmdir) + } + pub(crate) fn read_dir(path: P) -> io::Result { + path.with_path(super::readdir) + } + pub(crate) fn set_permissions(path: P, perms: FilePermissions) -> io::Result<()> { + path.with_path(|path| super::set_perm(path, perms)) + } + pub(crate) fn copy(from: P, to: Q) -> io::Result { + from.with_path(|from| to.with_path(|to| super::copy(from, to))) + } + pub(crate) fn canonicalize(path: P) -> io::Result { + path.with_path(super::canonicalize) + } + pub(crate) fn remove_dir_all(path: P) -> io::Result<()> { + path.with_path(super::remove_dir_all) + } + pub(crate) fn read_link(path: P) -> io::Result { + path.with_native_path(super::readlink) + } + pub(crate) fn try_exists(path: P) -> io::Result { + path.with_path(crate::sys_common::fs::try_exists) + } +} pub struct File { fd: WasiFd, @@ -398,7 +449,7 @@ impl OpenOptions { } impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + pub fn open_native(path: &CStr, opts: &OpenOptions) -> io::Result { let (dir, file) = open_parent(path)?; open_at(&dir, &file, opts) } @@ -548,8 +599,10 @@ impl DirBuilder { } pub fn mkdir(&self, p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.create_directory(osstr2str(file.as_ref())?) + run_path_with_cstr(p, &|p| { + let (dir, file) = open_parent(p)?; + dir.create_directory(osstr2str(file.as_ref())?) + }) } } @@ -563,18 +616,18 @@ pub fn readdir(p: &Path) -> io::Result { let mut opts = OpenOptions::new(); opts.directory(true); opts.read(true); - let dir = File::open(p, &opts)?; + let dir = run_path_with_cstr(p, &|p| File::open_native(p, &opts))?; Ok(ReadDir::new(dir, p.to_path_buf())) } -pub fn unlink(p: &Path) -> io::Result<()> { +pub fn unlink(p: &CStr) -> io::Result<()> { let (dir, file) = open_parent(p)?; dir.unlink_file(osstr2str(file.as_ref())?) } -pub fn rename(old: &Path, new: &Path) -> io::Result<()> { +pub fn rename(old: &CStr, new: &Path) -> io::Result<()> { let (old, old_file) = open_parent(old)?; - let (new, new_file) = open_parent(new)?; + let (new, new_file) = run_path_with_cstr(new, &|new| open_parent(new))?; old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) } @@ -584,12 +637,12 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } -pub fn rmdir(p: &Path) -> io::Result<()> { +pub fn rmdir(p: &CStr) -> io::Result<()> { let (dir, file) = open_parent(p)?; dir.remove_directory(osstr2str(file.as_ref())?) } -pub fn readlink(p: &Path) -> io::Result { +pub fn readlink(p: &CStr) -> io::Result { let (dir, file) = open_parent(p)?; read_link(&dir, &file) } @@ -625,24 +678,24 @@ fn read_link(fd: &WasiFd, file: &Path) -> io::Result { } } -pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { +pub fn symlink(original: &Path, link: &CStr) -> io::Result<()> { let (link, link_file) = open_parent(link)?; link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) } -pub fn link(original: &Path, link: &Path) -> io::Result<()> { +pub fn link(original: &CStr, link: &Path) -> io::Result<()> { let (original, original_file) = open_parent(original)?; - let (link, link_file) = open_parent(link)?; + let (link, link_file) = run_path_with_cstr(link, &|link| open_parent(link))?; // Pass 0 as the flags argument, meaning don't follow symlinks. original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?) } -pub fn stat(p: &Path) -> io::Result { +pub fn stat(p: &CStr) -> io::Result { let (dir, file) = open_parent(p)?; metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) } -pub fn lstat(p: &Path) -> io::Result { +pub fn lstat(p: &CStr) -> io::Result { let (dir, file) = open_parent(p)?; metadata_at(&dir, 0, &file) } @@ -697,53 +750,51 @@ fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result { /// /// Note that this can fail if `p` doesn't look like it can be opened relative /// to any pre-opened file descriptor. -fn open_parent(p: &Path) -> io::Result<(ManuallyDrop, PathBuf)> { - run_path_with_cstr(p, &|p| { - let mut buf = Vec::::with_capacity(512); - loop { - unsafe { - let mut relative_path = buf.as_ptr().cast(); - let mut abs_prefix = ptr::null(); - let fd = __wasilibc_find_relpath( - p.as_ptr(), - &mut abs_prefix, - &mut relative_path, - buf.capacity(), - ); - if fd == -1 { - if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) { - // Trigger the internal buffer resizing logic of `Vec` by requiring - // more space than the current capacity. - let cap = buf.capacity(); - buf.set_len(cap); - buf.reserve(1); - continue; - } - let msg = format!( - "failed to find a pre-opened file descriptor \ - through which {:?} could be opened", - p - ); - return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); +fn open_parent(p: &CStr) -> io::Result<(ManuallyDrop, PathBuf)> { + let mut buf = Vec::::with_capacity(512); + loop { + unsafe { + let mut relative_path = buf.as_ptr().cast(); + let mut abs_prefix = ptr::null(); + let fd = __wasilibc_find_relpath( + p.as_ptr(), + &mut abs_prefix, + &mut relative_path, + buf.capacity(), + ); + if fd == -1 { + if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) { + // Trigger the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. + let cap = buf.capacity(); + buf.set_len(cap); + buf.reserve(1); + continue; } - let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); - - return Ok(( - ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), - PathBuf::from(OsString::from_vec(relative)), - )); + let msg = format!( + "failed to find a pre-opened file descriptor \ + through which {:?} could be opened", + p + ); + return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); } - } + let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); - extern "C" { - pub fn __wasilibc_find_relpath( - path: *const libc::c_char, - abs_prefix: *mut *const libc::c_char, - relative_path: *mut *const libc::c_char, - relative_path_len: libc::size_t, - ) -> libc::c_int; + return Ok(( + ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), + PathBuf::from(OsString::from_vec(relative)), + )); } - }) + } + + extern "C" { + pub fn __wasilibc_find_relpath( + path: *const libc::c_char, + abs_prefix: *mut *const libc::c_char, + relative_path: *mut *const libc::c_char, + relative_path_len: libc::size_t, + ) -> libc::c_int; + } } pub fn osstr2str(f: &OsStr) -> io::Result<&str> { @@ -761,7 +812,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { } pub fn remove_dir_all(path: &Path) -> io::Result<()> { - let (parent, path) = open_parent(path)?; + let (parent, path) = run_path_with_cstr(path, &|path| open_parent(path))?; remove_dir_all_recursive(&parent, &path) } From 80261c4681c09827a78aa2edc057d6bdc3524a48 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 10 Mar 2023 10:26:12 +0000 Subject: [PATCH 6/6] Bless `NativePath` --- .../tests/fail/shims/fs/isolated_file.stderr | 16 ++++++++-------- tests/ui/async-await/issue-72442.stderr | 6 ++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr index 1f08649428a4c..1b0e74912e61d 100644 --- a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr +++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr @@ -8,15 +8,15 @@ LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode a = help: or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning = note: BACKTRACE: = note: inside closure at RUSTLIB/std/src/sys/pal/PLATFORM/fs.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::cvt_r::` at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::fs::File::open_c` at RUSTLIB/std/src/sys/pal/PLATFORM/fs.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/sys/pal/PLATFORM/fs.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::small_c_string::run_path_with_cstr::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC - = note: inside `std::sys::pal::PLATFORM::fs::File::open` at RUSTLIB/std/src/sys/pal/PLATFORM/fs.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::cvt_r::` at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::fs::File::open_native` at RUSTLIB/std/src/sys/pal/PLATFORM/fs.rs:LL:CC = note: inside `std::fs::OpenOptions::_open` at RUSTLIB/std/src/fs.rs:LL:CC - = note: inside `std::fs::OpenOptions::open::<&std::path::Path>` at RUSTLIB/std/src/fs.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/fs.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_path_with_cstr::` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::path::cstr_native::::with_native_path::::{closure#0}}>` at RUSTLIB/std/src/sys/path/cstr_native.rs:LL:CC + = note: inside `std::fs::OpenOptions::open::<&str>` at RUSTLIB/std/src/fs.rs:LL:CC = note: inside `std::fs::File::open::<&str>` at RUSTLIB/std/src/fs.rs:LL:CC note: inside `main` --> $DIR/isolated_file.rs:LL:CC diff --git a/tests/ui/async-await/issue-72442.stderr b/tests/ui/async-await/issue-72442.stderr index e68f7a299908f..f559b72f68330 100644 --- a/tests/ui/async-await/issue-72442.stderr +++ b/tests/ui/async-await/issue-72442.stderr @@ -1,11 +1,13 @@ -error[E0277]: the trait bound `Option<&str>: AsRef` is not satisfied +error[E0277]: the trait bound `Option<&str>: AsPath` is not satisfied --> $DIR/issue-72442.rs:12:36 | LL | let mut f = File::open(path.to_str())?; - | ---------- ^^^^^^^^^^^^^ the trait `AsRef` is not implemented for `Option<&str>` + | ---------- ^^^^^^^^^^^^^ the trait `AsRef` is not implemented for `Option<&str>`, which is required by `Option<&str>: AsPath` | | | required by a bound introduced by this call | + = help: the trait `AsPath` is implemented for `&NativePath` + = note: required for `Option<&str>` to implement `AsPath` note: required by a bound in `File::open` --> $SRC_DIR/std/src/fs.rs:LL:COL help: consider removing this method call, as the receiver has type `&Path` and `&Path: AsRef` trivially holds