]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: when skipping state deserializing units, also skip job subsections
authorLuca Boccassi <luca.boccassi@gmail.com>
Tue, 5 May 2026 22:17:11 +0000 (23:17 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 6 May 2026 11:04:51 +0000 (12:04 +0100)
If a unit has active jobs, when it gets serialized there are job
subsections, each with their own empty line marker. The skipping
function ignores this and skips until the marker, but then leaves
the job in place, breaking deserialization.
Consume jobs subsections too.

This shows up now that there's TEST-07-PID1.alias-corruption,
which occasionally fails when the aliased unit happens to
still have a job when the reexec happens.

[  967.551630] TEST-07-PID1.sh[179]: + echo 'Testing with: systemctl daemon-reexec'
[  967.551630] TEST-07-PID1.sh[179]: Testing with: systemctl daemon-reexec
[  968.405274] TEST-07-PID1.sh[179]: + echo '--- Attempt 1/3 ---'
[  968.405274] TEST-07-PID1.sh[179]: --- Attempt 1/3 ---
[  968.698641] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...'
[  968.698641] TEST-07-PID1.sh[179]: Running daemon-reexec...
[  969.130261] TEST-07-PID1.sh[179]: + echo 'legit.service PID remains 1282. Attempt 1 passed.'
[  969.130261] TEST-07-PID1.sh[179]: legit.service PID remains 1282. Attempt 1 passed.
[  970.870456] TEST-07-PID1.sh[179]: + echo '--- Attempt 2/3 ---'
[  970.870456] TEST-07-PID1.sh[179]: --- Attempt 2/3 ---
[  971.267205] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...'
[  971.267205] TEST-07-PID1.sh[179]: Running daemon-reexec...
[  971.715743] TEST-07-PID1.sh[179]: + echo 'legit.service PID changed from 1282 to 1643!'
[  971.715743] TEST-07-PID1.sh[179]: legit.service PID changed from 1282 to 1643!

Follow-up for a77c7a8224447890a304bd857f412c8103f217f1
Follow-up for 0742986650b36b604613f9aaa1f6bd45b51c0e67

src/core/unit-serialize.c

index d2aee125ddda6b566a5adb1f322ff0eef0ad38d8..867472a61923e855a9c62438873bfce98ccddb05 100644 (file)
@@ -402,6 +402,23 @@ int unit_deserialize_state_skip(FILE *f) {
                 /* End marker */
                 if (isempty(line))
                         return 1;
+
+                /* A unit's serialized state may embed one or more "job" subsections (for u->job and
+                 * u->nop_job), each itself terminated by an empty line. We must consume those nested
+                 * sections fully, otherwise we'd stop at the job's end marker and treat the rest of the
+                 * unit's fields as a new top-level entry. */
+                if (streq(line, "job"))
+                        for (;;) {
+                                _cleanup_free_ char *job_line = NULL;
+
+                                r = read_stripped_line(f, LONG_LINE_MAX, &job_line);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to read serialization line: %m");
+                                if (r == 0)
+                                        return 0;
+                                if (isempty(job_line))
+                                        break;
+                        }
         }
 }