manager: add 'needs-stop/needs-start' markers

Useful for packaging scripts, when units are removed.
When multiple markers are assigned without +/-, the last one wins.
When using +/-, the job merging logic is followed to the extent possible.
This commit is contained in:
Luca Boccassi
2026-02-23 13:05:23 +00:00
parent a2a78602b0
commit f3a34fcc3a
10 changed files with 240 additions and 21 deletions
+27 -6
View File
@@ -2644,12 +2644,33 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
</para>
<para><varname>Markers</varname> is an array of string flags that can be set using
<function>SetUnitProperties()</function> to indicate that the service should be reloaded or
restarted. Currently known values are <literal>needs-restart</literal> and
<literal>needs-reload</literal>. Package scripts may use the first to mark units for later restart when
a new version of the package is installed. Configuration management scripts may use the second to mark
units for a later reload when the configuration is adjusted. Those flags are not set by the manager,
except to unset as appropriate when the unit is stopped, restarted, or reloaded.</para>
<function>SetUnitProperties()</function> to indicate that the service should be reloaded or restarted.
Currently known values are <literal>needs-restart</literal>, <literal>needs-stop</literal>,
<literal>needs-start</literal> and <literal>needs-reload</literal>. Package scripts may use the first
three to mark units for later restart or start or stop when a new version of the package is installed
or removed. Configuration management scripts may use the fourth to mark units for a later reload when
the configuration is adjusted. Those flags are not set by the manager, except to unset as appropriate
when the unit is stopped, restarted, or reloaded. When markers are set, they are normalized according
to the following precedence rules, modeled after the job type merging logic. When new markers are
applied incrementally (using the <literal>+</literal> prefix), conflicting existing markers are
cleared before the new markers are merged in:
<itemizedlist>
<listitem><para><literal>needs-reload</literal> loses against all other markers. If any of
<literal>needs-restart</literal>, <literal>needs-start</literal>, or <literal>needs-stop</literal>
is set, <literal>needs-reload</literal> is cleared.</para></listitem>
<listitem><para><literal>needs-stop</literal> wins against <literal>needs-restart</literal> and
<literal>needs-reload</literal>, clearing both.</para></listitem>
<listitem><para><literal>needs-start</literal> wins against <literal>needs-stop</literal>, clearing
it.</para></listitem>
<listitem><para><literal>needs-restart</literal> wins against <literal>needs-start</literal>. If
both are set, <literal>needs-start</literal> is cleared.</para></listitem>
</itemizedlist>
For example, if a unit currently has <literal>needs-stop</literal> set and a new
<literal>+needs-start</literal> marker is applied, the existing <literal>needs-stop</literal> is
cleared and only <literal>needs-start</literal> remains. Conversely, applying
<literal>+needs-stop</literal> to any existing marker will clear all other markers, as
<literal>needs-stop</literal> takes precedence over <literal>needs-restart</literal> and
<literal>needs-reload</literal>, and the new marker clears conflicting existing ones.</para>
<para><varname>JobTimeoutUSec</varname> maps directly to the corresponding configuration setting in the
unit file.</para>
+2
View File
@@ -155,6 +155,8 @@ FreezerState freezer_state_objective(FreezerState state) {
static const char* const unit_marker_table[_UNIT_MARKER_MAX] = {
[UNIT_MARKER_NEEDS_RELOAD] = "needs-reload",
[UNIT_MARKER_NEEDS_RESTART] = "needs-restart",
[UNIT_MARKER_NEEDS_STOP] = "needs-stop",
[UNIT_MARKER_NEEDS_START] = "needs-start",
};
DEFINE_STRING_TABLE_LOOKUP(unit_marker, UnitMarker);
+2
View File
@@ -62,6 +62,8 @@ typedef enum FreezerState {
typedef enum UnitMarker {
UNIT_MARKER_NEEDS_RELOAD,
UNIT_MARKER_NEEDS_RESTART,
UNIT_MARKER_NEEDS_STOP,
UNIT_MARKER_NEEDS_START,
_UNIT_MARKER_MAX,
_UNIT_MARKER_INVALID = -EINVAL,
} UnitMarker;
+14 -5
View File
@@ -2132,17 +2132,26 @@ static int method_enqueue_marked_jobs(sd_bus_message *message, void *userdata, s
continue;
BusUnitQueueFlags flags;
if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART))
JobType job;
if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART)) {
flags = 0;
else if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_RELOAD))
job = JOB_TRY_RESTART;
} else if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_RELOAD)) {
flags = BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE;
else
job = JOB_TRY_RESTART;
} else if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_STOP)) {
flags = 0;
job = JOB_STOP;
} else if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_START)) {
flags = 0;
job = JOB_START;
} else
continue;
r = mac_selinux_unit_access_check(u, message, "start", &error);
r = mac_selinux_unit_access_check(u, message, job_type_to_access_method(job), &error);
if (r >= 0)
r = bus_unit_queue_job_one(message, u,
JOB_TRY_RESTART, JOB_FAIL, flags,
job, JOB_FAIL, flags,
reply, &error);
if (ERRNO_IS_NEG_RESOURCE(r))
return r;
+2 -3
View File
@@ -2151,9 +2151,8 @@ static int bus_unit_set_live_property(
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
if (some_absolute)
u->markers = settings;
else
u->markers = settings | (u->markers & ~mask);
mask = UINT_MAX;
u->markers = unit_normalize_markers((u->markers & ~mask), settings);
}
return 1;
+45 -2
View File
@@ -11,6 +11,7 @@
#include "all-units.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "bitfield.h"
#include "bpf-restrict-fs.h"
#include "bus-common-errors.h"
#include "bus-internal.h"
@@ -479,6 +480,9 @@ bool unit_may_gc(Unit *u) {
if (r <= 0 && !IN_SET(r, -ENXIO, -EOWNERDEAD))
return false; /* ENXIO/EOWNERDEAD means: currently not realized */
if (unit_can_start(u) && BIT_SET(u->markers, UNIT_MARKER_NEEDS_START))
return false;
if (!UNIT_VTABLE(u)->may_gc)
return true;
@@ -2758,11 +2762,13 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
/* Make sure the cgroup and state files are always removed when we become inactive */
if (UNIT_IS_INACTIVE_OR_FAILED(ns)) {
SET_FLAG(u->markers,
(1u << UNIT_MARKER_NEEDS_RELOAD)|(1u << UNIT_MARKER_NEEDS_RESTART),
(1u << UNIT_MARKER_NEEDS_RELOAD)|(1u << UNIT_MARKER_NEEDS_RESTART)|(1u << UNIT_MARKER_NEEDS_STOP),
false);
unit_prune_cgroup(u);
unit_unlink_state_files(u);
} else if (ns != os && ns == UNIT_RELOADING)
} else if (UNIT_IS_ACTIVE_OR_ACTIVATING(ns))
SET_FLAG(u->markers, 1u << UNIT_MARKER_NEEDS_START, false);
else if (ns != os && ns == UNIT_RELOADING)
SET_FLAG(u->markers, 1u << UNIT_MARKER_NEEDS_RELOAD, false);
unit_update_on_console(u);
@@ -7086,8 +7092,45 @@ int parse_unit_marker(const char *marker, unsigned *settings, unsigned *mask) {
if (m < 0)
return -EINVAL;
/* When +- are not used, last one wins, so reset the bitmask before storing the new result */
if (!some_plus_minus)
*settings = 0;
SET_FLAG(*settings, 1u << m, b);
SET_FLAG(*mask, 1u << m, true);
return some_plus_minus;
}
unsigned unit_normalize_markers(unsigned existing_markers, unsigned new_markers) {
/* Follow the job merging logic: when new markers conflict with existing ones, the new marker
* takes precedence and clears out conflicting existing markers. Then standard normalization
* resolves any remaining conflicts. */
/* New stop wins against all existing markers */
if (BIT_SET(new_markers, UNIT_MARKER_NEEDS_STOP))
CLEAR_BITS(existing_markers, UNIT_MARKER_NEEDS_RESTART, UNIT_MARKER_NEEDS_START, UNIT_MARKER_NEEDS_RELOAD);
/* New start wins against existing stop */
if (BIT_SET(new_markers, UNIT_MARKER_NEEDS_START))
CLEAR_BIT(existing_markers, UNIT_MARKER_NEEDS_STOP);
/* New restart wins against existing start and reload */
if (BIT_SET(new_markers, UNIT_MARKER_NEEDS_RESTART))
CLEAR_BITS(existing_markers, UNIT_MARKER_NEEDS_START, UNIT_MARKER_NEEDS_RELOAD);
unsigned markers = existing_markers | new_markers;
/* Standard normalization: reload loses against everything */
if (BIT_SET(markers, UNIT_MARKER_NEEDS_RESTART) || BIT_SET(markers, UNIT_MARKER_NEEDS_START) || BIT_SET(markers, UNIT_MARKER_NEEDS_STOP))
CLEAR_BIT(markers, UNIT_MARKER_NEEDS_RELOAD);
/* Stop wins against restart and reload */
if (BIT_SET(markers, UNIT_MARKER_NEEDS_STOP))
CLEAR_BITS(markers, UNIT_MARKER_NEEDS_RESTART, UNIT_MARKER_NEEDS_RELOAD);
/* Start wins against stop */
if (BIT_SET(markers, UNIT_MARKER_NEEDS_START))
CLEAR_BIT(markers, UNIT_MARKER_NEEDS_STOP);
/* Restart wins against start */
if (BITS_SET(markers, UNIT_MARKER_NEEDS_RESTART, UNIT_MARKER_NEEDS_START))
CLEAR_BIT(markers, UNIT_MARKER_NEEDS_START);
return markers;
}
+1
View File
@@ -1095,6 +1095,7 @@ DECLARE_STRING_TABLE_LOOKUP(oom_policy, OOMPolicy);
int unit_queue_job_check_and_mangle_type(Unit *u, JobType *type, bool reload_if_possible, sd_bus_error *reterr_error);
int parse_unit_marker(const char *marker, unsigned *settings, unsigned *mask);
unsigned unit_normalize_markers(unsigned existing_markers, unsigned new_markers);
/* Macros which append UNIT= or USER_UNIT= to the message */
+9 -2
View File
@@ -343,6 +343,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par
_cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL;
const char *error_id = NULL;
uint32_t job_id = 0; /* silence 'maybe-uninitialized' compiler warning */
JobType job;
/* ignore aliases */
if (u->id != k)
@@ -350,14 +351,20 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par
if (u->markers == 0)
continue;
if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_STOP))
job = JOB_STOP;
else if (BIT_SET(u->markers, UNIT_MARKER_NEEDS_START))
job = JOB_START;
else
job = JOB_TRY_RESTART;
r = mac_selinux_unit_access_check_varlink(u, link, job_type_to_access_method(JOB_TRY_RESTART));
r = mac_selinux_unit_access_check_varlink(u, link, job_type_to_access_method(job));
if (r < 0)
error_id = SD_VARLINK_ERROR_PERMISSION_DENIED;
else
r = varlink_unit_queue_job_one(
u,
JOB_TRY_RESTART,
job,
JOB_FAIL,
/* reload_if_possible= */ !BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART),
&job_id,
+1 -1
View File
@@ -668,7 +668,7 @@ int vl_method_set_unit_properties(sd_varlink *link, sd_json_variant *parameters,
return r;
if (p.markers_found)
unit->markers = p.markers | (unit->markers & ~p.markers_mask);
unit->markers = unit_normalize_markers((unit->markers & ~p.markers_mask), p.markers);
return sd_varlink_reply(link, NULL);
}
+137 -2
View File
@@ -371,18 +371,153 @@ systemctl status 1
# --marked
systemctl restart "$UNIT_NAME"
systemctl set-property "$UNIT_NAME" Markers=needs-restart
systemctl set-property "$UNIT_NAME" "Markers=needs-reload needs-restart"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-reload
systemctl reload-or-restart --marked
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-restart)
systemctl is-active "$UNIT_NAME"
systemctl set-property "$UNIT_NAME" "Markers=needs-reload needs-stop"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-reload
systemctl reload-or-restart --marked
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
(! systemctl is-active "$UNIT_NAME")
systemctl set-property "$UNIT_NAME" "Markers=needs-start"
systemctl show -P Markers "$UNIT_NAME" | grep needs-start
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-stop
systemctl reload-or-restart --marked
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
systemctl is-active "$UNIT_NAME"
systemctl set-property "$UNIT_NAME" "Markers=needs-start needs-stop"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-start
systemctl reload-or-restart --marked
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
(! systemctl is-active "$UNIT_NAME")
# Test marker normalization with incremental (+) syntax
# needs-start + +needs-restart → needs-restart (restart wins against start)
systemctl set-property "$UNIT_NAME" "Markers=needs-start"
systemctl set-property "$UNIT_NAME" "Markers=+needs-restart"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
systemctl set-property "$UNIT_NAME" "Markers="
# needs-restart + +needs-start → needs-restart (restart wins against start)
systemctl set-property "$UNIT_NAME" "Markers=needs-restart"
systemctl set-property "$UNIT_NAME" "Markers=+needs-start"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
systemctl set-property "$UNIT_NAME" "Markers="
# needs-restart + +needs-reload → needs-restart (reload loses against restart)
systemctl set-property "$UNIT_NAME" "Markers=needs-restart"
systemctl set-property "$UNIT_NAME" "Markers=+needs-reload"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload)
systemctl set-property "$UNIT_NAME" "Markers="
# needs-stop + +needs-start → needs-start (start overrides stop)
systemctl set-property "$UNIT_NAME" "Markers=needs-stop"
systemctl set-property "$UNIT_NAME" "Markers=+needs-start"
systemctl show -P Markers "$UNIT_NAME" | grep needs-start
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
systemctl set-property "$UNIT_NAME" "Markers="
# anything + +needs-stop → needs-stop (stop wins against everything)
for marker in needs-start needs-restart needs-reload; do
systemctl set-property "$UNIT_NAME" "Markers=$marker"
systemctl set-property "$UNIT_NAME" "Markers=+needs-stop"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
(! systemctl show -P Markers "$UNIT_NAME" | grep "$marker")
systemctl set-property "$UNIT_NAME" "Markers="
done
# needs-stop + +needs-reload → needs-stop (stop wins against reload)
systemctl set-property "$UNIT_NAME" "Markers=needs-stop"
systemctl set-property "$UNIT_NAME" "Markers=+needs-reload"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload)
systemctl set-property "$UNIT_NAME" "Markers="
# again, but with varlinkctl instead
systemctl restart "$UNIT_NAME"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-restart\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-reload\", \"needs-restart\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-reload
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.EnqueueMarkedJobs '{}'
timeout 30 bash -c "until systemctl list-jobs $UNIT_NAME | grep \"No jobs\" 2>/dev/null; do sleep 1; done"
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-restart)
systemctl is-active "$UNIT_NAME"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-reload\", \"needs-stop\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-reload
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.EnqueueMarkedJobs '{}'
timeout 30 bash -c "until systemctl list-jobs $UNIT_NAME | grep \"No jobs\" 2>/dev/null; do sleep 1; done"
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
(! systemctl is-active "$UNIT_NAME")
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-start\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-start
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-stop
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.EnqueueMarkedJobs '{}'
timeout 30 bash -c "until systemctl list-jobs $UNIT_NAME | grep \"No jobs\" 2>/dev/null; do sleep 1; done"
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
systemctl is-active "$UNIT_NAME"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-start\", \"needs-stop\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
systemctl show -P Markers "$UNIT_NAME" | grep -v needs-start
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.EnqueueMarkedJobs '{}'
timeout 30 bash -c "until systemctl list-jobs $UNIT_NAME | grep \"No jobs\" 2>/dev/null; do sleep 1; done"
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
(! systemctl is-active "$UNIT_NAME")
# Test marker normalization with incremental (+) syntax via varlinkctl
# needs-start + +needs-restart → needs-restart (restart wins against start)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-start\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-restart\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# needs-restart + +needs-start → needs-restart (restart wins against start)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-restart\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-start\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-start)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# needs-restart + +needs-reload → needs-restart (reload loses against restart)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-restart\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-reload\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# needs-stop + +needs-start → needs-start (start overrides stop)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-stop\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-start\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-start
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-stop)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# anything + +needs-stop → needs-stop (stop wins against everything)
for marker in needs-start needs-restart needs-reload; do
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"$marker\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-stop\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
(! systemctl show -P Markers "$UNIT_NAME" | grep "$marker")
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
done
# needs-stop + +needs-reload → needs-stop (stop wins against reload)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"needs-stop\"]}}"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": [\"+needs-reload\"]}}"
systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# --dry-run with destructive verbs
# kexec is skipped intentionally, as it requires a bit more involved setup