From 88a9e6575b24c3d087638b3dfd1952b55f4f48ff Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 27 Sep 2026 17:29:51 +0200 Subject: [PATCH] tmpfiles: apply mode and owner of e and f/F entries through an fd Same treatment as d/D got: e went through fisdir() and a path based chmod/chown, f/F did the chmod/chown by path after closing the file. Both now work on the open fd, so the mode and owner end up on the directory or file that was just checked or written. f/F use open(2) directly, which lets a plain f rely on O_EXCL for create-if-missing instead of the earlier stat(). Signed-off-by: Joachim Wiberg (cherry picked from commit 75739fbeef11bfedfc457f04ef770cc9a37f39cd) --- src/tmpfiles.c | 82 +++++++++++++++++++++++++++----------------------- src/util.c | 33 +++++++++++++------- src/util.h | 1 + 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 46c8a24b..a209f51a 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -24,6 +24,7 @@ #include "config.h" /* Generated by configure script */ #include +#include #include #include #include @@ -566,58 +567,65 @@ static void tmpfiles(char *line) umask(omask); break; } - case 'e': + case 'e': { + int uid, gid; + if (glob(path, GLOB_NOESCAPE, NULL, &gl)) break; + uid = parse_uid(user); + gid = parse_gid(group); + if (gid < 0) + gid = 0; + for (size_t i = 0; i < gl.gl_pathc; i++) { char *p = gl.gl_pathv[i]; - int uid, gid; - /* e only adjusts existing directories */ - if (!fisdir(p)) - continue; - - uid = parse_uid(user); - gid = parse_gid(group); - if (gid < 0) - gid = 0; - - if (mode) - chmod(p, mode); - if (uid >= 0 && chown(p, uid, gid)) - warn("Failed chown(%s, %d, %d)", p, uid, gid); + /* skip non-directories */ + if (dirperm(p, mode, uid, gid) && errno != ENOTDIR) + warn("Failed setting mode/owner on %s", p); } break; + } case 'f': - case 'F': - mkparent(path, 0755); + case 'F': { + int flags = O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC; + int fd, uid, gid; + if (type[1] == '+' || type[0] == 'F') { - /* f+/F will create or truncate the file */ - fp = fopen(path, "w+"); + flags |= O_TRUNC; /* create or truncate */ } else { - /* f will create the file if it doesn't exist */ - if (strc) - fp = fopen(path, "w"); + if (!strc) + break; /* create only if missing */ + flags |= O_EXCL; } - if (fp) { - int uid, gid; - - write_arg(fp, arg); - rc = fclose(fp); - - /* Apply mode and ownership */ - if (mode) - chmod(path, mode); - uid = parse_uid(user); - gid = parse_gid(group); - if (gid < 0) - gid = 0; - if (uid >= 0 && chown(path, uid, gid)) - warn("Failed chown(%s, %d, %d)", path, uid, gid); + mkparent(path, 0755); + fd = open(path, flags, 0666); + if (fd < 0) { + if (errno != EEXIST) + warn("Failed creating %s", path); + break; } + + fp = fdopen(fd, "w"); + if (!fp) { + close(fd); + break; + } + + write_arg(fp, arg); + + uid = parse_uid(user); + gid = parse_gid(group); + if (gid < 0) + gid = 0; + if (fdperm(fd, mode, uid, gid)) + warn("Failed setting mode/owner on %s", path); + + rc = fclose(fp); break; + } case 'l': /* Finit extension, like 'L' but only if target exists */ if (!arg) { paste(buf, sizeof(buf), "/usr/share/factory", path); diff --git a/src/util.c b/src/util.c index 161bfad8..72c59c1b 100644 --- a/src/util.c +++ b/src/util.c @@ -376,26 +376,37 @@ int rmrf(const char *path) } /* - * 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. + * Set mode and owner on an open fd, only what differs is touched, the + * file may be immutable. mode 0 skips the chmod, uid -1 the chown. + */ +int fdperm(int fd, mode_t mode, uid_t uid, gid_t gid) +{ + struct stat st; + int rc; + + rc = fstat(fd, &st); + if (!rc && mode && (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); + + return rc; +} + +/* + * Same for a directory that may already exist. Opened with + * O_NOFOLLOW | O_DIRECTORY, so the change lands on the directory + * itself and not on whatever a link at @path points to. */ 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); + rc = fdperm(fd, mode, uid, gid); close(fd); return rc; diff --git a/src/util.h b/src/util.h index 6e2cc053..7780dc49 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 fdperm (int fd, mode_t mode, uid_t uid, gid_t gid); 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);