diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 1592d046ad..5bae473856 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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 { + + @@ -1286,6 +1292,13 @@ node /org/freedesktop/systemd1 { ExecStop= and is spawned in parallel to the main daemon process in order to shut it down. + QueueSignalUnit() is similar to KillUnit() but may be + used to enqueue a POSIX Realtime Signal (i.e. SIGRTMIN+… and + SIGRTMAX-…) to the selected process(es). Takes the same paramaters as + KillUnit() with one additional argument: an integer that is passed in the + sival_int value accompanying the queued signal. See sigqueue3 for + details. + GetJob() returns the job object path for a specific job, identified by its id. @@ -1731,7 +1744,8 @@ node /org/freedesktop/systemd1 { 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 (StartUnit(), StopUnit(), KillUnit(), - RestartUnit() and similar, SetProperty()) require + QueueSignalUnit(), RestartUnit() and similar, + SetProperty()) require org.freedesktop.systemd1.manage-units. Operations which modify unit file enablement state (EnableUnitFiles(), DisableUnitFiles(), EnableUnitFilesWithFlags(), DisableUnitFilesWithFlags(), @@ -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 { + + @@ -2302,12 +2321,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { Start(), Stop(), Reload(), Restart(), TryRestart(), ReloadOrRestart(), ReloadOrTryRestart(), - Kill(), ResetFailed(), and - SetProperties() implement the same operation as the respective methods on the + Kill(), QueueSignal(), ResetFailed(), + and SetProperties() implement the same operation as the respective methods on the Manager 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 GetUnit() 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 GetUnit() call to get the unit object for a + specific unit name. Calling the methods on the Manager object is hence a round trip optimization. diff --git a/man/systemctl.xml b/man/systemctl.xml index 04b2e9813d..dfaca638c2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -501,10 +501,11 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err kill PATTERN… - Send a signal to one or more processes of the - unit. Use to select which - process to kill. Use to select - the signal to send. + Send a UNIX process signal to one or more processes of the unit. Use + to select which process to send the signal to. Use + to select the signal to send. Combine with + to enqueue a POSIX Realtime Signal with an associated + value. @@ -2124,40 +2125,46 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err - When used with kill, choose which - processes to send a signal to. Must be one of - , or - 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 - ExecStartPre=, - ExecStop= or - ExecReload= 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 - Type=forking, the initial process started - by the manager for ExecStart= 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 ExecStart= is always the main process - itself. A service unit consists of zero or one main process, - zero or one control process plus any number of additional - processes. Not all unit types manage processes of these - types however. For example, for mount units, control processes - are defined (which are the invocations of - &MOUNT_PATH; and - &UMOUNT_PATH;), but no main process - is defined. If omitted, defaults to + When used with kill, choose which processes to send a UNIX process signal + to. Must be one of , or 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 ExecStartPre=, ExecStop= or + ExecReload= 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 Type=forking, the initial process started by the manager for + ExecStart= 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 + ExecStart= is always the main process itself. A service unit consists of zero or + one main process, zero or one control process plus any number of additional processes. Not all unit + types manage processes of these types however. For example, for mount units, control processes are + defined (which are the invocations of &MOUNT_PATH; and + &UMOUNT_PATH;), but no main process is defined. If omitted, defaults to . + + INT + + If used with the kill 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. or + ), and ensures the signals are generated via the sigqueue3 + system call, rather than kill3. The + specified value must be a 32bit signed integer, and may be specified either in decimal, in + hexademical (if prefixed with 0x), octal (if prefixed with 0o) + or binary (if prefixed with 0b) + + 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. will only + affect main and control processes but no other processes. + + diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml index dd76cc0fb6..5a154686f5 100644 --- a/man/systemd-notify.xml +++ b/man/systemd-notify.xml @@ -25,6 +25,9 @@ systemd-notify OPTIONS VARIABLE=VALUE + + systemd-notify --exec OPTIONS VARIABLE=VALUE ; CMDLINE + @@ -174,6 +177,20 @@ this option set is prone to race conditions in all other cases. + + + + If specified systemd-notify 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 ; 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. + + Note that many shells interpret ; as their own separator for command lines, + hence when systemd-notify is invoked from a shell the semicolon must usually be + escaped as \;. + + diff --git a/src/basic/strv.c b/src/basic/strv.c index 2b7a61d442..9b4a7663a9 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -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; diff --git a/src/basic/strv.h b/src/basic/strv.h index 1f8da85fcc..71ff3a4edf 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index c4f205bc42..7d6f6bfc0f 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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, diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 1ef98da6fd..8f851cbceb 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -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, diff --git a/src/core/mount.c b/src/core/mount.c index 993dce5118..95bd04f6e9 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -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) { diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index 1cef421db8..7f44c32b83 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -246,6 +246,10 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="KillUnit"/> + + @@ -390,6 +394,10 @@ send_interface="org.freedesktop.systemd1.Unit" send_member="Kill"/> + + diff --git a/src/core/scope.c b/src/core/scope.c index 914d1cc744..62f23a9e1e 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -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) { diff --git a/src/core/service.c b/src/core/service.c index 9ad3c3d995..dc5ccfd239 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -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) { diff --git a/src/core/slice.c b/src/core/slice.c index 4824a300d0..eb0ba5e763 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -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) { diff --git a/src/core/socket.c b/src/core/socket.c index 409d415d8d..8241ba050b 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -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) { diff --git a/src/core/swap.c b/src/core/swap.c index 2d25014e5f..ab901a2cd1 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -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) { diff --git a/src/core/unit.c b/src/core/unit.c index 78a1f72f7f..be7b19877f 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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) { diff --git a/src/core/unit.h b/src/core/unit.h index 58417ebd0e..3f8377fbf6 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -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); diff --git a/src/notify/notify.c b/src/notify/notify.c index b1348383f5..7320ffebde 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -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; } diff --git a/src/systemctl/systemctl-kill.c b/src/systemctl/systemctl-kill.c index b3ae8bbcc2..c4c6096dc3 100644 --- a/src/systemctl/systemctl-kill.c +++ b/src/systemctl/systemctl-kill.c @@ -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) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 4e7fd0460c..4732398dd7 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -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) diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 9f9b8faa69..59da9e3ddb 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -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; diff --git a/src/test/test-execute.c b/src/test/test-execute.c index f8c64b6042..f6d05afc1b 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -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); diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 5c03eaa960..0f08dd4615 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -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); diff --git a/test/TEST-78-SIGQUEUE/Makefile b/test/TEST-78-SIGQUEUE/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-78-SIGQUEUE/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-78-SIGQUEUE/test.sh b/test/TEST-78-SIGQUEUE/test.sh new file mode 100755 index 0000000000..61e8e7d1c4 --- /dev/null +++ b/test/TEST-78-SIGQUEUE/test.sh @@ -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 "$@" diff --git a/test/units/testsuite-78.service b/test/units/testsuite-78.service new file mode 100644 index 0000000000..05f3effdb6 --- /dev/null +++ b/test/units/testsuite-78.service @@ -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 diff --git a/test/units/testsuite-78.sh b/test/units/testsuite-78.sh new file mode 100755 index 0000000000..1680166553 --- /dev/null +++ b/test/units/testsuite-78.sh @@ -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