mirror of
https://github.com/systemd/systemd.git
synced 2026-08-04 15:10:33 +00:00
add api to kill subcgroups of units (#38102)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
39
test/units/TEST-07-PID1.subgroup-kill.sh
Executable file
39
test/units/TEST-07-PID1.subgroup-kill.sh
Executable 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'
|
||||
Reference in New Issue
Block a user