]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: prefer glibc's fchmodat() if possible
authorLuca Boccassi <luca.boccassi@gmail.com>
Tue, 23 Sep 2025 22:13:23 +0000 (23:13 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 13 Oct 2025 08:04:47 +0000 (10:04 +0200)
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

(cherry picked from commit 696b1263dc78858f96345a366933c66d53ae4899)
(cherry picked from commit b465e4816ea2e2e8c39ae348c6600ecd2dcd9c1b)

src/basic/fs-util.c

index 4ede324c34c5965169d36c59ff0547b59182d90a..96bf2676bc3b8fdbea207e48d984fc750d595793 100644 (file)
@@ -293,15 +293,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;