From 9bfbdc6bac115f6f358443932d134d5ab1dc4894 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 (cherry picked from commit 3a72d1263cab1b95f0f325b1560af92d1295c9a3) --- src/tmpfiles.c | 10 +++++----- src/util.c | 34 +++++++++++++++++++++++++++++----- src/util.h | 1 + 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 4825ee6a..46c8a24b 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -557,11 +557,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 8a5f3ee4..161bfad8 100644 --- a/src/util.c +++ b/src/util.c @@ -25,6 +25,7 @@ #include /* isprint() */ #include +#include #include #include #ifdef HAVE_MNTENT_H @@ -374,6 +375,32 @@ int rmrf(const char *path) 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. uid + * -1 skips the chown. + */ +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 && uid != (uid_t)-1 && (st.st_uid != uid || st.st_gid != gid)) + rc = fchown(fd, uid, gid); + close(fd); + + return rc; +} + /* * Like mksubsys() but with the ids already resolved, uid -1 skips the * chown. Parents are created 0755, only the leaf gets @mode. @@ -386,11 +413,8 @@ int mksubsysd(const char *dir, mode_t mode, uid_t uid, gid_t gid) omask = umask(0); rc = mkpath(dir, 0755); - if (!rc) { - rc = chmod(dir, mode); - if (!rc && uid != (uid_t)-1 && chown(dir, uid, gid)) - err(1, "Failed chown(%s, %d, %d)", dir, (int)uid, (int)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 7e7a6aaf..6e2cc053 100644 --- a/src/util.h +++ b/src/util.h @@ -71,6 +71,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 mksubsysd (const char *dir, mode_t mode, uid_t uid, gid_t gid); int chownr (const char *path, uid_t uid, gid_t gid);