]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #974 from teg/resolved-fixes-2
authorDaniel Mack <github@zonque.org>
Mon, 17 Aug 2015 08:04:31 +0000 (10:04 +0200)
committerDaniel Mack <github@zonque.org>
Mon, 17 Aug 2015 08:04:31 +0000 (10:04 +0200)
resolved: debugging improvements

17 files changed:
README
TODO
man/systemd-nspawn.xml
src/basic/env-util.c
src/basic/strv.c
src/basic/strv.h
src/basic/util.c
src/basic/util.h
src/core/load-fragment.c
src/journal-remote/journal-remote.c
src/locale/localed.c
src/nspawn/nspawn.c
src/shared/condition.c
src/sysusers/sysusers.c
src/test/test-strv.c
src/test/test-util.c
src/tmpfiles/tmpfiles.c

diff --git a/README b/README
index 75959497650b63ae59bff341121abbb9c31c3a55..d84bf8ea69e05ef3d115656c2eb815ef541449e9 100644 (file)
--- a/README
+++ b/README
@@ -274,8 +274,3 @@ WARNINGS:
         servers if not specified otherwise at configure time. You
         really should not ship an OS or device with this default
         setting. See DISTRO_PORTING for details.
-
-ENGINEERING AND CONSULTING SERVICES:
-        ENDOCODE <https://endocode.com/> offers professional
-        engineering and consulting services for systemd. Please
-        contact Chris Kühl <chris@endocode.com> for more information.
diff --git a/TODO b/TODO
index 14a95177aae9169f86fd1cc1492acac60284b40f..fdb073aa2cc4ccecb3a7ca463e574a6c4ef8774a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -6,7 +6,7 @@ Bugfixes:
   automount points even when the original .automount file did not exist
   anymore. Only the .mount unit was still around.
 
-* ExecStart with unicode characters fails in strv_split_quoted:
+* ExecStart with unicode characters fails in strv_split_extract:
 
           [Service]
           Environment=ONE='one' "TWO='two two' too" THREE=
@@ -273,7 +273,7 @@ Features:
 
 * maybe add support for specifier expansion in user.conf, specifically DefaultEnvironment=
 
-* code cleanup: retire FOREACH_WORD_QUOTED, port to unquote_first_word() loops instead
+* code cleanup: retire FOREACH_WORD_QUOTED, port to extract_first_word() loops instead
 
 * introduce systemd-timesync-wait.service or so to sync on an NTP fix?
 
@@ -309,7 +309,7 @@ Features:
 
 * exponential backoff in timesyncd and resolved when we cannot reach a server
 
-* unquote_many_words() should probably be used by a lot of code that
+* extract_many_words() should probably be used by a lot of code that
   currently uses FOREACH_WORD and friends. For example, most conf
   parsing callbacks should use it.
 
@@ -472,7 +472,7 @@ Features:
   ReadOnlyDirectories=... for whitelisting files for a service.
 
 * sd-bus:
-  - figure out wat the UNPRIVILEGED flag shall mean for props that can be read *and* written
+  - change argv list matching logic
   - GetAllProperties() on a non-existing object does not result in a failure currently
   - kdbus: process fd=-1 for incoming msgs
   - port to sd-resolve for connecting to TCP dbus servers
index d1e050cb897d4dfea4840d197ebce0f675dcc1ca..496674925973d77bbff73dcd9ff147de88d00f10 100644 (file)
         same path in the container --, or a colon-separated pair of
         paths -- in which case the first specified path is the source
         in the host, and the second path is the destination in the
-        container. This option may be specified multiple times for
+        container. Backslash escapes are interpreted so
+        <literal>\:</literal> may be used to embed colons in either path.
+        This option may be specified multiple times for
         creating multiple independent bind mount points. The
         <option>--bind-ro=</option> option creates read-only bind
         mounts.</para></listitem>
         otherwise specified). This option is particularly useful for
         mounting directories such as <filename>/var</filename> as
         tmpfs, to allow state-less systems, in particular when
-        combined with <option>--read-only</option>.</para></listitem>
+        combined with <option>--read-only</option>.
+        Backslash escapes are interpreted in the path so
+        <literal>\:</literal> may be used to embed colons in the path.
+        </para></listitem>
       </varlistentry>
 
       <varlistentry>
         list of colon-separated paths to the directory trees to
         combine and the destination mount point.</para>
 
+        <para>Backslash escapes are interpreted in the paths, so
+        <literal>\:</literal> may be used to embed colons in the paths.
+        </para>
+
         <para>If three or more paths are specified, then the last
         specified path is the destination mount point in the
         container, all paths specified before refer to directory trees
index ac7bbdc7110c3f85d5149392c1058fb2894f1478..4804a67f914e5f19a0ebbedda296df30d9ca20c5 100644 (file)
@@ -550,7 +550,7 @@ char **replace_env_argv(char **argv, char **env) {
                         if (e) {
                                 int r;
 
-                                r = strv_split_quoted(&m, e, UNQUOTE_RELAX);
+                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
                                 if (r < 0) {
                                         ret[k] = NULL;
                                         strv_free(ret);
index d44a72fc484d09966ac745066499a71cedd05c65..eaf440a4b2bc7c168775bd48b3dfeb1c48613e01 100644 (file)
@@ -278,7 +278,7 @@ char **strv_split_newlines(const char *s) {
         return l;
 }
 
-int strv_split_quoted(char ***t, const char *s, UnquoteFlags flags) {
+int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) {
         size_t n = 0, allocated = 0;
         _cleanup_strv_free_ char **l = NULL;
         int r;
@@ -289,11 +289,12 @@ int strv_split_quoted(char ***t, const char *s, UnquoteFlags flags) {
         for (;;) {
                 _cleanup_free_ char *word = NULL;
 
-                r = unquote_first_word(&s, &word, flags);
+                r = extract_first_word(&s, &word, separators, flags);
                 if (r < 0)
                         return r;
-                if (r == 0)
+                if (r == 0) {
                         break;
+                }
 
                 if (!GREEDY_REALLOC(l, allocated, n + 2))
                         return -ENOMEM;
@@ -693,6 +694,26 @@ char **strv_reverse(char **l) {
         return l;
 }
 
+char **strv_shell_escape(char **l, const char *bad) {
+        char **s;
+
+        /* Escapes every character in every string in l that is in bad,
+         * edits in-place, does not roll-back on error. */
+
+        STRV_FOREACH(s, l) {
+                char *v;
+
+                v = shell_escape(*s, bad);
+                if (!v)
+                        return NULL;
+
+                free(*s);
+                *s = v;
+        }
+
+        return l;
+}
+
 bool strv_fnmatch(char* const* patterns, const char *s, int flags) {
         char* const* p;
 
index 22f8f98fdad09fc672ded27dba2eea1e732b7b60..f07da8cdf3b7965251ef851278cd4e68ba1c5058 100644 (file)
@@ -73,7 +73,7 @@ static inline bool strv_isempty(char * const *l) {
 char **strv_split(const char *s, const char *separator);
 char **strv_split_newlines(const char *s);
 
-int strv_split_quoted(char ***t, const char *s, UnquoteFlags flags);
+int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags);
 
 char *strv_join(char **l, const char *separator);
 char *strv_join_quoted(char **l);
@@ -145,6 +145,7 @@ void strv_print(char **l);
         }))
 
 char **strv_reverse(char **l);
+char **strv_shell_escape(char **l, const char *bad);
 
 bool strv_fnmatch(char* const* patterns, const char *s, int flags);
 
index d4d3d3c83a88536aab3c5e57c6f440753fab48d3..ebfc6c6a7275317eab86f410890b9d1b4294a6a8 100644 (file)
@@ -4843,7 +4843,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) {
                 _cleanup_free_ char *word = NULL;
                 char *value = NULL;
 
-                r = unquote_first_word(&p, &word, UNQUOTE_RELAX);
+                r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -4883,7 +4883,7 @@ int get_proc_cmdline_key(const char *key, char **value) {
                 _cleanup_free_ char *word = NULL;
                 const char *e;
 
-                r = unquote_first_word(&p, &word, UNQUOTE_RELAX);
+                r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -5698,7 +5698,7 @@ int is_device_node(const char *path) {
         return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
 }
 
-int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
         _cleanup_free_ char *s = NULL;
         size_t allocated = 0, sz = 0;
         int r;
@@ -5711,13 +5711,19 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
                 SINGLE_QUOTE_ESCAPE,
                 DOUBLE_QUOTE,
                 DOUBLE_QUOTE_ESCAPE,
-                SPACE,
+                SEPARATOR,
         } state = START;
 
         assert(p);
-        assert(*p);
         assert(ret);
 
+        if (!separators)
+                separators = WHITESPACE;
+
+        /* Bail early if called after last value or with no input */
+        if (!*p)
+                goto finish_force_terminate;
+
         /* Parses the first word of a string, and returns it in
          * *ret. Removes all quotes in the process. When parsing fails
          * (because of an uneven number of quotes or similar), leaves
@@ -5729,32 +5735,46 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
                 switch (state) {
 
                 case START:
-                        if (c == 0)
-                                goto finish;
-                        else if (strchr(WHITESPACE, c))
+                        if (c == 0) {
+                                if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
+                                        if (!GREEDY_REALLOC(s, allocated, sz+1))
+                                                return -ENOMEM;
+                                goto finish_force_terminate;
+                        } else if (strchr(separators, c)) {
+                                if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
+                                        if (!GREEDY_REALLOC(s, allocated, sz+1))
+                                                return -ENOMEM;
+                                        (*p) ++;
+                                        goto finish_force_next;
+                                }
                                 break;
+                        }
 
                         state = VALUE;
                         /* fallthrough */
 
                 case VALUE:
                         if (c == 0)
-                                goto finish;
-                        else if (c == '\'') {
+                                goto finish_force_terminate;
+                        else if (c == '\'' && (flags & EXTRACT_QUOTES)) {
                                 if (!GREEDY_REALLOC(s, allocated, sz+1))
                                         return -ENOMEM;
 
                                 state = SINGLE_QUOTE;
                         } else if (c == '\\')
                                 state = VALUE_ESCAPE;
-                        else if (c == '\"') {
+                        else if (c == '\"' && (flags & EXTRACT_QUOTES)) {
                                 if (!GREEDY_REALLOC(s, allocated, sz+1))
                                         return -ENOMEM;
 
                                 state = DOUBLE_QUOTE;
-                        } else if (strchr(WHITESPACE, c))
-                                state = SPACE;
-                        else {
+                        } else if (strchr(separators, c)) {
+                                if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
+                                        (*p) ++;
+                                        goto finish_force_next;
+                                }
+                                state = SEPARATOR;
+                        } else {
                                 if (!GREEDY_REALLOC(s, allocated, sz+2))
                                         return -ENOMEM;
 
@@ -5765,8 +5785,8 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
 
                 case SINGLE_QUOTE:
                         if (c == 0) {
-                                if (flags & UNQUOTE_RELAX)
-                                        goto finish;
+                                if (flags & EXTRACT_RELAX)
+                                        goto finish_force_terminate;
                                 return -EINVAL;
                         } else if (c == '\'')
                                 state = VALUE;
@@ -5804,29 +5824,29 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
                                 return -ENOMEM;
 
                         if (c == 0) {
-                                if ((flags & UNQUOTE_CUNESCAPE_RELAX) &&
-                                    (state == VALUE_ESCAPE || flags & UNQUOTE_RELAX)) {
+                                if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
+                                    (state == VALUE_ESCAPE || flags & EXTRACT_RELAX)) {
                                         /* If we find an unquoted trailing backslash and we're in
-                                         * UNQUOTE_CUNESCAPE_RELAX mode, keep it verbatim in the
+                                         * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
                                          * output.
                                          *
-                                         * Unbalanced quotes will only be allowed in UNQUOTE_RELAX
-                                         * mode, UNQUOTE_CUNESCAP_RELAX mode does not allow them.
+                                         * Unbalanced quotes will only be allowed in EXTRACT_RELAX
+                                         * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
                                          */
                                         s[sz++] = '\\';
-                                        goto finish;
+                                        goto finish_force_terminate;
                                 }
-                                if (flags & UNQUOTE_RELAX)
-                                        goto finish;
+                                if (flags & EXTRACT_RELAX)
+                                        goto finish_force_terminate;
                                 return -EINVAL;
                         }
 
-                        if (flags & UNQUOTE_CUNESCAPE) {
+                        if (flags & EXTRACT_CUNESCAPE) {
                                 uint32_t u;
 
                                 r = cunescape_one(*p, (size_t) -1, &c, &u);
                                 if (r < 0) {
-                                        if (flags & UNQUOTE_CUNESCAPE_RELAX) {
+                                        if (flags & EXTRACT_CUNESCAPE_RELAX) {
                                                 s[sz++] = '\\';
                                                 s[sz++] = c;
                                                 goto end_escape;
@@ -5849,24 +5869,29 @@ end_escape:
                                 VALUE;
                         break;
 
-                case SPACE:
+                case SEPARATOR:
                         if (c == 0)
+                                goto finish_force_terminate;
+                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
+                                goto finish_force_next;
+                        if (!strchr(separators, c))
                                 goto finish;
-                        if (!strchr(WHITESPACE, c))
-                                goto finish;
-
                         break;
                 }
 
                 (*p) ++;
         }
 
+finish_force_terminate:
+        *p = NULL;
 finish:
         if (!s) {
+                *p = NULL;
                 *ret = NULL;
                 return 0;
         }
 
+finish_force_next:
         s[sz] = 0;
         *ret = s;
         s = NULL;
@@ -5874,26 +5899,27 @@ finish:
         return 1;
 }
 
-int unquote_first_word_and_warn(
+int extract_first_word_and_warn(
                 const char **p,
                 char **ret,
-                UnquoteFlags flags,
+                const char *separators,
+                ExtractFlags flags,
                 const char *unit,
                 const char *filename,
                 unsigned line,
                 const char *rvalue) {
         /* Try to unquote it, if it fails, warn about it and try again but this
-         * time using UNQUOTE_CUNESCAPE_RELAX to keep the backslashes verbatim
+         * time using EXTRACT_CUNESCAPE_RELAX to keep the backslashes verbatim
          * in invalid escape sequences. */
         const char *save;
         int r;
 
         save = *p;
-        r = unquote_first_word(p, ret, flags);
-        if (r < 0 && !(flags&UNQUOTE_CUNESCAPE_RELAX)) {
-                /* Retry it with UNQUOTE_CUNESCAPE_RELAX. */
+        r = extract_first_word(p, ret, separators, flags);
+        if (r < 0 && !(flags&EXTRACT_CUNESCAPE_RELAX)) {
+                /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
                 *p = save;
-                r = unquote_first_word(p, ret, flags|UNQUOTE_CUNESCAPE_RELAX);
+                r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
                 if (r < 0)
                         log_syntax(unit, LOG_ERR, filename, line, EINVAL,
                                    "Unbalanced quoting in command line, ignoring: \"%s\"", rvalue);
@@ -5904,7 +5930,7 @@ int unquote_first_word_and_warn(
         return r;
 }
 
-int unquote_many_words(const char **p, UnquoteFlags flags, ...) {
+int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) {
         va_list ap;
         char **l;
         int n = 0, i, c, r;
@@ -5930,7 +5956,7 @@ int unquote_many_words(const char **p, UnquoteFlags flags, ...) {
         l = newa0(char*, n);
         for (c = 0; c < n; c++) {
 
-                r = unquote_first_word(p, &l[c], flags);
+                r = extract_first_word(p, &l[c], separators, flags);
                 if (r < 0) {
                         int j;
 
@@ -6511,6 +6537,32 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char
         return 0;
 }
 
+static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
+        assert(bad);
+
+        for (; *s; s++) {
+                if (*s == '\\' || strchr(bad, *s))
+                        *(t++) = '\\';
+
+                *(t++) = *s;
+        }
+
+        return t;
+}
+
+char *shell_escape(const char *s, const char *bad) {
+        char *r, *t;
+
+        r = new(char, strlen(s)*2+1);
+        if (!r)
+                return NULL;
+
+        t = strcpy_backslash_escaped(r, s, bad);
+        *t = 0;
+
+        return r;
+}
+
 char *shell_maybe_quote(const char *s) {
         const char *p;
         char *r, *t;
@@ -6537,13 +6589,7 @@ char *shell_maybe_quote(const char *s) {
         *(t++) = '"';
         t = mempcpy(t, s, p - s);
 
-        for (; *p; p++) {
-
-                if (strchr(SHELL_NEED_ESCAPE, *p))
-                        *(t++) = '\\';
-
-                *(t++) = *p;
-        }
+        t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE);
 
         *(t++)= '"';
         *t = 0;
index 426b7f7d1659652eb1d4787b8884f91df453ad8e..098f9edcc1637ec032056ec43a7fc83b86d77c6e 100644 (file)
@@ -854,15 +854,17 @@ int is_symlink(const char *path);
 int is_dir(const char *path, bool follow);
 int is_device_node(const char *path);
 
-typedef enum UnquoteFlags {
-        UNQUOTE_RELAX           = 1,
-        UNQUOTE_CUNESCAPE       = 2,
-        UNQUOTE_CUNESCAPE_RELAX = 4,
-} UnquoteFlags;
-
-int unquote_first_word(const char **p, char **ret, UnquoteFlags flags);
-int unquote_first_word_and_warn(const char **p, char **ret, UnquoteFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
-int unquote_many_words(const char **p, UnquoteFlags flags, ...) _sentinel_;
+typedef enum ExtractFlags {
+        EXTRACT_RELAX           = 1,
+        EXTRACT_CUNESCAPE       = 2,
+        EXTRACT_CUNESCAPE_RELAX = 4,
+        EXTRACT_QUOTES          = 8,
+        EXTRACT_DONT_COALESCE_SEPARATORS = 16,
+} ExtractFlags;
+
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
+int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
+int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_;
 
 static inline void free_and_replace(char **s, char *v) {
         free(*s);
@@ -917,6 +919,7 @@ void cmsg_close_all(struct msghdr *mh);
 
 int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
 
+char *shell_escape(const char *s, const char *bad);
 char *shell_maybe_quote(const char *s);
 
 int parse_mode(const char *s, mode_t *ret);
index 299172123e4b081b9896e73cd3d5aa308562e2a7..e1e3a5ffb72903f666980976ff240065302c07bb 100644 (file)
@@ -552,7 +552,7 @@ int config_parse_exec(
 
                 semicolon = false;
 
-                r = unquote_first_word_and_warn(&p, &firstword, UNQUOTE_CUNESCAPE, unit, filename, line, rvalue);
+                r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue);
                 if (r <= 0)
                         return 0;
 
@@ -614,7 +614,7 @@ int config_parse_exec(
 
                 path_kill_slashes(path);
 
-                for (;;) {
+                while (!isempty(p)) {
                         _cleanup_free_ char *word = NULL;
 
                         /* Check explicitly for an unquoted semicolon as
@@ -627,7 +627,7 @@ int config_parse_exec(
                         }
 
                         /* Check for \; explicitly, to not confuse it with \\;
-                         * or "\;" or "\\;" etc.  unquote_first_word would
+                         * or "\;" or "\\;" etc.  extract_first_word would
                          * return the same for all of those.  */
                         if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) {
                                 p += 2;
@@ -642,7 +642,7 @@ int config_parse_exec(
                                 continue;
                         }
 
-                        r = unquote_first_word_and_warn(&p, &word, UNQUOTE_CUNESCAPE, unit, filename, line, rvalue);
+                        r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue);
                         if (r == 0)
                                 break;
                         else if (r < 0)
index e3bd76051b7004dac3280e15da4f9e7435895c02..cdcda1cb52a17b1155d9928fb4f067db07be4424 100644 (file)
@@ -148,7 +148,7 @@ static int spawn_getter(const char *getter, const char *url) {
         _cleanup_strv_free_ char **words = NULL;
 
         assert(getter);
-        r = strv_split_quoted(&words, getter, 0);
+        r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES);
         if (r < 0)
                 return log_error_errno(r, "Failed to split getter option: %m");
 
index cb4052fdd5124c8fde4eb66387f1b0a8a6b12542..e9489f04c28a035982b5ea3505a643fcd1789f90 100644 (file)
@@ -222,7 +222,7 @@ static int x11_read_data(Context *c) {
                 if (in_section && first_word(l, "Option")) {
                         _cleanup_strv_free_ char **a = NULL;
 
-                        r = strv_split_quoted(&a, l, 0);
+                        r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
                         if (r < 0)
                                 return r;
 
@@ -245,7 +245,7 @@ static int x11_read_data(Context *c) {
                 } else if (!in_section && first_word(l, "Section")) {
                         _cleanup_strv_free_ char **a = NULL;
 
-                        r = strv_split_quoted(&a, l, 0);
+                        r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
                         if (r < 0)
                                 return -ENOMEM;
 
@@ -544,7 +544,7 @@ static int read_next_mapping(const char* filename,
                 if (l[0] == 0 || l[0] == '#')
                         continue;
 
-                r = strv_split_quoted(&b, l, 0);
+                r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
                 if (r < 0)
                         return r;
 
index 0e74f20f1ac9c7aa37af90b0183e29ae9f8d1517..347260013a33ad45499fd813184dae99c9a0c58d 100644 (file)
@@ -655,17 +655,22 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case ARG_BIND:
                 case ARG_BIND_RO: {
+                        const char *current = optarg;
                         _cleanup_free_ char *source = NULL, *destination = NULL;
                         CustomMount *m;
-                        char *e;
+                        _cleanup_strv_free_ char **strv = NULL;
 
-                        e = strchr(optarg, ':');
-                        if (e) {
-                                source = strndup(optarg, e - optarg);
-                                destination = strdup(e + 1);
-                        } else {
-                                source = strdup(optarg);
-                                destination = strdup(optarg);
+                        r = extract_many_words(&current, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL);
+                        switch (r) {
+                        case 1:
+                                destination = strdup(source);
+                        case 2:
+                                break;
+                        case -ENOMEM:
+                                return log_oom();
+                        default:
+                                log_error("Invalid bind mount specification: %s", optarg);
+                                return -EINVAL;
                         }
 
                         if (!source || !destination)
@@ -690,18 +695,21 @@ static int parse_argv(int argc, char *argv[]) {
                 }
 
                 case ARG_TMPFS: {
+                        const char *current = optarg;
                         _cleanup_free_ char *path = NULL, *opts = NULL;
                         CustomMount *m;
-                        char *e;
 
-                        e = strchr(optarg, ':');
-                        if (e) {
-                                path = strndup(optarg, e - optarg);
-                                opts = strdup(e + 1);
-                        } else {
-                                path = strdup(optarg);
-                                opts = strdup("mode=0755");
+                        r = extract_first_word(&current, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        else if (r < 0) {
+                                log_error("Invalid tmpfs specification: %s", optarg);
+                                return r;
                         }
+                        if (r)
+                                opts = strdup(current);
+                        else
+                                opts = strdup("mode=0755");
 
                         if (!path || !opts)
                                 return log_oom();
@@ -731,9 +739,13 @@ static int parse_argv(int argc, char *argv[]) {
                         unsigned n = 0;
                         char **i;
 
-                        lower = strv_split(optarg, ":");
-                        if (!lower)
+                        r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r == -ENOMEM)
                                 return log_oom();
+                        else if (r < 0) {
+                                log_error("Invalid overlay specification: %s", optarg);
+                                return r;
+                        }
 
                         STRV_FOREACH(i, lower) {
                                 if (!path_is_absolute(*i)) {
@@ -1227,6 +1239,21 @@ static int mount_tmpfs(const char *dest, CustomMount *m) {
         return 0;
 }
 
+static char *joined_and_escaped_lower_dirs(char * const *lower) {
+        _cleanup_strv_free_ char **sv = NULL;
+
+        sv = strv_copy(lower);
+        if (!sv)
+                return NULL;
+
+        strv_reverse(sv);
+
+        if (!strv_shell_escape(sv, ",:"))
+                return NULL;
+
+        return strv_join(sv, ":");
+}
+
 static int mount_overlay(const char *dest, CustomMount *m) {
         _cleanup_free_ char *lower = NULL;
         const char *where, *options;
@@ -1243,19 +1270,32 @@ static int mount_overlay(const char *dest, CustomMount *m) {
 
         (void) mkdir_p_label(m->source, 0755);
 
-        strv_reverse(m->lower);
-        lower = strv_join(m->lower, ":");
-        strv_reverse(m->lower);
+        lower = joined_and_escaped_lower_dirs(m->lower);
         if (!lower)
                 return log_oom();
 
-        if (m->read_only)
-                options = strjoina("lowerdir=", m->source, ":", lower);
-        else {
+        if (m->read_only) {
+                _cleanup_free_ char *escaped_source = NULL;
+
+                escaped_source = shell_escape(m->source, ",:");
+                if (!escaped_source)
+                        return log_oom();
+
+                options = strjoina("lowerdir=", escaped_source, ":", lower);
+        } else {
+                _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL;
+
                 assert(m->work_dir);
                 (void) mkdir_label(m->work_dir, 0700);
 
-                options = strjoina("lowerdir=", lower, ",upperdir=", m->source, ",workdir=", m->work_dir);
+                escaped_source = shell_escape(m->source, ",:");
+                if (!escaped_source)
+                        return log_oom();
+                escaped_work_dir = shell_escape(m->work_dir, ",:");
+                if (!escaped_work_dir)
+                        return log_oom();
+
+                options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir);
         }
 
         if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0)
index 24871b0daedb2b3491e78aaff0f42803ad1a79cc..f58e84a3d0daf62d5e8334c39b8bbdda8fc05d3d 100644 (file)
@@ -101,7 +101,7 @@ static int condition_test_kernel_command_line(Condition *c) {
                 _cleanup_free_ char *word = NULL;
                 bool found;
 
-                r = unquote_first_word(&p, &word, UNQUOTE_RELAX);
+                r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
                 if (r < 0)
                         return r;
                 if (r == 0)
index 3a92d120d26d65b1b9b88be79282fb2ba39caf45..b5e09cad26d91e460baa80ee70644c55d17ad5b0 100644 (file)
@@ -1380,7 +1380,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
         /* Parse columns */
         p = buffer;
-        r = unquote_many_words(&p, 0, &action, &name, &id, &description, &home, NULL);
+        r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
         if (r < 0) {
                 log_error("[%s:%u] Syntax error.", fname, line);
                 return r;
@@ -1389,7 +1389,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 log_error("[%s:%u] Missing action and name columns.", fname, line);
                 return -EINVAL;
         }
-        if (*p != 0) {
+        if (!isempty(p)) {
                 log_error("[%s:%u] Trailing garbage.", fname, line);
                 return -EINVAL;
         }
index 6e3c81395c81189312a52e60beebdb54e1e308b4..bff43950a9077cd602eb6b29f48f3c05d60343ea 100644 (file)
@@ -165,7 +165,7 @@ static void test_strv_quote_unquote(const char* const *split, const char *quoted
         assert_se(p);
         assert_se(streq(p, quoted));
 
-        r = strv_split_quoted(&s, quoted, 0);
+        r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES);
         assert_se(r == 0);
         assert_se(s);
         STRV_FOREACH(t, s) {
@@ -182,7 +182,7 @@ static void test_strv_unquote(const char *quoted, char **list) {
         char **t;
         int r;
 
-        r = strv_split_quoted(&s, quoted, 0);
+        r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES);
         assert_se(r == 0);
         assert_se(s);
         j = strv_join(s, " | ");
@@ -199,7 +199,7 @@ static void test_invalid_unquote(const char *quoted) {
         char **s = NULL;
         int r;
 
-        r = strv_split_quoted(&s, quoted, 0);
+        r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES);
         assert_se(s == NULL);
         assert_se(r == -EINVAL);
 }
@@ -219,6 +219,21 @@ static void test_strv_split(void) {
         }
 }
 
+static void test_strv_split_extract(void) {
+        _cleanup_strv_free_ char **l = NULL;
+        const char *str = ":foo\\:bar::waldo:";
+        int r;
+
+        r = strv_split_extract(&l, str, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        assert_se(r == 0);
+        assert_se(streq_ptr(l[0], ""));
+        assert_se(streq_ptr(l[1], "foo:bar"));
+        assert_se(streq_ptr(l[2], ""));
+        assert_se(streq_ptr(l[3], "waldo"));
+        assert_se(streq_ptr(l[4], ""));
+        assert_se(streq_ptr(l[5], NULL));
+}
+
 static void test_strv_split_newlines(void) {
         unsigned i = 0;
         char **s;
@@ -542,6 +557,18 @@ static void test_strv_reverse(void) {
         assert_se(streq_ptr(d[3], NULL));
 }
 
+static void test_strv_shell_escape(void) {
+        _cleanup_strv_free_ char **v = NULL;
+
+        v = strv_new("foo:bar", "bar,baz", "wal\\do", NULL);
+        assert_se(v);
+        assert_se(strv_shell_escape(v, ",:"));
+        assert_se(streq_ptr(v[0], "foo\\:bar"));
+        assert_se(streq_ptr(v[1], "bar\\,baz"));
+        assert_se(streq_ptr(v[2], "wal\\\\do"));
+        assert_se(streq_ptr(v[3], NULL));
+}
+
 int main(int argc, char *argv[]) {
         test_specifier_printf();
         test_strv_foreach();
@@ -583,6 +610,7 @@ int main(int argc, char *argv[]) {
         test_invalid_unquote("'x'y'g");
 
         test_strv_split();
+        test_strv_split_extract();
         test_strv_split_newlines();
         test_strv_split_nulstr();
         test_strv_parse_nulstr();
@@ -598,6 +626,7 @@ int main(int argc, char *argv[]) {
         test_strv_equal();
         test_strv_is_uniq();
         test_strv_reverse();
+        test_strv_shell_escape();
 
         return 0;
 }
index 03e18df0802cf8a612ff376a7998269e6fc99818..3c1bac4606b1c2076badc8c077340e24c4485521 100644 (file)
@@ -1487,350 +1487,448 @@ static void test_execute_directory(void) {
         (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
 }
 
-static void test_unquote_first_word(void) {
+static void test_extract_first_word(void) {
         const char *p, *original;
         char *t;
 
         p = original = "foobar waldo";
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
         assert_se(streq(t, "foobar"));
         free(t);
         assert_se(p == original + 7);
 
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
         assert_se(streq(t, "waldo"));
         free(t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
-        assert_se(unquote_first_word(&p, &t, 0) == 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 0);
         assert_se(!t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
         p = original = "\"foobar\" \'waldo\'";
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
+        assert_se(streq(t, "\"foobar\""));
+        free(t);
+        assert_se(p == original + 9);
+
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
+        assert_se(streq(t, "\'waldo\'"));
+        free(t);
+        assert_se(isempty(p));
+
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 0);
+        assert_se(!t);
+        assert_se(isempty(p));
+
+        p = original = "\"foobar\" \'waldo\'";
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0);
         assert_se(streq(t, "foobar"));
         free(t);
         assert_se(p == original + 9);
 
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0);
         assert_se(streq(t, "waldo"));
         free(t);
-        assert_se(p == original + 16);
+        assert_se(isempty(p));
 
-        assert_se(unquote_first_word(&p, &t, 0) == 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 0);
         assert_se(!t);
-        assert_se(p == original + 16);
+        assert_se(isempty(p));
 
         p = original = "\"";
-        assert_se(unquote_first_word(&p, &t, 0) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 1);
+        assert_se(streq(t, "\""));
+        assert_se(isempty(p));
+
+        p = original = "\"";
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) == -EINVAL);
         assert_se(p == original + 1);
 
         p = original = "\'";
-        assert_se(unquote_first_word(&p, &t, 0) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 1);
+        assert_se(streq(t, "\'"));
+        assert_se(isempty(p));
+
+        p = original = "\'";
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) == -EINVAL);
         assert_se(p == original + 1);
 
         p = original = "\'fooo";
-        assert_se(unquote_first_word(&p, &t, 0) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == 1);
+        assert_se(streq(t, "\'fooo"));
+        assert_se(isempty(p));
+
+        p = original = "\'fooo";
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\'fooo";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "yay\'foo\'bar";
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
+        assert_se(streq(t, "yay\'foo\'bar"));
+        free(t);
+        assert_se(isempty(p));
+
+        p = original = "yay\'foo\'bar";
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0);
         assert_se(streq(t, "yayfoobar"));
         free(t);
-        assert_se(p == original + 11);
+        assert_se(isempty(p));
 
         p = original = "   foobar   ";
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
         assert_se(streq(t, "foobar"));
         free(t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
         p = original = " foo\\ba\\x6ar ";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0);
         assert_se(streq(t, "foo\ba\x6ar"));
         free(t);
-        assert_se(p == original + 13);
+        assert_se(isempty(p));
 
         p = original = " foo\\ba\\x6ar ";
-        assert_se(unquote_first_word(&p, &t, 0) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, 0) > 0);
         assert_se(streq(t, "foobax6ar"));
         free(t);
-        assert_se(p == original + 13);
+        assert_se(isempty(p));
 
         p = original = "    f\\u00f6o \"pi\\U0001F4A9le\"   ";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0);
         assert_se(streq(t, "föo"));
         free(t);
         assert_se(p == original + 13);
 
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE) > 0);
         assert_se(streq(t, "pi\360\237\222\251le"));
         free(t);
-        assert_se(p == original + 32);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX|UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word(&p, &t, 0) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, 0) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_RELAX) > 0);
+        assert_se(streq(t, "foo"));
+        free(t);
+        assert_se(isempty(p));
+
+        p = original = "foo::bar";
+        assert_se(extract_first_word(&p, &t, ":", 0) == 1);
         assert_se(streq(t, "foo"));
         free(t);
         assert_se(p == original + 5);
 
+        assert_se(extract_first_word(&p, &t, ":", 0) == 1);
+        assert_se(streq(t, "bar"));
+        free(t);
+        assert_se(isempty(p));
+
+        assert_se(extract_first_word(&p, &t, ":", 0) == 0);
+        assert_se(!t);
+        assert_se(isempty(p));
+
+        p = original = "foo\\:bar::waldo";
+        assert_se(extract_first_word(&p, &t, ":", 0) == 1);
+        assert_se(streq(t, "foo:bar"));
+        free(t);
+        assert_se(p == original + 10);
+
+        assert_se(extract_first_word(&p, &t, ":", 0) == 1);
+        assert_se(streq(t, "waldo"));
+        free(t);
+        assert_se(isempty(p));
+
+        assert_se(extract_first_word(&p, &t, ":", 0) == 0);
+        assert_se(!t);
+        assert_se(isempty(p));
+
         p = original = "\"foo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE_RELAX) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX|UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "foo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_CUNESCAPE_RELAX|UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "foo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX|UNQUOTE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "fooo\\ bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "\\w+@\\K[\\d.]+";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) == -EINVAL);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) == -EINVAL);
         assert_se(p == original + 1);
 
         p = original = "\\w+@\\K[\\d.]+";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "\\w+@\\K[\\d.]+"));
         free(t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
         p = original = "\\w+\\b";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_CUNESCAPE_RELAX) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX) > 0);
         assert_se(streq(t, "\\w+\b"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "-N ''";
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0);
         assert_se(streq(t, "-N"));
         free(t);
         assert_se(p == original + 3);
 
-        assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0);
+        assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0);
         assert_se(streq(t, ""));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
+
+        p = original = ":foo\\:bar::waldo:";
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+        assert_se(t);
+        assert_se(streq(t, ""));
+        free(t);
+        assert_se(p == original + 1);
+
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+        assert_se(streq(t, "foo:bar"));
+        free(t);
+        assert_se(p == original + 10);
+
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+        assert_se(t);
+        assert_se(streq(t, ""));
+        free(t);
+        assert_se(p == original + 11);
+
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+        assert_se(streq(t, "waldo"));
+        free(t);
+        assert_se(p == original + 17);
+
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+        assert_se(streq(t, ""));
+        free(t);
+        assert_se(p == NULL);
+
+        assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 0);
+        assert_se(!t);
+        assert_se(!p);
 }
 
-static void test_unquote_first_word_and_warn(void) {
+static void test_extract_first_word_and_warn(void) {
         const char *p, *original;
         char *t;
 
         p = original = "foobar waldo";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foobar"));
         free(t);
         assert_se(p == original + 7);
 
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "waldo"));
         free(t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) == 0);
         assert_se(!t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
         p = original = "\"foobar\" \'waldo\'";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foobar"));
         free(t);
         assert_se(p == original + 9);
 
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "waldo"));
         free(t);
-        assert_se(p == original + 16);
+        assert_se(isempty(p));
 
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) == 0);
         assert_se(!t);
-        assert_se(p == original + 16);
+        assert_se(isempty(p));
 
         p = original = "\"";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == -EINVAL);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) == -EINVAL);
         assert_se(p == original + 1);
 
         p = original = "\'";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == -EINVAL);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) == -EINVAL);
         assert_se(p == original + 1);
 
         p = original = "\'fooo";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == -EINVAL);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\'fooo";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_RELAX, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_RELAX, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = " foo\\ba\\x6ar ";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foo\ba\x6ar"));
         free(t);
-        assert_se(p == original + 13);
+        assert_se(isempty(p));
 
         p = original = " foo\\ba\\x6ar ";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foobax6ar"));
         free(t);
-        assert_se(p == original + 13);
+        assert_se(isempty(p));
 
         p = original = "    f\\u00f6o \"pi\\U0001F4A9le\"   ";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "föo"));
         free(t);
         assert_se(p == original + 13);
 
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "pi\360\237\222\251le"));
         free(t);
-        assert_se(p == original + 32);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_RELAX, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo\\"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) == -EINVAL);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES, NULL, "fake", 1, original) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_RELAX, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_RELAX, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) == -EINVAL);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, NULL, "fake", 1, original) == -EINVAL);
         assert_se(p == original + 5);
 
         p = original = "\"foo\\";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_RELAX, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "foo"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_RELAX, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word_and_warn(&p, &t, 0, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "fooo\\ bar quux";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "fooo\\ bar"));
         free(t);
         assert_se(p == original + 10);
 
         p = original = "\\w+@\\K[\\d.]+";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "\\w+@\\K[\\d.]+"));
         free(t);
-        assert_se(p == original + 12);
+        assert_se(isempty(p));
 
         p = original = "\\w+\\b";
-        assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE, NULL, "fake", 1, original) > 0);
+        assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0);
         assert_se(streq(t, "\\w+\b"));
         free(t);
-        assert_se(p == original + 5);
+        assert_se(isempty(p));
 }
 
-static void test_unquote_many_words(void) {
+static void test_extract_many_words(void) {
         const char *p, *original;
         char *a, *b, *c;
 
         p = original = "foobar waldi piep";
-        assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 3);
-        assert_se(p == original + 17);
+        assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 3);
+        assert_se(isempty(p));
         assert_se(streq_ptr(a, "foobar"));
         assert_se(streq_ptr(b, "waldi"));
         assert_se(streq_ptr(c, "piep"));
@@ -1839,8 +1937,17 @@ static void test_unquote_many_words(void) {
         free(c);
 
         p = original = "'foobar' wa\"ld\"i   ";
-        assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 2);
-        assert_se(p == original + 19);
+        assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 2);
+        assert_se(isempty(p));
+        assert_se(streq_ptr(a, "'foobar'"));
+        assert_se(streq_ptr(b, "wa\"ld\"i"));
+        assert_se(streq_ptr(c, NULL));
+        free(a);
+        free(b);
+
+        p = original = "'foobar' wa\"ld\"i   ";
+        assert_se(extract_many_words(&p, NULL, EXTRACT_QUOTES, &a, &b, &c, NULL) == 2);
+        assert_se(isempty(p));
         assert_se(streq_ptr(a, "foobar"));
         assert_se(streq_ptr(b, "waldi"));
         assert_se(streq_ptr(c, NULL));
@@ -1848,32 +1955,32 @@ static void test_unquote_many_words(void) {
         free(b);
 
         p = original = "";
-        assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 0);
-        assert_se(p == original);
+        assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0);
+        assert_se(isempty(p));
         assert_se(streq_ptr(a, NULL));
         assert_se(streq_ptr(b, NULL));
         assert_se(streq_ptr(c, NULL));
 
         p = original = "  ";
-        assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 0);
-        assert_se(p == original+2);
+        assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0);
+        assert_se(isempty(p));
         assert_se(streq_ptr(a, NULL));
         assert_se(streq_ptr(b, NULL));
         assert_se(streq_ptr(c, NULL));
 
         p = original = "foobar";
-        assert_se(unquote_many_words(&p, 0, NULL) == 0);
+        assert_se(extract_many_words(&p, NULL, 0, NULL) == 0);
         assert_se(p == original);
 
         p = original = "foobar waldi";
-        assert_se(unquote_many_words(&p, 0, &a, NULL) == 1);
+        assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1);
         assert_se(p == original+7);
         assert_se(streq_ptr(a, "foobar"));
         free(a);
 
         p = original = "     foobar    ";
-        assert_se(unquote_many_words(&p, 0, &a, NULL) == 1);
-        assert_se(p == original+15);
+        assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1);
+        assert_se(isempty(p));
         assert_se(streq_ptr(a, "foobar"));
         free(a);
 }
@@ -1993,6 +2100,21 @@ static void test_sparse_write(void) {
         test_sparse_write_one(fd, test_e, sizeof(test_e));
 }
 
+static void test_shell_escape_one(const char *s, const char *bad, const char *expected) {
+        _cleanup_free_ char *r;
+
+        assert_se(r = shell_escape(s, bad));
+        assert_se(streq_ptr(r, expected));
+}
+
+static void test_shell_escape(void) {
+        test_shell_escape_one("", "", "");
+        test_shell_escape_one("\\", "", "\\\\");
+        test_shell_escape_one("foobar", "", "foobar");
+        test_shell_escape_one("foobar", "o", "f\\o\\obar");
+        test_shell_escape_one("foo:bar,baz", ",:", "foo\\:bar\\,baz");
+}
+
 static void test_shell_maybe_quote_one(const char *s, const char *expected) {
         _cleanup_free_ char *r;
 
@@ -2149,14 +2271,15 @@ int main(int argc, char *argv[]) {
         test_search_and_fopen_nulstr();
         test_glob_exists();
         test_execute_directory();
-        test_unquote_first_word();
-        test_unquote_first_word_and_warn();
-        test_unquote_many_words();
+        test_extract_first_word();
+        test_extract_first_word_and_warn();
+        test_extract_many_words();
         test_parse_proc_cmdline();
         test_raw_clone();
         test_same_fd();
         test_uid_ptr();
         test_sparse_write();
+        test_shell_escape();
         test_shell_maybe_quote();
         test_parse_mode();
         test_tempfn();
index d6f78015615c22907c5a25ee4fa508cd5de477d2..8f29256c6da9175983b1d3a4487a278a3104838e 100644 (file)
@@ -662,7 +662,7 @@ static int parse_xattrs_from_arg(Item *i) {
         for (;;) {
                 _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL;
 
-                r = unquote_first_word(&p, &xattr, UNQUOTE_CUNESCAPE);
+                r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
                 if (r < 0)
                         log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p);
                 if (r <= 0)
@@ -1760,9 +1760,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
         assert(line >= 1);
         assert(buffer);
 
-        r = unquote_many_words(
+        r = extract_many_words(
                         &buffer,
-                        0,
+                        NULL,
+                        EXTRACT_QUOTES,
                         &action,
                         &path,
                         &mode,