]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemctl: add --kill-subgroup= switch for killing subcgroup
authorLennart Poettering <lennart@poettering.net>
Mon, 30 Jun 2025 12:54:12 +0000 (14:54 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 8 Jul 2025 01:14:53 +0000 (03:14 +0200)
man/systemctl.xml
src/systemctl/systemctl-kill.c
src/systemctl/systemctl.c
src/systemctl/systemctl.h

index 9678be018e84b7e2891797380f392e194458533b..f147143a7c25a716d3bd1a4f3637a898ed6720a9 100644 (file)
@@ -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
           <varname>ExecStart=</varname> 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 <filename>&MOUNT_PATH;</filename> and
-          <filename>&UMOUNT_PATH;</filename>), but no main process is defined. If omitted, defaults to
-          <option>all</option>.</para>
+          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
+          <filename>&MOUNT_PATH;</filename> and <filename>&UMOUNT_PATH;</filename>), but no main process is
+          defined. If omitted, defaults to <option>all</option>, except if <option>--kill-subgroup=</option>
+          is used in which case defaults to <option>cgroup</option>.</para>
 
           <xi:include href="version-info.xml" xpointer="v252"/>
         </listitem>
@@ -2525,6 +2526,28 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
         <xi:include href="version-info.xml" xpointer="v254"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--kill-subgroup=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>Takes a control group sub-path to send signals to, for use with the
+        <command>kill</command> 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
+        <option>--kill-whom=</option>). But with this option a subgroup can be selelected instead. This
+        functionality is only available if <literal>cgroup</literal> or <literal>cgroup-fail</literal> are
+        used with <option>--kill-whom=</option>, and in fact the former is the default if
+        <option>--kill-subgroup=</option> is used.</para>
+
+        <para>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.</para>
+
+        <para>This functionality is only available on units where control group delegation is enabled (see
+        <varname>Delegate=</varname> in
+        <member><citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>).</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="signal" />
 
       <varlistentry>
index efeff44e0f84b5e092dd65d1f2555d448971f42a..1452deb5b7c907992aba0ab270da32257cf3a800 100644 (file)
@@ -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,
index 3e241254cd3eac51a523ce5b69db482436e6eb29..cfe206a94ba197a422d675507ff58ff492b08e63 100644 (file)
@@ -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);
index 40b207ad318f089265e9f43dfbebd7a7e8dddb77..af30d2a6f581a548571642ac916727e41c4c90a1 100644 (file)
@@ -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";