| /* 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) { |
| size_t i; |
| |
| for (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) { |
| size_t i; |
| |
| for (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; |
| |
| 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 == UNIT_FILE_SYSTEM ? "system" : "user", |
| arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", |
| name[0] == '-' ? " --" : "", |
| name); |
| |
| return r; |
| } |
| |
| 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) { |
| enum action i; |
| |
| for (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 != UNIT_FILE_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 start_unit(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; |
| char **name; |
| |
| 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 { |
| /* 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; |
| } |
| 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 { |
| 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"); |
| } |
| |
| 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; |
| } |