mkfs-util: Add support to populate vfat without mounting using mcopy
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 11 Oct 2022 08:50:58 +0000 (10:50 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 15 Nov 2022 19:07:54 +0000 (20:07 +0100)
mkfs.vfat doesn't support specifying a root directory to bootstrap
the filesystem from (see https://github.com/dosfstools/dosfstools/issues/183).
Instead, we can use the mcopy tool from the mtools package to copy
files into the vfat filesystem after creating it without needing to
mount the vfat filesystem.

mkosi.conf.d/10-systemd.conf
src/shared/mkfs-util.c
test/TEST-58-REPART/test.sh
test/test-functions

index 0915e713570ea03f4444b9a3d61387d323f82fe6..f9e4d086160ed3a3c2bef1b3b24b7d93b7df720c 100644 (file)
@@ -21,6 +21,7 @@ Packages=
         coreutils
         diffutils
         dnsmasq
+        dosfstools
         e2fsprogs
         findutils
         gcc # For sanitizer libraries
@@ -30,6 +31,7 @@ Packages=
         kexec-tools
         kmod
         less
+        mtools
         nano
         nftables
         openssl
index f7f4a35212fe9653f5534c7c5fe8d9e2c4b779ed..2adb2a25c7671173a14338cc88120fd1174ae91b 100644 (file)
@@ -2,11 +2,14 @@
 
 #include <unistd.h>
 
+#include "dirent-util.h"
+#include "fd-util.h"
 #include "id128-util.h"
 #include "mkfs-util.h"
 #include "mountpoint-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "utf8.h"
@@ -34,7 +37,7 @@ int mkfs_exists(const char *fstype) {
 }
 
 int mkfs_supports_root_option(const char *fstype) {
-        return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs");
+        return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat");
 }
 
 static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) {
@@ -91,6 +94,59 @@ static int mangle_fat_label(const char *s, char **ret) {
         return 0;
 }
 
+static int do_mcopy(const char *node, const char *root) {
+        _cleanup_strv_free_ char **argv = NULL;
+        _cleanup_closedir_ DIR *rootdir = NULL;
+        int r;
+
+        assert(node);
+        assert(root);
+
+        /* Return early if there's nothing to copy. */
+        if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
+                return 0;
+
+        argv = strv_new("mcopy", "-b", "-s", "-p", "-Q", "-n", "-m", "-i", node);
+        if (!argv)
+                return log_oom();
+
+        /* mcopy copies the top level directory instead of everything in it so we have to pass all
+         * the subdirectories to mcopy instead to end up with the correct directory structure. */
+
+        rootdir = opendir(root);
+        if (!rootdir)
+                return log_error_errno(errno, "Failed to open directory '%s'", root);
+
+        FOREACH_DIRENT(de, rootdir, return -errno) {
+                char *p = path_join(root, de->d_name);
+                if (!p)
+                        return log_oom();
+
+                r = strv_consume(&argv, TAKE_PTR(p));
+                if (r < 0)
+                        return log_oom();
+        }
+
+        r = strv_extend(&argv, "::");
+        if (r < 0)
+                return log_oom();
+
+        r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_NEW_USERNS, NULL);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
+                 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
+                execvpe("mcopy", argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
+
+                log_error_errno(errno, "Failed to execute mcopy: %m");
+
+                _exit(EXIT_FAILURE);
+        }
+
+        return 0;
+}
+
 int make_filesystem(
                 const char *node,
                 const char *fstype,
@@ -304,6 +360,12 @@ int make_filesystem(
                 _exit(EXIT_FAILURE);
         }
 
+        if (root && streq(fstype, "vfat")) {
+                r = do_mcopy(node, root);
+                if (r < 0)
+                        return r;
+        }
+
         if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
                 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
                          node, fstype, label, vol_id);
index 1638cb2b1638303e69a170f567df5bde188153e9..fb4a05fc7747f6a5dd3165e124190a5f3e8cc51c 100755 (executable)
@@ -15,6 +15,7 @@ test_append_files() {
         if command -v openssl >/dev/null 2>&1; then
             inst_binary openssl
         fi
+        inst_binary mcopy
         instmods dm_verity =md
         generate_module_dependencies
         image_install -o /sbin/mksquashfs
index 18c76a69abd0583371944997b3465f2438aa8996..e59a20f091e82789d33e0ab3b249b887a956ee63 100644 (file)
@@ -1310,6 +1310,11 @@ install_missing_libraries() {
     inst_simple "${path}/engines-3/capi.so" || true
     inst_simple "${path}/engines-3/loader_attic.so" || true
     inst_simple "${path}/engines-3/padlock.so" || true
+
+    # Binaries from mtools depend on the gconv modules to translate between codepages. Because there's no
+    # pkg-config file for these, we copy every gconv/ directory we can find in /usr/lib and /usr/lib64.
+    # shellcheck disable=SC2046
+    inst_recursive $(find /usr/lib* -name gconv 2>/dev/null)
 }
 
 cleanup_loopdev() {