From 3a72d1263cab1b95f0f325b1560af92d1295c9a3 Mon Sep 17 00:00:00 2001 From: Sam Brkopac Date: Sun, 27 Sep 2026 17:20:03 +0200 Subject: [PATCH] tmpfiles: set mode and owner of d/D directories through an fd makedir() swallows EEXIST, so the chmod branch for directories that already existed never ran, while chown() ran every time. On finix /var/empty is immutable, which gave a warning on every boot even though the owner was already correct. mksubsys() is the original of this code and has the same shape, the dbus plugin uses it for /tmp/dbus. Both now go through dirperm(), which opens the directory and uses fstat/fchmod/fchown on the fd, changing only what differs. A failed chown in mksubsys() is a warning now rather than err(1), PID 1 should not exit over a directory it cannot adjust. Signed-off-by: Joachim Wiberg --- src/tmpfiles.c | 10 +++++----- src/util.c | 32 ++++++++++++++++++++++++++++---- src/util.h | 1 + 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 4cf7ec07..e4f36989 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -586,11 +586,11 @@ static void tmpfiles(char *line) if (gid < 0) gid = 0; - rc = makedir(path, mode ?: 0755); - if (rc && errno == EEXIST) - rc = chmod(path, mode ?: 0755); - if (chown(path, uid, gid)) - warn("Failed chown(%s, %d, %d)", path, uid, gid); + if (!mode) + mode = 0755; + rc = makedir(path, mode); + if (!rc && dirperm(path, mode, uid, gid)) + warn("Failed setting mode/owner on %s", path); } umask(omask); break; diff --git a/src/util.c b/src/util.c index 3d8a0b22..ff4b4651 100644 --- a/src/util.c +++ b/src/util.c @@ -25,6 +25,7 @@ #include /* isprint() */ #include +#include #include #include #ifdef HAVE_MNTENT_H @@ -280,6 +281,31 @@ int getcgroup(char *buf, size_t len) return 0; } +/* + * Set mode and owner on a directory that may already exist. Works on + * an fd opened with O_NOFOLLOW | O_DIRECTORY, so the change lands on + * the directory itself and not on whatever a link at @path points to. + * Only what differs is touched, the directory may be immutable. + */ +int dirperm(const char *path, mode_t mode, uid_t uid, gid_t gid) +{ + struct stat st; + int fd, rc; + + fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) + return -1; + + rc = fstat(fd, &st); + if (!rc && (st.st_mode & 07777) != mode) + rc = fchmod(fd, mode); + if (!rc && (st.st_uid != uid || st.st_gid != gid)) + rc = fchown(fd, uid, gid); + close(fd); + + return rc; +} + int mksubsys(const char *dir, mode_t mode, char *user, char *group) { mode_t omask; @@ -295,10 +321,8 @@ int mksubsys(const char *dir, mode_t mode, char *user, char *group) gid = 0; rc = makedir(dir, mode); - if (rc && errno == EEXIST) - rc = chmod(dir, mode); - if (chown(dir, uid, gid)) - err(1, "Failed chown(%s, %d, %d)", dir, uid, gid); + if (!rc && dirperm(dir, mode, uid, gid)) + warn("Failed setting mode/owner on %s", dir); } umask(omask); diff --git a/src/util.h b/src/util.h index 7e8df0b2..de2a6b9c 100644 --- a/src/util.h +++ b/src/util.h @@ -68,6 +68,7 @@ int getgroup (const char *group); int getcuser (char *buf, size_t len); int getcgroup (char *buf, size_t len); +int dirperm (const char *path, mode_t mode, uid_t uid, gid_t gid); int mksubsys (const char *dir, mode_t mode, char *user, char *group); int fnread (char *buf, size_t len, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));