From 287419c119ef961db487a281162ab037eba70c61 Mon Sep 17 00:00:00 2001 From: Alban Crequy Date: Fri, 18 Sep 2015 13:37:34 +0200 Subject: [PATCH] containers: systemd exits with non-zero code When a systemd service running in a container exits with a non-zero code, it can be useful to terminate the container immediately and get the exit code back to the host, when systemd-nspawn returns. This was not possible to do. This patch adds the following to make it possible: - Add a read-only "ExitCode" property on PID 1's "Manager" bus object. By default, it is 0 so the behaviour stays the same as previously. - Add a method "SetExitCode" on the same object. The method fails when called on baremetal: it is only allowed in containers or in user session. - Add support in systemctl to call "systemctl exit 42". It reuses the existing code for user session. - Add exit.target and systemd-exit.service to the system instance. - Change main() to actually call systemd-shutdown to exit() with the correct value. - Add verb 'exit' in systemd-shutdown with parameter --exit-code - Update systemctl manpage. I used the following to test it: | $ sudo rkt --debug --insecure-skip-verify run \ | --mds-register=false --local docker://busybox \ | --exec=/bin/chroot -- /proc/1/root \ | systemctl --force exit 42 | ... | Container rkt-895a0cba-5c66-4fa5-831c-e3f8ddc5810d failed with error code 42. | $ echo $? | 42 Fixes https://github.com/systemd/systemd/issues/1290 --- Makefile.am | 3 +++ man/systemctl.xml | 10 +++++--- man/systemd.special.xml | 45 ++++++++++++++++------------------- src/core/dbus-manager.c | 32 +++++++++++++++++++++++-- src/core/main.c | 21 ++++++++++------ src/core/manager.h | 5 ++++ src/core/shutdown.c | 22 +++++++++++++++++ src/systemctl/systemctl.c | 30 +++++++++++++++++++++-- units/.gitignore | 1 + units/exit.target | 17 +++++++++++++ units/systemd-exit.service.in | 17 +++++++++++++ 11 files changed, 165 insertions(+), 38 deletions(-) create mode 100644 units/exit.target create mode 100644 units/systemd-exit.service.in diff --git a/Makefile.am b/Makefile.am index 3b35083d0a5..5b1431c866c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -474,6 +474,7 @@ dist_systemunit_DATA = \ units/getty.target \ units/halt.target \ units/kexec.target \ + units/exit.target \ units/local-fs.target \ units/local-fs-pre.target \ units/initrd.target \ @@ -550,6 +551,7 @@ nodist_systemunit_DATA = \ units/systemd-poweroff.service \ units/systemd-reboot.service \ units/systemd-kexec.service \ + units/systemd-exit.service \ units/systemd-fsck@.service \ units/systemd-fsck-root.service \ units/systemd-machine-id-commit.service \ @@ -601,6 +603,7 @@ EXTRA_DIST += \ units/systemd-poweroff.service.in \ units/systemd-reboot.service.in \ units/systemd-kexec.service.in \ + units/systemd-exit.service.in \ units/user/systemd-exit.service.in \ units/systemd-fsck@.service.in \ units/systemd-fsck-root.service.in \ diff --git a/man/systemctl.xml b/man/systemctl.xml index d8d433e4d33..c1359d1678e 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1633,13 +1633,17 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service - exit + exit EXIT_CODE Ask the systemd manager to quit. This is only supported for user service managers (i.e. in conjunction - with the option) and will fail - otherwise. + with the option) or in containers + and is equivalent to poweroff otherwise. + + The systemd manager can exit with a non-zero exit + code if the optional argument + EXIT_CODE is given. diff --git a/man/systemd.special.xml b/man/systemd.special.xml index e4700d950b4..6e0dff9b475 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -211,6 +211,26 @@ + + exit.target + + A special service unit for shutting down the system or + user service manager. It also works in containers and is + equivalent to poweroff.target on + non-container systems. + + Applications wanting to terminate the user service + manager should start this unit. If systemd receives + SIGTERM or SIGINT + when running as user service daemon, it will start this + unit. + + Normally, this pulls in + shutdown.target which in turn should be + conflicted by all units that want to be shut down on user + service manager exit. + + final.target @@ -797,6 +817,7 @@ When systemd runs as a user instance, the following special units are available, which have similar definitions as their system counterparts: + exit.target, default.target, shutdown.target, sockets.target, @@ -806,30 +827,6 @@ printer.target, smartcard.target, sound.target. - - In addition, the following special unit is understood only - when systemd runs as service instance: - - - - exit.target - - A special service unit for shutting down the user - service manager. - - Applications wanting to terminate the user service - manager should start this unit. If systemd receives - SIGTERM or SIGINT - when running as user service daemon, it will start this - unit. - - Normally, this pulls in - shutdown.target which in turn should be - conflicted by all units that want to be shut down on user - service manager exit. - - - diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 4e5d67fc192..561b6f8bfa5 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1201,8 +1201,10 @@ static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *er if (r < 0) return r; - if (m->running_as == MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers."); + /* Exit() (in contrast to SetExitCode()) is actually allowed even if + * we are running on the host. It will fall back on reboot() in + * systemd-shutdown if it cannot do the exit() because it isn't a + * container. */ m->exit_code = MANAGER_EXIT; @@ -1450,6 +1452,30 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd return sd_bus_reply_method_return(message, NULL); } +static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { + uint8_t code; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "exit", error); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(message, 'y', &code); + if (r < 0) + return r; + + if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); + + m->return_value = code; + + return sd_bus_reply_method_return(message, NULL); +} + static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; Manager *m = userdata; @@ -1933,6 +1959,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), + SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), @@ -1986,6 +2013,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("UnitNew", "so", 0), SD_BUS_SIGNAL("UnitRemoved", "so", 0), diff --git a/src/core/main.c b/src/core/main.c index 200fe740dab..9b596482798 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1254,6 +1254,7 @@ int main(int argc, char *argv[]) { char *switch_root_dir = NULL, *switch_root_init = NULL; struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); const char *error_message = NULL; + uint8_t shutdown_exit_code = 0; #ifdef HAVE_SYSV_COMPAT if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { @@ -1764,11 +1765,6 @@ int main(int argc, char *argv[]) { switch (m->exit_code) { - case MANAGER_EXIT: - retval = EXIT_SUCCESS; - log_debug("Exit."); - goto finish; - case MANAGER_RELOAD: log_info("Reloading."); @@ -1810,11 +1806,13 @@ int main(int argc, char *argv[]) { log_notice("Switching root."); goto finish; + case MANAGER_EXIT: case MANAGER_REBOOT: case MANAGER_POWEROFF: case MANAGER_HALT: case MANAGER_KEXEC: { static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_EXIT] = "exit", [MANAGER_REBOOT] = "reboot", [MANAGER_POWEROFF] = "poweroff", [MANAGER_HALT] = "halt", @@ -1836,8 +1834,10 @@ int main(int argc, char *argv[]) { finish: pager_close(); - if (m) + if (m) { arg_shutdown_watchdog = m->shutdown_watchdog; + shutdown_exit_code = m->return_value; + } m = manager_free(m); for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) @@ -1978,7 +1978,8 @@ finish: if (shutdown_verb) { char log_level[DECIMAL_STR_MAX(int) + 1]; - const char* command_line[9] = { + char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; + const char* command_line[11] = { SYSTEMD_SHUTDOWN_BINARY_PATH, shutdown_verb, "--log-level", log_level, @@ -2015,6 +2016,12 @@ finish: if (log_get_show_location()) command_line[pos++] = "--log-location"; + if (streq(shutdown_verb, "exit")) { + command_line[pos++] = "--exit-code"; + command_line[pos++] = exit_code; + xsprintf(exit_code, "%d", shutdown_exit_code); + } + assert(pos < ELEMENTSOF(command_line)); if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { diff --git a/src/core/manager.h b/src/core/manager.h index b9559821008..1384eb33a4c 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -242,6 +242,11 @@ struct Manager { bool test_run:1; + /* If non-zero, exit with the following value when the systemd + * process terminate. Useful for containers: systemd-nspawn could get + * the return value. */ + uint8_t return_value; + ShowStatus show_status; bool confirm_spawn; bool no_console_output; diff --git a/src/core/shutdown.c b/src/core/shutdown.c index 8cc6efc5b83..5296efce1d6 100644 --- a/src/core/shutdown.c +++ b/src/core/shutdown.c @@ -48,6 +48,7 @@ #define FINALIZE_ATTEMPTS 50 static char* arg_verb; +static uint8_t arg_exit_code; static int parse_argv(int argc, char *argv[]) { enum { @@ -55,6 +56,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOG_TARGET, ARG_LOG_COLOR, ARG_LOG_LOCATION, + ARG_EXIT_CODE, }; static const struct option options[] = { @@ -62,6 +64,7 @@ static int parse_argv(int argc, char *argv[]) { { "log-target", required_argument, NULL, ARG_LOG_TARGET }, { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, {} }; @@ -110,6 +113,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_EXIT_CODE: + r = safe_atou8(optarg, &arg_exit_code); + if (r < 0) + log_error("Failed to parse exit code %s, ignoring", optarg); + + break; + case '\001': if (!arg_verb) arg_verb = optarg; @@ -183,6 +193,8 @@ int main(int argc, char *argv[]) { cmd = RB_HALT_SYSTEM; else if (streq(arg_verb, "kexec")) cmd = LINUX_REBOOT_CMD_KEXEC; + else if (streq(arg_verb, "exit")) + cmd = 0; /* ignored, just checking that arg_verb is valid */ else { r = -EINVAL; log_error("Unknown action '%s'.", arg_verb); @@ -339,6 +351,16 @@ int main(int argc, char *argv[]) { if (!in_container) sync(); + if (streq(arg_verb, "exit")) { + if (in_container) + exit(arg_exit_code); + else { + /* We cannot exit() on the host, fallback on another + * method. */ + cmd = RB_POWER_OFF; + } + } + switch (cmd) { case LINUX_REBOOT_CMD_KEXEC: diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d1e443b8a66..e20fc1bbbb9 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3005,6 +3005,7 @@ static int prepare_firmware_setup(sd_bus *bus) { } static int start_special(sd_bus *bus, char **args) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; enum action a; int r; @@ -3029,6 +3030,31 @@ static int start_special(sd_bus *bus, char **args) { r = update_reboot_param_file(args[1]); if (r < 0) return r; + } else if (a == ACTION_EXIT && strv_length(args) > 1) { + /* If the exit code is not given on the command line, don't + * reset it to zero: just keep it as it might have been set + * previously. */ + uint8_t code = 0; + + r = safe_atou8(args[1], &code); + if (r < 0) { + log_error("Invalid exit code."); + return -EINVAL; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetExitCode", + &error, + NULL, + "y", code); + if (r < 0) { + log_error("Failed to execute operation: %s", bus_error_message(&error, r)); + return r; + } } if (arg_force >= 2 && @@ -6224,7 +6250,7 @@ static void systemctl_help(void) { " poweroff Shut down and power-off the system\n" " reboot [ARG] Shut down and reboot the system\n" " kexec Shut down and reboot the system with kexec\n" - " exit Request user instance exit\n" + " exit [EXIT_CODE] Request user instance or container exit\n" " switch-root ROOT [INIT] Change to a different root file system\n" " suspend Suspend the system\n" " hibernate Hibernate the system\n" @@ -7211,7 +7237,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "default", EQUAL, 1, start_special }, { "rescue", EQUAL, 1, start_special }, { "emergency", EQUAL, 1, start_special }, - { "exit", EQUAL, 1, start_special }, + { "exit", LESS, 2, start_special }, { "reset-failed", MORE, 1, reset_failed }, { "enable", MORE, 2, enable_unit, NOBUS }, { "disable", MORE, 2, enable_unit, NOBUS }, diff --git a/units/.gitignore b/units/.gitignore index d45492d06b7..049371884a2 100644 --- a/units/.gitignore +++ b/units/.gitignore @@ -30,6 +30,7 @@ /systemd-fsck@.service /systemd-machine-id-commit.service /systemd-halt.service +/systemd-exit.service /systemd-hibernate.service /systemd-hostnamed.service /systemd-hybrid-sleep.service diff --git a/units/exit.target b/units/exit.target new file mode 100644 index 00000000000..f5f953d112f --- /dev/null +++ b/units/exit.target @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Exit the container +Documentation=man:systemd.special(7) +DefaultDependencies=no +Requires=systemd-exit.service +After=systemd-exit.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/systemd-exit.service.in b/units/systemd-exit.service.in new file mode 100644 index 00000000000..2dbfb36b41c --- /dev/null +++ b/units/systemd-exit.service.in @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Exit the Session +Documentation=man:systemd.special(7) +DefaultDependencies=no +Requires=shutdown.target +After=shutdown.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force exit -- 2.39.2