Blob Blame History Raw
From 1677b88d01729f514dd17145e3aefaa5db6cdf95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 11:49:02 +0100
Subject: [PATCH 1/9] fstab-generator: do not propagate error if we fail to
 canonicalize

r is used for the return value of the function, so we shouldn't
use it a non-fatal check.
---
 src/fstab-generator/fstab-generator.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index 0910a9aa61b..7cb4ea286dc 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -611,11 +611,11 @@ static int parse_fstab(bool initrd) {
                          * /etc/fstab. So we canonicalize here. Note that we use CHASE_NONEXISTENT to handle the case
                          * where a symlink refers to another mount target; this works assuming the sub-mountpoint
                          * target is the final directory. */
-                        r = chase_symlinks(where, initrd ? "/sysroot" : NULL,
+                        k = chase_symlinks(where, initrd ? "/sysroot" : NULL,
                                            CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
                                            &canonical_where, NULL);
-                        if (r < 0) /* If we can't canonicalize we continue on as if it wasn't a symlink */
-                                log_debug_errno(r, "Failed to read symlink target for %s, ignoring: %m", where);
+                        if (k < 0) /* If we can't canonicalize we continue on as if it wasn't a symlink */
+                                log_debug_errno(k, "Failed to read symlink target for %s, ignoring: %m", where);
                         else if (streq(canonical_where, where)) /* If it was fully canonicalized, suppress the change */
                                 canonical_where = mfree(canonical_where);
                         else

From 924f65030529d5a232c2be4ab6e2642dfdc2ea71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 13:20:47 +0100
Subject: [PATCH 2/9] generators: warn but ignore failure to write timeouts

When we failed to split the options (because of disallowed quoting syntax, which
might be a bug in its own), we would silently fail. Instead, let's emit a warning.
Since we ignore the value if we cannot parse it anyway, let's ignore this error
too.
---
 src/shared/generator.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/shared/generator.c b/src/shared/generator.c
index 8b95c772db1..41922d67d8c 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -216,8 +216,12 @@ int generator_write_timeouts(
         r = fstab_filter_options(opts, "comment=systemd.device-timeout\0"
                                        "x-systemd.device-timeout\0",
                                  NULL, &timeout, filtered);
-        if (r <= 0)
-                return r;
+        if (r < 0) {
+                log_warning_errno(r, "Failed to parse fstab options, ignoring: %m");
+                return 0;
+        }
+        if (r == 0)
+                return 0;
 
         r = parse_sec_fix_0(timeout, &u);
         if (r < 0) {

From 5fa2da125157a1beca508792ee5d9be833c2c0cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 13:35:26 +0100
Subject: [PATCH 3/9] shared/fstab-util: immediately drop empty options again
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In the conversion from strv_split() to strv_split_full() done in
7bb553bb98a57b4e03804f8192bdc5a534325582, EXTRACT_DONT_COALESCE_SEPARATORS was
added. I think this was just by mistake… We never look for "empty options", so
whether we immediately ignore the extra separator or store the empty string in
strv, should make no difference.
---
 src/shared/fstab-util.c    | 2 +-
 src/test/test-fstab-util.c | 6 +++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c
index 292b97cd692..1ddcd371cfc 100644
--- a/src/shared/fstab-util.c
+++ b/src/shared/fstab-util.c
@@ -140,7 +140,7 @@ int fstab_filter_options(const char *opts, const char *names,
                                 break;
                 }
         } else {
-                r = strv_split_full(&stor, opts, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
+                r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS);
                 if (r < 0)
                         return r;
 
diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c
index 222ffbb2a75..ebbdd05ca62 100644
--- a/src/test/test-fstab-util.c
+++ b/src/test/test-fstab-util.c
@@ -91,9 +91,13 @@ static void test_fstab_filter_options(void) {
         do_fstab_filter_options("opt =0", "x-opt\0opt\0noopt\0x-noopt\0", 0, NULL, NULL, NULL);
         do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, NULL, NULL, NULL);
 
-        /* check function will NULL args */
+        /* check function with NULL args */
         do_fstab_filter_options(NULL, "opt\0", 0, NULL, NULL, "");
         do_fstab_filter_options("", "opt\0", 0, NULL, NULL, "");
+
+        /* unnecessary comma separators */
+        do_fstab_filter_options("opt=x,,,,", "opt\0", 1, "opt", "x", "");
+        do_fstab_filter_options(",,,opt=x,,,,", "opt\0", 1, "opt", "x", "");
 }
 
 static void test_fstab_find_pri(void) {

From 8723c716c795b4083372b11dd7065bca2aadbc70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 14:23:23 +0100
Subject: [PATCH 4/9] basic/extract_word: try to explain what the various
 options do

A test for stripping of escaped backslashes without any flags was explicitly
added back in 4034a06ddb82ec9868cd52496fef2f5faa25575f. So it seems to be on
purpose, though I would say that this is at least surprising and hence deserves
a comment.

In test-extract-word, add tests for standalone EXTRACT_UNESCAPE_SEPARATORS.
Only behaviour combined with EXTRACT_CUNESCAPE was tested.
---
 src/basic/extract-word.h     | 16 +++++++++-------
 src/test/test-extract-word.c | 21 +++++++++++++++++++++
 2 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h
index d1de32e5806..3c1e7d98b64 100644
--- a/src/basic/extract-word.h
+++ b/src/basic/extract-word.h
@@ -4,13 +4,15 @@
 #include "macro.h"
 
 typedef enum ExtractFlags {
-        EXTRACT_RELAX                    = 1 << 0,
-        EXTRACT_CUNESCAPE                = 1 << 1,
-        EXTRACT_CUNESCAPE_RELAX          = 1 << 2,
-        EXTRACT_UNESCAPE_SEPARATORS      = 1 << 3,
-        EXTRACT_UNQUOTE                  = 1 << 4,
-        EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5,
-        EXTRACT_RETAIN_ESCAPE            = 1 << 6,
+        EXTRACT_RELAX                    = 1 << 0, /* Allow unbalanced quote and eat up trailing backslash. */
+        EXTRACT_CUNESCAPE                = 1 << 1, /* Unescape known escape sequences. */
+        EXTRACT_CUNESCAPE_RELAX          = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */
+        EXTRACT_UNESCAPE_SEPARATORS      = 1 << 3, /* Unescape separators (those specified, or whitespace by default). */
+        EXTRACT_UNQUOTE                  = 1 << 4, /* Remove quoting with "" and ''. */
+        EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5, /* Don't treat multiple adjacent separators as one */
+        EXTRACT_RETAIN_ESCAPE            = 1 << 6, /* Treat escape character '\' as any other character without special meaning */
+
+        /* Note that if no flags are specified, escaped escape characters will be silently stripped. */
 } ExtractFlags;
 
 int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c
index 56b516fe40a..f718556d399 100644
--- a/src/test/test-extract-word.c
+++ b/src/test/test-extract-word.c
@@ -344,6 +344,27 @@ static void test_extract_first_word(void) {
         free(t);
         assert_se(p == NULL);
 
+        p = "\\:";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, ":"));
+        free(t);
+        assert_se(p == NULL);
+
+        p = "a\\:b";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "a:b"));
+        free(t);
+        assert_se(p == NULL);
+
+        p = "a\\ b:c";
+        assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "a b"));
+        free(t);
+        assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "c"));
+        free(t);
+        assert_se(p == NULL);
+
         p = "\\:";
         assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1);
         assert_se(streq(t, ":"));

From 76c4e48ee603593822b3076c4cf5b63768ec55e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 15:17:56 +0100
Subject: [PATCH 5/9] basic/extract-word: allow escape character to be escaped

With EXTRACT_UNESCAPE_SEPARATORS, backslash is used to escape the separator.
But it wasn't possible to insert the backslash itself. Let's allow this and
add test.
---
 src/basic/extract-word.c     |  4 ++--
 src/test/test-extract-word.c | 24 ++++++++++++++++++++++++
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c
index 4e4e7e8ce93..06b813c031a 100644
--- a/src/basic/extract-word.c
+++ b/src/basic/extract-word.c
@@ -102,8 +102,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                                         else
                                                 sz += utf8_encode_unichar(s + sz, u);
                                 } else if ((flags & EXTRACT_UNESCAPE_SEPARATORS) &&
-                                           strchr(separators, **p))
-                                        /* An escaped separator char */
+                                           (strchr(separators, **p) || **p == '\\'))
+                                        /* An escaped separator char or the escape char itself */
                                         s[sz++] = c;
                                 else if (flags & EXTRACT_CUNESCAPE_RELAX) {
                                         s[sz++] = '\\';
diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c
index f718556d399..217c600dac6 100644
--- a/src/test/test-extract-word.c
+++ b/src/test/test-extract-word.c
@@ -365,6 +365,18 @@ static void test_extract_first_word(void) {
         free(t);
         assert_se(p == NULL);
 
+        p = "a\\ b:c\\x";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL);
+
+        p = "a\\\\ b:c\\\\x";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "a\\ b"));
+        free(t);
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "c\\x"));
+        free(t);
+        assert_se(p == NULL);
+
         p = "\\:";
         assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1);
         assert_se(streq(t, ":"));
@@ -386,6 +398,18 @@ static void test_extract_first_word(void) {
         free(t);
         assert_se(p == NULL);
 
+        p = "a\\ b:c\\x";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL);
+
+        p = "a\\\\ b:c\\\\x";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "a\\ b"));
+        free(t);
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1);
+        assert_se(streq(t, "c\\x"));
+        free(t);
+        assert_se(p == NULL);
+
         p = "\\:";
         assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == -EINVAL);
 

From 3141089f53274849ecedb84aacc6a35fd679d3c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 15:39:47 +0100
Subject: [PATCH 6/9] basic/extract-word: rename flag

The flag enables "relaxed mode" for all kinds of unescaping, not just c-unescaping.
---
 src/basic/extract-word.c     | 16 ++++++++--------
 src/basic/extract-word.h     |  2 +-
 src/resolve/resolved-conf.c  |  2 +-
 src/test/test-extract-word.c | 22 +++++++++++-----------
 src/test/test-strv.c         |  4 ++--
 5 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c
index 06b813c031a..d1af11318a8 100644
--- a/src/basic/extract-word.c
+++ b/src/basic/extract-word.c
@@ -69,14 +69,14 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                                 return -ENOMEM;
 
                         if (c == 0) {
-                                if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
+                                if ((flags & EXTRACT_UNESCAPE_RELAX) &&
                                     (quote == 0 || flags & EXTRACT_RELAX)) {
                                         /* If we find an unquoted trailing backslash and we're in
-                                         * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
+                                         * EXTRACT_UNESCAPE_RELAX mode, keep it verbatim in the
                                          * output.
                                          *
                                          * Unbalanced quotes will only be allowed in EXTRACT_RELAX
-                                         * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
+                                         * mode, EXTRACT_UNESCAPE_RELAX mode does not allow them.
                                          */
                                         s[sz++] = '\\';
                                         goto finish_force_terminate;
@@ -105,7 +105,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                                            (strchr(separators, **p) || **p == '\\'))
                                         /* An escaped separator char or the escape char itself */
                                         s[sz++] = c;
-                                else if (flags & EXTRACT_CUNESCAPE_RELAX) {
+                                else if (flags & EXTRACT_UNESCAPE_RELAX) {
                                         s[sz++] = '\\';
                                         s[sz++] = c;
                                 } else
@@ -196,7 +196,7 @@ int extract_first_word_and_warn(
                 const char *rvalue) {
 
         /* Try to unquote it, if it fails, warn about it and try again
-         * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
+         * but this time using EXTRACT_UNESCAPE_RELAX to keep the
          * backslashes verbatim in invalid escape sequences. */
 
         const char *save;
@@ -207,11 +207,11 @@ int extract_first_word_and_warn(
         if (r >= 0)
                 return r;
 
-        if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
+        if (r == -EINVAL && !(flags & EXTRACT_UNESCAPE_RELAX)) {
 
-                /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
+                /* Retry it with EXTRACT_UNESCAPE_RELAX. */
                 *p = save;
-                r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
+                r = extract_first_word(p, ret, separators, flags|EXTRACT_UNESCAPE_RELAX);
                 if (r >= 0) {
                         /* It worked this time, hence it must have been an invalid escape sequence. */
                         log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h
index 3c1e7d98b64..0e9e77e93d3 100644
--- a/src/basic/extract-word.h
+++ b/src/basic/extract-word.h
@@ -6,7 +6,7 @@
 typedef enum ExtractFlags {
         EXTRACT_RELAX                    = 1 << 0, /* Allow unbalanced quote and eat up trailing backslash. */
         EXTRACT_CUNESCAPE                = 1 << 1, /* Unescape known escape sequences. */
-        EXTRACT_CUNESCAPE_RELAX          = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */
+        EXTRACT_UNESCAPE_RELAX           = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */
         EXTRACT_UNESCAPE_SEPARATORS      = 1 << 3, /* Unescape separators (those specified, or whitespace by default). */
         EXTRACT_UNQUOTE                  = 1 << 4, /* Remove quoting with "" and ''. */
         EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5, /* Don't treat multiple adjacent separators as one */
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index f2a33162517..87d1794a741 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -348,7 +348,7 @@ int config_parse_dnssd_txt(
                 int r;
 
                 r = extract_first_word(&rvalue, &word, NULL,
-                                       EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX);
+                                       EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
                 if (r == 0)
                         break;
                 if (r == -ENOMEM)
diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c
index 217c600dac6..f1085266df2 100644
--- a/src/test/test-extract-word.c
+++ b/src/test/test-extract-word.c
@@ -172,19 +172,19 @@ static void test_extract_first_word(void) {
         assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
         assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
         assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
         assert_se(isempty(p));
@@ -230,17 +230,17 @@ static void test_extract_first_word(void) {
         assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE_RELAX) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_UNESCAPE_RELAX) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\"foo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "foo\\"));
         free(t);
         assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "foo\\"));
         free(t);
         assert_se(isempty(p));
@@ -252,13 +252,13 @@ static void test_extract_first_word(void) {
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
@@ -268,7 +268,7 @@ static void test_extract_first_word(void) {
         assert_se(p == original + 5);
 
         p = original = "fooo\\ bar quux";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\ bar"));
         free(t);
         assert_se(p == original + 10);
@@ -278,13 +278,13 @@ static void test_extract_first_word(void) {
         assert_se(p == original + 1);
 
         p = original = "\\w+@\\K[\\d.]+";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "\\w+@\\K[\\d.]+"));
         free(t);
         assert_se(isempty(p));
 
         p = original = "\\w+\\b";
-        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0);
         assert_se(streq(t, "\\w+\b"));
         free(t);
         assert_se(isempty(p));
diff --git a/src/test/test-strv.c b/src/test/test-strv.c
index 162d8bed951..039bb2c78af 100644
--- a/src/test/test-strv.c
+++ b/src/test/test-strv.c
@@ -333,12 +333,12 @@ static void test_strv_split(void) {
         l = strv_free_erase(l);
 
         assert_se(strv_split_full(&l, "    'one'  \"  two\t three \"' four  five", NULL,
-                                     EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_CUNESCAPE_RELAX) == 2);
+                                     EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_UNESCAPE_RELAX) == 2);
         assert_se(strv_equal(l, (char**) input_table_quoted_joined));
 
         l = strv_free_erase(l);
 
-        assert_se(strv_split_full(&l, "\\", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_CUNESCAPE_RELAX) == 1);
+        assert_se(strv_split_full(&l, "\\", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_UNESCAPE_RELAX) == 1);
         assert_se(strv_equal(l, STRV_MAKE("\\")));
 }
 

From 0264b404b9f193b70a19db0f600cf6bab3a05368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 10 Mar 2021 16:53:38 +0100
Subject: [PATCH 7/9] shared/fstab-util: pass through the escape character
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

… when not used to escape the separator (,) or the escape character (\).
This mostly restores behaviour from before 0645b83a40d1c782f173c4d8440ab2fc82a75006,
but still allows "," to be escaped.

Partially fixes #18952.
---
 src/shared/fstab-util.c    | 20 ++++++++++++--------
 src/test/test-fstab-util.c |  4 ++++
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c
index 1ddcd371cfc..8dc1733c0de 100644
--- a/src/shared/fstab-util.c
+++ b/src/shared/fstab-util.c
@@ -97,16 +97,18 @@ int fstab_filter_options(const char *opts, const char *names,
                 for (const char *word = opts;;) {
                         const char *end = word;
 
-                        /* Look for an *non-escaped* comma separator. Only commas can be escaped, so "\," is
-                         * the only valid escape sequence, so we can do a very simple test here. */
+                        /* Look for a *non-escaped* comma separator. Only commas and backslashes can be
+                         * escaped, so "\," and "\\" are the only valid escape sequences, and we can do a
+                         * very simple test here. */
                         for (;;) {
-                                size_t n = strcspn(end, ",");
+                                end += strcspn(end, ",\\");
 
-                                end += n;
-                                if (n > 0 && end[-1] == '\\')
-                                        end++;
-                                else
+                                if (IN_SET(*end, ',', '\0'))
                                         break;
+                                assert(*end == '\\');
+                                end ++;                 /* Skip the backslash */
+                                if (*end != '\0')
+                                        end ++;         /* Skip the escaped char, but watch out for a trailing commma */
                         }
 
                         NULSTR_FOREACH(name, names) {
@@ -140,7 +142,9 @@ int fstab_filter_options(const char *opts, const char *names,
                                 break;
                 }
         } else {
-                r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS);
+                /* 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(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
                 if (r < 0)
                         return r;
 
diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c
index ebbdd05ca62..3a7ec170d65 100644
--- a/src/test/test-fstab-util.c
+++ b/src/test/test-fstab-util.c
@@ -98,6 +98,10 @@ static void test_fstab_filter_options(void) {
         /* unnecessary comma separators */
         do_fstab_filter_options("opt=x,,,,", "opt\0", 1, "opt", "x", "");
         do_fstab_filter_options(",,,opt=x,,,,", "opt\0", 1, "opt", "x", "");
+
+        /* escaped characters */
+        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt1\0", 1, "opt1", "\\", "opt2=\\xff");
+        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, "opt2", "\\xff", "opt1=\\");
 }
 
 static void test_fstab_find_pri(void) {

From ff0c31bc2722eed528eae6644a104e85ed97f2f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Thu, 11 Mar 2021 10:37:36 +0100
Subject: [PATCH 8/9] shared/fstab-util: teach fstab_filter_options() a mode
 where all values are returned

Apart from tests, the new argument isn't used anywhere, so there should be no
functional change. Note that the two arms of the big conditional are switched, so the
diff is artificially inflated. The actual code change is rather small. I dropped the
path which extracts ret_value manually, because it wasn't supporting unescaping of the
escape character properly.
---
 src/core/mount.c                      |   2 +-
 src/cryptsetup/cryptsetup-generator.c |   9 +-
 src/fstab-generator/fstab-generator.c |   2 +-
 src/shared/fstab-util.c               | 124 +++++++++++++----------
 src/shared/fstab-util.h               |  12 ++-
 src/shared/generator.c                |   2 +-
 src/test/test-fstab-util.c            | 139 +++++++++++++++-----------
 7 files changed, 169 insertions(+), 121 deletions(-)

diff --git a/src/core/mount.c b/src/core/mount.c
index 23b558859c2..ca5d0939a18 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1019,7 +1019,7 @@ static void mount_enter_mounting(Mount *m) {
         if (p) {
                 _cleanup_free_ char *opts = NULL;
 
-                r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, &opts);
+                r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, NULL, &opts);
                 if (r < 0)
                         goto fail;
 
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 74f739b5139..98c8408da54 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -301,7 +301,9 @@ static int create_disk(
         netdev = fstab_test_option(options, "_netdev\0");
         attach_in_initrd = fstab_test_option(options, "x-initrd.attach\0");
 
-        keyfile_can_timeout = fstab_filter_options(options, "keyfile-timeout\0", NULL, &keyfile_timeout_value, NULL);
+        keyfile_can_timeout = fstab_filter_options(options,
+                                                   "keyfile-timeout\0",
+                                                   NULL, &keyfile_timeout_value, NULL, NULL);
         if (keyfile_can_timeout < 0)
                 return log_error_errno(keyfile_can_timeout, "Failed to parse keyfile-timeout= option value: %m");
 
@@ -310,11 +312,12 @@ static int create_disk(
                 "header\0",
                 NULL,
                 &header_path,
+                NULL,
                 headerdev ? &filtered_header : NULL);
         if (detached_header < 0)
                 return log_error_errno(detached_header, "Failed to parse header= option value: %m");
 
-        tmp = fstab_filter_options(options, "tmp\0", NULL, &tmp_fstype, NULL);
+        tmp = fstab_filter_options(options, "tmp\0", NULL, &tmp_fstype, NULL, NULL);
         if (tmp < 0)
                 return log_error_errno(tmp, "Failed to parse tmp= option value: %m");
 
@@ -602,7 +605,7 @@ static int filter_header_device(const char *options,
         assert(ret_headerdev);
         assert(ret_filtered_headerdev_options);
 
-        r = fstab_filter_options(options, "header\0", NULL, &headerspec, &filtered_headerspec);
+        r = fstab_filter_options(options, "header\0", NULL, &headerspec, NULL, &filtered_headerspec);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse header= option value: %m");
 
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index 7cb4ea286dc..b454a5980d4 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -200,7 +200,7 @@ static int write_timeout(
         usec_t u;
         int r;
 
-        r = fstab_filter_options(opts, filter, NULL, &timeout, NULL);
+        r = fstab_filter_options(opts, filter, NULL, &timeout, NULL, NULL);
         if (r < 0)
                 return log_warning_errno(r, "Failed to parse options: %m");
         if (r == 0)
diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c
index 8dc1733c0de..6674ed4a19f 100644
--- a/src/shared/fstab-util.c
+++ b/src/shared/fstab-util.c
@@ -79,21 +79,80 @@ int fstab_is_mount_point(const char *mount) {
         return false;
 }
 
-int fstab_filter_options(const char *opts, const char *names,
-                         const char **ret_namefound, char **ret_value, char **ret_filtered) {
+int fstab_filter_options(
+                const char *opts,
+                const char *names,
+                const char **ret_namefound,
+                char **ret_value,
+                char ***ret_values,
+                char **ret_filtered) {
+
         const char *name, *namefound = NULL, *x;
-        _cleanup_strv_free_ char **stor = NULL;
-        _cleanup_free_ char *v = NULL, **strv = NULL;
+        _cleanup_strv_free_ char **stor = NULL, **values = NULL;
+        _cleanup_free_ char *value = NULL, **filtered = NULL;
         int r;
 
         assert(names && *names);
+        assert(!(ret_value && ret_values));
 
         if (!opts)
                 goto answer;
 
-        /* If !ret_value and !ret_filtered, this function is not allowed to fail. */
+        /* Finds any options matching 'names', and returns:
+         * - the last matching option name in ret_namefound,
+         * - the last matching value in ret_value,
+         * - any matching values in ret_values,
+         * - the rest of the option string in ret_filtered.
+         *
+         * If !ret_value and !ret_values and !ret_filtered, this function is not allowed to fail.
+         *
+         * Returns negative on error, true if any matching options were found, false otherwise. */
+
+        if (ret_filtered || ret_value || ret_values) {
+                /* 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(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
+                if (r < 0)
+                        return r;
+
+                filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
+                if (!filtered)
+                        return -ENOMEM;
+
+                char **t = filtered;
+                for (char **s = t; *s; s++) {
+                        NULSTR_FOREACH(name, names) {
+                                x = startswith(*s, name);
+                                if (!x)
+                                        continue;
+                                /* Match name, but when ret_values, only when followed by assignment. */
+                                if (*x == '=' || (!ret_values && *x == '\0'))
+                                        goto found;
+                        }
+
+                        *t = *s;
+                        t++;
+                        continue;
+                found:
+                        /* Keep the last occurrence found */
+                        namefound = name;
+
+                        if (ret_value || ret_values) {
+                                assert(IN_SET(*x, '=', '\0'));
 
-        if (!ret_filtered) {
+                                if (ret_value) {
+                                        r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
+                                        if (r < 0)
+                                                return r;
+                                } else if (*x) {
+                                        r = strv_extend(&values, x + 1);
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+                }
+                *t = NULL;
+        } else
                 for (const char *word = opts;;) {
                         const char *end = word;
 
@@ -121,17 +180,6 @@ int fstab_filter_options(const char *opts, const char *names,
                                 x = word + strlen(name);
                                 if (IN_SET(*x, '\0', '=', ',')) {
                                         namefound = name;
-                                        if (ret_value) {
-                                                bool eq = *x == '=';
-                                                assert(eq || IN_SET(*x, ',', '\0'));
-
-                                                r = free_and_strndup(&v,
-                                                                     eq ? x + 1 : NULL,
-                                                                     eq ? end - x - 1 : 0);
-                                                if (r < 0)
-                                                        return r;
-                                        }
-
                                         break;
                                 }
                         }
@@ -141,40 +189,6 @@ int fstab_filter_options(const char *opts, const char *names,
                         else
                                 break;
                 }
-        } else {
-                /* 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(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
-                if (r < 0)
-                        return r;
-
-                strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
-                if (!strv)
-                        return -ENOMEM;
-
-                char **t = strv;
-                for (char **s = strv; *s; s++) {
-                        NULSTR_FOREACH(name, names) {
-                                x = startswith(*s, name);
-                                if (x && IN_SET(*x, '\0', '='))
-                                        goto found;
-                        }
-
-                        *t = *s;
-                        t++;
-                        continue;
-                found:
-                        /* Keep the last occurrence found */
-                        namefound = name;
-                        if (ret_value) {
-                                assert(IN_SET(*x, '=', '\0'));
-                                r = free_and_strdup(&v, *x == '=' ? x + 1 : NULL);
-                                if (r < 0)
-                                        return r;
-                        }
-                }
-                *t = NULL;
-        }
 
 answer:
         if (ret_namefound)
@@ -182,14 +196,16 @@ int fstab_filter_options(const char *opts, const char *names,
         if (ret_filtered) {
                 char *f;
 
-                f = strv_join_full(strv, ",", NULL, true);
+                f = strv_join_full(filtered, ",", NULL, true);
                 if (!f)
                         return -ENOMEM;
 
                 *ret_filtered = f;
         }
         if (ret_value)
-                *ret_value = TAKE_PTR(v);
+                *ret_value = TAKE_PTR(value);
+        if (ret_values)
+                *ret_values = TAKE_PTR(values);
 
         return !!namefound;
 }
@@ -229,7 +245,7 @@ int fstab_find_pri(const char *options, int *ret) {
 
         assert(ret);
 
-        r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL);
+        r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL);
         if (r < 0)
                 return r;
         if (r == 0 || !opt)
diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h
index 1a602cb56b2..97f40221afb 100644
--- a/src/shared/fstab-util.h
+++ b/src/shared/fstab-util.h
@@ -10,12 +10,18 @@ bool fstab_is_extrinsic(const char *mount, const char *opts);
 int fstab_is_mount_point(const char *mount);
 int fstab_has_fstype(const char *fstype);
 
-int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered);
+int fstab_filter_options(
+                const char *opts,
+                const char *names,
+                const char **ret_namefound,
+                char **ret_value,
+                char ***ret_values,
+                char **ret_filtered);
 
 int fstab_extract_values(const char *opts, const char *name, char ***values);
 
 static inline bool fstab_test_option(const char *opts, const char *names) {
-        return !!fstab_filter_options(opts, names, NULL, NULL, NULL);
+        return !!fstab_filter_options(opts, names, NULL, NULL, NULL, NULL);
 }
 
 int fstab_find_pri(const char *options, int *ret);
@@ -26,7 +32,7 @@ static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no
         /* If first name given is last, return 1.
          * If second name given is last or neither is found, return 0. */
 
-        assert_se(fstab_filter_options(opts, yes_no, &opt, NULL, NULL) >= 0);
+        assert_se(fstab_filter_options(opts, yes_no, &opt, NULL, NULL, NULL) >= 0);
 
         return opt == yes_no;
 }
diff --git a/src/shared/generator.c b/src/shared/generator.c
index 41922d67d8c..5b9c4325271 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -215,7 +215,7 @@ int generator_write_timeouts(
 
         r = fstab_filter_options(opts, "comment=systemd.device-timeout\0"
                                        "x-systemd.device-timeout\0",
-                                 NULL, &timeout, filtered);
+                                 NULL, &timeout, NULL, filtered);
         if (r < 0) {
                 log_warning_errno(r, "Failed to parse fstab options, ignoring: %m");
                 return 0;
diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c
index 3a7ec170d65..d2f20185265 100644
--- a/src/test/test-fstab-util.c
+++ b/src/test/test-fstab-util.c
@@ -6,102 +6,125 @@
 #include "fstab-util.h"
 #include "log.h"
 #include "string-util.h"
+#include "strv.h"
 
 /*
-int fstab_filter_options(const char *opts, const char *names,
-                         const char **namefound, char **value, char **filtered);
+int fstab_filter_options(
+        const char *opts,
+        const char *names,
+        const char **ret_namefound,
+        const char **ret_value,
+        const char **ret_values,
+        char **ret_filtered);
 */
 
 static void do_fstab_filter_options(const char *opts,
                                     const char *remove,
                                     int r_expected,
+                                    int r_values_expected,
                                     const char *name_expected,
                                     const char *value_expected,
+                                    const char *values_expected,
                                     const char *filtered_expected) {
         int r;
         const char *name;
-        _cleanup_free_ char *value = NULL, *filtered = NULL;
+        _cleanup_free_ char *value = NULL, *filtered = NULL, *joined = NULL;
+        _cleanup_strv_free_ char **values = NULL;
 
-        r = fstab_filter_options(opts, remove, &name, &value, &filtered);
-        log_info("\"%s\" → %d, \"%s\", \"%s\", \"%s\", expected %d, \"%s\", \"%s\", \"%s\"",
-                 opts, r, name, value, filtered,
+        /* test mode which returns the last value */
+
+        r = fstab_filter_options(opts, remove, &name, &value, NULL, &filtered);
+        log_info("1: \"%s\" → %d, \"%s\", \"%s\", \"%s\", expected %d, \"%s\", \"%s\", \"%s\"",
+                 opts, r, strnull(name), value, filtered,
                  r_expected, name_expected, value_expected, filtered_expected ?: opts);
         assert_se(r == r_expected);
         assert_se(streq_ptr(name, name_expected));
         assert_se(streq_ptr(value, value_expected));
         assert_se(streq_ptr(filtered, filtered_expected ?: opts));
 
+        /* test mode which returns all the values */
+
+        r = fstab_filter_options(opts, remove, &name, NULL, &values, NULL);
+        assert_se(joined = strv_join(values, ":"));
+        log_info("2: \"%s\" → %d, \"%s\", \"%s\", expected %d, \"%s\", \"%s\"",
+                 opts, r, strnull(name), joined,
+                 r_values_expected, name_expected, values_expected);
+        assert_se(r == r_values_expected);
+        assert_se(streq_ptr(name, r_values_expected > 0 ? name_expected : NULL));
+        assert_se(streq_ptr(joined, values_expected));
+
         /* also test the malloc-less mode */
-        r = fstab_filter_options(opts, remove, &name, NULL, NULL);
-        log_info("\"%s\" → %d, \"%s\", expected %d, \"%s\"\n-",
-                 opts, r, name,
+        r = fstab_filter_options(opts, remove, &name, NULL, NULL, NULL);
+        log_info("3: \"%s\" → %d, \"%s\", expected %d, \"%s\"\n-",
+                 opts, r, strnull(name),
                  r_expected, name_expected);
         assert_se(r == r_expected);
         assert_se(streq_ptr(name, name_expected));
 }
 
 static void test_fstab_filter_options(void) {
-        do_fstab_filter_options("opt=0", "opt\0x-opt\0", 1, "opt", "0", "");
-        do_fstab_filter_options("opt=0", "x-opt\0opt\0", 1, "opt", "0", "");
-        do_fstab_filter_options("opt", "opt\0x-opt\0", 1, "opt", NULL, "");
-        do_fstab_filter_options("opt", "x-opt\0opt\0", 1, "opt", NULL, "");
-        do_fstab_filter_options("x-opt", "x-opt\0opt\0", 1, "x-opt", NULL, "");
-
-        do_fstab_filter_options("opt=0,other", "opt\0x-opt\0", 1, "opt", "0", "other");
-        do_fstab_filter_options("opt=0,other", "x-opt\0opt\0", 1, "opt", "0", "other");
-        do_fstab_filter_options("opt,other", "opt\0x-opt\0", 1, "opt", NULL, "other");
-        do_fstab_filter_options("opt,other", "x-opt\0opt\0", 1, "opt", NULL, "other");
-        do_fstab_filter_options("x-opt,other", "opt\0x-opt\0", 1, "x-opt", NULL, "other");
-
-        do_fstab_filter_options("opt=0\\,1,other", "opt\0x-opt\0", 1, "opt", "0,1", "other");
-        do_fstab_filter_options("opt=0,other,x-opt\\,foobar", "x-opt\0opt\0", 1, "opt", "0", "other,x-opt\\,foobar");
-        do_fstab_filter_options("opt,other,x-opt\\,part", "opt\0x-opt\0", 1, "opt", NULL, "other,x-opt\\,part");
-        do_fstab_filter_options("opt,other,part\\,x-opt", "x-opt\0opt\0", 1, "opt", NULL, "other,part\\,x-opt");
-        do_fstab_filter_options("opt,other\\,\\,\\,opt,x-part", "opt\0x-opt\0", 1, "opt", NULL, "other\\,\\,\\,opt,x-part");
-
-        do_fstab_filter_options("opto=0,other", "opt\0x-opt\0", 0, NULL, NULL, NULL);
-        do_fstab_filter_options("opto,other", "opt\0x-opt\0", 0, NULL, NULL, NULL);
-        do_fstab_filter_options("x-opto,other", "opt\0x-opt\0", 0, NULL, NULL, NULL);
-
-        do_fstab_filter_options("first,opt=0", "opt\0x-opt\0", 1, "opt", "0", "first");
-        do_fstab_filter_options("first=1,opt=0", "opt\0x-opt\0", 1, "opt", "0", "first=1");
-        do_fstab_filter_options("first,opt=", "opt\0x-opt\0", 1, "opt", "", "first");
-        do_fstab_filter_options("first=1,opt", "opt\0x-opt\0", 1, "opt", NULL, "first=1");
-        do_fstab_filter_options("first=1,x-opt", "opt\0x-opt\0", 1, "x-opt", NULL, "first=1");
-
-        do_fstab_filter_options("first,opt=0,last=1", "opt\0x-opt\0", 1, "opt", "0", "first,last=1");
-        do_fstab_filter_options("first=1,opt=0,last=2", "x-opt\0opt\0", 1, "opt", "0", "first=1,last=2");
-        do_fstab_filter_options("first,opt,last", "opt\0", 1, "opt", NULL, "first,last");
-        do_fstab_filter_options("first=1,opt,last", "x-opt\0opt\0", 1, "opt", NULL, "first=1,last");
-        do_fstab_filter_options("first=,opt,last", "opt\0noopt\0", 1, "opt", NULL, "first=,last");
+        do_fstab_filter_options("opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "");
+        do_fstab_filter_options("opt=0", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "");
+        do_fstab_filter_options("opt", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "");
+        do_fstab_filter_options("opt", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "");
+        do_fstab_filter_options("x-opt", "x-opt\0opt\0", 1, 0, "x-opt", NULL, "", "");
+
+        do_fstab_filter_options("opt=0,other", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "other");
+        do_fstab_filter_options("opt=0,other", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "other");
+        do_fstab_filter_options("opt,other", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other");
+        do_fstab_filter_options("opt,other", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "other");
+        do_fstab_filter_options("x-opt,other", "opt\0x-opt\0", 1, 0, "x-opt", NULL, "", "other");
+
+        do_fstab_filter_options("opt=0\\,1,other", "opt\0x-opt\0", 1, 1, "opt", "0,1", "0,1", "other");
+        do_fstab_filter_options("opt=0,other,x-opt\\,foobar", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "other,x-opt\\,foobar");
+        do_fstab_filter_options("opt,other,x-opt\\,part", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other,x-opt\\,part");
+        do_fstab_filter_options("opt,other,part\\,x-opt", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "other,part\\,x-opt");
+        do_fstab_filter_options("opt,other\\,\\,\\,opt,x-part", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other\\,\\,\\,opt,x-part");
+
+        do_fstab_filter_options("opto=0,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL);
+        do_fstab_filter_options("opto,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL);
+        do_fstab_filter_options("x-opto,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL);
+
+        do_fstab_filter_options("first,opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first");
+        do_fstab_filter_options("first=1,opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first=1");
+        do_fstab_filter_options("first,opt=", "opt\0x-opt\0", 1, 1, "opt", "", "", "first");
+        do_fstab_filter_options("first=1,opt", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "first=1");
+        do_fstab_filter_options("first=1,x-opt", "opt\0x-opt\0", 1, 0, "x-opt", NULL, "", "first=1");
+
+        do_fstab_filter_options("first,opt=0,last=1", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first,last=1");
+        do_fstab_filter_options("first=1,opt=0,last=2", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "first=1,last=2");
+        do_fstab_filter_options("first,opt,last", "opt\0", 1, 0, "opt", NULL, "", "first,last");
+        do_fstab_filter_options("first=1,opt,last", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "first=1,last");
+        do_fstab_filter_options("first=,opt,last", "opt\0noopt\0", 1, 0, "opt", NULL, "", "first=,last");
 
         /* check repeated options */
-        do_fstab_filter_options("first,opt=0,noopt=1,last=1", "opt\0noopt\0", 1, "noopt", "1", "first,last=1");
-        do_fstab_filter_options("first=1,opt=0,last=2,opt=1", "opt\0", 1, "opt", "1", "first=1,last=2");
-        do_fstab_filter_options("x-opt=0,x-opt=1", "opt\0x-opt\0", 1, "x-opt", "1", "");
-        do_fstab_filter_options("opt=0,x-opt=1", "opt\0x-opt\0", 1, "x-opt", "1", "");
+        do_fstab_filter_options("first,opt=0,noopt=1,last=1", "opt\0noopt\0", 1, 1, "noopt", "1", "0:1", "first,last=1");
+        do_fstab_filter_options("first=1,opt=0,last=2,opt=1", "opt\0", 1, 1, "opt", "1", "0:1", "first=1,last=2");
+        do_fstab_filter_options("x-opt=0,x-opt=1", "opt\0x-opt\0", 1, 1, "x-opt", "1", "0:1", "");
+        do_fstab_filter_options("opt=0,x-opt=1", "opt\0x-opt\0", 1, 1, "x-opt", "1", "0:1", "");
+        do_fstab_filter_options("opt=0,opt=1,opt=,opt=,opt=2", "opt\0noopt\0", 1, 1, "opt", "2", "0:1:::2", "");
 
         /* check that semicolons are not misinterpreted */
-        do_fstab_filter_options("opt=0;", "opt\0", 1, "opt", "0;", "");
-        do_fstab_filter_options("opt;=0", "x-opt\0opt\0noopt\0x-noopt\0", 0, NULL, NULL, NULL);
-        do_fstab_filter_options("opt;", "opt\0x-opt\0", 0, NULL, NULL, NULL);
+        do_fstab_filter_options("opt=0;", "opt\0", 1, 1, "opt", "0;", "0;", "");
+        do_fstab_filter_options("opt;=0", "x-opt\0opt\0noopt\0x-noopt\0", 0, 0, NULL, NULL, "", NULL);
+        do_fstab_filter_options("opt;", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL);
 
         /* check that spaces are not misinterpreted */
-        do_fstab_filter_options("opt=0 ", "opt\0", 1, "opt", "0 ", "");
-        do_fstab_filter_options("opt =0", "x-opt\0opt\0noopt\0x-noopt\0", 0, NULL, NULL, NULL);
-        do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, NULL, NULL, NULL);
+        do_fstab_filter_options("opt=0 ", "opt\0", 1, 1, "opt", "0 ", "0 ", "");
+        do_fstab_filter_options("opt =0", "x-opt\0opt\0noopt\0x-noopt\0", 0, 0, NULL, NULL, "", NULL);
+        do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL);
 
         /* check function with NULL args */
-        do_fstab_filter_options(NULL, "opt\0", 0, NULL, NULL, "");
-        do_fstab_filter_options("", "opt\0", 0, NULL, NULL, "");
+        do_fstab_filter_options(NULL, "opt\0", 0, 0, NULL, NULL, "", "");
+        do_fstab_filter_options("", "opt\0", 0, 0, NULL, NULL, "", "");
 
         /* unnecessary comma separators */
-        do_fstab_filter_options("opt=x,,,,", "opt\0", 1, "opt", "x", "");
-        do_fstab_filter_options(",,,opt=x,,,,", "opt\0", 1, "opt", "x", "");
+        do_fstab_filter_options("opt=x,,,,", "opt\0", 1, 1, "opt", "x", "x", "");
+        do_fstab_filter_options(",,,opt=x,,,,", "opt\0", 1, 1, "opt", "x", "x", "");
 
         /* escaped characters */
-        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt1\0", 1, "opt1", "\\", "opt2=\\xff");
-        do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, "opt2", "\\xff", "opt1=\\");
+        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=\\");
 }
 
 static void test_fstab_find_pri(void) {

From d6cef552dcb4764a89269ce9603eb21f348d911a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Thu, 11 Mar 2021 11:10:32 +0100
Subject: [PATCH 9/9] fstab-generator: get rid of fstab_extract_values()

This was a parallel implementation of option parsing that didn't
support escaping of separators. Let's port this over to the common code.

Fixes #18952.
---
 src/fstab-generator/fstab-generator.c | 14 ++++++-------
 src/shared/fstab-util.c               | 29 ---------------------------
 src/shared/fstab-util.h               |  2 --
 3 files changed, 7 insertions(+), 38 deletions(-)

diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index b454a5980d4..8c1087a9a33 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -241,7 +241,7 @@ static int write_dependency(
         assert(f);
         assert(opts);
 
-        r = fstab_extract_values(opts, filter, &names);
+        r = fstab_filter_options(opts, filter, NULL, NULL, &names, NULL);
         if (r < 0)
                 return log_warning_errno(r, "Failed to parse options: %m");
         if (r == 0)
@@ -274,17 +274,17 @@ static int write_dependency(
 
 static int write_after(FILE *f, const char *opts) {
         return write_dependency(f, opts,
-                                "x-systemd.after", "After=%1$s\n");
+                                "x-systemd.after\0", "After=%1$s\n");
 }
 
 static int write_requires_after(FILE *f, const char *opts) {
         return write_dependency(f, opts,
-                                "x-systemd.requires", "After=%1$s\nRequires=%1$s\n");
+                                "x-systemd.requires\0", "After=%1$s\nRequires=%1$s\n");
 }
 
 static int write_before(FILE *f, const char *opts) {
         return write_dependency(f, opts,
-                                "x-systemd.before", "Before=%1$s\n");
+                                "x-systemd.before\0", "Before=%1$s\n");
 }
 
 static int write_requires_mounts_for(FILE *f, const char *opts) {
@@ -295,7 +295,7 @@ static int write_requires_mounts_for(FILE *f, const char *opts) {
         assert(f);
         assert(opts);
 
-        r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths);
+        r = fstab_filter_options(opts, "x-systemd.requires-mounts-for\0", NULL, NULL, &paths, NULL);
         if (r < 0)
                 return log_warning_errno(r, "Failed to parse options: %m");
         if (r == 0)
@@ -376,11 +376,11 @@ static int add_mount(
             mount_point_ignore(where))
                 return 0;
 
-        r = fstab_extract_values(opts, "x-systemd.wanted-by", &wanted_by);
+        r = fstab_filter_options(opts, "x-systemd.wanted-by\0", NULL, NULL, &wanted_by, NULL);
         if (r < 0)
                 return r;
 
-        r = fstab_extract_values(opts, "x-systemd.required-by", &required_by);
+        r = fstab_filter_options(opts, "x-systemd.required-by\0", NULL, NULL, &required_by, NULL);
         if (r < 0)
                 return r;
 
diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c
index 6674ed4a19f..7fd3d9c2c34 100644
--- a/src/shared/fstab-util.c
+++ b/src/shared/fstab-util.c
@@ -210,35 +210,6 @@ int fstab_filter_options(
         return !!namefound;
 }
 
-int fstab_extract_values(const char *opts, const char *name, char ***values) {
-        _cleanup_strv_free_ char **optsv = NULL, **res = NULL;
-        char **s;
-
-        assert(opts);
-        assert(name);
-        assert(values);
-
-        optsv = strv_split(opts, ",");
-        if (!optsv)
-                return -ENOMEM;
-
-        STRV_FOREACH(s, optsv) {
-                char *arg;
-                int r;
-
-                arg = startswith(*s, name);
-                if (!arg || *arg != '=')
-                        continue;
-                r = strv_extend(&res, arg + 1);
-                if (r < 0)
-                        return r;
-        }
-
-        *values = TAKE_PTR(res);
-
-        return !!*values;
-}
-
 int fstab_find_pri(const char *options, int *ret) {
         _cleanup_free_ char *opt = NULL;
         int r, pri;
diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h
index 97f40221afb..6b596baafa1 100644
--- a/src/shared/fstab-util.h
+++ b/src/shared/fstab-util.h
@@ -18,8 +18,6 @@ int fstab_filter_options(
                 char ***ret_values,
                 char **ret_filtered);
 
-int fstab_extract_values(const char *opts, const char *name, char ***values);
-
 static inline bool fstab_test_option(const char *opts, const char *names) {
         return !!fstab_filter_options(opts, names, NULL, NULL, NULL, NULL);
 }