summaryrefslogtreecommitdiffstats
path: root/src/systemctl/systemctl-start-unit.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/systemctl/systemctl-start-unit.c')
-rw-r--r--src/systemctl/systemctl-start-unit.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c
new file mode 100644
index 0000000..6a50d1c
--- /dev/null
+++ b/src/systemctl/systemctl-start-unit.c
@@ -0,0 +1,407 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "bus-wait-for-jobs.h"
+#include "bus-wait-for-units.h"
+#include "macro.h"
+#include "special.h"
+#include "string-util.h"
+#include "systemctl-start-unit.h"
+#include "systemctl-util.h"
+#include "systemctl.h"
+#include "terminal-util.h"
+
+static const struct {
+ const char *verb; /* systemctl verb */
+ const char *method; /* Name of the specific D-Bus method */
+ const char *job_type; /* Job type when passing to the generic EnqueueUnitJob() method */
+} unit_actions[] = {
+ { "start", "StartUnit", "start" },
+ { "stop", "StopUnit", "stop" },
+ { "condstop", "StopUnit", "stop" }, /* legacy alias */
+ { "reload", "ReloadUnit", "reload" },
+ { "restart", "RestartUnit", "restart" },
+ { "try-restart", "TryRestartUnit", "try-restart" },
+ { "condrestart", "TryRestartUnit", "try-restart" }, /* legacy alias */
+ { "reload-or-restart", "ReloadOrRestartUnit", "reload-or-restart" },
+ { "try-reload-or-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" },
+ { "reload-or-try-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
+ { "condreload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
+ { "force-reload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
+};
+
+static const char *verb_to_method(const char *verb) {
+ for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
+ if (streq_ptr(unit_actions[i].verb, verb))
+ return unit_actions[i].method;
+
+ return "StartUnit";
+}
+
+static const char *verb_to_job_type(const char *verb) {
+ for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
+ if (streq_ptr(unit_actions[i].verb, verb))
+ return unit_actions[i].job_type;
+
+ return "start";
+}
+
+static int start_unit_one(
+ sd_bus *bus,
+ const char *method, /* When using classic per-job bus methods */
+ const char *job_type, /* When using new-style EnqueueUnitJob() */
+ const char *name,
+ const char *mode,
+ sd_bus_error *error,
+ BusWaitForJobs *w,
+ BusWaitForUnits *wu) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *path;
+ bool done = false;
+ int r;
+
+ assert(method);
+ assert(name);
+ assert(mode);
+ assert(error);
+
+ log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
+ arg_dry_run ? "Would execute" : "Executing",
+ method, name, mode);
+
+ if (arg_dry_run)
+ return 0;
+
+ if (arg_show_transaction) {
+ _cleanup_(sd_bus_error_free) sd_bus_error enqueue_error = SD_BUS_ERROR_NULL;
+
+ /* Use the new, fancy EnqueueUnitJob() API if the user wants us to print the transaction */
+ r = bus_call_method(
+ bus,
+ bus_systemd_mgr,
+ "EnqueueUnitJob",
+ &enqueue_error,
+ &reply,
+ "sss",
+ name, job_type, mode);
+ if (r < 0) {
+ if (!sd_bus_error_has_name(&enqueue_error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ (void) sd_bus_error_move(error, &enqueue_error);
+ goto fail;
+ }
+
+ /* Hmm, the API is not yet available. Let's use the classic API instead (see below). */
+ log_notice("--show-transaction not supported by this service manager, proceeding without.");
+ } else {
+ const char *u, *jt;
+ uint32_t id;
+
+ r = sd_bus_message_read(reply, "uosos", &id, &path, &u, NULL, &jt);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ log_info("Enqueued anchor job %" PRIu32 " %s/%s.", id, u, jt);
+
+ r = sd_bus_message_enter_container(reply, 'a', "(uosos)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+ for (;;) {
+ r = sd_bus_message_read(reply, "(uosos)", &id, NULL, &u, NULL, &jt);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ log_info("Enqueued auxiliary job %" PRIu32 " %s/%s.", id, u, jt);
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ done = true;
+ }
+ }
+
+ if (!done) {
+ r = bus_call_method(bus, bus_systemd_mgr, method, error, &reply, "ss", name, mode);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_read(reply, "o", &path);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ }
+
+ if (need_daemon_reload(bus, name) > 0)
+ warn_unit_file_changed(name);
+
+ if (w) {
+ log_debug("Adding %s to the set", path);
+ r = bus_wait_for_jobs_add(w, path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch job for %s: %m", name);
+ }
+
+ if (wu) {
+ r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch unit %s: %m", name);
+ }
+
+ return 0;
+
+fail:
+ /* There's always a fallback possible for legacy actions. */
+ if (arg_action != ACTION_SYSTEMCTL)
+ return r;
+
+ if (sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
+ STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) {
+ /* Ignore masked unit if try-* is requested */
+
+ log_debug_errno(r, "Failed to %s %s, ignoring: %s", job_type, name, bus_error_message(error, r));
+ return 0;
+ } else
+ log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
+
+ if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT,
+ BUS_ERROR_UNIT_MASKED,
+ BUS_ERROR_JOB_TYPE_NOT_APPLICABLE))
+ log_error("See %s logs and 'systemctl%s status%s %s' for details.",
+ arg_scope == LOOKUP_SCOPE_SYSTEM ? "system" : "user",
+ arg_scope == LOOKUP_SCOPE_SYSTEM ? "" : " --user",
+ name[0] == '-' ? " --" : "",
+ name);
+
+ return r;
+}
+
+static int enqueue_marked_jobs(
+ sd_bus *bus,
+ BusWaitForJobs *w) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ log_debug("%s dbus call org.freedesktop.systemd1.Manager EnqueueMarkedJobs()",
+ arg_dry_run ? "Would execute" : "Executing");
+
+ if (arg_dry_run)
+ return 0;
+
+ r = bus_call_method(bus, bus_systemd_mgr, "EnqueueMarkedJobs", &error, &reply, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start jobs: %s", bus_error_message(&error, r));
+
+ _cleanup_strv_free_ char **paths = NULL;
+ r = sd_bus_message_read_strv(reply, &paths);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (w)
+ STRV_FOREACH(path, paths) {
+ log_debug("Adding %s to the set", *path);
+ r = bus_wait_for_jobs_add(w, *path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch job %s: %m", *path);
+ }
+
+ return 0;
+}
+
+const struct action_metadata action_table[_ACTION_MAX] = {
+ [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" },
+ [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" },
+ [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" },
+ [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" },
+ [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" },
+ [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" },
+ [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" },
+ [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" },
+ [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" },
+ [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" },
+ [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" },
+ [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" },
+ [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" },
+ [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" },
+ [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" },
+ [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" },
+};
+
+enum action verb_to_action(const char *verb) {
+ for (enum action i = 0; i < _ACTION_MAX; i++)
+ if (streq_ptr(action_table[i].verb, verb))
+ return i;
+
+ return _ACTION_INVALID;
+}
+
+static const char** make_extra_args(const char *extra_args[static 4]) {
+ size_t n = 0;
+
+ assert(extra_args);
+
+ if (arg_scope != LOOKUP_SCOPE_SYSTEM)
+ extra_args[n++] = "--user";
+
+ if (arg_transport == BUS_TRANSPORT_REMOTE) {
+ extra_args[n++] = "-H";
+ extra_args[n++] = arg_host;
+ } else if (arg_transport == BUS_TRANSPORT_MACHINE) {
+ extra_args[n++] = "-M";
+ extra_args[n++] = arg_host;
+ } else
+ assert(arg_transport == BUS_TRANSPORT_LOCAL);
+
+ extra_args[n] = NULL;
+ return extra_args;
+}
+
+int verb_start(int argc, char *argv[], void *userdata) {
+ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL;
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+ const char *method, *job_type, *mode, *one_name, *suffix = NULL;
+ _cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */
+ _cleanup_strv_free_ char **names = NULL;
+ int r, ret = EXIT_SUCCESS;
+ sd_bus *bus;
+
+ if (arg_wait && !STR_IN_SET(argv[0], "start", "restart"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--wait may only be used with the 'start' or 'restart' commands.");
+
+ /* We cannot do sender tracking on the private bus, so we need the full one for RefUnit to implement
+ * --wait */
+ r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus);
+ if (r < 0)
+ return r;
+
+ ask_password_agent_open_maybe();
+ polkit_agent_open_maybe();
+
+ if (arg_action == ACTION_SYSTEMCTL) {
+ enum action action;
+
+ action = verb_to_action(argv[0]);
+
+ if (action != _ACTION_INVALID) {
+ /* A command in style "systemctl reboot", "systemctl poweroff", … */
+ method = "StartUnit";
+ job_type = "start";
+ mode = action_table[action].mode;
+ one_name = action_table[action].target;
+ } else {
+ if (streq(argv[0], "isolate")) {
+ /* A "systemctl isolate <unit1> <unit2> …" command */
+ method = "StartUnit";
+ job_type = "start";
+ mode = "isolate";
+ suffix = ".target";
+ } else if (!arg_marked) {
+ /* A command in style of "systemctl start <unit1> <unit2> …", "sysemctl stop <unit1> <unit2> …" and so on */
+ method = verb_to_method(argv[0]);
+ job_type = verb_to_job_type(argv[0]);
+ mode = arg_job_mode();
+ } else
+ method = job_type = mode = NULL;
+
+ one_name = NULL;
+ }
+ } else {
+ /* A SysV legacy command such as "halt", "reboot", "poweroff", … */
+ assert(arg_action >= 0 && arg_action < _ACTION_MAX);
+ assert(action_table[arg_action].target);
+ assert(action_table[arg_action].mode);
+
+ method = "StartUnit";
+ job_type = "start";
+ mode = action_table[arg_action].mode;
+ one_name = action_table[arg_action].target;
+ }
+
+ if (one_name) {
+ names = strv_new(one_name);
+ if (!names)
+ return log_oom();
+ } else if (!arg_marked) {
+ bool expanded;
+
+ r = expand_unit_names(bus, strv_skip(argv, 1), suffix, &names, &expanded);
+ if (r < 0)
+ return log_error_errno(r, "Failed to expand names: %m");
+
+ if (!arg_all && expanded && streq(job_type, "start") && !arg_quiet) {
+ log_warning("Warning: %ssystemctl start called with a glob pattern.%s",
+ ansi_highlight_red(),
+ ansi_normal());
+ log_notice("Hint: unit globs expand to loaded units, so start will usually have no effect.\n"
+ " Passing --all will also load units which are pulled in by other units.\n"
+ " See systemctl(1) for more details.");
+ }
+ }
+
+ if (!arg_no_block) {
+ r = bus_wait_for_jobs_new(bus, &w);
+ if (r < 0)
+ return log_error_errno(r, "Could not watch jobs: %m");
+ }
+
+ if (arg_wait) {
+ r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable subscription: %m");
+
+ r = bus_wait_for_units_new(bus, &wu);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate unit watch context: %m");
+ }
+
+ if (arg_marked)
+ ret = enqueue_marked_jobs(bus, w);
+ else
+ STRV_FOREACH(name, names) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu);
+ if (ret == EXIT_SUCCESS && r < 0)
+ ret = translate_bus_error_to_exit_status(r, &error);
+
+ if (r >= 0 && streq(method, "StopUnit")) {
+ r = strv_push(&stopped_units, *name);
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ if (!arg_no_block) {
+ const char* extra_args[4];
+
+ r = bus_wait_for_jobs(w, arg_quiet, make_extra_args(extra_args));
+ if (r < 0)
+ return r;
+
+ /* When stopping units, warn if they can still be triggered by
+ * another active unit (socket, path, timer) */
+ if (!arg_quiet)
+ STRV_FOREACH(name, stopped_units)
+ (void) check_triggering_units(bus, *name);
+ }
+
+ if (arg_wait) {
+ r = bus_wait_for_units_run(wu);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for units: %m");
+ if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
+ ret = EXIT_FAILURE;
+ }
+
+ return ret;
+}