937 lines
30 KiB
C
937 lines
30 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#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 bus_log_connect_error(r);
|
|
|
|
(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.");
|
|
}
|
|
}
|