From 6b02854f508be3f27b45353dd1d12de7d93cab5f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 30 Jun 2025 14:54:12 +0200 Subject: [PATCH] systemctl: add --kill-subgroup= switch for killing subcgroup --- man/systemctl.xml | 33 ++++++++++++++++++++++++++++----- src/systemctl/systemctl-kill.c | 13 ++++++++++++- src/systemctl/systemctl.c | 26 +++++++++++++++++++++++++- src/systemctl/systemctl.h | 1 + 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 9678be018e8..f147143a7c2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2494,11 +2494,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err one is then considered the main process of the unit (if it can be determined). This is different for service units of other types, where the process forked off by the manager for ExecStart= is always the main process itself. A service unit consists of zero or - one main process, zero or one control process plus any number of additional processes. Not all unit - types manage processes of these types however. For example, for mount units, control processes are - defined (which are the invocations of &MOUNT_PATH; and - &UMOUNT_PATH;), but no main process is defined. If omitted, defaults to - . + one main process, zero or one control process plus any number of additional processes part of the + unit's control group. Not all unit types manage processes of these types however. For example, for + mount units, control processes are defined (which are the invocations of + &MOUNT_PATH; and &UMOUNT_PATH;), but no main process is + defined. If omitted, defaults to , except if + is used in which case defaults to . @@ -2525,6 +2526,28 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + + Takes a control group sub-path to send signals to, for use with the + kill command. By default the chosen signal is delivered to all processes of the + unit's cgroups (as well as the main/control processes (if outside) – subject to + ). But with this option a subgroup can be selelected instead. This + functionality is only available if cgroup or cgroup-fail are + used with , and in fact the former is the default if + is used. + + The specified path may, but doesn't have to be prefixed with a slash, and its absence or + presence has no effect, the path is either way taken relative to the unit's main control group + path. + + This functionality is only available on units where control group delegation is enabled (see + Delegate= in + systemd.resource-control5). + + + + diff --git a/src/systemctl/systemctl-kill.c b/src/systemctl/systemctl-kill.c index efeff44e0f8..1452deb5b7c 100644 --- a/src/systemctl/systemctl-kill.c +++ b/src/systemctl/systemctl-kill.c @@ -20,6 +20,9 @@ int verb_kill(int argc, char *argv[], void *userdata) { sd_bus *bus; int r, q; + if (arg_kill_subgroup && arg_kill_value_set) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--kill-subgroup= and --kill-value= may not be combined."); + r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) return r; @@ -32,7 +35,7 @@ int verb_kill(int argc, char *argv[], void *userdata) { polkit_agent_open_maybe(); - kill_whom = arg_kill_whom ?: "all"; + kill_whom = arg_kill_whom ?: arg_kill_subgroup ? "cgroup" : "all"; /* --fail was specified */ if (streq(arg_job_mode(), "fail")) @@ -53,6 +56,14 @@ int verb_kill(int argc, char *argv[], void *userdata) { &error, NULL, "ssii", *name, kill_whom, arg_signal, arg_kill_value); + else if (arg_kill_subgroup) + q = bus_call_method( + bus, + bus_systemd_mgr, + "KillUnitSubgroup", + &error, + NULL, + "sssi", *name, kill_whom, arg_kill_subgroup, arg_signal); else q = bus_call_method( bus, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 3e241254cd3..cfe206a94ba 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -16,6 +16,7 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-util.h" #include "pretty-print.h" #include "static-destruct.h" #include "string-table.h" @@ -88,6 +89,7 @@ bool arg_mkdir = false; bool arg_marked = false; const char *arg_drop_in = NULL; ImagePolicy *arg_image_policy = NULL; +char *arg_kill_subgroup = NULL; STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep); @@ -103,6 +105,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_drop_in, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { _cleanup_free_ char *link = NULL; @@ -253,9 +256,11 @@ static int systemctl_help(void) { " Whether to check inhibitors before shutting down,\n" " sleeping, or hibernating\n" " -i Shortcut for --check-inhibitors=no\n" + " -s --signal=SIGNAL Which signal to send\n" " --kill-whom=WHOM Whom to send signal to\n" " --kill-value=INT Signal value to enqueue\n" - " -s --signal=SIGNAL Which signal to send\n" + " --kill-subgroup=PATH\n" + " Send signal to sub-control-group only\n" " --what=RESOURCES Which types of resources to remove\n" " --now Start or stop unit after enabling or disabling it\n" " --dry-run Only print what would be done\n" @@ -438,6 +443,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_DROP_IN, ARG_WHEN, ARG_STDIN, + ARG_KILL_SUBGROUP, }; static const struct option options[] = { @@ -507,6 +513,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "drop-in", required_argument, NULL, ARG_DROP_IN }, { "when", required_argument, NULL, ARG_WHEN }, { "stdin", no_argument, NULL, ARG_STDIN }, + { "kill-subgroup", required_argument, NULL, ARG_KILL_SUBGROUP }, {} }; @@ -1021,6 +1028,23 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_stdin = true; break; + case ARG_KILL_SUBGROUP: { + if (empty_or_root(optarg)) { + arg_kill_subgroup = mfree(arg_kill_subgroup); + break; + } + + _cleanup_free_ char *p = NULL; + if (path_simplify_alloc(optarg, &p) < 0) + return log_oom(); + + if (!path_is_safe(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Control group sub-path '%s' is not valid.", p); + + free_and_replace(arg_kill_subgroup, p); + break; + } + case '.': /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 40b207ad318..af30d2a6f58 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -100,6 +100,7 @@ extern bool arg_mkdir; extern bool arg_marked; extern const char *arg_drop_in; extern ImagePolicy *arg_image_policy; +extern char *arg_kill_subgroup; static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; -- 2.47.3