Skip to content

Commit a7222bd

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 a7222bd

11 files changed

Lines changed: 203 additions & 30 deletions

File tree

library/std/src/fs.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3337,23 +3337,60 @@ 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 `open` with `O_NOFOLLOW` flag enabled
3350+
/// + `fchmod` on WASI and the `fchmodat` function on all other Unix platforms with
3351+
/// the flag `AT_SYMLINK_NOFOLLOW` enabled. On Windows, the file is opened
3352+
/// with the flag `FILE_FLAG_OPEN_REPARSE_POINT` enabled and then the permissions
3353+
/// is set through `SetFileInformationByHandle`. On all other platforms, the behavior
3354+
/// remains the same with [`fs::set_permissions`].
33473355
///
3348-
/// On Unix platforms, this results in a [`FilesystemLoop`] error if the last element is a symlink.
3356+
/// [`fs::set_permissions`]: crate::fs::set_permissions
33493357
///
3350-
/// This behavior may change in the future.
3358+
/// Note that, this [may change in the future][changes].
33513359
///
3352-
/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop
3353-
#[doc(alias = "chmod", alias = "SetFileAttributes")]
3360+
/// [changes]: io#platform-specific-behavior
3361+
///
3362+
/// # Errors
3363+
///
3364+
/// This function will return an error in the following situations, but is not
3365+
/// limited to just these cases:
3366+
///
3367+
/// * `path` does not exist.
3368+
/// * The user lacks the permission to change attributes of the file.
3369+
///
3370+
/// Note: On Linux, this will result in a [`Unsupported`] error
3371+
/// if the final element is a symlink.
3372+
///
3373+
/// [`Unsupported`]: crate::io::ErrorKind::Unsupported
3374+
///
3375+
/// # Examples
3376+
///
3377+
/// ```no_run
3378+
/// #![feature(set_permissions_nofollow)]
3379+
/// use std::fs;
3380+
///
3381+
/// fn main() -> std::io::Result<()> {
3382+
/// let mut perms = fs::symlink_metadata("foo.txt")?.permissions();
3383+
/// perms.set_readonly(true);
3384+
/// // This should result in an error on certain platforms
3385+
/// // or succeed in modifying the permissions of a symlink
3386+
/// fs::set_permissions_nofollow("foo.txt", perms)?;
3387+
/// Ok(())
3388+
/// }
3389+
/// ```
3390+
#[doc(alias = "fchmodat", alias = "SetFileInformationByHandle")]
33543391
#[unstable(feature = "set_permissions_nofollow", issue = "141607")]
33553392
pub fn set_permissions_nofollow<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
3356-
fs_imp::set_permissions_nofollow(path.as_ref(), perm)
3393+
fs_imp::set_permissions_nofollow(path.as_ref(), perm.0)
33573394
}
33583395

33593396
impl DirBuilder {

library/std/src/fs/tests.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,66 @@ 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+
_ => {
634+
let error_kind = result.unwrap_err().kind();
635+
assert_eq!(error_kind, crate::io::ErrorKind::Unsupported);
636+
}
637+
}
638+
}
639+
640+
// Only Windows and Unix support `fs::set_permissions_nofollow`
641+
#[test]
642+
#[cfg(any(windows, unix))]
643+
fn set_get_permissions_nofollows_symlink() {
644+
#[cfg(not(windows))]
645+
use crate::os::unix::fs::symlink;
646+
#[cfg(windows)]
647+
use crate::os::windows::fs::symlink_dir;
648+
649+
let tmpdir = tmpdir();
650+
let filename = tmpdir.join("set_get_unix_permissions_file");
651+
let symlink_name = tmpdir.join("set_get_unix_permissions");
652+
check!(File::create(&filename));
653+
#[cfg(not(windows))]
654+
check!(symlink(&filename, &symlink_name));
655+
#[cfg(windows)]
656+
check!(symlink_dir(&filename, &symlink_name));
657+
658+
let sym_metadata = check!(fs::symlink_metadata(&symlink_name));
659+
let mut permission_bits = sym_metadata.permissions();
660+
permission_bits.set_readonly(true);
661+
let result = fs::set_permissions_nofollow(&symlink_name, permission_bits);
662+
663+
cfg_select! {
664+
any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "espidf", target_os = "horizon") => {
665+
assert_eq!(result.unwrap(), ());
666+
let metadata0 = check!(fs::symlink_metadata(&symlink_name));
667+
assert!(metadata0.permissions().readonly());
668+
},
669+
_ => {
670+
let error_kind = result.unwrap_err().kind();
671+
assert_eq!(error_kind, crate::io::ErrorKind::Unsupported);
672+
}
673+
}
674+
}
675+
616676
#[test]
617677
#[cfg(windows)]
618678
fn file_test_io_seek_read_write() {
@@ -1330,6 +1390,29 @@ fn fchmod_works() {
13301390
check!(file.set_permissions(p));
13311391
}
13321392

1393+
#[test]
1394+
fn fchmodat_works() {
1395+
let tmpdir = tmpdir();
1396+
let file = tmpdir.join("in.txt");
1397+
1398+
check!(File::create(&file));
1399+
let attr = check!(fs::metadata(&file));
1400+
assert!(!attr.permissions().readonly());
1401+
let mut p = attr.permissions();
1402+
p.set_readonly(true);
1403+
check!(fs::set_permissions_nofollow(&file, p.clone()));
1404+
let attr = check!(fs::metadata(&file));
1405+
assert!(attr.permissions().readonly());
1406+
1407+
match fs::set_permissions_nofollow(&tmpdir.join("foo"), p.clone()) {
1408+
Ok(..) => panic!("wanted an error"),
1409+
Err(..) => {}
1410+
}
1411+
1412+
p.set_readonly(false);
1413+
check!(fs::set_permissions_nofollow(&file, p));
1414+
}
1415+
13331416
#[test]
13341417
fn sync_doesnt_kill_anything() {
13351418
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+
unsupported()
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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,43 @@ 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+
// wasm32-wasip1 targets do not support fchmodat, so we fall down to
1997+
// open + fchmod
1998+
target_os = "wasi" => {
1999+
use crate::fs::OpenOptions;
2000+
use crate::fs::Permissions;
2001+
use crate::os::wasi::ffi::OsStrExt;
2002+
let mut options = OpenOptions::new();
2003+
2004+
#[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
2005+
{
2006+
use crate::os::wasi::fs::OpenOptionsExt;
2007+
options.custom_flags(libc::O_NOFOLLOW);
2008+
}
2009+
2010+
let bytes = p.to_bytes();
2011+
let os_str = OsStr::from_bytes(bytes);
2012+
options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm))
2013+
}
2014+
not(any(target_os = "espidf", target_os = "horizon")) => {
2015+
cvt_r(|| unsafe {
2016+
libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW)
2017+
})
2018+
.map(|_| ())
2019+
},
2020+
_ => {
2021+
cvt_r(|| unsafe {
2022+
libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0)
2023+
})
2024+
.map(|_| ())
2025+
}
2026+
}
2027+
}
2028+
19922029
pub fn rmdir(p: &CStr) -> io::Result<()> {
19932030
cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
19942031
}

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)