From bdea819a2fd52cea2f5d1fde7ac261bcb8cd55a4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Oct 2026 16:14:30 +0200 Subject: [PATCH] journald: prefer stream's unit ID over stale cached one if process is gone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 44a9316bf4 made us refresh the cached client context if a stdout stream tells us about a unit that doesn't match the cached one. That covers the case where sd-executor logs a few messages while still in the service manager's cgroup (on kernels lacking CLONE_INTO_CGROUP) and only then moves itself into the unit's cgroup and connects stdout/stderr to us. However, the refresh doesn't help if the process already exited by the time we process its output: reading its cgroup then fails, and we only used the stream's unit ID as fallback if nothing was cached at all. Hence the output was still attributed to init.scope, and "journalctl -u" would not show it. This made the "systemctl --verbose wait" check in TEST-03-JOBS flaky on CentOS 9 CI, where the unit's single line of output was attributed to init.scope: _SYSTEMD_CGROUP=/init.scope _SYSTEMD_UNIT=init.scope _TRANSPORT=stdout MESSAGE=hello-from-wait Fix that by flushing out the cached data entirely if a stream tells us about a unit while the cached data still refers to init.scope. This way we neither keep sd-executor's identity (_COMM=, _EXE=, …) nor any of init.scope's per-unit settings (log filters, max log level, extra fields, rate limits) around, and if the process already exited we fall back to the stream's unit ID as before. We deliberately do not do this if the cached data refers to any other unit, since processes of a unit might legitimately have been migrated elsewhere (e.g. into a session scope) while still writing to the stream they inherited. --- src/journal/journald-context.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index 221a9f0574f..56452988d8c 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -25,6 +25,7 @@ #include "procfs-util.h" #include "selinux-util.h" #include "set.h" +#include "special.h" #include "string-util.h" #include "syslog-util.h" #include "time-util.h" @@ -286,9 +287,10 @@ static int client_context_read_cgroup(Manager *m, ClientContext *c, const char * /* Try to acquire the current cgroup path */ r = cg_pid_get_path_shifted(c->pid, m->cgroup_root, &t); if (r < 0 || empty_or_root(t)) { - /* We use the unit ID passed in as fallback if we have nothing cached yet and cg_pid_get_path_shifted() - * failed or process is running in a root cgroup. Zombie processes are automatically migrated to root cgroup - * on cgroup v1 and we want to be able to map log messages from them too. */ + /* We use the unit ID passed in as fallback if cg_pid_get_path_shifted() failed (e.g. because + * the process already exited by the time we process the stdout stream it connected to us) or + * the process is running in a root cgroup. Zombie processes are automatically migrated to root + * cgroup on cgroup v1 and we want to be able to map log messages from them too. */ if (unit_id && !c->unit) { c->unit = strdup(unit_id); if (c->unit) @@ -539,6 +541,19 @@ static void client_context_really_refresh( if (timestamp == USEC_INFINITY) timestamp = now(CLOCK_MONOTONIC); + /* If the caller tells us about a unit (i.e. the one a stdout stream was opened for, which is always + * a system unit), but we have the process cached as part of PID 1's own cgroup, then the cached data + * is stale: on kernels lacking CLONE_INTO_CGROUP sd-executor logs a few messages while still in + * init.scope, and only then attaches itself to the unit's cgroup and connects stdout/stderr to + * us. Flush out everything we know in that case, so that we don't keep sd-executor's identity or + * init.scope's per-unit settings around. If the process is still around we'll reacquire everything + * below, and if it already exited we'll at least use the stream's unit ID as fallback. Note that we + * do not do this if the cached data refers to any other unit, since processes of a unit might very + * well have been migrated elsewhere (e.g. into a session scope) while still writing to the stream + * they inherited. */ + if (unit_id && streq_ptr(c->unit, SPECIAL_INIT_SCOPE) && !streq(unit_id, SPECIAL_INIT_SCOPE)) + client_context_reset(m, c); + client_context_read_uid_gid(c, ucred); client_context_read_basic(c); (void) client_context_read_label(c, label);