diff --git a/man/systemctl.xml b/man/systemctl.xml
index 2eb153e73..ca654ca5b 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -234,6 +234,57 @@
changes.
+
+
+
+ When used with
+ kill, choose the
+ mode how to kill the selected
+ processes. Must be one of
+ ,
+ or
+ to select
+ whether to kill the entire control
+ group, the process group or only the
+ selected process itself. If ommitted
+ defaults to
+ if
+ is
+ set, or
+ otherwise. You probably never need to
+ use this switch.
+
+
+
+
+
+ When used with
+ kill, choose which
+ processes to kill. Must be one of
+ ,
+ or
+ to select whether
+ to kill only the main process of the
+ unit, the control process or all
+ processes of the unit. If ommitted
+ defaults to
+ .
+
+
+
+
+
+
+ When used with
+ kill, choose which
+ signal to send to selected
+ processes. Must be one of the well
+ know signal specifiers such as
+ SIGTERM, SIGINT or SIGSTOP. If
+ ommitted defaults to
+ .
+
+
@@ -358,6 +409,18 @@
systemd.unit5
for details.
+
+ kill [NAME...]
+
+ Send a signal to one
+ or more processes of the unit. Use
+ to select
+ which process to kill. Use
+ to
+ select the kill mode and
+ to select
+ the signal to send.
+
is-active [NAME...]
diff --git a/src/bus-errors.h b/src/bus-errors.h
index d6ccd7407..82d4e99ee 100644
--- a/src/bus-errors.h
+++ b/src/bus-errors.h
@@ -43,6 +43,7 @@
#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting"
#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic"
#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
+#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess"
static inline const char *bus_error(const DBusError *e, int r) {
if (e && e->message)
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
index 52638435c..c1950319f 100644
--- a/src/dbus-manager.c
+++ b/src/dbus-manager.c
@@ -84,6 +84,12 @@
" \n" \
" \n" \
" \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
" \n" \
" \n" \
" \n" \
@@ -430,6 +436,40 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrTryRestartUnit")) {
reload_if_possible = true;
job_type = JOB_TRY_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KillUnit")) {
+ const char *name, *swho, *smode;
+ int32_t signo;
+ Unit *u;
+ KillMode mode;
+ KillWho who;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &swho,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INT32, &signo,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, connection, message, &error, -EINVAL);
+
+ if ((mode = kill_mode_from_string(smode)) < 0 ||
+ (who = kill_who_from_string(swho)) < 0 ||
+ signo <= 0 ||
+ signo >= _NSIG)
+ return bus_send_error_reply(m, connection, message, &error, -EINVAL);
+
+ if (!(u = manager_get_unit(m, name))) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+ return bus_send_error_reply(m, connection, message, &error, -ENOENT);
+ }
+
+ if ((r = unit_kill(u, who, mode, signo, &error)) < 0)
+ return bus_send_error_reply(m, connection, message, &error, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) {
uint32_t id;
Job *j;
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index 45eba8ab5..47aa8d338 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -367,6 +367,34 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
reload_if_possible = true;
job_type = JOB_TRY_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) {
+ const char *swho, *smode;
+ int32_t signo;
+ KillMode mode;
+ KillWho who;
+ int r;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &swho,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INT32, &signo,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, connection, message, &error, -EINVAL);
+
+ if ((mode = kill_mode_from_string(smode)) < 0 ||
+ (who = kill_who_from_string(swho)) < 0 ||
+ signo <= 0 ||
+ signo >= _NSIG)
+ return bus_send_error_reply(m, connection, message, &error, -EINVAL);
+
+ if ((r = unit_kill(u, who, mode, signo, &error)) < 0)
+ return bus_send_error_reply(m, connection, message, &error, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) {
unit_reset_failed(u);
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index 800498b8f..94e084f6e 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -56,7 +56,12 @@
" \n" \
" \n" \
" \n" \
- " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
+ " \n" \
" \n" \
" \n" \
" \n" \
diff --git a/src/execute.c b/src/execute.c
index b5afa6810..48e55ea4c 100644
--- a/src/execute.c
+++ b/src/execute.c
@@ -1797,6 +1797,8 @@ static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
[EXEC_INPUT_SOCKET] = "socket"
};
+DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
+
static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
[EXEC_OUTPUT_INHERIT] = "inherit",
[EXEC_OUTPUT_NULL] = "null",
@@ -1808,4 +1810,19 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
-DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
+static const char* const kill_mode_table[_KILL_MODE_MAX] = {
+ [KILL_CONTROL_GROUP] = "control-group",
+ [KILL_PROCESS_GROUP] = "process-group",
+ [KILL_PROCESS] = "process",
+ [KILL_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
+
+static const char* const kill_who_table[_KILL_WHO_MAX] = {
+ [KILL_MAIN] = "main",
+ [KILL_CONTROL] = "control",
+ [KILL_ALL] = "all"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
diff --git a/src/execute.h b/src/execute.h
index ed61e3c62..dd84c3d29 100644
--- a/src/execute.h
+++ b/src/execute.h
@@ -55,6 +55,14 @@ typedef enum KillMode {
_KILL_MODE_INVALID = -1
} KillMode;
+typedef enum KillWho {
+ KILL_MAIN,
+ KILL_CONTROL,
+ KILL_ALL,
+ _KILL_WHO_MAX,
+ _KILL_WHO_INVALID = -1
+} KillWho;
+
typedef enum ExecInput {
EXEC_INPUT_NULL,
EXEC_INPUT_TTY,
@@ -202,4 +210,10 @@ int exec_output_from_string(const char *s);
const char* exec_input_to_string(ExecInput i);
int exec_input_from_string(const char *s);
+const char *kill_mode_to_string(KillMode k);
+KillMode kill_mode_from_string(const char *s);
+
+const char *kill_who_to_string(KillWho k);
+KillWho kill_who_from_string(const char *s);
+
#endif
diff --git a/src/load-fragment.c b/src/load-fragment.c
index 0502fc45d..74fe78647 100644
--- a/src/load-fragment.c
+++ b/src/load-fragment.c
@@ -1047,11 +1047,7 @@ static int config_parse_kill_signal(
assert(rvalue);
assert(sig);
- if ((r = signal_from_string(rvalue)) <= 0)
- if (startswith(rvalue, "SIG"))
- r = signal_from_string(rvalue+3);
-
- if (r <= 0) {
+ if ((r = signal_from_string_try_harder(rvalue)) <= 0) {
log_error("[%s:%u] Failed to parse kill signal, ignoring: %s", filename, line, rvalue);
return 0;
}
diff --git a/src/mount.c b/src/mount.c
index bbc29d882..3fc0f1371 100644
--- a/src/mount.c
+++ b/src/mount.c
@@ -35,6 +35,7 @@
#include "unit-name.h"
#include "dbus-mount.h"
#include "special.h"
+#include "bus-errors.h"
static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = {
[MOUNT_DEAD] = UNIT_INACTIVE,
@@ -1636,6 +1637,52 @@ static void mount_reset_failed(Unit *u) {
m->failure = false;
}
+static int mount_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
+ Mount *m = MOUNT(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(m);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Mount units have no main processes");
+ return -EINVAL;
+ }
+
+ if (m->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ENOENT;
+ }
+
+ if (m->control_pid > 0)
+ if (kill(mode == KILL_PROCESS_GROUP ? -m->control_pid : m->control_pid, signo) < 0)
+ r = -errno;
+
+ if (mode == KILL_CONTROL_GROUP) {
+ int q;
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (m->control_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if ((q = cgroup_bonding_kill_list(m->meta.cgroup_bondings, signo, pid_set)) < 0)
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
static const char* const mount_state_table[_MOUNT_STATE_MAX] = {
[MOUNT_DEAD] = "dead",
[MOUNT_MOUNTING] = "mounting",
@@ -1682,6 +1729,8 @@ const UnitVTable mount_vtable = {
.stop = mount_stop,
.reload = mount_reload,
+ .kill = mount_kill,
+
.serialize = mount_serialize,
.deserialize_item = mount_deserialize_item,
diff --git a/src/service.c b/src/service.c
index 3ebe60e46..6d6c540a7 100644
--- a/src/service.c
+++ b/src/service.c
@@ -3145,6 +3145,62 @@ static void service_reset_failed(Unit *u) {
s->failure = false;
}
+static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
+ Service *s = SERVICE(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (s->main_pid <= 0 && who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill");
+ return -EINVAL;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ENOENT;
+ }
+
+ if (s->control_pid > 0)
+ if (kill(mode == KILL_PROCESS_GROUP ? -s->control_pid : s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (s->main_pid > 0)
+ if (kill(mode == KILL_PROCESS_GROUP ? -s->main_pid : s->main_pid, signo) < 0)
+ r = -errno;
+
+ if (mode == KILL_CONTROL_GROUP) {
+ int q;
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ /* Exclude the control/main pid from being killed via the cgroup */
+ if (s->control_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if (s->main_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if ((q = cgroup_bonding_kill_list(s->meta.cgroup_bondings, signo, pid_set)) < 0)
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_DEAD] = "dead",
[SERVICE_START_PRE] = "start-pre",
@@ -3222,6 +3278,8 @@ const UnitVTable service_vtable = {
.can_reload = service_can_reload,
+ .kill = service_kill,
+
.serialize = service_serialize,
.deserialize_item = service_deserialize_item,
diff --git a/src/socket.c b/src/socket.c
index fc6088c26..e8c016cc7 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -1784,6 +1784,52 @@ static void socket_reset_failed(Unit *u) {
s->failure = false;
}
+static int socket_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
+ Socket *s = SOCKET(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Socket units have no main processes");
+ return -EINVAL;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ENOENT;
+ }
+
+ if (s->control_pid > 0)
+ if (kill(mode == KILL_PROCESS_GROUP ? -s->control_pid : s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (mode == KILL_CONTROL_GROUP) {
+ int q;
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if ((q = cgroup_bonding_kill_list(s->meta.cgroup_bondings, signo, pid_set)) < 0)
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
[SOCKET_DEAD] = "dead",
[SOCKET_START_PRE] = "start-pre",
@@ -1817,6 +1863,8 @@ const UnitVTable socket_vtable = {
.done = socket_done,
.load = socket_load,
+ .kill = socket_kill,
+
.coldplug = socket_coldplug,
.dump = socket_dump,
diff --git a/src/swap.c b/src/swap.c
index cf9644fc6..f7f9530a0 100644
--- a/src/swap.c
+++ b/src/swap.c
@@ -35,6 +35,7 @@
#include "unit-name.h"
#include "dbus-swap.h"
#include "special.h"
+#include "bus-errors.h"
static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = {
[SWAP_DEAD] = UNIT_INACTIVE,
@@ -1213,6 +1214,52 @@ static void swap_reset_failed(Unit *u) {
s->failure = false;
}
+static int swap_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
+ Swap *s = SWAP(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Swap units have no main processes");
+ return -EINVAL;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ENOENT;
+ }
+
+ if (s->control_pid > 0)
+ if (kill(mode == KILL_PROCESS_GROUP ? -s->control_pid : s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (mode == KILL_CONTROL_GROUP) {
+ int q;
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if ((q = cgroup_bonding_kill_list(s->meta.cgroup_bondings, signo, pid_set)) < 0)
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
static const char* const swap_state_table[_SWAP_STATE_MAX] = {
[SWAP_DEAD] = "dead",
[SWAP_ACTIVATING] = "activating",
@@ -1253,6 +1300,8 @@ const UnitVTable swap_vtable = {
.start = swap_start,
.stop = swap_stop,
+ .kill = swap_kill,
+
.serialize = swap_serialize,
.deserialize_item = swap_deserialize_item,
diff --git a/src/systemctl.c b/src/systemctl.c
index a03769034..ffbe4db54 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -74,6 +74,9 @@ static bool arg_full = false;
static bool arg_force = false;
static bool arg_defaults = false;
static char **arg_wall = NULL;
+static const char *arg_kill_who = NULL;
+static const char *arg_kill_mode = NULL;
+static int arg_signal = SIGTERM;
static usec_t arg_when = 0;
static enum action {
ACTION_INVALID,
@@ -1408,6 +1411,7 @@ static int check_unit(DBusConnection *bus, char **args, unsigned n) {
puts("unknown");
dbus_error_free(&error);
+ dbus_message_unref(m);
continue;
}
@@ -1486,6 +1490,71 @@ finish:
return r;
}
+static int kill_unit(DBusConnection *bus, char **args, unsigned n) {
+ DBusMessage *m = NULL, *reply = NULL;
+ int r = 0;
+ DBusError error;
+ unsigned i;
+
+ assert(bus);
+ assert(args);
+
+ dbus_error_init(&error);
+
+ if (!arg_kill_who)
+ arg_kill_who = "all";
+
+ if (!arg_kill_mode)
+ arg_kill_mode = streq(arg_kill_who, "all") ? "control-group" : "process";
+
+ for (i = 1; i < n; i++) {
+
+ if (!(m = dbus_message_new_method_call(
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit"))) {
+ log_error("Could not allocate message.");
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &args[i],
+ DBUS_TYPE_STRING, &arg_kill_who,
+ DBUS_TYPE_STRING, &arg_kill_mode,
+ DBUS_TYPE_INT32, &arg_signal,
+ DBUS_TYPE_INVALID)) {
+ log_error("Could not append arguments to message.");
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+ log_error("Failed to issue method call: %s", bus_error_message(&error));
+ dbus_error_free(&error);
+ r = -EIO;
+ }
+
+ dbus_message_unref(m);
+
+ if (reply)
+ dbus_message_unref(reply);
+ m = reply = NULL;
+ }
+
+finish:
+ if (m)
+ dbus_message_unref(m);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+
+ return r;
+}
+
typedef struct ExecStatusInfo {
char *path;
char **argv;
@@ -3923,27 +3992,30 @@ static int systemctl_help(void) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Send control commands to or query the systemd manager.\n\n"
- " -h --help Show this help\n"
- " --version Show package version\n"
- " -t --type=TYPE List only units of a particular type\n"
- " -p --property=NAME Show only properties by this name\n"
- " -a --all Show all units/properties, including dead/empty ones\n"
- " --full Don't ellipsize unit names on output\n"
- " --fail When queueing a new job, fail if conflicting jobs are\n"
- " pending\n"
- " -q --quiet Suppress output\n"
- " --no-block Do not wait until operation finished\n"
- " --system Connect to system bus\n"
- " --session Connect to session bus\n"
- " --order When generating graph for dot, show only order\n"
- " --require When generating graph for dot, show only requirement\n"
- " --no-wall Don't send wall message before halt/power-off/reboot\n"
- " --global Enable/disable unit files globally\n"
- " --no-reload When enabling/disabling unit files, don't reload daemon\n"
- " configuration\n"
- " -f --force When enabling unit files, override existing symlinks\n"
- " When shutting down, execute action immediately\n"
- " --defaults When disabling unit files, remove default symlinks only\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -t --type=TYPE List only units of a particular type\n"
+ " -p --property=NAME Show only properties by this name\n"
+ " -a --all Show all units/properties, including dead/empty ones\n"
+ " --full Don't ellipsize unit names on output\n"
+ " --fail When queueing a new job, fail if conflicting jobs are\n"
+ " pending\n"
+ " -q --quiet Suppress output\n"
+ " --no-block Do not wait until operation finished\n"
+ " --system Connect to system bus\n"
+ " --session Connect to session bus\n"
+ " --order When generating graph for dot, show only order\n"
+ " --require When generating graph for dot, show only requirement\n"
+ " --no-wall Don't send wall message before halt/power-off/reboot\n"
+ " --global Enable/disable unit files globally\n"
+ " --no-reload When enabling/disabling unit files, don't reload daemon\n"
+ " configuration\n"
+ " --kill-mode=MODE How to send signal\n"
+ " --kill-who=WHO Who to send signal to\n"
+ " -s --signal=SIGNAL Which signal to send\n"
+ " -f --force When enabling unit files, override existing symlinks\n"
+ " When shutting down, execute action immediately\n"
+ " --defaults When disabling unit files, remove default symlinks only\n\n"
"Commands:\n"
" list-units List units\n"
" start [NAME...] Start (activate) one or more units\n"
@@ -3956,6 +4028,7 @@ static int systemctl_help(void) {
" reload-or-try-restart [NAME...] Reload one or more units is possible,\n"
" otherwise restart if active\n"
" isolate [NAME] Start one unit and stop all others\n"
+ " kill [NAME...] Send signal to processes of a unit\n"
" is-active [NAME...] Check whether units are active\n"
" status [NAME...|PID...] Show runtime status of one or more units\n"
" show [NAME...|JOB...] Show properties of one or more\n"
@@ -4071,7 +4144,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_REQUIRE,
ARG_FULL,
ARG_NO_RELOAD,
- ARG_DEFAULTS
+ ARG_DEFAULTS,
+ ARG_KILL_MODE,
+ ARG_KILL_WHO
};
static const struct option options[] = {
@@ -4093,6 +4168,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "force", no_argument, NULL, 'f' },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "defaults", no_argument, NULL, ARG_DEFAULTS },
+ { "kill-mode", required_argument, NULL, ARG_KILL_MODE },
+ { "kill-who", required_argument, NULL, ARG_KILL_WHO },
+ { "signal", required_argument, NULL, 's' },
{ NULL, 0, NULL, 0 }
};
@@ -4101,7 +4179,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "ht:p:aqf", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "ht:p:aqfs:", options, NULL)) >= 0) {
switch (c) {
@@ -4192,6 +4270,21 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_defaults = true;
break;
+ case ARG_KILL_WHO:
+ arg_kill_who = optarg;
+ break;
+
+ case ARG_KILL_MODE:
+ arg_kill_mode = optarg;
+ break;
+
+ case 's':
+ if ((arg_signal = signal_from_string_try_harder(optarg)) < 0) {
+ log_error("Failed to parse signal string %s.", optarg);
+ return -EINVAL;
+ }
+ break;
+
case '?':
return -EINVAL;
@@ -4785,6 +4878,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
{ "force-reload", MORE, 2, start_unit }, /* For compatibility with SysV */
{ "condrestart", MORE, 2, start_unit }, /* For compatibility with RH */
{ "isolate", EQUAL, 2, start_unit },
+ { "kill", MORE, 2, kill_unit },
{ "is-active", MORE, 2, check_unit },
{ "check", MORE, 2, check_unit },
{ "show", MORE, 1, show },
diff --git a/src/unit.c b/src/unit.c
index ab6eb2022..d2f60801a 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -2252,6 +2252,22 @@ bool unit_name_is_valid(const char *n, bool template_ok) {
return unit_name_is_valid_no_type(n, template_ok);
}
+int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error) {
+ assert(u);
+ assert(w >= 0 && w < _KILL_WHO_MAX);
+ assert(m >= 0 && m < _KILL_MODE_MAX);
+ assert(signo > 0);
+ assert(signo < _NSIG);
+
+ if (m == KILL_NONE)
+ return 0;
+
+ if (!UNIT_VTABLE(u)->kill)
+ return -ENOTSUP;
+
+ return UNIT_VTABLE(u)->kill(u, w, m, signo, error);
+}
+
static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
[UNIT_STUB] = "stub",
[UNIT_LOADED] = "loaded",
@@ -2292,12 +2308,3 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);
-
-static const char* const kill_mode_table[_KILL_MODE_MAX] = {
- [KILL_CONTROL_GROUP] = "control-group",
- [KILL_PROCESS_GROUP] = "process-group",
- [KILL_PROCESS] = "process",
- [KILL_NONE] = "none"
-};
-
-DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
diff --git a/src/unit.h b/src/unit.h
index fa869ece8..3f0ef01c5 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -277,6 +277,8 @@ struct UnitVTable {
int (*stop)(Unit *u);
int (*reload)(Unit *u);
+ int (*kill)(Unit *u, KillWho w, KillMode m, int signo, DBusError *error);
+
bool (*can_reload)(Unit *u);
/* Write all data that cannot be restored from other sources
@@ -458,6 +460,8 @@ int unit_start(Unit *u);
int unit_stop(Unit *u);
int unit_reload(Unit *u);
+int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error);
+
void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns);
int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w);
@@ -520,7 +524,4 @@ UnitActiveState unit_active_state_from_string(const char *s);
const char *unit_dependency_to_string(UnitDependency i);
UnitDependency unit_dependency_from_string(const char *s);
-const char *kill_mode_to_string(KillMode k);
-KillMode kill_mode_from_string(const char *s);
-
#endif
diff --git a/src/util.c b/src/util.c
index 58f67b0d1..cf3cf292a 100644
--- a/src/util.c
+++ b/src/util.c
@@ -3339,6 +3339,17 @@ DIR *xopendirat(int fd, const char *name) {
return fdopendir(openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
}
+int signal_from_string_try_harder(const char *s) {
+ int signo;
+ assert(s);
+
+ if ((signo = signal_from_string(s)) <= 0)
+ if (startswith(s, "SIG"))
+ return signal_from_string(s+3);
+
+ return signo;
+}
+
static const char *const ioprio_class_table[] = {
[IOPRIO_CLASS_NONE] = "none",
[IOPRIO_CLASS_RT] = "realtime",
diff --git a/src/util.h b/src/util.h
index e87fb23bf..b257ee560 100644
--- a/src/util.h
+++ b/src/util.h
@@ -391,4 +391,6 @@ int ip_tos_from_string(const char *s);
const char *signal_to_string(int i);
int signal_from_string(const char *s);
+int signal_from_string_try_harder(const char *s);
+
#endif