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.
This commit is contained in:
Lennart Poettering 2020-10-07 11:27:56 +02:00
parent 4dcc0653b5
commit daf71ef61c
72 changed files with 9252 additions and 8534 deletions

View File

@ -2105,9 +2105,77 @@ endif
public_programs += executable(
'systemctl',
'src/systemctl/systemctl-add-dependency.c',
'src/systemctl/systemctl-add-dependency.h',
'src/systemctl/systemctl-cancel-job.c',
'src/systemctl/systemctl-cancel-job.h',
'src/systemctl/systemctl-clean-or-freeze.c',
'src/systemctl/systemctl-clean-or-freeze.h',
'src/systemctl/systemctl-compat-halt.c',
'src/systemctl/systemctl-compat-halt.h',
'src/systemctl/systemctl-compat-runlevel.c',
'src/systemctl/systemctl-compat-runlevel.h',
'src/systemctl/systemctl-compat-shutdown.c',
'src/systemctl/systemctl-compat-shutdown.h',
'src/systemctl/systemctl-compat-telinit.c',
'src/systemctl/systemctl-compat-telinit.h',
'src/systemctl/systemctl-daemon-reload.c',
'src/systemctl/systemctl-daemon-reload.h',
'src/systemctl/systemctl-edit.c',
'src/systemctl/systemctl-edit.h',
'src/systemctl/systemctl-enable.c',
'src/systemctl/systemctl-enable.h',
'src/systemctl/systemctl-is-active.c',
'src/systemctl/systemctl-is-active.h',
'src/systemctl/systemctl-is-enabled.c',
'src/systemctl/systemctl-is-enabled.h',
'src/systemctl/systemctl-is-system-running.c',
'src/systemctl/systemctl-is-system-running.h',
'src/systemctl/systemctl-kill.c',
'src/systemctl/systemctl-kill.h',
'src/systemctl/systemctl-list-dependencies.c',
'src/systemctl/systemctl-list-dependencies.h',
'src/systemctl/systemctl-list-jobs.c',
'src/systemctl/systemctl-list-jobs.h',
'src/systemctl/systemctl-list-machines.c',
'src/systemctl/systemctl-list-machines.h',
'src/systemctl/systemctl-list-unit-files.c',
'src/systemctl/systemctl-list-unit-files.h',
'src/systemctl/systemctl-list-units.c',
'src/systemctl/systemctl-list-units.h',
'src/systemctl/systemctl-log-setting.c',
'src/systemctl/systemctl-log-setting.h',
'src/systemctl/systemctl-logind.c',
'src/systemctl/systemctl-logind.h',
'src/systemctl/systemctl-preset-all.c',
'src/systemctl/systemctl-preset-all.h',
'src/systemctl/systemctl-reset-failed.c',
'src/systemctl/systemctl-reset-failed.h',
'src/systemctl/systemctl-service-watchdogs.c',
'src/systemctl/systemctl-service-watchdogs.h',
'src/systemctl/systemctl-set-default.c',
'src/systemctl/systemctl-set-default.h',
'src/systemctl/systemctl-set-environment.c',
'src/systemctl/systemctl-set-environment.h',
'src/systemctl/systemctl-set-property.c',
'src/systemctl/systemctl-set-property.h',
'src/systemctl/systemctl-show.c',
'src/systemctl/systemctl-show.h',
'src/systemctl/systemctl-start-special.c',
'src/systemctl/systemctl-start-special.h',
'src/systemctl/systemctl-start-unit.c',
'src/systemctl/systemctl-start-unit.h',
'src/systemctl/systemctl-switch-root.c',
'src/systemctl/systemctl-switch-root.h',
'src/systemctl/systemctl-sysv-compat.c',
'src/systemctl/systemctl-sysv-compat.h',
'src/systemctl/systemctl-trivial-method.c',
'src/systemctl/systemctl-trivial-method.h',
'src/systemctl/systemctl-util.c',
'src/systemctl/systemctl-util.c',
'src/systemctl/systemctl-util.h',
'src/systemctl/systemctl.c',
'src/systemctl/sysv-compat.h',
'src/systemctl/sysv-compat.c',
'src/systemctl/systemctl.h',
include_directories : includes,
link_with : systemctl_link_with,
dependencies : [threads,

View File

@ -0,0 +1,88 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-add-dependency.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-util.h"
#include "systemctl.h"
int add_dependency(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
_cleanup_free_ char *target = NULL;
const char *verb = argv[0];
UnitFileChange *changes = NULL;
size_t n_changes = 0;
UnitDependency dep;
int r;
if (!argv[1])
return 0;
r = unit_name_mangle_with_suffix(argv[1], "as target",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".target", &target);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = mangle_names("as dependency", strv_skip(argv, 2), &names);
if (r < 0)
return r;
if (streq(verb, "add-wants"))
dep = UNIT_WANTS;
else if (streq(verb, "add-requires"))
dep = UNIT_REQUIRES;
else
assert_not_reached("Unknown verb");
if (install_client_side()) {
r = unit_file_add_dependency(arg_scope, unit_file_flags_from_args(), arg_root, names, target, dep, &changes, &n_changes);
unit_file_dump_changes(r, "add dependency on", changes, n_changes, arg_quiet);
if (r > 0)
r = 0;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "AddDependencyUnitFiles");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, names);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r));
r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
if (r < 0)
goto finish;
if (arg_no_reload) {
r = 0;
goto finish;
}
r = daemon_reload(argc, argv, userdata);
}
finish:
unit_file_changes_free(changes, n_changes);
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int add_dependency(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "parse-util.h"
#include "systemctl-cancel-job.h"
#include "systemctl-trivial-method.h"
#include "systemctl-util.h"
#include "systemctl.h"
int cancel_job(int argc, char *argv[], void *userdata) {
sd_bus *bus;
char **name;
int r;
if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */
return trivial_method(argc, argv, userdata);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
STRV_FOREACH(name, strv_skip(argv, 1)) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
uint32_t id;
int q;
q = safe_atou32(*name, &id);
if (q < 0)
return log_error_errno(q, "Failed to parse job id \"%s\": %m", *name);
q = bus_call_method(bus, bus_systemd_mgr, "CancelJob", &error, NULL, "u", id);
if (q < 0) {
log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int cancel_job(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-wait-for-units.h"
#include "systemctl-clean-or-freeze.h"
#include "systemctl-util.h"
#include "systemctl.h"
int clean_or_freeze_unit(int argc, char *argv[], void *userdata) {
_cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL;
_cleanup_strv_free_ char **names = NULL;
int r, ret = EXIT_SUCCESS;
char **name;
const char *method;
sd_bus *bus;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
if (!arg_clean_what) {
arg_clean_what = strv_new("cache", "runtime");
if (!arg_clean_what)
return log_oom();
}
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
if (!arg_no_block) {
r = bus_wait_for_units_new(bus, &w);
if (r < 0)
return log_error_errno(r, "Failed to allocate unit waiter: %m");
}
if (streq(argv[0], "clean"))
method = "CleanUnit";
else if (streq(argv[0], "freeze"))
method = "FreezeUnit";
else if (streq(argv[0], "thaw"))
method = "ThawUnit";
else
assert_not_reached("Unhandled method");
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
if (w) {
/* If we shall wait for the cleaning to complete, let's add a ref on the unit first */
r = bus_call_method(bus, bus_systemd_mgr, "RefUnit", &error, NULL, "s", *name);
if (r < 0) {
log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r));
if (ret == EXIT_SUCCESS)
ret = r;
continue;
}
}
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", *name);
if (r < 0)
return bus_log_create_error(r);
if (streq(method, "CleanUnit")) {
r = sd_bus_message_append_strv(m, arg_clean_what);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, 0, &error, NULL);
if (r < 0) {
log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r));
if (ret == EXIT_SUCCESS) {
ret = r;
continue;
}
}
if (w) {
r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to watch unit %s: %m", *name);
}
}
r = bus_wait_for_units_run(w);
if (r < 0)
return log_error_errno(r, "Failed to wait for units: %m");
if (r == BUS_WAIT_FAILURE)
ret = EXIT_FAILURE;
return ret;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int clean_or_freeze_unit(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,202 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <getopt.h>
#include <unistd.h>
#include "sd-daemon.h"
#include "alloc-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "reboot-util.h"
#include "systemctl-compat-halt.h"
#include "systemctl-compat-telinit.h"
#include "systemctl-logind.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
#include "utmp-wtmp.h"
static int halt_help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("halt", "8", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...]%s\n"
"\n%s%s the system.%s\n"
"\nOptions:\n"
" --help Show this help\n"
" --halt Halt the machine\n"
" -p --poweroff Switch off the machine\n"
" --reboot Reboot the machine\n"
" -f --force Force immediate halt/power-off/reboot\n"
" -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n"
" -d --no-wtmp Don't write wtmp record\n"
" --no-wall Don't send wall message before halt/power-off/reboot\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, arg_action == ACTION_REBOOT ? " [ARG]" : ""
, ansi_highlight()
, arg_action == ACTION_REBOOT ? "Reboot" :
arg_action == ACTION_POWEROFF ? "Power off" :
"Halt"
, ansi_normal()
, link
);
return 0;
}
int halt_parse_argv(int argc, char *argv[]) {
enum {
ARG_HELP = 0x100,
ARG_HALT,
ARG_REBOOT,
ARG_NO_WALL
};
static const struct option options[] = {
{ "help", no_argument, NULL, ARG_HELP },
{ "halt", no_argument, NULL, ARG_HALT },
{ "poweroff", no_argument, NULL, 'p' },
{ "reboot", no_argument, NULL, ARG_REBOOT },
{ "force", no_argument, NULL, 'f' },
{ "wtmp-only", no_argument, NULL, 'w' },
{ "no-wtmp", no_argument, NULL, 'd' },
{ "no-sync", no_argument, NULL, 'n' },
{ "no-wall", no_argument, NULL, ARG_NO_WALL },
{}
};
int c, r, runlevel;
assert(argc >= 0);
assert(argv);
if (utmp_get_runlevel(&runlevel, NULL) >= 0)
if (IN_SET(runlevel, '0', '6'))
arg_force = 2;
while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0)
switch (c) {
case ARG_HELP:
return halt_help();
case ARG_HALT:
arg_action = ACTION_HALT;
break;
case 'p':
if (arg_action != ACTION_REBOOT)
arg_action = ACTION_POWEROFF;
break;
case ARG_REBOOT:
arg_action = ACTION_REBOOT;
break;
case 'f':
arg_force = 2;
break;
case 'w':
arg_dry_run = true;
break;
case 'd':
arg_no_wtmp = true;
break;
case 'n':
arg_no_sync = true;
break;
case ARG_NO_WALL:
arg_no_wall = true;
break;
case 'i':
case 'h':
/* Compatibility nops */
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) {
r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL, false);
if (r < 0)
return r;
} else if (optind < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too many arguments.");
return 1;
}
int halt_main(void) {
int r;
r = logind_check_inhibitors(arg_action);
if (r < 0)
return r;
/* Delayed shutdown requested, and was successful */
if (arg_when > 0 && logind_schedule_shutdown() == 0)
return 0;
/* No delay, or logind failed or is not at all available */
if (geteuid() != 0) {
if (arg_dry_run || arg_force > 0) {
(void) must_be_root();
return -EPERM;
}
/* Try logind if we are a normal user and no special mode applies. Maybe polkit allows us to
* shutdown the machine. */
if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT, ACTION_HALT)) {
r = logind_reboot(arg_action);
if (r >= 0)
return r;
if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS))
/* Requested operation is not supported on the local system or already in
* progress */
return r;
/* on all other errors, try low-level operation */
}
}
/* In order to minimize the difference between operation with and without logind, we explicitly
* enable non-blocking mode for this, as logind's shutdown operations are always non-blocking. */
arg_no_block = true;
if (!arg_dry_run && !arg_force)
return start_with_fallback();
assert(geteuid() == 0);
if (!arg_no_wtmp) {
if (sd_booted() > 0)
log_debug("Not writing utmp record, assuming that systemd-update-utmp is used.");
else {
r = utmp_put_shutdown();
if (r < 0)
log_warning_errno(r, "Failed to write utmp record: %m");
}
}
if (arg_dry_run)
return 0;
r = halt_now(arg_action);
return log_error_errno(r, "Failed to reboot: %m");
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int halt_parse_argv(int argc, char *argv[]);
int halt_main(void);

View File

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <getopt.h>
#include "alloc-util.h"
#include "pretty-print.h"
#include "systemctl-compat-runlevel.h"
#include "systemctl.h"
#include "terminal-util.h"
#include "utmp-wtmp.h"
static int runlevel_help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("runlevel", "8", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...]\n"
"\n%sPrints the previous and current runlevel of the init system.%s\n"
"\nOptions:\n"
" --help Show this help\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
, link
);
return 0;
}
int runlevel_parse_argv(int argc, char *argv[]) {
enum {
ARG_HELP = 0x100,
};
static const struct option options[] = {
{ "help", no_argument, NULL, ARG_HELP },
{}
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
switch (c) {
case ARG_HELP:
return runlevel_help();
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (optind < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too many arguments.");
return 1;
}
int runlevel_main(void) {
int r, runlevel, previous;
r = utmp_get_runlevel(&runlevel, &previous);
if (r < 0) {
puts("unknown");
return r;
}
printf("%c %c\n",
previous <= 0 ? 'N' : previous,
runlevel <= 0 ? 'N' : runlevel);
return 0;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int runlevel_parse_argv(int argc, char *argv[]);
int runlevel_main(void);

View File

@ -0,0 +1,143 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <getopt.h>
#include "alloc-util.h"
#include "pretty-print.h"
#include "systemctl-compat-shutdown.h"
#include "systemctl-sysv-compat.h"
#include "systemctl.h"
#include "terminal-util.h"
static int shutdown_help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("shutdown", "8", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] [TIME] [WALL...]\n"
"\n%sShut down the system.%s\n"
"\nOptions:\n"
" --help Show this help\n"
" -H --halt Halt the machine\n"
" -P --poweroff Power-off the machine\n"
" -r --reboot Reboot the machine\n"
" -h Equivalent to --poweroff, overridden by --halt\n"
" -k Don't halt/power-off/reboot, just send warnings\n"
" --no-wall Don't send wall message before halt/power-off/reboot\n"
" -c Cancel a pending shutdown\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
, link
);
return 0;
}
int shutdown_parse_argv(int argc, char *argv[]) {
enum {
ARG_HELP = 0x100,
ARG_NO_WALL
};
static const struct option options[] = {
{ "help", no_argument, NULL, ARG_HELP },
{ "halt", no_argument, NULL, 'H' },
{ "poweroff", no_argument, NULL, 'P' },
{ "reboot", no_argument, NULL, 'r' },
{ "kexec", no_argument, NULL, 'K' }, /* not documented extension */
{ "no-wall", no_argument, NULL, ARG_NO_WALL },
{}
};
char **wall = NULL;
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "HPrhkKat:fFc", options, NULL)) >= 0)
switch (c) {
case ARG_HELP:
return shutdown_help();
case 'H':
arg_action = ACTION_HALT;
break;
case 'P':
arg_action = ACTION_POWEROFF;
break;
case 'r':
if (kexec_loaded())
arg_action = ACTION_KEXEC;
else
arg_action = ACTION_REBOOT;
break;
case 'K':
arg_action = ACTION_KEXEC;
break;
case 'h':
if (arg_action != ACTION_HALT)
arg_action = ACTION_POWEROFF;
break;
case 'k':
arg_dry_run = true;
break;
case ARG_NO_WALL:
arg_no_wall = true;
break;
case 'a':
case 't': /* Note that we also ignore any passed argument to -t, not just the -t itself */
case 'f':
case 'F':
/* Compatibility nops */
break;
case 'c':
arg_action = ACTION_CANCEL_SHUTDOWN;
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) {
r = parse_shutdown_time_spec(argv[optind], &arg_when);
if (r < 0) {
log_error("Failed to parse time specification: %s", argv[optind]);
return r;
}
} else
arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE;
if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN)
/* No time argument for shutdown cancel */
wall = argv + optind;
else if (argc > optind + 1)
/* We skip the time argument */
wall = argv + optind + 1;
if (wall) {
arg_wall = strv_copy(wall);
if (!arg_wall)
return log_oom();
}
optind = argc;
return 1;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int shutdown_parse_argv(int argc, char *argv[]);

View File

@ -0,0 +1,152 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <getopt.h>
#include "alloc-util.h"
#include "pretty-print.h"
#include "systemctl-compat-telinit.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-start-unit.h"
#include "systemctl-sysv-compat.h"
#include "systemctl.h"
#include "terminal-util.h"
static int telinit_help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("telinit", "8", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] COMMAND\n\n"
"%sSend control commands to the init daemon.%s\n"
"\nCommands:\n"
" 0 Power-off the machine\n"
" 6 Reboot the machine\n"
" 2, 3, 4, 5 Start runlevelX.target unit\n"
" 1, s, S Enter rescue mode\n"
" q, Q Reload init daemon configuration\n"
" u, U Reexecute init daemon\n"
"\nOptions:\n"
" --help Show this help\n"
" --no-wall Don't send wall message before halt/power-off/reboot\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
, link
);
return 0;
}
int telinit_parse_argv(int argc, char *argv[]) {
enum {
ARG_HELP = 0x100,
ARG_NO_WALL
};
static const struct option options[] = {
{ "help", no_argument, NULL, ARG_HELP },
{ "no-wall", no_argument, NULL, ARG_NO_WALL },
{}
};
static const struct {
char from;
enum action to;
} table[] = {
{ '0', ACTION_POWEROFF },
{ '6', ACTION_REBOOT },
{ '1', ACTION_RESCUE },
{ '2', ACTION_RUNLEVEL2 },
{ '3', ACTION_RUNLEVEL3 },
{ '4', ACTION_RUNLEVEL4 },
{ '5', ACTION_RUNLEVEL5 },
{ 's', ACTION_RESCUE },
{ 'S', ACTION_RESCUE },
{ 'q', ACTION_RELOAD },
{ 'Q', ACTION_RELOAD },
{ 'u', ACTION_REEXEC },
{ 'U', ACTION_REEXEC }
};
unsigned i;
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
switch (c) {
case ARG_HELP:
return telinit_help();
case ARG_NO_WALL:
arg_no_wall = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (optind >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: required argument missing.",
program_invocation_short_name);
if (optind + 1 < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too many arguments.");
if (strlen(argv[optind]) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected single character argument.");
for (i = 0; i < ELEMENTSOF(table); i++)
if (table[i].from == argv[optind][0])
break;
if (i >= ELEMENTSOF(table))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown command '%s'.", argv[optind]);
arg_action = table[i].to;
optind++;
return 1;
}
int start_with_fallback(void) {
/* First, try systemd via D-Bus. */
if (start_unit(0, NULL, NULL) == 0)
return 0;
#if HAVE_SYSV_COMPAT
/* Nothing else worked, so let's try /dev/initctl */
if (talk_initctl(action_to_runlevel()) > 0)
return 0;
#endif
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to talk to init daemon.");
}
int reload_with_fallback(void) {
/* First, try systemd via D-Bus. */
if (daemon_reload(0, NULL, NULL) >= 0)
return 0;
/* Nothing else worked, so let's try signals */
assert(IN_SET(arg_action, ACTION_RELOAD, ACTION_REEXEC));
if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0)
return log_error_errno(errno, "kill() failed: %m");
return 0;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int telinit_parse_argv(int argc, char *argv[]);
int start_with_fallback(void);
int reload_with_fallback(void);

View File

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-util.h"
#include "systemctl.h"
int daemon_reload(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *method;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
switch (arg_action) {
case ACTION_RELOAD:
method = "Reload";
break;
case ACTION_REEXEC:
method = "Reexecute";
break;
case ACTION_SYSTEMCTL:
method = streq(argv[0], "daemon-reexec") ? "Reexecute" :
/* "daemon-reload" */ "Reload";
break;
default:
assert_not_reached("Unexpected action");
}
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
return bus_log_create_error(r);
/* Note we use an extra-long timeout here. This is because a reload or reexec means generators are
* rerun which are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the
* generators can have their timeout, and for everything else there's the same time budget in
* place. */
r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
/* On reexecution, we expect a disconnect, not a reply */
if (IN_SET(r, -ETIMEDOUT, -ECONNRESET) && streq(method, "Reexecute"))
r = 0;
if (r < 0 && arg_action == ACTION_SYSTEMCTL)
return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
/* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support
* fallbacks to the old ways of doing things, hence don't log any error in that case here. */
return r < 0 ? r : 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int daemon_reload(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,490 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "copy.h"
#include "fs-util.h"
#include "mkdir.h"
#include "pager.h"
#include "path-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "selinux-util.h"
#include "stat-util.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-edit.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
int cat(int argc, char *argv[], void *userdata) {
_cleanup_(hashmap_freep) Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
char **name;
sd_bus *bus;
bool first = true;
int r, rc = 0;
/* Include all units by default — i.e. continue as if the --all option was used */
if (strv_isempty(arg_states))
arg_all = true;
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
r = lookup_paths_init(&lp, arg_scope, 0, arg_root);
if (r < 0)
return log_error_errno(r, "Failed to determine unit paths: %m");
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
r = maybe_extend_with_unit_dependencies(bus, &names);
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
STRV_FOREACH(name, names) {
_cleanup_free_ char *fragment_path = NULL;
_cleanup_strv_free_ char **dropin_paths = NULL;
r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
if (r == -ERFKILL) {
printf("%s# Unit %s is masked%s.\n",
ansi_highlight_magenta(),
*name,
ansi_normal());
continue;
}
if (r == -EKEYREJECTED) {
printf("%s# Unit %s could not be loaded.%s\n",
ansi_highlight_magenta(),
*name,
ansi_normal());
continue;
}
if (r < 0)
return r;
if (r == 0) {
/* Skip units which have no on-disk counterpart, but propagate the error to the
* user */
rc = -ENOENT;
continue;
}
if (first)
first = false;
else
puts("");
if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
fprintf(stderr,
"%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n"
"%s# This output shows the current version of the unit's original fragment and drop-in files.\n"
"%s# If fragments or drop-ins were added or removed, they are not properly reflected in this output.\n"
"%s# Run 'systemctl%s daemon-reload' to reload units.%s\n",
ansi_highlight_red(),
*name,
ansi_highlight_red(),
ansi_highlight_red(),
ansi_highlight_red(),
arg_scope == UNIT_FILE_SYSTEM ? "" : " --user",
ansi_normal());
r = cat_files(fragment_path, dropin_paths, 0);
if (r < 0)
return r;
}
return rc;
}
static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) {
_cleanup_free_ char *t = NULL;
int r;
assert(new_path);
assert(original_path);
assert(ret_tmp_fn);
r = tempfn_random(new_path, NULL, &t);
if (r < 0)
return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path);
r = mkdir_parents_label(new_path, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path);
r = mac_selinux_create_file_prepare(original_path, S_IFREG);
if (r < 0)
return r;
r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK);
if (r == -ENOENT) {
r = touch(t);
mac_selinux_create_file_clear();
if (r < 0)
return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
} else {
mac_selinux_create_file_clear();
if (r < 0)
return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path);
}
*ret_tmp_fn = TAKE_PTR(t);
return 0;
}
static int get_file_to_edit(
const LookupPaths *paths,
const char *name,
char **ret_path) {
_cleanup_free_ char *path = NULL, *run = NULL;
assert(name);
assert(ret_path);
path = path_join(paths->persistent_config, name);
if (!path)
return log_oom();
if (arg_runtime) {
run = path_join(paths->runtime_config, name);
if (!run)
return log_oom();
}
if (arg_runtime) {
if (access(path, F_OK) >= 0)
return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
"Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.",
run, path);
*ret_path = TAKE_PTR(run);
} else
*ret_path = TAKE_PTR(path);
return 0;
}
static int unit_file_create_new(
const LookupPaths *paths,
const char *unit_name,
const char *suffix,
char **ret_new_path,
char **ret_tmp_path) {
_cleanup_free_ char *new_path = NULL, *tmp_path = NULL;
const char *ending;
int r;
assert(unit_name);
assert(ret_new_path);
assert(ret_tmp_path);
ending = strjoina(unit_name, suffix);
r = get_file_to_edit(paths, ending, &new_path);
if (r < 0)
return r;
r = create_edit_temp_file(new_path, new_path, &tmp_path);
if (r < 0)
return r;
*ret_new_path = TAKE_PTR(new_path);
*ret_tmp_path = TAKE_PTR(tmp_path);
return 0;
}
static int unit_file_create_copy(
const LookupPaths *paths,
const char *unit_name,
const char *fragment_path,
char **ret_new_path,
char **ret_tmp_path) {
_cleanup_free_ char *new_path = NULL, *tmp_path = NULL;
int r;
assert(fragment_path);
assert(unit_name);
assert(ret_new_path);
assert(ret_tmp_path);
r = get_file_to_edit(paths, unit_name, &new_path);
if (r < 0)
return r;
if (!path_equal(fragment_path, new_path) && access(new_path, F_OK) >= 0) {
char response;
r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path);
if (r < 0)
return r;
if (response != 'y')
return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
}
r = create_edit_temp_file(new_path, fragment_path, &tmp_path);
if (r < 0)
return r;
*ret_new_path = TAKE_PTR(new_path);
*ret_tmp_path = TAKE_PTR(tmp_path);
return 0;
}
static int run_editor(char **paths) {
int r;
assert(paths);
r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL);
if (r < 0)
return r;
if (r == 0) {
char **editor_args = NULL, **tmp_path, **original_path;
size_t n_editor_args = 0, i = 1, argc;
const char **args, *editor, *p;
argc = strv_length(paths)/2 + 1;
/* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL. If
* neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, we try to execute well known
* editors. */
editor = getenv("SYSTEMD_EDITOR");
if (!editor)
editor = getenv("EDITOR");
if (!editor)
editor = getenv("VISUAL");
if (!isempty(editor)) {
editor_args = strv_split(editor, WHITESPACE);
if (!editor_args) {
(void) log_oom();
_exit(EXIT_FAILURE);
}
n_editor_args = strv_length(editor_args);
argc += n_editor_args - 1;
}
args = newa(const char*, argc + 1);
if (n_editor_args > 0) {
args[0] = editor_args[0];
for (; i < n_editor_args; i++)
args[i] = editor_args[i];
}
STRV_FOREACH_PAIR(original_path, tmp_path, paths)
args[i++] = *tmp_path;
args[i] = NULL;
if (n_editor_args > 0)
execvp(args[0], (char* const*) args);
FOREACH_STRING(p, "editor", "nano", "vim", "vi") {
args[0] = p;
execvp(p, (char* const*) args);
/* We do not fail if the editor doesn't exist because we want to try each one of them
* before failing. */
if (errno != ENOENT) {
log_error_errno(errno, "Failed to execute %s: %m", editor);
_exit(EXIT_FAILURE);
}
}
log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL.");
_exit(EXIT_FAILURE);
}
return 0;
}
static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
_cleanup_(hashmap_freep) Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
char **name;
int r;
assert(names);
assert(paths);
r = lookup_paths_init(&lp, arg_scope, 0, arg_root);
if (r < 0)
return r;
STRV_FOREACH(name, names) {
_cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL, *tmp_name = NULL;
const char *unit_name;
r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &path, NULL);
if (r == -EKEYREJECTED) {
/* If loading of the unit failed server side complete, then the server won't tell us
* the unit file path. In that case, find the file client side. */
log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name);
r = unit_find_paths(bus, *name, &lp, true, &cached_id_map, &cached_name_map, &path, NULL);
}
if (r == -ERFKILL)
return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
if (r < 0)
return r;
if (r == 0) {
assert(!path);
if (!arg_force) {
log_info("Run 'systemctl edit%s --force --full %s' to create a new unit.",
arg_scope == UNIT_FILE_GLOBAL ? " --global" :
arg_scope == UNIT_FILE_USER ? " --user" : "",
*name);
return -ENOENT;
}
/* Create a new unit from scratch */
unit_name = *name;
r = unit_file_create_new(&lp, unit_name,
arg_full ? NULL : ".d/override.conf",
&new_path, &tmp_path);
} else {
assert(path);
unit_name = basename(path);
/* We follow unit aliases, but we need to propagate the instance */
if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) &&
unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
_cleanup_free_ char *instance = NULL;
r = unit_name_to_instance(*name, &instance);
if (r < 0)
return r;
r = unit_name_replace_instance(unit_name, instance, &tmp_name);
if (r < 0)
return r;
unit_name = tmp_name;
}
if (arg_full)
r = unit_file_create_copy(&lp, unit_name, path, &new_path, &tmp_path);
else
r = unit_file_create_new(&lp, unit_name, ".d/override.conf", &new_path, &tmp_path);
}
if (r < 0)
return r;
r = strv_push_pair(paths, new_path, tmp_path);
if (r < 0)
return log_oom();
new_path = tmp_path = NULL;
}
return 0;
}
int edit(int argc, char *argv[], void *userdata) {
_cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
_cleanup_strv_free_ char **paths = NULL;
char **original, **tmp;
sd_bus *bus;
int r;
if (!on_tty())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units if not on a tty.");
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
r = lookup_paths_init(&lp, arg_scope, 0, arg_root);
if (r < 0)
return log_error_errno(r, "Failed to determine unit paths: %m");
r = mac_selinux_init();
if (r < 0)
return r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
STRV_FOREACH(tmp, names) {
r = unit_is_masked(bus, &lp, *tmp);
if (r < 0)
return r;
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
}
r = find_paths_to_edit(bus, names, &paths);
if (r < 0)
return r;
if (strv_isempty(paths))
return -ENOENT;
r = run_editor(paths);
if (r < 0)
goto end;
STRV_FOREACH_PAIR(original, tmp, paths) {
/* If the temporary file is empty we ignore it. This allows the user to cancel the
* modification. */
if (null_or_empty_path(*tmp)) {
log_warning("Editing \"%s\" canceled: temporary file is empty.", *original);
continue;
}
r = rename(*tmp, *original);
if (r < 0) {
r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original);
goto end;
}
}
r = 0;
if (!arg_no_reload && !install_client_side())
r = daemon_reload(argc, argv, userdata);
end:
STRV_FOREACH_PAIR(original, tmp, paths) {
(void) unlink(*tmp);
/* Removing empty dropin dirs */
if (!arg_full) {
_cleanup_free_ char *dir;
dir = dirname_malloc(*original);
if (!dir)
return log_oom();
/* No need to check if the dir is empty, rmdir does nothing if it is not the case. */
(void) rmdir(dir);
}
}
return r;
}

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int cat(int argc, char *argv[], void *userdata);
int edit(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,284 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "locale-util.h"
#include "path-util.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-enable.h"
#include "systemctl-start-unit.h"
#include "systemctl-sysv-compat.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int normalize_filenames(char **names) {
char **u;
int r;
STRV_FOREACH(u, names)
if (!path_is_absolute(*u)) {
char* normalized_path;
if (!isempty(arg_root))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Non-absolute paths are not allowed when --root is used: %s",
*u);
if (!strchr(*u,'/'))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Link argument does contain at least one directory separator: %s",
*u);
r = path_make_absolute_cwd(*u, &normalized_path);
if (r < 0)
return r;
free_and_replace(*u, normalized_path);
}
return 0;
}
static int normalize_names(char **names, bool warn_if_path) {
char **u;
bool was_path = false;
STRV_FOREACH(u, names) {
int r;
if (!is_path(*u))
continue;
r = free_and_strdup(u, basename(*u));
if (r < 0)
return log_error_errno(r, "Failed to normalize unit file path: %m");
was_path = true;
}
if (warn_if_path && was_path)
log_warning("Warning: Can't execute disable on the unit file path. Proceeding with the unit name.");
return 0;
}
int enable_unit(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
const char *verb = argv[0];
UnitFileChange *changes = NULL;
size_t n_changes = 0;
int carries_install_info = -1;
bool ignore_carries_install_info = arg_quiet;
int r;
if (!argv[1])
return 0;
r = mangle_names("to enable", strv_skip(argv, 1), &names);
if (r < 0)
return r;
r = enable_sysv_units(verb, names);
if (r < 0)
return r;
/* If the operation was fully executed by the SysV compat, let's finish early */
if (strv_isempty(names)) {
if (arg_no_reload || install_client_side())
return 0;
return daemon_reload(argc, argv, userdata);
}
if (streq(verb, "disable")) {
r = normalize_names(names, true);
if (r < 0)
return r;
}
if (streq(verb, "link")) {
r = normalize_filenames(names);
if (r < 0)
return r;
}
if (install_client_side()) {
UnitFileFlags flags;
flags = unit_file_flags_from_args();
if (streq(verb, "enable")) {
r = unit_file_enable(arg_scope, flags, arg_root, names, &changes, &n_changes);
carries_install_info = r;
} else if (streq(verb, "disable"))
r = unit_file_disable(arg_scope, flags, arg_root, names, &changes, &n_changes);
else if (streq(verb, "reenable")) {
r = unit_file_reenable(arg_scope, flags, arg_root, names, &changes, &n_changes);
carries_install_info = r;
} else if (streq(verb, "link"))
r = unit_file_link(arg_scope, flags, arg_root, names, &changes, &n_changes);
else if (streq(verb, "preset")) {
r = unit_file_preset(arg_scope, flags, arg_root, names, arg_preset_mode, &changes, &n_changes);
} else if (streq(verb, "mask"))
r = unit_file_mask(arg_scope, flags, arg_root, names, &changes, &n_changes);
else if (streq(verb, "unmask"))
r = unit_file_unmask(arg_scope, flags, arg_root, names, &changes, &n_changes);
else if (streq(verb, "revert"))
r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes);
else
assert_not_reached("Unknown verb");
unit_file_dump_changes(r, verb, changes, n_changes, arg_quiet);
if (r < 0)
goto finish;
r = 0;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
bool expect_carries_install_info = false;
bool send_runtime = true, send_force = true, send_preset_mode = false;
const char *method;
sd_bus *bus;
if (STR_IN_SET(verb, "mask", "unmask")) {
char **name;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
r = lookup_paths_init(&lp, arg_scope, 0, arg_root);
if (r < 0)
return r;
STRV_FOREACH(name, names) {
r = unit_exists(&lp, *name);
if (r < 0)
return r;
if (r == 0)
log_notice("Unit %s does not exist, proceeding anyway.", *name);
}
}
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
if (streq(verb, "enable")) {
method = "EnableUnitFiles";
expect_carries_install_info = true;
} else if (streq(verb, "disable")) {
method = "DisableUnitFiles";
send_force = false;
} else if (streq(verb, "reenable")) {
method = "ReenableUnitFiles";
expect_carries_install_info = true;
} else if (streq(verb, "link"))
method = "LinkUnitFiles";
else if (streq(verb, "preset")) {
if (arg_preset_mode != UNIT_FILE_PRESET_FULL) {
method = "PresetUnitFilesWithMode";
send_preset_mode = true;
} else
method = "PresetUnitFiles";
expect_carries_install_info = true;
ignore_carries_install_info = true;
} else if (streq(verb, "mask"))
method = "MaskUnitFiles";
else if (streq(verb, "unmask")) {
method = "UnmaskUnitFiles";
send_force = false;
} else if (streq(verb, "revert")) {
method = "RevertUnitFiles";
send_runtime = send_force = false;
} else
assert_not_reached("Unknown verb");
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, names);
if (r < 0)
return bus_log_create_error(r);
if (send_preset_mode) {
r = sd_bus_message_append(m, "s", unit_file_preset_mode_to_string(arg_preset_mode));
if (r < 0)
return bus_log_create_error(r);
}
if (send_runtime) {
r = sd_bus_message_append(m, "b", arg_runtime);
if (r < 0)
return bus_log_create_error(r);
}
if (send_force) {
r = sd_bus_message_append(m, "b", arg_force);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to %s unit: %s", verb, bus_error_message(&error, r));
if (expect_carries_install_info) {
r = sd_bus_message_read(reply, "b", &carries_install_info);
if (r < 0)
return bus_log_parse_error(r);
}
r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
if (r < 0)
goto finish;
/* Try to reload if enabled */
if (!arg_no_reload)
r = daemon_reload(argc, argv, userdata);
else
r = 0;
}
if (carries_install_info == 0 && !ignore_carries_install_info)
log_notice("The unit files have no installation config (WantedBy=, RequiredBy=, Also=,\n"
"Alias= settings in the [Install] section, and DefaultInstance= for template\n"
"units). This means they are not meant to be enabled using systemctl.\n"
" \n" /* trick: the space is needed so that the line does not get stripped from output */
"Possible reasons for having this kind of units are:\n"
"%1$s A unit may be statically enabled by being symlinked from another unit's\n"
" .wants/ or .requires/ directory.\n"
"%1$s A unit's purpose may be to act as a helper for some other unit which has\n"
" a requirement dependency on it.\n"
"%1$s A unit may be started when needed via activation (socket, path, timer,\n"
" D-Bus, udev, scripted systemctl call, ...).\n"
"%1$s In case of template units, the unit is meant to be enabled with some\n"
" instance name specified.",
special_glyph(SPECIAL_GLYPH_BULLET));
if (arg_now && STR_IN_SET(argv[0], "enable", "disable", "mask")) {
sd_bus *bus;
size_t len, i;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
goto finish;
len = strv_length(names);
{
char *new_args[len + 2];
new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop");
for (i = 0; i < len; i++)
new_args[i + 1] = basename(names[i]);
new_args[i + 1] = NULL;
r = start_unit(len + 1, new_args, userdata);
}
}
finish:
unit_file_changes_free(changes, n_changes);
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int enable_unit(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,62 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "pretty-print.h"
#include "syslog-util.h"
#include "systemctl-is-active.h"
#include "systemctl-sysv-compat.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int check_unit_generic(int code, const UnitActiveState good_states[], int nb_states, char **args) {
_cleanup_strv_free_ char **names = NULL;
UnitActiveState active_state;
sd_bus *bus;
char **name;
int r, i;
bool found = false;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = expand_unit_names(bus, args, NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
STRV_FOREACH(name, names) {
r = get_state_one_unit(bus, *name, &active_state);
if (r < 0)
return r;
if (!arg_quiet)
puts(unit_active_state_to_string(active_state));
for (i = 0; i < nb_states; ++i)
if (good_states[i] == active_state)
found = true;
}
/* use the given return code for the case that we won't find
* any unit which matches the list */
return found ? 0 : code;
}
int check_unit_active(int argc, char *argv[], void *userdata) {
static const UnitActiveState states[] = {
UNIT_ACTIVE,
UNIT_RELOADING,
};
/* According to LSB: 3, "program is not running" */
return check_unit_generic(EXIT_PROGRAM_NOT_RUNNING, states, ELEMENTSOF(states), strv_skip(argv, 1));
}
int check_unit_failed(int argc, char *argv[], void *userdata) {
static const UnitActiveState states[] = {
UNIT_FAILED,
};
return check_unit_generic(EXIT_PROGRAM_DEAD_AND_PID_EXISTS, states, ELEMENTSOF(states), strv_skip(argv, 1));
}

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int check_unit_active(int argc, char *argv[], void *userdata);
int check_unit_failed(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,138 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-is-enabled.h"
#include "systemctl-sysv-compat.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int show_installation_targets_client_side(const char *name) {
UnitFileChange *changes = NULL;
size_t n_changes = 0, i;
UnitFileFlags flags;
char **p;
int r;
p = STRV_MAKE(name);
flags = UNIT_FILE_DRY_RUN |
(arg_runtime ? UNIT_FILE_RUNTIME : 0);
r = unit_file_disable(UNIT_FILE_SYSTEM, flags, NULL, p, &changes, &n_changes);
if (r < 0)
return log_error_errno(r, "Failed to get file links for %s: %m", name);
for (i = 0; i < n_changes; i++)
if (changes[i].type == UNIT_FILE_UNLINK)
printf(" %s\n", changes[i].path);
return 0;
}
static int show_installation_targets(sd_bus *bus, const char *name) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *link;
int r;
r = bus_call_method(bus, bus_systemd_mgr, "GetUnitFileLinks", &error, &reply, "sb", name, arg_runtime);
if (r < 0)
return log_error_errno(r, "Failed to get unit file links for %s: %s", name, bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "s", &link)) > 0)
printf(" %s\n", link);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
return 0;
}
int unit_is_enabled(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
bool enabled;
char **name;
int r;
r = mangle_names("to check", strv_skip(argv, 1), &names);
if (r < 0)
return r;
r = enable_sysv_units(argv[0], names);
if (r < 0)
return r;
enabled = r > 0;
if (install_client_side()) {
STRV_FOREACH(name, names) {
UnitFileState state;
r = unit_file_get_state(arg_scope, arg_root, *name, &state);
if (r < 0)
return log_error_errno(r, "Failed to get unit file state for %s: %m", *name);
if (IN_SET(state,
UNIT_FILE_ENABLED,
UNIT_FILE_ENABLED_RUNTIME,
UNIT_FILE_STATIC,
UNIT_FILE_ALIAS,
UNIT_FILE_INDIRECT,
UNIT_FILE_GENERATED))
enabled = true;
if (!arg_quiet) {
puts(unit_file_state_to_string(state));
if (arg_full) {
r = show_installation_targets_client_side(*name);
if (r < 0)
return r;
}
}
}
r = 0;
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *s;
r = bus_call_method(bus, bus_systemd_mgr, "GetUnitFileState", &error, &reply, "s", *name);
if (r < 0)
return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &s);
if (r < 0)
return bus_log_parse_error(r);
if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect", "generated"))
enabled = true;
if (!arg_quiet) {
puts(s);
if (arg_full) {
r = show_installation_targets(bus, *name);
if (r < 0)
return r;
}
}
}
}
return enabled ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int unit_is_enabled(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,84 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-event.h"
#include "sd-daemon.h"
#include "systemctl-util.h"
#include "systemctl-is-system-running.h"
#include "virt.h"
#include "systemctl.h"
#include "bus-util.h"
#include "bus-locator.h"
#include "bus-error.h"
static int match_startup_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
char **state = userdata;
int r;
assert(state);
r = bus_get_property_string(sd_bus_message_get_bus(m), bus_systemd_mgr, "SystemState", NULL, state);
sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), r);
return 0;
}
int is_system_running(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_startup_finished = NULL;
_cleanup_(sd_event_unrefp) sd_event* event = NULL;
_cleanup_free_ char *state = NULL;
sd_bus *bus;
int r;
if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) {
if (!arg_quiet)
puts("offline");
return EXIT_FAILURE;
}
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (arg_wait) {
r = sd_event_default(&event);
if (r >= 0)
r = sd_bus_attach_event(bus, event, 0);
if (r >= 0)
r = bus_match_signal_async(
bus,
&slot_startup_finished,
bus_systemd_mgr,
"StartupFinished",
match_startup_finished, NULL, &state);
if (r < 0) {
log_warning_errno(r, "Failed to request match for StartupFinished: %m");
arg_wait = false;
}
}
r = bus_get_property_string(bus, bus_systemd_mgr, "SystemState", &error, &state);
if (r < 0) {
log_warning_errno(r, "Failed to query system state: %s", bus_error_message(&error, r));
if (!arg_quiet)
puts("unknown");
return EXIT_FAILURE;
}
if (arg_wait && STR_IN_SET(state, "initializing", "starting")) {
r = sd_event_loop(event);
if (r < 0) {
log_warning_errno(r, "Failed to get property from event loop: %m");
if (!arg_quiet)
puts("unknown");
return EXIT_FAILURE;
}
}
if (!arg_quiet)
puts(state);
return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int is_system_running(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,50 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-kill.h"
#include "systemctl-util.h"
#include "systemctl.h"
int kill_unit(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
char *kill_who = NULL, **name;
sd_bus *bus;
int r, q;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
if (!arg_kill_who)
arg_kill_who = "all";
/* --fail was specified */
if (streq(arg_job_mode, "fail"))
kill_who = strjoina(arg_kill_who, "-fail");
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
q = bus_call_method(
bus,
bus_systemd_mgr,
"KillUnit",
&error,
NULL,
"ssi", *name, kill_who ? kill_who : arg_kill_who, arg_signal);
if (q < 0) {
log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int kill_unit(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,174 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "locale-util.h"
#include "sort-util.h"
#include "special.h"
#include "systemctl-list-dependencies.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
static int list_dependencies_print(const char *name, int level, unsigned branches, bool last) {
_cleanup_free_ char *n = NULL;
size_t max_len = MAX(columns(),20u);
size_t len = 0;
int i;
if (!arg_plain) {
for (i = level - 1; i >= 0; i--) {
len += 2;
if (len > max_len - 3 && !arg_full) {
printf("%s...\n",max_len % 2 ? "" : " ");
return 0;
}
printf("%s", special_glyph(branches & (1 << i) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
}
len += 2;
if (len > max_len - 3 && !arg_full) {
printf("%s...\n",max_len % 2 ? "" : " ");
return 0;
}
printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
}
if (arg_full) {
printf("%s\n", name);
return 0;
}
n = ellipsize(name, max_len-len, 100);
if (!n)
return log_oom();
printf("%s\n", n);
return 0;
}
static int list_dependencies_compare(char * const *a, char * const *b) {
if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET)
return 1;
if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET)
return -1;
return strcasecmp(*a, *b);
}
static int list_dependencies_one(
sd_bus *bus,
const char *name,
int level,
char ***units,
unsigned branches) {
_cleanup_strv_free_ char **deps = NULL;
char **c;
int r;
assert(bus);
assert(name);
assert(units);
r = strv_extend(units, name);
if (r < 0)
return log_oom();
r = unit_get_dependencies(bus, name, &deps);
if (r < 0)
return r;
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
STRV_FOREACH(c, deps) {
if (strv_contains(*units, *c)) {
if (!arg_plain) {
printf(" ");
r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1);
if (r < 0)
return r;
}
continue;
}
if (arg_plain)
printf(" ");
else {
UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID;
const char *on;
(void) get_state_one_unit(bus, *c, &active_state);
switch (active_state) {
case UNIT_ACTIVE:
case UNIT_RELOADING:
case UNIT_ACTIVATING:
on = ansi_highlight_green();
break;
case UNIT_INACTIVE:
case UNIT_DEACTIVATING:
on = ansi_normal();
break;
default:
on = ansi_highlight_red();
break;
}
printf("%s%s%s ", on, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), ansi_normal());
}
r = list_dependencies_print(*c, level, branches, c[1] == NULL);
if (r < 0)
return r;
if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) {
r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (c[1] == NULL ? 0 : 1));
if (r < 0)
return r;
}
}
if (!arg_plain)
strv_remove(*units, name);
return 0;
}
int list_dependencies(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **units = NULL, **done = NULL;
char **u, **patterns;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
patterns = strv_skip(argv, 1);
if (strv_isempty(patterns)) {
units = strv_new(SPECIAL_DEFAULT_TARGET);
if (!units)
return log_oom();
} else {
r = expand_unit_names(bus, patterns, NULL, &units, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
}
(void) pager_open(arg_pager_flags);
STRV_FOREACH(u, units) {
if (u != units)
puts("");
puts(*u);
r = list_dependencies_one(bus, *u, 0, &done, 0);
if (r < 0)
return r;
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int list_dependencies(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,176 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "locale-util.h"
#include "systemctl-list-jobs.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
static int output_waiting_jobs(sd_bus *bus, Table *table, uint32_t id, const char *method, const char *prefix) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *name, *type;
uint32_t other_id;
int r;
assert(bus);
r = bus_call_method(bus, bus_systemd_mgr, method, &error, &reply, "u", id);
if (r < 0)
return log_debug_errno(r, "Failed to get waiting jobs for job %" PRIu32, id);
r = sd_bus_message_enter_container(reply, 'a', "(usssoo)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(usssoo)", &other_id, &name, &type, NULL, NULL, NULL)) > 0) {
_cleanup_free_ char *row = NULL;
int rc;
if (asprintf(&row, "%s %u (%s/%s)", prefix, other_id, name, type) < 0)
return log_oom();
rc = table_add_many(table,
TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_RIGHT),
TABLE_STRING, row,
TABLE_EMPTY,
TABLE_EMPTY);
if (rc < 0)
return table_log_add_error(r);
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
return 0;
}
struct job_info {
uint32_t id;
const char *name, *type, *state;
};
static int output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n, bool skipped) {
_cleanup_(table_unrefp) Table *table = NULL;
const struct job_info *j;
const char *on, *off;
int r;
assert(n == 0 || jobs);
if (n == 0) {
if (!arg_no_legend) {
on = ansi_highlight_green();
off = ansi_normal();
printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off);
}
return 0;
}
(void) pager_open(arg_pager_flags);
table = table_new("job", "unit", "type", "state");
if (!table)
return log_oom();
table_set_header(table, !arg_no_legend);
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
for (j = jobs; j < jobs + n; j++) {
if (streq(j->state, "running"))
on = ansi_highlight();
else
on = "";
r = table_add_many(table,
TABLE_UINT, j->id,
TABLE_STRING, j->name,
TABLE_SET_COLOR, on,
TABLE_STRING, j->type,
TABLE_STRING, j->state,
TABLE_SET_COLOR, on);
if (r < 0)
return table_log_add_error(r);
if (arg_jobs_after)
output_waiting_jobs(bus, table, j->id, "GetJobAfter", "\twaiting for job");
if (arg_jobs_before)
output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\tblocking job");
}
r = table_print(table, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print the table: %m");
if (!arg_no_legend) {
on = ansi_highlight();
off = ansi_normal();
printf("\n%s%u jobs listed%s.\n", on, n, off);
}
return 0;
}
static bool output_show_job(struct job_info *job, char **patterns) {
return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE);
}
int list_jobs(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ struct job_info *jobs = NULL;
const char *name, *type, *state;
bool skipped = false;
size_t size = 0;
unsigned c = 0;
sd_bus *bus;
uint32_t id;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "ListJobs", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, 'a', "(usssoo)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, NULL, NULL)) > 0) {
struct job_info job = { id, name, type, state };
if (!output_show_job(&job, strv_skip(argv, 1))) {
skipped = true;
continue;
}
if (!GREEDY_REALLOC(jobs, size, c + 1))
return log_oom();
jobs[c++] = job;
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
(void) pager_open(arg_pager_flags);
return output_jobs_list(bus, jobs, c, skipped);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int list_jobs(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,246 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <unistd.h>
#include "sd-login.h"
#include "bus-map-properties.h"
#include "hostname-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "sort-util.h"
#include "systemctl-list-machines.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
const struct bus_properties_map machine_info_property_map[] = {
{ "SystemState", "s", NULL, offsetof(struct machine_info, state) },
{ "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) },
{ "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) },
{ "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) },
{ "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) },
{}
};
void machine_info_clear(struct machine_info *info) {
assert(info);
free(info->name);
free(info->state);
free(info->control_group);
zero(*info);
}
static void free_machines_list(struct machine_info *machine_infos, int n) {
int i;
if (!machine_infos)
return;
for (i = 0; i < n; i++)
machine_info_clear(&machine_infos[i]);
free(machine_infos);
}
static int compare_machine_info(const struct machine_info *a, const struct machine_info *b) {
int r;
r = CMP(b->is_host, a->is_host);
if (r != 0)
return r;
return strcasecmp(a->name, b->name);
}
static int get_machine_properties(sd_bus *bus, struct machine_info *mi) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL;
int r;
assert(mi);
if (!bus) {
r = sd_bus_open_system_machine(&container, mi->name);
if (r < 0)
return r;
bus = container;
}
r = bus_map_all_properties(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
machine_info_property_map,
BUS_MAP_STRDUP,
NULL,
NULL,
mi);
if (r < 0)
return r;
return 0;
}
static bool output_show_machine(const char *name, char **patterns) {
return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE);
}
static int get_machine_list(
sd_bus *bus,
struct machine_info **_machine_infos,
char **patterns) {
struct machine_info *machine_infos = NULL;
_cleanup_strv_free_ char **m = NULL;
_cleanup_free_ char *hn = NULL;
size_t sz = 0;
char **i;
int c = 0, r;
hn = gethostname_malloc();
if (!hn)
return log_oom();
if (output_show_machine(hn, patterns)) {
if (!GREEDY_REALLOC0(machine_infos, sz, c+1))
return log_oom();
machine_infos[c].is_host = true;
machine_infos[c].name = TAKE_PTR(hn);
(void) get_machine_properties(bus, &machine_infos[c]);
c++;
}
r = sd_get_machine_names(&m);
if (r < 0)
return log_error_errno(r, "Failed to get machine list: %m");
STRV_FOREACH(i, m) {
_cleanup_free_ char *class = NULL;
if (!output_show_machine(*i, patterns))
continue;
sd_machine_get_class(*i, &class);
if (!streq_ptr(class, "container"))
continue;
if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) {
free_machines_list(machine_infos, c);
return log_oom();
}
machine_infos[c].is_host = false;
machine_infos[c].name = strdup(*i);
if (!machine_infos[c].name) {
free_machines_list(machine_infos, c);
return log_oom();
}
(void) get_machine_properties(NULL, &machine_infos[c]);
c++;
}
*_machine_infos = machine_infos;
return c;
}
static int output_machines_list(struct machine_info *machine_infos, unsigned n) {
_cleanup_(table_unrefp) Table *table = NULL;
struct machine_info *m;
bool state_missing = false;
int r;
assert(machine_infos || n == 0);
table = table_new("", "name", "state", "failed", "jobs");
if (!table)
return log_oom();
table_set_header(table, !arg_no_legend);
if (arg_plain) {
/* Hide the 'glyph' column when --plain is requested */
r = table_hide_column_from_display(table, 0);
if (r < 0)
return log_error_errno(r, "Failed to hide column: %m");
}
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
for (m = machine_infos; m < machine_infos + n; m++) {
_cleanup_free_ char *mname = NULL;
const char *on_state = "", *on_failed = "";
bool circle = false;
if (streq_ptr(m->state, "degraded")) {
on_state = ansi_highlight_red();
circle = true;
} else if (!streq_ptr(m->state, "running")) {
on_state = ansi_highlight_yellow();
circle = true;
}
if (m->n_failed_units > 0)
on_failed = ansi_highlight_red();
else
on_failed = "";
if (!m->state)
state_missing = true;
if (m->is_host)
mname = strjoin(strna(m->name), " (host)");
r = table_add_many(table,
TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
TABLE_SET_COLOR, on_state,
TABLE_STRING, m->is_host ? mname : strna(m->name),
TABLE_STRING, strna(m->state),
TABLE_SET_COLOR, on_state,
TABLE_UINT32, m->n_failed_units,
TABLE_SET_COLOR, on_failed,
TABLE_UINT32, m->n_jobs);
if (r < 0)
return table_log_add_error(r);
}
r = output_table(table);
if (r < 0)
return r;
if (!arg_no_legend) {
printf("\n");
if (state_missing && geteuid() != 0)
printf("Notice: some information only available to privileged users was not shown.\n");
printf("%u machines listed.\n", n);
}
return 0;
}
int list_machines(int argc, char *argv[], void *userdata) {
struct machine_info *machine_infos = NULL;
sd_bus *bus;
int r, rc;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1));
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
typesafe_qsort(machine_infos, r, compare_machine_info);
rc = output_machines_list(machine_infos, r);
free_machines_list(machine_infos, r);
return rc;
}

View File

@ -0,0 +1,24 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include "bus-map-properties.h"
#include "time-util.h"
int list_machines(int argc, char *argv[], void *userdata);
struct machine_info {
bool is_host;
char *name;
char *state;
char *control_group;
uint32_t n_failed_units;
uint32_t n_jobs;
usec_t timestamp;
};
void machine_info_clear(struct machine_info *info);
extern const struct bus_properties_map machine_info_property_map[];

View File

@ -0,0 +1,275 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "sort-util.h"
#include "systemctl-list-unit-files.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
static int compare_unit_file_list(const UnitFileList *a, const UnitFileList *b) {
const char *d1, *d2;
d1 = strrchr(a->path, '.');
d2 = strrchr(b->path, '.');
if (d1 && d2) {
int r;
r = strcasecmp(d1, d2);
if (r != 0)
return r;
}
return strcasecmp(basename(a->path), basename(b->path));
}
static bool output_show_unit_file(const UnitFileList *u, char **states, char **patterns) {
assert(u);
if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE))
return false;
if (!strv_isempty(arg_types)) {
const char *dot;
dot = strrchr(u->path, '.');
if (!dot)
return false;
if (!strv_find(arg_types, dot+1))
return false;
}
if (!strv_isempty(states) &&
!strv_find(states, unit_file_state_to_string(u->state)))
return false;
return true;
}
static int output_unit_file_list(const UnitFileList *units, unsigned c) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
int r;
table = table_new("unit file", "state", "vendor preset");
if (!table)
return log_oom();
table_set_header(table, !arg_no_legend);
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
for (const UnitFileList *u = units; u < units + c; u++) {
const char *on_underline = NULL, *on_unit_color = NULL, *id;
bool underline;
underline = u + 1 < units + c &&
!streq(unit_type_suffix(u->path), unit_type_suffix((u + 1)->path));
if (underline)
on_underline = ansi_underline();
if (IN_SET(u->state,
UNIT_FILE_MASKED,
UNIT_FILE_MASKED_RUNTIME,
UNIT_FILE_DISABLED,
UNIT_FILE_BAD))
on_unit_color = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
else if (IN_SET(u->state,
UNIT_FILE_ENABLED,
UNIT_FILE_ALIAS))
on_unit_color = underline ? ansi_highlight_green_underline() : ansi_highlight_green();
else
on_unit_color = on_underline;
id = basename(u->path);
r = table_add_many(table,
TABLE_STRING, id,
TABLE_SET_BOTH_COLORS, strempty(on_underline),
TABLE_STRING, unit_file_state_to_string(u->state),
TABLE_SET_BOTH_COLORS, strempty(on_unit_color));
if (r < 0)
return table_log_add_error(r);
if (show_preset_for_state(u->state)) {
const char *unit_preset_str, *on_preset_color;
r = unit_file_query_preset(arg_scope, arg_root, id, &presets);
if (r < 0) {
unit_preset_str = "n/a";
on_preset_color = underline ? on_underline : ansi_normal();
} else if (r == 0) {
unit_preset_str = "disabled";
on_preset_color = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
} else {
unit_preset_str = "enabled";
on_preset_color = underline ? ansi_highlight_green_underline() : ansi_highlight_green();
}
r = table_add_many(table,
TABLE_STRING, unit_preset_str,
TABLE_SET_BOTH_COLORS, strempty(on_preset_color));
} else
r = table_add_many(table,
TABLE_EMPTY,
TABLE_SET_BOTH_COLORS, underline ? ansi_grey_underline() : ansi_grey());
if (r < 0)
return table_log_add_error(r);
}
r = output_table(table);
if (r < 0)
return r;
if (!arg_no_legend)
printf("\n%u unit files listed.\n", c);
return 0;
}
int list_unit_files(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ UnitFileList *units = NULL;
UnitFileList *unit;
size_t size = 0;
unsigned c = 0;
const char *state;
char *path;
int r;
bool fallback = false;
if (install_client_side()) {
Hashmap *h;
UnitFileList *u;
unsigned n_units;
h = hashmap_new(&string_hash_ops);
if (!h)
return log_oom();
r = unit_file_get_list(arg_scope, arg_root, h, arg_states, strv_skip(argv, 1));
if (r < 0) {
unit_file_list_free(h);
return log_error_errno(r, "Failed to get unit file list: %m");
}
n_units = hashmap_size(h);
units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */
if (!units) {
unit_file_list_free(h);
return log_oom();
}
HASHMAP_FOREACH(u, h) {
if (!output_show_unit_file(u, NULL, NULL))
continue;
units[c++] = *u;
free(u);
}
assert(c <= n_units);
hashmap_free(h);
r = 0;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitFilesByPatterns");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, arg_states);
if (r < 0)
return bus_log_create_error(r);
if (arg_with_dependencies) {
_cleanup_strv_free_ char **names_with_deps = NULL;
r = append_unit_dependencies(bus, strv_skip(argv, 1), &names_with_deps);
if (r < 0)
return log_error_errno(r, "Failed to append unit dependencies: %m");
r = sd_bus_message_append_strv(m, names_with_deps);
if (r < 0)
return bus_log_create_error(r);
} else {
r = sd_bus_message_append_strv(m, strv_skip(argv, 1));
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
/* Fallback to legacy ListUnitFiles method */
fallback = true;
log_debug_errno(r, "Failed to list unit files: %s Falling back to ListUnitsFiles method.", bus_error_message(&error, r));
m = sd_bus_message_unref(m);
sd_bus_error_free(&error);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitFiles");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
}
if (r < 0)
return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(ss)", &path, &state)) > 0) {
if (!GREEDY_REALLOC(units, size, c + 1))
return log_oom();
units[c] = (struct UnitFileList) {
path,
unit_file_state_from_string(state)
};
if (output_show_unit_file(&units[c],
fallback ? arg_states : NULL,
fallback ? strv_skip(argv, 1) : NULL))
c++;
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
}
(void) pager_open(arg_pager_flags);
typesafe_qsort(units, c, compare_unit_file_list);
r = output_unit_file_list(units, c);
if (r < 0)
return r;
if (install_client_side())
for (unit = units; unit < units + c; unit++)
free(unit->path);
if (c == 0)
return -ENOENT;
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int list_unit_files(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,770 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-login.h"
#include "bus-error.h"
#include "format-table.h"
#include "locale-util.h"
#include "set.h"
#include "sort-util.h"
#include "systemctl-list-units.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
static void message_set_freep(Set **set) {
set_free_with_destructor(*set, sd_bus_message_unref);
}
static int get_unit_list_recursive(
sd_bus *bus,
char **patterns,
UnitInfo **ret_unit_infos,
Set **ret_replies,
char ***ret_machines) {
_cleanup_free_ UnitInfo *unit_infos = NULL;
_cleanup_(message_set_freep) Set *replies;
sd_bus_message *reply;
int c, r;
assert(bus);
assert(ret_replies);
assert(ret_unit_infos);
assert(ret_machines);
replies = set_new(NULL);
if (!replies)
return log_oom();
c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply);
if (c < 0)
return c;
r = set_put(replies, reply);
if (r < 0) {
sd_bus_message_unref(reply);
return log_oom();
}
if (arg_recursive) {
_cleanup_strv_free_ char **machines = NULL;
char **i;
r = sd_get_machine_names(&machines);
if (r < 0)
return log_error_errno(r, "Failed to get machine names: %m");
STRV_FOREACH(i, machines) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL;
int k;
r = sd_bus_open_system_machine(&container, *i);
if (r < 0) {
log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i);
continue;
}
k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply);
if (k < 0)
return k;
c = k;
r = set_put(replies, reply);
if (r < 0) {
sd_bus_message_unref(reply);
return log_oom();
}
}
*ret_machines = TAKE_PTR(machines);
} else
*ret_machines = NULL;
*ret_unit_infos = TAKE_PTR(unit_infos);
*ret_replies = TAKE_PTR(replies);
return c;
}
static int output_units_list(const UnitInfo *unit_infos, unsigned c) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
table = table_new("", "unit", "load", "active", "sub", "job", "description");
if (!table)
return log_oom();
table_set_header(table, !arg_no_legend);
if (arg_plain) {
/* Hide the 'glyph' column when --plain is requested */
r = table_hide_column_from_display(table, 0);
if (r < 0)
return log_error_errno(r, "Failed to hide column: %m");
}
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
int job_count = 0;
for (const UnitInfo *u = unit_infos; unit_infos && u < unit_infos + c; u++) {
_cleanup_free_ char *j = NULL;
const char *on_underline = "", *on_loaded = "", *on_active = "";
const char *on_circle = "", *id;
bool circle = false, underline = false;
if (u + 1 < unit_infos + c &&
!streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) {
on_underline = ansi_underline();
underline = true;
}
if (STR_IN_SET(u->load_state, "error", "not-found", "bad-setting", "masked") && !arg_plain) {
on_circle = underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
circle = true;
on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
} else if (streq(u->active_state, "failed") && !arg_plain) {
on_circle = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
circle = true;
on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
} else {
on_circle = on_underline;
on_active = on_underline;
on_loaded = on_underline;
}
if (u->machine) {
j = strjoin(u->machine, ":", u->id);
if (!j)
return log_oom();
id = j;
} else
id = u->id;
r = table_add_many(table,
TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
TABLE_SET_BOTH_COLORS, on_circle,
TABLE_STRING, id,
TABLE_SET_BOTH_COLORS, on_active,
TABLE_STRING, u->load_state,
TABLE_SET_BOTH_COLORS, on_loaded,
TABLE_STRING, u->active_state,
TABLE_SET_BOTH_COLORS, on_active,
TABLE_STRING, u->sub_state,
TABLE_SET_BOTH_COLORS, on_active,
TABLE_STRING, u->job_id ? u->job_type: "",
TABLE_SET_BOTH_COLORS, u->job_id ? on_underline : "",
TABLE_STRING, u->description,
TABLE_SET_BOTH_COLORS, on_underline);
if (r < 0)
return table_log_add_error(r);
if (u->job_id != 0)
job_count++;
}
if (job_count == 0) {
/* There's no data in the JOB column, so let's hide it */
r = table_hide_column_from_display(table, 5);
if (r < 0)
return log_error_errno(r, "Failed to hide column: %m");
}
r = output_table(table);
if (r < 0)
return r;
if (!arg_no_legend) {
const char *on, *off;
size_t records = table_get_rows(table) - 1;
if (records > 0) {
puts("\n"
"LOAD = Reflects whether the unit definition was properly loaded.\n"
"ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n"
"SUB = The low-level unit activation state, values depend on unit type.");
puts(job_count ? "JOB = Pending job for the unit.\n" : "");
on = ansi_highlight();
off = ansi_normal();
} else {
on = ansi_highlight_red();
off = ansi_normal();
}
if (arg_all || strv_contains(arg_states, "inactive"))
printf("%s%zu loaded units listed.%s\n"
"To show all installed unit files use 'systemctl list-unit-files'.\n",
on, records, off);
else if (!arg_states)
printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
"To show all installed unit files use 'systemctl list-unit-files'.\n",
on, records, off);
else
printf("%zu loaded units listed.\n", records);
}
return 0;
}
int list_units(int argc, char *argv[], void *userdata) {
_cleanup_free_ UnitInfo *unit_infos = NULL;
_cleanup_(message_set_freep) Set *replies = NULL;
_cleanup_strv_free_ char **machines = NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
if (arg_with_dependencies) {
_cleanup_strv_free_ char **names = NULL;
r = append_unit_dependencies(bus, strv_skip(argv, 1), &names);
if (r < 0)
return r;
r = get_unit_list_recursive(bus, names, &unit_infos, &replies, &machines);
if (r < 0)
return r;
} else {
r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
if (r < 0)
return r;
}
typesafe_qsort(unit_infos, r, unit_info_compare);
return output_units_list(unit_infos, r);
}
static int get_triggered_units(
sd_bus *bus,
const char* path,
char*** ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(bus);
assert(path);
assert(ret);
r = sd_bus_get_property_strv(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Unit",
"Triggers",
&error,
ret);
if (r < 0)
return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r));
return 0;
}
static int get_listening(
sd_bus *bus,
const char* unit_path,
char*** listening) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *type, *path;
int r, n = 0;
r = sd_bus_get_property(
bus,
"org.freedesktop.systemd1",
unit_path,
"org.freedesktop.systemd1.Socket",
"Listen",
&error,
&reply,
"a(ss)");
if (r < 0)
return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) {
r = strv_extend(listening, type);
if (r < 0)
return log_oom();
r = strv_extend(listening, path);
if (r < 0)
return log_oom();
n++;
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
return n;
}
struct socket_info {
const char *machine;
const char* id;
char* type;
char* path;
/* Note: triggered is a list here, although it almost certainly will always be one
* unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
char** triggered;
/* The strv above is shared. free is set only in the first one. */
bool own_triggered;
};
static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) {
int r;
assert(a);
assert(b);
r = strcasecmp_ptr(a->machine, b->machine);
if (r != 0)
return r;
r = strcmp(a->path, b->path);
if (r != 0)
return r;
return strcmp(a->type, b->type);
}
static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) {
_cleanup_(table_unrefp) Table *table = NULL;
struct socket_info *s;
const char *on, *off;
int r;
table = table_new("listen", "type", "unit", "activates");
if (!table)
return log_oom();
if (!arg_show_types) {
/* Hide the second (TYPE) column */
r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3, (size_t) -1);
if (r < 0)
return log_error_errno(r, "Failed to set columns to display: %m");
}
table_set_header(table, !arg_no_legend);
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
if (cs) {
for (s = socket_infos; s < socket_infos + cs; s++) {
_cleanup_free_ char *j = NULL;
const char *path;
if (s->machine) {
j = strjoin(s->machine, ":", s->path);
if (!j)
return log_oom();
path = j;
} else
path = s->path;
r = table_add_many(table,
TABLE_STRING, path,
TABLE_STRING, s->type,
TABLE_STRING, s->id);
if (r < 0)
return table_log_add_error(r);
if (strv_isempty(s->triggered))
r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
else if (strv_length(s->triggered) == 1)
r = table_add_cell(table, NULL, TABLE_STRING, s->triggered[0]);
else
/* This should never happen, currently our socket units can only trigger a
* single unit. But let's handle this anyway, who knows what the future
* brings? */
r = table_add_cell(table, NULL, TABLE_STRV, s->triggered);
if (r < 0)
return table_log_add_error(r);
}
on = ansi_highlight();
off = ansi_normal();
} else {
on = ansi_highlight_red();
off = ansi_normal();
}
r = output_table(table);
if (r < 0)
return r;
if (!arg_no_legend) {
printf("\n%s%u sockets listed.%s\n", on, cs, off);
if (!arg_all)
printf("Pass --all to see loaded but inactive sockets, too.\n");
}
return 0;
}
int list_sockets(int argc, char *argv[], void *userdata) {
_cleanup_(message_set_freep) Set *replies = NULL;
_cleanup_strv_free_ char **machines = NULL;
_cleanup_strv_free_ char **sockets_with_suffix = NULL;
_cleanup_free_ UnitInfo *unit_infos = NULL;
_cleanup_free_ struct socket_info *socket_infos = NULL;
const UnitInfo *u;
struct socket_info *s;
unsigned cs = 0;
size_t size = 0;
int r, n;
sd_bus *bus;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL);
if (r < 0)
return r;
if (argc == 1 || sockets_with_suffix) {
n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies, &machines);
if (n < 0)
return n;
for (u = unit_infos; u < unit_infos + n; u++) {
_cleanup_strv_free_ char **listening = NULL, **triggered = NULL;
int i, c;
if (!endswith(u->id, ".socket"))
continue;
r = get_triggered_units(bus, u->unit_path, &triggered);
if (r < 0)
goto cleanup;
c = get_listening(bus, u->unit_path, &listening);
if (c < 0) {
r = c;
goto cleanup;
}
if (!GREEDY_REALLOC(socket_infos, size, cs + c)) {
r = log_oom();
goto cleanup;
}
for (i = 0; i < c; i++)
socket_infos[cs + i] = (struct socket_info) {
.machine = u->machine,
.id = u->id,
.type = listening[i*2],
.path = listening[i*2 + 1],
.triggered = triggered,
.own_triggered = i==0,
};
/* from this point on we will cleanup those socket_infos */
cs += c;
free(listening);
listening = triggered = NULL; /* avoid cleanup */
}
typesafe_qsort(socket_infos, cs, socket_info_compare);
}
output_sockets_list(socket_infos, cs);
cleanup:
assert(cs == 0 || socket_infos);
for (s = socket_infos; s < socket_infos + cs; s++) {
free(s->type);
free(s->path);
if (s->own_triggered)
strv_free(s->triggered);
}
return r;
}
static int get_next_elapse(
sd_bus *bus,
const char *path,
dual_timestamp *next) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
dual_timestamp t;
int r;
assert(bus);
assert(path);
assert(next);
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Timer",
"NextElapseUSecMonotonic",
&error,
't',
&t.monotonic);
if (r < 0)
return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Timer",
"NextElapseUSecRealtime",
&error,
't',
&t.realtime);
if (r < 0)
return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
*next = t;
return 0;
}
static int get_last_trigger(
sd_bus *bus,
const char *path,
usec_t *last) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(bus);
assert(path);
assert(last);
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Timer",
"LastTriggerUSec",
&error,
't',
last);
if (r < 0)
return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r));
return 0;
}
struct timer_info {
const char* machine;
const char* id;
usec_t next_elapse;
usec_t last_trigger;
char** triggered;
};
static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) {
int r;
assert(a);
assert(b);
r = strcasecmp_ptr(a->machine, b->machine);
if (r != 0)
return r;
r = CMP(a->next_elapse, b->next_elapse);
if (r != 0)
return r;
return strcmp(a->id, b->id);
}
static int output_timers_list(struct timer_info *timer_infos, unsigned n) {
_cleanup_(table_unrefp) Table *table = NULL;
struct timer_info *t;
const char *on, *off;
int r;
assert(timer_infos || n == 0);
table = table_new("next", "left", "last", "passed", "unit", "activates");
if (!table)
return log_oom();
table_set_header(table, !arg_no_legend);
if (arg_full)
table_set_width(table, 0);
(void) table_set_empty_string(table, "-");
if (n > 0) {
for (t = timer_infos; t < timer_infos + n; t++) {
_cleanup_free_ char *j = NULL, *activates = NULL;
const char *unit;
if (t->machine) {
j = strjoin(t->machine, ":", t->id);
if (!j)
return log_oom();
unit = j;
} else
unit = t->id;
activates = strv_join(t->triggered, ", ");
if (!activates)
return log_oom();
r = table_add_many(table,
TABLE_TIMESTAMP, t->next_elapse,
TABLE_TIMESTAMP_RELATIVE, t->next_elapse,
TABLE_TIMESTAMP, t->last_trigger,
TABLE_TIMESTAMP_RELATIVE, t->last_trigger,
TABLE_STRING, unit,
TABLE_STRING, activates);
if (r < 0)
return table_log_add_error(r);
}
on = ansi_highlight();
off = ansi_normal();
} else {
on = ansi_highlight_red();
off = ansi_normal();
}
r = output_table(table);
if (r < 0)
return r;
if (!arg_no_legend) {
printf("\n%s%u timers listed.%s\n", on, n, off);
if (!arg_all)
printf("Pass --all to see loaded but inactive timers, too.\n");
}
return 0;
}
usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
usec_t next_elapse;
assert(nw);
assert(next);
if (timestamp_is_set(next->monotonic)) {
usec_t converted;
if (next->monotonic > nw->monotonic)
converted = nw->realtime + (next->monotonic - nw->monotonic);
else
converted = nw->realtime - (nw->monotonic - next->monotonic);
if (timestamp_is_set(next->realtime))
next_elapse = MIN(converted, next->realtime);
else
next_elapse = converted;
} else
next_elapse = next->realtime;
return next_elapse;
}
int list_timers(int argc, char *argv[], void *userdata) {
_cleanup_(message_set_freep) Set *replies = NULL;
_cleanup_strv_free_ char **machines = NULL;
_cleanup_strv_free_ char **timers_with_suffix = NULL;
_cleanup_free_ struct timer_info *timer_infos = NULL;
_cleanup_free_ UnitInfo *unit_infos = NULL;
struct timer_info *t;
const UnitInfo *u;
size_t size = 0;
int n, c = 0;
dual_timestamp nw;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL);
if (r < 0)
return r;
if (argc == 1 || timers_with_suffix) {
n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines);
if (n < 0)
return n;
dual_timestamp_get(&nw);
for (u = unit_infos; u < unit_infos + n; u++) {
_cleanup_strv_free_ char **triggered = NULL;
dual_timestamp next = DUAL_TIMESTAMP_NULL;
usec_t m, last = 0;
if (!endswith(u->id, ".timer"))
continue;
r = get_triggered_units(bus, u->unit_path, &triggered);
if (r < 0)
goto cleanup;
r = get_next_elapse(bus, u->unit_path, &next);
if (r < 0)
goto cleanup;
get_last_trigger(bus, u->unit_path, &last);
if (!GREEDY_REALLOC(timer_infos, size, c+1)) {
r = log_oom();
goto cleanup;
}
m = calc_next_elapse(&nw, &next);
timer_infos[c++] = (struct timer_info) {
.machine = u->machine,
.id = u->id,
.next_elapse = m,
.last_trigger = last,
.triggered = TAKE_PTR(triggered),
};
}
typesafe_qsort(timer_infos, c, timer_info_compare);
}
output_timers_list(timer_infos, c);
cleanup:
for (t = timer_infos; t < timer_infos + c; t++)
strv_free(t->triggered);
return r;
}

View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int list_units(int argc, char *argv[], void *userdata);
int list_sockets(int argc, char *argv[], void *userdata);
int list_timers(int argc, char *argv[], void *userdata);
usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next);

View File

@ -0,0 +1,144 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "pretty-print.h"
#include "syslog-util.h"
#include "systemctl-log-setting.h"
#include "systemctl-util.h"
#include "systemctl.h"
static void give_log_control1_hint(const char *name) {
_cleanup_free_ char *link = NULL;
if (arg_quiet)
return;
(void) terminal_urlify_man("org.freedesktop.LogControl1", "5", &link);
log_notice("Hint: the service must declare BusName= and implement the appropriate D-Bus interface.\n"
" See the %s for details.", link ?: "org.freedesktop.LogControl1(5) man page");
}
static int log_setting_internal(sd_bus *bus, const BusLocator* bloc, const char *verb, const char *value) {
assert(bus);
assert(STR_IN_SET(verb, "log-level", "log-target", "service-log-level", "service-log-target"));
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
bool level = endswith(verb, "log-level");
int r;
if (value) {
if (level) {
if (log_level_from_string(value) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"\"%s\" is not a valid log level.", value);
}
r = bus_set_property(bus, bloc,
level ? "LogLevel" : "LogTarget",
&error, "s", value);
if (r >= 0)
return 0;
log_error_errno(r, "Failed to set log %s of %s to %s: %s",
level ? "level" : "target",
bloc->destination, value, bus_error_message(&error, r));
} else {
_cleanup_free_ char *t = NULL;
r = bus_get_property_string(bus, bloc,
level ? "LogLevel" : "LogTarget",
&error, &t);
if (r >= 0) {
puts(t);
return 0;
}
log_error_errno(r, "Failed to get log %s of %s: %s",
level ? "level" : "target",
bloc->destination, bus_error_message(&error, r));
}
if (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_METHOD,
SD_BUS_ERROR_UNKNOWN_OBJECT,
SD_BUS_ERROR_UNKNOWN_INTERFACE,
SD_BUS_ERROR_UNKNOWN_PROPERTY))
give_log_control1_hint(bloc->destination);
return r;
}
int log_setting(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r;
assert(argc >= 1 && argc <= 2);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
return log_setting_internal(bus, bus_systemd_mgr, argv[0], argv[1]);
}
static int service_name_to_dbus(sd_bus *bus, const char *name, char **ret_dbus_name) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *bus_name = NULL;
int r;
/* First, look for the BusName= property */
_cleanup_free_ char *dbus_path = unit_dbus_path_from_name(name);
if (!dbus_path)
return log_oom();
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Service",
"BusName",
&error,
&bus_name);
if (r < 0)
return log_error_errno(r, "Failed to obtain BusName= property of %s: %s",
name, bus_error_message(&error, r));
if (isempty(bus_name)) {
log_error("Unit %s doesn't declare BusName=.", name);
give_log_control1_hint(name);
return -ENOLINK;
}
*ret_dbus_name = TAKE_PTR(bus_name);
return 0;
}
int service_log_setting(int argc, char *argv[], void *userdata) {
sd_bus *bus;
_cleanup_free_ char *unit = NULL, *dbus_name = NULL;
int r;
assert(argc >= 2 && argc <= 3);
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = unit_name_mangle_with_suffix(argv[1], argv[0],
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".service", &unit);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = service_name_to_dbus(bus, unit, &dbus_name);
if (r < 0)
return r;
const BusLocator bloc = {
.destination = dbus_name,
.path = "/org/freedesktop/LogControl1",
.interface = "org.freedesktop.LogControl1",
};
return log_setting_internal(bus, &bloc, argv[0], argv[2]);
}

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int log_setting(int argc, char *argv[], void *userdata);
int service_log_setting(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,381 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <unistd.h>
#include "sd-login.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "process-util.h"
#include "systemctl-logind.h"
#include "systemctl-start-unit.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
#include "user-util.h"
int logind_set_wall_message(void) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
_cleanup_free_ char *m = NULL;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
m = strv_join(arg_wall, " ");
if (!m)
return log_oom();
log_debug("%s wall message \"%s\".", arg_dry_run ? "Would set" : "Setting", m);
if (arg_dry_run)
return 0;
r = bus_call_method(bus, bus_login_mgr, "SetWallMessage", &error, NULL, "sb", m, !arg_no_wall);
if (r < 0)
return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r));
#endif
return 0;
}
/* Ask systemd-logind, which might grant access to unprivileged users through polkit */
int logind_reboot(enum action a) {
#if ENABLE_LOGIND
static const struct {
const char *method;
const char *description;
} actions[_ACTION_MAX] = {
[ACTION_POWEROFF] = { "PowerOff", "power off system" },
[ACTION_REBOOT] = { "Reboot", "reboot system" },
[ACTION_HALT] = { "Halt", "halt system" },
[ACTION_SUSPEND] = { "Suspend", "suspend system" },
[ACTION_HIBERNATE] = { "Hibernate", "hibernate system" },
[ACTION_HYBRID_SLEEP] = { "HybridSleep", "put system into hybrid sleep" },
[ACTION_SUSPEND_THEN_HIBERNATE] = { "SuspendThenHibernate", "suspend system, hibernate later" },
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
if (a < 0 || a >= _ACTION_MAX || !actions[a].method)
return -EINVAL;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
(void) logind_set_wall_message();
log_debug("%s org.freedesktop.login1.Manager %s dbus call.", arg_dry_run ? "Would execute" : "Executing", actions[a].method);
if (arg_dry_run)
return 0;
r = bus_call_method(bus, bus_login_mgr, actions[a].method, &error, NULL, "b", arg_ask_password);
if (r < 0)
return log_error_errno(r, "Failed to %s via logind: %s", actions[a].description, bus_error_message(&error, r));
return 0;
#else
return -ENOSYS;
#endif
}
int logind_check_inhibitors(enum action a) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_strv_free_ char **sessions = NULL;
const char *what, *who, *why, *mode;
uint32_t uid, pid;
sd_bus *bus;
unsigned c = 0;
char **s;
int r;
if (arg_ignore_inhibitors || arg_force > 0)
return 0;
if (arg_when > 0)
return 0;
if (geteuid() == 0)
return 0;
if (!on_tty())
return 0;
if (arg_transport != BUS_TRANSPORT_LOCAL)
return 0;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_login_mgr, "ListInhibitors", NULL, &reply, NULL);
if (r < 0)
/* If logind is not around, then there are no inhibitors... */
return 0;
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) {
_cleanup_free_ char *comm = NULL, *user = NULL;
_cleanup_strv_free_ char **sv = NULL;
if (!streq(mode, "block"))
continue;
sv = strv_split(what, ":");
if (!sv)
return log_oom();
if (!pid_is_valid((pid_t) pid))
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid PID "PID_FMT".", (pid_t) pid);
if (!strv_contains(sv,
IN_SET(a,
ACTION_HALT,
ACTION_POWEROFF,
ACTION_REBOOT,
ACTION_KEXEC) ? "shutdown" : "sleep"))
continue;
get_process_comm(pid, &comm);
user = uid_to_name(uid);
log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".",
who, (pid_t) pid, strna(comm), strna(user), why);
c++;
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
/* Check for current sessions */
sd_get_sessions(&sessions);
STRV_FOREACH(s, sessions) {
_cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL;
if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid())
continue;
if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user"))
continue;
if (sd_session_get_type(*s, &type) < 0 || !STR_IN_SET(type, "x11", "wayland", "tty", "mir"))
continue;
sd_session_get_tty(*s, &tty);
sd_session_get_seat(*s, &seat);
sd_session_get_service(*s, &service);
user = uid_to_name(uid);
log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty);
c++;
}
if (c <= 0)
return 0;
log_error("Please retry operation after closing inhibitors and logging out other users.\n"
"Alternatively, ignore inhibitors and users with 'systemctl %s -i'.",
action_table[a].verb);
return -EPERM;
#else
return 0;
#endif
}
int prepare_firmware_setup(void) {
if (!arg_firmware_setup)
return 0;
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_login_mgr, "SetRebootToFirmwareSetup", &error, NULL, "b", true);
if (r < 0)
return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r));
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Booting into firmware setup not supported.");
#endif
}
int prepare_boot_loader_menu(void) {
if (arg_boot_loader_menu == USEC_INFINITY)
return 0;
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_login_mgr, "SetRebootToBootLoaderMenu", &error, NULL, "t", arg_boot_loader_menu);
if (r < 0)
return log_error_errno(r, "Cannot indicate to boot loader to enter boot loader entry menu: %s", bus_error_message(&error, r));
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Booting into boot loader menu not supported.");
#endif
}
int prepare_boot_loader_entry(void) {
if (!arg_boot_loader_entry)
return 0;
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_login_mgr, "SetRebootToBootLoaderEntry", &error, NULL, "s", arg_boot_loader_entry);
if (r < 0)
return log_error_errno(r, "Cannot set boot into loader entry '%s': %s", arg_boot_loader_entry, bus_error_message(&error, r));
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Booting into boot loader entry not supported.");
#endif
}
int logind_schedule_shutdown(void) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
char date[FORMAT_TIMESTAMP_MAX];
const char *action;
const char *log_action;
sd_bus *bus;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
switch (arg_action) {
case ACTION_HALT:
action = "halt";
log_action = "Shutdown";
break;
case ACTION_POWEROFF:
action = "poweroff";
log_action = "Shutdown";
break;
case ACTION_KEXEC:
action = "kexec";
log_action = "Reboot via kexec";
break;
case ACTION_EXIT:
action = "exit";
log_action = "Shutdown";
break;
case ACTION_REBOOT:
default:
action = "reboot";
log_action = "Reboot";
break;
}
if (arg_dry_run)
action = strjoina("dry-", action);
(void) logind_set_wall_message();
r = bus_call_method(bus, bus_login_mgr, "ScheduleShutdown", &error, NULL, "st", action, arg_when);
if (r < 0)
return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r));
if (!arg_quiet)
log_info("%s scheduled for %s, use 'shutdown -c' to cancel.", log_action, format_timestamp_style(date, sizeof(date), arg_when, arg_timestamp_style));
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Cannot schedule shutdown without logind support, proceeding with immediate shutdown.");
#endif
}
int logind_cancel_shutdown(void) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
(void) logind_set_wall_message();
r = bus_call_method(bus, bus_login_mgr, "CancelScheduledShutdown", &error, NULL, NULL);
if (r < 0)
return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r));
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Not compiled with logind support, cannot cancel scheduled shutdowns.");
#endif
}
int help_boot_loader_entry(void) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_strv_free_ char **l = NULL;
sd_bus *bus;
char **i;
int r;
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
r = bus_get_property_strv(bus, bus_login_mgr, "BootLoaderEntries", &error, &l);
if (r < 0)
return log_error_errno(r, "Failed to enumerate boot loader entries: %s", bus_error_message(&error, r));
if (strv_isempty(l))
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "No boot loader entries discovered.");
STRV_FOREACH(i, l)
puts(*i);
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
"Not compiled with logind support, cannot display boot loader entries.");
#endif
}

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "systemctl.h"
int logind_set_wall_message(void);
int logind_reboot(enum action a);
int logind_check_inhibitors(enum action a);
int prepare_firmware_setup(void);
int prepare_boot_loader_menu(void);
int prepare_boot_loader_entry(void);
int logind_schedule_shutdown(void);
int logind_cancel_shutdown(void);
int help_boot_loader_entry(void);

View File

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-preset-all.h"
#include "systemctl-util.h"
#include "systemctl.h"
int preset_all(int argc, char *argv[], void *userdata) {
UnitFileChange *changes = NULL;
size_t n_changes = 0;
int r;
if (install_client_side()) {
r = unit_file_preset_all(arg_scope, unit_file_flags_from_args(), arg_root, arg_preset_mode, &changes, &n_changes);
unit_file_dump_changes(r, "preset", changes, n_changes, arg_quiet);
if (r > 0)
r = 0;
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus *bus;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = bus_call_method(
bus,
bus_systemd_mgr,
"PresetAllUnitFiles",
&error,
&reply,
"sbb",
unit_file_preset_mode_to_string(arg_preset_mode),
arg_runtime,
arg_force);
if (r < 0)
return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r));
r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
if (r < 0)
goto finish;
if (arg_no_reload) {
r = 0;
goto finish;
}
r = daemon_reload(argc, argv, userdata);
}
finish:
unit_file_changes_free(changes, n_changes);
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int preset_all(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,41 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-reset-failed.h"
#include "systemctl-trivial-method.h"
#include "systemctl-util.h"
#include "systemctl.h"
int reset_failed(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
sd_bus *bus;
char **name;
int r, q;
if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */
return trivial_method(argc, argv, userdata);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
q = bus_call_method(bus, bus_systemd_mgr, "ResetFailedUnit", &error, NULL, "s", *name);
if (q < 0) {
log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q));
if (r == 0)
r = q;
}
}
return r;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int reset_failed(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "parse-util.h"
#include "systemctl-service-watchdogs.h"
#include "systemctl-util.h"
#include "systemctl.h"
int service_watchdogs(int argc, char *argv[], void *userdata) {
sd_bus *bus;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int b, r;
assert(argv);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
if (argc == 1) {
/* get ServiceWatchdogs */
r = bus_get_property_trivial(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, 'b', &b);
if (r < 0)
return log_error_errno(r, "Failed to get service-watchdog state: %s", bus_error_message(&error, r));
printf("%s\n", yes_no(!!b));
} else {
/* set ServiceWatchdogs */
assert(argc == 2);
b = parse_boolean(argv[1]);
if (b < 0)
return log_error_errno(b, "Failed to parse service-watchdogs argument: %m");
r = bus_set_property(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, "b", b);
if (r < 0)
return log_error_errno(r, "Failed to set service-watchdog state: %s", bus_error_message(&error, r));
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int service_watchdogs(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,158 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "proc-cmdline.h"
#include "systemctl-daemon-reload.h"
#include "systemctl-set-default.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
char **ret = data;
if (streq(key, "systemd.unit")) {
if (proc_cmdline_value_missing(key, value))
return 0;
if (!unit_name_is_valid(value, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
log_warning("Unit name specified on %s= is not valid, ignoring: %s", key, value);
return 0;
}
return free_and_strdup_warn(ret, key);
} else if (!value) {
if (runlevel_to_target(key))
return free_and_strdup_warn(ret, key);
}
return 0;
}
static void emit_cmdline_warning(void) {
if (arg_quiet || arg_root)
/* don't bother checking the commandline if we're operating on a container */
return;
_cleanup_free_ char *override = NULL;
int r;
r = proc_cmdline_parse(parse_proc_cmdline_item, &override, 0);
if (r < 0)
log_debug_errno(r, "Failed to parse kernel command line, ignoring: %m");
if (override)
log_notice("Note: found \"%s\" on the kernel commandline, which overrides the default unit.",
override);
}
static int determine_default(char **ret_name) {
int r;
if (install_client_side()) {
r = unit_file_get_default(arg_scope, arg_root, ret_name);
if (r < 0)
return log_error_errno(r, "Failed to get default target: %m");
return 0;
} else {
sd_bus *bus;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *name;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "GetDefaultTarget", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &name);
if (r < 0)
return bus_log_parse_error(r);
return free_and_strdup_warn(ret_name, name);
}
}
int get_default(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *name = NULL;
int r;
r = determine_default(&name);
if (r < 0)
return r;
printf("%s\n", name);
emit_cmdline_warning();
return 0;
}
int set_default(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *unit = NULL;
UnitFileChange *changes = NULL;
size_t n_changes = 0;
int r;
assert(argc >= 2);
assert(argv);
r = unit_name_mangle_with_suffix(argv[1], "set-default",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".target", &unit);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
if (install_client_side()) {
r = unit_file_set_default(arg_scope, UNIT_FILE_FORCE, arg_root, unit, &changes, &n_changes);
unit_file_dump_changes(r, "set default", changes, n_changes, arg_quiet);
if (r > 0)
r = 0;
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus *bus;
polkit_agent_open_maybe();
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "SetDefaultTarget", &error, &reply, "sb", unit, 1);
if (r < 0)
return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r));
r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
if (r < 0)
goto finish;
/* Try to reload if enabled */
if (!arg_no_reload)
r = daemon_reload(argc, argv, userdata);
else
r = 0;
}
emit_cmdline_warning();
if (!arg_quiet) {
_cleanup_free_ char *final = NULL;
r = determine_default(&final);
if (r < 0)
return r;
if (!streq(final, unit))
log_notice("Note: \"%s\" is the default unit (possibly a runtime override).", final);
}
finish:
unit_file_changes_free(changes, n_changes);
return r;
}

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int get_default(int argc, char *argv[], void *userdata);
int set_default(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "env-util.h"
#include "escape.h"
#include "systemctl-set-environment.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int print_variable(const char *s) {
const char *sep;
_cleanup_free_ char *esc = NULL;
sep = strchr(s, '=');
if (!sep)
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Invalid environment block");
esc = shell_maybe_quote(sep + 1, ESCAPE_POSIX);
if (!esc)
return log_oom();
printf("%.*s=%s\n", (int)(sep-s), s, esc);
return 0;
}
int show_environment(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *text;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
r = bus_get_property(bus, bus_systemd_mgr, "Environment", &error, &reply, "as");
if (r < 0)
return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) {
r = print_variable(text);
if (r < 0)
return r;
}
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
return 0;
}
int set_environment(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *method;
sd_bus *bus;
int r;
assert(argc > 1);
assert(argv);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
method = streq(argv[0], "set-environment")
? "SetEnvironment"
: "UnsetEnvironment";
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, strv_skip(argv, 1));
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r));
return 0;
}
int import_environment(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetEnvironment");
if (r < 0)
return bus_log_create_error(r);
if (argc < 2)
r = sd_bus_message_append_strv(m, environ);
else {
char **a, **b;
r = sd_bus_message_open_container(m, 'a', "s");
if (r < 0)
return bus_log_create_error(r);
STRV_FOREACH(a, strv_skip(argv, 1)) {
if (!env_name_is_valid(*a))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid environment variable name: %s", *a);
STRV_FOREACH(b, environ) {
const char *eq;
eq = startswith(*b, *a);
if (eq && *eq == '=') {
r = sd_bus_message_append(m, "s", *b);
if (r < 0)
return bus_log_create_error(r);
break;
}
}
}
r = sd_bus_message_close_container(m);
}
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r));
return 0;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int show_environment(int argc, char *argv[], void *userdata);
int set_environment(int argc, char *argv[], void *userdata);
int import_environment(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-set-property.h"
#include "systemctl-util.h"
#include "systemctl.h"
int set_property(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *n = NULL;
UnitType t;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties");
if (r < 0)
return bus_log_create_error(r);
r = unit_name_mangle(argv[1], arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, &n);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
t = unit_name_to_type(n);
if (t < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid unit type: %s", n);
r = sd_bus_message_append(m, "sb", n, arg_runtime);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)");
if (r < 0)
return bus_log_create_error(r);
r = bus_append_unit_property_assignment_many(m, t, strv_skip(argv, 2));
if (r < 0)
return r;
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r));
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int set_property(int argc, char *argv[], void *userdata);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int show(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,248 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bootspec.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "efivars.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "reboot-util.h"
#include "systemctl-logind.h"
#include "systemctl-start-special.h"
#include "systemctl-start-unit.h"
#include "systemctl-trivial-method.h"
#include "systemctl-util.h"
#include "systemctl.h"
static int load_kexec_kernel(void) {
_cleanup_(boot_config_free) BootConfig config = {};
_cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL;
const BootEntry *e;
pid_t pid;
int r;
if (kexec_loaded()) {
log_debug("Kexec kernel already loaded.");
return 0;
}
if (access(KEXEC, X_OK) < 0)
return log_error_errno(errno, KEXEC" is not available: %m");
r = boot_entries_load_config_auto(NULL, NULL, &config);
if (r == -ENOKEY)
/* The call doesn't log about ENOKEY, let's do so here. */
return log_error_errno(r,
"No kexec kernel loaded and autodetection failed.\n%s",
is_efi_boot()
? "Cannot automatically load kernel: ESP partition mount point not found."
: "Automatic loading works only on systems booted with EFI.");
if (r < 0)
return r;
e = boot_config_default_entry(&config);
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No boot loader entry suitable as default, refusing to guess.");
log_debug("Found default boot loader entry in file \"%s\"", e->path);
if (!e->kernel)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry does not refer to Linux kernel, which is not supported currently.");
if (strv_length(e->initrd) > 1)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry specifies multiple initrds, which is not supported currently.");
kernel = path_join(e->root, e->kernel);
if (!kernel)
return log_oom();
if (!strv_isempty(e->initrd)) {
initrd = path_join(e->root, e->initrd[0]);
if (!initrd)
return log_oom();
}
options = strv_join(e->options, " ");
if (!options)
return log_oom();
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
"%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s",
arg_dry_run ? "Would run" : "Running",
kernel,
options,
initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : "");
if (arg_dry_run)
return 0;
r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
const char* const args[] = {
KEXEC,
"--load", kernel,
"--append", options,
initrd ? "--initrd" : NULL, initrd,
NULL
};
/* Child */
execv(args[0], (char * const *) args);
_exit(EXIT_FAILURE);
}
return 0;
}
static int set_exit_code(uint8_t code) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "SetExitCode", &error, NULL, "y", code);
if (r < 0)
return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r));
return 0;
}
int start_special(int argc, char *argv[], void *userdata) {
bool termination_action; /* An action that terminates the manager, can be performed also by
* signal. */
enum action a;
int r;
assert(argv);
a = verb_to_action(argv[0]);
r = logind_check_inhibitors(a);
if (r < 0)
return r;
if (arg_force >= 2) {
r = must_be_root();
if (r < 0)
return r;
}
r = prepare_firmware_setup();
if (r < 0)
return r;
r = prepare_boot_loader_menu();
if (r < 0)
return r;
r = prepare_boot_loader_entry();
if (r < 0)
return r;
if (a == ACTION_REBOOT) {
const char *arg = NULL;
if (argc > 1) {
if (arg_reboot_argument)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --reboot-argument= and positional argument passed to reboot command, refusing.");
log_notice("Positional argument to reboot command is deprecated, please use --reboot-argument= instead. Accepting anyway.");
arg = argv[1];
} else
arg = arg_reboot_argument;
if (arg) {
r = update_reboot_parameter_and_warn(arg, false);
if (r < 0)
return r;
}
} else if (a == ACTION_KEXEC) {
r = load_kexec_kernel();
if (r < 0 && arg_force >= 1)
log_notice("Failed to load kexec kernel, continuing without.");
else if (r < 0)
return r;
} else if (a == ACTION_EXIT && argc > 1) {
uint8_t code;
/* If the exit code is not given on the command line, don't reset it to zero: just keep it as
* it might have been set previously. */
r = safe_atou8(argv[1], &code);
if (r < 0)
return log_error_errno(r, "Invalid exit code.");
r = set_exit_code(code);
if (r < 0)
return r;
}
termination_action = IN_SET(a,
ACTION_HALT,
ACTION_POWEROFF,
ACTION_REBOOT);
if (termination_action && arg_force >= 2)
return halt_now(a);
if (arg_force >= 1 &&
(termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT)))
r = trivial_method(argc, argv, userdata);
else {
/* First try logind, to allow authentication with polkit */
if (IN_SET(a,
ACTION_POWEROFF,
ACTION_REBOOT,
ACTION_HALT,
ACTION_SUSPEND,
ACTION_HIBERNATE,
ACTION_HYBRID_SLEEP,
ACTION_SUSPEND_THEN_HIBERNATE)) {
r = logind_reboot(a);
if (r >= 0)
return r;
if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS))
/* Requested operation is not supported or already in progress */
return r;
/* On all other errors, try low-level operation. In order to minimize the difference
* between operation with and without logind, we explicitly enable non-blocking mode
* for this, as logind's shutdown operations are always non-blocking. */
arg_no_block = true;
} else if (IN_SET(a, ACTION_EXIT, ACTION_KEXEC))
/* Since exit/kexec are so close in behaviour to power-off/reboot, let's also make
* them asynchronous, in order to not confuse the user needlessly with unexpected
* behaviour. */
arg_no_block = true;
r = start_unit(argc, argv, userdata);
}
if (termination_action && arg_force < 2 &&
IN_SET(r, -ENOENT, -ETIMEDOUT))
log_notice("It is possible to perform action directly, see discussion of --force --force in man:systemctl(1).");
return r;
}
int start_system_special(int argc, char *argv[], void *userdata) {
/* Like start_special above, but raises an error when running in user mode */
if (arg_scope != UNIT_FILE_SYSTEM)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Bad action for %s mode.",
arg_scope == UNIT_FILE_GLOBAL ? "--global" : "--user");
return start_special(argc, argv, userdata);
}

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int start_special(int argc, char *argv[], void *userdata);
int start_system_special(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,368 @@
/* 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;
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "systemctl.h"
int start_unit(int argc, char *argv[], void *userdata);
struct action_metadata {
const char *target;
const char *verb;
const char *mode;
};
extern const struct action_metadata action_table[_ACTION_MAX];
enum action verb_to_action(const char *verb);

View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "signal-util.h"
#include "stat-util.h"
#include "systemctl-switch-root.h"
#include "systemctl-util.h"
#include "systemctl.h"
int switch_root(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *cmdline_init = NULL;
const char *root, *init;
sd_bus *bus;
int r;
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot switch root remotely.");
if (argc < 2 || argc > 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Wrong number of arguments.");
root = argv[1];
if (argc >= 3)
init = argv[2];
else {
r = proc_cmdline_get_key("init", 0, &cmdline_init);
if (r < 0)
log_debug_errno(r, "Failed to parse /proc/cmdline: %m");
init = cmdline_init;
}
init = empty_to_null(init);
if (init) {
const char *root_systemd_path = NULL, *root_init_path = NULL;
root_systemd_path = prefix_roota(root, "/" SYSTEMD_BINARY_PATH);
root_init_path = prefix_roota(root, init);
/* If the passed init is actually the same as the systemd binary, then let's suppress it. */
if (files_same(root_init_path, root_systemd_path, 0) > 0)
init = NULL;
}
/* Instruct PID1 to exclude us from its killing spree applied during the transition. Otherwise we
* would exit with a failure status even though the switch to the new root has succeed. */
assert(saved_argv);
assert(saved_argv[0]);
saved_argv[0][0] = '@';
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
/* If we are slow to exit after the root switch, the new systemd instance will send us a signal to
* terminate. Just ignore it and exit normally. This way the unit does not end up as failed. */
r = ignore_signals(SIGTERM, -1);
if (r < 0)
log_warning_errno(r, "Failed to change disposition of SIGTERM to ignore: %m");
log_debug("Switching root - root: %s; init: %s", root, strna(init));
r = bus_call_method(bus, bus_systemd_mgr, "SwitchRoot", &error, NULL, "ss", root, init);
if (r < 0) {
(void) default_signals(SIGTERM, -1);
return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r));
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int switch_root(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,271 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "env-util.h"
#include "fd-util.h"
#include "initreq.h"
#include "install.h"
#include "io-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "strv.h"
#include "systemctl-sysv-compat.h"
#include "systemctl.h"
int talk_initctl(char rl) {
#if HAVE_SYSV_COMPAT
struct init_request request;
_cleanup_close_ int fd = -1;
const char *p;
int r;
/* Try to switch to the specified SysV runlevel. Returns == 0 if the operation does not apply on this
* system, and > 0 on success. */
if (rl == 0)
return 0;
FOREACH_STRING(p, "/run/initctl", "/dev/initctl") {
fd = open(p, O_WRONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd >= 0 || errno != ENOENT)
break;
}
if (fd < 0) {
if (errno == ENOENT)
return 0;
return log_error_errno(errno, "Failed to open initctl fifo: %m");
}
request = (struct init_request) {
.magic = INIT_MAGIC,
.sleeptime = 0,
.cmd = INIT_CMD_RUNLVL,
.runlevel = rl,
};
r = loop_write(fd, &request, sizeof(request), false);
if (r < 0)
return log_error_errno(r, "Failed to write to %s: %m", p);
return 1;
#else
return -EOPNOTSUPP;
#endif
}
int parse_shutdown_time_spec(const char *t, usec_t *ret) {
assert(t);
assert(ret);
if (streq(t, "now"))
*ret = 0;
else if (!strchr(t, ':')) {
uint64_t u;
if (safe_atou64(t, &u) < 0)
return -EINVAL;
*ret = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u;
} else {
char *e = NULL;
long hour, minute;
struct tm tm = {};
time_t s;
usec_t n;
errno = 0;
hour = strtol(t, &e, 10);
if (errno > 0 || *e != ':' || hour < 0 || hour > 23)
return -EINVAL;
minute = strtol(e+1, &e, 10);
if (errno > 0 || *e != 0 || minute < 0 || minute > 59)
return -EINVAL;
n = now(CLOCK_REALTIME);
s = (time_t) (n / USEC_PER_SEC);
assert_se(localtime_r(&s, &tm));
tm.tm_hour = (int) hour;
tm.tm_min = (int) minute;
tm.tm_sec = 0;
s = mktime(&tm);
assert(s >= 0);
*ret = (usec_t) s * USEC_PER_SEC;
while (*ret <= n)
*ret += USEC_PER_DAY;
}
return 0;
}
int enable_sysv_units(const char *verb, char **args) {
int r = 0;
#if HAVE_SYSV_COMPAT
_cleanup_(lookup_paths_free) LookupPaths paths = {};
unsigned f = 0;
/* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */
if (arg_scope != UNIT_FILE_SYSTEM)
return 0;
if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0)
return 0;
if (!STR_IN_SET(verb,
"enable",
"disable",
"is-enabled"))
return 0;
r = lookup_paths_init(&paths, arg_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root);
if (r < 0)
return r;
r = 0;
while (args[f]) {
const char *argv[] = {
ROOTLIBEXECDIR "/systemd-sysv-install",
NULL, /* --root= */
NULL, /* verb */
NULL, /* service */
NULL,
};
_cleanup_free_ char *p = NULL, *q = NULL, *l = NULL, *v = NULL;
bool found_native = false, found_sysv;
const char *name;
unsigned c = 1;
pid_t pid;
int j;
name = args[f++];
if (!endswith(name, ".service"))
continue;
if (path_is_absolute(name))
continue;
j = unit_file_exists(arg_scope, &paths, name);
if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL))
return log_error_errno(j, "Failed to look up unit file state: %m");
found_native = j != 0;
/* If we have both a native unit and a SysV script, enable/disable them both (below); for
* is-enabled, prefer the native unit */
if (found_native && streq(verb, "is-enabled"))
continue;
p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name);
if (!p)
return log_oom();
p[strlen(p) - STRLEN(".service")] = 0;
found_sysv = access(p, F_OK) >= 0;
if (!found_sysv)
continue;
if (!arg_quiet) {
if (found_native)
log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]);
else
log_info("%s is not a native service, redirecting to systemd-sysv-install.", name);
}
if (!isempty(arg_root)) {
q = strjoin("--root=", arg_root);
if (!q)
return log_oom();
argv[c++] = q;
}
/* Let's copy the verb, since it's still pointing directly into the original argv[] array we
* got passed, but safe_fork() is likely going to rewrite that for the new child */
v = strdup(verb);
if (!v)
return log_oom();
argv[c++] = v;
argv[c++] = basename(p);
argv[c] = NULL;
l = strv_join((char**)argv, " ");
if (!l)
return log_oom();
if (!arg_quiet)
log_info("Executing: %s", l);
j = safe_fork("(sysv-install)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
if (j < 0)
return j;
if (j == 0) {
/* Child */
execv(argv[0], (char**) argv);
log_error_errno(errno, "Failed to execute %s: %m", argv[0]);
_exit(EXIT_FAILURE);
}
j = wait_for_terminate_and_check("sysv-install", pid, WAIT_LOG_ABNORMAL);
if (j < 0)
return j;
if (streq(verb, "is-enabled")) {
if (j == EXIT_SUCCESS) {
if (!arg_quiet)
puts("enabled");
r = 1;
} else {
if (!arg_quiet)
puts("disabled");
}
} else if (j != EXIT_SUCCESS)
return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */
if (found_native)
continue;
/* Remove this entry, so that we don't try enabling it as native unit */
assert(f > 0);
f--;
assert(args[f] == name);
strv_remove(args + f, name);
}
#endif
return r;
}
int action_to_runlevel(void) {
#if HAVE_SYSV_COMPAT
static const char table[_ACTION_MAX] = {
[ACTION_HALT] = '0',
[ACTION_POWEROFF] = '0',
[ACTION_REBOOT] = '6',
[ACTION_RUNLEVEL2] = '2',
[ACTION_RUNLEVEL3] = '3',
[ACTION_RUNLEVEL4] = '4',
[ACTION_RUNLEVEL5] = '5',
[ACTION_RESCUE] = '1'
};
assert(arg_action >= 0 && arg_action < _ACTION_MAX);
return table[arg_action];
#else
return -EOPNOTSUPP;
#endif
}

View File

@ -3,9 +3,7 @@
#include "time-util.h"
#if HAVE_SYSV_COMPAT
int talk_initctl(char runlevel);
#endif
int parse_shutdown_time_spec(const char *t, usec_t *ret);
@ -31,3 +29,7 @@ enum {
EXIT_PROGRAM_NOT_RUNNING = 3,
EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN = 4,
};
int enable_sysv_units(const char *verb, char **args);
int action_to_runlevel(void) _pure_;

View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-trivial-method.h"
#include "systemctl-util.h"
#include "systemctl.h"
/* A generic implementation for cases we just need to invoke a simple method call on the Manager object. */
int trivial_method(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *method;
sd_bus *bus;
int r;
if (arg_dry_run)
return 0;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
method =
streq(argv[0], "clear-jobs") ||
streq(argv[0], "cancel") ? "ClearJobs" :
streq(argv[0], "reset-failed") ? "ResetFailed" :
streq(argv[0], "halt") ? "Halt" :
streq(argv[0], "reboot") ? "Reboot" :
streq(argv[0], "kexec") ? "KExec" :
streq(argv[0], "exit") ? "Exit" :
/* poweroff */ "PowerOff";
r = bus_call_method(bus, bus_systemd_mgr, method, &error, NULL, NULL);
if (r < 0 && arg_action == ACTION_SYSTEMCTL)
return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r));
/* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support
* fallbacks to the old ways of doing things, hence don't log any error in that case here. */
return r < 0 ? r : 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int trivial_method(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,936 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <sys/reboot.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-daemon.h"
#include "bus-common-errors.h"
#include "bus-locator.h"
#include "bus-map-properties.h"
#include "bus-unit-util.h"
#include "dropin.h"
#include "env-util.h"
#include "exit-status.h"
#include "fs-util.h"
#include "glob-util.h"
#include "macro.h"
#include "path-util.h"
#include "reboot-util.h"
#include "set.h"
#include "spawn-ask-password-agent.h"
#include "spawn-polkit-agent.h"
#include "stat-util.h"
#include "systemctl-util.h"
#include "systemctl.h"
#include "terminal-util.h"
#include "verbs.h"
static sd_bus *buses[_BUS_FOCUS_MAX] = {};
int acquire_bus(BusFocus focus, sd_bus **ret) {
int r;
assert(focus < _BUS_FOCUS_MAX);
assert(ret);
/* We only go directly to the manager, if we are using a local transport */
if (arg_transport != BUS_TRANSPORT_LOCAL)
focus = BUS_FULL;
if (getenv_bool("SYSTEMCTL_FORCE_BUS") > 0)
focus = BUS_FULL;
if (!buses[focus]) {
bool user;
user = arg_scope != UNIT_FILE_SYSTEM;
if (focus == BUS_MANAGER)
r = bus_connect_transport_systemd(arg_transport, arg_host, user, &buses[focus]);
else
r = bus_connect_transport(arg_transport, arg_host, user, &buses[focus]);
if (r < 0)
return log_error_errno(r, "Failed to connect to bus: %m");
(void) sd_bus_set_allow_interactive_authorization(buses[focus], arg_ask_password);
}
*ret = buses[focus];
return 0;
}
void release_busses(void) {
BusFocus w;
for (w = 0; w < _BUS_FOCUS_MAX; w++)
buses[w] = sd_bus_flush_close_unref(buses[w]);
}
void ask_password_agent_open_maybe(void) {
/* Open the password agent as a child process if necessary */
if (arg_dry_run)
return;
if (arg_scope != UNIT_FILE_SYSTEM)
return;
ask_password_agent_open_if_enabled(arg_transport, arg_ask_password);
}
void polkit_agent_open_maybe(void) {
/* Open the polkit agent as a child process if necessary */
if (arg_scope != UNIT_FILE_SYSTEM)
return;
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
}
int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) {
assert(error);
if (!sd_bus_error_is_set(error))
return r;
if (sd_bus_error_has_names(error, SD_BUS_ERROR_ACCESS_DENIED,
BUS_ERROR_ONLY_BY_DEPENDENCY,
BUS_ERROR_NO_ISOLATION,
BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE))
return EXIT_NOPERMISSION;
if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT))
return EXIT_NOTINSTALLED;
if (sd_bus_error_has_names(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE,
SD_BUS_ERROR_NOT_SUPPORTED))
return EXIT_NOTIMPLEMENTED;
if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED))
return EXIT_NOTCONFIGURED;
if (r != 0)
return r;
return EXIT_FAILURE;
}
int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_active_state) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *buf = NULL, *dbus_path = NULL;
UnitActiveState state;
int r;
assert(unit);
assert(ret_active_state);
dbus_path = unit_dbus_path_from_name(unit);
if (!dbus_path)
return log_oom();
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Unit",
"ActiveState",
&error,
&buf);
if (r < 0)
return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r));
state = unit_active_state_from_string(buf);
if (state < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid unit state '%s' for: %s", buf, unit);
*ret_active_state = state;
return 0;
}
int get_unit_list(
sd_bus *bus,
const char *machine,
char **patterns,
UnitInfo **unit_infos,
int c,
sd_bus_message **ret_reply) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
size_t size = c;
int r;
bool fallback = false;
assert(bus);
assert(unit_infos);
assert(ret_reply);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsByPatterns");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, arg_states);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, patterns);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0 && (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_METHOD,
SD_BUS_ERROR_ACCESS_DENIED))) {
/* Fallback to legacy ListUnitsFiltered method */
fallback = true;
log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r));
m = sd_bus_message_unref(m);
sd_bus_error_free(&error);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsFiltered");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, arg_states);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
}
if (r < 0)
return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
UnitInfo u;
r = bus_parse_unit_info(reply, &u);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
u.machine = machine;
if (!output_show_unit(&u, fallback ? patterns : NULL))
continue;
if (!GREEDY_REALLOC(*unit_infos, size, c+1))
return log_oom();
(*unit_infos)[c++] = u;
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
*ret_reply = TAKE_PTR(reply);
return c;
}
int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded) {
_cleanup_strv_free_ char **mangled = NULL, **globs = NULL;
char **name;
int r, i;
assert(bus);
assert(ret);
STRV_FOREACH(name, names) {
UnitNameMangle options = UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN);
char *t;
r = unit_name_mangle_with_suffix(*name, NULL, options, suffix ?: ".service", &t);
if (r < 0)
return log_error_errno(r, "Failed to mangle name: %m");
if (string_is_glob(t))
r = strv_consume(&globs, t);
else
r = strv_consume(&mangled, t);
if (r < 0)
return log_oom();
}
/* Query the manager only if any of the names are a glob, since this is fairly expensive */
bool expanded = !strv_isempty(globs);
if (expanded) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ UnitInfo *unit_infos = NULL;
size_t allocated, n;
r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply);
if (r < 0)
return r;
n = strv_length(mangled);
allocated = n + 1;
for (i = 0; i < r; i++) {
if (!GREEDY_REALLOC(mangled, allocated, n+2))
return log_oom();
mangled[n] = strdup(unit_infos[i].id);
if (!mangled[n])
return log_oom();
mangled[++n] = NULL;
}
}
if (ret_expanded)
*ret_expanded = expanded;
*ret = TAKE_PTR(mangled);
return 0;
}
int check_triggering_units(sd_bus *bus, const char *unit) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *n = NULL, *dbus_path = NULL, *load_state = NULL;
_cleanup_strv_free_ char **triggered_by = NULL;
bool print_warning_label = true;
UnitActiveState active_state;
char **i;
int r;
r = unit_name_mangle(unit, 0, &n);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = unit_load_state(bus, n, &load_state);
if (r < 0)
return r;
if (streq(load_state, "masked"))
return 0;
dbus_path = unit_dbus_path_from_name(n);
if (!dbus_path)
return log_oom();
r = sd_bus_get_property_strv(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Unit",
"TriggeredBy",
&error,
&triggered_by);
if (r < 0)
return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r));
STRV_FOREACH(i, triggered_by) {
r = get_state_one_unit(bus, *i, &active_state);
if (r < 0)
return r;
if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING))
continue;
if (print_warning_label) {
log_warning("Warning: Stopping %s, but it can still be activated by:", n);
print_warning_label = false;
}
log_warning(" %s", *i);
}
return 0;
}
int need_daemon_reload(sd_bus *bus, const char *unit) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *path;
int b, r;
/* We ignore all errors here, since this is used to show a
* warning only */
/* We don't use unit_dbus_path_from_name() directly since we
* don't want to load the unit if it isn't loaded. */
r = bus_call_method(bus, bus_systemd_mgr, "GetUnit", NULL, &reply, "s", unit);
if (r < 0)
return r;
r = sd_bus_message_read(reply, "o", &path);
if (r < 0)
return r;
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Unit",
"NeedDaemonReload",
NULL,
'b', &b);
if (r < 0)
return r;
return b;
}
void warn_unit_file_changed(const char *unit) {
assert(unit);
log_warning("%sWarning:%s The unit file, source configuration file or drop-ins of %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.",
ansi_highlight_red(),
ansi_normal(),
unit,
arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
}
int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **ret_unit_path) {
char **p;
assert(lp);
assert(unit_name);
STRV_FOREACH(p, lp->search_path) {
_cleanup_free_ char *path = NULL, *lpath = NULL;
int r;
path = path_join(*p, unit_name);
if (!path)
return log_oom();
r = chase_symlinks(path, arg_root, 0, &lpath, NULL);
if (r == -ENOENT)
continue;
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to access path \"%s\": %m", path);
if (ret_unit_path)
*ret_unit_path = TAKE_PTR(lpath);
return 1;
}
if (ret_unit_path)
*ret_unit_path = NULL;
return 0;
}
int unit_find_paths(
sd_bus *bus,
const char *unit_name,
LookupPaths *lp,
bool force_client_side,
Hashmap **cached_name_map,
Hashmap **cached_id_map,
char **ret_fragment_path,
char ***ret_dropin_paths) {
_cleanup_strv_free_ char **dropins = NULL;
_cleanup_free_ char *path = NULL;
int r;
/**
* Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is
* found, and sets:
* - the path to the unit in *ret_frament_path, if it exists on disk,
* - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins
* were found.
*
* Returns -ERFKILL if the unit is masked, and -EKEYREJECTED if the unit file could not be loaded for
* some reason (the latter only applies if we are going through the service manager).
*/
assert(unit_name);
assert(ret_fragment_path);
assert(lp);
/* Go via the bus to acquire the path, unless we are explicitly told not to, or when the unit name is a template */
if (!force_client_side &&
!install_client_side() &&
!unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *load_state = NULL, *dbus_path = NULL;
dbus_path = unit_dbus_path_from_name(unit_name);
if (!dbus_path)
return log_oom();
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Unit",
"LoadState",
&error,
&load_state);
if (r < 0)
return log_error_errno(r, "Failed to get LoadState: %s", bus_error_message(&error, r));
if (streq(load_state, "masked"))
return -ERFKILL;
if (streq(load_state, "not-found")) {
r = 0;
goto not_found;
}
if (!STR_IN_SET(load_state, "loaded", "bad-setting"))
return -EKEYREJECTED;
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Unit",
"FragmentPath",
&error,
&path);
if (r < 0)
return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r));
if (ret_dropin_paths) {
r = sd_bus_get_property_strv(
bus,
"org.freedesktop.systemd1",
dbus_path,
"org.freedesktop.systemd1.Unit",
"DropInPaths",
&error,
&dropins);
if (r < 0)
return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r));
}
} else {
const char *_path;
_cleanup_set_free_free_ Set *names = NULL;
if (!*cached_name_map) {
r = unit_file_build_name_map(lp, NULL, cached_id_map, cached_name_map, NULL);
if (r < 0)
return r;
}
r = unit_file_find_fragment(*cached_id_map, *cached_name_map, unit_name, &_path, &names);
if (r < 0)
return r;
if (_path) {
path = strdup(_path);
if (!path)
return log_oom();
}
if (ret_dropin_paths) {
r = unit_file_find_dropin_paths(arg_root, lp->search_path, NULL,
".d", ".conf",
NULL, names, &dropins);
if (r < 0)
return r;
}
}
if (isempty(path)) {
*ret_fragment_path = NULL;
r = 0;
} else {
*ret_fragment_path = TAKE_PTR(path);
r = 1;
}
if (ret_dropin_paths) {
if (!strv_isempty(dropins)) {
*ret_dropin_paths = TAKE_PTR(dropins);
r = 1;
} else
*ret_dropin_paths = NULL;
}
not_found:
if (r == 0 && !arg_force)
log_error("No files found for %s.", unit_name);
return r;
}
static int unit_find_template_path(
const char *unit_name,
LookupPaths *lp,
char **ret_fragment_path,
char **ret_template) {
_cleanup_free_ char *t = NULL, *f = NULL;
int r;
/* Returns 1 if a fragment was found, 0 if not found, negative on error. */
r = unit_file_find_path(lp, unit_name, &f);
if (r < 0)
return r;
if (r > 0) {
if (ret_fragment_path)
*ret_fragment_path = TAKE_PTR(f);
if (ret_template)
*ret_template = NULL;
return r; /* found a real unit */
}
r = unit_name_template(unit_name, &t);
if (r == -EINVAL) {
if (ret_fragment_path)
*ret_fragment_path = NULL;
if (ret_template)
*ret_template = NULL;
return 0; /* not a template, does not exist */
}
if (r < 0)
return log_error_errno(r, "Failed to determine template name: %m");
r = unit_file_find_path(lp, t, ret_fragment_path);
if (r < 0)
return r;
if (ret_template)
*ret_template = r > 0 ? TAKE_PTR(t) : NULL;
return r;
}
int unit_is_masked(sd_bus *bus, LookupPaths *lp, const char *name) {
_cleanup_free_ char *load_state = NULL;
int r;
if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
_cleanup_free_ char *path = NULL;
/* A template cannot be loaded, but it can be still masked, so
* we need to use a different method. */
r = unit_file_find_path(lp, name, &path);
if (r < 0)
return r;
if (r == 0)
return false;
return null_or_empty_path(path);
}
r = unit_load_state(bus, name, &load_state);
if (r < 0)
return r;
return streq(load_state, "masked");
}
int unit_exists(LookupPaths *lp, const char *unit) {
typedef struct UnitStateInfo {
const char *load_state;
const char *active_state;
} UnitStateInfo;
static const struct bus_properties_map property_map[] = {
{ "LoadState", "s", NULL, offsetof(UnitStateInfo, load_state) },
{ "ActiveState", "s", NULL, offsetof(UnitStateInfo, active_state) },
{},
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_free_ char *path = NULL;
UnitStateInfo info = {};
sd_bus *bus;
int r;
if (unit_name_is_valid(unit, UNIT_NAME_TEMPLATE))
return unit_find_template_path(unit, lp, NULL, NULL);
path = unit_dbus_path_from_name(unit);
if (!path)
return log_oom();
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_map_all_properties(bus, "org.freedesktop.systemd1", path, property_map, 0, &error, &m, &info);
if (r < 0)
return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
return !streq_ptr(info.load_state, "not-found") || !streq_ptr(info.active_state, "inactive");
}
int append_unit_dependencies(sd_bus *bus, char **names, char ***ret) {
_cleanup_strv_free_ char **with_deps = NULL;
char **name;
assert(bus);
assert(ret);
STRV_FOREACH(name, names) {
_cleanup_strv_free_ char **deps = NULL;
if (strv_extend(&with_deps, *name) < 0)
return log_oom();
(void) unit_get_dependencies(bus, *name, &deps);
if (strv_extend_strv(&with_deps, deps, true) < 0)
return log_oom();
}
*ret = TAKE_PTR(with_deps);
return 0;
}
int maybe_extend_with_unit_dependencies(sd_bus *bus, char ***list) {
_cleanup_strv_free_ char **list_with_deps = NULL;
int r;
assert(bus);
assert(list);
if (!arg_with_dependencies)
return 0;
r = append_unit_dependencies(bus, *list, &list_with_deps);
if (r < 0)
return log_error_errno(r, "Failed to append unit dependencies: %m");
strv_free(*list);
*list = TAKE_PTR(list_with_deps);
return 0;
}
int unit_get_dependencies(sd_bus *bus, const char *name, char ***ret) {
_cleanup_strv_free_ char **deps = NULL;
static const struct bus_properties_map map[_DEPENDENCY_MAX][6] = {
[DEPENDENCY_FORWARD] = {
{ "Requires", "as", NULL, 0 },
{ "Requisite", "as", NULL, 0 },
{ "Wants", "as", NULL, 0 },
{ "ConsistsOf", "as", NULL, 0 },
{ "BindsTo", "as", NULL, 0 },
{}
},
[DEPENDENCY_REVERSE] = {
{ "RequiredBy", "as", NULL, 0 },
{ "RequisiteOf", "as", NULL, 0 },
{ "WantedBy", "as", NULL, 0 },
{ "PartOf", "as", NULL, 0 },
{ "BoundBy", "as", NULL, 0 },
{}
},
[DEPENDENCY_AFTER] = {
{ "After", "as", NULL, 0 },
{}
},
[DEPENDENCY_BEFORE] = {
{ "Before", "as", NULL, 0 },
{}
},
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *dbus_path = NULL;
int r;
assert(bus);
assert(name);
assert(ret);
dbus_path = unit_dbus_path_from_name(name);
if (!dbus_path)
return log_oom();
r = bus_map_all_properties(bus,
"org.freedesktop.systemd1",
dbus_path,
map[arg_dependency],
BUS_MAP_STRDUP,
&error,
NULL,
&deps);
if (r < 0)
return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r));
strv_uniq(deps); /* Sometimes a unit might have multiple deps on the other unit,
* but we still want to show it just once. */
*ret = TAKE_PTR(deps);
return 0;
}
const char* unit_type_suffix(const char *unit) {
const char *dot;
dot = strrchr(unit, '.');
if (!dot)
return "";
return dot + 1;
}
bool output_show_unit(const UnitInfo *u, char **patterns) {
assert(u);
if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE))
return false;
if (arg_types && !strv_find(arg_types, unit_type_suffix(u->id)))
return false;
if (arg_all)
return true;
/* Note that '--all' is not purely a state filter, but also a filter that hides units that "follow"
* other units (which is used for device units that appear under different names). */
if (!isempty(u->following))
return false;
if (!strv_isempty(arg_states))
return true;
/* By default show all units except the ones in inactive state and with no pending job */
if (u->job_id > 0)
return true;
if (streq(u->active_state, "inactive"))
return false;
return true;
}
bool install_client_side(void) {
/* Decides when to execute enable/disable/... operations client-side rather than server-side. */
if (running_in_chroot_or_offline())
return true;
if (sd_booted() <= 0)
return true;
if (!isempty(arg_root))
return true;
if (arg_scope == UNIT_FILE_GLOBAL)
return true;
/* Unsupported environment variable, mostly for debugging purposes */
if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0)
return true;
return false;
}
int output_table(Table *table) {
int r;
assert(table);
if (OUTPUT_MODE_IS_JSON(arg_output))
r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
else
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
return 0;
}
bool show_preset_for_state(UnitFileState state) {
/* Don't show preset state in those unit file states, it'll only confuse users. */
return !IN_SET(state,
UNIT_FILE_ALIAS,
UNIT_FILE_STATIC,
UNIT_FILE_GENERATED,
UNIT_FILE_TRANSIENT);
}
UnitFileFlags unit_file_flags_from_args(void) {
return (arg_runtime ? UNIT_FILE_RUNTIME : 0) |
(arg_force ? UNIT_FILE_FORCE : 0);
}
int mangle_names(const char *operation, char **original_names, char ***ret_mangled_names) {
_cleanup_strv_free_ char **l = NULL;
char **i, **name;
int r;
assert(ret_mangled_names);
l = i = new(char*, strv_length(original_names) + 1);
if (!l)
return log_oom();
STRV_FOREACH(name, original_names) {
/* When enabling units qualified path names are OK, too, hence allow them explicitly. */
if (is_path(*name)) {
*i = strdup(*name);
if (!*i)
return log_oom();
} else {
r = unit_name_mangle_with_suffix(*name, operation,
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".service", i);
if (r < 0) {
*i = NULL;
return log_error_errno(r, "Failed to mangle unit name: %m");
}
}
i++;
}
*i = NULL;
*ret_mangled_names = TAKE_PTR(l);
return 0;
}
int halt_now(enum action a) {
/* The kernel will automatically flush ATA disks and suchlike on reboot(), but the file systems need
* to be synced explicitly in advance. */
if (!arg_no_sync && !arg_dry_run)
(void) sync();
/* Make sure C-A-D is handled by the kernel from this point on... */
if (!arg_dry_run)
(void) reboot(RB_ENABLE_CAD);
switch (a) {
case ACTION_HALT:
if (!arg_quiet)
log_info("Halting.");
if (arg_dry_run)
return 0;
(void) reboot(RB_HALT_SYSTEM);
return -errno;
case ACTION_POWEROFF:
if (!arg_quiet)
log_info("Powering off.");
if (arg_dry_run)
return 0;
(void) reboot(RB_POWER_OFF);
return -errno;
case ACTION_KEXEC:
case ACTION_REBOOT:
return reboot_with_parameter(REBOOT_FALLBACK |
(arg_quiet ? 0 : REBOOT_LOG) |
(arg_dry_run ? REBOOT_DRY_RUN : 0));
default:
assert_not_reached("Unknown action.");
}
}

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "bus-unit-util.h"
#include "format-table.h"
#include "systemctl.h"
typedef enum BusFocus {
BUS_FULL, /* The full bus indicated via --system or --user */
BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */
_BUS_FOCUS_MAX
} BusFocus;
int acquire_bus(BusFocus focus, sd_bus **ret);
void release_busses(void);
void ask_password_agent_open_maybe(void);
void polkit_agent_open_maybe(void);
int translate_bus_error_to_exit_status(int r, const sd_bus_error *error);
int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *ret_active_state);
int get_unit_list(sd_bus *bus, const char *machine, char **patterns, UnitInfo **unit_infos, int c, sd_bus_message **ret_reply);
int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded);
int check_triggering_units(sd_bus *bus, const char *unit);
int need_daemon_reload(sd_bus *bus, const char *unit);
void warn_unit_file_changed(const char *unit);
int append_unit_dependencies(sd_bus *bus, char **names, char ***ret);
int maybe_extend_with_unit_dependencies(sd_bus *bus, char ***list);
int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **ret_unit_path);
int unit_find_paths(sd_bus *bus, const char *unit_name, LookupPaths *lp, bool force_client_side, Hashmap **cached_id_map, Hashmap **cached_name_map, char **ret_fragment_path, char ***ret_dropin_paths);
int unit_is_masked(sd_bus *bus, LookupPaths *lp, const char *name);
int unit_exists(LookupPaths *lp, const char *unit);
int unit_get_dependencies(sd_bus *bus, const char *name, char ***ret);
const char* unit_type_suffix(const char *unit);
bool output_show_unit(const UnitInfo *u, char **patterns);
bool install_client_side(void);
int output_table(Table *table);
bool show_preset_for_state(UnitFileState state);
int mangle_names(const char *operation, char **original_names, char ***ret_mangled_names);
UnitFileFlags unit_file_flags_from_args(void);
int halt_now(enum action a);

File diff suppressed because it is too large Load Diff

92
src/systemctl/systemctl.h Normal file
View File

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <stdbool.h>
#include "bus-util.h"
#include "install.h"
#include "output-mode.h"
#include "pager.h"
enum action {
ACTION_SYSTEMCTL,
ACTION_HALT,
ACTION_POWEROFF,
ACTION_REBOOT,
ACTION_KEXEC,
ACTION_EXIT,
ACTION_SUSPEND,
ACTION_HIBERNATE,
ACTION_HYBRID_SLEEP,
ACTION_SUSPEND_THEN_HIBERNATE,
ACTION_RUNLEVEL2,
ACTION_RUNLEVEL3,
ACTION_RUNLEVEL4,
ACTION_RUNLEVEL5,
ACTION_RESCUE,
ACTION_EMERGENCY,
ACTION_DEFAULT,
ACTION_RELOAD,
ACTION_REEXEC,
ACTION_RUNLEVEL,
ACTION_CANCEL_SHUTDOWN,
_ACTION_MAX,
_ACTION_INVALID = -1
};
enum dependency {
DEPENDENCY_FORWARD,
DEPENDENCY_REVERSE,
DEPENDENCY_AFTER,
DEPENDENCY_BEFORE,
_DEPENDENCY_MAX
};
extern char **arg_types;
extern char **arg_states;
extern char **arg_properties;
extern bool arg_all;
extern enum dependency arg_dependency;
extern const char *arg_job_mode;
extern UnitFileScope arg_scope;
extern bool arg_wait;
extern bool arg_no_block;
extern bool arg_no_legend;
extern PagerFlags arg_pager_flags;
extern bool arg_no_wtmp;
extern bool arg_no_sync;
extern bool arg_no_wall;
extern bool arg_no_reload;
extern bool arg_value;
extern bool arg_show_types;
extern bool arg_ignore_inhibitors;
extern bool arg_dry_run;
extern bool arg_quiet;
extern bool arg_full;
extern bool arg_recursive;
extern bool arg_with_dependencies;
extern bool arg_show_transaction;
extern int arg_force;
extern bool arg_ask_password;
extern bool arg_runtime;
extern UnitFilePresetMode arg_preset_mode;
extern char **arg_wall;
extern const char *arg_kill_who;
extern int arg_signal;
extern char *arg_root;
extern usec_t arg_when;
extern const char *arg_reboot_argument;
extern enum action arg_action;
extern BusTransport arg_transport;
extern const char *arg_host;
extern unsigned arg_lines;
extern OutputMode arg_output;
extern bool arg_plain;
extern bool arg_firmware_setup;
extern usec_t arg_boot_loader_menu;
extern const char *arg_boot_loader_entry;
extern bool arg_now;
extern bool arg_jobs_before;
extern bool arg_jobs_after;
extern char **arg_clean_what;
extern TimestampStyle arg_timestamp_style;

View File

@ -1,102 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "fd-util.h"
#include "initreq.h"
#include "io-util.h"
#include "parse-util.h"
#include "strv.h"
#include "sysv-compat.h"
#if HAVE_SYSV_COMPAT
int talk_initctl(char rl) {
struct init_request request;
_cleanup_close_ int fd = -1;
const char *p;
int r;
/* Try to switch to the specified SysV runlevel. Returns == 0 if the operation does not apply on this
* system, and > 0 on success. */
if (rl == 0)
return 0;
FOREACH_STRING(p, "/run/initctl", "/dev/initctl") {
fd = open(p, O_WRONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd >= 0 || errno != ENOENT)
break;
}
if (fd < 0) {
if (errno == ENOENT)
return 0;
return log_error_errno(errno, "Failed to open initctl fifo: %m");
}
request = (struct init_request) {
.magic = INIT_MAGIC,
.sleeptime = 0,
.cmd = INIT_CMD_RUNLVL,
.runlevel = rl,
};
r = loop_write(fd, &request, sizeof(request), false);
if (r < 0)
return log_error_errno(r, "Failed to write to %s: %m", p);
return 1;
}
#endif
int parse_shutdown_time_spec(const char *t, usec_t *ret) {
assert(t);
assert(ret);
if (streq(t, "now"))
*ret = 0;
else if (!strchr(t, ':')) {
uint64_t u;
if (safe_atou64(t, &u) < 0)
return -EINVAL;
*ret = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u;
} else {
char *e = NULL;
long hour, minute;
struct tm tm = {};
time_t s;
usec_t n;
errno = 0;
hour = strtol(t, &e, 10);
if (errno > 0 || *e != ':' || hour < 0 || hour > 23)
return -EINVAL;
minute = strtol(e+1, &e, 10);
if (errno > 0 || *e != 0 || minute < 0 || minute > 59)
return -EINVAL;
n = now(CLOCK_REALTIME);
s = (time_t) (n / USEC_PER_SEC);
assert_se(localtime_r(&s, &tm));
tm.tm_hour = (int) hour;
tm.tm_min = (int) minute;
tm.tm_sec = 0;
s = mktime(&tm);
assert(s >= 0);
*ret = (usec_t) s * USEC_PER_SEC;
while (*ret <= n)
*ret += USEC_PER_DAY;
}
return 0;
}