+ r = seccomp_load(seccomp);
+ if (r < 0)
+ log_error("Failed to install seccomp audit filter: %s", strerror(-r));
+
+finish:
+ seccomp_release(seccomp);
+ return r;
+#else
+ return 0;
+#endif
+
+}
+
+static int setup_image(char **device_path, int *loop_nr) {
+ struct loop_info64 info = {
+ .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN
+ };
+ _cleanup_close_ int fd = -1, control = -1, loop = -1;
+ _cleanup_free_ char* loopdev = NULL;
+ struct stat st;
+ int r, nr;
+
+ assert(device_path);
+ assert(loop_nr);
+
+ fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY);
+ if (fd < 0) {
+ log_error("Failed to open %s: %m", arg_image);
+ return -errno;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ log_error("Failed to stat %s: %m", arg_image);
+ return -errno;
+ }
+
+ if (S_ISBLK(st.st_mode)) {
+ char *p;
+
+ p = strdup(arg_image);
+ if (!p)
+ return log_oom();
+
+ *device_path = p;
+
+ *loop_nr = -1;
+
+ r = fd;
+ fd = -1;
+
+ return r;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ log_error("%s is not a regular file or block device: %m", arg_image);
+ return -EINVAL;
+ }
+
+ control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (control < 0) {
+ log_error("Failed to open /dev/loop-control: %m");
+ return -errno;
+ }
+
+ nr = ioctl(control, LOOP_CTL_GET_FREE);
+ if (nr < 0) {
+ log_error("Failed to allocate loop device: %m");
+ return -errno;
+ }
+
+ if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
+ return log_oom();
+
+ loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY);
+ if (loop < 0) {
+ log_error("Failed to open loop device %s: %m", loopdev);
+ return -errno;
+ }
+
+ if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
+ log_error("Failed to set loopback file descriptor on %s: %m", loopdev);
+ return -errno;
+ }
+
+ if (arg_read_only)
+ info.lo_flags |= LO_FLAGS_READ_ONLY;
+
+ if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
+ log_error("Failed to set loopback settings on %s: %m", loopdev);
+ return -errno;
+ }
+
+ *device_path = loopdev;
+ loopdev = NULL;
+
+ *loop_nr = nr;
+
+ r = loop;
+ loop = -1;
+
+ return r;
+}
+
+static int dissect_image(
+ int fd,
+ char **root_device, bool *root_device_rw,
+ char **home_device, bool *home_device_rw,
+ char **srv_device, bool *srv_device_rw,
+ bool *secondary) {
+
+#ifdef HAVE_BLKID
+ int home_nr = -1, root_nr = -1, secondary_root_nr = -1, srv_nr = -1;
+ _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL;
+ _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *d = NULL;
+ _cleanup_blkid_free_probe_ blkid_probe b = NULL;
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ struct udev_list_entry *first, *item;
+ bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true;
+ const char *pttype = NULL;
+ blkid_partlist pl;
+ struct stat st;
+ int r;
+
+ assert(fd >= 0);
+ assert(root_device);
+ assert(home_device);
+ assert(srv_device);
+ assert(secondary);
+
+ b = blkid_new_probe();
+ if (!b)
+ return log_oom();
+
+ errno = 0;
+ r = blkid_probe_set_device(b, fd, 0, 0);
+ if (r != 0) {
+ if (errno == 0)
+ return log_oom();
+
+ log_error("Failed to set device on blkid probe: %m");
+ return -errno;
+ }
+
+ blkid_probe_enable_partitions(b, 1);
+ blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
+
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (r == -2 || r == 1) {
+ log_error("Failed to identify any partition table on %s.\n"
+ "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
+ return -EINVAL;
+ } else if (r != 0) {
+ if (errno == 0)
+ errno = EIO;
+ log_error("Failed to probe: %m");
+ return -errno;
+ }
+
+ blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
+ if (!streq_ptr(pttype, "gpt")) {
+ log_error("Image %s does not carry a GUID Partition Table.\n"
+ "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
+ return -EINVAL;
+ }
+
+ errno = 0;
+ pl = blkid_probe_get_partitions(b);
+ if (!pl) {
+ if (errno == 0)
+ return log_oom();
+
+ log_error("Failed to list partitions of %s", arg_image);
+ return -errno;
+ }
+
+ udev = udev_new();
+ if (!udev)
+ return log_oom();
+
+ if (fstat(fd, &st) < 0) {
+ log_error("Failed to stat block device: %m");
+ return -errno;
+ }
+
+ d = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
+ if (!d)
+ return log_oom();
+
+ e = udev_enumerate_new(udev);
+ if (!e)
+ return log_oom();
+
+ r = udev_enumerate_add_match_parent(e, d);
+ if (r < 0)
+ return log_oom();
+
+ r = udev_enumerate_scan_devices(e);
+ if (r < 0) {
+ log_error("Failed to scan for partition devices of %s: %s", arg_image, strerror(-r));
+ return r;
+ }
+
+ first = udev_enumerate_get_list_entry(e);
+ udev_list_entry_foreach(item, first) {
+ _cleanup_udev_device_unref_ struct udev_device *q;
+ const char *stype, *node;
+ unsigned long long flags;
+ sd_id128_t type_id;
+ blkid_partition pp;
+ dev_t qn;
+ int nr;
+
+ errno = 0;
+ q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+ if (!q) {
+ if (!errno)
+ errno = ENOMEM;
+
+ log_error("Failed to get partition device of %s: %m", arg_image);
+ return -errno;
+ }
+
+ qn = udev_device_get_devnum(q);
+ if (major(qn) == 0)
+ continue;
+
+ if (st.st_rdev == qn)
+ continue;
+
+ node = udev_device_get_devnode(q);
+ if (!node)
+ continue;
+
+ pp = blkid_partlist_devno_to_partition(pl, qn);
+ if (!pp)
+ continue;
+
+ flags = blkid_partition_get_flags(pp);
+ if (flags & GPT_FLAG_NO_AUTO)
+ continue;
+
+ nr = blkid_partition_get_partno(pp);
+ if (nr < 0)
+ continue;
+
+ stype = blkid_partition_get_type_string(pp);
+ if (!stype)
+ continue;
+
+ if (sd_id128_from_string(stype, &type_id) < 0)
+ continue;
+
+ if (sd_id128_equal(type_id, GPT_HOME)) {
+
+ if (home && nr >= home_nr)
+ continue;
+
+ home_nr = nr;
+ home_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+ free(home);
+ home = strdup(node);
+ if (!home)
+ return log_oom();
+ } else if (sd_id128_equal(type_id, GPT_SRV)) {
+
+ if (srv && nr >= srv_nr)
+ continue;
+
+ srv_nr = nr;
+ srv_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+ free(srv);
+ srv = strdup(node);
+ if (!srv)
+ return log_oom();
+ }
+#ifdef GPT_ROOT_NATIVE
+ else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
+
+ if (root && nr >= root_nr)
+ continue;
+
+ root_nr = nr;
+ root_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+ free(root);
+ root = strdup(node);
+ if (!root)
+ return log_oom();
+ }
+#endif
+#ifdef GPT_ROOT_SECONDARY
+ else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
+
+ if (secondary_root && nr >= secondary_root_nr)
+ continue;
+
+ secondary_root_nr = nr;
+ secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+
+ free(secondary_root);
+ secondary_root = strdup(node);
+ if (!secondary_root)
+ return log_oom();
+ }
+#endif
+ }
+
+ if (!root && !secondary_root) {
+ log_error("Failed to identify root partition in disk image %s.\n"
+ "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
+ return -EINVAL;
+ }
+
+ if (root) {
+ *root_device = root;
+ root = NULL;
+
+ *root_device_rw = root_rw;
+ *secondary = false;
+ } else if (secondary_root) {
+ *root_device = secondary_root;
+ secondary_root = NULL;
+
+ *root_device_rw = secondary_root_rw;
+ *secondary = true;
+ }
+
+ if (home) {
+ *home_device = home;
+ home = NULL;
+
+ *home_device_rw = home_rw;
+ }
+
+ if (srv) {
+ *srv_device = srv;
+ srv = NULL;
+
+ *srv_device_rw = srv_rw;
+ }
+
+ return 0;
+#else
+ log_error("--image= is not supported, compiled without blkid support.");
+ return -ENOTSUP;
+#endif
+}
+
+static int mount_device(const char *what, const char *where, const char *directory, bool rw) {
+#ifdef HAVE_BLKID
+ _cleanup_blkid_free_probe_ blkid_probe b = NULL;
+ const char *fstype, *p;
+ int r;
+
+ assert(what);
+ assert(where);
+
+ if (arg_read_only)
+ rw = false;
+
+ if (directory)
+ p = strappenda(where, directory);
+ else
+ p = where;
+
+ errno = 0;
+ b = blkid_new_probe_from_filename(what);
+ if (!b) {
+ if (errno == 0)
+ return log_oom();
+ log_error("Failed to allocate prober for %s: %m", what);
+ return -errno;
+ }
+
+ blkid_probe_enable_superblocks(b, 1);
+ blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
+
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (r == -1 || r == 1) {
+ log_error("Cannot determine file system type of %s", what);
+ return -EINVAL;
+ } else if (r != 0) {
+ if (errno == 0)
+ errno = EIO;
+ log_error("Failed to probe %s: %m", what);
+ return -errno;
+ }
+
+ errno = 0;
+ if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) {
+ if (errno == 0)
+ errno = EINVAL;
+ log_error("Failed to determine file system type of %s", what);
+ return -errno;
+ }
+
+ if (streq(fstype, "crypto_LUKS")) {
+ log_error("nspawn currently does not support LUKS disk images.");
+ return -ENOTSUP;
+ }
+
+ if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) {
+ log_error("Failed to mount %s: %m", what);
+ return -errno;
+ }
+
+ return 0;
+#else
+ log_error("--image= is not supported, compiled without blkid support.");
+ return -ENOTSUP;
+#endif
+}
+
+static int mount_devices(
+ const char *where,
+ const char *root_device, bool root_device_rw,
+ const char *home_device, bool home_device_rw,
+ const char *srv_device, bool srv_device_rw) {
+ int r;
+
+ assert(where);
+
+ if (root_device) {
+ r = mount_device(root_device, arg_directory, NULL, root_device_rw);
+ if (r < 0) {
+ log_error("Failed to mount root directory: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ if (home_device) {
+ r = mount_device(home_device, arg_directory, "/home", home_device_rw);
+ if (r < 0) {
+ log_error("Failed to mount home directory: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ if (srv_device) {
+ r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw);
+ if (r < 0) {
+ log_error("Failed to mount server data directory: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static void loop_remove(int nr, int *image_fd) {
+ _cleanup_close_ int control = -1;
+
+ if (nr < 0)
+ return;
+
+ if (image_fd && *image_fd >= 0) {
+ ioctl(*image_fd, LOOP_CLR_FD);
+ *image_fd = safe_close(*image_fd);
+ }
+
+ control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (control < 0)
+ return;
+
+ ioctl(control, LOOP_CTL_REMOVE, nr);
+}
+
+static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
+ int pipe_fds[2];
+ pid_t pid;
+
+ assert(database);
+ assert(key);
+ assert(rpid);
+
+ if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
+ log_error("Failed to allocate pipe: %m");
+ return -errno;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_error("Failed to fork getent child: %m");
+ return -errno;
+ } else if (pid == 0) {
+ int nullfd;
+ char *empty_env = NULL;
+
+ if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (pipe_fds[0] > 2)
+ safe_close(pipe_fds[0]);
+ if (pipe_fds[1] > 2)
+ safe_close(pipe_fds[1]);
+
+ nullfd = open("/dev/null", O_RDWR);
+ if (nullfd < 0)
+ _exit(EXIT_FAILURE);
+
+ if (dup3(nullfd, STDIN_FILENO, 0) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (dup3(nullfd, STDERR_FILENO, 0) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (nullfd > 2)
+ safe_close(nullfd);
+
+ reset_all_signal_handlers();
+ close_all_fds(NULL, 0);
+
+ execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
+ execle("/bin/getent", "getent", database, key, NULL, &empty_env);
+ _exit(EXIT_FAILURE);
+ }
+
+ pipe_fds[1] = safe_close(pipe_fds[1]);
+
+ *rpid = pid;
+
+ return pipe_fds[0];
+}
+
+static int change_uid_gid(char **_home) {
+ char line[LINE_MAX], *w, *x, *state, *u, *g, *h;
+ _cleanup_free_ uid_t *uids = NULL;
+ _cleanup_free_ char *home = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_close_ int fd = -1;
+ unsigned n_uids = 0;
+ size_t sz = 0, l;
+ uid_t uid;
+ gid_t gid;
+ pid_t pid;
+ int r;
+
+ assert(_home);
+
+ if (!arg_user || streq(arg_user, "root") || streq(arg_user, "0")) {
+ /* Reset everything fully to 0, just in case */
+
+ if (setgroups(0, NULL) < 0) {
+ log_error("setgroups() failed: %m");
+ return -errno;
+ }
+
+ if (setresgid(0, 0, 0) < 0) {
+ log_error("setregid() failed: %m");
+ return -errno;
+ }
+
+ if (setresuid(0, 0, 0) < 0) {
+ log_error("setreuid() failed: %m");
+ return -errno;
+ }
+
+ *_home = NULL;
+ return 0;
+ }
+
+ /* First, get user credentials */
+ fd = spawn_getent("passwd", arg_user, &pid);
+ if (fd < 0)
+ return fd;
+
+ f = fdopen(fd, "r");
+ if (!f)
+ return log_oom();
+ fd = -1;
+
+ if (!fgets(line, sizeof(line), f)) {
+
+ if (!ferror(f)) {
+ log_error("Failed to resolve user %s.", arg_user);
+ return -ESRCH;
+ }
+
+ log_error("Failed to read from getent: %m");
+ return -errno;
+ }
+
+ truncate_nl(line);
+
+ wait_for_terminate_and_warn("getent passwd", pid);
+
+ x = strchr(line, ':');
+ if (!x) {
+ log_error("/etc/passwd entry has invalid user field.");
+ return -EIO;
+ }
+
+ u = strchr(x+1, ':');
+ if (!u) {
+ log_error("/etc/passwd entry has invalid password field.");
+ return -EIO;
+ }
+
+ u++;
+ g = strchr(u, ':');
+ if (!g) {
+ log_error("/etc/passwd entry has invalid UID field.");
+ return -EIO;
+ }
+
+ *g = 0;
+ g++;
+ x = strchr(g, ':');
+ if (!x) {
+ log_error("/etc/passwd entry has invalid GID field.");
+ return -EIO;
+ }
+
+ *x = 0;
+ h = strchr(x+1, ':');
+ if (!h) {
+ log_error("/etc/passwd entry has invalid GECOS field.");
+ return -EIO;
+ }
+
+ h++;
+ x = strchr(h, ':');
+ if (!x) {
+ log_error("/etc/passwd entry has invalid home directory field.");
+ return -EIO;
+ }
+
+ *x = 0;
+
+ r = parse_uid(u, &uid);
+ if (r < 0) {
+ log_error("Failed to parse UID of user.");
+ return -EIO;
+ }
+
+ r = parse_gid(g, &gid);
+ if (r < 0) {
+ log_error("Failed to parse GID of user.");
+ return -EIO;
+ }
+
+ home = strdup(h);
+ if (!home)
+ return log_oom();
+
+ /* Second, get group memberships */
+ fd = spawn_getent("initgroups", arg_user, &pid);
+ if (fd < 0)
+ return fd;
+
+ fclose(f);
+ f = fdopen(fd, "r");
+ if (!f)
+ return log_oom();
+ fd = -1;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (!ferror(f)) {
+ log_error("Failed to resolve user %s.", arg_user);
+ return -ESRCH;
+ }
+
+ log_error("Failed to read from getent: %m");
+ return -errno;
+ }
+
+ truncate_nl(line);
+
+ wait_for_terminate_and_warn("getent initgroups", pid);
+
+ /* Skip over the username and subsequent separator whitespace */
+ x = line;
+ x += strcspn(x, WHITESPACE);
+ x += strspn(x, WHITESPACE);
+
+ FOREACH_WORD(w, l, x, state) {
+ char c[l+1];
+
+ memcpy(c, w, l);
+ c[l] = 0;
+
+ if (!GREEDY_REALLOC(uids, sz, n_uids+1))
+ return log_oom();
+
+ r = parse_uid(c, &uids[n_uids++]);
+ if (r < 0) {
+ log_error("Failed to parse group data from getent.");
+ return -EIO;
+ }
+ }
+
+ r = mkdir_parents(home, 0775);
+ if (r < 0) {
+ log_error("Failed to make home root directory: %s", strerror(-r));
+ return r;
+ }
+
+ r = mkdir_safe(home, 0755, uid, gid);
+ if (r < 0 && r != -EEXIST) {
+ log_error("Failed to make home directory: %s", strerror(-r));
+ return r;
+ }
+
+ fchown(STDIN_FILENO, uid, gid);
+ fchown(STDOUT_FILENO, uid, gid);
+ fchown(STDERR_FILENO, uid, gid);
+
+ if (setgroups(n_uids, uids) < 0) {
+ log_error("Failed to set auxiliary groups: %m");
+ return -errno;
+ }
+
+ if (setresgid(gid, gid, gid) < 0) {
+ log_error("setregid() failed: %m");
+ return -errno;
+ }
+
+ if (setresuid(uid, uid, uid) < 0) {
+ log_error("setreuid() failed: %m");
+ return -errno;
+ }
+
+ if (_home) {
+ *_home = home;
+ home = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Return values:
+ * < 0 : wait_for_terminate() failed to get the state of the
+ * container, the container was terminated by a signal, or
+ * failed for an unknown reason. No change is made to the
+ * container argument.
+ * > 0 : The program executed in the container terminated with an
+ * error. The exit code of the program executed in the
+ * container is returned. No change is made to the container
+ * argument.
+ * 0 : The container is being rebooted, has been shut down or exited
+ * successfully. The container argument has been set to either
+ * CONTAINER_TERMINATED or CONTAINER_REBOOTED.
+ *
+ * That is, success is indicated by a return value of zero, and an
+ * error is indicated by a non-zero value.
+ */
+static int wait_for_container(pid_t pid, ContainerStatus *container) {
+ int r;
+ siginfo_t status;
+
+ r = wait_for_terminate(pid, &status);
+ if (r < 0) {
+ log_warning("Failed to wait for container: %s", strerror(-r));
+ return r;
+ }
+
+ switch (status.si_code) {
+ case CLD_EXITED:
+ r = status.si_status;
+ if (r == 0) {
+ if (!arg_quiet)
+ log_debug("Container %s exited successfully.",
+ arg_machine);
+
+ *container = CONTAINER_TERMINATED;
+ } else {
+ log_error("Container %s failed with error code %i.",
+ arg_machine, status.si_status);
+ }
+ break;
+
+ case CLD_KILLED:
+ if (status.si_status == SIGINT) {
+ if (!arg_quiet)
+ log_info("Container %s has been shut down.",
+ arg_machine);
+
+ *container = CONTAINER_TERMINATED;
+ r = 0;
+ break;
+ } else if (status.si_status == SIGHUP) {
+ if (!arg_quiet)
+ log_info("Container %s is being rebooted.",
+ arg_machine);
+
+ *container = CONTAINER_REBOOTED;
+ r = 0;
+ break;
+ }
+ /* CLD_KILLED fallthrough */
+
+ case CLD_DUMPED:
+ log_error("Container %s terminated by signal %s.",
+ arg_machine, signal_to_string(status.si_status));
+ r = -1;
+ break;
+
+ default:
+ log_error("Container %s failed due to unknown reason.",
+ arg_machine);
+ r = -1;
+ break;
+ }
+
+ return r;
+}