X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fnspawn%2Fnspawn.c;h=7c47f6ecd2434356a3a1f6851fbdeaca2238f5bc;hp=8fb72d6d3632efd2a3a2f1c35db1f9e37b6df52b;hb=7566e26721ee95d6fc864e9e6654fb61bd3cd603;hpb=8baaf7a3d8c42970c5215f4dcf84393b84b07e78 diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 8fb72d6d3..7c47f6ecd 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -84,12 +83,12 @@ #include "def.h" #include "rtnl-util.h" #include "udev-util.h" -#include "eventfd-util.h" #include "blkid-util.h" #include "gpt.h" #include "siphash24.h" #include "copy.h" #include "base-filesystem.h" +#include "barrier.h" #ifdef HAVE_SECCOMP #include "seccomp-util.h" @@ -107,6 +106,12 @@ typedef enum LinkJournal { LINK_GUEST } LinkJournal; +typedef enum Volatile { + VOLATILE_NO, + VOLATILE_YES, + VOLATILE_STATE, +} Volatile; + static char *arg_directory = NULL; static char *arg_user = NULL; static sd_id128_t arg_uuid = {}; @@ -159,6 +164,7 @@ static bool arg_network_veth = false; static const char *arg_network_bridge = NULL; static unsigned long arg_personality = 0xffffffffLU; static const char *arg_image = NULL; +static Volatile arg_volatile = VOLATILE_NO; static int help(void) { @@ -207,7 +213,8 @@ static int help(void) { " --share-system Share system namespaces with host\n" " --register=BOOLEAN Register container as machine\n" " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit nspawn is running in\n", + " the service unit nspawn is running in\n" + " --volatile[=MODE] Run the system in volatile mode\n", program_invocation_short_name); return 0; @@ -235,6 +242,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NETWORK_VETH, ARG_NETWORK_BRIDGE, ARG_PERSONALITY, + ARG_VOLATILE, }; static const struct option options[] = { @@ -267,6 +275,7 @@ static int parse_argv(int argc, char *argv[]) { { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, { "personality", required_argument, NULL, ARG_PERSONALITY }, { "image", required_argument, NULL, 'i' }, + { "volatile", optional_argument, NULL, ARG_VOLATILE }, {} }; @@ -559,6 +568,25 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_VOLATILE: + + if (!optarg) + arg_volatile = VOLATILE_YES; + else { + r = parse_boolean(optarg); + if (r < 0) { + if (streq(optarg, "state")) + arg_volatile = VOLATILE_STATE; + else { + log_error("Failed to parse --volatile= argument: %s", optarg); + return r; + } + } else + arg_volatile = r ? VOLATILE_YES : VOLATILE_NO; + } + + break; + case '?': return -EINVAL; @@ -585,6 +613,11 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_volatile != VOLATILE_NO && arg_read_only) { + log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); + return -EINVAL; + } + arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; return 1; @@ -712,7 +745,7 @@ static int mount_binds(const char *dest, char **l, bool ro) { } /* Create the mount point, but be conservative -- refuse to create block - * and char devices. */ + * and char devices. */ if (S_ISDIR(source_st.st_mode)) mkdir_label(where, 0755); else if (S_ISFIFO(source_st.st_mode)) @@ -796,7 +829,6 @@ static int setup_timezone(const char *dest) { if (!y) y = path_startswith(q, "/usr/share/zoneinfo/"); - /* Already pointing to the right place? Then do nothing .. */ if (y && streq(y, z)) return 0; @@ -815,7 +847,9 @@ static int setup_timezone(const char *dest) { if (!what) return log_oom(); + mkdir_parents(where, 0755); unlink(where); + if (symlink(what, where) < 0) { log_error("Failed to correct timezone of container: %m"); return 0; @@ -839,11 +873,105 @@ static int setup_resolv_conf(const char *dest) { /* We don't really care for the results of this really. If it * fails, it fails, but meh... */ + mkdir_parents(where, 0755); copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644); return 0; } +static int setup_volatile_state(const char *directory) { + const char *p; + int r; + + assert(directory); + + if (arg_volatile != VOLATILE_STATE) + return 0; + + /* --volatile=state means we simply overmount /var + with a tmpfs, and the rest read-only. */ + + r = bind_remount_recursive(directory, true); + if (r < 0) { + log_error("Failed to remount %s read-only: %s", directory, strerror(-r)); + return r; + } + + p = strappenda(directory, "/var"); + mkdir(p, 0755); + + if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, "mode=755") < 0) { + log_error("Failed to mount tmpfs to /var: %m"); + return -errno; + } + + return 0; +} + +static int setup_volatile(const char *directory) { + bool tmpfs_mounted = false, bind_mounted = false; + char template[] = "/tmp/nspawn-volatile-XXXXXX"; + const char *f, *t; + int r; + + assert(directory); + + if (arg_volatile != VOLATILE_YES) + return 0; + + /* --volatile=yes means we mount a tmpfs to the root dir, and + the original /usr to use inside it, and that read-only. */ + + if (!mkdtemp(template)) { + log_error("Failed to create temporary directory: %m"); + return -errno; + } + + if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, "mode=755") < 0) { + log_error("Failed to mount tmpfs for root directory: %m"); + r = -errno; + goto fail; + } + + tmpfs_mounted = true; + + f = strappenda(directory, "/usr"); + t = strappenda(template, "/usr"); + + mkdir(t, 0755); + if (mount(f, t, "bind", MS_BIND|MS_REC, NULL) < 0) { + log_error("Failed to create /usr bind mount: %m"); + r = -errno; + goto fail; + } + + bind_mounted = true; + + r = bind_remount_recursive(t, true); + if (r < 0) { + log_error("Failed to remount %s read-only: %s", t, strerror(-r)); + goto fail; + } + + if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { + log_error("Failed to move root mount: %m"); + r = -errno; + goto fail; + } + + rmdir(template); + + return 0; + +fail: + if (bind_mounted) + umount(t); + if (tmpfs_mounted) + umount(template); + rmdir(template); + return r; +} + static char* id128_format_as_uuid(sd_id128_t id, char s[37]) { snprintf(s, 37, @@ -1259,7 +1387,7 @@ static int drop_capabilities(void) { return capability_bounding_set_drop(~arg_retain, false); } -static int register_machine(pid_t pid) { +static int register_machine(pid_t pid, int local_ifindex) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_bus_unref_ sd_bus *bus = NULL; int r; @@ -1279,16 +1407,17 @@ static int register_machine(pid_t pid) { "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - "RegisterMachine", + "RegisterMachineWithNetwork", &error, NULL, - "sayssus", + "sayssusai", arg_machine, SD_BUS_MESSAGE_APPEND_ID128(arg_uuid), "nspawn", "container", (uint32_t) pid, - strempty(arg_directory)); + strempty(arg_directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); } else { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; @@ -1298,7 +1427,7 @@ static int register_machine(pid_t pid) { "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - "CreateMachine"); + "CreateMachineWithNetwork"); if (r < 0) { log_error("Failed to create message: %s", strerror(-r)); return r; @@ -1306,13 +1435,14 @@ static int register_machine(pid_t pid) { r = sd_bus_message_append( m, - "sayssus", + "sayssusai", arg_machine, SD_BUS_MESSAGE_APPEND_ID128(arg_uuid), "nspawn", "container", (uint32_t) pid, - strempty(arg_directory)); + strempty(arg_directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); if (r < 0) { log_error("Failed to append message arguments: %s", strerror(-r)); return r; @@ -1515,11 +1645,11 @@ static int get_mac(struct ether_addr *mac) { return 0; } -static int setup_veth(pid_t pid, char iface_name[IFNAMSIZ]) { +static int setup_veth(pid_t pid, char iface_name[IFNAMSIZ], int *ifi) { _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL; _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL; struct ether_addr mac; - int r; + int r, i; if (!arg_private_network) return 0; @@ -1619,10 +1749,18 @@ static int setup_veth(pid_t pid, char iface_name[IFNAMSIZ]) { return r; } + i = (int) if_nametoindex(iface_name); + if (i <= 0) { + log_error("Failed to resolve interface %s: %m", iface_name); + return -errno; + } + + *ifi = i; + return 0; } -static int setup_bridge(const char veth_name[]) { +static int setup_bridge(const char veth_name[], int *ifi) { _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL; _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL; int r, bridge; @@ -1642,6 +1780,8 @@ static int setup_bridge(const char veth_name[]) { return -errno; } + *ifi = bridge; + r = sd_rtnl_open(&rtnl, 0); if (r < 0) { log_error("Failed to connect to netlink: %s", strerror(-r)); @@ -1864,22 +2004,25 @@ static int setup_macvlan(pid_t pid) { return 0; } -static int audit_still_doesnt_work_in_containers(void) { +static int setup_seccomp(void) { #ifdef HAVE_SECCOMP + static const int blacklist[] = { + SCMP_SYS(kexec_load), + SCMP_SYS(open_by_handle_at), + SCMP_SYS(init_module), + SCMP_SYS(finit_module), + SCMP_SYS(delete_module), + SCMP_SYS(iopl), + SCMP_SYS(ioperm), + SCMP_SYS(swapon), + SCMP_SYS(swapoff), + }; + scmp_filter_ctx seccomp; + unsigned i; int r; - /* - Audit is broken in containers, much of the userspace audit - hookup will fail if running inside a container. We don't - care and just turn off creation of audit sockets. - - This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail - with EAFNOSUPPORT which audit userspace uses as indication - that audit is disabled in the kernel. - */ - seccomp = seccomp_init(SCMP_ACT_ALLOW); if (!seccomp) return log_oom(); @@ -1890,6 +2033,26 @@ static int audit_still_doesnt_work_in_containers(void) { goto finish; } + for (i = 0; i < ELEMENTSOF(blacklist); i++) { + r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), blacklist[i], 0); + if (r == -EFAULT) + continue; /* unknown syscall */ + if (r < 0) { + log_error("Failed to block syscall: %s", strerror(-r)); + goto finish; + } + } + + /* + Audit is broken in containers, much of the userspace audit + hookup will fail if running inside a container. We don't + care and just turn off creation of audit sockets. + + This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail + with EAFNOSUPPORT which audit userspace uses as indication + that audit is disabled in the kernel. + */ + r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EAFNOSUPPORT), @@ -2845,7 +3008,11 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(image_fd, &root_device, &root_device_rw, &home_device, &home_device_rw, &srv_device, &srv_device_rw, &secondary); + r = dissect_image(image_fd, + &root_device, &root_device_rw, + &home_device, &home_device_rw, + &srv_device, &srv_device_rw, + &secondary); if (r < 0) goto finish; } @@ -2906,12 +3073,18 @@ int main(int argc, char *argv[]) { for (;;) { ContainerStatus container_status; - int eventfds[2] = { -1, -1 }; + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; struct sigaction sa = { .sa_handler = nop_handler, .sa_flags = SA_NOCLDSTOP, }; + r = barrier_create(&barrier); + if (r < 0) { + log_error("Cannot initialize IPC barrier: %s", strerror(-r)); + goto finish; + } + /* Child can be killed before execv(), so handle SIGCHLD * in order to interrupt parent's blocking calls and * give it a chance to call wait() and terminate. */ @@ -2927,9 +3100,9 @@ int main(int argc, char *argv[]) { goto finish; } - pid = clone_with_eventfd(SIGCHLD|CLONE_NEWNS| - (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS)| - (arg_private_network ? CLONE_NEWNET : 0), eventfds); + pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWNS| + (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS)| + (arg_private_network ? CLONE_NEWNET : 0), NULL); if (pid < 0) { if (errno == EINVAL) log_error("clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); @@ -2958,6 +3131,8 @@ int main(int argc, char *argv[]) { }; char **env_use; + barrier_set_role(&barrier, BARRIER_CHILD); + envp[n_env] = strv_find_prefix(environ, "TERM="); if (envp[n_env]) n_env ++; @@ -2983,26 +3158,26 @@ int main(int argc, char *argv[]) { } log_error("Failed to open console: %s", strerror(-k)); - goto child_fail; + _exit(EXIT_FAILURE); } if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) { log_error("Failed to duplicate console: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } if (setsid() < 0) { log_error("setsid() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } if (reset_audit_loginuid() < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) { log_error("PR_SET_PDEATHSIG failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } /* Mark everything as slave, so that we still @@ -3010,108 +3185,109 @@ int main(int argc, char *argv[]) { * propagate mounts to the real root. */ if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { log_error("MS_SLAVE|MS_REC failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } if (mount_devices(arg_directory, root_device, root_device_rw, home_device, home_device_rw, srv_device, srv_device_rw) < 0) - goto child_fail; - - r = base_filesystem_create(arg_directory); - if (r < 0) { - log_error("Failed to create the base filesystem: %s", strerror(-r)); - goto child_fail; - } + _exit(EXIT_FAILURE); /* Turn directory into bind mount */ if (mount(arg_directory, arg_directory, "bind", MS_BIND|MS_REC, NULL) < 0) { log_error("Failed to make bind mount: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } + r = setup_volatile(arg_directory); + if (r < 0) + _exit(EXIT_FAILURE); + + if (setup_volatile_state(arg_directory) < 0) + _exit(EXIT_FAILURE); + + r = base_filesystem_create(arg_directory); + if (r < 0) + _exit(EXIT_FAILURE); + if (arg_read_only) { k = bind_remount_recursive(arg_directory, true); if (k < 0) { log_error("Failed to make tree read-only: %s", strerror(-k)); - goto child_fail; + _exit(EXIT_FAILURE); } } if (mount_all(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (copy_devnodes(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_ptmx(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); dev_setup(arg_directory); - if (audit_still_doesnt_work_in_containers() < 0) - goto child_fail; + if (setup_seccomp() < 0) + _exit(EXIT_FAILURE); if (setup_dev_console(arg_directory, console) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_kmsg(arg_directory, kmsg_socket_pair[1]) < 0) - goto child_fail; + _exit(EXIT_FAILURE); kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); if (setup_boot_id(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_timezone(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_resolv_conf(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_journal(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (mount_binds(arg_directory, arg_bind, false) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (mount_binds(arg_directory, arg_bind_ro, true) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (mount_tmpfs(arg_directory) < 0) - goto child_fail; + _exit(EXIT_FAILURE); if (setup_kdbus(arg_directory, kdbus_domain) < 0) - goto child_fail; + _exit(EXIT_FAILURE); /* Tell the parent that we are ready, and that * it can cgroupify us to that we lack access * to certain devices and resources. */ - r = eventfd_send_state(eventfds[1], - EVENTFD_CHILD_SUCCEEDED); - eventfds[1] = safe_close(eventfds[1]); - if (r < 0) - goto child_fail; + barrier_place(&barrier); if (chdir(arg_directory) < 0) { log_error("chdir(%s) failed: %m", arg_directory); - goto child_fail; + _exit(EXIT_FAILURE); } if (mount(arg_directory, "/", NULL, MS_MOVE, NULL) < 0) { log_error("mount(MS_MOVE) failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } if (chroot(".") < 0) { log_error("chroot() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } if (chdir("/") < 0) { log_error("chdir() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } umask(0022); @@ -3121,18 +3297,18 @@ int main(int argc, char *argv[]) { if (drop_capabilities() < 0) { log_error("drop_capabilities() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } r = change_uid_gid(&home); if (r < 0) - goto child_fail; + _exit(EXIT_FAILURE); if ((asprintf((char**)(envp + n_env++), "HOME=%s", home ? home: "/root") < 0) || (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ? arg_user : "root") < 0) || (asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) { log_oom(); - goto child_fail; + _exit(EXIT_FAILURE); } if (!sd_id128_equal(arg_uuid, SD_ID128_NULL)) { @@ -3140,7 +3316,7 @@ int main(int argc, char *argv[]) { if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0) { log_oom(); - goto child_fail; + _exit(EXIT_FAILURE); } } @@ -3148,13 +3324,13 @@ int main(int argc, char *argv[]) { k = fdset_cloexec(fds, false); if (k < 0) { log_error("Failed to unset O_CLOEXEC for file descriptors."); - goto child_fail; + _exit(EXIT_FAILURE); } if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", n_fd_passed) < 0) || (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) { log_oom(); - goto child_fail; + _exit(EXIT_FAILURE); } } @@ -3163,12 +3339,12 @@ int main(int argc, char *argv[]) { if (arg_personality != 0xffffffffLU) { if (personality(arg_personality) < 0) { log_error("personality() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } } else if (secondary) { if (personality(PER_LINUX32) < 0) { log_error("personality() failed: %m"); - goto child_fail; + _exit(EXIT_FAILURE); } } @@ -3176,7 +3352,7 @@ int main(int argc, char *argv[]) { if (arg_selinux_context) if (setexeccon((security_context_t) arg_selinux_context) < 0) { log_error("setexeccon(\"%s\") failed: %m", arg_selinux_context); - goto child_fail; + _exit(EXIT_FAILURE); } #endif @@ -3186,7 +3362,7 @@ int main(int argc, char *argv[]) { n = strv_env_merge(2, envp, arg_setenv); if (!n) { log_oom(); - goto child_fail; + _exit(EXIT_FAILURE); } env_use = n; @@ -3194,10 +3370,8 @@ int main(int argc, char *argv[]) { env_use = (char**) envp; /* Wait until the parent is ready with the setup, too... */ - r = eventfd_parent_succeeded(eventfds[0]); - eventfds[0] = safe_close(eventfds[0]); - if (r < 0) - goto child_fail; + if (!barrier_place_and_sync(&barrier)) + _exit(EXIT_FAILURE); if (arg_boot) { char **a; @@ -3226,100 +3400,99 @@ int main(int argc, char *argv[]) { } log_error("execv() failed: %m"); - - child_fail: - /* Tell the parent that the setup failed, so he - * can clean up resources and terminate. */ - if (eventfds[1] != -1) - eventfd_send_state(eventfds[1], - EVENTFD_CHILD_FAILED); _exit(EXIT_FAILURE); } + barrier_set_role(&barrier, BARRIER_PARENT); fdset_free(fds); fds = NULL; - /* Wait for the child event: - * If EVENTFD_CHILD_FAILED, the child will terminate soon. - * If EVENTFD_CHILD_SUCCEEDED, the child is reporting that - * it is ready with all it needs to do with priviliges. - * After we got the notification we can make the process - * join its cgroup which might limit what it can do */ - r = eventfd_child_succeeded(eventfds[1]); - eventfds[1] = safe_close(eventfds[1]); - if (r < 0) - goto check_container_status; + /* wait for child-setup to be done */ + if (barrier_place_and_sync(&barrier)) { + int ifi = 0; - r = register_machine(pid); - if (r < 0) - goto finish; + r = move_network_interfaces(pid); + if (r < 0) + goto finish; - r = move_network_interfaces(pid); - if (r < 0) - goto finish; + r = setup_veth(pid, veth_name, &ifi); + if (r < 0) + goto finish; - r = setup_veth(pid, veth_name); - if (r < 0) - goto finish; + r = setup_bridge(veth_name, &ifi); + if (r < 0) + goto finish; - r = setup_bridge(veth_name); - if (r < 0) - goto finish; + r = setup_macvlan(pid); + if (r < 0) + goto finish; - r = setup_macvlan(pid); - if (r < 0) - goto finish; + r = register_machine(pid, ifi); + if (r < 0) + goto finish; - /* Block SIGCHLD here, before notifying child. - * process_pty() will handle it with the other signals. */ - r = sigprocmask(SIG_BLOCK, &mask_chld, NULL); - if (r < 0) - goto finish; + /* Block SIGCHLD here, before notifying child. + * process_pty() will handle it with the other signals. */ + r = sigprocmask(SIG_BLOCK, &mask_chld, NULL); + if (r < 0) + goto finish; - /* Reset signal to default */ - r = default_signals(SIGCHLD, -1); - if (r < 0) - goto finish; + /* Reset signal to default */ + r = default_signals(SIGCHLD, -1); + if (r < 0) + goto finish; - /* Notify the child that the parent is ready with all - * its setup, and that the child can now hand over - * control to the code to run inside the container. */ - r = eventfd_send_state(eventfds[0], - EVENTFD_PARENT_SUCCEEDED); - eventfds[0] = safe_close(eventfds[0]); - if (r < 0) - goto finish; + /* Notify the child that the parent is ready with all + * its setup, and that the child can now hand over + * control to the code to run inside the container. */ + barrier_place(&barrier); - k = process_pty(master, &mask, arg_boot ? pid : 0, SIGRTMIN+3); - if (k < 0) { - r = EXIT_FAILURE; - break; - } + k = process_pty(master, &mask, arg_boot ? pid : 0, SIGRTMIN+3); + if (k < 0) { + r = EXIT_FAILURE; + break; + } - if (!arg_quiet) - putc('\n', stdout); + if (!arg_quiet) + putc('\n', stdout); - /* Kill if it is not dead yet anyway */ - terminate_machine(pid); + /* Kill if it is not dead yet anyway */ + terminate_machine(pid); + } -check_container_status: - /* Redundant, but better safe than sorry */ + /* Normally redundant, but better safe than sorry */ kill(pid, SIGKILL); r = wait_for_container(pid, &container_status); pid = 0; - if (r != 0) { - /* If r < 0, explicitly set to EXIT_FAILURE, - * otherwise return the exit code of the - * containered process. */ - if (r < 0) - r = EXIT_FAILURE; + if (r < 0) { + /* We failed to wait for the container, or the + * container exited abnormally */ + r = EXIT_FAILURE; break; - } else if (container_status == CONTAINER_TERMINATED) + } else if (r > 0 || container_status == CONTAINER_TERMINATED) + /* The container exited with a non-zero + * status, or with zero status and no reboot + * was requested. */ break; /* CONTAINER_REBOOTED, loop again */ + + if (arg_keep_unit) { + /* Special handling if we are running as a + * service: instead of simply restarting the + * machine we want to restart the entire + * service, so let's inform systemd about this + * with the special exit code 133. The service + * file uses RestartForceExitStatus=133 so + * that this results in a full nspawn + * restart. This is necessary since we might + * have cgroup parameters set we want to have + * flushed out. */ + r = 133; + break; + } } finish: