Skip to content

Commit 3411717

Browse files
committed
Added implementation on set_permissions_nofollow for all platforms supported (windows, unix, uefi, etc.); modified the Unix implementation to use fchmodat instead of open + fchmod; clarified documentations for set_permissions_nofollow; added a test case for set_permissions_nofollow
1 parent c55fad5 commit 3411717

11 files changed

Lines changed: 127 additions & 30 deletions

File tree

library/std/src/fs.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,23 +3335,58 @@ pub fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result
33353335
fs_imp::set_permissions(path.as_ref(), perm.0)
33363336
}
33373337

3338-
/// Set the permissions of a file, unless it is a symlink.
3338+
/// Changes the permissions found on a file or a directory. On certain platforms, if the file
3339+
/// is a symlink, it will change the permissions bits on the symlink itself rather than
3340+
/// the target (e.g. Windows, BSD, MacOS). On other platforms, this results in an error when
3341+
/// attempting to change permissions on a symlink (e.g. Linux).
33393342
///
3340-
/// Note that the non-final path elements are allowed to be symlinks.
3343+
/// Note that non-final path elements are allowed to be symlinks.
33413344
///
33423345
/// # Platform-specific behavior
33433346
///
3344-
/// Currently unimplemented on Windows.
3347+
/// This function currently corresponds to the `fchmodat` function on Unix
3348+
/// with the flag `AT_SYMLINK_NOFOLLOW` enabled. On Windows, the file is opened
3349+
/// with the flag `FILE_FLAG_OPEN_REPARSE_POINT` enabled and then the permissions
3350+
/// is set through `SetFileInformationByHandle`. On all other platforms, the behavior
3351+
/// remains the same with [`fs::set_permissions`].
33453352
///
3346-
/// On Unix platforms, this results in a [`FilesystemLoop`] error if the last element is a symlink.
3353+
/// [`fs::set_permissions`]: crate::fs::set_permissions
33473354
///
3348-
/// This behavior may change in the future.
3355+
/// Note that, this [may change in the future][changes].
33493356
///
3350-
/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop
3351-
#[doc(alias = "chmod", alias = "SetFileAttributes")]
3357+
/// [changes]: io#platform-specific-behavior
3358+
///
3359+
/// # Errors
3360+
///
3361+
/// This function will return an error in the following situations, but is not
3362+
/// limited to just these cases:
3363+
///
3364+
/// * `path` does not exist.
3365+
/// * The user lacks the permission to change attributes of the file.
3366+
///
3367+
/// Note: On Linux, this will result in a [`Unsupported`] error
3368+
/// if the final element is a symlink.
3369+
///
3370+
/// [`Unsupported`]: crate::io::ErrorKind::Unsupported
3371+
///
3372+
/// # Examples
3373+
///
3374+
/// ```no_run
3375+
/// use std::fs;
3376+
///
3377+
/// fn main() -> std::io::Result<()> {
3378+
/// let mut perms = fs::symlink_metadata("foo.txt")?.permissions();
3379+
/// perms.set_readonly(true);
3380+
/// // This should result in an error on certain platforms
3381+
/// // or succeed in modifying the permissions of a symlink
3382+
/// fs::set_permissions_nofollow("foo.txt", perms)?;
3383+
/// Ok(())
3384+
/// }
3385+
/// ```
3386+
#[doc(alias = "fchmodat", alias = "SetFileInformationByHandle")]
33523387
#[unstable(feature = "set_permissions_nofollow", issue = "141607")]
33533388
pub fn set_permissions_nofollow<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
3354-
fs_imp::set_permissions_nofollow(path.as_ref(), perm)
3389+
fs_imp::set_permissions_nofollow(path.as_ref(), perm.0)
33553390
}
33563391

33573392
impl DirBuilder {

library/std/src/fs/tests.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,50 @@ fn set_get_unix_permissions() {
613613
assert_eq!(mask & metadata1.permissions().mode(), 0o0777);
614614
}
615615

616+
// On vxworks, set_permissions_nofollow will simply panic.
617+
#[test]
618+
#[cfg(any(windows, all(unix, not(target_os = "vxworks"))))]
619+
fn set_get_unix_permissions_nofollows() {
620+
#[cfg(not(windows))]
621+
use crate::os::unix::fs::symlink;
622+
#[cfg(windows)]
623+
use crate::os::windows::fs::symlink_dir;
624+
625+
let tmpdir = tmpdir();
626+
let filename = tmpdir.join("set_get_unix_permissions_file");
627+
let symlink_name = tmpdir.join("set_get_unix_permissions");
628+
check!(File::create(&filename));
629+
#[cfg(not(windows))]
630+
check!(symlink(&filename, &symlink_name));
631+
#[cfg(windows)]
632+
check!(symlink_dir(&filename, &symlink_name));
633+
634+
let sym_metadata = check!(fs::symlink_metadata(&symlink_name));
635+
let mut permission_bits = sym_metadata.permissions();
636+
permission_bits.set_readonly(true);
637+
let result = fs::set_permissions_nofollow(&symlink_name, permission_bits);
638+
639+
cfg_select! {
640+
any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "espidf", target_os = "horizon") => {
641+
assert_eq!(result.unwrap(), ());
642+
let metadata0 = check!(fs::symlink_metadata(&symlink_name));
643+
assert!(metadata0.permissions().readonly());
644+
},
645+
_ => {
646+
let error_kind = result.unwrap_err().kind();
647+
assert_eq!(error_kind, crate::io::ErrorKind::Unsupported);
648+
}
649+
}
650+
651+
let file_metadata = check!(fs::metadata(&filename));
652+
assert!(!file_metadata.permissions().readonly());
653+
let mut permission_bits = file_metadata.permissions();
654+
permission_bits.set_readonly(true);
655+
check!(fs::set_permissions_nofollow(&filename, permission_bits));
656+
let metadata1 = check!(fs::metadata(&filename));
657+
assert!(metadata1.permissions().readonly());
658+
}
659+
616660
#[test]
617661
#[cfg(windows)]
618662
fn file_test_io_seek_read_write() {

library/std/src/sys/fs/hermit.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,10 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
566566
Err(Error::from_raw_os_error(22))
567567
}
568568

569+
pub fn set_perm_nofollow(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
570+
set_perm(_p, _perm)
571+
}
572+
569573
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
570574
Err(Error::from_raw_os_error(22))
571575
}

library/std/src/sys/fs/mod.rs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -120,28 +120,8 @@ pub fn set_permissions(path: &Path, perm: FilePermissions) -> io::Result<()> {
120120
with_native_path(path, &|path| imp::set_perm(path, perm.clone()))
121121
}
122122

123-
#[cfg(all(unix, not(target_os = "vxworks")))]
124-
pub fn set_permissions_nofollow(path: &Path, perm: crate::fs::Permissions) -> io::Result<()> {
125-
use crate::fs::OpenOptions;
126-
127-
let mut options = OpenOptions::new();
128-
129-
// ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it.
130-
// Their filesystems do not have symbolic links, so no special handling is required.
131-
#[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
132-
{
133-
use crate::os::unix::fs::OpenOptionsExt;
134-
options.custom_flags(libc::O_NOFOLLOW);
135-
}
136-
137-
options.open(path)?.set_permissions(perm)
138-
}
139-
140-
#[cfg(any(not(unix), target_os = "vxworks"))]
141-
pub fn set_permissions_nofollow(_path: &Path, _perm: crate::fs::Permissions) -> io::Result<()> {
142-
crate::unimplemented!(
143-
"`set_permissions_nofollow` is currently only implemented on Unix platforms"
144-
)
123+
pub fn set_permissions_nofollow(path: &Path, perm: FilePermissions) -> io::Result<()> {
124+
with_native_path(path, &|path| imp::set_perm_nofollow(path, perm.clone()))
145125
}
146126

147127
pub fn canonicalize(path: &Path) -> io::Result<PathBuf> {

library/std/src/sys/fs/motor.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> {
323323
moto_rt::fs::set_perm(path, perm.rt_perm).map_err(map_motor_error)
324324
}
325325

326+
pub fn set_perm_nofollow(path: &Path, perm: FilePermissions) -> io::Result<()> {
327+
set_perm(path, perm)
328+
}
329+
326330
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
327331
unsupported()
328332
}

library/std/src/sys/fs/solid.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,10 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
538538
Ok(())
539539
}
540540

541+
pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> {
542+
set_perm(p, perm);
543+
}
544+
541545
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
542546
unsupported()
543547
}

library/std/src/sys/fs/uefi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,10 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
484484
set_perm_inner(&f, perm)
485485
}
486486

487+
pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> {
488+
set_perm(p, perm)
489+
}
490+
487491
pub fn set_times(p: &Path, times: FileTimes) -> io::Result<()> {
488492
// UEFI does not support symlinks
489493
set_times_nofollow(p, times)

library/std/src/sys/fs/unix.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,13 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> {
19891989
cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())
19901990
}
19911991

1992+
pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> {
1993+
cvt_r(|| unsafe {
1994+
libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW)
1995+
})
1996+
.map(|_| ())
1997+
}
1998+
19921999
pub fn rmdir(p: &CStr) -> io::Result<()> {
19932000
cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
19942001
}

library/std/src/sys/fs/unsupported.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> {
313313
match perm.0 {}
314314
}
315315

316+
pub fn set_perm_nofollow(_p: &Path, perm: FilePermissions) -> io::Result<()> {
317+
match perm.0 {}
318+
}
319+
316320
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
317321
unsupported()
318322
}

library/std/src/sys/fs/vexos.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
492492
unsupported()
493493
}
494494

495+
pub fn set_perm_nofollow(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
496+
unsupported()
497+
}
498+
495499
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
496500
unsupported()
497501
}

0 commit comments

Comments
 (0)