From a4f66f90ad7ead6aeea24a596425d8844902712d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Mar 2023 12:39:36 +0900 Subject: [PATCH 1/3] nulstr-util: introduce strv_parse_nulstr_full() that optionally drop trailing empty strings --- src/basic/nulstr-util.c | 6 +++++- src/basic/nulstr-util.h | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/basic/nulstr-util.c b/src/basic/nulstr-util.c index 98d68e0b01..2acc61886b 100644 --- a/src/basic/nulstr-util.c +++ b/src/basic/nulstr-util.c @@ -4,7 +4,7 @@ #include "string-util.h" #include "strv.h" -char** strv_parse_nulstr(const char *s, size_t l) { +char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) { /* l is the length of the input data, which will be split at NULs into elements of the resulting * strv. Hence, the number of items in the resulting strv will be equal to one plus the number of NUL * bytes in the l bytes starting at s, unless s[l-1] is NUL, in which case the final empty string is @@ -18,6 +18,10 @@ char** strv_parse_nulstr(const char *s, size_t l) { assert(s || l <= 0); + if (drop_trailing_nuls) + while (l > 0 && s[l-1] == '\0') + l--; + if (l <= 0) return new0(char*, 1); diff --git a/src/basic/nulstr-util.h b/src/basic/nulstr-util.h index fd0ed44528..d7bc5fd1ce 100644 --- a/src/basic/nulstr-util.h +++ b/src/basic/nulstr-util.h @@ -20,7 +20,10 @@ static inline bool nulstr_contains(const char *nulstr, const char *needle) { return nulstr_get(nulstr, needle); } -char** strv_parse_nulstr(const char *s, size_t l); +char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls); +static inline char** strv_parse_nulstr(const char *s, size_t l) { + return strv_parse_nulstr_full(s, l, false); +} char** strv_split_nulstr(const char *s); int strv_make_nulstr(char * const *l, char **p, size_t *n); int set_make_nulstr(Set *s, char **ret, size_t *ret_size); From 55479c208d753929445da6121cfa9beab3ae4b15 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Mar 2023 12:40:03 +0900 Subject: [PATCH 2/3] test: add tests from strv_parse_nulstr_full() --- src/test/test-nulstr-util.c | 111 +++++++++++++++--------------------- 1 file changed, 45 insertions(+), 66 deletions(-) diff --git a/src/test/test-nulstr-util.c b/src/test/test-nulstr-util.c index 70f1e8705d..95c25f1540 100644 --- a/src/test/test-nulstr-util.c +++ b/src/test/test-nulstr-util.c @@ -19,87 +19,66 @@ TEST(strv_split_nulstr) { assert_se(streq(l[3], "str3")); } -TEST(strv_parse_nulstr) { - _cleanup_strv_free_ char **l = NULL; - const char nulstr[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx"; +#define strv_parse_nulstr_full_one(s, n, e0, e1) \ + ({ \ + _cleanup_strv_free_ char **v0 = NULL, **v1 = NULL; \ + \ + assert_se(v0 = strv_parse_nulstr_full(s, n, false)); \ + assert_se(strv_equal(v0, e0)); \ + assert_se(v1 = strv_parse_nulstr_full(s, n, true)); \ + assert_se(strv_equal(v1, e1)); \ + }) - l = strv_parse_nulstr(nulstr, sizeof(nulstr)-1); - assert_se(l); - puts("Parse nulstr:"); - strv_print(l); +TEST(strv_parse_nulstr_full) { + const char nulstr1[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx"; + const char nulstr2[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx\0\0\0"; - assert_se(streq(l[0], "hoge")); - assert_se(streq(l[1], "hoge2")); - assert_se(streq(l[2], "hoge3")); - assert_se(streq(l[3], "")); - assert_se(streq(l[4], "hoge5")); - assert_se(streq(l[5], "")); - assert_se(streq(l[6], "xxx")); - strv_free(l); + strv_parse_nulstr_full_one(nulstr1, sizeof(nulstr1) - 1, + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"), + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx")); - l = strv_parse_nulstr((const char[0]) {}, 0); - assert_se(l); - assert_se(strv_isempty(l)); - strv_free(l); + strv_parse_nulstr_full_one(nulstr2, sizeof(nulstr2) - 1, + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx", "", ""), + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx")); - l = strv_parse_nulstr((const char[1]) { 0 }, 1); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE(""))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[0]) {}), 0, + STRV_MAKE_EMPTY, STRV_MAKE_EMPTY); - l = strv_parse_nulstr((const char[1]) { 'x' }, 1); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("x"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[1]) { 0 }), 1, + STRV_MAKE(""), STRV_MAKE_EMPTY); - l = strv_parse_nulstr((const char[2]) { 0, 0 }, 2); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("", ""))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[1]) { 'x' }), 1, + STRV_MAKE("x"), STRV_MAKE("x")); - l = strv_parse_nulstr((const char[2]) { 'x', 0 }, 2); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("x"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[2]) { 0, 0 }), 2, + STRV_MAKE("", ""), STRV_MAKE_EMPTY); - l = strv_parse_nulstr((const char[3]) { 0, 0, 0 }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("", "", ""))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[2]) { 'x', 0 }), 2, + STRV_MAKE("x"), STRV_MAKE("x")); - l = strv_parse_nulstr((const char[3]) { 'x', 0, 0 }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("x", ""))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 0 }), 3, + STRV_MAKE("", "", ""), STRV_MAKE_EMPTY); - l = strv_parse_nulstr((const char[3]) { 0, 'x', 0 }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("", "x"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 0 }), 3, + STRV_MAKE("x", ""), STRV_MAKE("x")); - l = strv_parse_nulstr((const char[3]) { 0, 0, 'x' }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("", "", "x"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 0 }), 3, + STRV_MAKE("", "x"), STRV_MAKE("", "x")); - l = strv_parse_nulstr((const char[3]) { 'x', 'x', 0 }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("xx"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 'x' }), 3, + STRV_MAKE("", "", "x"), STRV_MAKE("", "", "x")); - l = strv_parse_nulstr((const char[3]) { 0, 'x', 'x' }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("", "xx"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 0 }), 3, + STRV_MAKE("xx"), STRV_MAKE("xx")); - l = strv_parse_nulstr((const char[3]) { 'x', 0, 'x' }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("x", "x"))); - strv_free(l); + strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 'x' }), 3, + STRV_MAKE("", "xx"), STRV_MAKE("", "xx")); - l = strv_parse_nulstr((const char[3]) { 'x', 'x', 'x' }, 3); - assert_se(l); - assert_se(strv_equal(l, STRV_MAKE("xxx"))); + strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 'x' }), 3, + STRV_MAKE("x", "x"), STRV_MAKE("x", "x")); + + strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 'x' }), 3, + STRV_MAKE("xxx"), STRV_MAKE("xxx")); } static void test_strv_make_nulstr_one(char **l) { From 4669be626d3e0098323741e70cc3a3cdf5ff22ed Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Mar 2023 11:59:44 +0900 Subject: [PATCH 3/3] process-util: drop trailing NUls before parsing the nulstr No functional changes, just refactoring. --- src/basic/process-util.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 2fc2f92450..cf2dca1dc4 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -222,18 +222,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags _cleanup_strv_free_ char **args = NULL; - args = strv_parse_nulstr(t, k); + /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end. + * See also issue #21186. */ + args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true); if (!args) return -ENOMEM; - /* Drop trailing empty strings. See issue #21186. */ - STRV_FOREACH_BACKWARDS(p, args) { - if (!isempty(*p)) - break; - - *p = mfree(*p); - } - ans = quote_command_line(args, shflags); if (!ans) return -ENOMEM;