systemd-252-76

Resolves: RHEL-208860, RHEL-137251, RHEL-118224, RHEL-213655, RHEL-212610
This commit is contained in:
Jan Macku 2026-08-05 09:38:59 +02:00
parent 63d29adf9b
commit 00712f2c9c
14 changed files with 2184 additions and 1 deletions

View File

@ -0,0 +1,53 @@
From 13549c3d3be415eafa0104199cea247706dfa452 Mon Sep 17 00:00:00 2001
From: Milan Kyselica <mil.kyselica@gmail.com>
Date: Thu, 9 Apr 2026 19:43:14 +0200
Subject: [PATCH] resolved: replace assert() with error return in DNSSEC verify
functions
dnssec_rsa_verify_raw() asserts that RSA_size(key) matches the RRSIG
signature size, and dnssec_ecdsa_verify_raw() asserts that
EC_KEY_check_key() succeeds. Both conditions depend on parsed DNS
record content. Replace with proper error returns.
The actual crypto verify calls (EVP_PKEY_verify / ECDSA_do_verify)
handle mismatches fine on their own, so the asserts were also redundant.
While at it, fix the misleading "EC_POINT_bn2point failed" log message
that actually refers to an EC_KEY_set_public_key() failure.
Fixes: https://github.com/systemd/systemd/issues/41569
(cherry picked from commit dd80e5a348bdb8185e040f66ede00fd4ffdee777)
Resolves: RHEL-208860
---
src/resolve/resolved-dns-dnssec.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index df25b7f619..7243c2119f 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -125,7 +125,8 @@ static int dnssec_rsa_verify_raw(
return -EIO;
e = m = NULL;
- assert((size_t) RSA_size(rpubkey) == signature_size);
+ if ((size_t) RSA_size(rpubkey) != signature_size)
+ return -EINVAL;
epubkey = EVP_PKEY_new();
if (!epubkey)
@@ -337,9 +338,11 @@ static int dnssec_ecdsa_verify_raw(
if (EC_KEY_set_public_key(eckey, p) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
- "EC_POINT_bn2point failed: 0x%lx", ERR_get_error());
+ "EC_KEY_set_public_key failed: 0x%lx", ERR_get_error());
- assert(EC_KEY_check_key(eckey) == 1);
+ if (EC_KEY_check_key(eckey) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "EC_KEY_check_key failed: 0x%lx", ERR_get_error());
r = BN_bin2bn(signature_r, signature_r_size, NULL);
if (!r)

View File

@ -0,0 +1,399 @@
From 3c1b490d6c754b8f91c3b7860a75ab59f8c3e6c8 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 24 Mar 2023 16:04:34 +0100
Subject: [PATCH] pid1: introduce new SERVICE_{DEAD|FAILED}_BEFORE_AUTO_RESTART
service substates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a service deactivates and is then automatically restarted via
Restart= we currently quickly transition through
SERVICE_DEAD/SERVICE_FAILED. Which is weird given it's not the
normal ("permanent") dead/failed state, but a transitory one we
immediately leave from again. We do this so that software that looks for
failures/successes can take notice, even if we restart as a consequence
of the deactivation.
Let's clean this up a bit: let's introduce two new states:
SERVICE_DEAD_BEFORE_AUTO_RESTART and SERVICE_FAILED_BEFORE_AUTO_RESTART
that are used for the transitory states. Both the SERVICE_DEAD and
SERVICE_DEAD_BEFORE_AUTO_RESTART will map to the high-level
UNIT_INACTIVE state though. (and similar for the respective failed
states). This means the high-level state machine won't change by this,
only the low-level one.
This clearly seperates the substates, which makes the state engine
cleaner, and allows clients to follow precisely whether we are in a
transitory dead/failed state, or a permanent one, by looking at the
service substate. Moreover it allows us to remove the 'n_keep_fd_store'
which so far we used to ensure the fdstore was not released during this
transitory dead/failed state but only during the permanent one. Since we
can now distinguish these states properly we can just use that.
This has been bugging me for a while. Let's clean this up.
Note that the unit restart logic is already nicely covered in the
testsiute, hence this adds no new tests for that.
And yes, this could be considered a compat break, but sofar we took the
liberty to make changes to the low-level state machine (i.e. SERVICE_xyz
states, sometimes called "substates") without considering this a bad
breakage the high-level state machine (i.e. UNIT_xyz states) should
be considered API that cannot be changed.
(cherry picked from commit a1d315730ffddf283d4bb9d73878fbcd97a4d244)
Related: RHEL-137251
---
src/basic/unit-def.c | 44 ++++++++--------
src/basic/unit-def.h | 2 +
src/core/service.c | 116 +++++++++++++++++++++++++++++--------------
src/core/service.h | 1 -
src/core/socket.c | 4 +-
5 files changed, 105 insertions(+), 62 deletions(-)
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index bdb1860246..a0fab46a19 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -180,27 +180,29 @@ static const char* const scope_state_table[_SCOPE_STATE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
static const char* const service_state_table[_SERVICE_STATE_MAX] = {
- [SERVICE_DEAD] = "dead",
- [SERVICE_CONDITION] = "condition",
- [SERVICE_START_PRE] = "start-pre",
- [SERVICE_START] = "start",
- [SERVICE_START_POST] = "start-post",
- [SERVICE_RUNNING] = "running",
- [SERVICE_EXITED] = "exited",
- [SERVICE_RELOAD] = "reload",
- [SERVICE_RELOAD_SIGNAL] = "reload-signal",
- [SERVICE_RELOAD_NOTIFY] = "reload-notify",
- [SERVICE_STOP] = "stop",
- [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
- [SERVICE_STOP_SIGTERM] = "stop-sigterm",
- [SERVICE_STOP_SIGKILL] = "stop-sigkill",
- [SERVICE_STOP_POST] = "stop-post",
- [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
- [SERVICE_FINAL_SIGTERM] = "final-sigterm",
- [SERVICE_FINAL_SIGKILL] = "final-sigkill",
- [SERVICE_FAILED] = "failed",
- [SERVICE_AUTO_RESTART] = "auto-restart",
- [SERVICE_CLEANING] = "cleaning",
+ [SERVICE_DEAD] = "dead",
+ [SERVICE_CONDITION] = "condition",
+ [SERVICE_START_PRE] = "start-pre",
+ [SERVICE_START] = "start",
+ [SERVICE_START_POST] = "start-post",
+ [SERVICE_RUNNING] = "running",
+ [SERVICE_EXITED] = "exited",
+ [SERVICE_RELOAD] = "reload",
+ [SERVICE_RELOAD_SIGNAL] = "reload-signal",
+ [SERVICE_RELOAD_NOTIFY] = "reload-notify",
+ [SERVICE_STOP] = "stop",
+ [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
+ [SERVICE_STOP_SIGTERM] = "stop-sigterm",
+ [SERVICE_STOP_SIGKILL] = "stop-sigkill",
+ [SERVICE_STOP_POST] = "stop-post",
+ [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
+ [SERVICE_FINAL_SIGTERM] = "final-sigterm",
+ [SERVICE_FINAL_SIGKILL] = "final-sigkill",
+ [SERVICE_FAILED] = "failed",
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart",
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
+ [SERVICE_AUTO_RESTART] = "auto-restart",
+ [SERVICE_CLEANING] = "cleaning",
};
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index bae132ea09..2fab42e9c7 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -144,6 +144,8 @@ typedef enum ServiceState {
SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
SERVICE_FINAL_SIGKILL,
SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART,
+ SERVICE_FAILED_BEFORE_AUTO_RESTART,
SERVICE_AUTO_RESTART,
SERVICE_CLEANING,
_SERVICE_STATE_MAX,
diff --git a/src/core/service.c b/src/core/service.c
index e152fb6227..e6c0c71595 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -65,6 +65,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
@@ -91,6 +93,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
@@ -335,9 +339,6 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
static void service_release_fd_store(Service *s) {
assert(s);
- if (s->n_keep_fd_store > 0)
- return;
-
log_unit_debug(UNIT(s), "Releasing all stored fds");
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
@@ -350,6 +351,10 @@ static void service_release_resources(Unit *u) {
assert(s);
+ /* Don't release resources if this is a transitionary failed/dead state */
+ if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
+ return;
+
if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
return;
@@ -1124,7 +1129,9 @@ static void service_set_state(Service *s, ServiceState state) {
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
}
- if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
+ if (IN_SET(state,
+ SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART)) {
unit_unwatch_all_pids(UNIT(s));
unit_dequeue_rewatch_pids(UNIT(s));
}
@@ -1237,7 +1244,10 @@ static int service_coldplug(Unit *u) {
return r;
}
- if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
+ if (!IN_SET(s->deserialized_state,
+ SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
+ SERVICE_CLEANING)) {
(void) unit_enqueue_rewatch_pids(u);
(void) unit_setup_dynamic_creds(u);
(void) unit_setup_exec_runtime(u);
@@ -1815,14 +1825,14 @@ static bool service_will_restart(Unit *u) {
if (s->will_auto_restart)
return true;
- if (s->state == SERVICE_AUTO_RESTART)
+ if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
return true;
return unit_will_restart_default(u);
}
static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
- ServiceState end_state;
+ ServiceState end_state, restart_state;
int r;
assert(s);
@@ -1838,12 +1848,15 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
if (s->result == SERVICE_SUCCESS) {
unit_log_success(UNIT(s));
end_state = SERVICE_DEAD;
+ restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
} else if (s->result == SERVICE_SKIP_CONDITION) {
unit_log_skip(UNIT(s), service_result_to_string(s->result));
end_state = SERVICE_DEAD;
+ restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
} else {
unit_log_failure(UNIT(s), service_result_to_string(s->result));
end_state = SERVICE_FAILED;
+ restart_state = SERVICE_FAILED_BEFORE_AUTO_RESTART;
}
unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
@@ -1861,30 +1874,33 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
s->will_auto_restart = true;
}
- /* Make sure service_release_resources() doesn't destroy our FD store, while we are changing through
- * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
- s->n_keep_fd_store ++;
-
- service_set_state(s, end_state);
-
if (s->will_auto_restart) {
s->will_auto_restart = false;
+ /* We make two state changes here: one that maps to the high-level UNIT_INACTIVE/UNIT_FAILED
+ * state (i.e. a state indicating deactivation), and then one that that maps to the
+ * high-level UNIT_STARTING state (i.e. a state indicating activation). We do this so that
+ * external software can watch the state changes and see all service failures, even if they
+ * are only transitionary and followed by an automatic restart. We have fine-grained
+ * low-level states for this though so that software can distinguish the permanent UNIT_INACTIVE
+ * state from this transitionary UNIT_INACTIVE state by looking at the low-level states. */
+ service_set_state(s, restart_state);
+
r = service_arm_timer(s, /* relative= */ true, s->restart_usec);
- if (r < 0) {
- s->n_keep_fd_store--;
+ if (r < 0)
goto fail;
- }
service_set_state(s, SERVICE_AUTO_RESTART);
- } else
+ } else {
+ service_set_state(s, end_state);
+
/* If we shan't restart, then flush out the restart counter. But don't do that immediately, so that the
* user can still introspect the counter. Do so on the next start. */
s->flush_n_restarts = true;
+ }
/* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
* queue, so that the fd store is possibly gc'ed again */
- s->n_keep_fd_store--;
unit_add_to_gc_queue(UNIT(s));
/* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
@@ -2611,34 +2627,55 @@ static int service_stop(Unit *u) {
/* Don't create restart jobs from manual stops. */
s->forbid_restart = true;
- /* Already on it */
- if (IN_SET(s->state,
- SERVICE_STOP, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
+ switch (s->state) {
+
+ case SERVICE_STOP:
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+ case SERVICE_STOP_POST:
+ case SERVICE_FINAL_WATCHDOG:
+ case SERVICE_FINAL_SIGTERM:
+ case SERVICE_FINAL_SIGKILL:
+ /* Already on it */
return 0;
- /* A restart will be scheduled or is in progress. */
- if (s->state == SERVICE_AUTO_RESTART) {
+ case SERVICE_AUTO_RESTART:
+ /* A restart will be scheduled or is in progress. */
service_set_state(s, SERVICE_DEAD);
return 0;
- }
- /* If there's already something running we go directly into kill mode. */
- if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) {
+ case SERVICE_CONDITION:
+ case SERVICE_START_PRE:
+ case SERVICE_START:
+ case SERVICE_START_POST:
+ case SERVICE_RELOAD:
+ case SERVICE_RELOAD_SIGNAL:
+ case SERVICE_RELOAD_NOTIFY:
+ case SERVICE_STOP_WATCHDOG:
+ /* If there's already something running we go directly into kill mode. */
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
return 0;
- }
- /* If we are currently cleaning, then abort it, brutally. */
- if (s->state == SERVICE_CLEANING) {
+ case SERVICE_CLEANING:
+ /* If we are currently cleaning, then abort it, brutally. */
service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
return 0;
+
+ case SERVICE_RUNNING:
+ case SERVICE_EXITED:
+ service_enter_stop(s, SERVICE_SUCCESS);
+ return 1;
+
+ case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+ case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+ case SERVICE_DEAD:
+ case SERVICE_FAILED:
+ default:
+ /* Unknown state, or unit_stop() should already have handled these */
+ assert_not_reached();
}
- assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
- service_enter_stop(s, SERVICE_SUCCESS);
- return 1;
}
static int service_reload(Unit *u) {
@@ -3214,6 +3251,11 @@ static bool service_may_gc(Unit *u) {
control_pid_good(s) > 0)
return false;
+ /* Only allow collection of actually dead services, i.e. not those that are in the transitionary
+ * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */
+ if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED))
+ return false;
+
return true;
}
@@ -3375,11 +3417,9 @@ static void service_notify_cgroup_empty_event(Unit *u) {
switch (s->state) {
- /* Waiting for SIGCHLD is usually more interesting,
- * because it includes return codes/signals. Which is
- * why we ignore the cgroup events for most cases,
- * except when we don't know pid which to expect the
- * SIGCHLD for. */
+ /* Waiting for SIGCHLD is usually more interesting, because it includes return
+ * codes/signals. Which is why we ignore the cgroup events for most cases, except when we
+ * don't know pid which to expect the SIGCHLD for. */
case SERVICE_START:
if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
diff --git a/src/core/service.h b/src/core/service.h
index 194067f0e1..58780ebd41 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -203,7 +203,6 @@ struct Service {
ServiceFDStore *fd_store;
size_t n_fd_store;
unsigned n_fd_store_max;
- unsigned n_keep_fd_store;
char *usb_function_descriptors;
char *usb_function_strings;
diff --git a/src/core/socket.c b/src/core/socket.c
index 103b399ab8..ebb83cd5b0 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -2471,7 +2471,7 @@ static int socket_start(Unit *u) {
/* If the service is already active we cannot start the
* socket */
- if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART))
+ if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY), "Socket service %s already active, refusing.", UNIT(service)->id);
}
@@ -3274,7 +3274,7 @@ static void socket_trigger_notify(Unit *u, Unit *other) {
return;
if (IN_SET(SERVICE(other)->state,
- SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED, SERVICE_FAILED_BEFORE_AUTO_RESTART,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_AUTO_RESTART))
socket_enter_listening(s);

View File

@ -0,0 +1,423 @@
From 219f89250a9d41c357eb1224f624ae3cf08cf4e9 Mon Sep 17 00:00:00 2001
From: Richard Phibel <rphibel@googlemail.com>
Date: Thu, 6 Jul 2023 14:33:52 +0200
Subject: [PATCH] service: add new RestartMode option
When this option is set to direct, the service restarts without entering a failed
state. Dependent units are not notified of transitory failure.
This is useful for the following use case:
We have a target with Requires=my-service, After=my-service.
my-service.service is a oneshot service and has Restart=on-failure in
its definition.
my-service.service can get stuck for various reasons and time out, in
which case it is restarted. Currently, when it fails the first time, the
target fails, even though my-service is restarted.
The behavior we're looking for is that until my-service is not restarted
anymore, the target stays pending waiting for my-service.service to
start successfully or fail without being restarted anymore.
(cherry picked from commit e568fea9fcd2189d4366df254a8a4031dc433762)
Resolves: RHEL-137251
---
man/org.freedesktop.systemd1.xml | 6 +++++
man/systemd.service.xml | 22 +++++++++++++++++++
src/core/dbus-service.c | 6 +++++
src/core/load-fragment-gperf.gperf.in | 1 +
src/core/load-fragment.c | 2 ++
src/core/load-fragment.h | 1 +
src/core/service.c | 10 ++++++++-
src/core/service.h | 11 ++++++++++
src/shared/bus-unit-util.c | 1 +
src/test/test-tables.c | 1 +
.../fails-on-restart-restartdirect.service | 11 ++++++++++
.../fails-on-restart-restartdirect.target | 3 +++
.../fails-on-restart.service | 11 ++++++++++
.../fails-on-restart.target | 3 +++
.../succeeds-on-restart-restartdirect.service | 6 +++++
.../succeeds-on-restart-restartdirect.target | 3 +++
.../succeeds-on-restart.service | 6 +++++
.../testsuite-03.units/succeeds-on-restart.sh | 10 +++++++++
.../succeeds-on-restart.target | 3 +++
test/units/testsuite-03.sh | 13 +++++++++++
20 files changed, 129 insertions(+), 1 deletion(-)
create mode 100644 test/testsuite-03.units/fails-on-restart-restartdirect.service
create mode 100755 test/testsuite-03.units/fails-on-restart-restartdirect.target
create mode 100644 test/testsuite-03.units/fails-on-restart.service
create mode 100755 test/testsuite-03.units/fails-on-restart.target
create mode 100755 test/testsuite-03.units/succeeds-on-restart-restartdirect.service
create mode 100755 test/testsuite-03.units/succeeds-on-restart-restartdirect.target
create mode 100755 test/testsuite-03.units/succeeds-on-restart.service
create mode 100755 test/testsuite-03.units/succeeds-on-restart.sh
create mode 100755 test/testsuite-03.units/succeeds-on-restart.target
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index e7b9b0a127..8298b726b7 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2524,6 +2524,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s Restart = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s RestartMode = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PIDFile = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NotifyAccess = '...';
@@ -3128,6 +3130,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property Restart is not documented!-->
+ <!--property RestartMode is not documented!-->
+
<!--property PIDFile is not documented!-->
<!--property NotifyAccess is not documented!-->
@@ -3666,6 +3670,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="Restart"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RestartMode"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="PIDFile"/>
<variablelist class="dbus-property" generated="True" extra-ref="NotifyAccess"/>
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index ae54332440..d2d6f589da 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -941,6 +941,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>RestartMode=</varname></term>
+
+ <listitem>
+ <para>Takes a string value that specifies how a service should restart:
+ <itemizedlist>
+ <listitem><para>If set to <option>normal</option> (the default), the service restarts by
+ going through a failed/inactive state.</para></listitem>
+
+ <listitem><para>If set to <option>direct</option>, the service transitions to the activating
+ state directly during auto-restart, skipping failed/inactive state.
+ <varname>ExecStopPost=</varname> is invoked.
+ <varname>OnSuccess=</varname> and <varname>OnFailure=</varname> are skipped.</para></listitem>
+ </itemizedlist>
+ </para>
+
+ <para>This option is useful in cases where a dependency can fail temporarily
+ but we don't want these temporary failures to make the dependent units fail.
+ When this option is set to <option>direct</option>, dependent units are not notified of these temporary failures.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>SuccessExitStatus=</varname></term>
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 3d130db66a..f8bf455707 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -31,6 +31,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exit_type, service_exit_type, S
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart_mode, service_restart_mode, ServiceRestartMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, service_timeout_abort_usec);
static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
@@ -193,6 +194,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExitType", "s", property_get_exit_type, offsetof(Service, exit_type), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RestartMode", "s", property_get_restart_mode, offsetof(Service, restart_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -372,6 +374,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(notify_access, NotifyAccess, notify_access
static BUS_DEFINE_SET_TRANSIENT_PARSE(service_type, ServiceType, service_type_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(service_exit_type, ServiceExitType, service_exit_type_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(service_restart, ServiceRestart, service_restart_from_string);
+static BUS_DEFINE_SET_TRANSIENT_PARSE(service_restart_mode, ServiceRestartMode, service_restart_mode_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(oom_policy, OOMPolicy, oom_policy_from_string);
static BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(bus_name, sd_bus_service_name_is_valid);
static BUS_DEFINE_SET_TRANSIENT_PARSE(timeout_failure_mode, ServiceTimeoutFailureMode, service_timeout_failure_mode_from_string);
@@ -511,6 +514,9 @@ static int bus_service_set_transient_property(
if (streq(name, "Restart"))
return bus_set_transient_service_restart(u, name, &s->restart, message, flags, error);
+ if (streq(name, "RestartMode"))
+ return bus_set_transient_service_restart_mode(u, name, &s->restart_mode, message, flags, error);
+
if (streq(name, "RestartPreventExitStatus"))
return bus_set_transient_exit_status(u, name, &s->restart_prevent_status, message, flags, error);
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 53089d5590..602cd2befd 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -407,6 +407,7 @@ Service.RebootArgument, config_parse_unit_string_printf,
Service.Type, config_parse_service_type, 0, offsetof(Service, type)
Service.ExitType, config_parse_service_exit_type, 0, offsetof(Service, exit_type)
Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart)
+Service.RestartMode, config_parse_service_restart_mode, 0, offsetof(Service, restart_mode)
Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only)
Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only)
Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 2699ccf6ef..17863d7139 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -140,6 +140,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode,
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart_mode, service_restart_mode, ServiceRestartMode, "Failed to parse service restart mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode, "Failed to parse timeout failure mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_socket_bind, socket_address_bind_ipv6_only_or_bool, SocketAddressBindIPv6Only, "Failed to parse bind IPv6 only value");
DEFINE_CONFIG_PARSE_ENUM(config_parse_oom_policy, oom_policy, OOMPolicy, "Failed to parse OOM policy");
@@ -6166,6 +6167,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_service_type, "SERVICETYPE" },
{ config_parse_service_exit_type, "SERVICEEXITTYPE" },
{ config_parse_service_restart, "SERVICERESTART" },
+ { config_parse_service_restart_mode, "SERVICERESTARTMODE" },
{ config_parse_service_timeout_failure_mode, "TIMEOUTMODE" },
{ config_parse_kill_mode, "KILLMODE" },
{ config_parse_signal, "SIGNAL" },
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index c57a6b2277..bdedbc51e2 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -39,6 +39,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_service_timeout_failure_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_service_type);
CONFIG_PARSER_PROTOTYPE(config_parse_service_exit_type);
CONFIG_PARSER_PROTOTYPE(config_parse_service_restart);
+CONFIG_PARSER_PROTOTYPE(config_parse_service_restart_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_socket_bindtodevice);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_output);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_input);
diff --git a/src/core/service.c b/src/core/service.c
index e6c0c71595..96f419bb47 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1884,7 +1884,8 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
* are only transitionary and followed by an automatic restart. We have fine-grained
* low-level states for this though so that software can distinguish the permanent UNIT_INACTIVE
* state from this transitionary UNIT_INACTIVE state by looking at the low-level states. */
- service_set_state(s, restart_state);
+ if (s->restart_mode != SERVICE_RESTART_MODE_DIRECT)
+ service_set_state(s, restart_state);
r = service_arm_timer(s, /* relative= */ true, s->restart_usec);
if (r < 0)
@@ -4771,6 +4772,13 @@ static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart);
+static const char* const service_restart_mode_table[_SERVICE_RESTART_MODE_MAX] = {
+ [SERVICE_RESTART_MODE_NORMAL] = "normal",
+ [SERVICE_RESTART_MODE_DIRECT] = "direct",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_restart_mode, ServiceRestartMode);
+
static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
[SERVICE_SIMPLE] = "simple",
[SERVICE_FORKING] = "forking",
diff --git a/src/core/service.h b/src/core/service.h
index 58780ebd41..bfbb3261a8 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -90,6 +90,13 @@ typedef enum ServiceTimeoutFailureMode {
_SERVICE_TIMEOUT_FAILURE_MODE_INVALID = -EINVAL,
} ServiceTimeoutFailureMode;
+typedef enum ServiceRestartMode {
+ SERVICE_RESTART_MODE_NORMAL,
+ SERVICE_RESTART_MODE_DIRECT,
+ _SERVICE_RESTART_MODE_MAX,
+ _SERVICE_RESTART_MODE_INVALID = -EINVAL,
+} ServiceRestartMode;
+
struct ServiceFDStore {
Service *service;
@@ -107,6 +114,7 @@ struct Service {
ServiceType type;
ServiceExitType exit_type;
ServiceRestart restart;
+ ServiceRestartMode restart_mode;
ExitStatusSet restart_prevent_status;
ExitStatusSet restart_force_status;
ExitStatusSet success_status;
@@ -238,6 +246,9 @@ void service_close_socket_fd(Service *s);
const char* service_restart_to_string(ServiceRestart i) _const_;
ServiceRestart service_restart_from_string(const char *s) _pure_;
+const char* service_restart_mode_to_string(ServiceRestartMode i) _const_;
+ServiceRestartMode service_restart_mode_from_string(const char *s) _pure_;
+
const char* service_type_to_string(ServiceType i) _const_;
ServiceType service_type_from_string(const char *s) _pure_;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index a9844e1cc3..43abdce1b4 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -2156,6 +2156,7 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
"Type",
"ExitType",
"Restart",
+ "RestartMode",
"BusName",
"NotifyAccess",
"USBFunctionDescriptors",
diff --git a/src/test/test-tables.c b/src/test/test-tables.c
index d47d3d75cc..0be3c43cde 100644
--- a/src/test/test-tables.c
+++ b/src/test/test-tables.c
@@ -98,6 +98,7 @@ int main(int argc, char **argv) {
test_table(scope_state, SCOPE_STATE);
test_table(service_exec_command, SERVICE_EXEC_COMMAND);
test_table(service_restart, SERVICE_RESTART);
+ test_table(service_restart_mode, SERVICE_RESTART_MODE);
test_table(service_result, SERVICE_RESULT);
test_table(service_state, SERVICE_STATE);
test_table(service_type, SERVICE_TYPE);
diff --git a/test/testsuite-03.units/fails-on-restart-restartdirect.service b/test/testsuite-03.units/fails-on-restart-restartdirect.service
new file mode 100644
index 0000000000..60ffd7a600
--- /dev/null
+++ b/test/testsuite-03.units/fails-on-restart-restartdirect.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Fail on restart
+StartLimitIntervalSec=1m
+StartLimitBurst=3
+
+[Service]
+Type=oneshot
+ExecStart=false
+Restart=on-failure
+RestartMode=direct
diff --git a/test/testsuite-03.units/fails-on-restart-restartdirect.target b/test/testsuite-03.units/fails-on-restart-restartdirect.target
new file mode 100755
index 0000000000..58e2561039
--- /dev/null
+++ b/test/testsuite-03.units/fails-on-restart-restartdirect.target
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Requires=fails-on-restart-restartdirect.service
diff --git a/test/testsuite-03.units/fails-on-restart.service b/test/testsuite-03.units/fails-on-restart.service
new file mode 100644
index 0000000000..fb7e7aeb4c
--- /dev/null
+++ b/test/testsuite-03.units/fails-on-restart.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Fail on restart
+StartLimitIntervalSec=1m
+StartLimitBurst=3
+
+[Service]
+Type=oneshot
+ExecStart=false
+Restart=on-failure
+RestartMode=normal
diff --git a/test/testsuite-03.units/fails-on-restart.target b/test/testsuite-03.units/fails-on-restart.target
new file mode 100755
index 0000000000..865fb2af44
--- /dev/null
+++ b/test/testsuite-03.units/fails-on-restart.target
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Requires=fails-on-restart.service
diff --git a/test/testsuite-03.units/succeeds-on-restart-restartdirect.service b/test/testsuite-03.units/succeeds-on-restart-restartdirect.service
new file mode 100755
index 0000000000..b05f2f8dcf
--- /dev/null
+++ b/test/testsuite-03.units/succeeds-on-restart-restartdirect.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-03.units/succeeds-on-restart.sh
+Restart=on-failure
+RestartMode=direct
diff --git a/test/testsuite-03.units/succeeds-on-restart-restartdirect.target b/test/testsuite-03.units/succeeds-on-restart-restartdirect.target
new file mode 100755
index 0000000000..2cf3c60d2a
--- /dev/null
+++ b/test/testsuite-03.units/succeeds-on-restart-restartdirect.target
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Requires=succeeds-on-restart-restartdirect.service
diff --git a/test/testsuite-03.units/succeeds-on-restart.service b/test/testsuite-03.units/succeeds-on-restart.service
new file mode 100755
index 0000000000..d7b3c7a210
--- /dev/null
+++ b/test/testsuite-03.units/succeeds-on-restart.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-03.units/succeeds-on-restart.sh
+Restart=on-failure
+RestartMode=normal
diff --git a/test/testsuite-03.units/succeeds-on-restart.sh b/test/testsuite-03.units/succeeds-on-restart.sh
new file mode 100755
index 0000000000..1428b186e5
--- /dev/null
+++ b/test/testsuite-03.units/succeeds-on-restart.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+if [[ ! -f "/succeeds-on-restart.ko" ]]
+then
+ touch "/succeeds-on-restart.ko"
+ exit 1
+else
+ rm "/succeeds-on-restart.ko"
+ exit 0
+fi
diff --git a/test/testsuite-03.units/succeeds-on-restart.target b/test/testsuite-03.units/succeeds-on-restart.target
new file mode 100755
index 0000000000..eb82f47aa3
--- /dev/null
+++ b/test/testsuite-03.units/succeeds-on-restart.target
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Requires=succeeds-on-restart.service
diff --git a/test/units/testsuite-03.sh b/test/units/testsuite-03.sh
index 1d4bf3aaaa..ec51b20bf0 100755
--- a/test/units/testsuite-03.sh
+++ b/test/units/testsuite-03.sh
@@ -110,4 +110,17 @@ ELAPSED=$((END_SEC-START_SEC))
[[ "$ELAPSED" -ge 3 ]] && [[ "$ELAPSED" -le 5 ]] || exit 1
[[ "$RESULT" -ne 0 ]] || exit 1
+# Test restart mode direct
+systemctl start succeeds-on-restart-restartdirect.target
+assert_rc 0 systemctl --quiet is-active succeeds-on-restart-restartdirect.target
+
+systemctl start fails-on-restart-restartdirect.target || :
+assert_rc 3 systemctl --quiet is-active fails-on-restart-restartdirect.target
+
+systemctl start succeeds-on-restart.target || :
+assert_rc 3 systemctl --quiet is-active succeeds-on-restart.target
+
+systemctl start fails-on-restart.target || :
+assert_rc 3 systemctl --quiet is-active fails-on-restart.target
+
touch /testok

View File

@ -0,0 +1,37 @@
From 01efd088ea54f56ec5e8c7077be2e05dc5b316e4 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Wed, 12 Feb 2025 17:38:47 +0100
Subject: [PATCH] core/service: drop unneeded unit_add_to_gc_queue()
Follow-up for a1d315730ffddf283d4bb9d73878fbcd97a4d244
and 6ac62d61db737b01ad3776a7688d8a4c57b3f7d9
With the aforementioned commits, unit_release_resources()
is dispatched in a dedicated queue, and Service.n_keep_fd_store
has been dropped, hence the comment is outdated. Moreover,
the unit is added to GC queue in unit_notify() already.
No other unit types do this in corresponding _enter_dead()
functions, nor does Service need it anymore.
(cherry picked from commit 818315ae61370998a4df7b92d89c338c9442c6df)
Related: RHEL-137251
---
src/core/service.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/core/service.c b/src/core/service.c
index 96f419bb47..0da2af7c35 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1900,10 +1900,6 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
s->flush_n_restarts = true;
}
- /* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
- * queue, so that the fd store is possibly gc'ed again */
- unit_add_to_gc_queue(UNIT(s));
-
/* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
s->forbid_restart = false;

View File

@ -0,0 +1,119 @@
From 3907ede0d5bcb7e514b4875b8c01d67ea761a412 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 22 May 2023 08:30:30 +0800
Subject: [PATCH] core: get rid of unused Service.will_auto_restart logic
The announced new behavior for OnFailure= never worked properly,
and we've fixed the document instead in #27675.
Therefore, let's get rid of the unused logic completely. More at #27594.
The to-be-added RestartMode= option should cover the use case hopefully.
Closes #27594
(cherry picked from commit 49b34f75e7c801210624e0c7dd00be990873628a)
Resolves: RHEL-118224
---
src/core/service.c | 16 ++++------------
src/core/service.h | 2 --
src/core/unit.c | 7 ++-----
src/core/unit.h | 1 -
4 files changed, 6 insertions(+), 20 deletions(-)
diff --git a/src/core/service.c b/src/core/service.c
index 0da2af7c35..55e8bf5182 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1160,8 +1160,7 @@ static void service_set_state(Service *s, ServiceState state) {
log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state));
unit_notify(UNIT(s), table[old_state], table[state],
- (s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE) |
- (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0));
+ s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE);
}
static usec_t service_coldplug_timeout(Service *s) {
@@ -1823,8 +1822,6 @@ static bool service_will_restart(Unit *u) {
assert(s);
- if (s->will_auto_restart)
- return true;
if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
return true;
@@ -1864,19 +1861,14 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
log_unit_debug(UNIT(s), "Service restart not allowed.");
else {
const char *reason;
- bool shall_restart;
- shall_restart = service_shall_restart(s, &reason);
+ allow_restart = service_shall_restart(s, &reason);
log_unit_debug(UNIT(s), "Service will %srestart (%s)",
- shall_restart ? "" : "not ",
+ allow_restart ? "" : "not ",
reason);
- if (shall_restart)
- s->will_auto_restart = true;
}
- if (s->will_auto_restart) {
- s->will_auto_restart = false;
-
+ if (allow_restart) {
/* We make two state changes here: one that maps to the high-level UNIT_INACTIVE/UNIT_FAILED
* state (i.e. a state indicating deactivation), and then one that that maps to the
* high-level UNIT_STARTING state (i.e. a state indicating activation). We do this so that
diff --git a/src/core/service.h b/src/core/service.h
index bfbb3261a8..0ca04caf24 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -187,8 +187,6 @@ struct Service {
bool main_pid_alien:1;
bool bus_name_good:1;
bool forbid_restart:1;
- /* Keep restart intention between UNIT_FAILED and UNIT_ACTIVATING */
- bool will_auto_restart:1;
bool start_timeout_defined:1;
bool exec_fd_hot:1;
diff --git a/src/core/unit.c b/src/core/unit.c
index 0b58d0498b..c076b5126f 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2703,9 +2703,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
if (ns != os && ns == UNIT_FAILED) {
log_unit_debug(u, "Unit entered failed state.");
-
- if (!(flags & UNIT_NOTIFY_WILL_AUTO_RESTART))
- unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode);
+ unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode);
}
if (UNIT_IS_ACTIVE_OR_RELOADING(ns) && !UNIT_IS_ACTIVE_OR_RELOADING(os)) {
@@ -2722,8 +2720,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
unit_log_resources(u);
}
- if (ns == UNIT_INACTIVE && !IN_SET(os, UNIT_FAILED, UNIT_INACTIVE, UNIT_MAINTENANCE) &&
- !(flags & UNIT_NOTIFY_WILL_AUTO_RESTART))
+ if (ns == UNIT_INACTIVE && !IN_SET(os, UNIT_FAILED, UNIT_INACTIVE, UNIT_MAINTENANCE))
unit_start_on_failure(u, "OnSuccess=", UNIT_ATOM_ON_SUCCESS, u->on_success_job_mode);
}
diff --git a/src/core/unit.h b/src/core/unit.h
index acbf74477e..77cc429803 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -903,7 +903,6 @@ void unit_notify_cgroup_oom(Unit *u, bool managed_oom);
typedef enum UnitNotifyFlags {
UNIT_NOTIFY_RELOAD_FAILURE = 1 << 0,
- UNIT_NOTIFY_WILL_AUTO_RESTART = 1 << 1,
} UnitNotifyFlags;
void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags);

View File

@ -0,0 +1,239 @@
From 762a8cff390322bb578b5844326a2b654e9d2c23 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 22 May 2023 08:35:53 +0800
Subject: [PATCH] core: drop UnitNotifyFlags
This essentially reverts 2ad2e41a72ec19159c0746a78e15ff880fe32a63.
No longer needed after dropping UNIT_NOTIFY_WILL_AUTO_RESTART.
(cherry picked from commit 96b09de500f9d658b2e49abf3be15e06f9bd1ca6)
Related: RHEL-118224
---
src/core/automount.c | 2 +-
src/core/device.c | 2 +-
src/core/mount.c | 3 +--
src/core/path.c | 2 +-
src/core/scope.c | 2 +-
src/core/service.c | 3 +--
src/core/slice.c | 2 +-
src/core/socket.c | 2 +-
src/core/swap.c | 2 +-
src/core/target.c | 2 +-
src/core/timer.c | 2 +-
src/core/unit.c | 10 +++++-----
src/core/unit.h | 6 +-----
13 files changed, 17 insertions(+), 23 deletions(-)
diff --git a/src/core/automount.c b/src/core/automount.c
index ae8399d1af..f559454a88 100644
--- a/src/core/automount.c
+++ b/src/core/automount.c
@@ -283,7 +283,7 @@ static void automount_set_state(Automount *a, AutomountState state) {
if (state != old_state)
log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state));
- unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static int automount_coldplug(Unit *u) {
diff --git a/src/core/device.c b/src/core/device.c
index f007bdfd9b..31f014d94b 100644
--- a/src/core/device.c
+++ b/src/core/device.c
@@ -174,7 +174,7 @@ static void device_set_state(Device *d, DeviceState state) {
if (state != old_state)
log_unit_debug(UNIT(d), "Changed %s -> %s", device_state_to_string(old_state), device_state_to_string(state));
- unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static void device_found_changed(Device *d, DeviceFound previous, DeviceFound now) {
diff --git a/src/core/mount.c b/src/core/mount.c
index 5789a253cd..1a6aca0aa8 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -753,8 +753,7 @@ static void mount_set_state(Mount *m, MountState state) {
if (state != old_state)
log_unit_debug(UNIT(m), "Changed %s -> %s", mount_state_to_string(old_state), mount_state_to_string(state));
- unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state],
- m->reload_result == MOUNT_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE);
+ unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS);
}
static int mount_coldplug(Unit *u) {
diff --git a/src/core/path.c b/src/core/path.c
index 6f850244f1..6b548d40bc 100644
--- a/src/core/path.c
+++ b/src/core/path.c
@@ -475,7 +475,7 @@ static void path_set_state(Path *p, PathState state) {
if (state != old_state)
log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state));
- unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify);
diff --git a/src/core/scope.c b/src/core/scope.c
index 4e1a954c6c..1be18c21d6 100644
--- a/src/core/scope.c
+++ b/src/core/scope.c
@@ -126,7 +126,7 @@ static void scope_set_state(Scope *s, ScopeState state) {
if (state != old_state)
log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state));
- unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static int scope_add_default_dependencies(Scope *s) {
diff --git a/src/core/service.c b/src/core/service.c
index 55e8bf5182..a3c6b1380f 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1159,8 +1159,7 @@ static void service_set_state(Service *s, ServiceState state) {
if (old_state != state)
log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state));
- unit_notify(UNIT(s), table[old_state], table[state],
- s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE);
+ unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS);
}
static usec_t service_coldplug_timeout(Service *s) {
diff --git a/src/core/slice.c b/src/core/slice.c
index 8f913a8d45..cb6670ff45 100644
--- a/src/core/slice.c
+++ b/src/core/slice.c
@@ -43,7 +43,7 @@ static void slice_set_state(Slice *t, SliceState state) {
slice_state_to_string(old_state),
slice_state_to_string(state));
- unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static int slice_add_parent_slice(Slice *s) {
diff --git a/src/core/socket.c b/src/core/socket.c
index ebb83cd5b0..c916ff7ee5 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -1838,7 +1838,7 @@ static void socket_set_state(Socket *s, SocketState state) {
if (state != old_state)
log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state));
- unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static int socket_coldplug(Unit *u) {
diff --git a/src/core/swap.c b/src/core/swap.c
index 458c935b0c..535657d3b5 100644
--- a/src/core/swap.c
+++ b/src/core/swap.c
@@ -586,7 +586,7 @@ static void swap_set_state(Swap *s, SwapState state) {
if (state != old_state)
log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state));
- unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
/* If there other units for the same device node have a job
queued it might be worth checking again if it is runnable
diff --git a/src/core/target.c b/src/core/target.c
index 6225df5b0d..3519b4b653 100644
--- a/src/core/target.c
+++ b/src/core/target.c
@@ -31,7 +31,7 @@ static void target_set_state(Target *t, TargetState state) {
target_state_to_string(old_state),
target_state_to_string(state));
- unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static int target_add_default_dependencies(Target *t) {
diff --git a/src/core/timer.c b/src/core/timer.c
index b96e88af90..310bc79903 100644
--- a/src/core/timer.c
+++ b/src/core/timer.c
@@ -298,7 +298,7 @@ static void timer_set_state(Timer *t, TimerState state) {
if (state != old_state)
log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state));
- unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], 0);
+ unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
}
static void timer_enter_waiting(Timer *t, bool time_change);
diff --git a/src/core/unit.c b/src/core/unit.c
index c076b5126f..53098c5b08 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2017,7 +2017,7 @@ int unit_reload(Unit *u) {
if (!UNIT_VTABLE(u)->reload) {
/* Unit doesn't have a reload function, but we need to propagate the reload anyway */
- unit_notify(u, unit_active_state(u), unit_active_state(u), 0);
+ unit_notify(u, unit_active_state(u), unit_active_state(u), /* reload_success = */ true);
return 0;
}
@@ -2547,7 +2547,7 @@ static void unit_emit_audit_stop(Unit *u, UnitActiveState state) {
}
}
-static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) {
+static bool unit_process_job(Job *j, UnitActiveState ns, bool reload_success) {
bool unexpected = false;
JobResult result;
@@ -2590,7 +2590,7 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags)
if (j->state == JOB_RUNNING) {
if (ns == UNIT_ACTIVE)
- job_finish_and_invalidate(j, (flags & UNIT_NOTIFY_RELOAD_FAILURE) ? JOB_FAILED : JOB_DONE, true, false);
+ job_finish_and_invalidate(j, reload_success ? JOB_DONE : JOB_FAILED, true, false);
else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING)) {
unexpected = true;
@@ -2621,7 +2621,7 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags)
return unexpected;
}
-void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags) {
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) {
const char *reason;
Manager *m;
@@ -2686,7 +2686,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
/* Let's propagate state changes to the job */
if (u->job)
- unexpected = unit_process_job(u->job, ns, flags);
+ unexpected = unit_process_job(u->job, ns, reload_success);
else
unexpected = true;
diff --git a/src/core/unit.h b/src/core/unit.h
index 77cc429803..08969d4cb9 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -901,11 +901,7 @@ int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t cont
void unit_notify_cgroup_oom(Unit *u, bool managed_oom);
-typedef enum UnitNotifyFlags {
- UNIT_NOTIFY_RELOAD_FAILURE = 1 << 0,
-} UnitNotifyFlags;
-
-void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags);
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success);
int unit_watch_pid(Unit *u, pid_t pid, bool exclusive);
void unit_unwatch_pid(Unit *u, pid_t pid);

View File

@ -0,0 +1,128 @@
From b8dabd7117205eadc185c9b86fcaa10e1d0e5f84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 15 Nov 2023 17:17:12 +0100
Subject: [PATCH] core: split out the helper to serialize/deserialize
ratelimits
(cherry picked from commit 07a6647abbd74288a00b1b8f17ec9a9cce080ae4)
Related: RHEL-213655
---
src/core/manager-serialize.c | 23 ++---------------------
src/shared/serialize.c | 27 +++++++++++++++++++++++++++
src/shared/serialize.h | 3 +++
3 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c
index a87d490219..74853181b7 100644
--- a/src/core/manager-serialize.c
+++ b/src/core/manager-serialize.c
@@ -164,13 +164,7 @@ int manager_serialize(
(void) serialize_item_format(f, "user-lookup", "%i %i", copy0, copy1);
}
- (void) serialize_item_format(f,
- "dump-ratelimit",
- USEC_FMT " " USEC_FMT " %u %u",
- m->dump_ratelimit.begin,
- m->dump_ratelimit.interval,
- m->dump_ratelimit.num,
- m->dump_ratelimit.burst);
+ (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit);
bus_track_serialize(m->subscribed, f, "subscribed");
@@ -558,20 +552,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
if (deserialize_varlink_sockets)
(void) varlink_server_deserialize_one(m->varlink_server, val, fds);
} else if ((val = startswith(l, "dump-ratelimit="))) {
- usec_t begin, interval;
- unsigned num, burst;
-
- if (sscanf(val, USEC_FMT " " USEC_FMT " %u %u", &begin, &interval, &num, &burst) != 4)
- log_notice("Failed to parse dump ratelimit, ignoring: %s", val);
- else {
- /* If we changed the values across versions, flush the counter */
- if (interval != m->dump_ratelimit.interval || burst != m->dump_ratelimit.burst)
- m->dump_ratelimit.num = 0;
- else
- m->dump_ratelimit.num = num;
- m->dump_ratelimit.begin = begin;
- }
-
+ deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val);
} else {
ManagerTimestamp q;
diff --git a/src/shared/serialize.c b/src/shared/serialize.c
index cd48286355..5c73fc5f8e 100644
--- a/src/shared/serialize.c
+++ b/src/shared/serialize.c
@@ -130,6 +130,17 @@ int serialize_strv(FILE *f, const char *key, char **l) {
return ret;
}
+int serialize_ratelimit(FILE *f, const char *key, const RateLimit *rl) {
+ assert(rl);
+
+ return serialize_item_format(f, key,
+ USEC_FMT " " USEC_FMT " %u %u",
+ rl->begin,
+ rl->interval,
+ rl->num,
+ rl->burst);
+}
+
int deserialize_usec(const char *value, usec_t *ret) {
int r;
@@ -194,6 +205,22 @@ int deserialize_environment(const char *value, char ***list) {
return 0;
}
+void deserialize_ratelimit(RateLimit *rl, const char *name, const char *value) {
+ usec_t begin, interval;
+ unsigned num, burst;
+
+ assert(rl);
+ assert(name);
+ assert(value);
+
+ if (sscanf(value, USEC_FMT " " USEC_FMT " %u %u", &begin, &interval, &num, &burst) != 4)
+ return log_notice("Failed to parse %s, ignoring: %s", name, value);
+
+ /* Preserve the counter only if the configuration didn't change. */
+ rl->num = (interval == rl->interval && burst == rl->burst) ? num : 0;
+ rl->begin = begin;
+}
+
int open_serialization_fd(const char *ident) {
int fd;
diff --git a/src/shared/serialize.h b/src/shared/serialize.h
index 6d4f1ef418..3fc1e60911 100644
--- a/src/shared/serialize.h
+++ b/src/shared/serialize.h
@@ -5,6 +5,7 @@
#include "fdset.h"
#include "macro.h"
+#include "ratelimit.h"
#include "string-util.h"
#include "time-util.h"
@@ -15,6 +16,7 @@ int serialize_fd(FILE *f, FDSet *fds, const char *key, int fd);
int serialize_usec(FILE *f, const char *key, usec_t usec);
int serialize_dual_timestamp(FILE *f, const char *key, const dual_timestamp *t);
int serialize_strv(FILE *f, const char *key, char **l);
+int serialize_ratelimit(FILE *f, const char *key, const RateLimit *rl);
static inline int serialize_bool(FILE *f, const char *key, bool b) {
return serialize_item(f, key, yes_no(b));
@@ -23,5 +25,6 @@ static inline int serialize_bool(FILE *f, const char *key, bool b) {
int deserialize_usec(const char *value, usec_t *timestamp);
int deserialize_dual_timestamp(const char *value, dual_timestamp *t);
int deserialize_environment(const char *value, char ***environment);
+void deserialize_ratelimit(RateLimit *rl, const char *name, const char *value);
int open_serialization_fd(const char *ident);

View File

@ -0,0 +1,244 @@
From 9d258e84de4235fe807990782752f8b9d8e54c59 Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Wed, 13 May 2026 16:20:55 +0200
Subject: [PATCH] core: make manager event loop rate limit configurable
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit e1c63fd4c0657a7fc7b749c64283ab4dbc6fb39e)
Resolves: RHEL-213655
---
man/org.freedesktop.systemd1.xml | 12 ++++++++++++
man/systemd-system.conf.xml | 15 +++++++++++++++
src/core/dbus-manager.c | 2 ++
src/core/main.c | 31 +++++++++++++++++++++++++++++++
src/core/manager-serialize.c | 4 ++++
src/core/manager.c | 3 +--
src/core/manager.h | 3 +++
src/core/system.conf.in | 2 ++
src/core/user.conf.in | 2 ++
9 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 8298b726b7..51e017bf10 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -524,6 +524,10 @@ node /org/freedesktop/systemd1 {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t DefaultTasksMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly t EventLoopRateLimitIntervalUSec = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly u EventLoopRateLimitBurst = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t TimerSlackNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s DefaultOOMPolicy = '...';
@@ -776,6 +780,10 @@ node /org/freedesktop/systemd1 {
<!--property DefaultTasksMax is not documented!-->
+ <!--property EventLoopRateLimitIntervalUSec is not documented!-->
+
+ <!--property EventLoopRateLimitBurst is not documented!-->
+
<!--property TimerSlackNSec is not documented!-->
<!--property DefaultOOMPolicy is not documented!-->
@@ -1200,6 +1208,10 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-property" generated="True" extra-ref="DefaultTasksMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EventLoopRateLimitIntervalUSec"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EventLoopRateLimitBurst"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="TimerSlackNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="DefaultOOMPolicy"/>
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
index 00de04d426..e563be8615 100644
--- a/man/systemd-system.conf.xml
+++ b/man/systemd-system.conf.xml
@@ -550,6 +550,21 @@
<para>If the value is <literal>/</literal>, only labels specified with <varname>SmackProcessLabel=</varname>
are assigned and the compile-time default is ignored.</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>EventLoopRateLimitIntervalSec=</varname></term>
+ <term><varname>EventLoopRateLimitBurst=</varname></term>
+
+ <listitem><para>Configures the rate limiting applied to the manager's main event loop. If the event
+ loop iterates more than <varname>EventLoopRateLimitBurst=</varname> times within
+ <varname>EventLoopRateLimitIntervalSec=</varname>, event processing is briefly paused to prevent
+ excessive CPU usage. <varname>EventLoopRateLimitIntervalSec=</varname> defaults to 1s.
+ <varname>EventLoopRateLimitBurst=</varname> defaults to 50000. These settings can also be set on the
+ kernel command line via
+ <varname>systemd.event_loop_ratelimit_interval_sec=</varname> and
+ <varname>systemd.event_loop_ratelimit_burst=</varname>.</para></listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 16c9680d80..2537c4adf8 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -2911,6 +2911,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultTasksMax", "t", bus_property_get_tasks_max, offsetof(Manager, default_tasks_max), 0),
+ SD_BUS_PROPERTY("EventLoopRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Manager, event_loop_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("EventLoopRateLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, event_loop_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultOOMPolicy", "s", bus_property_get_oom_policy, offsetof(Manager, default_oom_policy), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultOOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/core/main.c b/src/core/main.c
index 18f5781126..1e45ed6bd6 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -173,6 +173,8 @@ static size_t arg_random_seed_size;
static int arg_default_oom_score_adjust;
static bool arg_default_oom_score_adjust_set;
static char *arg_default_smack_process_label;
+static usec_t arg_event_loop_ratelimit_interval_sec;
+static unsigned arg_event_loop_ratelimit_burst;
/* A copy of the original environment block */
static char **saved_env = NULL;
@@ -483,6 +485,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
arg_random_seed = sz > 0 ? p : mfree(p);
arg_random_seed_size = sz;
+ } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_interval_sec")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = parse_sec(value, &arg_event_loop_ratelimit_interval_sec);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_interval_sec= argument '%s', ignoring: %m", value);
+ return 0;
+ }
+
+ } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_burst")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = safe_atou(value, &arg_event_loop_ratelimit_burst);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_burst= argument '%s', ignoring: %m", value);
+ return 0;
+ }
+
} else if (streq(key, "quiet") && !value) {
if (arg_show_status == _SHOW_STATUS_INVALID)
@@ -662,6 +686,8 @@ static int parse_config_file(void) {
{ "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action },
{ "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_default_oom_policy },
{ "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL },
+ { "Manager", "EventLoopRateLimitIntervalSec", config_parse_sec, 0, &arg_event_loop_ratelimit_interval_sec },
+ { "Manager", "EventLoopRateLimitBurst", config_parse_unsigned, 0, &arg_event_loop_ratelimit_burst },
#if ENABLE_SMACK
{ "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_default_smack_process_label },
#else
@@ -764,6 +790,8 @@ static void set_manager_settings(Manager *m) {
m->confirm_spawn = arg_confirm_spawn;
m->service_watchdogs = arg_service_watchdogs;
m->cad_burst_action = arg_cad_burst_action;
+ m->event_loop_ratelimit.interval = arg_event_loop_ratelimit_interval_sec;
+ m->event_loop_ratelimit.burst = arg_event_loop_ratelimit_burst;
manager_set_watchdog(m, WATCHDOG_RUNTIME, arg_runtime_watchdog);
manager_set_watchdog(m, WATCHDOG_REBOOT, arg_reboot_watchdog);
@@ -2495,6 +2523,9 @@ static void reset_arguments(void) {
arg_default_oom_score_adjust_set = false;
arg_default_smack_process_label = mfree(arg_default_smack_process_label);
+ arg_event_loop_ratelimit_interval_sec = 1 * USEC_PER_SEC;
+ arg_event_loop_ratelimit_burst = 50000;
+
}
static void determine_default_oom_score_adjust(void) {
diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c
index 74853181b7..125783a48f 100644
--- a/src/core/manager-serialize.c
+++ b/src/core/manager-serialize.c
@@ -166,6 +166,8 @@ int manager_serialize(
(void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit);
+ (void) serialize_ratelimit(f, "event-loop-ratelimit", &m->event_loop_ratelimit);
+
bus_track_serialize(m->subscribed, f, "subscribed");
r = dynamic_user_serialize(m, f, fds);
@@ -553,6 +555,8 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
(void) varlink_server_deserialize_one(m->varlink_server, val, fds);
} else if ((val = startswith(l, "dump-ratelimit="))) {
deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val);
+ } else if ((val = startswith(l, "event-loop-ratelimit="))) {
+ deserialize_ratelimit(&m->event_loop_ratelimit, "event-loop-ratelimit", val);
} else {
ManagerTimestamp q;
diff --git a/src/core/manager.c b/src/core/manager.c
index 79408b18dc..ebfc4d3a9a 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -3024,7 +3024,6 @@ static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t use
}
int manager_loop(Manager *m) {
- RateLimit rl = { .interval = 1*USEC_PER_SEC, .burst = 50000 };
int r;
assert(m);
@@ -3041,7 +3040,7 @@ int manager_loop(Manager *m) {
(void) watchdog_ping();
- if (!ratelimit_below(&rl)) {
+ if (!ratelimit_below(&m->event_loop_ratelimit)) {
/* Yay, something is going seriously wrong, pause a little */
log_warning("Looping too fast. Throttling execution a little.");
sleep(1);
diff --git a/src/core/manager.h b/src/core/manager.h
index 4d5b2e0602..b7917732b5 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -465,6 +465,9 @@ struct Manager {
/* Dump*() are slow, so always rate limit them to 10 per 10 minutes */
RateLimit dump_ratelimit;
+
+ /* Rate limit for the manager event loop */
+ RateLimit event_loop_ratelimit;
};
static inline usec_t manager_default_timeout_abort_usec(Manager *m) {
diff --git a/src/core/system.conf.in b/src/core/system.conf.in
index 5d1f6d24f0..31f0f95b7a 100644
--- a/src/core/system.conf.in
+++ b/src/core/system.conf.in
@@ -75,3 +75,5 @@
#DefaultLimitRTTIME=
#DefaultOOMPolicy=stop
#DefaultSmackProcessLabel=
+#EventLoopRateLimitIntervalSec=1s
+#EventLoopRateLimitBurst=50000
diff --git a/src/core/user.conf.in b/src/core/user.conf.in
index b69974978e..0da7ae8be3 100644
--- a/src/core/user.conf.in
+++ b/src/core/user.conf.in
@@ -48,3 +48,5 @@
#DefaultLimitRTPRIO=
#DefaultLimitRTTIME=
#DefaultSmackProcessLabel=
+#EventLoopRateLimitIntervalSec=1s
+#EventLoopRateLimitBurst=50000

View File

@ -0,0 +1,53 @@
From c34d349f0e6467f06484aa93f4b422437b9ee2ca Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 19 Oct 2023 17:04:04 +0200
Subject: [PATCH] iovec-util: add some useful helpers for dealing with iovecs
that refer to dynamic memory
(cherry picked from commit 3a856171c2dc78f040c560142d3b275de1c1bb0a)
Related: RHEL-212610
---
src/basic/io-util.h | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/basic/io-util.h b/src/basic/io-util.h
index 3afb134266..4646666a99 100644
--- a/src/basic/io-util.h
+++ b/src/basic/io-util.h
@@ -8,6 +8,7 @@
#include <sys/types.h>
#include <sys/uio.h>
+#include "alloc-util.h"
#include "macro.h"
#include "time-util.h"
@@ -79,6 +80,27 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
+#define TAKE_IOVEC(p) TAKE_GENERIC((p), struct iovec, IOVEC_NULL)
+
+static inline void iovec_done(struct iovec *iovec) {
+ /* A _cleanup_() helper that frees the iov_base in the iovec */
+ assert(iovec);
+
+ iovec->iov_base = mfree(iovec->iov_base);
+ iovec->iov_len = 0;
+}
+
+static inline void iovec_done_erase(struct iovec *iovec) {
+ assert(iovec);
+
+ iovec->iov_base = erase_and_free(iovec->iov_base);
+ iovec->iov_len = 0;
+}
+
+static inline bool iovec_is_set(const struct iovec *iov) {
+ return iov && iov->iov_len > 0 && iov->iov_base;
+}
+
char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);

View File

@ -0,0 +1,78 @@
From 8159189ecc79ff507976502620034938da98400d Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Mon, 14 Jul 2025 07:56:49 -0400
Subject: [PATCH] repart: use iovec structure for --key-file
Use the iovec structure for --key-file, instead of a char pointer and a size.
(cherry picked from commit d4397999324c5093380fc1e6c8ce430a58e57145)
Related: RHEL-212610
---
src/partition/repart.c | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 5db931e7bc..08d43ce6c9 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -116,8 +116,7 @@ static bool arg_size_auto = false;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
-static void *arg_key = NULL;
-static size_t arg_key_size = 0;
+static struct iovec arg_key = {};
static EVP_PKEY *arg_private_key = NULL;
static X509 *arg_certificate = NULL;
static char *arg_tpm2_device = NULL;
@@ -132,7 +131,7 @@ static bool arg_split = false;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
-STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_key, iovec_done_erase);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
@@ -3011,8 +3010,8 @@ static int partition_encrypt(
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
- strempty(arg_key),
- arg_key_size);
+ strempty(arg_key.iov_base),
+ arg_key.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
}
@@ -5237,20 +5236,21 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_KEY_FILE: {
- _cleanup_(erase_and_freep) char *k = NULL;
- size_t n = 0;
+ struct iovec key = {};
r = read_full_file_full(
- AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
+ AT_FDCWD, optarg,
+ /* offset= */ UINT64_MAX,
+ /* size= */ SIZE_MAX,
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
- NULL,
- &k, &n);
+ /* bind_name= */ NULL,
+ (char **) &key.iov_base,
+ &key.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
- erase_and_free(arg_key);
- arg_key = TAKE_PTR(k);
- arg_key_size = n;
+ iovec_done_erase(&arg_key);
+ arg_key = key;
break;
}

View File

@ -0,0 +1,159 @@
From 53bd7705fa61caef84ac8cbbda27d5b198de9841 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 3 Jul 2025 08:08:53 -0400
Subject: [PATCH] repart: make --tpm2-pcrs also configurable in repart.d/*
Add repart.d TPM2PCRs= option with the same syntax as --tpm2-pcrs.
This allows a per-partition pcr binding, and not rely on a global config
applicable to all partitions.
The global --tpm2-pcrs overrides TPM2PCRs config. If none of them
is defined, rely on default.
(cherry picked from commit 49dcc89ddc15651ebca8da7a13e5c5b08ec247cb)
Resolves: RHEL-212610
---
man/repart.d.xml | 12 +++++++++++
src/partition/repart.c | 46 ++++++++++++++++++++++++++++++++++++------
2 files changed, 52 insertions(+), 6 deletions(-)
diff --git a/man/repart.d.xml b/man/repart.d.xml
index ebbb31cc20..6cc60e53a2 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -581,6 +581,18 @@
below. Defaults to <literal>%t</literal>. To disable split artifact generation for a partition, set
<varname>SplitName=</varname> to <literal>-</literal>.</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>TPM2PCRs=</varname></term>
+
+ <listitem><para>Configures the list of PCRs to use for LUKS2 volumes configured with
+ the <varname>Encrypt=tpm2</varname> setting in partition files.
+ This option take the same parameters as the similary named options to
+ <citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ and have the same effect on partitions where TPM2 enrollment is requested.
+ This option will be overridden by the global <varname>--tpm2-pcrs=</varname> option.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 08d43ce6c9..d3fcf3b86b 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -198,6 +198,8 @@ struct Partition {
char **copy_files;
char **make_directories;
EncryptMode encrypt;
+ Tpm2PCRValue *tpm2_hash_pcr_values;
+ size_t tpm2_n_hash_pcr_values;
VerityMode verity;
char *verity_match_key;
@@ -328,6 +330,7 @@ static Partition* partition_free(Partition *p) {
free(p->format);
strv_free(p->copy_files);
strv_free(p->make_directories);
+ free(p->tpm2_hash_pcr_values);
free(p->verity_match_key);
free(p->roothash);
@@ -354,6 +357,7 @@ static void partition_foreignize(Partition *p) {
p->format = mfree(p->format);
p->copy_files = strv_free(p->copy_files);
p->make_directories = strv_free(p->make_directories);
+ p->tpm2_hash_pcr_values = mfree(p->tpm2_hash_pcr_values);
p->verity_match_key = mfree(p->verity_match_key);
p->new_uuid = SD_ID128_NULL;
@@ -1470,6 +1474,33 @@ static int config_parse_uuid(
return 0;
}
+static int config_parse_tpm2_pcrs(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Partition *partition = ASSERT_PTR(data);
+
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Clear existing PCR values if empty */
+ partition->tpm2_hash_pcr_values = mfree(partition->tpm2_hash_pcr_values);
+ partition->tpm2_n_hash_pcr_values = 0;
+ return 0;
+ }
+
+ return tpm2_parse_pcr_argument_append(rvalue, &partition->tpm2_hash_pcr_values,
+ &partition->tpm2_n_hash_pcr_values);
+}
+
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode");
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
@@ -1498,6 +1529,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
{ "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
{ "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
{ "Partition", "SplitName", config_parse_string, 0, &p->split_name_format },
+ { "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p },
{}
};
int r;
@@ -3026,6 +3058,8 @@ static int partition_encrypt(
size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0;
ssize_t base64_encoded_size;
int keyslot;
+ Tpm2PCRValue *pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_hash_pcr_values : p->tpm2_hash_pcr_values;
+ size_t n_pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_n_hash_pcr_values : p->tpm2_n_hash_pcr_values;
if (arg_tpm2_public_key_pcr_mask != 0) {
r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size);
@@ -3050,29 +3084,29 @@ static int partition_encrypt(
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
- r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values);
+ r = tpm2_pcr_read_missing_values(tpm2_context, pcr_values, n_pcr_values);
if (r < 0)
return log_error_errno(r, "Could not read pcr values: %m");
uint16_t hash_pcr_bank = 0;
uint32_t hash_pcr_mask = 0;
- if (arg_tpm2_n_hash_pcr_values > 0) {
+ if (n_pcr_values > 0) {
size_t hash_count;
- r = tpm2_pcr_values_hash_count(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &hash_count);
+ r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count);
if (r < 0)
return log_error_errno(r, "Could not get hash count: %m");
if (hash_count > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected.");
- hash_pcr_bank = arg_tpm2_hash_pcr_values[0].hash;
- r = tpm2_pcr_values_to_mask(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
+ hash_pcr_bank = pcr_values[0].hash;
+ r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, hash_pcr_bank, &hash_pcr_mask);
if (r < 0)
return log_error_errno(r, "Could not get hash mask: %m");
}
TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
- r = tpm2_calculate_sealing_policy(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, pubkey ? &public : NULL, /* use_pin= */ false, &policy);
+ r = tpm2_calculate_sealing_policy(pcr_values, n_pcr_values, pubkey ? &public : NULL, /* use_pin= */ false, &policy);
if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m");

View File

@ -0,0 +1,196 @@
From 068788ddbadbfa7fe3583b732a2957dc4bfc60c5 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Mon, 14 Jul 2025 05:51:49 -0400
Subject: [PATCH] repart: make --key-file also configurable in repart.d/*
Add repart.d KeyFile= option with the same syntax as --key-file.
This allows a per-partition key file encryption, and not rely on a global key
applicable to all partitions.
The global --key-file overrides KeyFile config. If none of them is
defined, rely on default.
(cherry picked from commit eb44fa4d198d1da11c998f77bc88f95aaf67e186)
Resolves: RHEL-212610
---
man/repart.d.xml | 11 +++++++
man/systemd-repart.xml | 9 ++---
src/partition/repart.c | 74 +++++++++++++++++++++++++++++++++---------
3 files changed, 74 insertions(+), 20 deletions(-)
diff --git a/man/repart.d.xml b/man/repart.d.xml
index 6cc60e53a2..a4f49af39b 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -593,6 +593,17 @@
This option will be overridden by the global <varname>--tpm2-pcrs=</varname> option.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>KeyFile=</varname></term>
+
+ <listitem><para>Takes a file system path. This path must be absolute, otherwise the option is ignored.
+ Configures the encryption key to use when setting up LUKS2 volumes configured with the
+ <varname>Encrypt=key-file</varname> setting in partition files. Please refer to the documentation of
+ <varname>--key-file=</varname> for more details. This option will be overridden by the global
+ <varname>--key-file=</varname> option.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 3585cbf107..f33e2d772a 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -306,10 +306,11 @@
<listitem><para>Takes a file system path. Configures the encryption key to use when setting up LUKS2
volumes configured with the <varname>Encrypt=key-file</varname> setting in partition files. Should
refer to a regular file containing the key, or an <constant>AF_UNIX</constant> stream socket in the
- file system. In the latter case a connection is made to it and the key read from it. If this switch
- is not specified the empty key (i.e. zero length key) is used. This behaviour is useful for setting
- up encrypted partitions during early first boot that receive their user-supplied password only in a
- later setup step.</para></listitem>
+ file system. In the latter case, a connection is made to it and the key read from it. If this switch
+ is not specified, and no <varname>KeyFile=</varname> is specified in the partition file, the empty
+ key (i.e. zero length key) is used. This behaviour is useful for setting up encrypted partitions during
+ early first boot that receive their user-supplied password only in a later setup step.</para>
+ </listitem>
</varlistentry>
<varlistentry>
diff --git a/src/partition/repart.c b/src/partition/repart.c
index d3fcf3b86b..c2643231c3 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -198,6 +198,7 @@ struct Partition {
char **copy_files;
char **make_directories;
EncryptMode encrypt;
+ struct iovec key;
Tpm2PCRValue *tpm2_hash_pcr_values;
size_t tpm2_n_hash_pcr_values;
VerityMode verity;
@@ -333,6 +334,8 @@ static Partition* partition_free(Partition *p) {
free(p->tpm2_hash_pcr_values);
free(p->verity_match_key);
+ iovec_done_erase(&p->key);
+
free(p->roothash);
free(p->split_name_format);
@@ -360,6 +363,8 @@ static void partition_foreignize(Partition *p) {
p->tpm2_hash_pcr_values = mfree(p->tpm2_hash_pcr_values);
p->verity_match_key = mfree(p->verity_match_key);
+ iovec_done_erase(&p->key);
+
p->new_uuid = SD_ID128_NULL;
p->new_uuid_is_set = false;
p->priority = 0;
@@ -1501,6 +1506,51 @@ static int config_parse_tpm2_pcrs(
&partition->tpm2_n_hash_pcr_values);
}
+static int parse_key_file(const char *filename, struct iovec *key) {
+ _cleanup_(erase_and_freep) char *k = NULL;
+ size_t n = 0;
+ int r;
+
+ r = read_full_file_full(
+ AT_FDCWD, filename,
+ /* offset= */ UINT64_MAX,
+ /* size= */ SIZE_MAX,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+ /* bind_name= */ NULL,
+ &k, &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read key file '%s': %m", filename);
+
+ iovec_done_erase(key);
+ *key = IOVEC_MAKE(TAKE_PTR(k), n);
+
+ return 0;
+}
+
+static int config_parse_key_file(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Partition *partition = ASSERT_PTR(userdata);
+
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ iovec_done_erase(&partition->key);
+ return 0;
+ }
+
+ return parse_key_file(rvalue, &partition->key);
+}
+
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode");
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
@@ -1530,6 +1580,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
{ "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
{ "Partition", "SplitName", config_parse_string, 0, &p->split_name_format },
{ "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p },
+ { "Partition", "KeyFile", config_parse_key_file, 0, p },
{}
};
int r;
@@ -3037,13 +3088,16 @@ static int partition_encrypt(
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) {
+ /* Use partition-specific key if available, otherwise fall back to global key */
+ struct iovec *iovec_key = arg_key.iov_base ? &arg_key : &p->key;
+
r = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
- strempty(arg_key.iov_base),
- arg_key.iov_len);
+ strempty(iovec_key->iov_base),
+ iovec_key->iov_len);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
}
@@ -5270,21 +5324,9 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_KEY_FILE: {
- struct iovec key = {};
-
- r = read_full_file_full(
- AT_FDCWD, optarg,
- /* offset= */ UINT64_MAX,
- /* size= */ SIZE_MAX,
- READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
- /* bind_name= */ NULL,
- (char **) &key.iov_base,
- &key.iov_len);
+ r = parse_key_file(optarg, &arg_key);
if (r < 0)
- return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
-
- iovec_done_erase(&arg_key);
- arg_key = key;
+ return r;
break;
}

View File

@ -0,0 +1,27 @@
From 77567a88f1d9767fed57ad520d9ff1956f3401c6 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 19 Sep 2025 20:24:06 +0900
Subject: [PATCH] man/repart: fix typo
Follow-up for 49dcc89ddc15651ebca8da7a13e5c5b08ec247cb.
(cherry picked from commit cbdbf68a7266c515db12c87542483b907fe7becf)
Related: RHEL-212610
---
man/repart.d.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/man/repart.d.xml b/man/repart.d.xml
index a4f49af39b..6ce2e0eaf4 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -587,7 +587,7 @@
<listitem><para>Configures the list of PCRs to use for LUKS2 volumes configured with
the <varname>Encrypt=tpm2</varname> setting in partition files.
- This option take the same parameters as the similary named options to
+ This option take the same parameters as the similarly named options to
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and have the same effect on partitions where TPM2 enrollment is requested.
This option will be overridden by the global <varname>--tpm2-pcrs=</varname> option.</para>

View File

@ -21,7 +21,7 @@
Name: systemd
Url: https://systemd.io
Version: 252
Release: 73%{?dist}
Release: 76%{?dist}
# For a breakdown of the licensing, see README
License: LGPLv2+ and MIT and GPLv2+
Summary: System and Service Manager
@ -1440,6 +1440,19 @@ Patch1354: 1354-ci-bump-super-linter-to-v8.7.0.patch
Patch1355: 1355-ci-explicitly-disable-multi-status-for-Super-Linter.patch
Patch1356: 1356-github-linter-disable-ENABLE_GITHUB_PULL_REQUEST_SUM.patch
Patch1357: 1357-test-install-iscsi-gen-initiatorname-from-iscsi-init.patch
Patch1358: 1358-resolved-replace-assert-with-error-return-in-DNSSEC-.patch
Patch1359: 1359-pid1-introduce-new-SERVICE_-DEAD-FAILED-_BEFORE_AUTO.patch
Patch1360: 1360-service-add-new-RestartMode-option.patch
Patch1361: 1361-core-service-drop-unneeded-unit_add_to_gc_queue.patch
Patch1362: 1362-core-get-rid-of-unused-Service.will_auto_restart-log.patch
Patch1363: 1363-core-drop-UnitNotifyFlags.patch
Patch1364: 1364-core-split-out-the-helper-to-serialize-deserialize-r.patch
Patch1365: 1365-core-make-manager-event-loop-rate-limit-configurable.patch
Patch1366: 1366-iovec-util-add-some-useful-helpers-for-dealing-with-.patch
Patch1367: 1367-repart-use-iovec-structure-for-key-file.patch
Patch1368: 1368-repart-make-tpm2-pcrs-also-configurable-in-repart.d.patch
Patch1369: 1369-repart-make-key-file-also-configurable-in-repart.d.patch
Patch1370: 1370-man-repart-fix-typo.patch
# Downstream-only patches (90009999)
@ -2317,6 +2330,21 @@ systemd-hwdb update &>/dev/null || :
%{_prefix}/lib/dracut/modules.d/70rhel-net-naming-sysattrs/*
%changelog
* Wed Aug 05 2026 systemd maintenance team <systemd-maint@redhat.com> - 252-76
- resolved: replace assert() with error return in DNSSEC verify functions (RHEL-208860)
- pid1: introduce new SERVICE_{DEAD|FAILED}_BEFORE_AUTO_RESTART service substates (RHEL-137251)
- service: add new RestartMode option (RHEL-137251)
- core/service: drop unneeded unit_add_to_gc_queue() (RHEL-137251)
- core: get rid of unused Service.will_auto_restart logic (RHEL-118224)
- core: drop UnitNotifyFlags (RHEL-118224)
- core: split out the helper to serialize/deserialize ratelimits (RHEL-213655)
- core: make manager event loop rate limit configurable (RHEL-213655)
- iovec-util: add some useful helpers for dealing with iovecs that refer to dynamic memory (RHEL-212610)
- repart: use iovec structure for --key-file (RHEL-212610)
- repart: make --tpm2-pcrs also configurable in repart.d/* (RHEL-212610)
- repart: make --key-file also configurable in repart.d/* (RHEL-212610)
- man/repart: fix typo (RHEL-212610)
* Tue Jul 21 2026 systemd maintenance team <systemd-maint@redhat.com> - 252-73
- hwdb,rules: add 82-net-auto-link-local.{hwdb,rules} to build (RHEL-180938)
- test: reenable test for cg_get_keyed_attribute() (RHEL-180940)