diff --git a/man/systemctl.xml b/man/systemctl.xml
index 1a424c1e2b..d07c8eae28 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -443,6 +443,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
It will not fail if any of the commands configured to stop the unit
(ExecStop=, etc.) fail, because the manager will still forcibly terminate the
unit.
+
+ If a unit that gets stopped can still be triggered by other units, a warning containing
+ the names of the triggering units is shown. can be used to suppress
+ the warning.
@@ -892,6 +896,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
executed. This output may be suppressed by passing .
+ If a unit gets disabled but its triggering units are still active, a warning containing
+ the names of the triggering units is shown. can be used to suppress
+ the warning.
+
When this command is used with , the units being operated on might
still be enabled in global scope, and thus get started automatically even after a successful
disablement in user scope. In this case, a warning about it is shown, which can be suppressed
@@ -1079,6 +1087,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
mode, in which case the directories are below the user's home directory
however.
+ If a unit gets masked but its triggering units are still active, a warning containing
+ the names of the triggering units is shown. can be used to suppress
+ the warning.
+
@@ -2233,7 +2245,11 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
when using disable combined with on units
- that are enabled in global scope.
+ that are enabled in global scope,
+
+
+ when a stop-ped, disable-d, or mask-ed
+ unit still has active triggering units.
diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c
index e3f25d52d5..ac0bbb4542 100644
--- a/src/systemctl/systemctl-edit.c
+++ b/src/systemctl/systemctl-edit.c
@@ -318,7 +318,6 @@ int verb_edit(int argc, char *argv[], void *userdata) {
.remove_parent = !arg_full,
.overwrite_with_origin = true,
};
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
sd_bus *bus;
int r;
@@ -329,10 +328,6 @@ int verb_edit(int argc, char *argv[], void *userdata) {
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
- r = lookup_paths_init_or_warn(&lp, arg_runtime_scope, 0, arg_root);
- if (r < 0)
- return r;
-
r = mac_init();
if (r < 0)
return r;
@@ -348,7 +343,7 @@ int verb_edit(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
STRV_FOREACH(tmp, names) {
- r = unit_is_masked(bus, &lp, *tmp);
+ r = unit_is_masked(bus, *tmp);
if (r < 0)
return r;
if (r > 0)
diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c
index 940f54607f..4d554444d3 100644
--- a/src/systemctl/systemctl-enable.c
+++ b/src/systemctl/systemctl-enable.c
@@ -138,7 +138,8 @@ int verb_enable(int argc, char *argv[], void *userdata) {
_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;
+ const char *method, *warn_trigger_operation = NULL;
+ bool warn_trigger_ignore_masked = true; /* suppress "used uninitialized" warning */
sd_bus *bus;
if (STR_IN_SET(verb, "mask", "unmask")) {
@@ -170,6 +171,9 @@ int verb_enable(int argc, char *argv[], void *userdata) {
method = "DisableUnitFilesWithFlagsAndInstallInfo";
expect_carries_install_info = true;
send_force = false;
+
+ warn_trigger_operation = "Disabling";
+ warn_trigger_ignore_masked = true;
} else if (streq(verb, "reenable")) {
method = "ReenableUnitFiles";
expect_carries_install_info = true;
@@ -185,9 +189,12 @@ int verb_enable(int argc, char *argv[], void *userdata) {
expect_carries_install_info = true;
ignore_carries_install_info = true;
- } else if (streq(verb, "mask"))
+ } else if (streq(verb, "mask")) {
method = "MaskUnitFiles";
- else if (streq(verb, "unmask")) {
+
+ warn_trigger_operation = "Masking";
+ warn_trigger_ignore_masked = false;
+ } else if (streq(verb, "unmask")) {
method = "UnmaskUnitFiles";
send_force = false;
} else if (streq(verb, "revert")) {
@@ -245,6 +252,10 @@ int verb_enable(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
}
+
+ if (warn_trigger_operation && !arg_quiet && !arg_no_warn)
+ STRV_FOREACH(unit, names)
+ warn_triggering_units(bus, *unit, warn_trigger_operation, warn_trigger_ignore_masked);
}
if (carries_install_info == 0 && !ignore_carries_install_info)
@@ -254,7 +265,7 @@ int verb_enable(int argc, char *argv[], void *userdata) {
" \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"
+ " .wants/, .requires/, or .upholds/ 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"
@@ -267,7 +278,8 @@ int verb_enable(int argc, char *argv[], void *userdata) {
/* If some of the units are disabled in user scope but still enabled in global scope,
* we emit a warning for that. */
- _cleanup_strv_free_ char **enabled_in_global_scope = NULL;
+ /* No strv_free here, strings are owned by 'names' */
+ _cleanup_free_ char **enabled_in_global_scope = NULL;
STRV_FOREACH(name, names) {
UnitFileState state;
@@ -279,24 +291,24 @@ int verb_enable(int argc, char *argv[], void *userdata) {
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)) {
- r = strv_extend(&enabled_in_global_scope, *name);
+ r = strv_push(&enabled_in_global_scope, *name);
if (r < 0)
return log_oom();
}
}
if (!strv_isempty(enabled_in_global_scope)) {
- _cleanup_free_ char *units = NULL;
+ _cleanup_free_ char *joined = NULL;
- units = strv_join(enabled_in_global_scope, ", ");
- if (!units)
+ joined = strv_join(enabled_in_global_scope, ", ");
+ if (!joined)
return log_oom();
log_notice("The following unit files have been enabled in global scope. This means\n"
"they will still be started automatically after a successful disablement\n"
"in user scope:\n"
"%s",
- units);
+ joined);
}
}
diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c
index 551e6f6768..6927e97747 100644
--- a/src/systemctl/systemctl-start-unit.c
+++ b/src/systemctl/systemctl-start-unit.c
@@ -392,9 +392,9 @@ int verb_start(int argc, char *argv[], void *userdata) {
/* 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_quiet && !arg_no_warn)
+ STRV_FOREACH(unit, stopped_units)
+ warn_triggering_units(bus, *unit, "Stopping", /* ignore_masked = */ true);
}
if (arg_wait) {
diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c
index 074bb8d83b..2498725d3f 100644
--- a/src/systemctl/systemctl-util.c
+++ b/src/systemctl/systemctl-util.c
@@ -315,26 +315,33 @@ int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret
return 0;
}
-int check_triggering_units(sd_bus *bus, const char *unit) {
+int get_active_triggering_units(sd_bus *bus, const char *unit, bool ignore_masked, char ***ret) {
_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;
+ _cleanup_strv_free_ char **triggered_by = NULL, **active = NULL;
+ _cleanup_free_ char *name = NULL, *dbus_path = NULL;
int r;
- r = unit_name_mangle(unit, 0, &n);
- if (r < 0)
- return log_error_errno(r, "Failed to mangle unit name: %m");
+ assert(bus);
+ assert(unit);
+ assert(ret);
- r = unit_load_state(bus, n, &load_state);
+ r = unit_name_mangle(unit, 0, &name);
if (r < 0)
return r;
- if (streq(load_state, "masked"))
- return 0;
+ if (ignore_masked) {
+ r = unit_is_masked(bus, name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *ret = NULL;
+ return 0;
+ }
+ }
- dbus_path = unit_dbus_path_from_name(n);
+ dbus_path = unit_dbus_path_from_name(name);
if (!dbus_path)
- return log_oom();
+ return -ENOMEM;
r = sd_bus_get_property_strv(
bus,
@@ -345,9 +352,9 @@ int check_triggering_units(sd_bus *bus, const char *unit) {
&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));
+ return log_debug_errno(r, "Failed to get TriggeredBy property of unit '%s': %s",
+ name, bus_error_message(&error, r));
- bool first = true;
STRV_FOREACH(i, triggered_by) {
UnitActiveState active_state;
@@ -358,17 +365,43 @@ int check_triggering_units(sd_bus *bus, const char *unit) {
if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING))
continue;
- if (first) {
- log_warning("Warning: Stopping %s, but it can still be activated by:", n);
- first = false;
- }
-
- log_warning(" %s", *i);
+ r = strv_extend(&active, *i);
+ if (r < 0)
+ return r;
}
+ *ret = TAKE_PTR(active);
return 0;
}
+void warn_triggering_units(sd_bus *bus, const char *unit, const char *operation, bool ignore_masked) {
+ _cleanup_strv_free_ char **triggered_by = NULL;
+ _cleanup_free_ char *joined = NULL;
+ int r;
+
+ assert(bus);
+ assert(unit);
+ assert(operation);
+
+ r = get_active_triggering_units(bus, unit, ignore_masked, &triggered_by);
+ if (r < 0) {
+ log_warning_errno(r,
+ "Failed to get triggering units for '%s', ignoring: %m", unit);
+ return;
+ }
+
+ if (strv_isempty(triggered_by))
+ return;
+
+ joined = strv_join(triggered_by, ", ");
+ if (!joined)
+ return (void) log_oom();
+
+ log_warning("%s '%s', but its triggering units are still active:\n"
+ "%s",
+ operation, unit, joined);
+}
+
int need_daemon_reload(sd_bus *bus, const char *unit) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *path;
@@ -624,25 +657,31 @@ static int unit_find_template_path(
return r;
}
-int unit_is_masked(sd_bus *bus, LookupPaths *lp, const char *name) {
+int unit_is_masked(sd_bus *bus, const char *unit) {
_cleanup_free_ char *load_state = NULL;
int r;
- if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
- _cleanup_free_ char *path = NULL;
+ assert(bus);
+ assert(unit);
- /* A template cannot be loaded, but it can be still masked, so
- * we need to use a different method. */
+ if (unit_name_is_valid(unit, UNIT_NAME_TEMPLATE)) {
+ _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 *state;
- r = unit_file_find_path(lp, name, &path);
+ r = bus_call_method(bus, bus_systemd_mgr, "GetUnitFileState", &error, &reply, "s", unit);
if (r < 0)
- return r;
- if (r == 0)
- return false;
- return null_or_empty_path(path);
+ return log_debug_errno(r, "Failed to get UnitFileState for '%s': %s",
+ unit, bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "s", &state);
+ if (r < 0)
+ return bus_log_parse_error_debug(r);
+
+ return STR_IN_SET(state, "masked", "masked-runtime");
}
- r = unit_load_state(bus, name, &load_state);
+ r = unit_load_state(bus, unit, &load_state);
if (r < 0)
return r;
diff --git a/src/systemctl/systemctl-util.h b/src/systemctl/systemctl-util.h
index 317bab75b7..7bddef07ef 100644
--- a/src/systemctl/systemctl-util.h
+++ b/src/systemctl/systemctl-util.h
@@ -26,7 +26,8 @@ int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_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 get_active_triggering_units(sd_bus *bus, const char *unit, bool ignore_masked, char ***ret);
+void warn_triggering_units(sd_bus *bus, const char *unit, const char *operation, bool ignore_masked);
int need_daemon_reload(sd_bus *bus, const char *unit);
@@ -38,7 +39,7 @@ 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_is_masked(sd_bus *bus, const char *unit);
int unit_exists(LookupPaths *lp, const char *unit);
int unit_get_dependencies(sd_bus *bus, const char *name, char ***ret);