Skip to content

Commit d64bc97

Browse files
committed
set_mode: properly handle symlinks
The `AT_SYMLINK_NOFOLLOW` flag for chmod was not implemented in glibc until very recent releases (v2.32 in 2020-08). Before that version, passing this flag will always result in `ENOTSUP`. Refs: * https://github.com/bminor/glibc/blob/glibc-2.31/sysdeps/unix/sysv/linux/fchmodat.c#L36 * https://github.com/bminor/glibc/blob/glibc-2.32/sysdeps/unix/sysv/linux/fchmodat.c
1 parent fcb3231 commit d64bc97

File tree

1 file changed

+26
-3
lines changed

1 file changed

+26
-3
lines changed

src/lib.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ pub trait OpenatDirExt {
116116
fn update_timestamps<P: openat::AsPath>(&self, path: P) -> io::Result<()>;
117117

118118
/// Update permissions for the given path (see `fchmodat(2)`).
119+
///
120+
/// If the entry at `path` is a symlink, no action is performed.
119121
fn set_mode<P: openat::AsPath>(&self, path: P, mode: libc::mode_t) -> io::Result<()>;
120122

121123
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
@@ -384,14 +386,23 @@ impl OpenatDirExt for openat::Dir {
384386

385387
fn set_mode<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
386388
use nix::sys::stat::{fchmodat, FchmodatFlags, Mode};
389+
use openat::SimpleType;
387390

388391
let path = p
389392
.to_path()
390393
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
391-
let perms = Mode::from_bits_truncate(mode);
392394

393-
// NOTE(lucab): `AT_SYMLINK_NOFOLLOW` exists but it is not really implemented
394-
// and short-circuits to `ENOTSUP`.
395+
{
396+
// NOTE(lucab): `AT_SYMLINK_NOFOLLOW` used to short-circuit to `ENOTSUP`
397+
// in older glibc versions, so we don't use it. Instead we try to detect
398+
// any symlink, and skip it.
399+
let entry_meta = self.metadata(path.as_ref())?;
400+
if entry_meta.simple_type() == SimpleType::Symlink {
401+
return Ok(());
402+
};
403+
}
404+
405+
let perms = Mode::from_bits_truncate(mode);
395406
fchmodat(
396407
Some(self.as_raw_fd()),
397408
path.as_ref(),
@@ -931,6 +942,18 @@ mod tests {
931942
d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
932943
0o700
933944
);
945+
946+
d.symlink("bar", "foo").unwrap();
947+
d.set_mode("bar", 0o000).unwrap();
948+
d.syncfs().unwrap();
949+
assert_ne!(
950+
d.metadata("bar").unwrap().stat().st_mode & !libc::S_IFMT,
951+
0o000
952+
);
953+
assert_ne!(
954+
d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
955+
0o000
956+
);
934957
}
935958

936959
fn find_test_file(tempdir: &Path) -> Result<PathBuf> {

0 commit comments

Comments
 (0)