#include <sys/un.h>
#include <sys/socket.h>
#include <linux/netlink.h>
-#include <sys/eventfd.h>
#include <net/if.h>
#include <linux/veth.h>
#include <sys/personality.h>
#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"
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 = {};
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) {
-
+static void help(void) {
printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
"Spawn a minimal namespace container for debugging, testing and building.\n\n"
" -h --help Show this help\n"
" --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;
}
static int parse_argv(int argc, char *argv[]) {
ARG_NETWORK_VETH,
ARG_NETWORK_BRIDGE,
ARG_PERSONALITY,
+ ARG_VOLATILE,
};
static const struct option options[] = {
{ "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 },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:qi:", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:qi:", options, NULL)) >= 0)
switch (c) {
case 'h':
- return help();
+ help();
+ return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
case ARG_CAPABILITY:
case ARG_DROP_CAPABILITY: {
- char *state, *word;
+ const char *state, *word;
size_t length;
FOREACH_WORD_SEPARATOR(word, length, optarg, ",", state) {
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;
default:
assert_not_reached("Unhandled option");
}
- }
if (arg_share_system)
arg_register = false;
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;
}
/* 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))
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;
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;
/* 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,
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;
+ _cleanup_bus_close_unref_ sd_bus *bus = NULL;
int r;
if (!arg_register)
"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;
"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;
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;
static int terminate_machine(pid_t pid) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
- _cleanup_bus_unref_ sd_bus *bus = NULL;
+ _cleanup_bus_close_unref_ sd_bus *bus = NULL;
const char *path;
int r;
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;
/* Use two different interface name prefixes depending whether
* we are in bridge mode or not. */
- if (arg_network_bridge)
- memcpy(iface_name, "vb-", 3);
- else
- memcpy(iface_name, "ve-", 3);
- strncpy(iface_name+3, arg_machine, IFNAMSIZ - 3);
+ snprintf(iface_name, IFNAMSIZ, "%s-%s",
+ arg_network_bridge ? "vb" : "ve", arg_machine);
r = get_mac(&mac);
if (r < 0) {
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;
return -errno;
}
+ *ifi = bridge;
+
r = sd_rtnl_open(&rtnl, 0);
if (r < 0) {
log_error("Failed to connect to netlink: %s", strerror(-r));
}
static int change_uid_gid(char **_home) {
- char line[LINE_MAX], *w, *x, *state, *u, *g, *h;
+ char line[LINE_MAX], *x, *u, *g, *h;
+ const char *word, *state;
_cleanup_free_ uid_t *uids = NULL;
_cleanup_free_ char *home = NULL;
_cleanup_fclose_ FILE *f = NULL;
x += strcspn(x, WHITESPACE);
x += strspn(x, WHITESPACE);
- FOREACH_WORD(w, l, x, state) {
+ FOREACH_WORD(word, l, x, state) {
char c[l+1];
- memcpy(c, w, l);
+ memcpy(c, word, l);
c[l] = 0;
if (!GREEDY_REALLOC(uids, sz, n_uids+1))
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;
}
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. */
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");
};
char **env_use;
+ barrier_set_role(&barrier, BARRIER_CHILD);
+
envp[n_env] = strv_find_prefix(environ, "TERM=");
if (envp[n_env])
n_env ++;
}
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
* 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 (setup_seccomp() < 0)
- goto child_fail;
+ _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);
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)) {
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);
}
}
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);
}
}
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);
}
}
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
n = strv_env_merge(2, envp, arg_setenv);
if (!n) {
log_oom();
- goto child_fail;
+ _exit(EXIT_FAILURE);
}
env_use = n;
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;
}
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) {
- r = register_machine(pid);
- if (r < 0)
- goto finish;
+ /* wait for child-setup to be done */
+ if (barrier_place_and_sync(&barrier)) {
+ int ifi = 0;
r = move_network_interfaces(pid);
if (r < 0)
goto finish;
- r = setup_veth(pid, veth_name);
+ r = setup_veth(pid, veth_name, &ifi);
if (r < 0)
goto finish;
- r = setup_bridge(veth_name);
+ r = setup_bridge(veth_name, &ifi);
if (r < 0)
goto finish;
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);
/* 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;
+ barrier_place(&barrier);
k = process_pty(master, &mask, arg_boot ? pid : 0, SIGRTMIN+3);
if (k < 0) {