Skip to content

Commit feafe24

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 01f54e8 commit feafe24

11 files changed

Lines changed: 187 additions & 30 deletions

File tree

library/std/src/fs.rs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3337,23 +3337,59 @@ pub fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result
33373337
fs_imp::set_permissions(path.as_ref(), perm.0)
33383338
}
33393339

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

33593395
impl DirBuilder {

library/std/src/fs/tests.rs

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

616+
#[test]
617+
fn set_get_permissions_nofollows() {
618+
let tmpdir = tmpdir();
619+
let filename = tmpdir.join("set_get_unix_permissions_file");
620+
check!(File::create(&filename));
621+
let file_metadata = check!(fs::metadata(&filename));
622+
assert!(!file_metadata.permissions().readonly());
623+
let mut permission_bits = file_metadata.permissions();
624+
permission_bits.set_readonly(true);
625+
let result = fs::set_permissions_nofollow(&filename, permission_bits);
626+
627+
cfg_select! {
628+
any(windows, unix, target_os = "uefi", target_os = "solid_asp3", target_os = "motor") => {
629+
assert_eq!(result.unwrap(), ());
630+
let metadata0 = check!(fs::metadata(&filename));
631+
assert!(metadata0.permissions().readonly());
632+
},
633+
target_os = "hermit" => {
634+
assert_eq!(result.unwrap_err().raw_os_error(), Some(22));
635+
}
636+
_ => {
637+
let error_kind = result.unwrap_err().kind();
638+
assert_eq!(error_kind, crate::io::ErrorKind::Unsupported);
639+
}
640+
}
641+
}
642+
643+
// Only Windows and Unix support `fs::set_permissions_nofollow`
644+
#[test]
645+
#[cfg(any(windows, unix))]
646+
fn set_get_permissions_nofollows_symlink() {
647+
#[cfg(not(windows))]
648+
use crate::os::unix::fs::symlink;
649+
#[cfg(windows)]
650+
use crate::os::windows::fs::symlink_dir;
651+
652+
let tmpdir = tmpdir();
653+
let filename = tmpdir.join("set_get_unix_permissions_file");
654+
let symlink_name = tmpdir.join("set_get_unix_permissions");
655+
check!(File::create(&filename));
656+
#[cfg(not(windows))]
657+
check!(symlink(&filename, &symlink_name));
658+
#[cfg(windows)]
659+
check!(symlink_dir(&filename, &symlink_name));
660+
661+
let sym_metadata = check!(fs::symlink_metadata(&symlink_name));
662+
let mut permission_bits = sym_metadata.permissions();
663+
permission_bits.set_readonly(true);
664+
let result = fs::set_permissions_nofollow(&symlink_name, permission_bits);
665+
666+
cfg_select! {
667+
any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "espidf", target_os = "horizon") => {
668+
assert_eq!(result.unwrap(), ());
669+
let metadata0 = check!(fs::symlink_metadata(&symlink_name));
670+
assert!(metadata0.permissions().readonly());
671+
},
672+
_ => {
673+
let error_kind = result.unwrap_err().kind();
674+
assert_eq!(error_kind, crate::io::ErrorKind::Unsupported);
675+
}
676+
}
677+
}
678+
616679
#[test]
617680
#[cfg(windows)]
618681
fn file_test_io_seek_read_write() {
@@ -1330,6 +1393,29 @@ fn fchmod_works() {
13301393
check!(file.set_permissions(p));
13311394
}
13321395

1396+
#[test]
1397+
fn fchmodat_works() {
1398+
let tmpdir = tmpdir();
1399+
let file = tmpdir.join("in.txt");
1400+
1401+
check!(File::create(&file));
1402+
let attr = check!(fs::metadata(&file));
1403+
assert!(!attr.permissions().readonly());
1404+
let mut p = attr.permissions();
1405+
p.set_readonly(true);
1406+
check!(fs::set_permissions_nofollow(&file, p.clone()));
1407+
let attr = check!(fs::metadata(&file));
1408+
assert!(attr.permissions().readonly());
1409+
1410+
match fs::set_permissions_nofollow(&tmpdir.join("foo"), p.clone()) {
1411+
Ok(..) => panic!("wanted an error"),
1412+
Err(..) => {}
1413+
}
1414+
1415+
p.set_readonly(false);
1416+
check!(fs::set_permissions_nofollow(&file, p));
1417+
}
1418+
13331419
#[test]
13341420
fn sync_doesnt_kill_anything() {
13351421
let tmpdir = tmpdir();

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+
Err(Error::from_raw_os_error(22))
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> {
319319
}
320320

321321
pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> {
322+
// Motor does not support symlinks
323+
set_perm_nofollow(path, perm)
324+
}
325+
326+
pub fn set_perm_nofollow(path: &Path, perm: FilePermissions) -> io::Result<()> {
322327
let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?;
323328
moto_rt::fs::set_perm(path, perm.rt_perm).map_err(map_motor_error)
324329
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,11 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
531531
}
532532

533533
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
534+
// Solid does not support symlinks
535+
set_perm_nofollow(p, perm)
536+
}
537+
538+
pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> {
534539
error::SolidError::err_if_negative(unsafe {
535540
abi::SOLID_FS_Chmod(cstr(p)?.as_ptr(), perm.0.into())
536541
})

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
480480
}
481481

482482
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
483+
// UEFI does not support symlinks
484+
set_perm_nofollow(p, perm);
485+
}
486+
487+
pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> {
483488
let f = uefi_fs::File::from_path(p, file::MODE_READ | file::MODE_WRITE, 0)?;
484489
set_perm_inner(&f, perm)
485490
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,25 @@ 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+
// ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it.
1994+
// Their filesystems do not have symbolic links, so no special handling is required.
1995+
cfg_select! {
1996+
not(any(target_os = "espidf", target_os = "horizon")) => {
1997+
cvt_r(|| unsafe {
1998+
libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW)
1999+
})
2000+
.map(|_| ())
2001+
},
2002+
_ => {
2003+
cvt_r(|| unsafe {
2004+
libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0)
2005+
})
2006+
.map(|_| ())
2007+
}
2008+
}
2009+
}
2010+
19922011
pub fn rmdir(p: &CStr) -> io::Result<()> {
19932012
cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
19942013
}

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)