]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
portablectl: use new EnqueueUnitJobMany() when available
authorLuca Boccassi <luca.boccassi@gmail.com>
Tue, 19 May 2026 18:01:32 +0000 (19:01 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 22 Jun 2026 20:19:16 +0000 (21:19 +0100)
src/portable/portablectl.c

index 9286a6fbb366413072d8bd5b363b017a1fde08da..831b454372275500fff5414069f9b16cf171f99b 100644 (file)
@@ -678,22 +678,18 @@ static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) {
         return 0;
 }
 
-static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *method, BusWaitForJobs *wait) {
+static int maybe_start_stop_restart(sd_bus *bus, const char *name, const char *method, BusWaitForJobs *wait) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_free_ char *name = NULL;
         const char *job = NULL;
         int r;
 
+        assert(name);
         assert(STR_IN_SET(method, "StartUnit", "StopUnit", "RestartUnit"));
 
         if (!arg_now)
                 return 0;
 
-        r = path_extract_filename(path, &name);
-        if (r < 0)
-                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
-
         r = bus_call_method(
                         bus,
                         bus_systemd_mgr,
@@ -704,7 +700,7 @@ static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *m
         if (r < 0)
                 return log_error_errno(r, "Failed to call %s on the portable service %s: %s",
                                        method,
-                                       path,
+                                       name,
                                        bus_error_message(&error, r));
 
         r = sd_bus_message_read(reply, "o", &job);
@@ -724,8 +720,92 @@ static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *m
         return 0;
 }
 
+static int maybe_start_stop_restart_units(
+                sd_bus *bus,
+                char * const *names,
+                const char *job_type,
+                BusWaitForJobs *wait) {
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        const char *method;
+        int r;
+
+        assert(bus);
+        assert(STR_IN_SET(job_type, "start", "stop", "restart"));
+
+        if (!arg_now || strv_isempty(names))
+                return 0;
+
+        method = streq(job_type, "start") ? "StartUnit" :
+                 streq(job_type, "stop")  ? "StopUnit"  :
+                                            "RestartUnit";
+
+        /* Prefer the new EnqueueUnitJobMany() method which submits all units in a single transaction.
+         * Falls back to per-unit calls on older managers (UnknownMethod) or when the new method rejects
+         * something about the request (InvalidArgs). */
+        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "EnqueueUnitJobMany");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append_strv(m, (char**) names);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append(m, "sst", job_type, "replace", UINT64_C(0));
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_call(bus, m, 0, &error, &reply);
+        if (r >= 0) {
+                r = sd_bus_message_enter_container(reply, 'a', "(uosos)");
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                for (;;) {
+                        const char *path, *unit_id;
+                        uint32_t id;
+
+                        r = sd_bus_message_read(reply, "(uosos)", &id, &path, &unit_id, NULL, NULL);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+                        if (r == 0)
+                                break;
+
+                        if (!arg_quiet)
+                                log_info("Queued %s to call %s on portable service %s.", path, method, unit_id);
+
+                        if (wait) {
+                                r = bus_wait_for_jobs_add(wait, path);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to watch %s job to call %s on %s: %m",
+                                                               path, method, unit_id);
+                        }
+                }
+
+                r = sd_bus_message_exit_container(reply);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                return 0;
+        }
+
+        if (!sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_METHOD, SD_BUS_ERROR_INVALID_ARGS))
+                return log_error_errno(r, "Failed to enqueue jobs for portable services: %s",
+                                       bus_error_message(&error, r));
+
+        log_debug_errno(r, "EnqueueUnitJobMany() not supported (%s), falling back to per-unit calls.",
+                        bus_error_message(&error, r));
+
+        STRV_FOREACH(name, names)
+                (void) maybe_start_stop_restart(bus, *name, method, wait);
+
+        return 0;
+}
+
 static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
+        _cleanup_strv_free_ char **start_names = NULL;
         int r;
 
         if (!arg_enable && !arg_now)
@@ -755,7 +835,15 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
 
                 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
                         (void) maybe_enable_disable(bus, path, true);
-                        (void) maybe_start_stop_restart(bus, path, "StartUnit", wait);
+
+                        _cleanup_free_ char *name = NULL;
+                        r = path_extract_filename(path, &name);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
+
+                        r = strv_consume(&start_names, TAKE_PTR(name));
+                        if (r < 0)
+                                return log_oom();
                 }
         }
 
@@ -763,6 +851,8 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
         if (r < 0)
                 return r;
 
+        (void) maybe_start_stop_restart_units(bus, start_names, "start", wait);
+
         if (!arg_no_block) {
                 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
                 if (r < 0)
@@ -774,6 +864,7 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
 
 static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
+        _cleanup_strv_free_ char **stop_names = NULL, **restart_names = NULL;
         int r;
 
         if (!arg_enable && !arg_now)
@@ -804,14 +895,25 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
                 if (r == 0)
                         break;
 
-                if (streq(type, "unlink") && is_portable_managed(path))
-                        (void) maybe_start_stop_restart(bus, path, "StopUnit", wait);
+                if (streq(type, "unlink") && is_portable_managed(path) && arg_now) {
+                        _cleanup_free_ char *name = NULL;
+
+                        r = path_extract_filename(path, &name);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
+
+                        r = strv_consume(&stop_names, TAKE_PTR(name));
+                        if (r < 0)
+                                return log_oom();
+                }
         }
 
         r = sd_bus_message_exit_container(reply);
         if (r < 0)
                 return r;
 
+        (void) maybe_start_stop_restart_units(bus, stop_names, "stop", wait);
+
         /* Then we get a list of units that were either added or changed, so that we can
          * enable them and/or restart them if the user asked us to. */
         r = sd_bus_message_enter_container(reply, 'a', "(sss)");
@@ -829,7 +931,15 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
 
                 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
                         (void) maybe_enable_disable(bus, path, true);
-                        (void) maybe_start_stop_restart(bus, path, "RestartUnit", wait);
+
+                        _cleanup_free_ char *name = NULL;
+                        r = path_extract_filename(path, &name);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
+
+                        r = strv_consume(&restart_names, TAKE_PTR(name));
+                        if (r < 0)
+                                return log_oom();
                 }
         }
 
@@ -837,6 +947,8 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
         if (r < 0)
                 return r;
 
+        (void) maybe_start_stop_restart_units(bus, restart_names, "restart", wait);
+
         if (!arg_no_block) {
                 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
                 if (r < 0)
@@ -940,9 +1052,6 @@ static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) {
                 if (r < 0)
                         return bus_log_parse_error(r);
 
-                (void) maybe_start_stop_restart(bus, name, "StopUnit", wait);
-                (void) maybe_enable_disable(bus, name, false);
-
                 r = strv_extend(&units, name);
                 if (r < 0)
                         return log_oom();
@@ -952,6 +1061,12 @@ static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
+        (void) maybe_start_stop_restart_units(bus, units, "stop", wait);
+
+        /* Disable after stopping to match the idiomatic stop-then-disable lifecycle order. */
+        STRV_FOREACH(name, units)
+                (void) maybe_enable_disable(bus, *name, false);
+
         /* Stopping must always block or the detach will fail if the unit is still running */
         r = bus_wait_for_jobs(wait, arg_quiet, NULL);
         if (r < 0)