diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index f0a7ccc6d6e..f4a06901b03 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2644,12 +2644,33 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
Markers is an array of string flags that can be set using
- SetUnitProperties() to indicate that the service should be reloaded or
- restarted. Currently known values are needs-restart and
- needs-reload. 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.
+ SetUnitProperties() to indicate that the service should be reloaded or restarted.
+ Currently known values are needs-restart, needs-stop,
+ needs-start and needs-reload. 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 + prefix), conflicting existing markers are
+ cleared before the new markers are merged in:
+
+ needs-reload loses against all other markers. If any of
+ needs-restart, needs-start, or needs-stop
+ is set, needs-reload is cleared.
+ needs-stop wins against needs-restart and
+ needs-reload, clearing both.
+ needs-start wins against needs-stop, clearing
+ it.
+ needs-restart wins against needs-start. If
+ both are set, needs-start is cleared.
+
+ For example, if a unit currently has needs-stop set and a new
+ +needs-start marker is applied, the existing needs-stop is
+ cleared and only needs-start remains. Conversely, applying
+ +needs-stop to any existing marker will clear all other markers, as
+ needs-stop takes precedence over needs-restart and
+ needs-reload, and the new marker clears conflicting existing ones.
JobTimeoutUSec maps directly to the corresponding configuration setting in the
unit file.
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 63454505dcb..619805891c1 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -2855,10 +2855,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
- Only allowed with reload-or-restart. Enqueues restart jobs for all
- units that have the needs-restart mark, and reload jobs for units that have the
- needs-reload mark. When a unit marked for reload does not support reload, restart
- will be queued. Those properties can be set using set-property Markers=….
+ Only allowed with reload-or-restart, start, or
+ stop. Enqueues jobs for all units that are marked. When a unit marked for reload
+ does not support reload, restart will be queued. Those properties can be set using
+ set-property Markers=….
Unless is used, systemctl will wait for the
queued jobs to finish.
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index 6da61c510d8..ea4eebbf5d3 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -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);
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index 6ed4c0cbb7a..5fecd3ecec1 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -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;
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 232389e06ca..ad79c6e96ab 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -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;
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index eff73591fde..525ec76da26 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -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;
diff --git a/src/core/unit.c b/src/core/unit.c
index 78c67c9833b..1995bb7f8eb 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -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;
+}
diff --git a/src/core/unit.h b/src/core/unit.h
index 95959cfcea4..9c94113239e 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -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 */
diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c
index dbe97f20c9b..d00f7e5a248 100644
--- a/src/core/varlink-manager.c
+++ b/src/core/varlink-manager.c
@@ -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,
diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c
index 790d0df2dfb..b3375aebfe8 100644
--- a/src/core/varlink-unit.c
+++ b/src/core/varlink-unit.c
@@ -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);
}
diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c
index c0596cfb861..b23bc4e6fff 100644
--- a/src/systemctl/systemctl-main.c
+++ b/src/systemctl/systemctl-main.c
@@ -59,8 +59,8 @@ static int systemctl_main(int argc, char *argv[]) {
{ "list-machines", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_machines },
{ "clear-jobs", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_trivial_method },
{ "cancel", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_cancel },
- { "start", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
- { "stop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
+ { "start", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
+ { "stop", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
{ "condstop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with ALTLinux */
{ "reload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
{ "restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start },
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 382a50e3123..fef1cb7a77b 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -1069,14 +1069,13 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--wait may not be combined with --no-block.");
- bool do_reload_or_restart = streq_ptr(argv[optind], "reload-or-restart");
if (arg_marked) {
- if (!do_reload_or_restart)
+ if (!STRPTR_IN_SET(argv[optind], "reload-or-restart", "start", "stop"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--marked may only be used with 'reload-or-restart'.");
+ "--marked may only be used with 'reload-or-restart', 'start', or 'stop'.");
if (optind + 1 < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "No additional arguments allowed with 'reload-or-restart --marked'.");
+ "No additional arguments allowed with '%s --marked'.", strna(argv[optind]));
if (arg_wait)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--marked --wait is not supported.");
@@ -1084,10 +1083,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--marked --show-transaction is not supported.");
- } else if (do_reload_or_restart) {
+ } else if (STRPTR_IN_SET(argv[optind], "reload-or-restart", "start", "stop")) {
if (optind + 1 >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "List of units to restart/reload is required.");
+ "List of units to %s is required.", strna(argv[optind]));
}
if (arg_image && arg_root)
diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh
index 2b8c6fa2308..75feedd3b09 100755
--- a/test/units/TEST-26-SYSTEMCTL.sh
+++ b/test/units/TEST-26-SYSTEMCTL.sh
@@ -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