Merge pull request #26447 from poettering/sigqueue

pid1/systemctl: add ability to enqueue POSIX RT sigs with associated value to service processes
This commit is contained in:
Lennart Poettering
2023-02-17 10:51:58 +01:00
committed by GitHub
26 changed files with 442 additions and 130 deletions
+25 -6
View File
@@ -112,6 +112,10 @@ node /org/freedesktop/systemd1 {
KillUnit(in s name,
in s whom,
in i signal);
QueueSignalUnit(in s name,
in s whom,
in i signal,
in i value);
CleanUnit(in s name,
in as mask);
FreezeUnit(in s name);
@@ -826,6 +830,8 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-method" generated="True" extra-ref="KillUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="QueueSignalUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="CleanUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="FreezeUnit()"/>
@@ -1286,6 +1292,13 @@ node /org/freedesktop/systemd1 {
<varname>ExecStop=</varname> and is spawned in parallel to the main daemon process in order to shut it
down.</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
<constant>SIGRTMAX-…</constant>) to the selected process(es). Takes the same paramaters as
<function>KillUnit()</function> with one additional argument: an integer that is passed in the
<varname>sival_int</varname> value accompanying the queued signal. See <citerefentry project="man-pages"><refentrytitle>sigqueue</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
details.</para>
<para><function>GetJob()</function> returns the job object path for a specific job, identified by its
id.</para>
@@ -1731,7 +1744,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>RestartUnit()</function> and similar, <function>SetProperty()</function>) require
<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>,
@@ -1778,6 +1792,9 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
out a(uosos) affected_jobs);
Kill(in s whom,
in i signal);
QueueSignal(in s whom,
in i signal,
in i value);
ResetFailed();
SetProperties(in b runtime,
in a(sv) properties);
@@ -2084,6 +2101,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="QueueSignal()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ResetFailed()"/>
<variablelist class="dbus-method" generated="True" extra-ref="SetProperties()"/>
@@ -2302,12 +2321,12 @@ 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>ResetFailed()</function>, and
<function>SetProperties()</function> implement the same operation as the respective methods on the
<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
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>
+41 -34
View File
@@ -501,10 +501,11 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<term><command>kill <replaceable>PATTERN</replaceable>…</command></term>
<listitem>
<para>Send a signal to one or more processes of the
unit. Use <option>--kill-whom=</option> to select which
process to kill. Use <option>--signal=</option> to select
the signal to send.</para>
<para>Send a UNIX process signal to one or more processes of the unit. Use
<option>--kill-whom=</option> to select which process to send the signal to. Use
<option>--signal=</option> to select the signal to send. Combine with
<option>--kill-value=</option> to enqueue a POSIX Realtime Signal with an associated
value.</para>
</listitem>
</varlistentry>
<varlistentry>
@@ -2124,40 +2125,46 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<term><option>--kill-whom=</option></term>
<listitem>
<para>When used with <command>kill</command>, choose which
processes to send a 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 <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
<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
<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>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--kill-value=</option><replaceable>INT</replaceable></term>
<listitem><para>If used with the <command>kill</command> command, enqueues a signal along with the
specified integer value parameter to the specified process(es). This operation is only available for
POSIX Realtime Signals (i.e. <option>--signal=SIGRTMIN+…</option> or
<option>--signal=SIGRTMAX-…</option>), and ensures the signals are generated via the <citerefentry
project='man-pages'><refentrytitle>sigqueue</refentrytitle><manvolnum>3</manvolnum></citerefentry>
system call, rather than <citerefentry
project='man-pages'><refentrytitle>kill</refentrytitle><manvolnum>3</manvolnum></citerefentry>. The
specified value must be a 32bit signed integer, and may be specified either in decimal, in
hexademical (if prefixed with <literal>0x</literal>), octal (if prefixed with <literal>0o</literal>)
or binary (if prefixed with <literal>0b</literal>)</para>
<para>If this option is used the signal will only be enqueued on the control or main process of the
unit, never on other processes belonging to the unit, i.e. <option>--kill-whom=all</option> will only
affect main and control processes but no other processes.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="signal" />
<varlistentry>
+17
View File
@@ -25,6 +25,9 @@
<cmdsynopsis>
<command>systemd-notify <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-notify --exec <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg> ; <arg rep="repeat">CMDLINE</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@@ -174,6 +177,20 @@
this option set is prone to race conditions in all other cases.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--exec</option></term>
<listitem><para>If specified <command>systemd-notify</command> will execute another command line
after it completed its operation, replacing its own process. If used, the list of assignments to
include in the message sent must be followed by a <literal>;</literal> character (as separate
argument), followed by the command line to execute. This permits "chaining" of commands, i.e. issuing
one operation, followed immediately by another, without changing PIDs.</para>
<para>Note that many shells interpret <literal>;</literal> as their own separator for command lines,
hence when <command>systemd-notify</command> is invoked from a shell the semicolon must usually be
escaped as <literal>\;</literal>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
+8 -2
View File
@@ -77,20 +77,26 @@ char** strv_free_erase(char **l) {
return mfree(l);
}
char** strv_copy(char * const *l) {
char** strv_copy_n(char * const *l, size_t m) {
_cleanup_strv_free_ char **result = NULL;
char **k;
result = new(char*, strv_length(l) + 1);
result = new(char*, MIN(strv_length(l), m) + 1);
if (!result)
return NULL;
k = result;
STRV_FOREACH(i, l) {
if (m == 0)
break;
*k = strdup(*i);
if (!*k)
return NULL;
k++;
if (m != SIZE_MAX)
m--;
}
*k = NULL;
+4 -1
View File
@@ -29,7 +29,10 @@ char** strv_free_erase(char **l);
DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
char** strv_copy(char * const *l);
char** strv_copy_n(char * const *l, size_t n);
static inline char** strv_copy(char * const *l) {
return strv_copy_n(l, SIZE_MAX);
}
size_t strv_length(char * const *l) _pure_;
int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
+5
View File
@@ -3034,6 +3034,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_NO_RESULT,
method_kill_unit,
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,
method_kill_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("CleanUnit",
SD_BUS_ARGS("s", name, "as", mask),
SD_BUS_NO_RESULT,
+22 -3
View File
@@ -513,10 +513,11 @@ int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_
int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Unit *u = ASSERT_PTR(userdata);
int32_t value = 0;
const char *swho;
int32_t signo;
KillWho who;
int r;
int r, code;
assert(message);
@@ -528,17 +529,30 @@ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *
if (r < 0)
return r;
if (startswith(sd_bus_message_get_member(message), "QueueSignal")) {
r = sd_bus_message_read(message, "i", &value);
if (r < 0)
return r;
code = SI_QUEUE;
} else
code = SI_USER;
if (isempty(swho))
who = KILL_ALL;
else {
who = kill_who_from_string(swho);
if (who < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument %s", swho);
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument: %s", swho);
}
if (!SIGNAL_VALID(signo))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range.");
if (code == SI_QUEUE && !((signo >= SIGRTMIN) && (signo <= SIGRTMAX)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Value parameter only accepted for realtime signals (SIGRTMIN…SIGRTMAX), refusing for signal SIG%s.", signal_to_string(signo));
r = bus_verify_manage_units_async_full(
u,
"kill",
@@ -552,7 +566,7 @@ 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, who, signo, error);
r = unit_kill(u, who, signo, code, value, error);
if (r < 0)
return r;
@@ -984,6 +998,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("QueueSignal",
SD_BUS_ARGS("s", whom, "i", signal, "i", value),
SD_BUS_NO_RESULT,
bus_unit_method_kill,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetFailed",
NULL,
NULL,
+2 -2
View File
@@ -2158,12 +2158,12 @@ static void mount_reset_failed(Unit *u) {
m->clean_result = MOUNT_SUCCESS;
}
static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
static int mount_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
Mount *m = MOUNT(u);
assert(m);
return unit_kill_common(u, who, signo, -1, m->control_pid, error);
return unit_kill_common(u, who, signo, code, value, -1, m->control_pid, error);
}
static int mount_control_pid(Unit *u) {
+8
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="QueueSignalUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="ResetFailedUnit"/>
@@ -390,6 +394,10 @@
send_interface="org.freedesktop.systemd1.Unit"
send_member="Kill"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Unit"
send_member="QueueSignal"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Unit"
send_member="ResetFailed"/>
+2 -2
View File
@@ -530,8 +530,8 @@ static void scope_reset_failed(Unit *u) {
s->result = SCOPE_SUCCESS;
}
static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
return unit_kill_common(u, who, signo, -1, -1, error);
static int scope_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
return unit_kill_common(u, who, signo, code, value, -1, -1, error);
}
static int scope_get_timeout(Unit *u, usec_t *timeout) {
+2 -2
View File
@@ -4596,12 +4596,12 @@ static void service_reset_failed(Unit *u) {
s->flush_n_restarts = false;
}
static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
static int service_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
Service *s = SERVICE(u);
assert(s);
return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error);
return unit_kill_common(u, who, signo, code, value, s->main_pid, s->control_pid, error);
}
static int service_main_pid(Unit *u) {
+2 -2
View File
@@ -247,8 +247,8 @@ static int slice_stop(Unit *u) {
return 1;
}
static int slice_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
return unit_kill_common(u, who, signo, -1, -1, error);
static int slice_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
return unit_kill_common(u, who, signo, code, value, -1, -1, error);
}
static int slice_serialize(Unit *u, FILE *f, FDSet *fds) {
+2 -2
View File
@@ -3299,8 +3299,8 @@ static void socket_trigger_notify(Unit *u, Unit *other) {
socket_set_state(s, SOCKET_RUNNING);
}
static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error);
static int socket_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
return unit_kill_common(u, who, signo, code, value, -1, SOCKET(u)->control_pid, error);
}
static int socket_get_timeout(Unit *u, usec_t *timeout) {
+2 -2
View File
@@ -1480,8 +1480,8 @@ static void swap_reset_failed(Unit *u) {
s->clean_result = SWAP_SUCCESS;
}
static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error);
static int swap_kill(Unit *u, KillWho who, int signo, int code, int value, sd_bus_error *error) {
return unit_kill_common(u, who, signo, code, value, -1, SWAP(u)->control_pid, error);
}
static int swap_get_timeout(Unit *u, usec_t *timeout) {
+96 -54
View File
@@ -3785,15 +3785,16 @@ bool unit_will_restart(Unit *u) {
return UNIT_VTABLE(u)->will_restart(u);
}
int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) {
int unit_kill(Unit *u, KillWho w, int signo, int code, int value, sd_bus_error *error) {
assert(u);
assert(w >= 0 && w < _KILL_WHO_MAX);
assert(SIGNAL_VALID(signo));
assert(IN_SET(code, SI_USER, SI_QUEUE));
if (!UNIT_VTABLE(u)->kill)
return -EOPNOTSUPP;
return UNIT_VTABLE(u)->kill(u, w, signo, error);
return UNIT_VTABLE(u)->kill(u, w, signo, code, value, error);
}
void unit_notify_cgroup_oom(Unit *u, bool managed_oom) {
@@ -3838,21 +3839,48 @@ static int kill_common_log(pid_t pid, int signo, void *userdata) {
return 1;
}
static int kill_or_sigqueue(pid_t pid, int signo, int code, int value) {
assert(pid > 0);
assert(SIGNAL_VALID(signo));
switch (code) {
case SI_USER:
log_debug("Killing " PID_FMT " with signal SIG%s.", pid, signal_to_string(signo));
return RET_NERRNO(kill(pid, signo));
case SI_QUEUE:
log_debug("Enqueuing value %i to " PID_FMT " on signal SIG%s.", value, pid, signal_to_string(signo));
return RET_NERRNO(sigqueue(pid, signo, (const union sigval) { .sival_int = value }));
default:
assert_not_reached();
}
}
int unit_kill_common(
Unit *u,
KillWho who,
int signo,
int code,
int value,
pid_t main_pid,
pid_t control_pid,
sd_bus_error *error) {
int r = 0;
bool killed = false;
int ret = 0, r;
/* This is the common implementation for explicit user-requested killing of unit processes, shared by
* various unit types. Do not confuse with unit_kill_context(), which is what we use when we want to
* stop a service ourselves. */
assert(u);
assert(who >= 0);
assert(who < _KILL_WHO_MAX);
assert(SIGNAL_VALID(signo));
assert(IN_SET(code, SI_USER, SI_QUEUE));
if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) {
if (main_pid < 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type));
@@ -3867,71 +3895,85 @@ int unit_kill_common(
return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
}
if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL))
if (control_pid > 0) {
_cleanup_free_ char *comm = NULL;
(void) get_process_comm(control_pid, &comm);
if (control_pid > 0 &&
IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) {
_cleanup_free_ char *comm = NULL;
(void) get_process_comm(control_pid, &comm);
r = kill_or_sigqueue(control_pid, signo, code, value);
if (r < 0) {
ret = r;
/* Report this failure both to the logs and to the client */
sd_bus_error_set_errnof(
error, r,
"Failed to send signal SIG%s to control process " PID_FMT " (%s): %m",
signal_to_string(signo), control_pid, strna(comm));
log_unit_warning_errno(
u, r,
"Failed to send signal SIG%s to control process " PID_FMT " (%s) on client request: %m",
signal_to_string(signo), control_pid, strna(comm));
} else {
log_unit_info(u, "Sent signal SIG%s to control process " PID_FMT " (%s) on client request.",
signal_to_string(signo), control_pid, strna(comm));
killed = true;
}
}
if (main_pid > 0 &&
IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) {
_cleanup_free_ char *comm = NULL;
(void) get_process_comm(main_pid, &comm);
r = kill_or_sigqueue(main_pid, signo, code, value);
if (r < 0) {
if (ret == 0) {
ret = r;
if (kill(control_pid, signo) < 0) {
/* Report this failure both to the logs and to the client */
sd_bus_error_set_errnof(
error, errno,
"Failed to send signal SIG%s to control process " PID_FMT " (%s): %m",
signal_to_string(signo), control_pid, strna(comm));
r = log_unit_warning_errno(
u, errno,
"Failed to send signal SIG%s to control process " PID_FMT " (%s) on client request: %m",
signal_to_string(signo), control_pid, strna(comm));
} else {
log_unit_info(u, "Sent signal SIG%s to control process " PID_FMT " (%s) on client request.",
signal_to_string(signo), control_pid, strna(comm));
killed = true;
}
}
if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL))
if (main_pid > 0) {
_cleanup_free_ char *comm = NULL;
(void) get_process_comm(main_pid, &comm);
if (kill(main_pid, signo) < 0) {
if (r == 0)
sd_bus_error_set_errnof(
error, errno,
"Failed to send signal SIG%s to main process " PID_FMT " (%s): %m",
signal_to_string(signo), main_pid, strna(comm));
r = log_unit_warning_errno(
u, errno,
"Failed to send signal SIG%s to main process " PID_FMT " (%s) on client request: %m",
error, r,
"Failed to send signal SIG%s to main process " PID_FMT " (%s): %m",
signal_to_string(signo), main_pid, strna(comm));
} else {
log_unit_info(u, "Sent signal SIG%s to main process " PID_FMT " (%s) on client request.",
signal_to_string(signo), main_pid, strna(comm));
killed = true;
}
}
if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) {
log_unit_warning_errno(
u, r,
"Failed to send signal SIG%s to main process " PID_FMT " (%s) on client request: %m",
signal_to_string(signo), main_pid, strna(comm));
} else {
log_unit_info(u, "Sent signal SIG%s to main process " PID_FMT " (%s) on client request.",
signal_to_string(signo), main_pid, strna(comm));
killed = true;
}
}
/* 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(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path && code == SI_USER) {
_cleanup_set_free_ Set *pid_set = NULL;
int q;
/* Exclude the main/control pids from being killed via the cgroup */
pid_set = unit_pid_set(main_pid, control_pid);
if (!pid_set)
return log_oom();
q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, 0, pid_set, kill_common_log, u);
if (q < 0) {
if (!IN_SET(q, -ESRCH, -ENOENT)) {
if (r == 0)
r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, 0, pid_set, kill_common_log, u);
if (r < 0) {
if (!IN_SET(r, -ESRCH, -ENOENT)) {
if (ret == 0) {
ret = r;
sd_bus_error_set_errnof(
error, q,
error, r,
"Failed to send signal SIG%s to auxiliary processes: %m",
signal_to_string(signo));
}
r = log_unit_warning_errno(
u, q,
log_unit_warning_errno(
u, r,
"Failed to send signal SIG%s to auxiliary processes on client request: %m",
signal_to_string(signo));
}
@@ -3940,10 +3982,10 @@ int unit_kill_common(
}
/* If the "fail" versions of the operation are requested, then complain if the set of processes we killed is empty */
if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL))
if (ret == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL))
return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No matching processes to kill");
return r;
return ret;
}
int unit_following_set(Unit *u, Set **s) {
+3 -3
View File
@@ -619,7 +619,7 @@ typedef struct UnitVTable {
int (*stop)(Unit *u);
int (*reload)(Unit *u);
int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error);
int (*kill)(Unit *u, KillWho w, int signo, int code, int value, sd_bus_error *error);
/* Clear out the various runtime/state/cache/logs/configuration data */
int (*clean)(Unit *u, ExecCleanMask m);
@@ -889,8 +889,8 @@ int unit_start(Unit *u, ActivationDetails *details);
int unit_stop(Unit *u);
int unit_reload(Unit *u);
int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error);
int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, sd_bus_error *error);
int unit_kill(Unit *u, KillWho w, int signo, int code, int value, sd_bus_error *error);
int unit_kill_common(Unit *u, KillWho who, int signo, int code, int value, pid_t main_pid, pid_t control_pid, sd_bus_error *error);
void unit_notify_cgroup_oom(Unit *u, bool managed_oom);
+56 -3
View File
@@ -31,6 +31,11 @@ static bool arg_booted = false;
static uid_t arg_uid = UID_INVALID;
static gid_t arg_gid = GID_INVALID;
static bool arg_no_block = false;
static char **arg_env = NULL;
static char **arg_exec = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -41,6 +46,7 @@ static int help(void) {
return log_oom();
printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n"
"%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; CMDLINE...\n"
"\n%sNotify the init system about service status updates.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
@@ -53,8 +59,10 @@ static int help(void) {
" --status=TEXT Set status text\n"
" --booted Check if the system was booted up with systemd\n"
" --no-block Do not wait until operation finished\n"
" --exec Execute command line separated by ';' once done\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
@@ -93,7 +101,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_STATUS,
ARG_BOOTED,
ARG_UID,
ARG_NO_BLOCK
ARG_NO_BLOCK,
ARG_EXEC,
};
static const struct option options[] = {
@@ -107,10 +116,12 @@ static int parse_argv(int argc, char *argv[]) {
{ "booted", no_argument, NULL, ARG_BOOTED },
{ "uid", required_argument, NULL, ARG_UID },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "exec", no_argument, NULL, ARG_EXEC },
{}
};
int c, r;
bool do_exec = false;
int c, r, n_env;
assert(argc >= 0);
assert(argv);
@@ -183,6 +194,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_no_block = true;
break;
case ARG_EXEC:
do_exec = true;
break;
case '?':
return -EINVAL;
@@ -202,6 +217,32 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
if (do_exec) {
int i;
for (i = optind; i < argc; i++)
if (streq(argv[i], ";"))
break;
if (i >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing.");
if (i+1 == argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing");
arg_exec = strv_copy_n(argv + i + 1, argc - i - 1);
if (!arg_exec)
return log_oom();
n_env = i - optind;
} else
n_env = argc - optind;
if (n_env > 0) {
arg_env = strv_copy_n(argv + optind, n_env);
if (!arg_env)
return log_oom();
}
return 1;
}
@@ -263,7 +304,7 @@ static int run(int argc, char* argv[]) {
our_env[i++] = NULL;
final_env = strv_env_merge(our_env, argv + optind);
final_env = strv_env_merge(our_env, arg_env);
if (!final_env)
return log_oom();
@@ -313,6 +354,18 @@ static int run(int argc, char* argv[]) {
"No status data could be sent: $NOTIFY_SOCKET was not set");
}
if (arg_exec) {
_cleanup_free_ char *cmdline = NULL;
execvp(arg_exec[0], arg_exec);
cmdline = strv_join(arg_exec, " ");
if (!cmdline)
return log_oom();
return log_error_errno(errno, "Failed to execute command line: %s", cmdline);
}
return 0;
}
+19 -11
View File
@@ -8,7 +8,7 @@
int verb_kill(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
char *kill_whom = NULL;
const char *kill_whom;
sd_bus *bus;
int r, q;
@@ -18,12 +18,11 @@ int verb_kill(int argc, char *argv[], void *userdata) {
polkit_agent_open_maybe();
if (!arg_kill_whom)
arg_kill_whom = "all";
kill_whom = arg_kill_whom ?: "all";
/* --fail was specified */
if (streq(arg_job_mode(), "fail"))
kill_whom = strjoina(arg_kill_whom, "-fail");
kill_whom = strjoina(kill_whom, "-fail");
r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
if (r < 0)
@@ -32,13 +31,22 @@ int verb_kill(int argc, char *argv[], void *userdata) {
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
q = bus_call_method(
bus,
bus_systemd_mgr,
"KillUnit",
&error,
NULL,
"ssi", *name, kill_whom ?: arg_kill_whom, arg_signal);
if (arg_kill_value_set)
q = bus_call_method(
bus,
bus_systemd_mgr,
"QueueSignalUnit",
&error,
NULL,
"ssii", *name, kill_whom, arg_signal, arg_kill_value);
else
q = bus_call_method(
bus,
bus_systemd_mgr,
"KillUnit",
&error,
NULL,
"ssi", *name, kill_whom, arg_signal);
if (q < 0) {
log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q));
if (r == 0)
+29
View File
@@ -97,6 +97,8 @@ UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL;
char **arg_wall = NULL;
const char *arg_kill_whom = NULL;
int arg_signal = SIGTERM;
int arg_kill_value;
bool arg_kill_value_set = false;
char *arg_root = NULL;
char *arg_image = NULL;
usec_t arg_when = 0;
@@ -273,6 +275,7 @@ static int systemctl_help(void) {
" sleeping, or hibernating\n"
" -i Shortcut for --check-inhibitors=no\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"
" --what=RESOURCES Which types of resources to remove\n"
" --now Start or stop unit after enabling or disabling it\n"
@@ -419,6 +422,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_IMAGE,
ARG_NO_RELOAD,
ARG_KILL_WHOM,
ARG_KILL_VALUE,
ARG_NO_ASK_PASSWORD,
ARG_FAILED,
ARG_RUNTIME,
@@ -479,6 +483,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "force", no_argument, NULL, 'f' },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
{ "kill-value", required_argument, NULL, ARG_KILL_VALUE },
{ "signal", required_argument, NULL, 's' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "host", required_argument, NULL, 'H' },
@@ -723,6 +728,30 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_kill_whom = optarg;
break;
case ARG_KILL_VALUE: {
unsigned u;
if (isempty(optarg)) {
arg_kill_value_set = false;
return 0;
}
/* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */
r = safe_atou_full(optarg, 0, &u);
if (r < 0)
/* If this didn't work, try as signed integer, without those prefixes */
r = safe_atoi(optarg, &arg_kill_value);
else if (u > INT_MAX)
r = -ERANGE;
else
arg_kill_value = (int) u;
if (r < 0)
return log_error_errno(r, "Unable to parse signal queue value: %s", optarg);
arg_kill_value_set = true;
break;
}
case 's':
r = parse_signal_argument(optarg, &arg_signal);
if (r <= 0)
+2
View File
@@ -77,6 +77,8 @@ extern UnitFilePresetMode arg_preset_mode;
extern char **arg_wall;
extern const char *arg_kill_whom;
extern int arg_signal;
extern int arg_kill_value;
extern bool arg_kill_value_set;
extern char *arg_root;
extern usec_t arg_when;
extern const char *arg_reboot_argument;
+1 -1
View File
@@ -73,7 +73,7 @@ static void wait_for_service_finish(Manager *m, Unit *unit) {
n = now(CLOCK_MONOTONIC);
if (ts + timeout < n) {
log_error("Test timeout when testing %s", unit->id);
r = unit_kill(unit, KILL_ALL, SIGKILL, NULL);
r = unit_kill(unit, KILL_ALL, SIGKILL, SI_USER, 0, NULL);
if (r < 0)
log_error_errno(r, "Failed to kill %s: %m", unit->id);
exit(EXIT_FAILURE);
+40
View File
@@ -954,4 +954,44 @@ TEST(strv_extend_join) {
assert_se(streq(v[1], "ABC=QER"));
}
TEST(strv_copy_n) {
char **x = STRV_MAKE("a", "b", "c", "d", "e");
_cleanup_strv_free_ char **l = NULL;
l = strv_copy_n(x, 0);
assert_se(strv_equal(l, NULL));
strv_free(l);
l = strv_copy_n(x, 0);
assert_se(strv_equal(l, (char**) { NULL }));
strv_free(l);
l = strv_copy_n(x, 1);
assert_se(strv_equal(l, STRV_MAKE("a")));
strv_free(l);
l = strv_copy_n(x, 2);
assert_se(strv_equal(l, STRV_MAKE("a", "b")));
strv_free(l);
l = strv_copy_n(x, 3);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c")));
strv_free(l);
l = strv_copy_n(x, 4);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d")));
strv_free(l);
l = strv_copy_n(x, 5);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
strv_free(l);
l = strv_copy_n(x, 6);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
strv_free(l);
l = strv_copy_n(x, SIZE_MAX);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
}
DEFINE_TEST_MAIN(LOG_INFO);
+1
View File
@@ -0,0 +1 @@
../TEST-01-BASIC/Makefile
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
TEST_DESCRIPTION="Test queue signal logic"
# shellcheck source=test/test-functions
. "$TEST_BASE_DIR/test-functions"
do_test "$@"
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-78-SIGQUEUE
[Service]
Type=oneshot
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -ex
set -o pipefail
if ! env --block-signal=SIGUSR1 true 2> /dev/null ; then
echo "env tool too old, can't block signals, skipping test." >&2
echo OK >/testok
exit 0
fi
systemd-analyze log-level debug
UNIT="test-sigqueue-$RANDOM.service"
systemd-run -u "$UNIT" -p Type=notify -p DynamicUser=1 -- env --block-signal=SIGRTMIN+7 systemd-notify --exec --ready \; sleep infinity
systemctl kill --kill-whom=main --kill-value=4 --signal=SIGRTMIN+7 "$UNIT"
systemctl kill --kill-whom=main --kill-value=4 --signal=SIGRTMIN+7 "$UNIT"
systemctl kill --kill-whom=main --kill-value=7 --signal=SIGRTMIN+7 "$UNIT"
systemctl kill --kill-whom=main --kill-value=16 --signal=SIGRTMIN+7 "$UNIT"
systemctl kill --kill-whom=main --kill-value=32 --signal=SIGRTMIN+7 "$UNIT"
systemctl kill --kill-whom=main --kill-value=16 --signal=SIGRTMIN+7 "$UNIT"
# We simply check that six signals are queued now. There's no easy way to check
# from shell wich ones those are, hence we don't check that.
P=$(systemctl show -P MainPID "$UNIT")
test "$(grep SigQ: /proc/"$P"/status | cut -d: -f2 | cut -d/ -f1)" -eq 6
systemctl stop $UNIT
systemd-analyze log-level info
echo OK >/testok
exit 0