From: Luca Boccassi Date: Tue, 23 Sep 2025 22:13:23 +0000 (+0100) Subject: fs-util: prefer glibc's fchmodat() if possible X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=696b1263dc78858f96345a366933c66d53ae4899;p=thirdparty%2Fsystemd.git fs-util: prefer glibc's fchmodat() if possible Since v2.39 glibc's fchmodat() will call into the kernel's fchmodat2() if flags are passed: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=65341f7bbea824d2ff9d37db15d8be162df42bd3;hp=c52c2c32db15aba8bbe1a0b4d3235f97d9c1a525 On older versions, if the flag is anything other than AT_SYMLINK_NOFOLLOW, it returns EINVAL, so we can detect it and call the kernel syscall directly ourselves. Using the glibc wrappers when possible is prefereable so that programs like fakeroot can intercept its calls and redirect them. Follow-up for adecfb3bc0be0def49433277fcad5333893756cc --- diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a12cfd33c4f..8cbe29fc0f0 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -288,15 +288,20 @@ int fchmod_opath(int fd, mode_t m) { * - fchmod(2) only operates on open files (i. e., fds with an open file description); * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH; * + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument, - * but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW); + * and supports AT_EMPTY_PATH since v2.39 (previously only supported AT_SYMLINK_NOFOLLOW). So if + * the kernel has fchmodat2(2), since v2.39 glibc will call into it directly. If the kernel + * doesn't, or glibc is older than v2.39, glibc's internal fallback will return EINVAL if + * AT_EMPTY_PATH is passed. * - fchmodat2(2) supports all the AT_* flags, but is still very recent. * - * We try to use fchmodat2(), and, if it is not supported, resort - * to the /proc/self/fd dance. */ + * We try to use fchmodat(3) first, and on EINVAL fall back to fchmodat2(), and, if that is also not + * supported, resort to the /proc/self/fd dance. */ assert(fd >= 0); - if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0) + if (fchmodat(fd, "", m, AT_EMPTY_PATH) >= 0) + return 0; + if (errno == EINVAL && fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0) /* glibc too old? */ return 0; if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */ return -errno;