]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mkfs-util: Escape spaces with slashes in protofile format 26541/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 22 Feb 2023 13:36:14 +0000 (14:36 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 21 Jul 2023 13:35:50 +0000 (15:35 +0200)
man/repart.d.xml
src/shared/mkfs-util.c

index 19694cfbb893cfddfab95377321eb8bf2c05535e..76d2cb97bf4847e6a395f2e8bda55f2861e5dd3e 100644 (file)
 
         <para>Note that when populating XFS filesystems with <command>systemd-repart</command> and loop
         devices are not available, populating XFS filesystems with files containing spaces, tabs or newlines
-        will fail due to limitations of <citerefentry
-        project='man-pages'><refentrytitle>mkfs.xfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-        protofile format.</para>
+        might fail on old versions of
+        <citerefentry project='man-pages'><refentrytitle>mkfs.xfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        due to limitations of its protofile format.</para>
 
         <para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para>
 
index 9f6ddda6497870e1d22eca7691873460e3d7efae..dd65d3ba7b37995218af4d0dbd14dcf426d33605 100644 (file)
@@ -14,6 +14,7 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "recurse-dir.h"
+#include "rm-rf.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -170,6 +171,12 @@ static int do_mcopy(const char *node, const char *root) {
         return 0;
 }
 
+typedef struct ProtofileData {
+        FILE *file;
+        bool has_filename_with_spaces;
+        const char *tmpdir;
+} ProtofileData;
+
 static int protofile_print_item(
                 RecurseDirEvent event,
                 const char *path,
@@ -179,11 +186,12 @@ static int protofile_print_item(
                 const struct statx *sx,
                 void *userdata) {
 
-        FILE *f = ASSERT_PTR(userdata);
+        ProtofileData *data = ASSERT_PTR(userdata);
+        _cleanup_free_ char *copy = NULL;
         int r;
 
         if (event == RECURSE_DIR_LEAVE) {
-                fputs("$\n", f);
+                fputs("$\n", data->file);
                 return 0;
         }
 
@@ -199,38 +207,78 @@ static int protofile_print_item(
         if (type == 0)
                 return RECURSE_DIR_CONTINUE;
 
-        fprintf(f, "%s %c%c%c%03o 0 0 ",
-                de->d_name,
+        /* The protofile format does not support spaces in filenames as whitespace is used as a token
+         * delimiter. To work around this limitation, mkfs.xfs allows escaping whitespace by using the /
+         * character (which isn't allowed in filenames and as such can be used to escape whitespace). See
+         * https://lore.kernel.org/linux-xfs/20230222090303.h6tujm7y32gjhgal@andromeda/T/#m8066b3e7d62a080ee7434faac4861d944e64493b
+         * for more information.*/
+
+        if (strchr(de->d_name, ' ')) {
+                copy = strdup(de->d_name);
+                if (!copy)
+                        return log_oom();
+
+                string_replace_char(copy, ' ', '/');
+                data->has_filename_with_spaces = true;
+        }
+
+        fprintf(data->file, "%s %c%c%c%03o 0 0 ",
+                copy ?: de->d_name,
                 type,
                 sx->stx_mode & S_ISUID ? 'u' : '-',
                 sx->stx_mode & S_ISGID ? 'g' : '-',
                 (unsigned) (sx->stx_mode & 0777));
 
-        if (S_ISREG(sx->stx_mode))
-                fputs(path, f);
-        else if (S_ISLNK(sx->stx_mode)) {
+        if (S_ISREG(sx->stx_mode)) {
+                _cleanup_free_ char *p = NULL;
+
+                /* While we can escape whitespace in the filename, we cannot escape whitespace in the source
+                 * path, so hack around that by creating a symlink to the path in a temporary directory and
+                 * using the symlink as the source path instead. */
+
+                if (strchr(path, ' ')) {
+                        r = tempfn_random_child(data->tmpdir, "mkfs-xfs", &p);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to generate random child name in %s: %m", data->tmpdir);
+
+                        if (symlink(path, p) < 0)
+                                return log_error_errno(errno, "Failed to symlink %s to %s: %m", p, path);
+                }
+
+                fputs(p ?: path, data->file);
+        } else if (S_ISLNK(sx->stx_mode)) {
                 _cleanup_free_ char *p = NULL;
 
                 r = readlinkat_malloc(dir_fd, de->d_name, &p);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read symlink %s: %m", path);
 
-                fputs(p, f);
+                /* If we have a symlink to a path with whitespace in it, we're out of luck, as there's no way
+                 * to encode that in the mkfs.xfs protofile format. */
+
+                if (strchr(p, ' '))
+                        return log_error_errno(r, "Symlinks to paths containing whitespace are not supported by mkfs.xfs: %m");
+
+                fputs(p, data->file);
         } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
-                fprintf(f, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
+                fprintf(data->file, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
 
-        fputc('\n', f);
+        fputc('\n', data->file);
 
         return RECURSE_DIR_CONTINUE;
 }
 
-static int make_protofile(const char *root, char **ret) {
+static int make_protofile(const char *root, char **ret_path, bool *ret_has_filename_with_spaces, char **ret_tmpdir) {
+        _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_(unlink_and_freep) char *p = NULL;
+        struct ProtofileData data = {};
         const char *vt;
         int r;
 
-        assert(ret);
+        assert(ret_path);
+        assert(ret_has_filename_with_spaces);
+        assert(ret_tmpdir);
 
         r = var_tmp_dir(&vt);
         if (r < 0)
@@ -240,11 +288,19 @@ static int make_protofile(const char *root, char **ret) {
         if (r < 0)
                 return log_error_errno(r, "Failed to open temporary file: %m");
 
+        /* Explicitly use /tmp here because this directory cannot have spaces its path. */
+        r = mkdtemp_malloc("/tmp/systemd-mkfs-XXXXXX", &tmpdir);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create temporary directory: %m");
+
+        data.file = f;
+        data.tmpdir = tmpdir;
+
         fputs("/\n"
               "0 0\n"
               "d--755 0 0\n", f);
 
-        r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, RECURSE_DIR_SORT, protofile_print_item, f);
+        r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, RECURSE_DIR_SORT, protofile_print_item, &data);
         if (r < 0)
                 return log_error_errno(r, "Failed to recurse through %s: %m", root);
 
@@ -254,7 +310,9 @@ static int make_protofile(const char *root, char **ret) {
         if (r < 0)
                 return log_error_errno(r, "Failed to flush %s: %m", p);
 
-        *ret = TAKE_PTR(p);
+        *ret_path = TAKE_PTR(p);
+        *ret_has_filename_with_spaces = data.has_filename_with_spaces;
+        *ret_tmpdir = TAKE_PTR(tmpdir);
 
         return 0;
 }
@@ -272,6 +330,7 @@ int make_filesystem(
 
         _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
         _cleanup_strv_free_ char **argv = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *protofile_tmpdir = NULL;
         _cleanup_(unlink_and_freep) char *protofile = NULL;
         char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
         int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO};
@@ -424,11 +483,23 @@ int make_filesystem(
                         return log_oom();
 
                 if (root) {
-                        r = make_protofile(root, &protofile);
+                        bool has_filename_with_spaces = false;
+                        _cleanup_free_ char *protofile_with_opt = NULL;
+
+                        r = make_protofile(root, &protofile, &has_filename_with_spaces, &protofile_tmpdir);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false) < 0)
+                        /* Gross hack to make mkfs.xfs interpret slashes as spaces so we can encode filenames
+                         * with spaces in the protofile format. */
+                        if (has_filename_with_spaces)
+                                protofile_with_opt = strjoin("slashes_are_spaces=1,", protofile);
+                        else
+                                protofile_with_opt = strdup(protofile);
+                        if (!protofile_with_opt)
+                                return -ENOMEM;
+
+                        if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0)
                                 return log_oom();
                 }