add api to kill subcgroups of units (#38102)

This commit is contained in:
Lennart Poettering
2025-07-08 06:33:32 +02:00
committed by GitHub
15 changed files with 289 additions and 58 deletions

View File

@@ -112,6 +112,10 @@ node /org/freedesktop/systemd1 {
KillUnit(in s name,
in s whom,
in i signal);
KillUnitSubgroup(in s name,
in s whom,
in s subgroup,
in i signal);
QueueSignalUnit(in s name,
in s whom,
in i signal,
@@ -831,6 +835,8 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-method" generated="True" extra-ref="KillUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="KillUnitSubgroup()"/>
<variablelist class="dbus-method" generated="True" extra-ref="QueueSignalUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="CleanUnit()"/>
@@ -1315,12 +1321,24 @@ node /org/freedesktop/systemd1 {
<para><function>KillUnit()</function> may be used to kill (i.e. send a signal to) all processes of a
unit. It takes the unit <varname>name</varname>, an enum <varname>who</varname> and a UNIX
<varname>signal</varname> number to send. The <varname>who</varname> enum is one of
<literal>main</literal>, <literal>control</literal> or <literal>all</literal>. If
<literal>main</literal>, only the main process of the unit is killed. If <literal>control</literal>, only
the control process of the unit is killed. If <literal>all</literal>, all processes are killed. A
<literal>main</literal>, <literal>control</literal>, <literal>cgroup</literal> or
<literal>all</literal>. If <literal>main</literal>, only the main process of the unit is killed. If
<literal>control</literal>, only the control process of the unit is killed. If
<literal>cgroup</literal> is specified only the processes in the control group of the unit are killed,
which might or might not include the main and control processes too. If <literal>all</literal>, all
processes are killed, i.e. the main process, the control process and those in the control group. A
<literal>control</literal> process is for example a process that is configured via
<varname>ExecStop=</varname> and is spawned in parallel to the main daemon process in order to shut it
down.</para>
down. The value may be suffixed by <literal>-fail</literal> in which case the operation will fail of no
matching process was found (otherwise it will return successfully, executing no operation).</para>
<para><function>KillUnitSubgroup()</function> is just like <function>KillUnit()</function> but takes an
additional path argument that selects a sub-control-group of the unit's control group. Only processes
in that subgroup are killed. The path my be specified with our without leading <literal>/</literal>, in
both cases it is taken relatively to the unit's control group. If the subgroup path is specified as an
empty string or as <literal>/</literal> it has the same effect as <function>KillUnit()</function>. If
it is specified as anything else the <literal>who</literal> parameter must be set to either
<literal>cgroup</literal> or <literal>cgroup-fail</literal>.</para>
<para><function>QueueSignalUnit()</function> is similar to <function>KillUnit()</function> but may be
used to enqueue a POSIX Realtime Signal (i.e. <constant>SIGRTMIN+…</constant> and
@@ -1886,8 +1904,8 @@ node /org/freedesktop/systemd1 {
<para>Read access is generally granted to all clients. Additionally, for unprivileged clients, some
operations are allowed through the polkit privilege system. Operations which modify unit state
(<function>StartUnit()</function>, <function>StopUnit()</function>, <function>KillUnit()</function>,
<function>QueueSignalUnit()</function>, <function>RestartUnit()</function> and similar,
<function>SetProperty()</function>) require
<function>KillUnitSubgroup()</function>, <function>QueueSignalUnit()</function>,
<function>RestartUnit()</function> and similar, <function>SetProperty()</function>) require
<interfacename>org.freedesktop.systemd1.manage-units</interfacename>. Operations which modify unit file
enablement state (<function>EnableUnitFiles()</function>, <function>DisableUnitFiles()</function>,
<function>EnableUnitFilesWithFlags()</function>, <function>DisableUnitFilesWithFlags()</function>,
@@ -1936,6 +1954,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
out a(uosos) affected_jobs);
Kill(in s whom,
in i signal);
KillSubgroup(in s subgroup,
in i signal);
QueueSignal(in s whom,
in i signal,
in i value);
@@ -2259,6 +2279,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-method" generated="True" extra-ref="Kill()"/>
<variablelist class="dbus-method" generated="True" extra-ref="KillSubgroup()"/>
<variablelist class="dbus-method" generated="True" extra-ref="QueueSignal()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ResetFailed()"/>
@@ -2487,13 +2509,13 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<para><function>Start()</function>, <function>Stop()</function>, <function>Reload()</function>,
<function>Restart()</function>, <function>TryRestart()</function>,
<function>ReloadOrRestart()</function>, <function>ReloadOrTryRestart()</function>,
<function>Kill()</function>, <function>QueueSignal()</function>, <function>ResetFailed()</function>,
and <function>SetProperties()</function> implement the same operation as the respective methods on the
<interfacename>Manager</interfacename> object (see above). However, these methods operate on the unit
object and hence do not take a unit name parameter. Invoking the methods directly on the Manager object
has the advantage of not requiring a <function>GetUnit()</function> call to get the unit object for a
specific unit name. Calling the methods on the Manager object is hence a round trip
optimization.</para>
<function>Kill()</function>, <function>KillSubgroup()</function>, <function>QueueSignal()</function>,
<function>ResetFailed()</function>, and <function>SetProperties()</function> implement the same
operation as the respective methods on the <interfacename>Manager</interfacename> object (see
above). However, these methods operate on the unit object and hence do not take a unit name
parameter. Invoking the methods directly on the Manager object has the advantage of not requiring a
<function>GetUnit()</function> call to get the unit object for a specific unit name. Calling the
methods on the Manager object is hence a round trip optimization.</para>
</refsect2>
<refsect2>
@@ -12224,7 +12246,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<para><varname>ShutdownStartTimestamp</varname>,
<varname>ShutdownStartTimestampMonotonic</varname>, and
<varname>SoftRebootsCount</varname> were added in version 256.</para>
<para><function>RemoveSubgroupFromUnit()</function> was added in version 258.</para>
<para><function>RemoveSubgroupFromUnit()</function>, and
<function>KillUnitSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Unit Objects</title>
@@ -12301,8 +12324,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CacheDirectoryQuotaUsage</varname>,
<varname>CacheDirectoryAccounting</varname>,
<varname>LogsDirectoryQuota</varname>,
<varname>LogsDirectoryQuotaUsage</varname>, and
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
@@ -12360,8 +12384,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CacheDirectoryQuotaUsage</varname>,
<varname>CacheDirectoryAccounting</varname>,
<varname>LogsDirectoryQuota</varname>,
<varname>LogsDirectoryQuotaUsage</varname>, and
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
@@ -12414,8 +12439,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CacheDirectoryQuotaUsage</varname>,
<varname>CacheDirectoryAccounting</varname>,
<varname>LogsDirectoryQuota</varname>,
<varname>LogsDirectoryQuotaUsage</varname>, and
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
@@ -12466,8 +12492,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CacheDirectoryQuotaUsage</varname>,
<varname>CacheDirectoryAccounting</varname>,
<varname>LogsDirectoryQuota</varname>,
<varname>LogsDirectoryQuotaUsage</varname>, and
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Slice Unit Objects</title>
@@ -12495,8 +12522,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<para><varname>ManagedOOMMemoryPressureDurationUSec</varname> was added in version 257.</para>
<para><varname>ConcurrencyHardMax</varname>,
<varname>ConcurrencySoftMax</varname>,
<varname>NCurrentlyActive</varname> and
<function>RemoveSubgroup()</function> were added in version 258.</para>
<varname>NCurrentlyActive</varname>,
<function>RemoveSubgroup()</function>, and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Scope Unit Objects</title>
@@ -12523,7 +12551,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<para><varname>ManagedOOMMemoryPressureDurationUSec</varname> was added in version 257.</para>
<para><function>RemoveSubgroup()</function> was added in version 258.</para>
<para><function>RemoveSubgroup()</function> and
<function>KillSubgroup()</function> were added in version 258.</para>
</refsect2>
<refsect2>
<title>Job Objects</title>

View File

@@ -2481,23 +2481,25 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<listitem>
<para>When used with <command>kill</command>, choose which processes to send a UNIX process signal
to. Must be one of <option>main</option>, <option>control</option> or <option>all</option> to
select whether to kill only the main process, the control process or all processes of the unit. The
main process of the unit is the one that defines the life-time of it. A control process of a unit
is one that is invoked by the manager to induce state changes of it. For example, all processes
started due to the <varname>ExecStartPre=</varname>, <varname>ExecStop=</varname> or
<varname>ExecReload=</varname> settings of service units are control processes. Note that there is
only one control process per unit at a time, as only one state change is executed at a time. For
services of type <varname>Type=forking</varname>, the initial process started by the manager for
to. Must be one of <option>main</option>, <option>control</option>, <option>cgroup</option> or
<option>all</option> to select whether to kill only the main process, the control process, all
processes in the unit's control group or all processes of the unit. The main process of the unit is
the one that defines the life-time of it. A control process of a unit is one that is invoked by the
manager to induce state changes of it. For example, all processes started due to the
<varname>ExecStartPre=</varname>, <varname>ExecStop=</varname> or <varname>ExecReload=</varname>
settings of service units are control processes. Note that there is only one control process per
unit at a time, as only one state change is executed at a time. For services of type
<varname>Type=forking</varname>, the initial process started by the manager for
<varname>ExecStart=</varname> is a control process, while the process ultimately forked off by that
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>
@@ -2524,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>

View File

@@ -831,6 +831,12 @@ static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_erro
return method_generic_unit_operation(message, userdata, error, bus_unit_method_kill, 0);
}
static int method_kill_unit_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* We don't bother with GENERIC_UNIT_LOAD nor GENERIC_UNIT_VALIDATE_LOADED here, as it shouldn't
* matter whether a unit is loaded for killing any processes possibly in the unit's cgroup. */
return method_generic_unit_operation(message, userdata, error, bus_unit_method_kill_subgroup, 0);
}
static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Load the unit if necessary, in order to load it, and insist on the unit being loaded to be
* cleaned */
@@ -3025,6 +3031,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_NO_RESULT,
method_kill_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("KillUnitSubgroup",
SD_BUS_ARGS("s", name, "s", whom, "s", subgroup, "i", signal),
SD_BUS_NO_RESULT,
method_kill_unit_subgroup,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("QueueSignalUnit",
SD_BUS_ARGS("s", name, "s", whom, "i", signal, "i", value),
SD_BUS_NO_RESULT,

View File

@@ -572,7 +572,60 @@ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
r = unit_kill(u, whom, signo, code, value, error);
r = unit_kill(u, whom, /* subgroup= */ NULL, signo, code, value, error);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
int bus_unit_method_kill_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Unit *u = ASSERT_PTR(userdata);
int r;
assert(message);
r = mac_selinux_unit_access_check(u, message, "stop", error);
if (r < 0)
return r;
const char *swhom, *subgroup;
int32_t signo;
r = sd_bus_message_read(message, "ssi", &swhom, &subgroup, &signo);
if (r < 0)
return r;
KillWhom whom;
if (isempty(swhom))
whom = KILL_CGROUP;
else {
whom = kill_whom_from_string(swhom);
if (whom < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid whom argument: %s", swhom);
}
if (isempty(subgroup))
subgroup = NULL;
else if (!path_is_normalized(subgroup))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified cgroup sub-path is not valid.");
else if (!IN_SET(whom, KILL_CGROUP, KILL_CGROUP_FAIL))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Subgroup can only be specified in combination with 'cgroup' or 'cgroup-fail'.");
if (!SIGNAL_VALID(signo))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range.");
r = bus_verify_manage_units_async_full(
u,
"kill-subgroup",
N_("Authentication is required to send a UNIX signal to the processes of subgroup of '$(unit)'."),
message,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
r = unit_kill(u, whom, subgroup, signo, SI_USER, /* value= */ 0, error);
if (r < 0)
return r;
@@ -986,6 +1039,11 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_NO_RESULT,
bus_unit_method_kill,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("KillSubgroup",
SD_BUS_ARGS("s", subgroup, "i", signal),
SD_BUS_NO_RESULT,
bus_unit_method_kill_subgroup,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("QueueSignal",
SD_BUS_ARGS("s", whom, "i", signal, "i", value),
SD_BUS_NO_RESULT,

View File

@@ -16,6 +16,7 @@ void bus_unit_send_removed_signal(Unit *u);
int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error);
int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_kill_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitWriteFlags flags, bool commit, sd_bus_error *error);

View File

@@ -54,6 +54,8 @@ static const char* const kill_whom_table[_KILL_WHOM_MAX] = {
[KILL_MAIN_FAIL] = "main-fail",
[KILL_CONTROL_FAIL] = "control-fail",
[KILL_ALL_FAIL] = "all-fail",
[KILL_CGROUP] = "cgroup",
[KILL_CGROUP_FAIL] = "cgroup-fail",
};
DEFINE_STRING_TABLE_LOOKUP(kill_whom, KillWhom);

View File

@@ -31,6 +31,8 @@ typedef enum KillWhom {
KILL_MAIN_FAIL,
KILL_CONTROL_FAIL,
KILL_ALL_FAIL,
KILL_CGROUP,
KILL_CGROUP_FAIL,
_KILL_WHOM_MAX,
_KILL_WHOM_INVALID = -EINVAL,
} KillWhom;

View File

@@ -246,6 +246,10 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="KillUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="KillUnitSubgroup"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="QueueSignalUnit"/>
@@ -410,6 +414,10 @@
send_interface="org.freedesktop.systemd1.Unit"
send_member="Kill"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Unit"
send_member="KillSubgroup"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Unit"
send_member="QueueSignal"/>

View File

@@ -4041,6 +4041,7 @@ static int unit_kill_one(
int unit_kill(
Unit *u,
KillWhom whom,
const char *subgroup,
int signo,
int code,
int value,
@@ -4060,11 +4061,19 @@ int unit_kill(
assert(SIGNAL_VALID(signo));
assert(IN_SET(code, SI_USER, SI_QUEUE));
if (subgroup) {
if (!IN_SET(whom, KILL_CGROUP, KILL_CGROUP_FAIL))
return sd_bus_error_set(ret_error, SD_BUS_ERROR_NOT_SUPPORTED, "Killing by subgroup is only supported for 'cgroup' or 'cgroup-kill' modes.");
if (!unit_cgroup_delegate(u))
return sd_bus_error_set(ret_error, SD_BUS_ERROR_NOT_SUPPORTED, "Killing by subgroup is only available for units with control group delegation enabled.");
}
main_pid = unit_main_pid(u);
control_pid = unit_control_pid(u);
if (!UNIT_HAS_CGROUP_CONTEXT(u) && !main_pid && !control_pid)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit type does not support process killing.");
return sd_bus_error_set(ret_error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit type does not support process killing.");
if (IN_SET(whom, KILL_MAIN, KILL_MAIN_FAIL)) {
if (!main_pid)
@@ -4095,54 +4104,66 @@ int unit_kill(
/* Note: if we shall enqueue rather than kill we won't do this via the cgroup mechanism, since it
* doesn't really make much sense (and given that enqueued values are a relatively expensive
* resource, and we shouldn't allow us to be subjects for such allocation sprees) */
if (IN_SET(whom, KILL_ALL, KILL_ALL_FAIL) && code == SI_USER) {
if (IN_SET(whom, KILL_ALL, KILL_ALL_FAIL, KILL_CGROUP, KILL_CGROUP_FAIL) && code == SI_USER) {
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
if (crt && crt->cgroup_path) {
_cleanup_set_free_ Set *pid_set = NULL;
_cleanup_free_ char *joined = NULL;
const char *p;
if (empty_or_root(subgroup))
p = crt->cgroup_path;
else {
joined = path_join(crt->cgroup_path, subgroup);
if (!joined)
return -ENOMEM;
p = joined;
}
if (signo == SIGKILL) {
r = cg_kill_kernel_sigkill(crt->cgroup_path);
r = cg_kill_kernel_sigkill(p);
if (r >= 0) {
killed = true;
log_unit_info(u, "Killed unit cgroup with SIGKILL on client request.");
log_unit_info(u, "Killed unit cgroup '%s' with SIGKILL on client request.", p);
goto finish;
}
if (r != -EOPNOTSUPP) {
if (ret >= 0)
sd_bus_error_set_errnof(ret_error, r,
"Failed to kill unit cgroup: %m");
RET_GATHER(ret, log_unit_warning_errno(u, r, "Failed to kill unit cgroup: %m"));
RET_GATHER(ret, log_unit_warning_errno(u, r, "Failed to kill unit cgroup '%s': %m", p));
goto finish;
}
/* Fall back to manual enumeration */
} else {
/* Exclude the main/control pids from being killed via the cgroup if
* not SIGKILL */
} else if (IN_SET(whom, KILL_ALL, KILL_ALL_FAIL)) {
/* Exclude the main/control pids from being killed via the cgroup if not
* SIGKILL */
r = unit_pid_set(u, &pid_set);
if (r < 0)
return log_oom();
}
r = cg_kill_recursive(crt->cgroup_path, signo, 0, pid_set, kill_common_log, u);
r = cg_kill_recursive(p, signo, /* flags= */ 0, pid_set, kill_common_log, u);
if (r < 0 && !IN_SET(r, -ESRCH, -ENOENT)) {
if (ret >= 0)
sd_bus_error_set_errnof(
ret_error, r,
"Failed to send signal SIG%s to auxiliary processes: %m",
signal_to_string(signo));
"Failed to send signal SIG%s to processes in unit cgroup '%s': %m",
signal_to_string(signo), p);
RET_GATHER(ret, log_unit_warning_errno(
u, r,
"Failed to send signal SIG%s to auxiliary processes on client request: %m",
signal_to_string(signo)));
"Failed to send signal SIG%s to processes in unit cgroup '%s' on client request: %m",
signal_to_string(signo), p));
}
killed = killed || r >= 0;
killed = killed || r > 0;
}
}
finish:
/* If the "fail" versions of the operation are requested, then complain if the set of processes we killed is empty */
if (ret >= 0 && !killed && IN_SET(whom, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL))
if (ret >= 0 && !killed && IN_SET(whom, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL, KILL_CGROUP_FAIL))
return sd_bus_error_set_const(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "No matching processes to kill");
return ret;

View File

@@ -886,7 +886,7 @@ int unit_start(Unit *u, ActivationDetails *details);
int unit_stop(Unit *u);
int unit_reload(Unit *u);
int unit_kill(Unit *u, KillWhom w, int signo, int code, int value, sd_bus_error *ret_error);
int unit_kill(Unit *u, KillWhom w, const char *subgroup, int signo, int code, int value, sd_bus_error *ret_error);
void unit_notify_cgroup_oom(Unit *u, bool managed_oom);

View 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,

View 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);

View 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";

View File

@@ -63,7 +63,7 @@ static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) {
int r;
log_error("Test timeout when testing %s", unit->id);
r = unit_kill(unit, KILL_ALL, SIGKILL, SI_USER, 0, NULL);
r = unit_kill(unit, KILL_ALL, /* subgroup= */ NULL, SIGKILL, SI_USER, /* value= */ 0, /* ret_error= */ NULL);
if (r < 0)
log_error_errno(r, "Failed to kill %s, ignoring: %m", unit->id);

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
at_exit() {
loginctl disable-linger testuser ||:
run0 -u testuser systemctl --user stop subgroup-test.service ||:
}
trap at_exit EXIT
loginctl enable-linger testuser
run0 -u testuser systemd-run --user --unit=subgroup-test.service sleep infinity
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=waldo
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=/waldo
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=waldo/knurz
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=/waldo/knurz
(! systemctl kill user@"$(id -u testuser)".service --kill-subgroup=waldo --kill-whom=cgroup-fail)
(! systemctl kill user@"$(id -u testuser)".service --kill-subgroup=/waldo --kill-whom=cgroup-fail)
(! systemctl kill user@"$(id -u testuser)".service --kill-subgroup=waldo/knurz --kill-whom=cgroup-fail)
(! systemctl kill user@"$(id -u testuser)".service --kill-subgroup=/waldo/knurz --kill-whom=cgroup-fail)
run0 -u testuser systemctl --user is-active subgroup-test.service
(! systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgroup-test.service/waldo --kill-whom=cgroup-fail)
run0 -u testuser systemctl --user is-active subgroup-test.service
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgroup-test.service/waldo
run0 -u testuser systemctl --user is-active subgroup-test.service
systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgroup-test.service --kill-whom=cgroup-fail
timeout 60 bash -c 'while run0 -u testuser systemctl --user is-active subgroup-test.service ; do sleep 1 ; done'