From abdb9b08f6b0da45603a5928eb884a8d012127a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Nov 2017 19:27:47 +0100 Subject: [PATCH] nspawn: make use of the RequestStop logic of scope units Since time began, scope units had a concept of "Controllers", a bus peer that would be notified when somebody requested a unit to stop. None of our code used that facility so far, let's change that. This way, nspawn can print a nice message when somebody invokes "systemctl stop" on the container's scope unit, and then react with the right action to shut it down. --- src/nspawn/nspawn-register.c | 45 +++++++++++++++++++++--------- src/nspawn/nspawn-register.h | 6 ++-- src/nspawn/nspawn.c | 66 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index 9d58953..ef9db31 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -99,7 +99,26 @@ static int append_machine_properties( return 0; } +static int append_controller_property(sd_bus *bus, sd_bus_message *m) { + const char *unique; + int r; + + assert(bus); + assert(m); + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) + return log_error_errno(r, "Failed to get unique name: %m"); + + r = sd_bus_message_append(m, "(sv)", "Controller", "s", unique); + if (r < 0) + return bus_log_create_error(r); + + return 0; +} + int register_machine( + sd_bus *bus, const char *machine_name, pid_t pid, const char *directory, @@ -114,12 +133,9 @@ int register_machine( const char *service) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + assert(bus); if (keep_unit) { r = sd_bus_call_method( @@ -174,6 +190,10 @@ int register_machine( return bus_log_create_error(r); } + r = append_controller_property(bus, m); + if (r < 0) + return r; + r = append_machine_properties( m, mounts, @@ -202,16 +222,13 @@ int register_machine( return 0; } -int terminate_machine(pid_t pid) { +int terminate_machine(sd_bus *bus, pid_t pid) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; const char *path; int r; - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + assert(bus); r = sd_bus_call_method( bus, @@ -253,6 +270,7 @@ int terminate_machine(pid_t pid) { } int allocate_scope( + sd_bus *bus, const char *machine_name, pid_t pid, const char *slice, @@ -263,16 +281,13 @@ int allocate_scope( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_free_ char *scope = NULL; const char *description, *object; int r; - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + assert(bus); r = bus_wait_for_jobs_new(bus, &w); if (r < 0) @@ -311,6 +326,10 @@ int allocate_scope( if (r < 0) return bus_log_create_error(r); + r = append_controller_property(bus, m); + if (r < 0) + return r; + r = append_machine_properties( m, mounts, diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h index 76c13bb..fa3644c 100644 --- a/src/nspawn/nspawn-register.h +++ b/src/nspawn/nspawn-register.h @@ -26,7 +26,7 @@ #include "nspawn-mount.h" -int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); -int terminate_machine(pid_t pid); +int register_machine(sd_bus *bus, const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); +int terminate_machine(sd_bus *bus, pid_t pid); -int allocate_scope(const char *machine_name, pid_t pid, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties); +int allocate_scope(sd_bus *bus, const char *machine_name, pid_t pid, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3bf4d6e..8fc39dd 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2039,18 +2039,27 @@ static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo } static int on_sigchld(sd_event_source *s, const struct signalfd_siginfo *ssi, void *userdata) { + pid_t pid; + + assert(s); + assert(ssi); + + pid = PTR_TO_PID(userdata); + for (;;) { siginfo_t si = {}; + if (waitid(P_ALL, 0, &si, WNOHANG|WNOWAIT|WEXITED) < 0) return log_error_errno(errno, "Failed to waitid(): %m"); if (si.si_pid == 0) /* No pending children. */ break; - if (si.si_pid == PTR_TO_PID(userdata)) { + if (si.si_pid == pid) { /* The main process we care for has exited. Return from * signal handler but leave the zombie. */ sd_event_exit(sd_event_source_get_event(s), 0); break; } + /* Reap all other children. */ (void) waitid(P_PID, si.si_pid, &si, WNOHANG|WEXITED); } @@ -2058,6 +2067,24 @@ static int on_sigchld(sd_event_source *s, const struct signalfd_siginfo *ssi, vo return 0; } +static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *error) { + pid_t pid; + + assert(m); + + pid = PTR_TO_PID(userdata); + + if (arg_kill_signal > 0) { + log_info("Container termination requested. Attempting to halt container."); + (void) kill(pid, arg_kill_signal); + } else { + log_info("Container termination requested. Exiting."); + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 0); + } + + return 0; +} + static int determine_names(void) { int r; @@ -3214,6 +3241,7 @@ static int run(int master, _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(pty_forward_freep) PTYForward *forward = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; ContainerStatus container_status = 0; char last_char = 0; int ifi = 0, r; @@ -3444,8 +3472,31 @@ static int run(int master, return r; } + if (arg_register || !arg_keep_unit) { + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + } + + if (!arg_keep_unit) { + /* When a new scope is created for this container, then we'll be registered as its controller, in which + * case PID 1 will send us a friendly RequestStop signal, when it is asked to terminate the + * scope. Let's hook into that, and cleanly shut down the container, and print a friendly message. */ + + r = sd_bus_add_match(bus, NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Scope'," + "member='RequestStop'", + on_request_stop, PID_TO_PTR(*pid)); + if (r < 0) + return log_error_errno(r, "Failed to install request stop match: %m"); + } + if (arg_register) { + r = register_machine( + bus, arg_machine, *pid, arg_directory, @@ -3459,8 +3510,11 @@ static int run(int master, arg_container_service_name); if (r < 0) return r; + } else if (!arg_keep_unit) { + r = allocate_scope( + bus, arg_machine, *pid, arg_slice, @@ -3506,6 +3560,12 @@ static int run(int master, if (r < 0) return log_error_errno(r, "Failed to get default event source: %m"); + if (bus) { + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + } + r = setup_sd_notify_parent(event, notify_socket, PID_TO_PTR(*pid), ¬ify_event_source); if (r < 0) return r; @@ -3567,8 +3627,8 @@ static int run(int master, putc('\n', stdout); /* Kill if it is not dead yet anyway */ - if (arg_register && !arg_keep_unit) - terminate_machine(*pid); + if (arg_register && !arg_keep_unit && bus) + terminate_machine(bus, *pid); /* Normally redundant, but better safe than sorry */ (void) kill(*pid, SIGKILL); -- 2.7.4