]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fstab-filter: preserve fstab option escaping in the filtered output
authorwyu <97944796+wyu71@users.noreply.github.com>
Wed, 12 Aug 2026 01:43:55 +0000 (09:43 +0800)
committerwyu <97944796+wyu71@users.noreply.github.com>
Thu, 13 Aug 2026 02:36:12 +0000 (10:36 +0800)
fstab_filter_options() used strv_split_full(..., EXTRACT_UNESCAPE_SEPARATORS |
EXTRACT_UNESCAPE_RELAX), which unescapes "\," and "\\" while splitting, and then
strv_join_full(escape_separator=true) to re-escape the kept options. The round-trip
was lossy for invalid escapes kept verbatim by RELAX (e.g. an option value "\xff"
became "\\xff"), so the generated Options= line diverged from the input. A trailing
backslash was also collapsed: "val1\\" in the fstab became "val1\" in the split
word, which let the backslash escape the option separator in Options= (issue #42787).

Rework fstab_filter_options() to split with EXTRACT_UNESCAPE_RELAX only, so the
escape sequences of kept options are preserved verbatim and the filtered output is
assembled with the plain strv_join(); the matching option values are unescaped
manually so callers keep getting the unescaped form they had before.

Fixes a bug introduced by d4d9f034b13acef37375daec5074d9d271e21eed.
Fixes #42787.

src/shared/fstab-util.c
src/test/test-fstab-util.c

index 25e229bf3e2b8c99404727829a6597957b2306e7..a4f5e348ab03874188b6fd209893ff3d99d08309 100644 (file)
@@ -200,6 +200,34 @@ int fstab_is_mount_point_full(const char *where, const char *path) {
 #endif
 }
 
+static char* fstab_unescape_option(const char *s) {
+        /* Undo the fstab option escaping: "\," -> ",", "\\" -> "\", and keep any
+         * other "\x" verbatim (matches EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX). */
+        char *ret, *e;
+
+        assert(s);
+
+        ret = new(char, strlen(s) + 1);
+        if (!ret)
+                return NULL;
+
+        e = ret;
+        for (const char *p = s; *p; p++) {
+                if (*p == '\\' && p[1] != '\0') {
+                        if (IN_SET(p[1], ',', '\\')) {
+                                *e++ = p[1];
+                                p++;
+                                continue;
+                        }
+                        /* Keep the backslash and the following character verbatim. */
+                        *e++ = *p++;
+                }
+                *e++ = *p;
+        }
+        *e = '\0';
+        return ret;
+}
+
 int fstab_filter_options(
                 const char *opts,
                 const char *names,
@@ -234,8 +262,10 @@ int fstab_filter_options(
                 _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */
 
                 /* For backwards compatibility, we need to pass-through escape characters.
-                 * The only ones we "consume" are the ones used as "\," or "\\". */
-                r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
+                 * The only ones we "consume" are the ones used as "\," or "\\". The option
+                 * values are not unescaped by strv_split_full() here; matching option values are
+                 * unescaped manually below so that callers keep getting the unescaped form. */
+                r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_RELAX);
                 if (r < 0)
                         return r;
 
@@ -257,10 +287,18 @@ int fstab_filter_options(
                         }
 
                         if (found) {
+                                _cleanup_free_ char *unescaped = NULL;
+
+                                if ((ret_value && *x == '=') || ret_values) {
+                                        unescaped = fstab_unescape_option(x + 1);
+                                        if (!unescaped)
+                                                return -ENOMEM;
+                                }
+
                                 if (ret_value)
-                                        r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
+                                        r = free_and_replace(value, unescaped);
                                 else if (ret_values)
-                                        r = strv_extend(&values, x + 1);
+                                        r = strv_extend(&values, unescaped);
                                 else
                                         r = 0;
                         } else
@@ -270,7 +308,7 @@ int fstab_filter_options(
                 }
 
                 if (ret_filtered) {
-                        filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator= */ true);
+                        filtered = strv_join(filtered_strv, ",");
                         if (!filtered)
                                 return -ENOMEM;
                 }
index 773b1f9799fd7d88687664c1643fe48fae74bd8a..63b70863e610e521996cd3fd792da4283209b0a8 100644 (file)
@@ -125,7 +125,7 @@ TEST(fstab_filter_options) {
 
         /* escaped characters */
         do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt1\0", 1, 1, "opt1", "\\", "\\", "opt2=\\xff");
-        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, 1, "opt2", "\\xff", "\\xff", "opt1=\\");
+        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, 1, "opt2", "\\xff", "\\xff", "opt1=\\\\");
 }
 
 TEST(fstab_find_pri) {