]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
manager: add EnqueueMarkedJobs varlink method
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 17 Nov 2025 00:16:44 +0000 (00:16 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 16 Jan 2026 12:45:48 +0000 (12:45 +0000)
Same as the D-Bus method. Returns array of job IDs.

src/core/dbus-unit.c
src/core/unit.c
src/core/unit.h
src/core/varlink-manager.c
src/core/varlink-manager.h
src/core/varlink-unit.c
src/core/varlink-unit.h
src/core/varlink.c
src/shared/varlink-io.systemd.Manager.c
test/units/TEST-26-SYSTEMCTL.sh

index 730b1a054543cddafabf3ae42b82c079e909d92d..5b04d2079fe5209744435fee6fabd0b0769c3589 100644 (file)
@@ -24,9 +24,9 @@
 #include "manager.h"
 #include "namespace-util.h"
 #include "path-util.h"
+#include "pidref.h"
 #include "process-util.h"
 #include "selinux-access.h"
-#include "service.h"
 #include "set.h"
 #include "signal-util.h"
 #include "special.h"
@@ -1960,44 +1960,23 @@ int bus_unit_queue_job_one(
         Job *j, *a;
         int r;
 
-        if (FLAGS_SET(flags, BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE) && unit_can_reload(u)) {
-                if (type == JOB_RESTART)
-                        type = JOB_RELOAD_OR_START;
-                else if (type == JOB_TRY_RESTART)
-                        type = JOB_TRY_RELOAD;
-        }
+        assert(u);
 
-        if (type == JOB_STOP && UNIT_IS_LOAD_ERROR(u->load_state) && unit_active_state(u) == UNIT_INACTIVE)
+        r = unit_queue_job_check_and_collapse_type(u, &type, /* reload_if_possible= */ FLAGS_SET(flags, BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE));
+        if (r == -ENOENT)
                 return sd_bus_error_setf(reterr_error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id);
-
-        if ((type == JOB_START && u->refuse_manual_start) ||
-            (type == JOB_STOP && u->refuse_manual_stop) ||
-            (IN_SET(type, JOB_RESTART, JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) ||
-            (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start))
+        if (r == -EUNATCH)
                 return sd_bus_error_setf(reterr_error,
                                          BUS_ERROR_ONLY_BY_DEPENDENCY,
                                          "Operation refused, unit %s may be requested by dependency only (it is configured to refuse manual start/stop).",
                                          u->id);
-
-        /* dbus-broker issues StartUnit for activation requests, and Type=dbus services automatically
-         * gain dependency on dbus.socket. Therefore, if dbus has a pending stop job, the new start
-         * job that pulls in dbus again would cause job type conflict. Let's avoid that by rejecting
-         * job enqueuing early.
-         *
-         * Note that unlike signal_activation_request(), we can't use unit_inactive_or_pending()
-         * here. StartUnit is a more generic interface, and thus users are allowed to use e.g. systemctl
-         * to start Type=dbus services even when dbus is inactive. */
-        if (type == JOB_START && u->type == UNIT_SERVICE && SERVICE(u)->type == SERVICE_DBUS)
-                FOREACH_STRING(dbus_unit, SPECIAL_DBUS_SOCKET, SPECIAL_DBUS_SERVICE) {
-                        Unit *dbus;
-
-                        dbus = manager_get_unit(u->manager, dbus_unit);
-                        if (dbus && unit_stop_pending(dbus))
-                                return sd_bus_error_setf(reterr_error,
-                                                         BUS_ERROR_SHUTTING_DOWN,
-                                                         "Operation for unit %s refused, D-Bus is shutting down.",
-                                                         u->id);
-                }
+        if (r == -ESHUTDOWN)
+                return sd_bus_error_setf(reterr_error,
+                                         BUS_ERROR_SHUTTING_DOWN,
+                                         "Operation for unit %s refused, D-Bus is shutting down.",
+                                         u->id);
+        if (r < 0)
+                return r;
 
         if (FLAGS_SET(flags, BUS_UNIT_QUEUE_VERBOSE_REPLY)) {
                 affected = set_new(NULL);
index ded0c3e14d3ff3e2902f86172645c957ea68f8b8..5494d718e65b6d6e8c06d1aa9d7bb96a5ac00acc 100644 (file)
@@ -7003,3 +7003,57 @@ UnitDependency unit_mount_dependency_type_to_dependency_type(UnitMountDependency
                 assert_not_reached();
         }
 }
+
+int unit_queue_job_check_and_collapse_type(
+                Unit *u,
+                JobType *type, /* input and output */
+                bool reload_if_possible) {
+
+        /* Returns:
+         *
+         * -ENOENT    → Unit not loaded
+         * -EUNATCH   → Unit can only be activated via dependency, not directly
+         * -ESHUTDOWN → System bus is shutting down */
+
+        JobType t;
+
+        assert(u);
+        assert(type);
+
+        t = *type;
+
+        if (reload_if_possible && unit_can_reload(u)) {
+                if (t == JOB_RESTART)
+                        t = JOB_RELOAD_OR_START;
+                else if (t == JOB_TRY_RESTART)
+                        t = JOB_TRY_RELOAD;
+        }
+
+        if (t == JOB_STOP && UNIT_IS_LOAD_ERROR(u->load_state) && unit_active_state(u) == UNIT_INACTIVE)
+                return -ENOENT;
+
+        if ((t == JOB_START && u->refuse_manual_start) ||
+            (t == JOB_STOP && u->refuse_manual_stop) ||
+            (IN_SET(t, JOB_RESTART, JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) ||
+            (t == JOB_RELOAD_OR_START && job_type_collapse(t, u) == JOB_START && u->refuse_manual_start))
+                return -EUNATCH;
+
+        /* dbus-broker issues StartUnit for activation requests, and Type=dbus services automatically
+         * gain dependency on dbus.socket. Therefore, if dbus has a pending stop job, the new start
+         * job that pulls in dbus again would cause job type conflict. Let's avoid that by rejecting
+         * job enqueuing early.
+         *
+         * Note that unlike signal_activation_request(), we can't use unit_inactive_or_pending()
+         * here. StartUnit is a more generic interface, and thus users are allowed to use e.g. systemctl
+         * to start Type=dbus services even when dbus is inactive. */
+        if (t == JOB_START && u->type == UNIT_SERVICE && SERVICE(u)->type == SERVICE_DBUS)
+                FOREACH_STRING(dbus_unit, SPECIAL_DBUS_SOCKET, SPECIAL_DBUS_SERVICE) {
+                        Unit *dbus = manager_get_unit(u->manager, dbus_unit);
+                        if (dbus && unit_stop_pending(dbus))
+                                return -ESHUTDOWN;
+                }
+
+        *type = t;
+
+        return 0;
+}
index 56bd75b56ee9d62e1ccc2f4a3c84670beebdbcd1..57795cd580381811de7c08c3144a5afbb65d1fa1 100644 (file)
@@ -1092,6 +1092,8 @@ UnitDependency unit_mount_dependency_type_to_dependency_type(UnitMountDependency
 
 DECLARE_STRING_TABLE_LOOKUP(oom_policy, OOMPolicy);
 
+int unit_queue_job_check_and_collapse_type(Unit *u, JobType *type, bool reload_if_possible);
+
 /* Macros which append UNIT= or USER_UNIT= to the message */
 
 #define log_unit_full_errno_zerook(unit, level, error, ...)             \
index 7493c963e83a5fb036a506a31b23eb82f7e575f9..785cc206ccee7d4880bf1df637ca44e1256273bc 100644 (file)
@@ -6,9 +6,13 @@
 
 #include "alloc-util.h"
 #include "architecture.h"
+#include "bitfield.h"
 #include "build.h"
 #include "bus-polkit.h"
 #include "confidential-virt.h"
+#include "dbus-job.h"
+#include "errno-util.h"
+#include "glyph-util.h"
 #include "json-util.h"
 #include "manager.h"
 #include "pidref.h"
@@ -20,6 +24,7 @@
 #include "version.h"
 #include "varlink-common.h"
 #include "varlink-manager.h"
+#include "varlink-unit.h"
 #include "varlink-util.h"
 #include "virt.h"
 #include "watchdog.h"
@@ -299,3 +304,109 @@ int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, s
 
         return 1;
 }
+static int varlink_manager_queue_job_one(
+                sd_varlink *link,
+                Unit *u,
+                JobType type,
+                JobMode mode,
+                uint32_t *ret_job_id) {
+
+        int r;
+
+        assert(u);
+
+        r = unit_queue_job_check_and_collapse_type(u, &type, /* reload_if_possible= */ BIT_SET(u->markers, UNIT_MARKER_NEEDS_RELOAD));
+        if (r == -ENOENT)
+                return varlink_error_no_such_unit(link, "name");
+        if (r == -EUNATCH)
+                return sd_varlink_errorb(link, VARLINK_ERROR_MANAGER_ONLY_BY_DEPENDENCY);
+        if (r == -ESHUTDOWN)
+                return sd_varlink_errorb(link, VARLINK_ERROR_MANAGER_BUS_SHUTTING_DOWN);
+        if (r < 0)
+                return r;
+
+        Job *j;
+        r = manager_add_job(u->manager, type, u, mode, /* reterr_error= */ NULL, &j);
+        if (r < 0)
+                return r;
+
+        /* Before we send the method reply, force out the announcement JobNew for this job */
+        bus_job_send_pending_change_signal(j, /* including_new= */ true);
+
+        if (ret_job_id)
+                *ret_job_id = j->id;
+
+        return 0;
+}
+
+int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        Manager *manager = ASSERT_PTR(userdata);
+        int r;
+
+        assert(link);
+        assert(parameters);
+
+        r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
+        if (r != 0)
+                return r;
+
+        r = mac_selinux_access_check_varlink(link, "start");
+        if (r < 0)
+                return r;
+
+        r = varlink_verify_polkit_async(
+                        link,
+                        manager->system_bus,
+                        "org.freedesktop.systemd1.manage-units",
+                        /* details= */ NULL,
+                        &manager->polkit_registry);
+        if (r <= 0)
+                return r;
+
+        log_info("Queuing reload/restart jobs for marked units%s", glyph(GLYPH_ELLIPSIS));
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *reply = NULL;
+        Unit *u;
+        char *k;
+        int ret = 0;
+        HASHMAP_FOREACH_KEY(u, k, manager->units) {
+                uint32_t job_id = 0; /* silence 'maybe-uninitialized' compiler warning */
+
+                /* ignore aliases */
+                if (u->id != k)
+                        continue;
+
+                if (u->markers == 0)
+                        continue;
+
+                r = mac_selinux_unit_access_check_varlink(u, link, job_type_to_access_method(JOB_TRY_RESTART));
+                if (r >= 0)
+                        r = varlink_manager_queue_job_one(
+                                        link,
+                                        u,
+                                        JOB_TRY_RESTART,
+                                        JOB_FAIL,
+                                        &job_id);
+                if (ERRNO_IS_NEG_RESOURCE(r))
+                        return r;
+                RET_GATHER(ret, r);
+                if (r >= 0) {
+                        r = sd_json_variant_append_arrayb(&array, SD_JSON_BUILD_UNSIGNED(job_id));
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (ret < 0)
+                return ret;
+
+        /* Return parameter is not nullable, build empty array if there's nothing to return */
+        if (array)
+                r = sd_json_buildo(&reply, SD_JSON_BUILD_PAIR_VARIANT("JobIDs", array));
+        else
+                r = sd_json_buildo(&reply, SD_JSON_BUILD_PAIR_EMPTY_ARRAY("JobIDs"));
+        if (r < 0)
+                return r;
+
+        return sd_varlink_reply(link, reply);
+}
index bff621d536fa92aef85c72489bab44f96da21e41..c84c652e5541827cc7ecdc50e0b34cf72983e95e 100644 (file)
@@ -4,7 +4,10 @@
 #include "core-forward.h"
 
 #define VARLINK_ERROR_MANAGER_RATE_LIMIT_REACHED "io.systemd.Manager.RateLimitReached"
+#define VARLINK_ERROR_MANAGER_ONLY_BY_DEPENDENCY "io.systemd.Manager.OnlyByDependency"
+#define VARLINK_ERROR_MANAGER_BUS_SHUTTING_DOWN "io.systemd.Manager.BusShuttingDown"
 
 int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
 int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
 int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
index ccfb76564943d130d7312910e6b717a10902b4e4..2d3f02d4c0b5fcbf385c46a2582226ac858c4a44 100644 (file)
@@ -382,7 +382,7 @@ static void unit_lookup_parameters_done(UnitLookupParameters *p) {
         pidref_done(&p->pidref);
 }
 
-static int varlink_error_no_such_unit(sd_varlink *v, const char *name) {
+int varlink_error_no_such_unit(sd_varlink *v, const char *name) {
         return sd_varlink_errorbo(
                         ASSERT_PTR(v),
                         VARLINK_ERROR_UNIT_NO_SUCH_UNIT,
index 4b5e7aa52ab84cd0a3fae3b3a6872983372ab003..e60d5a5b9bc4182d5415ee83a6b7548c2f1e3e11 100644 (file)
@@ -5,4 +5,5 @@
 
 #define VARLINK_ERROR_UNIT_NO_SUCH_UNIT "io.systemd.Unit.NoSuchUnit"
 
+int varlink_error_no_such_unit(sd_varlink *v, const char *name);
 int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
index 9834497881e1f1954ef5ef44901367e2d2d888ad..605673ef1977d1f95238b5ccc49d363ba1aca6f0 100644 (file)
@@ -386,6 +386,7 @@ int manager_setup_varlink_server(Manager *m) {
                         "io.systemd.Manager.Describe", vl_method_describe_manager,
                         "io.systemd.Manager.Reexecute", vl_method_reexecute_manager,
                         "io.systemd.Manager.Reload", vl_method_reload_manager,
+                        "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager,
                         "io.systemd.Unit.List", vl_method_list_units,
                         "io.systemd.service.Ping", varlink_method_ping,
                         "io.systemd.service.GetEnvironment", varlink_method_get_environment);
index ca9036652164511fdea2d6e892c655afe494b991..ecae5a0924b5869481e88c8d0c58ec33b1e30216 100644 (file)
@@ -181,8 +181,17 @@ static SD_VARLINK_DEFINE_METHOD(
 static SD_VARLINK_DEFINE_METHOD(
                 Reload);
 
+static SD_VARLINK_DEFINE_METHOD(
+                EnqueueMarkedJobs,
+                SD_VARLINK_FIELD_COMMENT("IDs of enqueued jobs"),
+                SD_VARLINK_DEFINE_OUTPUT(JobIDs, SD_VARLINK_INT, SD_VARLINK_ARRAY));
+
 static SD_VARLINK_DEFINE_ERROR(RateLimitReached);
 
+static SD_VARLINK_DEFINE_ERROR(OnlyByDependency);
+
+static SD_VARLINK_DEFINE_ERROR(BusShuttingDown);
+
 SD_VARLINK_DEFINE_INTERFACE(
                 io_systemd_Manager,
                 "io.systemd.Manager",
@@ -191,7 +200,13 @@ SD_VARLINK_DEFINE_INTERFACE(
                 &vl_method_Reexecute,
                 SD_VARLINK_SYMBOL_COMMENT("Reload the manager configuration"),
                 &vl_method_Reload,
+                SD_VARLINK_SYMBOL_COMMENT("Enqueue all marked jobs"),
+                &vl_method_EnqueueMarkedJobs,
                 &vl_error_RateLimitReached,
+                SD_VARLINK_SYMBOL_COMMENT("Unit operation may be requested by dependency only"),
+                &vl_error_OnlyByDependency,
+                SD_VARLINK_SYMBOL_COMMENT("Operation refused, the bus is shutting down"),
+                &vl_error_BusShuttingDown,
                 &vl_type_ManagerContext,
                 &vl_type_ManagerRuntime,
                 &vl_type_Timestamp,
index f18357075a5a4623a0df8acc67c3fa37ad98dced..793ecf3af45e987c84732a5cabdc898f0f0b59ef 100755 (executable)
@@ -376,6 +376,14 @@ systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
 systemctl reload-or-restart --marked
 (! systemctl show -P Markers "$UNIT_NAME" | grep needs-restart)
 
+# again, but with varlinkctl instead
+systemctl restart "$UNIT_NAME"
+systemctl set-property "$UNIT_NAME" Markers=needs-restart
+systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
+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)
+
 # --dry-run with destructive verbs
 # kexec is skipped intentionally, as it requires a bit more involved setup
 VERBS=(