1
Fork 0

Rollup merge of #138832 - ChrisDenton:with_native_path, r=joboet

Start using `with_native_path` in `std::sys::fs`

Ideally, each platform should use their own native path type internally. This will, for example, allow passing a `CStr` directly to `std::fs::File::open` and therefore avoid the need for allocating a new null-terminated C string.

However, doing that for every function and platform all at once makes for a large PR that is way too prone to breaking. So this PR does some minimal refactoring which should help progress towards that goal. The changes are Unix-only and even then I avoided functions that require more changes so that this PR is just moving things around.

r? joboet
This commit is contained in:
Matthias Krüger 2025-03-29 21:08:12 +01:00 committed by GitHub
commit fb6d10e13b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 191 additions and 125 deletions

View file

@ -2370,7 +2370,7 @@ impl AsInner<fs_imp::DirEntry> for DirEntry {
#[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")] #[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> { pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
fs_imp::unlink(path.as_ref()) fs_imp::remove_file(path.as_ref())
} }
/// Given a path, queries the file system to get information about a file, /// Given a path, queries the file system to get information about a file,
@ -2409,7 +2409,7 @@ pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[doc(alias = "stat")] #[doc(alias = "stat")]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> { pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
fs_imp::stat(path.as_ref()).map(Metadata) fs_imp::metadata(path.as_ref()).map(Metadata)
} }
/// Queries the metadata about a file without following symlinks. /// Queries the metadata about a file without following symlinks.
@ -2444,7 +2444,7 @@ pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
#[doc(alias = "lstat")] #[doc(alias = "lstat")]
#[stable(feature = "symlink_metadata", since = "1.1.0")] #[stable(feature = "symlink_metadata", since = "1.1.0")]
pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> { pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
fs_imp::lstat(path.as_ref()).map(Metadata) fs_imp::symlink_metadata(path.as_ref()).map(Metadata)
} }
/// Renames a file or directory to a new name, replacing the original file if /// Renames a file or directory to a new name, replacing the original file if
@ -2598,7 +2598,7 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
#[doc(alias = "CreateHardLink", alias = "linkat")] #[doc(alias = "CreateHardLink", alias = "linkat")]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> { pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
fs_imp::link(original.as_ref(), link.as_ref()) fs_imp::hard_link(original.as_ref(), link.as_ref())
} }
/// Creates a new symbolic link on the filesystem. /// Creates a new symbolic link on the filesystem.
@ -2664,7 +2664,7 @@ pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Re
/// ``` /// ```
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
fs_imp::readlink(path.as_ref()) fs_imp::read_link(path.as_ref())
} }
/// Returns the canonical, absolute form of a path with all intermediate /// Returns the canonical, absolute form of a path with all intermediate
@ -2840,7 +2840,7 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[doc(alias = "rmdir", alias = "RemoveDirectory")] #[doc(alias = "rmdir", alias = "RemoveDirectory")]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> { pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
fs_imp::rmdir(path.as_ref()) fs_imp::remove_dir(path.as_ref())
} }
/// Removes a directory at this path, after removing all its contents. Use /// Removes a directory at this path, after removing all its contents. Use
@ -2967,7 +2967,7 @@ pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[doc(alias = "ls", alias = "opendir", alias = "FindFirstFile", alias = "FindNextFile")] #[doc(alias = "ls", alias = "opendir", alias = "FindFirstFile", alias = "FindNextFile")]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> { pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
fs_imp::readdir(path.as_ref()).map(ReadDir) fs_imp::read_dir(path.as_ref()).map(ReadDir)
} }
/// Changes the permissions found on a file or a directory. /// Changes the permissions found on a file or a directory.
@ -3003,7 +3003,7 @@ pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
#[doc(alias = "chmod", alias = "SetFileAttributes")] #[doc(alias = "chmod", alias = "SetFileAttributes")]
#[stable(feature = "set_permissions", since = "1.1.0")] #[stable(feature = "set_permissions", since = "1.1.0")]
pub fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> { pub fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
fs_imp::set_perm(path.as_ref(), perm.0) fs_imp::set_permissions(path.as_ref(), perm.0)
} }
impl DirBuilder { impl DirBuilder {

View file

@ -1,28 +1,115 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
use crate::io;
use crate::path::{Path, PathBuf};
pub mod common; pub mod common;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_family = "unix")] { if #[cfg(target_family = "unix")] {
mod unix; mod unix;
pub use unix::*; use unix as imp;
pub use unix::{chown, fchown, lchown};
#[cfg(not(target_os = "fuchsia"))]
pub use unix::chroot;
pub(crate) use unix::debug_assert_fd_is_open;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) use unix::CachedFileMetadata;
use crate::sys::common::small_c_string::run_path_with_cstr as with_native_path;
} else if #[cfg(target_os = "windows")] { } else if #[cfg(target_os = "windows")] {
mod windows; mod windows;
pub use windows::*; use windows as imp;
pub use windows::{symlink_inner, junction_point};
} else if #[cfg(target_os = "hermit")] { } else if #[cfg(target_os = "hermit")] {
mod hermit; mod hermit;
pub use hermit::*; use hermit as imp;
} else if #[cfg(target_os = "solid_asp3")] { } else if #[cfg(target_os = "solid_asp3")] {
mod solid; mod solid;
pub use solid::*; use solid as imp;
} else if #[cfg(target_os = "uefi")] { } else if #[cfg(target_os = "uefi")] {
mod uefi; mod uefi;
pub use uefi::*; use uefi as imp;
} else if #[cfg(target_os = "wasi")] { } else if #[cfg(target_os = "wasi")] {
mod wasi; mod wasi;
pub use wasi::*; use wasi as imp;
} else { } else {
mod unsupported; mod unsupported;
pub use unsupported::*; use unsupported as imp;
} }
} }
// FIXME: Replace this with platform-specific path conversion functions.
#[cfg(not(target_family = "unix"))]
#[inline]
pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> io::Result<T> {
f(path)
}
pub use imp::{
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
ReadDir,
};
pub fn read_dir(path: &Path) -> io::Result<ReadDir> {
// FIXME: use with_native_path
imp::readdir(path)
}
pub fn remove_file(path: &Path) -> io::Result<()> {
with_native_path(path, &imp::unlink)
}
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
with_native_path(old, &|old| with_native_path(new, &|new| imp::rename(old, new)))
}
pub fn remove_dir(path: &Path) -> io::Result<()> {
with_native_path(path, &imp::rmdir)
}
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
// FIXME: use with_native_path
imp::remove_dir_all(path)
}
pub fn read_link(path: &Path) -> io::Result<PathBuf> {
with_native_path(path, &imp::readlink)
}
pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
with_native_path(original, &|original| {
with_native_path(link, &|link| imp::symlink(original, link))
})
}
pub fn hard_link(original: &Path, link: &Path) -> io::Result<()> {
with_native_path(original, &|original| {
with_native_path(link, &|link| imp::link(original, link))
})
}
pub fn metadata(path: &Path) -> io::Result<FileAttr> {
with_native_path(path, &imp::stat)
}
pub fn symlink_metadata(path: &Path) -> io::Result<FileAttr> {
with_native_path(path, &imp::lstat)
}
pub fn set_permissions(path: &Path, perm: FilePermissions) -> io::Result<()> {
with_native_path(path, &|path| imp::set_perm(path, perm.clone()))
}
pub fn canonicalize(path: &Path) -> io::Result<PathBuf> {
with_native_path(path, &imp::canonicalize)
}
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
// FIXME: use with_native_path
imp::copy(from, to)
}
pub fn exists(path: &Path) -> io::Result<bool> {
// FIXME: use with_native_path
imp::exists(path)
}

View file

@ -926,7 +926,7 @@ impl DirEntry {
miri miri
))] ))]
pub fn metadata(&self) -> io::Result<FileAttr> { pub fn metadata(&self) -> io::Result<FileAttr> {
lstat(&self.path()) run_path_with_cstr(&self.path(), &lstat)
} }
#[cfg(any( #[cfg(any(
@ -1657,7 +1657,7 @@ impl fmt::Debug for File {
fn get_path(fd: c_int) -> Option<PathBuf> { fn get_path(fd: c_int) -> Option<PathBuf> {
let mut p = PathBuf::from("/proc/self/fd"); let mut p = PathBuf::from("/proc/self/fd");
p.push(&fd.to_string()); p.push(&fd.to_string());
readlink(&p).ok() run_path_with_cstr(&p, &readlink).ok()
} }
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))] #[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
@ -1675,7 +1675,7 @@ impl fmt::Debug for File {
// fallback to procfs as last resort // fallback to procfs as last resort
let mut p = PathBuf::from("/proc/self/fd"); let mut p = PathBuf::from("/proc/self/fd");
p.push(&fd.to_string()); p.push(&fd.to_string());
return readlink(&p).ok(); return run_path_with_cstr(&p, &readlink).ok()
} else { } else {
return None; return None;
} }
@ -1830,127 +1830,106 @@ pub fn readdir(path: &Path) -> io::Result<ReadDir> {
} }
} }
pub fn unlink(p: &Path) -> io::Result<()> { pub fn unlink(p: &CStr) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ())) cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ())
} }
pub fn rename(old: &Path, new: &Path) -> io::Result<()> { pub fn rename(old: &CStr, new: &CStr) -> io::Result<()> {
run_path_with_cstr(old, &|old| { cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
run_path_with_cstr(new, &|new| {
cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
})
})
} }
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())) cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())
} }
pub fn rmdir(p: &Path) -> io::Result<()> { pub fn rmdir(p: &CStr) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())) cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
} }
pub fn readlink(p: &Path) -> io::Result<PathBuf> { pub fn readlink(c_path: &CStr) -> io::Result<PathBuf> {
run_path_with_cstr(p, &|c_path| { let p = c_path.as_ptr();
let p = c_path.as_ptr();
let mut buf = Vec::with_capacity(256); let mut buf = Vec::with_capacity(256);
loop { loop {
let buf_read = let buf_read =
cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize;
as usize;
unsafe { unsafe {
buf.set_len(buf_read); buf.set_len(buf_read);
}
if buf_read != buf.capacity() {
buf.shrink_to_fit();
return Ok(PathBuf::from(OsString::from_vec(buf)));
}
// Trigger the internal buffer resizing logic of `Vec` by requiring
// more space than the current capacity. The length is guaranteed to be
// the same as the capacity due to the if statement above.
buf.reserve(1);
}
})
}
pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
run_path_with_cstr(original, &|original| {
run_path_with_cstr(link, &|link| {
cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
})
})
}
pub fn link(original: &Path, link: &Path) -> io::Result<()> {
run_path_with_cstr(original, &|original| {
run_path_with_cstr(link, &|link| {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita", target_env = "nto70"))] {
// VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
// it implementation-defined whether `link` follows symlinks, so rely on the
// `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior.
// Android has `linkat` on newer versions, but we happen to know `link`
// always has the correct behavior, so it's here as well.
cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
} else {
// Where we can, use `linkat` instead of `link`; see the comment above
// this one for details on why.
cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
}
}
Ok(())
})
})
}
pub fn stat(p: &Path) -> io::Result<FileAttr> {
run_path_with_cstr(p, &|p| {
cfg_has_statx! {
if let Some(ret) = unsafe { try_statx(
libc::AT_FDCWD,
p.as_ptr(),
libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
} }
let mut stat: stat64 = unsafe { mem::zeroed() }; if buf_read != buf.capacity() {
cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?; buf.shrink_to_fit();
Ok(FileAttr::from_stat64(stat))
})
}
pub fn lstat(p: &Path) -> io::Result<FileAttr> { return Ok(PathBuf::from(OsString::from_vec(buf)));
run_path_with_cstr(p, &|p| {
cfg_has_statx! {
if let Some(ret) = unsafe { try_statx(
libc::AT_FDCWD,
p.as_ptr(),
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
} }
let mut stat: stat64 = unsafe { mem::zeroed() }; // Trigger the internal buffer resizing logic of `Vec` by requiring
cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?; // more space than the current capacity. The length is guaranteed to be
Ok(FileAttr::from_stat64(stat)) // the same as the capacity due to the if statement above.
}) buf.reserve(1);
}
} }
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> { pub fn symlink(original: &CStr, link: &CStr) -> io::Result<()> {
let r = run_path_with_cstr(p, &|path| unsafe { cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
Ok(libc::realpath(path.as_ptr(), ptr::null_mut())) }
})?;
pub fn link(original: &CStr, link: &CStr) -> io::Result<()> {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita", target_env = "nto70"))] {
// VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
// it implementation-defined whether `link` follows symlinks, so rely on the
// `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior.
// Android has `linkat` on newer versions, but we happen to know `link`
// always has the correct behavior, so it's here as well.
cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
} else {
// Where we can, use `linkat` instead of `link`; see the comment above
// this one for details on why.
cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
}
}
Ok(())
}
pub fn stat(p: &CStr) -> io::Result<FileAttr> {
cfg_has_statx! {
if let Some(ret) = unsafe { try_statx(
libc::AT_FDCWD,
p.as_ptr(),
libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
}
let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
Ok(FileAttr::from_stat64(stat))
}
pub fn lstat(p: &CStr) -> io::Result<FileAttr> {
cfg_has_statx! {
if let Some(ret) = unsafe { try_statx(
libc::AT_FDCWD,
p.as_ptr(),
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
}
let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
Ok(FileAttr::from_stat64(stat))
}
pub fn canonicalize(path: &CStr) -> io::Result<PathBuf> {
let r = unsafe { libc::realpath(path.as_ptr(), ptr::null_mut()) };
if r.is_null() { if r.is_null() {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
} }
@ -2328,19 +2307,19 @@ mod remove_dir_impl {
Ok(()) Ok(())
} }
fn remove_dir_all_modern(p: &Path) -> io::Result<()> { fn remove_dir_all_modern(p: &CStr) -> io::Result<()> {
// We cannot just call remove_dir_all_recursive() here because that would not delete a passed // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
// symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
// into symlinks. // into symlinks.
let attr = lstat(p)?; let attr = lstat(p)?;
if attr.file_type().is_symlink() { if attr.file_type().is_symlink() {
crate::fs::remove_file(p) super::unlink(p)
} else { } else {
run_path_with_cstr(p, &|p| remove_dir_all_recursive(None, &p)) remove_dir_all_recursive(None, &p)
} }
} }
pub fn remove_dir_all(p: &Path) -> io::Result<()> { pub fn remove_dir_all(p: &Path) -> io::Result<()> {
remove_dir_all_modern(p) run_path_with_cstr(p, &remove_dir_all_modern)
} }
} }