From 4b1c17535115b70f4ddf4bf5850049b885a40173 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:29 +0000 Subject: [PATCH 01/15] Convert unquote_*_word users to expect isempty(p) after the last entry This is so that, when called in a loop, unquote_first_word can distinguish between reaching the end of a string because it has consumed all the input before the end, and consuming all the input. This is important because we later add a flag that allows char *in = ""; char *out; unquote_first_word(&in, &out, flags); To put "" in out, and set in = NULL, so the trailing empty string of the input can be consumed, and mark that the input has been consumed. --- src/core/load-fragment.c | 2 +- src/sysusers/sysusers.c | 2 +- src/test/test-util.c | 80 ++++++++++++++++++++-------------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 299172123e..fc5b3477ff 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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 diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 3a92d120d2..2eb0dce6c3 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -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; } diff --git a/src/test/test-util.c b/src/test/test-util.c index 03e18df080..95f1fad44a 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -1500,11 +1500,11 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, 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(!t); - assert_se(p == original + 12); + assert_se(isempty(p)); p = original = "\"foobar\" \'waldo\'"; assert_se(unquote_first_word(&p, &t, 0) > 0); @@ -1515,11 +1515,11 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, 0) > 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(!t); - assert_se(p == original + 16); + assert_se(isempty(p)); p = original = "\""; assert_se(unquote_first_word(&p, &t, 0) == -EINVAL); @@ -1537,31 +1537,31 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_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(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(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(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(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); @@ -1572,31 +1572,31 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_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(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(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(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(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); @@ -1606,7 +1606,7 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_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_RELAX) == -EINVAL); @@ -1616,13 +1616,13 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE_RELAX|UNQUOTE_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(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); @@ -1660,13 +1660,13 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_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(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); @@ -1677,7 +1677,7 @@ static void test_unquote_first_word(void) { assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0); assert_se(streq(t, "")); free(t); - assert_se(p == original + 5); + assert_se(isempty(p)); } static void test_unquote_first_word_and_warn(void) { @@ -1693,11 +1693,11 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, 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(!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); @@ -1708,11 +1708,11 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, 0, 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(!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); @@ -1730,19 +1730,19 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_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(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(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); @@ -1753,25 +1753,25 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_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(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(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(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); @@ -1781,7 +1781,7 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_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); @@ -1791,7 +1791,7 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_CUNESCAPE|UNQUOTE_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); @@ -1815,13 +1815,13 @@ static void test_unquote_first_word_and_warn(void) { assert_se(unquote_first_word_and_warn(&p, &t, UNQUOTE_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(streq(t, "\\w+\b")); free(t); - assert_se(p == original + 5); + assert_se(isempty(p)); } static void test_unquote_many_words(void) { @@ -1830,7 +1830,7 @@ static void test_unquote_many_words(void) { p = original = "foobar waldi piep"; assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 3); - assert_se(p == original + 17); + assert_se(isempty(p)); assert_se(streq_ptr(a, "foobar")); assert_se(streq_ptr(b, "waldi")); assert_se(streq_ptr(c, "piep")); @@ -1840,7 +1840,7 @@ static void test_unquote_many_words(void) { 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(isempty(p)); assert_se(streq_ptr(a, "foobar")); assert_se(streq_ptr(b, "waldi")); assert_se(streq_ptr(c, NULL)); @@ -1849,14 +1849,14 @@ static void test_unquote_many_words(void) { p = original = ""; assert_se(unquote_many_words(&p, 0, &a, &b, &c, NULL) == 0); - assert_se(p == original); + 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(isempty(p)); assert_se(streq_ptr(a, NULL)); assert_se(streq_ptr(b, NULL)); assert_se(streq_ptr(c, NULL)); @@ -1873,7 +1873,7 @@ static void test_unquote_many_words(void) { p = original = " foobar "; assert_se(unquote_many_words(&p, 0, &a, NULL) == 1); - assert_se(p == original+15); + assert_se(isempty(p)); assert_se(streq_ptr(a, "foobar")); free(a); } From 53f0db71771bf757b6b429525a4e5db3dcf3afef Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 16:38:06 +0000 Subject: [PATCH 02/15] unquote_first_word: set *p=NULL on termination To add a flag to allow an empty string to be parsed as an argument, we need to be able to distinguish between the end of the string, and after the end of the string, so when we *do* reach the end, let's set *p to this state. --- src/basic/util.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/basic/util.c b/src/basic/util.c index d4d3d3c83a..bb7ec007d7 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -5715,9 +5715,12 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { } state = START; assert(p); - assert(*p); assert(ret); + /* 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 @@ -5730,7 +5733,7 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case START: if (c == 0) - goto finish; + goto finish_force_terminate; else if (strchr(WHITESPACE, c)) break; @@ -5739,7 +5742,7 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case VALUE: if (c == 0) - goto finish; + goto finish_force_terminate; else if (c == '\'') { if (!GREEDY_REALLOC(s, allocated, sz+1)) return -ENOMEM; @@ -5766,7 +5769,7 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case SINGLE_QUOTE: if (c == 0) { if (flags & UNQUOTE_RELAX) - goto finish; + goto finish_force_terminate; return -EINVAL; } else if (c == '\'') state = VALUE; @@ -5814,10 +5817,10 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { * mode, UNQUOTE_CUNESCAP_RELAX mode does not allow them. */ s[sz++] = '\\'; - goto finish; + goto finish_force_terminate; } if (flags & UNQUOTE_RELAX) - goto finish; + goto finish_force_terminate; return -EINVAL; } @@ -5861,8 +5864,11 @@ end_escape: (*p) ++; } +finish_force_terminate: + *p = NULL; finish: if (!s) { + *p = NULL; *ret = NULL; return 0; } From 6868560773ada8ea31d1f86422be6bf026a1f660 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 23 Jun 2015 16:20:53 +0000 Subject: [PATCH 03/15] util: change unquote_*_word to extract_*_word It now takes a separators argument, which defaults to WHITESPACE if NULL is passed. --- TODO | 4 +- src/basic/env-util.c | 2 +- src/basic/strv.c | 4 +- src/basic/strv.h | 2 +- src/basic/util.c | 58 +++++++------ src/basic/util.h | 16 ++-- src/core/load-fragment.c | 6 +- src/shared/condition.c | 2 +- src/sysusers/sysusers.c | 2 +- src/test/test-util.c | 178 +++++++++++++++++++++++---------------- src/tmpfiles/tmpfiles.c | 5 +- 11 files changed, 157 insertions(+), 122 deletions(-) diff --git a/TODO b/TODO index 628d5ba315..bb4d878c1a 100644 --- a/TODO +++ b/TODO @@ -267,7 +267,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? @@ -303,7 +303,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. diff --git a/src/basic/env-util.c b/src/basic/env-util.c index ac7bbdc711..9517fbc802 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -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_quoted(&m, e, EXTRACT_RELAX); if (r < 0) { ret[k] = NULL; strv_free(ret); diff --git a/src/basic/strv.c b/src/basic/strv.c index d44a72fc48..69eb8c7fdb 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -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_quoted(char ***t, const char *s, ExtractFlags flags) { size_t n = 0, allocated = 0; _cleanup_strv_free_ char **l = NULL; int r; @@ -289,7 +289,7 @@ 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, NULL, flags); if (r < 0) return r; if (r == 0) diff --git a/src/basic/strv.h b/src/basic/strv.h index 22f8f98fda..fe3dfd245c 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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_quoted(char ***t, const char *s, ExtractFlags flags); char *strv_join(char **l, const char *separator); char *strv_join_quoted(char **l); diff --git a/src/basic/util.c b/src/basic/util.c index bb7ec007d7..8ddd627ac0 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -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_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_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,12 +5711,15 @@ 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(ret); + if (!separators) + separators = WHITESPACE; + /* Bail early if called after last value or with no input */ if (!*p) goto finish_force_terminate; @@ -5734,7 +5737,7 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case START: if (c == 0) goto finish_force_terminate; - else if (strchr(WHITESPACE, c)) + else if (strchr(separators, c)) break; state = VALUE; @@ -5755,8 +5758,8 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { return -ENOMEM; state = DOUBLE_QUOTE; - } else if (strchr(WHITESPACE, c)) - state = SPACE; + } else if (strchr(separators, c)) + state = SEPARATOR; else { if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; @@ -5768,7 +5771,7 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case SINGLE_QUOTE: if (c == 0) { - if (flags & UNQUOTE_RELAX) + if (flags & EXTRACT_RELAX) goto finish_force_terminate; return -EINVAL; } else if (c == '\'') @@ -5807,29 +5810,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_force_terminate; } - if (flags & UNQUOTE_RELAX) + 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; @@ -5852,10 +5855,10 @@ end_escape: VALUE; break; - case SPACE: + case SEPARATOR: if (c == 0) goto finish; - if (!strchr(WHITESPACE, c)) + if (!strchr(separators, c)) goto finish; break; @@ -5880,26 +5883,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); @@ -5910,7 +5914,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; @@ -5936,7 +5940,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; diff --git a/src/basic/util.h b/src/basic/util.h index 426b7f7d16..a5d035b8bd 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -854,15 +854,15 @@ 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; +typedef enum ExtractFlags { + EXTRACT_RELAX = 1, + EXTRACT_CUNESCAPE = 2, + EXTRACT_CUNESCAPE_RELAX = 4, +} ExtractFlags; -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_; +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); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index fc5b3477ff..9c93f526e8 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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_CUNESCAPE, unit, filename, line, rvalue); if (r <= 0) return 0; @@ -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_CUNESCAPE, unit, filename, line, rvalue); if (r == 0) break; else if (r < 0) diff --git a/src/shared/condition.c b/src/shared/condition.c index 24871b0dae..f7e182284f 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -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_RELAX); if (r < 0) return r; if (r == 0) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 2eb0dce6c3..b08b3aba57 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -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, 0, &action, &name, &id, &description, &home, NULL); if (r < 0) { log_error("[%s:%u] Syntax error.", fname, line); return r; diff --git a/src/test/test-util.c b/src/test/test-util.c index 95f1fad44a..db80379395 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -1487,349 +1487,379 @@ 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(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(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(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(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(isempty(p)); p = original = "\""; - assert_se(unquote_first_word(&p, &t, 0) == -EINVAL); + assert_se(extract_first_word(&p, &t, NULL, 0) == -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) == -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) == -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_RELAX) > 0); assert_se(streq(t, "fooo")); free(t); 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, "yayfoobar")); free(t); 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(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(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(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_CUNESCAPE) > 0); assert_se(streq(t, "pi\360\237\222\251le")); free(t); 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(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(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(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(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_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_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_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0); assert_se(streq(t, "foo\\")); free(t); 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_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX|EXTRACT_RELAX) > 0); assert_se(streq(t, "foo\\")); free(t); 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(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(isempty(p)); p = original = "-N ''"; - assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0); + assert_se(extract_first_word(&p, &t, NULL, 0) > 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, 0) > 0); assert_se(streq(t, "")); free(t); assert_se(isempty(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(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(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, 0, 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, 0, NULL, "fake", 1, original) > 0); assert_se(streq(t, "waldo")); free(t); 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(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, 0, 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, 0, 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, 0, 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_RELAX, NULL, "fake", 1, original) > 0); assert_se(streq(t, "fooo")); free(t); 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(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(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_CUNESCAPE, NULL, "fake", 1, original) > 0); assert_se(streq(t, "pi\360\237\222\251le")); free(t); 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(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(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(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, 0, 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_RELAX, NULL, "fake", 1, original) > 0); assert_se(streq(t, "foo")); free(t); 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_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_CUNESCAPE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); assert_se(streq(t, "foo")); free(t); 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(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(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(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")); @@ -1839,7 +1869,7 @@ 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(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, "waldi")); @@ -1848,31 +1878,31 @@ 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(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(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(extract_many_words(&p, NULL, 0, &a, NULL) == 1); assert_se(isempty(p)); assert_se(streq_ptr(a, "foobar")); free(a); @@ -2149,9 +2179,9 @@ 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(); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index d6f7801561..d73138900d 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -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_CUNESCAPE); if (r < 0) log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p); if (r <= 0) @@ -1760,8 +1760,9 @@ 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, + NULL, 0, &action, &path, From 12ba2c44dde4d7cfc0e531dbc3cbd0581c323637 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 23 Jun 2015 16:26:49 +0000 Subject: [PATCH 04/15] util: Don't interpret quotes by default in extract_first_word This adds an EXTRACT_QUOTES option to allow the previous behaviour, of not interpreting any character inside ' or " quotes as separators. --- src/basic/strv.c | 2 +- src/basic/util.c | 8 ++-- src/basic/util.h | 1 + src/core/load-fragment.c | 4 +- src/shared/condition.c | 2 +- src/sysusers/sysusers.c | 2 +- src/test/test-util.c | 91 ++++++++++++++++++++++++++++++---------- src/tmpfiles/tmpfiles.c | 4 +- 8 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/basic/strv.c b/src/basic/strv.c index 69eb8c7fdb..72964a166a 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -289,7 +289,7 @@ int strv_split_quoted(char ***t, const char *s, ExtractFlags flags) { for (;;) { _cleanup_free_ char *word = NULL; - r = extract_first_word(&s, &word, NULL, flags); + r = extract_first_word(&s, &word, NULL, flags|EXTRACT_QUOTES); if (r < 0) return r; if (r == 0) diff --git a/src/basic/util.c b/src/basic/util.c index 8ddd627ac0..d4c385fcef 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -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 = extract_first_word(&p, &word, NULL, EXTRACT_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 = extract_first_word(&p, &word, NULL, EXTRACT_RELAX); + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) return r; if (r == 0) @@ -5746,14 +5746,14 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra case VALUE: if (c == 0) goto finish_force_terminate; - else if (c == '\'') { + 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; diff --git a/src/basic/util.h b/src/basic/util.h index a5d035b8bd..82fb771e20 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -858,6 +858,7 @@ typedef enum ExtractFlags { EXTRACT_RELAX = 1, EXTRACT_CUNESCAPE = 2, EXTRACT_CUNESCAPE_RELAX = 4, + EXTRACT_QUOTES = 8, } ExtractFlags; int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 9c93f526e8..e1e3a5ffb7 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -552,7 +552,7 @@ int config_parse_exec( semicolon = false; - r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_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; @@ -642,7 +642,7 @@ int config_parse_exec( continue; } - r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_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) diff --git a/src/shared/condition.c b/src/shared/condition.c index f7e182284f..f58e84a3d0 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -101,7 +101,7 @@ static int condition_test_kernel_command_line(Condition *c) { _cleanup_free_ char *word = NULL; bool found; - r = extract_first_word(&p, &word, NULL, EXTRACT_RELAX); + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) return r; if (r == 0) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index b08b3aba57..b5e09cad26 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1380,7 +1380,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { /* Parse columns */ p = buffer; - r = extract_many_words(&p, NULL, 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; diff --git a/src/test/test-util.c b/src/test/test-util.c index db80379395..40f5d34de3 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -1508,11 +1508,26 @@ static void test_extract_first_word(void) { p = original = "\"foobar\" \'waldo\'"; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "foobar")); + 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(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0); assert_se(streq(t, "waldo")); free(t); assert_se(isempty(p)); @@ -1522,25 +1537,46 @@ static void test_extract_first_word(void) { assert_se(isempty(p)); p = original = "\""; - assert_se(extract_first_word(&p, &t, NULL, 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(extract_first_word(&p, &t, NULL, 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(extract_first_word(&p, &t, NULL, 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(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES|EXTRACT_RELAX) > 0); assert_se(streq(t, "fooo")); free(t); assert_se(isempty(p)); p = original = "yay\'foo\'bar"; 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(isempty(p)); @@ -1569,7 +1605,7 @@ static void test_extract_first_word(void) { free(t); assert_se(p == original + 13); - assert_se(extract_first_word(&p, &t, NULL, EXTRACT_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(isempty(p)); @@ -1603,7 +1639,7 @@ static void test_extract_first_word(void) { assert_se(p == original + 5); p = original = "\"foo\\"; - assert_se(extract_first_word(&p, &t, NULL, EXTRACT_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)); @@ -1639,17 +1675,17 @@ static void test_extract_first_word(void) { assert_se(isempty(p)); p = original = "\"foo\\"; - assert_se(extract_first_word(&p, &t, NULL, EXTRACT_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(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE_RELAX|EXTRACT_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(isempty(p)); p = original = "\"foo\\"; - assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX|EXTRACT_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(isempty(p)); @@ -1699,12 +1735,12 @@ static void test_extract_first_word(void) { assert_se(isempty(p)); p = original = "-N ''"; - assert_se(extract_first_word(&p, &t, NULL, 0) > 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(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_QUOTES) > 0); assert_se(streq(t, "")); free(t); assert_se(isempty(p)); @@ -1730,12 +1766,12 @@ static void test_extract_first_word_and_warn(void) { assert_se(isempty(p)); p = original = "\"foobar\" \'waldo\'"; - assert_se(extract_first_word_and_warn(&p, &t, NULL, 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(extract_first_word_and_warn(&p, &t, NULL, 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(isempty(p)); @@ -1745,19 +1781,19 @@ static void test_extract_first_word_and_warn(void) { assert_se(isempty(p)); p = original = "\""; - assert_se(extract_first_word_and_warn(&p, &t, NULL, 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(extract_first_word_and_warn(&p, &t, NULL, 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(extract_first_word_and_warn(&p, &t, NULL, 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(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_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(isempty(p)); @@ -1780,7 +1816,7 @@ static void test_extract_first_word_and_warn(void) { free(t); assert_se(p == original + 13); - assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_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(isempty(p)); @@ -1804,21 +1840,21 @@ static void test_extract_first_word_and_warn(void) { assert_se(isempty(p)); p = original = "\"foo\\"; - assert_se(extract_first_word_and_warn(&p, &t, NULL, 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(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_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(isempty(p)); p = original = "\"foo\\"; - assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_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(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_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(isempty(p)); @@ -1871,6 +1907,15 @@ static void test_extract_many_words(void) { p = original = "'foobar' wa\"ld\"i "; 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)); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index d73138900d..8f29256c6d 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -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 = extract_first_word(&p, &xattr, NULL, EXTRACT_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) @@ -1763,7 +1763,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { r = extract_many_words( &buffer, NULL, - 0, + EXTRACT_QUOTES, &action, &path, &mode, From 206644aedeb8859801051ac170ec562c6a113a79 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 23 Jun 2015 17:00:40 +0000 Subject: [PATCH 05/15] util: Allow non-separator coalescing parsing in extract_first_word If EXTRACT_DONT_COALESCE_SEPARATORS is passed, then leading separators, trailing separators and spans of multiple separators aren't skipped, and empty arguments from before, after or between separators may be extracted. --- src/basic/util.c | 28 ++++++++++++++++++++++------ src/basic/util.h | 1 + src/test/test-util.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/basic/util.c b/src/basic/util.c index d4c385fcef..0b974b2ab5 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -5735,10 +5735,20 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra switch (state) { case START: - if (c == 0) + 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)) + } 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 */ @@ -5758,9 +5768,13 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra return -ENOMEM; state = DOUBLE_QUOTE; - } else if (strchr(separators, c)) + } else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p) ++; + goto finish_force_next; + } state = SEPARATOR; - else { + } else { if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; @@ -5857,10 +5871,11 @@ end_escape: case SEPARATOR: if (c == 0) - goto finish; + goto finish_force_terminate; + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) + goto finish_force_next; if (!strchr(separators, c)) goto finish; - break; } @@ -5876,6 +5891,7 @@ finish: return 0; } +finish_force_next: s[sz] = 0; *ret = s; s = NULL; diff --git a/src/basic/util.h b/src/basic/util.h index 82fb771e20..4d6a8abb57 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -859,6 +859,7 @@ typedef enum ExtractFlags { 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); diff --git a/src/test/test-util.c b/src/test/test-util.c index 40f5d34de3..fc7a3de106 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -1744,6 +1744,38 @@ static void test_extract_first_word(void) { assert_se(streq(t, "")); free(t); 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_extract_first_word_and_warn(void) { From 6330ee108362a419dfc8806ab6402416c793a4ca Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:31 +0000 Subject: [PATCH 06/15] nspawn: Allow : characters in --tmpfs path This now accepts : characters with the \: escape sequence. Other escape sequences are also interpreted, but having a \ in your file path is less likely than :, so this shouldn't break anyone's existing tools. --- src/nspawn/nspawn.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 0e74f20f1a..fe5f3528ce 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -690,18 +690,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(¤t, &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(); From ffcd3e89d55c870c94aa15ee94dab3e029a586cf Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:33 +0000 Subject: [PATCH 07/15] man: Document \: escapes in nspawn's --tmpfs option --- man/systemd-nspawn.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index d1e050cb89..f028959fca 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -600,7 +600,10 @@ otherwise specified). This option is particularly useful for mounting directories such as /var as tmpfs, to allow state-less systems, in particular when - combined with . + combined with . + Backslash escapes are interpreted in the path so + \: may be used to embed colons in the path. + From 8adaf7bd23baa6e2cd99e9e88e55d0f5f5db29a2 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:34 +0000 Subject: [PATCH 08/15] strv: convert strv_split_quotes into a generic strv_split_extract strv_split_extract is to strv_split_quotes as extract_first_word was to unquote_first_word. Now there's extract_first_word for extracting a single argument, extract_many_words for extracting a bounded number of arguments, and strv_split_extract for extracting an arbitrary number of arguments. --- TODO | 2 +- src/basic/env-util.c | 2 +- src/basic/strv.c | 7 ++++--- src/basic/strv.h | 2 +- src/journal-remote/journal-remote.c | 2 +- src/locale/localed.c | 6 +++--- src/test/test-strv.c | 22 +++++++++++++++++++--- 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/TODO b/TODO index bb4d878c1a..81cd353a5a 100644 --- 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= diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 9517fbc802..4804a67f91 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -550,7 +550,7 @@ char **replace_env_argv(char **argv, char **env) { if (e) { int r; - r = strv_split_quoted(&m, e, EXTRACT_RELAX); + r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); if (r < 0) { ret[k] = NULL; strv_free(ret); diff --git a/src/basic/strv.c b/src/basic/strv.c index 72964a166a..79a9d8d421 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -278,7 +278,7 @@ char **strv_split_newlines(const char *s) { return l; } -int strv_split_quoted(char ***t, const char *s, ExtractFlags 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, ExtractFlags flags) { for (;;) { _cleanup_free_ char *word = NULL; - r = extract_first_word(&s, &word, NULL, flags|EXTRACT_QUOTES); + 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; diff --git a/src/basic/strv.h b/src/basic/strv.h index fe3dfd245c..954da06fcb 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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, ExtractFlags 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); diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index e3bd76051b..cdcda1cb52 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -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"); diff --git a/src/locale/localed.c b/src/locale/localed.c index cb4052fdd5..e9489f04c2 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -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; diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 6e3c81395c..53074b4b65 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -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; @@ -583,6 +598,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(); From e4a5d9edee1d967e4c1386ce442cbe1f465766ec Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:35 +0000 Subject: [PATCH 09/15] nspawn: Allow : characters in nspawn --bind paths : characters in bind paths can be entered as the \: escape sequence. --- src/nspawn/nspawn.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index fe5f3528ce..b65b13426f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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(¤t, ":", 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) From 8ef24e7a4f4b4d464b66fa7d3f0acaa88800d6cb Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:36 +0000 Subject: [PATCH 10/15] man: Document \: escapes in nspawn's --bind option --- man/systemd-nspawn.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index f028959fca..095f511154 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -581,7 +581,9 @@ 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 + \: 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 creates read-only bind mounts. From 61ee6939819963b7845c101485e188ca2a8119c6 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 23 Jun 2015 10:32:02 +0000 Subject: [PATCH 11/15] util: Add shell_escape This is for shell-style \ escaping rather than quoting, which while it has the same effect in produced shell commands, is not exclusively useful for shell commands. shell_escape would be useful for producing sed commands, as you would be able to \ escape the normal special characters, plus whichever argument separator was chosen; or it could be used to escape arguments passed to the overlayfs mount command. --- src/basic/util.c | 34 +++++++++++++++++++++++++++------- src/basic/util.h | 1 + src/test/test-util.c | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/basic/util.c b/src/basic/util.c index 0b974b2ab5..ebfc6c6a72 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -6537,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; @@ -6563,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; diff --git a/src/basic/util.h b/src/basic/util.h index 4d6a8abb57..098f9edcc1 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -919,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); diff --git a/src/test/test-util.c b/src/test/test-util.c index fc7a3de106..3c1bac4606 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -2100,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; @@ -2264,6 +2279,7 @@ int main(int argc, char *argv[]) { test_same_fd(); test_uid_ptr(); test_sparse_write(); + test_shell_escape(); test_shell_maybe_quote(); test_parse_mode(); test_tempfn(); From 04c14b25412cdbde834e8369bd7268cbe92873c0 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 23 Jun 2015 10:57:41 +0000 Subject: [PATCH 12/15] strv: Add strv_shell_escape This modifies the strv in-place, replacing strings with their escaped version. It's mostly just a convenience function for when you need to join a strv together because it's passed as a string to something, and the separator needs escaping. --- src/basic/strv.c | 20 ++++++++++++++++++++ src/basic/strv.h | 1 + src/test/test-strv.c | 13 +++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/basic/strv.c b/src/basic/strv.c index 79a9d8d421..eaf440a4b2 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -694,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; diff --git a/src/basic/strv.h b/src/basic/strv.h index 954da06fcb..f07da8cdf3 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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); diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 53074b4b65..bff43950a9 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -557,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(); @@ -614,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; } From 872d0dbdc335053ee94ae9158fb9637c3c904ade Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:37 +0000 Subject: [PATCH 13/15] nspawn: escape paths in overlay mount options Overlayfs uses , as an option separator and : as a list separator. These characters are both valid in file paths, so overlayfs allows file paths which contain these characters to backslash escape these values. --- src/nspawn/nspawn.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b65b13426f..59e013d5c5 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1235,6 +1235,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; @@ -1251,19 +1266,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) From 62f9f39a455204fac698f702a1369bd6fdbdbf9d Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:38 +0000 Subject: [PATCH 14/15] nspawn: Allow : characters in overlay paths : characters can be entered with the \: escape sequence. --- src/nspawn/nspawn.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 59e013d5c5..347260013a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -739,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)) { From 2eadf91ca15a982adf71b86e6ee035ac368e74bc Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 19 Jun 2015 15:24:40 +0000 Subject: [PATCH 15/15] man: Document \: escapes in nspawn's --overlay option --- man/systemd-nspawn.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 095f511154..4966749259 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -617,6 +617,10 @@ list of colon-separated paths to the directory trees to combine and the destination mount point. + Backslash escapes are interpreted in the paths, so + \: may be used to embed colons in the paths. + + 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