From babe876014fe2aead092d82e0677b55422066192 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Wed, 29 Jul 2026 13:21:59 -0400 Subject: [PATCH 1/8] Refactored std::fs::set_permissions_nofollow docs clarifying behavior on different platforms + fixed BSD-based systems to use fchmodat with AT_SYMLINK_NOFOLLOW --- library/std/src/fs.rs | 26 +++++++++++--------------- library/std/src/fs/tests.rs | 7 +------ library/std/src/sys/fs/unix.rs | 10 +++++++++- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 0702148957695..b0afb7c6dc0a2 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3470,17 +3470,12 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// /// # Platform-specific behavior /// -/// This function currently corresponds to: -/// * `open` with `O_NOFOLLOW` flag enabled + `fchmod` on WASI -/// * `fchmodat` function with the flag `AT_SYMLINK_NOFOLLOW` enabled -/// on Unix platforms -/// * The flag `FILE_FLAG_OPEN_REPARSE_POINT` is enabled and then the -/// permissions of the file is set through `SetFileInformationByHandle` -/// on Windows. -/// * On all other platforms, the behavior remains the same with -/// [`fs::set_permissions`]. -/// -/// [`fs::set_permissions`]: crate::fs::set_permissions +/// This function currently corresponds to the following underlying operations: +/// * WASI: `open` with `O_NOFOLLOW` followed by `fchmod`. +/// * Unix: `fchmodat` with `AT_SYMLINK_NOFOLLOW` (or no flag is set if `AT_SYMLINK_NOFOLLOW` does +/// not exist on a specific Unix-based platform) +/// * Windows: `CreateFileW` with `FILE_FLAG_OPEN_REPARSE_POINT` followed +/// by `SetFileInformationByHandle`. /// /// Note that, this [may change in the future][changes]. /// @@ -3496,8 +3491,8 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// /// Note: On Linux, this will result in a [`Unsupported`] error /// if the final element is a symlink. On BSD-based systems, the -/// behavior can vary from symlink permission bits changing or -/// there being no effects on symlinks +/// behavior in this case can vary: the operation may have no effect at all +/// or it may change the permission bits of the symlink itself. /// /// [`Unsupported`]: crate::io::ErrorKind::Unsupported /// @@ -3510,8 +3505,9 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// fn main() -> std::io::Result<()> { /// let mut perms = fs::symlink_metadata("foo.txt")?.permissions(); /// perms.set_readonly(true); -/// // This should result in an error on certain platforms -/// // or succeed in modifying the permissions of a symlink +/// // This should result in an error on certain platforms, +/// // succeed in modifying the permissions of a symlink, +/// // or do nothing at all. /// fs::set_permissions_nofollow("foo.txt", perms)?; /// Ok(()) /// } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 3a6c04146922a..059edd100f5e8 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -679,14 +679,9 @@ fn set_get_permissions_nofollows_symlink() { ) => { assert_eq!(result.unwrap(), ()); let metadata0 = check!(fs::symlink_metadata(&symlink_name)); - // So seems like BSD-based systems trying to set permissions - // on symlinks could lead to no effect, so we should expect - // there being no change to BSD-based systems. + // On these systems, it's confirmed the symlink itself is marked readonly // https://superuser.com/questions/1099634/change-permissions-symbolic-link-mac-os - #[cfg(windows)] assert!(metadata0.permissions().readonly()); - #[cfg(not(windows))] - assert!(!metadata0.permissions().readonly()); // Reset the read-only bit under Windows 7: avoids the // `TempDir::drop` from crashing on a permission denial when diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index b33ebadebe4ad..eb8a1b00e2c28 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1902,11 +1902,19 @@ pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { let os_str = OsStr::from_bytes(bytes); options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm)) } - all(target_os = "linux", not(any(target_os = "espidf", target_os = "horizon"))) => { + all(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android"), not(any(target_os = "espidf", target_os = "horizon"))) => { cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) }) .map(|_| ()) + }, + _ => { + // These platforms do not have `AT_SYMLINK_NOFOLLOW` but support fchmodat, + // so no flag is set for fchmodat. + cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) + }) + .map(|_| ()) } _ => cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) }).map(|_| ()), } From dc3bf29b3ab533f527e78ec44f4cb754fe0ab7ca Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 30 Jul 2026 13:07:42 -0400 Subject: [PATCH 2/8] Refactored documentation for std::fs::set_permissions_nofollow, refactored non-BSD-based/non-Linux platforms to use OpenOptions open + set_permissions, and refactored tests accordingly --- library/std/src/fs.rs | 23 +++++++++-------- library/std/src/fs/tests.rs | 15 ++++------- library/std/src/path.rs | 2 +- library/std/src/sys/fs/unix.rs | 46 +++++++++++++++++----------------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index b0afb7c6dc0a2..3962383fe14ce 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3471,15 +3471,18 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// # Platform-specific behavior /// /// This function currently corresponds to the following underlying operations: -/// * WASI: `open` with `O_NOFOLLOW` followed by `fchmod`. -/// * Unix: `fchmodat` with `AT_SYMLINK_NOFOLLOW` (or no flag is set if `AT_SYMLINK_NOFOLLOW` does -/// not exist on a specific Unix-based platform) +/// * Linux, BSD-based platforms, Android: `fchmodat` with `AT_SYMLINK_NOFOLLOW`. +/// * Other Unix-based platforms with symlinks: `open` with `O_NOFOLLOW` followed by behavior +/// denoted in [`fs::set_permissions`]. +/// * Other Unix-based platforms without symlinks: `open` with followed by behavior +/// denoted in [`fs::set_permissions`]. /// * Windows: `CreateFileW` with `FILE_FLAG_OPEN_REPARSE_POINT` followed /// by `SetFileInformationByHandle`. /// /// Note that, this [may change in the future][changes]. /// /// [changes]: io#platform-specific-behavior +/// [`fs::set_permissions`]: crate::fs::set_permissions /// /// # Errors /// @@ -3489,12 +3492,13 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// * `path` does not exist. /// * The user lacks the permission to change attributes of the file. /// -/// Note: On Linux, this will result in a [`Unsupported`] error -/// if the final element is a symlink. On BSD-based systems, the -/// behavior in this case can vary: the operation may have no effect at all -/// or it may change the permission bits of the symlink itself. +/// Note: On Linux, this will result in an [`Unsupported`] error +/// if the final element is a symlink. On other Unix-based platforms +/// with symlinks (non-BSD-based), this will result in an [`InvalidInput`] +/// error. /// /// [`Unsupported`]: crate::io::ErrorKind::Unsupported +/// [`InvalidInput`]: crate::io::ErrorKind::InvalidInput /// /// # Examples /// @@ -3505,9 +3509,8 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// fn main() -> std::io::Result<()> { /// let mut perms = fs::symlink_metadata("foo.txt")?.permissions(); /// perms.set_readonly(true); -/// // This should result in an error on certain platforms, -/// // succeed in modifying the permissions of a symlink, -/// // or do nothing at all. +/// // This should result in an error on certain platforms or +/// // succeed in modifying the permissions of a symlink /// fs::set_permissions_nofollow("foo.txt", perms)?; /// Ok(()) /// } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 059edd100f5e8..8196c7c60a188 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -649,7 +649,10 @@ fn set_get_permissions_nofollows() { // Only Windows and Unix support `fs::set_permissions_nofollow` #[test] -#[cfg(all(any(windows, unix), not(any(target_os = "espidf", target_os = "horizon"))))] +#[cfg(all( + any(windows, unix), + not(any(target_os = "espidf", target_os = "horizon", target_os = "wasi")) +))] fn set_get_permissions_nofollows_symlink() { #[cfg(not(windows))] use crate::os::unix::fs::symlink as symlink_dir; @@ -668,15 +671,7 @@ fn set_get_permissions_nofollows_symlink() { let result = fs::set_permissions_nofollow(&symlink_name, permission_bits); cfg_select! { - any( - windows, - target_os = "android", - target_os = "macos", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "dragonfly" - ) => { + any(windows, target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly") => { assert_eq!(result.unwrap(), ()); let metadata0 = check!(fs::symlink_metadata(&symlink_name)); // On these systems, it's confirmed the symlink itself is marked readonly diff --git a/library/std/src/path.rs b/library/std/src/path.rs index 8b41a3792ac9a..e3d02af15a814 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -2377,7 +2377,7 @@ pub struct NormalizeError; impl Path { // The following (private!) function allows construction of a path from a u8 // slice, which is only safe when it is known to follow the OsStr encoding. - unsafe fn from_u8_slice(s: &[u8]) -> &Path { + pub(crate) unsafe fn from_u8_slice(s: &[u8]) -> &Path { unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(s)) } } // The following (private!) function reveals the byte encoding used for OsStr. diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index eb8a1b00e2c28..a8747a29554e6 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1885,36 +1885,36 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { } pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { - // 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_select! { - // wasm32-wasip1 targets do not support fchmodat, so we fall down to - // open + fchmod - target_os = "wasi" => { - use crate::fs::{OpenOptions, Permissions}; - use crate::os::wasi::ffi::OsStrExt; - use crate::os::wasi::fs::OpenOptionsExt; - - let mut options = OpenOptions::new(); - options.custom_flags(libc::O_NOFOLLOW); - - let bytes = p.to_bytes(); - let os_str = OsStr::from_bytes(bytes); - options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm)) - } - all(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android"), not(any(target_os = "espidf", target_os = "horizon"))) => { + any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android") => { cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) }) .map(|_| ()) }, + // Not all targets support fchmodat, so we fall back to + // open + fchmod. _ => { - // These platforms do not have `AT_SYMLINK_NOFOLLOW` but support fchmodat, - // so no flag is set for fchmodat. - cvt_r(|| unsafe { - libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) - }) - .map(|_| ()) + use crate::fs::OpenOptions; + use crate::fs::Permissions; + 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")))] + { + #[cfg(target_os = "wasi")] + use crate::os::wasi::fs::OpenOptionsExt; + #[cfg(not(target_os = "wasi"))] + use crate::os::unix::fs::OpenOptionsExt; + options.custom_flags(libc::O_NOFOLLOW); + } + + // SAFETY: Since this function is called with `with_native_path` + // and that successfully converted the `&Path` to a `CString`, it + // should be safe to slice away the nul byte from `&CStr` and convert + // it back to a `&Path`. + let path = unsafe { Path::from_u8_slice(p.to_bytes()) }; + options.open(path)?.set_permissions(Permissions::from_inner(perm)) } _ => cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) }).map(|_| ()), } From 23e388881bc9766b045e52a1d32cd25b769fef69 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Mon, 10 Aug 2026 13:58:40 -0400 Subject: [PATCH 3/8] Add fallback behavior on Linux to use open + fchmod if fchmodat returns ENOTSUP (e.g. for Ubuntu 20.04 returns ENOTSUP on non-symlinks + symlinks when using fchmodat with AT_SYMLINK_NOFOLLOW). Update docs accordingly as well and corrected behavior + docs for other Unix platforms with symlinks should return `FilesystemLoop` error instead of `InvalidInput` due to not setting `OpenOptions` with read enabled. Co-authored-by: Rachel Barker --- library/std/src/fs.rs | 15 +++++++++--- library/std/src/sys/fs/unix.rs | 45 ++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 3962383fe14ce..ab0d6d4e27189 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3471,17 +3471,24 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// # Platform-specific behavior /// /// This function currently corresponds to the following underlying operations: -/// * Linux, BSD-based platforms, Android: `fchmodat` with `AT_SYMLINK_NOFOLLOW`. +/// * Linux: `fchmodat` with `AT_SYMLINK_NOFOLLOW` with a fallback behavior to use +/// `open` with `O_NOFOLLOW` followed by behavior denoted in [`fs::set_permissions`] when +/// the former `fchmodat` call errors with `ENOTSUP`[^1]. +/// * BSD-based platforms, Android: `fchmodat` with `AT_SYMLINK_NOFOLLOW` /// * Other Unix-based platforms with symlinks: `open` with `O_NOFOLLOW` followed by behavior /// denoted in [`fs::set_permissions`]. -/// * Other Unix-based platforms without symlinks: `open` with followed by behavior +/// * Other Unix-based platforms without symlinks: `open` followed by behavior /// denoted in [`fs::set_permissions`]. /// * Windows: `CreateFileW` with `FILE_FLAG_OPEN_REPARSE_POINT` followed /// by `SetFileInformationByHandle`. /// /// Note that, this [may change in the future][changes]. /// +/// [^1]: Ubuntu 20.04, for example, makes `fchmodat` with `AT_SYMLINK_NOFOLLOW` return `ENOTSUP` +/// on both symlinks and non-symlinks +/// /// [changes]: io#platform-specific-behavior +/// /// [`fs::set_permissions`]: crate::fs::set_permissions /// /// # Errors @@ -3494,11 +3501,11 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// /// Note: On Linux, this will result in an [`Unsupported`] error /// if the final element is a symlink. On other Unix-based platforms -/// with symlinks (non-BSD-based), this will result in an [`InvalidInput`] +/// with symlinks (non-BSD-based), this will result in a [`FilesystemLoop`] /// error. /// /// [`Unsupported`]: crate::io::ErrorKind::Unsupported -/// [`InvalidInput`]: crate::io::ErrorKind::InvalidInput +/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop /// /// # Examples /// diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index a8747a29554e6..b941a15cbe505 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1886,7 +1886,48 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { cfg_select! { - any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android") => { + target_os = "linux" => { + let res = cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) + }) + .map(|_| ()); + + match res { + Ok(_) => return Ok(()), + Err(err) => { + if err.kind() == crate::io::ErrorKind::Unsupported { + use crate::fs::OpenOptions; + use crate::fs::Permissions; + use crate::os::unix::ffi::OsStrExt; + use crate::os::unix::fs::OpenOptionsExt; + + let mut options = OpenOptions::new(); + options.read(true).custom_flags(libc::O_NOFOLLOW); + + let os_str = OsStr::from_bytes(p.to_bytes()); + let path = Path::new(os_str); + match options.open(path) { + Ok(file) => { + return file.set_permissions(Permissions::from_inner(perm)); + }, + Err(e) => { + if e.kind() == crate::io::ErrorKind::FilesystemLoop { + // When O_NOFOLLOW flag is enabled, if the trailing component of + // a path is a symbolic link, open should fail with ELOOP error + // For consistency with other Linux distributions, we return + // `ErrorKind::Unsupported`. + return Err(err); + } + return Err(e); + } + } + } + + return Err(err); + } + } + } + any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android") => { cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) }) @@ -1906,7 +1947,7 @@ pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { use crate::os::wasi::fs::OpenOptionsExt; #[cfg(not(target_os = "wasi"))] use crate::os::unix::fs::OpenOptionsExt; - options.custom_flags(libc::O_NOFOLLOW); + options.read(true).custom_flags(libc::O_NOFOLLOW); } // SAFETY: Since this function is called with `with_native_path` From 9c6ebca4725d647107bbcc43fddee99195af3dd4 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 28 Aug 2026 09:22:08 +0800 Subject: [PATCH 4/8] Add regression test for the Polonius help default --- tests/run-make/rustc-help/polonius-help.stdout | 1 + tests/run-make/rustc-help/rmake.rs | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 tests/run-make/rustc-help/polonius-help.stdout diff --git a/tests/run-make/rustc-help/polonius-help.stdout b/tests/run-make/rustc-help/polonius-help.stdout new file mode 100644 index 0000000000000..9f56fb6de9481 --- /dev/null +++ b/tests/run-make/rustc-help/polonius-help.stdout @@ -0,0 +1 @@ + -Z polonius=val -- enable polonius-based borrow-checker (default: no) diff --git a/tests/run-make/rustc-help/rmake.rs b/tests/run-make/rustc-help/rmake.rs index 17811ef18449f..a5e733fb8d9fc 100644 --- a/tests/run-make/rustc-help/rmake.rs +++ b/tests/run-make/rustc-help/rmake.rs @@ -22,6 +22,12 @@ fn main() { // Check that all help options can be invoked at once let codegen_help = bare_rustc().arg("-Chelp").run().stdout_utf8(); let unstable_help = bare_rustc().arg("-Zhelp").run().stdout_utf8(); + let polonius_help = + format!("{}\n", unstable_help.lines().find(|line| line.contains("polonius=val")).unwrap()); + diff() + .expected_file("polonius-help.stdout") + .actual_text("rustc -Zhelp (polonius)", &polonius_help) + .run(); let lints_help = bare_rustc().arg("-Whelp").run().stdout_utf8(); let expected_all = format!("{help}{codegen_help}{unstable_help}{lints_help}"); let all_help = bare_rustc().args(["--help", "-Chelp", "-Zhelp", "-Whelp"]).run().stdout_utf8(); From b901613fcab6b1e784be3af9f5f1e84888a0569d Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sun, 16 Aug 2026 16:55:53 -0400 Subject: [PATCH 5/8] Refactored set_perm_nofollow and have it written so that all supported fchmodat platform call on fchmodat and every platform falls back to open + fchmod when _res is set to ErrorKind::Unsupported; updated docs to reflect change --- library/std/src/fs.rs | 24 ++---- library/std/src/fs/tests.rs | 53 +++++++++--- library/std/src/sys/fs/unix.rs | 145 ++++++++++++++++++--------------- 3 files changed, 126 insertions(+), 96 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index ab0d6d4e27189..56c2af297eebc 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3471,22 +3471,17 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// # Platform-specific behavior /// /// This function currently corresponds to the following underlying operations: -/// * Linux: `fchmodat` with `AT_SYMLINK_NOFOLLOW` with a fallback behavior to use -/// `open` with `O_NOFOLLOW` followed by behavior denoted in [`fs::set_permissions`] when -/// the former `fchmodat` call errors with `ENOTSUP`[^1]. -/// * BSD-based platforms, Android: `fchmodat` with `AT_SYMLINK_NOFOLLOW` -/// * Other Unix-based platforms with symlinks: `open` with `O_NOFOLLOW` followed by behavior -/// denoted in [`fs::set_permissions`]. -/// * Other Unix-based platforms without symlinks: `open` followed by behavior -/// denoted in [`fs::set_permissions`]. +/// * Android: returns [`Unsupported`] on all files. +/// * Linux, BSD-based platforms, QNX, NTO: `fchmodat` with `AT_SYMLINK_NOFOLLOW`. +/// If that is not supported, we fall back to: +/// * Unix-based platforms with symlinks: `open` with `O_NOFOLLOW` followed by +/// [`fs::set_permissions`]. +/// * Unix-based platforms without symlinks: `open` followed by [`fs::set_permissions`]. /// * Windows: `CreateFileW` with `FILE_FLAG_OPEN_REPARSE_POINT` followed /// by `SetFileInformationByHandle`. /// /// Note that, this [may change in the future][changes]. /// -/// [^1]: Ubuntu 20.04, for example, makes `fchmodat` with `AT_SYMLINK_NOFOLLOW` return `ENOTSUP` -/// on both symlinks and non-symlinks -/// /// [changes]: io#platform-specific-behavior /// /// [`fs::set_permissions`]: crate::fs::set_permissions @@ -3499,13 +3494,10 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result /// * `path` does not exist. /// * The user lacks the permission to change attributes of the file. /// -/// Note: On Linux, this will result in an [`Unsupported`] error -/// if the final element is a symlink. On other Unix-based platforms -/// with symlinks (non-BSD-based), this will result in a [`FilesystemLoop`] -/// error. +/// Note: On Linux and other Unix-based platforms with symlinks (non-BSD-based), +/// this will result in an [`Unsupported`] error if the final element is a symlink. /// /// [`Unsupported`]: crate::io::ErrorKind::Unsupported -/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop /// /// # Examples /// diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 8196c7c60a188..a84969976daf7 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -613,6 +613,7 @@ fn set_get_unix_permissions() { assert_eq!(mask & metadata1.permissions().mode(), 0o0777); } +#[cfg(not(target_os = "android"))] #[test] fn set_get_permissions_nofollows() { let tmpdir = tmpdir(); @@ -655,40 +656,65 @@ fn set_get_permissions_nofollows() { ))] fn set_get_permissions_nofollows_symlink() { #[cfg(not(windows))] - use crate::os::unix::fs::symlink as symlink_dir; + use crate::os::unix::fs::symlink as symlink_file; #[cfg(windows)] - use crate::os::windows::fs::symlink_dir; + use crate::os::windows::fs::symlink_file; let tmpdir = tmpdir(); let filename = tmpdir.join("set_get_unix_permissions_file"); let symlink_name = tmpdir.join("set_get_unix_permissions"); check!(File::create(&filename)); - check!(symlink_dir(&filename, &symlink_name)); + check!(symlink_file(&filename, &symlink_name)); - let sym_metadata = check!(fs::symlink_metadata(&symlink_name)); - let mut permission_bits = sym_metadata.permissions(); - permission_bits.set_readonly(true); - let result = fs::set_permissions_nofollow(&symlink_name, permission_bits); + let init_symlink_metadata = check!(fs::symlink_metadata(&symlink_name)); + let mut init_symlink_permissions = init_symlink_metadata.permissions(); + + let init_target_metadata = check!(fs::metadata(&symlink_name)); + let init_target_permissions = init_target_metadata.permissions(); + + // Set symlink permissions to readonly + init_symlink_permissions.set_readonly(true); + let result = fs::set_permissions_nofollow(&symlink_name, init_symlink_permissions); cfg_select! { - any(windows, target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly") => { + any( + windows, + target_os = "macos", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "nto", + target_os = "qnx" + ) => { assert_eq!(result.unwrap(), ()); - let metadata0 = check!(fs::symlink_metadata(&symlink_name)); + + let after_target_metadata = check!(fs::metadata(&symlink_name)); + // We should expect the target file to not have its permission bits + // changed + assert_eq!(after_target_metadata.permissions(), init_target_permissions); + + let after_symlink_metadata = check!(fs::symlink_metadata(&symlink_name)); // On these systems, it's confirmed the symlink itself is marked readonly // https://superuser.com/questions/1099634/change-permissions-symbolic-link-mac-os - assert!(metadata0.permissions().readonly()); + assert!(after_symlink_metadata.permissions().readonly()); // Reset the read-only bit under Windows 7: avoids the // `TempDir::drop` from crashing on a permission denial when // trying to delete the file that has it. #[cfg(all(windows, target_vendor = "win7"))] { - let mut permission_bits = metadata0.permissions(); - permission_bits.set_readonly(false); - check!(fs::set_permissions_nofollow(&symlink_name, permission_bits)); + let mut symlink_permission_bits = after_symlink_metadata.permissions(); + symlink_permission_bits.set_readonly(false); + check!(fs::set_permissions_nofollow(&symlink_name, symlink_permission_bits)); } } _ => { + let after_target_metadata = check!(fs::metadata(&symlink_name)); + // We should expect the target file to not have its permission bits + // changed + assert_eq!(after_target_metadata.permissions(), init_target_permissions); + let error_kind = result.unwrap_err().kind(); assert_eq!(error_kind, crate::io::ErrorKind::Unsupported); } @@ -1416,6 +1442,7 @@ fn fchmod_works() { check!(file.set_permissions(p)); } +#[cfg(not(target_os = "android"))] #[test] fn fchmodat_works() { let tmpdir = tmpdir(); diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index b941a15cbe505..74b4322d027c5 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1884,80 +1884,91 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } +#[cfg(target_os = "android")] +pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> { + // Currently Android seems to be having inconsistent behavior with fchmodat + // with `AT_SYMLINK_NOFOLLOW` or openat with `O_NOFOLLOW` + fchmod. + // See this issue here mentioning inconsistent behavior on fchmodat: + // https://github.com/android/ndk/issues/1258 + // On the arm-android CI job, using fchmodat with `AT_SYMLINK_NOFOLLOW` + + // fallback behavior on a symlink sets the target file's permissions, + // which is incorrect behavior. + Err(crate::io::ErrorKind::Unsupported.into()) +} + +#[cfg(not(target_os = "android"))] pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { - cfg_select! { - target_os = "linux" => { - let res = cvt_r(|| unsafe { - libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) - }) - .map(|_| ()); - - match res { - Ok(_) => return Ok(()), - Err(err) => { - if err.kind() == crate::io::ErrorKind::Unsupported { - use crate::fs::OpenOptions; - use crate::fs::Permissions; - use crate::os::unix::ffi::OsStrExt; - use crate::os::unix::fs::OpenOptionsExt; - - let mut options = OpenOptions::new(); - options.read(true).custom_flags(libc::O_NOFOLLOW); - - let os_str = OsStr::from_bytes(p.to_bytes()); - let path = Path::new(os_str); - match options.open(path) { - Ok(file) => { - return file.set_permissions(Permissions::from_inner(perm)); - }, - Err(e) => { - if e.kind() == crate::io::ErrorKind::FilesystemLoop { - // When O_NOFOLLOW flag is enabled, if the trailing component of - // a path is a symbolic link, open should fail with ELOOP error - // For consistency with other Linux distributions, we return - // `ErrorKind::Unsupported`. - return Err(err); - } - return Err(e); - } + #[inline] + /// Helper function for fallback open with `O_NOFOLLOW` + `fchmod` behavior + fn open_and_set_permissions(p: &CStr, perm: FilePermissions) -> io::Result<()> { + use crate::fs::{OpenOptions, Permissions}; + + 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")))] + { + #[cfg(not(target_os = "wasi"))] + use crate::os::unix::fs::OpenOptionsExt; + #[cfg(target_os = "wasi")] + use crate::os::wasi::fs::OpenOptionsExt; + options.read(true).custom_flags(libc::O_NOFOLLOW); + } + + // SAFETY: Since this function is called with `with_native_path` + // and that successfully converted the `&Path` to a `CString`, + // it should be safe to convert the `&CStr` back to a `Path`. + let os_str = unsafe { OsStr::from_encoded_bytes_unchecked(p.to_bytes()) }; + options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm)) + } + + // This res value is modified for platforms that support the `fchmodat` syscall. + #[allow(unused)] + let mut res: Result<(), core::io::Error> = Err(crate::io::ErrorKind::Unsupported.into()); + + // These platforms support `fchmodat`, so utilize this syscall over `open` + `fchmod` + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "nto", + target_os = "qnx" + ))] + { + res = cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) + }) + .map(|_| ()); + } + + // If fchmodat fails with `ErrorKind::Unsupported` fallback to using open + fchmod. This is just in case + // for older systems like Ubuntu 20.04 where fchmodat fails with EOPNOTSUPP on both regular files and + // symlinks when AT_SYMLINK_NOFOLLOW is passed in. + match res { + Ok(_) => Ok(()), + Err(err) => { + if err.kind() == crate::io::ErrorKind::Unsupported { + match open_and_set_permissions(p, perm) { + Ok(_) => return Ok(()), + Err(e) => { + if e.kind() == crate::io::ErrorKind::FilesystemLoop { + // When open is used with O_NOFOLLOW flag, if the trailing component of + // a path is a symbolic link, it should fail with ELOOP error. Instead of + // returning `FilesystemLoop`, this returns `Unsupported` to keep it consistent + // with what `fchmodat` would return when chmoding a symlink using AT_SYMLINK_NOFOLLOW. + return Err(err); } + return Err(e); } - - return Err(err); } } - } - any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "android") => { - cvt_r(|| unsafe { - libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) - }) - .map(|_| ()) - }, - // Not all targets support fchmodat, so we fall back to - // open + fchmod. - _ => { - use crate::fs::OpenOptions; - use crate::fs::Permissions; - 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")))] - { - #[cfg(target_os = "wasi")] - use crate::os::wasi::fs::OpenOptionsExt; - #[cfg(not(target_os = "wasi"))] - use crate::os::unix::fs::OpenOptionsExt; - options.read(true).custom_flags(libc::O_NOFOLLOW); - } - // SAFETY: Since this function is called with `with_native_path` - // and that successfully converted the `&Path` to a `CString`, it - // should be safe to slice away the nul byte from `&CStr` and convert - // it back to a `&Path`. - let path = unsafe { Path::from_u8_slice(p.to_bytes()) }; - options.open(path)?.set_permissions(Permissions::from_inner(perm)) + Err(err) } - _ => cvt_r(|| unsafe { libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) }).map(|_| ()), } } From 3637675bfcbe708cdced437b1ee1f70b0d4796bc Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 28 Aug 2026 09:32:05 +0800 Subject: [PATCH 6/8] Report the configured Polonius default in -Z help --- compiler/rustc_session/src/config.rs | 5 ++++- compiler/rustc_session/src/options.rs | 10 ++++++++-- tests/run-make/rustc-help/polonius-help-stable.stdout | 1 + tests/run-make/rustc-help/polonius-help.stdout | 2 +- tests/run-make/rustc-help/rmake.rs | 11 ++++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/run-make/rustc-help/polonius-help-stable.stdout diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index a5053c408b155..95f6348cfbdbb 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3647,11 +3647,14 @@ pub enum Polonius { impl Default for Polonius { fn default() -> Self { - if option_env!("CFG_DEFAULT_POLONIUS_NEXT").is_some() { Self::Next } else { Self::Off } + Self::DEFAULT } } impl Polonius { + pub(crate) const DEFAULT: Self = + if option_env!("CFG_DEFAULT_POLONIUS_NEXT").is_some() { Self::Next } else { Self::Off }; + /// Returns whether the legacy version of polonius is enabled pub fn is_legacy_enabled(&self) -> bool { matches!(self, Polonius::Legacy) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 90459090ced87..bab087249593a 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -510,7 +510,7 @@ macro_rules! options { $( { TARGET_MODIFIER: $tmod_variant:ident } )? $( { MITIGATION: $mitigation_variant:ident } )? , - $desc:literal + $desc:expr $(, removed: $removed:ident )? ), )* @@ -2350,6 +2350,12 @@ options! { // - src/doc/rustc/src/codegen-options/index.md } +const POLONIUS_HELP: &str = match Polonius::DEFAULT { + Polonius::Off => "enable polonius-based borrow-checker (default: no)", + Polonius::Next => "enable polonius-based borrow-checker (default: next)", + Polonius::Legacy => panic!("Polonius::Legacy is not a valid default value"), +}; + options! { UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", @@ -2750,7 +2756,7 @@ options! { `vt-ptr-type-discrimination - incorporate type discrimination in authenticated vtable pointers Example: `-Zpointer-authentication=+calls,-init-fini`."), polonius: Polonius = (Polonius::default(), parse_polonius, [TRACKED], - "enable polonius-based borrow-checker (default: no)"), + POLONIUS_HELP), pre_link_arg: (/* redirected to pre_link_args */) = ((), parse_string_push, [UNTRACKED], "a single extra argument to prepend the linker invocation (can be used several times)"), pre_link_args: Vec = (Vec::new(), parse_list, [UNTRACKED], diff --git a/tests/run-make/rustc-help/polonius-help-stable.stdout b/tests/run-make/rustc-help/polonius-help-stable.stdout new file mode 100644 index 0000000000000..9f56fb6de9481 --- /dev/null +++ b/tests/run-make/rustc-help/polonius-help-stable.stdout @@ -0,0 +1 @@ + -Z polonius=val -- enable polonius-based borrow-checker (default: no) diff --git a/tests/run-make/rustc-help/polonius-help.stdout b/tests/run-make/rustc-help/polonius-help.stdout index 9f56fb6de9481..b1dfd15c09958 100644 --- a/tests/run-make/rustc-help/polonius-help.stdout +++ b/tests/run-make/rustc-help/polonius-help.stdout @@ -1 +1 @@ - -Z polonius=val -- enable polonius-based borrow-checker (default: no) + -Z polonius=val -- enable polonius-based borrow-checker (default: next) diff --git a/tests/run-make/rustc-help/rmake.rs b/tests/run-make/rustc-help/rmake.rs index a5e733fb8d9fc..84f8c1b59b629 100644 --- a/tests/run-make/rustc-help/rmake.rs +++ b/tests/run-make/rustc-help/rmake.rs @@ -5,6 +5,7 @@ use run_make_support::{bare_rustc, diff, similar}; fn main() { // `rustc --help` let help = bare_rustc().arg("--help").run().stdout_utf8(); + diff().expected_file("help.stdout").actual_text("(rustc --help)", &help).run(); // `rustc` should be the same as `rustc --help` @@ -22,12 +23,20 @@ fn main() { // Check that all help options can be invoked at once let codegen_help = bare_rustc().arg("-Chelp").run().stdout_utf8(); let unstable_help = bare_rustc().arg("-Zhelp").run().stdout_utf8(); + let polonius_help = format!("{}\n", unstable_help.lines().find(|line| line.contains("polonius=val")).unwrap()); + let version = bare_rustc().arg("--version").run().stdout_utf8(); + let expected_file = if version.contains("-nightly") || version.contains("-dev") { + "polonius-help.stdout" + } else { + "polonius-help-stable.stdout" + }; diff() - .expected_file("polonius-help.stdout") + .expected_file(expected_file) .actual_text("rustc -Zhelp (polonius)", &polonius_help) .run(); + let lints_help = bare_rustc().arg("-Whelp").run().stdout_utf8(); let expected_all = format!("{help}{codegen_help}{unstable_help}{lints_help}"); let all_help = bare_rustc().args(["--help", "-Chelp", "-Zhelp", "-Whelp"]).run().stdout_utf8(); From d180f00098c64d59b57ef8ed2823db143dfcd3b2 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Sat, 29 Aug 2026 14:07:14 +0330 Subject: [PATCH 7/8] Add regression test for TAIT in extern fn ICE with the new solver --- ...tait-extern-fn-next-solver-issue-156345.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/ui/lint/improper-ctypes/tait-extern-fn-next-solver-issue-156345.rs diff --git a/tests/ui/lint/improper-ctypes/tait-extern-fn-next-solver-issue-156345.rs b/tests/ui/lint/improper-ctypes/tait-extern-fn-next-solver-issue-156345.rs new file mode 100644 index 0000000000000..fd8ba56b10be6 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/tait-extern-fn-next-solver-issue-156345.rs @@ -0,0 +1,42 @@ +//@ compile-flags: -Znext-solver=globally +//@ edition: 2021 +//@ check-pass + +// Regression test for . +// An `extern "C" fn` taking a type alias impl trait argument used to ICE with +// the new solver, leaving an entry in the `OpaqueTypeStorage`. Only the new +// solver was affected. + +#![feature(type_alias_impl_trait)] +#![allow(improper_ctypes_definitions)] + +struct Foo { + field: String, +} + +type Tait = impl Sized; + +#[define_opaque(Tait)] +extern "C" fn ice_cold(beverage: Tait) { + let Foo { field } = beverage; + let _ = field; +} + +// A second reproducer from the same issue, with the opaque type in return +// position behind a higher-ranked closure bound. +struct Parser(H); + +impl Parser +where + H: for<'a> Fn(&'a str) -> T, +{ + fn new(handler: H) -> Parser { + Parser(handler) + } + + extern "C" fn many<'s>() -> Parser Fn(&'a str) + 's> { + Parser::new(|_| ()) + } +} + +fn main() {} From 1253a875d17e122dc181a6a7842182bf12875deb Mon Sep 17 00:00:00 2001 From: malezjaa Date: Sat, 29 Aug 2026 23:32:51 +0200 Subject: [PATCH 8/8] regression test for unexpected region ICE --- .../assoc-const-equality-unexpected-region.rs | 14 +++++++ ...oc-const-equality-unexpected-region.stderr | 40 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/ui/associated-consts/assoc-const-equality-unexpected-region.rs create mode 100644 tests/ui/associated-consts/assoc-const-equality-unexpected-region.stderr diff --git a/tests/ui/associated-consts/assoc-const-equality-unexpected-region.rs b/tests/ui/associated-consts/assoc-const-equality-unexpected-region.rs new file mode 100644 index 0000000000000..e8a3dcf47eea3 --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-equality-unexpected-region.rs @@ -0,0 +1,14 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/143896. + +trait TraitA<'a> { + const K: usize = 0; +} + +impl TraitA<'_> for () {} +//~^ ERROR the type parameter `T` is not constrained + +impl dyn TraitA<'_> where (): TraitA<'a, K = 0> {} +//~^ ERROR use of undeclared lifetime name `'a` +//~| ERROR associated const equality is incomplete + +pub fn main() {} diff --git a/tests/ui/associated-consts/assoc-const-equality-unexpected-region.stderr b/tests/ui/associated-consts/assoc-const-equality-unexpected-region.stderr new file mode 100644 index 0000000000000..6e38e3e41acbf --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-equality-unexpected-region.stderr @@ -0,0 +1,40 @@ +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/assoc-const-equality-unexpected-region.rs:10:38 + | +LL | impl dyn TraitA<'_> where (): TraitA<'a, K = 0> {} + | ^^ undeclared lifetime + | + = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html +help: consider making the bound lifetime-generic with a new `'a` lifetime + | +LL | impl dyn TraitA<'_> where (): for<'a> TraitA<'a, K = 0> {} + | +++++++ +help: consider making the bound lifetime-generic with a new `'a` lifetime + | +LL | impl dyn TraitA<'_> where for<'a> (): TraitA<'a, K = 0> {} + | +++++++ +help: consider introducing lifetime `'a` here + | +LL | impl<'a> dyn TraitA<'_> where (): TraitA<'a, K = 0> {} + | ++++ + +error[E0658]: associated const equality is incomplete + --> $DIR/assoc-const-equality-unexpected-region.rs:10:42 + | +LL | impl dyn TraitA<'_> where (): TraitA<'a, K = 0> {} + | ^^^^^ + | + = note: see issue #132980 for more information + = help: add `#![feature(min_generic_const_args)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates + --> $DIR/assoc-const-equality-unexpected-region.rs:7:6 + | +LL | impl TraitA<'_> for () {} + | ^ unconstrained type parameter + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0207, E0261, E0658. +For more information about an error, try `rustc --explain E0207`.