From 34467ffa3c1861021293fcfadb6ebc24775b6fda Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Jan 2025 09:31:35 +0100 Subject: [PATCH 1/2] string-util: make strjoin() just a special case of strextend() The functions are very similar, let's make them the same. If the first argument to strextend() is NULL instead of extending a string we'll allocate a fresh one and return that. --- src/basic/string-util.c | 45 +++++++++-------------------------------- src/basic/string-util.h | 3 +-- 2 files changed, 10 insertions(+), 38 deletions(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 5621ffc768..5e4f57a5b1 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -77,38 +77,6 @@ char* strnappend(const char *s, const char *suffix, size_t b) { return r; } -char* strjoin_real(const char *x, ...) { - va_list ap; - size_t l = 1; - char *r, *p; - - va_start(ap, x); - for (const char *t = x; t; t = va_arg(ap, const char *)) { - size_t n; - - n = strlen(t); - if (n > SIZE_MAX - l) { - va_end(ap); - return NULL; - } - l += n; - } - va_end(ap); - - p = r = new(char, l); - if (!r) - return NULL; - - va_start(ap, x); - for (const char *t = x; t; t = va_arg(ap, const char *)) - p = stpcpy(p, t); - va_end(ap); - - *p = 0; - - return r; -} - char* strstrip(char *s) { if (!s) return NULL; @@ -843,12 +811,14 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { } char* strextend_with_separator_internal(char **x, const char *separator, ...) { + _cleanup_free_ char *buffer = NULL; size_t f, l, l_separator; bool need_separator; char *nr, *p; va_list ap; - assert(x); + if (!x) + x = &buffer; l = f = strlen_ptr(*x); @@ -856,8 +826,7 @@ char* strextend_with_separator_internal(char **x, const char *separator, ...) { l_separator = strlen_ptr(separator); va_start(ap, separator); - for (;;) { - const char *t; + for (const char *t;;) { size_t n; t = va_arg(ap, const char *); @@ -906,9 +875,13 @@ char* strextend_with_separator_internal(char **x, const char *separator, ...) { va_end(ap); assert(p == nr + l); - *p = 0; + /* If no buffer to extend was passed in return the start of the buffer */ + if (buffer) + return TAKE_PTR(buffer); + + /* Otherwise we extended the buffer: return the end */ return p; } diff --git a/src/basic/string-util.h b/src/basic/string-util.h index a1592b6e6d..1bcb1c40e3 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -108,8 +108,7 @@ char* first_word(const char *s, const char *word) _pure_; char* strnappend(const char *s, const char *suffix, size_t length); -char* strjoin_real(const char *x, ...) _sentinel_; -#define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL) +#define strjoin(a, ...) strextend_with_separator_internal(NULL, NULL, a, __VA_ARGS__, NULL) #define strjoina(a, ...) \ ({ \ From fd3b7cf77239be58680dfd5a98c73d3d6355b303 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Jan 2025 09:32:44 +0100 Subject: [PATCH 2/2] string-util: add a mechanism for strextend_with_separator() for specifying "ignore" arguments in strv_new() we have STRV_IGNORE for skipping over an argument in the argument list. Let's add the same to strextend_with_separator(): strextend_with_separator(&x, "foo", POINTER_MAX, "bar"); will result in "foobar" being appended to "x". (POINTER_MAX Which is different from NULL, which terminates the argument list). This is useful for ternary op situations. (We should probably get rid of STRV_IGNORE and just use POINTER_MAX everywhere directly, but that's for another time.) --- src/basic/string-util.c | 4 ++++ src/test/test-string-util.c | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 5e4f57a5b1..b2919164d6 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -832,6 +832,8 @@ char* strextend_with_separator_internal(char **x, const char *separator, ...) { t = va_arg(ap, const char *); if (!t) break; + if (t == POINTER_MAX) + continue; n = strlen(t); @@ -864,6 +866,8 @@ char* strextend_with_separator_internal(char **x, const char *separator, ...) { t = va_arg(ap, const char *); if (!t) break; + if (t == POINTER_MAX) + continue; if (need_separator && separator) p = stpcpy(p, separator); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index b692af6cc0..2b44905346 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -262,6 +262,12 @@ TEST(strextend) { ASSERT_STREQ(str, "0123"); assert_se(strextend(&str, "456", "78", "9")); ASSERT_STREQ(str, "0123456789"); + + assert_se(strextend(&str, "more", NULL, "huch")); + ASSERT_STREQ(str, "0123456789more"); + + assert_se(strextend(&str, "MORE", POINTER_MAX, "HUCH")); + ASSERT_STREQ(str, "0123456789moreMOREHUCH"); } TEST(strextend_with_separator) { @@ -285,6 +291,9 @@ TEST(strextend_with_separator) { ASSERT_STREQ(str, "start,,1,234"); assert_se(strextend_with_separator(&str, ";", "more", "5", "678")); ASSERT_STREQ(str, "start,,1,234;more;5;678"); + + assert_se(strextend_with_separator(&str, ";", "xxxx", POINTER_MAX, "yyy")); + ASSERT_STREQ(str, "start,,1,234;more;5;678;xxxx;yyy"); } TEST(strrep) { @@ -400,6 +409,10 @@ TEST(strjoin) { actual = strjoin("foo", NULL, "bar"); ASSERT_STREQ(actual, "foo"); free(actual); + + actual = strjoin("foo", POINTER_MAX, "bar"); + ASSERT_STREQ(actual, "foobar"); + free(actual); } TEST(strcmp_ptr) {