diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0c297c5766b82..c3ca301255e82 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -122,19 +122,10 @@ pub fn set_permissions(path: &Path, perm: FilePermissions) -> io::Result<()> { #[cfg(all(unix, not(target_os = "vxworks")))] pub fn set_permissions_nofollow(path: &Path, perm: crate::fs::Permissions) -> io::Result<()> { - use crate::fs::OpenOptions; - - let mut options = OpenOptions::new(); - - // ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it. - // Their filesystems do not have symbolic links, so no special handling is required. - #[cfg(not(any(target_os = "espidf", target_os = "horizon")))] - { - use crate::os::unix::fs::OpenOptionsExt; - options.custom_flags(libc::O_NOFOLLOW); - } - - options.open(path)?.set_permissions(perm) + // Operate on the path itself (do not follow a trailing symlink) via fchmodat + // where supported. Opening with O_NOFOLLOW fails with ELOOP on Linux/macOS + // when the path is a symlink, which cannot implement "chmod the link". + with_native_path(path, &|path| imp::set_perm_nofollow(path, perm.0.clone())) } #[cfg(any(not(unix), target_os = "vxworks"))] diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3152a22534f6c..b1e391c2240e8 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1989,6 +1989,23 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } +/// Change permissions without following the final path component if it is a symlink. +/// +/// Uses `fchmodat(..., AT_SYMLINK_NOFOLLOW)` where available. Platforms without +/// that API (or without symlinks) fall back to normal `chmod` via `set_perm`. +#[cfg(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "vxworks")))] +pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { + cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) + }) + .map(|_| ()) +} + +#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "vxworks"))] +pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { + set_perm(p, perm) +} + pub fn rmdir(p: &CStr) -> io::Result<()> { cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()) }