Systemd/src/systemctl/systemctl-start-unit.c
Lennart Poettering daf71ef61c systemctl: split up humungous systemctl.c file
This is just some refactoring: shifting around of code, not change in
codeflow.

This splits up the way too huge systemctl.c in multiple more easily
digestable files. It roughly follows the rule that each family of verbs
gets its own .c/.h file pair, and so do all the compat executable names
we support. Plus three extra files for sysv compat (which existed before
already, but I renamed slightly, to get the systemctl- prefix lik
everything else), a -util file with generic stuff everything uses, and a
-logind file with everything that talks directly to logind instead of
PID1.

systemctl is still a bit too complex for my taste, but I think this way
itc omes in a more digestable bits at least.

No change of behaviour, just reshuffling of some code.
2020-10-07 23:12:15 +02:00

369 lines
16 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#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;
}