Skip to content

Commit

Permalink
containers: systemd exits with non-zero code
Browse files Browse the repository at this point in the history
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 to the system instance. It has the following
  condition: ConditionVirtualization=container.
- Change main() to actually allow exit() with the correct value.
- 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

I don't know why I have to use --force.

Fixes systemd#1290
  • Loading branch information
alban committed Sep 18, 2015
1 parent 7ee7b22 commit ed344f1
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 10 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,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 \
Expand Down
10 changes: 7 additions & 3 deletions man/systemctl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1612,13 +1612,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 will fail 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>

Expand Down
30 changes: 28 additions & 2 deletions src/core/dbus-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -1206,8 +1206,8 @@ 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.");
if (m->running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers or in containers.");

m->exit_code = MANAGER_EXIT;

Expand Down Expand Up @@ -1455,6 +1455,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) {
unsigned 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, 'u', &code);
if (r < 0)
return r;

if (detect_container(NULL) <= 0)
return sd_bus_error_setf(error, BUS_ERROR_NOT_ALLOWED, "ExitCode can only be set in containers or user service manager.");

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;
Expand Down Expand Up @@ -1954,6 +1978,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", "u", 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),
Expand Down Expand Up @@ -2007,6 +2032,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", "u", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED),

SD_BUS_SIGNAL("UnitNew", "so", 0),
SD_BUS_SIGNAL("UnitRemoved", "so", 0),
Expand Down
6 changes: 3 additions & 3 deletions src/core/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1764,7 +1764,7 @@ int main(int argc, char *argv[]) {
switch (m->exit_code) {

case MANAGER_EXIT:
retval = EXIT_SUCCESS;
retval = m->return_value;
log_debug("Exit.");
goto finish;

Expand Down Expand Up @@ -1988,7 +1988,7 @@ int main(int argc, char *argv[]) {

if (shutdown_verb) {
char log_level[DECIMAL_STR_MAX(int) + 1];
const char* command_line[9] = {
const char* command_line[10] = {
SYSTEMD_SHUTDOWN_BINARY_PATH,
shutdown_verb,
"--log-level", log_level,
Expand Down Expand Up @@ -2053,7 +2053,7 @@ int main(int argc, char *argv[]) {
getpid() == 1 ? "freezing" : "quitting");
}

if (getpid() == 1) {
if (getpid() == 1 && detect_container(NULL) <= 0) {
if (error_message)
manager_status_printf(NULL, STATUS_TYPE_EMERGENCY,
ANSI_HIGHLIGHT_RED_ON "!!!!!!" ANSI_HIGHLIGHT_OFF,
Expand Down
5 changes: 5 additions & 0 deletions src/core/manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ struct Manager {

const char *unit_log_field;
const char *unit_log_format_string;

/* If non-zero, exit with the following value when the systemd
* process terminate. Useful for containers: systemd-nspawn could get
* the return value. */
unsigned return_value;
};

int manager_new(ManagerRunningAs running_as, bool test_run, Manager **m);
Expand Down
1 change: 1 addition & 0 deletions src/libsystemd/sd-bus/bus-common-errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation"
#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning"
#define BUS_ERROR_NOT_ALLOWED "org.freedesktop.systemd1.NotAllowed"

#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
Expand Down
29 changes: 27 additions & 2 deletions src/systemctl/systemctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -4838,6 +4838,31 @@ static int daemon_reload(sd_bus *bus, char **args) {
/* "daemon-reload" */ "Reload";
}

if (streq(args[0], "exit")) {
unsigned code = 0;
if (strv_length(args) > 1) {
r = safe_atou(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,
"u", code);
if (r < 0) {
log_error("Failed to execute operation: %s", bus_error_message(&error, r));
return r;
}
}

r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
Expand Down Expand Up @@ -6155,7 +6180,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"
Expand Down Expand Up @@ -7132,7 +7157,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 },
Expand Down
18 changes: 18 additions & 0 deletions units/exit.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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)
ConditionVirtualization=container
DefaultDependencies=no
Requires=systemd-exit.service
After=systemd-exit.service
AllowIsolate=yes

[Install]
Alias=ctrl-alt-del.target

0 comments on commit ed344f1

Please sign in to comment.