]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
containers: systemd exits with non-zero code 1318/head
authorAlban Crequy <alban.crequy@gmail.com>
Fri, 18 Sep 2015 11:37:34 +0000 (13:37 +0200)
committerAlban Crequy <alban.crequy@gmail.com>
Mon, 21 Sep 2015 15:32:45 +0000 (17:32 +0200)
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
man/systemctl.xml
man/systemd.special.xml
src/core/dbus-manager.c
src/core/main.c
src/core/manager.h
src/core/shutdown.c
src/systemctl/systemctl.c
units/.gitignore
units/exit.target [new file with mode: 0644]
units/systemd-exit.service.in [new file with mode: 0644]

index 3b35083d0a56cc6600e7f72f1114d6544d45b5a3..5b1431c866cad43f564f589994ad7bd0453f55e2 100644 (file)
@@ -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 \
index d8d433e4d3345101edcfa5022526be4422ef357c..c1359d1678eb816e6ae28b9a10fd2cfbd93d2d0d 100644 (file)
@@ -1633,13 +1633,17 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service
         </varlistentry>
 
         <varlistentry>
-          <term><command>exit</command></term>
+          <term><command>exit <optional><replaceable>EXIT_CODE</replaceable></optional></command></term>
 
           <listitem>
             <para>Ask the systemd manager to quit. This is only
             supported for user service managers (i.e. in conjunction
-            with the <option>--user</option> option) and will fail
-            otherwise.</para>
+            with the <option>--user</option> option) or in containers
+            and is equivalent to <command>poweroff</command> otherwise.</para>
+
+            <para>The systemd manager can exit with a non-zero exit
+            code if the optional argument
+            <replaceable>EXIT_CODE</replaceable> is given.</para>
           </listitem>
         </varlistentry>
 
index e4700d950b47f8cfb7bfae90d1d8258b94ca33e8..6e0dff9b4755315bb905c33b78a7359ff7704641 100644 (file)
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><filename>exit.target</filename></term>
+        <listitem>
+          <para>A special service unit for shutting down the system or
+          user service manager. It also works in containers and is
+          equivalent to <filename>poweroff.target</filename> on
+          non-container systems.</para>
+
+          <para>Applications wanting to terminate the user service
+          manager should start this unit. If systemd receives
+          <constant>SIGTERM</constant> or <constant>SIGINT</constant>
+          when running as user service daemon, it will start this
+          unit.</para>
+
+          <para>Normally, this pulls in
+          <filename>shutdown.target</filename> which in turn should be
+          conflicted by all units that want to be shut down on user
+          service manager exit.</para>
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term><filename>final.target</filename></term>
         <listitem>
     <para>When systemd runs as a user instance, the following special
     units are available, which have similar definitions as their
     system counterparts:
+    <filename>exit.target</filename>,
     <filename>default.target</filename>,
     <filename>shutdown.target</filename>,
     <filename>sockets.target</filename>,
     <filename>printer.target</filename>,
     <filename>smartcard.target</filename>,
     <filename>sound.target</filename>.</para>
-
-    <para>In addition, the following special unit is understood only
-    when systemd runs as service instance:</para>
-
-    <variablelist>
-      <varlistentry>
-        <term><filename>exit.target</filename></term>
-        <listitem>
-          <para>A special service unit for shutting down the user
-          service manager.</para>
-
-          <para>Applications wanting to terminate the user service
-          manager should start this unit. If systemd receives
-          <constant>SIGTERM</constant> or <constant>SIGINT</constant>
-          when running as user service daemon, it will start this
-          unit.</para>
-
-          <para>Normally, this pulls in
-          <filename>shutdown.target</filename> which in turn should be
-          conflicted by all units that want to be shut down on user
-          service manager exit.</para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
   </refsect1>
 
   <refsect1>
index 4e5d67fc192f38987d7fdaf725883fa98daa5c47..561b6f8bfa55efbdfad042b6da3fa51393396c39 100644 (file)
@@ -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),
index 200fe740dab4fccf41c9a8023eadef519d7bbc0c..9b59648279893ebe90b1f9f79fea25edff9dd8be 100644 (file)
@@ -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) {
index b9559821008093a857346aa12c3d1c333efeeba5..1384eb33a4c3ae06b5273539fffb8e48cbc5976d 100644 (file)
@@ -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;
index 8cc6efc5b837da216d60cbd0defd4de2981687b0..5296efce1d68471a1e8394f5c878d53003d3012d 100644 (file)
@@ -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:
index d1e443b8a66d30696f275f6e2187f0654ac20cd1..e20fc1bbbb978874aa8990eebe83a8ad4bd71669 100644 (file)
@@ -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 },
index d45492d06b7d809bc349cbe86c82695b9e414d18..049371884a2f25e0399fd856cc3d92517a5de9ce 100644 (file)
@@ -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 (file)
index 0000000..f5f953d
--- /dev/null
@@ -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 (file)
index 0000000..2dbfb36
--- /dev/null
@@ -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