]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: split out copy_devnode_one() and bind_mount_devnode() from copy_devnodes()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 13 Nov 2024 04:17:42 +0000 (13:17 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 14 Nov 2024 07:54:06 +0000 (16:54 +0900)
While doing that, even if mknod() failed, we anyway try to fall back to
use bind mount if arg_uid_shift == 0.

Mostly no functional change, just refactoring and preparation for later commit.

src/nspawn/nspawn.c

index 18c23b211756eb08cdfc58f4d97093576ac4c4dd..3f443daf237ebaafb3866853df551ed7570b4e9f 100644 (file)
@@ -2227,97 +2227,131 @@ static bool should_enable_fuse(void) {
         return true;
 }
 
-static int copy_devnodes(const char *dest, bool enable_fuse) {
-        _cleanup_strv_free_ char **devnodes = NULL;
-        int r = 0;
+static int bind_mount_devnode(const char *from, const char *to) {
+        int r;
 
-        assert(dest);
+        assert(from);
+        assert(to);
 
-        devnodes = strv_new("null",
-                            "zero",
-                            "full",
-                            "random",
-                            "urandom",
-                            "tty",
-                            STRV_IFNOTNULL(enable_fuse ? "fuse" : NULL),
-                            "net/tun");
-        if (!devnodes)
-                return log_oom();
+        r = touch(to);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to touch %s: %m", to);
+
+        r = mount_nofollow_verbose(LOG_DEBUG, from, to, NULL, MS_BIND, NULL);
+        if (r < 0) {
+                (void) unlink(to);
+                return log_error_errno(r, "Failed to bind mount %s to %s: %m", from, to);
+        }
+
+        return 0;
+}
+
+static int copy_devnode_one(const char *dest, const char *node) {
+        int r;
+
+        assert(dest);
+        assert(!isempty(node));
 
         BLOCK_WITH_UMASK(0000);
 
-        /* Create /dev/net, so that we can create /dev/net/tun in it */
-        if (userns_mkdir(dest, "/dev/net", 0755, 0, 0) < 0)
-                return log_error_errno(r, "Failed to create /dev/net directory: %m");
+        _cleanup_free_ char *from = path_join("/dev/", node);
+        if (!from)
+                return log_oom();
 
-        STRV_FOREACH(d, devnodes) {
-                _cleanup_free_ char *from = NULL, *to = NULL;
-                struct stat st;
+        _cleanup_free_ char *to = path_join(dest, from);
+        if (!to)
+                return log_oom();
 
-                from = path_join("/dev/", *d);
-                if (!from)
-                        return log_oom();
+        struct stat st;
+        if (stat(from, &st) < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to stat %s: %m", from);
 
-                to = path_join(dest, from);
-                if (!to)
-                        return log_oom();
+                log_debug_errno(errno, "Device node %s does not exist, ignoring.", from);
+                return 0;
+        }
+        if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode))
+                return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "%s is not a device node.", from);
+
+        /* Create the parent directory of the device node. Here, we assume that the path has at most one
+         * subdirectory under /dev/, e.g. /dev/net/tun. */
+        _cleanup_free_ char *parent = NULL;
+        r = path_extract_directory(from, &parent);
+        if (r < 0)
+                return log_error_errno(r, "Failed to extract directory from %s: %m", from);
+        if (!path_equal(parent, "/dev/")) {
+                if (userns_mkdir(dest, parent, 0755, 0, 0) < 0)
+                        return log_error_errno(r, "Failed to create directory %s: %m", parent);
+        }
+
+        if (mknod(to, st.st_mode, st.st_rdev) < 0) {
+                r = -errno; /* Save the original error code. */
+                /* Explicitly warn the user when /dev/ is already populated. */
+                if (r == -EEXIST)
+                        log_notice("%s/dev/ is pre-mounted and pre-populated. If a pre-mounted /dev/ is provided it needs to be an unpopulated file system.", dest);
+                /* If arg_uid_shift != 0, then we cannot fall back to use bind mount. */
+                if (arg_uid_shift != 0)
+                        return log_error_errno(r, "Failed to mknod(%s): %m", to);
+
+                /* Some systems abusively restrict mknod but allow bind mounts. */
+                if (bind_mount_devnode(from, to) < 0)
+                        /* use the original error code. */
+                        return log_error_errno(r, "Both mknod() and bind mount %s failed: %m", to);
+        } else {
+                /* mknod() succeeds, chown() it if necessary. */
+                r = userns_lchown(to, 0, 0);
+                if (r < 0)
+                        return log_error_errno(r, "chown() of device node %s failed: %m", to);
+        }
 
-                if (stat(from, &st) < 0) {
+        _cleanup_free_ char *dn = path_join("/dev", S_ISCHR(st.st_mode) ? "char" : "block");
+        if (!dn)
+                return log_oom();
 
-                        if (errno != ENOENT)
-                                return log_error_errno(errno, "Failed to stat %s: %m", from);
+        r = userns_mkdir(dest, dn, 0755, 0, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create '%s': %m", dn);
 
-                } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode))
-                        return log_error_errno(SYNTHETIC_ERRNO(EIO),
-                                               "%s is not a char or block device, cannot copy.", from);
-                else {
-                        _cleanup_free_ char *sl = NULL, *prefixed = NULL, *dn = NULL, *t = NULL;
+        _cleanup_free_ char *sl = NULL;
+        if (asprintf(&sl, "%s/%u:%u", dn, major(st.st_rdev), minor(st.st_rdev)) < 0)
+                return log_oom();
 
-                        if (mknod(to, st.st_mode, st.st_rdev) < 0) {
-                                /* Explicitly warn the user when /dev is already populated. */
-                                if (errno == EEXIST)
-                                        log_notice("%s/dev/ is pre-mounted and pre-populated. If a pre-mounted /dev/ is provided it needs to be an unpopulated file system.", dest);
-                                if (!ERRNO_IS_PRIVILEGE(errno) || arg_uid_shift != 0)
-                                        return log_error_errno(errno, "mknod(%s) failed: %m", to);
+        _cleanup_free_ char *prefixed = path_join(dest, sl);
+        if (!prefixed)
+                return log_oom();
 
-                                /* Some systems abusively restrict mknod but allow bind mounts. */
-                                r = touch(to);
-                                if (r < 0)
-                                        return log_error_errno(r, "touch (%s) failed: %m", to);
-                                r = mount_nofollow_verbose(LOG_DEBUG, from, to, NULL, MS_BIND, NULL);
-                                if (r < 0)
-                                        return log_error_errno(r, "Both mknod and bind mount (%s) failed: %m", to);
-                        } else {
-                                r = userns_lchown(to, 0, 0);
-                                if (r < 0)
-                                        return log_error_errno(r, "chown() of device node %s failed: %m", to);
-                        }
+        _cleanup_free_ char *t = path_join("..", node);
+        if (!t)
+                return log_oom();
 
-                        dn = path_join("/dev", S_ISCHR(st.st_mode) ? "char" : "block");
-                        if (!dn)
-                                return log_oom();
+        if (symlink(t, prefixed) < 0)
+                log_debug_errno(errno, "Failed to symlink '%s' to '%s', ignoring: %m", t, prefixed);
 
-                        r = userns_mkdir(dest, dn, 0755, 0, 0);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to create '%s': %m", dn);
+        return 0;
+}
 
-                        if (asprintf(&sl, "%s/%u:%u", dn, major(st.st_rdev), minor(st.st_rdev)) < 0)
-                                return log_oom();
+static int copy_devnodes(const char *dest, bool enable_fuse) {
+        int r = 0;
 
-                        prefixed = path_join(dest, sl);
-                        if (!prefixed)
-                                return log_oom();
+        assert(dest);
 
-                        t = path_join("..", *d);
-                        if (!t)
-                                return log_oom();
+        FOREACH_STRING(node, "null", "zero", "full", "random", "urandom", "tty") {
+                r = copy_devnode_one(dest, node);
+                if (r < 0)
+                        return r;
+        }
 
-                        if (symlink(t, prefixed) < 0)
-                                log_debug_errno(errno, "Failed to symlink '%s' to '%s': %m", t, prefixed);
-                }
+        if (enable_fuse) {
+                r = copy_devnode_one(dest, "fuse");
+                if (r < 0)
+                        return r;
         }
 
-        return r;
+        r = copy_devnode_one(dest, "net/tun");
+        if (r < 0)
+                return r;
+
+        return 0;
 }
 
 static int make_extra_nodes(const char *dest) {