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")]
#[stable(feature = "rust1", since = "1.0.0")]
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,
@ -2409,7 +2409,7 @@ pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[doc(alias = "stat")]
#[stable(feature = "rust1", since = "1.0.0")]
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.
@ -2444,7 +2444,7 @@ pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
#[doc(alias = "lstat")]
#[stable(feature = "symlink_metadata", since = "1.1.0")]
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
@ -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")]
#[stable(feature = "rust1", since = "1.0.0")]
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.
@ -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")]
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
@ -2840,7 +2840,7 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
#[doc(alias = "rmdir", alias = "RemoveDirectory")]
#[stable(feature = "rust1", since = "1.0.0")]
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
@ -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")]
#[stable(feature = "rust1", since = "1.0.0")]
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.
@ -3003,7 +3003,7 @@ pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
#[doc(alias = "chmod", alias = "SetFileAttributes")]
#[stable(feature = "set_permissions", since = "1.1.0")]
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 {

View file

@ -1,28 +1,115 @@
#![deny(unsafe_op_in_unsafe_fn)]
use crate::io;
use crate::path::{Path, PathBuf};
pub mod common;
cfg_if::cfg_if! {
if #[cfg(target_family = "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")] {
mod windows;
pub use windows::*;
use windows as imp;
pub use windows::{symlink_inner, junction_point};
} else if #[cfg(target_os = "hermit")] {
mod hermit;
pub use hermit::*;
use hermit as imp;
} else if #[cfg(target_os = "solid_asp3")] {
mod solid;
pub use solid::*;
use solid as imp;
} else if #[cfg(target_os = "uefi")] {
mod uefi;
pub use uefi::*;
use uefi as imp;
} else if #[cfg(target_os = "wasi")] {
mod wasi;
pub use wasi::*;
use wasi as imp;
} else {
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
))]
pub fn metadata(&self) -> io::Result<FileAttr> {
lstat(&self.path())
run_path_with_cstr(&self.path(), &lstat)
}
#[cfg(any(
@ -1657,7 +1657,7 @@ impl fmt::Debug for File {
fn get_path(fd: c_int) -> Option<PathBuf> {
let mut p = PathBuf::from("/proc/self/fd");
p.push(&fd.to_string());
readlink(&p).ok()
run_path_with_cstr(&p, &readlink).ok()
}
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
@ -1675,7 +1675,7 @@ impl fmt::Debug for File {
// fallback to procfs as last resort
let mut p = PathBuf::from("/proc/self/fd");
p.push(&fd.to_string());
return readlink(&p).ok();
return run_path_with_cstr(&p, &readlink).ok()
} else {
return None;
}
@ -1830,127 +1830,106 @@ pub fn readdir(path: &Path) -> io::Result<ReadDir> {
}
}
pub fn unlink(p: &Path) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ()))
pub fn unlink(p: &CStr) -> io::Result<()> {
cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ())
}
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
run_path_with_cstr(old, &|old| {
run_path_with_cstr(new, &|new| {
cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
})
})
pub fn rename(old: &CStr, new: &CStr) -> io::Result<()> {
cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
}
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()))
pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> {
cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())
}
pub fn rmdir(p: &Path) -> io::Result<()> {
run_path_with_cstr(p, &|p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()))
pub fn rmdir(p: &CStr) -> io::Result<()> {
cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
}
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
run_path_with_cstr(p, &|c_path| {
let p = c_path.as_ptr();
pub fn readlink(c_path: &CStr) -> io::Result<PathBuf> {
let p = c_path.as_ptr();
let mut buf = Vec::with_capacity(256);
let mut buf = Vec::with_capacity(256);
loop {
let buf_read =
cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })?
as usize;
loop {
let buf_read =
cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize;
unsafe {
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;
}
unsafe {
buf.set_len(buf_read);
}
let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
Ok(FileAttr::from_stat64(stat))
})
}
if buf_read != buf.capacity() {
buf.shrink_to_fit();
pub fn lstat(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_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
return Ok(PathBuf::from(OsString::from_vec(buf)));
}
let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
Ok(FileAttr::from_stat64(stat))
})
// 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 canonicalize(p: &Path) -> io::Result<PathBuf> {
let r = run_path_with_cstr(p, &|path| unsafe {
Ok(libc::realpath(path.as_ptr(), ptr::null_mut()))
})?;
pub fn symlink(original: &CStr, link: &CStr) -> io::Result<()> {
cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
}
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() {
return Err(io::Error::last_os_error());
}
@ -2328,19 +2307,19 @@ mod remove_dir_impl {
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
// symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
// into symlinks.
let attr = lstat(p)?;
if attr.file_type().is_symlink() {
crate::fs::remove_file(p)
super::unlink(p)
} 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<()> {
remove_dir_all_modern(p)
run_path_with_cstr(p, &remove_dir_all_modern)
}
}