X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fnspawn%2Fnspawn.c;h=4e465dfe4f6f510a2a4012880e9c493b76c73dfe;hp=e71d9a88ad8867f79a728d240da8a730c6bfefc0;hb=bbb99c30d01a8bcdc27fb151cc6376a7877a6b07;hpb=2fbe4296c5ba5bcce3ac845f196c60a88e3181fe diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e71d9a88a..4e465dfe4 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -180,6 +180,7 @@ static bool arg_register = true; static bool arg_keep_unit = false; static char **arg_network_interfaces = NULL; static char **arg_network_macvlan = NULL; +static char **arg_network_ipvlan = NULL; static bool arg_network_veth = false; static const char *arg_network_bridge = NULL; static unsigned long arg_personality = 0xffffffffLU; @@ -211,6 +212,9 @@ static void help(void) { " --network-macvlan=INTERFACE\n" " Create a macvlan network interface based on an\n" " existing network interface to the container\n" + " --network-ipvlan=INTERFACE\n" + " Create a ipvlan network interface based on an\n" + " existing network interface to the container\n" " -n --network-veth Add a virtual ethernet connection between host\n" " and container\n" " --network-bridge=INTERFACE\n" @@ -285,6 +289,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_KEEP_UNIT, ARG_NETWORK_INTERFACE, ARG_NETWORK_MACVLAN, + ARG_NETWORK_IPVLAN, ARG_NETWORK_BRIDGE, ARG_PERSONALITY, ARG_VOLATILE, @@ -319,6 +324,7 @@ static int parse_argv(int argc, char *argv[]) { { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, + { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, { "network-veth", no_argument, NULL, 'n' }, { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, { "personality", required_argument, NULL, ARG_PERSONALITY }, @@ -401,6 +407,13 @@ static int parse_argv(int argc, char *argv[]) { if (strv_extend(&arg_network_macvlan, optarg) < 0) return log_oom(); + arg_private_network = true; + break; + + case ARG_NETWORK_IPVLAN: + if (strv_extend(&arg_network_ipvlan, optarg) < 0) + return log_oom(); + /* fall through */ case ARG_PRIVATE_NETWORK: @@ -803,6 +816,7 @@ static int mount_all(const char *dest) { { "devpts", "/dev/pts", "devpts","newinstance,ptmxmode=0666,mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, true }, { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true }, { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true }, + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true }, #ifdef HAVE_SELINUX { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false }, /* Bind mount first */ { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, false }, /* Then, make it r/o */ @@ -900,8 +914,12 @@ static int mount_binds(const char *dest, char **l, bool ro) { r = stat(where, &dest_st); if (r == 0) { - if ((source_st.st_mode & S_IFMT) != (dest_st.st_mode & S_IFMT)) { - log_error("The file types of %s and %s do not match. Refusing bind mount", *x, where); + if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount directory %s on file %s.", *x, where); + return -EINVAL; + } + if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount file %s on directory %s.", *x, where); return -EINVAL; } } else if (errno == ENOENT) { @@ -913,27 +931,18 @@ static int mount_binds(const char *dest, char **l, bool ro) { return -errno; } - /* Create the mount point, but be conservative -- refuse to create block - * and char devices. */ + /* Create the mount point. Any non-directory file can be + * mounted on any non-directory file (regular, fifo, socket, + * char, block). + */ if (S_ISDIR(source_st.st_mode)) { r = mkdir_label(where, 0755); if (r < 0 && errno != EEXIST) return log_error_errno(r, "Failed to create mount point %s: %m", where); - } else if (S_ISFIFO(source_st.st_mode)) { - r = mkfifo(where, 0644); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create mount point %s: %m", where); - } else if (S_ISSOCK(source_st.st_mode)) { - r = mknod(where, 0644 | S_IFSOCK, 0); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create mount point %s: %m", where); - } else if (S_ISREG(source_st.st_mode)) { + } else { r = touch(where); if (r < 0) return log_error_errno(r, "Failed to create mount point %s: %m", where); - } else { - log_error("Refusing to create mountpoint for file: %s", *x); - return -ENOTSUP; } if (mount(*x, where, "bind", MS_BIND, NULL) < 0) @@ -2381,6 +2390,87 @@ static int setup_macvlan(pid_t pid) { return 0; } +static int setup_ipvlan(pid_t pid) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL; + char **i; + int r; + + if (!arg_private_network) + return 0; + + if (strv_isempty(arg_network_ipvlan)) + return 0; + + r = sd_rtnl_open(&rtnl, 0); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, arg_network_ipvlan) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL; + _cleanup_free_ char *n = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_rtnl_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("iv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_rtnl_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_rtnl_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_rtnl_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_rtnl_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_rtnl_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); + if (r < 0) + return log_error_errno(r, "Failed to add ipvlan mode: %m"); + + r = sd_rtnl_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_rtnl_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_rtnl_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); + } + + return 0; +} + static int setup_seccomp(void) { #ifdef HAVE_SECCOMP @@ -2559,66 +2649,10 @@ static int setup_image(char **device_path, int *loop_nr) { return r; } -static int wait_for_block_device(struct udev *udev, dev_t devnum, struct udev_device **ret) { - _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; - int r; - - assert(udev); - assert(ret); - - for (;;) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - struct pollfd pfd = { - .events = POLLIN - }; - - d = udev_device_new_from_devnum(udev, 'b', devnum); - if (!d) - return log_oom(); - - r = udev_device_get_is_initialized(d); - if (r < 0) - return log_error_errno(r, "Failed to check if device is initialized: %m"); - if (r > 0) { - *ret = d; - d = NULL; - return 0; - } - d = udev_device_unref(d); - - if (!monitor) { - monitor = udev_monitor_new_from_netlink(udev, "udev"); - if (!monitor) - return log_oom(); - - r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL); - if (r < 0) - return log_error_errno(r, "Failed to add block match: %m"); - - r = udev_monitor_enable_receiving(monitor); - if (r < 0) - return log_error_errno(r, "Failed to turn on monitor: %m"); - - continue; - } - - pfd.fd = udev_monitor_get_fd(monitor); - if (pfd.fd < 0) - return log_error_errno(r, "Failed to get udev monitor fd: %m"); - - r = poll(&pfd, 1, -1); - if (r < 0) - return log_error_errno(errno, "Failed to wait for device initialization: %m"); - - d = udev_monitor_receive_device(monitor); - } - - return 0; -} - #define PARTITION_TABLE_BLURB \ "Note that the disk image needs to either contain only a single MBR partition of\n" \ - "type 0x83 that is marked bootable, or follow\n" \ + "type 0x83 that is marked bootable, or a sinlge GPT partition of type" \ + "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \ " http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \ "to be bootable with systemd-nspawn." @@ -2637,19 +2671,19 @@ static int dissect_image( #ifdef GPT_ROOT_SECONDARY int secondary_root_nr = -1; #endif - - _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL; + _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = 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; + bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true; + bool is_gpt, is_mbr, multiple_generic = false; const char *pttype = NULL; blkid_partlist pl; struct stat st; + unsigned i; int r; - bool is_gpt, is_mbr; assert(fd >= 0); assert(root_device); @@ -2718,21 +2752,81 @@ static int dissect_image( if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat block device: %m"); - r = wait_for_block_device(udev, st.st_rdev, &d); - if (r < 0) - return r; - - e = udev_enumerate_new(udev); - if (!e) + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) return log_oom(); - r = udev_enumerate_add_match_parent(e, d); - if (r < 0) - return log_oom(); + for (i = 0;; i++) { + int n, m; - r = udev_enumerate_scan_devices(e); - if (r < 0) - return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); + if (i >= 10) { + log_error("Kernel partitions never appeared."); + return -ENXIO; + } + + 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) + return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); + + /* Count the partitions enumerated by the kernel */ + n = 0; + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) + n++; + + /* Count the partitions enumerated by blkid */ + m = blkid_partlist_numof_partitions(pl); + if (n == m + 1) + break; + if (n > m + 1) { + log_error("blkid and kernel partition list do not match."); + return -EIO; + } + if (n < m + 1) { + unsigned j; + + /* The kernel has probed fewer partitions than + * blkid? Maybe the kernel prober is still + * running or it got EBUSY because udev + * already opened the device. Let's reprobe + * the device, which is a synchronous call + * that waits until probing is complete. */ + + for (j = 0; j < 20; j++) { + + r = ioctl(fd, BLKRRPART, 0); + if (r < 0) + r = -errno; + if (r >= 0 || r != -EBUSY) + break; + + /* If something else has the device + * open, such as an udev rule, the + * ioctl will return EBUSY. Since + * there's no way to wait until it + * isn't busy anymore, let's just wait + * a bit, and try again. + * + * This is really something they + * should fix in the kernel! */ + + usleep(50 * USEC_PER_MSEC); + } + + if (r < 0) + return log_error_errno(r, "Failed to reread partition table: %m"); + } + + e = udev_enumerate_unref(e); + } first = udev_enumerate_get_list_entry(e); udev_list_entry_foreach(item, first) { @@ -2769,10 +2863,6 @@ static int dissect_image( continue; flags = blkid_partition_get_flags(pp); - if (is_gpt && (flags & GPT_FLAG_NO_AUTO)) - continue; - if (is_mbr && (flags != 0x80)) /* Bootable flag */ - continue; nr = blkid_partition_get_partno(pp); if (nr < 0) @@ -2782,6 +2872,9 @@ static int dissect_image( sd_id128_t type_id; const char *stype; + if (flags & GPT_FLAG_NO_AUTO) + continue; + stype = blkid_partition_get_type_string(pp); if (!stype) continue; @@ -2841,48 +2934,41 @@ static int dissect_image( return log_oom(); } #endif + else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { + + if (generic) + multiple_generic = true; + else { + generic_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&generic, node); + if (r < 0) + return log_oom(); + } + } } else if (is_mbr) { int type; + if (flags != 0x80) /* Bootable flag */ + continue; + type = blkid_partition_get_type(pp); if (type != 0x83) /* Linux partition */ continue; - /* Note that there's a certain, intended - * asymmetry here: while for GPT we simply - * take the first valid partition and ignore - * all others of the same type, for MBR we - * fail if there are multiple suitable - * partitions. This is because the GPT - * partition types are defined by us, and - * hence we can define their lookup semantics, - * while for the MBR logic we reuse existing - * definitions, and simply don't want to make - * out the situation. */ - - if (root) { - log_error("Identified multiple bootable Linux 0x83 partitions on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - root_nr = nr; + if (generic) + multiple_generic = true; + else { + generic_rw = true; - r = free_and_strdup(&root, node); - if (r < 0) - return log_oom(); + r = free_and_strdup(&root, node); + if (r < 0) + return log_oom(); + } } } - if (!root && !secondary_root) { - log_error("Failed to identify root partition in disk image\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - if (root) { *root_device = root; root = NULL; @@ -2895,6 +2981,31 @@ static int dissect_image( *root_device_rw = secondary_root_rw; *secondary = true; + } else if (generic) { + + /* There were no partitions with precise meanings + * around, but we found generic partitions. In this + * case, if there's only one, we can go ahead and boot + * it, otherwise we bail out, because we really cannot + * make any sense of it. */ + + if (multiple_generic) { + log_error("Identified multiple bootable Linux partitions on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + *root_device = generic; + generic = NULL; + + *root_device_rw = generic_rw; + *secondary = false; + } else { + log_error("Failed to identify root partition in disk image\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; } if (home) { @@ -4027,6 +4138,10 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; + r = setup_ipvlan(pid); + if (r < 0) + goto finish; + r = register_machine(pid, ifi); if (r < 0) goto finish; @@ -4184,6 +4299,7 @@ finish: strv_free(arg_setenv); strv_free(arg_network_interfaces); strv_free(arg_network_macvlan); + strv_free(arg_network_ipvlan); strv_free(arg_bind); strv_free(arg_bind_ro); strv_free(arg_tmpfs);