diff options
Diffstat (limited to '')
-rw-r--r-- | src/basic/strv.c | 997 |
1 files changed, 997 insertions, 0 deletions
diff --git a/src/basic/strv.c b/src/basic/strv.c new file mode 100644 index 0000000..3616134 --- /dev/null +++ b/src/basic/strv.c @@ -0,0 +1,997 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fnmatch.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fileio.h" +#include "memory-util.h" +#include "nulstr-util.h" +#include "sort-util.h" +#include "string-util.h" +#include "strv.h" + +char* strv_find(char * const *l, const char *name) { + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char* strv_find_case(char * const *l, const char *name) { + assert(name); + + STRV_FOREACH(i, l) + if (strcaseeq(*i, name)) + return *i; + + return NULL; +} + +char* strv_find_prefix(char * const *l, const char *name) { + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +char* strv_find_startswith(char * const *l, const char *name) { + assert(name); + + /* Like strv_find_prefix, but actually returns only the + * suffix, not the whole item */ + + STRV_FOREACH(i, l) { + char *e; + + e = startswith(*i, name); + if (e) + return e; + } + + return NULL; +} + +char** strv_free(char **l) { + STRV_FOREACH(k, l) + free(*k); + + return mfree(l); +} + +char** strv_free_erase(char **l) { + STRV_FOREACH(i, l) + erase_and_freep(i); + + return mfree(l); +} + +char** strv_copy(char * const *l) { + _cleanup_strv_free_ char **result = NULL; + char **k; + + result = new(char*, strv_length(l) + 1); + if (!result) + return NULL; + + k = result; + STRV_FOREACH(i, l) { + *k = strdup(*i); + if (!*k) + return NULL; + k++; + } + + *k = NULL; + return TAKE_PTR(result); +} + +int strv_copy_unless_empty(char * const *l, char ***ret) { + assert(ret); + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + char **copy = strv_copy(l); + if (!copy) + return -ENOMEM; + + *ret = TAKE_PTR(copy); + return 1; +} + +size_t strv_length(char * const *l) { + size_t n = 0; + + STRV_FOREACH(i, l) + n++; + + return n; +} + +char** strv_new_ap(const char *x, va_list ap) { + _cleanup_strv_free_ char **a = NULL; + size_t n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * STRV_IGNORE. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + va_copy(aq, ap); + for (const char *s = x; s; s = va_arg(aq, const char*)) { + if (s == STRV_IGNORE) + continue; + + n++; + } + va_end(aq); + + a = new(char*, n+1); + if (!a) + return NULL; + + for (const char *s = x; s; s = va_arg(ap, const char*)) { + if (s == STRV_IGNORE) + continue; + + a[i] = strdup(s); + if (!a[i]) + return NULL; + + i++; + } + + a[i] = NULL; + + return TAKE_PTR(a); +} + +char** strv_new_internal(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) { + size_t p, q, i = 0; + char **t; + + assert(a); + + if (strv_isempty(b)) + return 0; + + p = strv_length(*a); + q = strv_length(b); + + if (p >= SIZE_MAX - q) + return -ENOMEM; + + t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *)); + if (!t) + return -ENOMEM; + + t[p] = NULL; + *a = t; + + STRV_FOREACH(s, b) { + if (filter_duplicates && strv_contains(t, *s)) + continue; + + t[p+i] = strdup(*s); + if (!t[p+i]) + goto rollback; + + i++; + t[p+i] = NULL; + } + + assert(i <= q); + + return (int) i; + +rollback: + for (size_t j = 0; j < i; j++) + free(t[p + j]); + + t[p] = NULL; + return -ENOMEM; +} + +int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix) { + int r; + + STRV_FOREACH(s, b) { + char *v; + + v = strjoin(*s, suffix); + if (!v) + return -ENOMEM; + + r = strv_push(a, v); + if (r < 0) { + free(v); + return r; + } + } + + return 0; +} + +int strv_split_newlines_full(char ***ret, const char *s, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + size_t n; + int r; + + assert(s); + + /* Special version of strv_split_full() that splits on newlines and + * suppresses an empty string at the end. */ + + r = strv_split_full(&l, s, NEWLINE, flags); + if (r < 0) + return r; + + n = strv_length(l); + if (n > 0 && isempty(l[n - 1])) { + l[n - 1] = mfree(l[n - 1]); + n--; + } + + *ret = TAKE_PTR(l); + return n; +} + +int strv_split_full(char ***t, const char *s, const char *separators, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + size_t n = 0; + int r; + + assert(t); + assert(s); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&s, &word, separators, flags); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(l, n + 2)) + return -ENOMEM; + + l[n++] = TAKE_PTR(word); + l[n] = NULL; + } + + if (!l) { + l = new0(char*, 1); + if (!l) + return -ENOMEM; + } + + *t = TAKE_PTR(l); + + return (int) n; +} + +int strv_split_and_extend_full(char ***t, const char *s, const char *separators, bool filter_duplicates, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(t); + assert(s); + + r = strv_split_full(&l, s, separators, flags); + if (r < 0) + return r; + + r = strv_extend_strv(t, l, filter_duplicates); + if (r < 0) + return r; + + return (int) strv_length(*t); +} + +int strv_split_colon_pairs(char ***t, const char *s) { + _cleanup_strv_free_ char **l = NULL; + size_t n = 0; + int r; + + assert(t); + assert(s); + + for (;;) { + _cleanup_free_ char *first = NULL, *second = NULL, *tuple = NULL, *second_or_empty = NULL; + + r = extract_first_word(&s, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + const char *p = tuple; + r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, + &first, &second, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + /* Enforce that at most 2 colon-separated words are contained in each group */ + if (!isempty(p)) + return -EINVAL; + + second_or_empty = strdup(strempty(second)); + if (!second_or_empty) + return -ENOMEM; + + if (!GREEDY_REALLOC(l, n + 3)) + return -ENOMEM; + + l[n++] = TAKE_PTR(first); + l[n++] = TAKE_PTR(second_or_empty); + + l[n] = NULL; + } + + if (!l) { + l = new0(char*, 1); + if (!l) + return -ENOMEM; + } + + *t = TAKE_PTR(l); + + return (int) n; +} + +char* strv_join_full(char * const *l, const char *separator, const char *prefix, bool escape_separator) { + char *r, *e; + size_t n, k, m; + + if (!separator) + separator = " "; + + k = strlen(separator); + m = strlen_ptr(prefix); + + if (escape_separator) /* If the separator was multi-char, we wouldn't know how to escape it. */ + assert(k == 1); + + n = 0; + STRV_FOREACH(s, l) { + if (s != l) + n += k; + + bool needs_escaping = escape_separator && strchr(*s, *separator); + + n += m + strlen(*s) * (1 + needs_escaping); + } + + r = new(char, n+1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (s != l) + e = stpcpy(e, separator); + + if (prefix) + e = stpcpy(e, prefix); + + bool needs_escaping = escape_separator && strchr(*s, *separator); + + if (needs_escaping) + for (size_t i = 0; (*s)[i]; i++) { + if ((*s)[i] == *separator) + *(e++) = '\\'; + *(e++) = (*s)[i]; + } + else + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +int strv_push_with_size(char ***l, size_t *n, char *value) { + /* n is a pointer to a variable to store the size of l. + * If not given (i.e. n is NULL or *n is SIZE_MAX), size will be calculated using strv_length(). + * If n is not NULL, the size after the push will be returned. + * If value is empty, no action is taken and *n is not set. */ + + if (!value) + return 0; + + size_t size = n ? *n : SIZE_MAX; + if (size == SIZE_MAX) + size = strv_length(*l); + + /* Check for overflow */ + if (size > SIZE_MAX-2) + return -ENOMEM; + + char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(size + 2), sizeof(char*)); + if (!c) + return -ENOMEM; + + c[size] = value; + c[size+1] = NULL; + + *l = c; + if (n) + *n = size + 1; + return 0; +} + +int strv_push_pair(char ***l, char *a, char *b) { + char **c; + size_t n; + + if (!a && !b) + return 0; + + n = strv_length(*l); + + /* Check for overflow */ + if (n > SIZE_MAX-3) + return -ENOMEM; + + /* increase and check for overflow */ + c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + !!a + !!b + 1), sizeof(char*)); + if (!c) + return -ENOMEM; + + if (a) + c[n++] = a; + if (b) + c[n++] = b; + c[n] = NULL; + + *l = c; + return 0; +} + +int strv_insert(char ***l, size_t position, char *value) { + char **c; + size_t n, m; + + if (!value) + return 0; + + n = strv_length(*l); + position = MIN(position, n); + + /* increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = new(char*, m); + if (!c) + return -ENOMEM; + + for (size_t i = 0; i < position; i++) + c[i] = (*l)[i]; + c[position] = value; + for (size_t i = position; i < n; i++) + c[i+1] = (*l)[i]; + c[n+1] = NULL; + + return free_and_replace(*l, c); +} + +int strv_consume_with_size(char ***l, size_t *n, char *value) { + int r; + + r = strv_push_with_size(l, n, value); + if (r < 0) + free(value); + + return r; +} + +int strv_consume_pair(char ***l, char *a, char *b) { + int r; + + r = strv_push_pair(l, a, b); + if (r < 0) { + free(a); + free(b); + } + + return r; +} + +int strv_consume_prepend(char ***l, char *value) { + int r; + + r = strv_push_prepend(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_prepend(char ***l, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume_prepend(l, v); +} + +int strv_extend_with_size(char ***l, size_t *n, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume_with_size(l, n, v); +} + +int strv_extend_front(char ***l, const char *value) { + size_t n, m; + char *v, **c; + + assert(l); + + /* Like strv_extend(), but prepends rather than appends the new entry */ + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and overflow check. */ + m = n + 2; + if (m < n) + return -ENOMEM; + + v = strdup(value); + if (!v) + return -ENOMEM; + + c = reallocarray(*l, m, sizeof(char*)); + if (!c) { + free(v); + return -ENOMEM; + } + + memmove(c+1, c, n * sizeof(char*)); + c[0] = v; + c[n+1] = NULL; + + *l = c; + return 0; +} + +char** strv_uniq(char **l) { + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +bool strv_is_uniq(char * const *l) { + STRV_FOREACH(i, l) + if (strv_contains(i+1, *i)) + return false; + + return true; +} + +char** strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) + if (streq(*f, s)) + free(*f); + else + *(t++) = *f; + + *t = NULL; + return l; +} + +char** strv_parse_nulstr(const char *s, size_t l) { + /* 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 not stored in + * the resulting strv, and length is equal to the number of NUL bytes. + * + * Note that contrary to a normal nulstr which cannot contain empty strings, because + * the input data is terminated by any two consequent NUL bytes, this parser accepts + * empty strings in s. + */ + + size_t c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return new0(char*, 1); + + for (const char *p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + v = new0(char*, c+1); + if (!v) + return NULL; + + for (const char *p = s; p < s + l; ) { + const char *e; + + e = memchr(p, 0, s + l - p); + + v[i] = strndup(p, e ? e - p : s + l - p); + if (!v[i]) { + strv_free(v); + return NULL; + } + + i++; + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +char** strv_split_nulstr(const char *s) { + const char *i; + char **r = NULL; + + NULSTR_FOREACH(i, s) + if (strv_extend(&r, i) < 0) { + strv_free(r); + return NULL; + } + + if (!r) + return strv_new(NULL); + + return r; +} + +int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { + /* A valid nulstr with two NULs at the end will be created, but + * q will be the length without the two trailing NULs. Thus the output + * string is a valid nulstr and can be iterated over using NULSTR_FOREACH, + * and can also be parsed by strv_parse_nulstr as long as the length + * is provided separately. + */ + + _cleanup_free_ char *m = NULL; + size_t n = 0; + + assert(ret); + assert(ret_size); + + STRV_FOREACH(i, l) { + size_t z; + + z = strlen(*i); + + if (!GREEDY_REALLOC(m, n + z + 2)) + return -ENOMEM; + + memcpy(m + n, *i, z + 1); + n += z + 1; + } + + if (!m) { + m = new0(char, 2); + if (!m) + return -ENOMEM; + n = 1; + } else + /* make sure there is a second extra NUL at the end of resulting nulstr */ + m[n] = '\0'; + + assert(n > 0); + *ret = TAKE_PTR(m); + *ret_size = n - 1; + + return 0; +} + +bool strv_overlap(char * const *a, char * const *b) { + STRV_FOREACH(i, a) + if (strv_contains(b, *i)) + return true; + + return false; +} + +static int str_compare(char * const *a, char * const *b) { + return strcmp(*a, *b); +} + +char** strv_sort(char **l) { + typesafe_qsort(l, strv_length(l), str_compare); + return l; +} + +int strv_compare(char * const *a, char * const *b) { + int r; + + if (strv_isempty(a)) { + if (strv_isempty(b)) + return 0; + else + return -1; + } + + if (strv_isempty(b)) + return 1; + + for ( ; *a || *b; ++a, ++b) { + r = strcmp_ptr(*a, *b); + if (r != 0) + return r; + } + + return 0; +} + +void strv_print(char * const *l) { + STRV_FOREACH(s, l) + puts(*s); +} + +int strv_extendf(char ***l, const char *format, ...) { + va_list ap; + char *x; + int r; + + va_start(ap, format); + r = vasprintf(&x, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +char** strv_reverse(char **l) { + size_t n; + + n = strv_length(l); + if (n <= 1) + return l; + + for (size_t i = 0; i < n / 2; i++) + SWAP_TWO(l[i], l[n-1-i]); + + return l; +} + +char** strv_shell_escape(char **l, const char *bad) { + /* 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_full( + char* const* patterns, + const char *s, + int flags, + size_t *ret_matched_pos) { + + assert(s); + + if (patterns) + for (size_t i = 0; patterns[i]; i++) + /* NB: We treat all fnmatch() errors as equivalent to FNM_NOMATCH, i.e. if fnmatch() fails to + * process the pattern for some reason we'll consider this equivalent to non-matching. */ + if (fnmatch(patterns[i], s, flags) == 0) { + if (ret_matched_pos) + *ret_matched_pos = i; + return true; + } + + if (ret_matched_pos) + *ret_matched_pos = SIZE_MAX; + + return false; +} + +char** strv_skip(char **l, size_t n) { + + while (n > 0) { + if (strv_isempty(l)) + return l; + + l++, n--; + } + + return l; +} + +int strv_extend_n(char ***l, const char *value, size_t n) { + size_t i, k; + char **nl; + + assert(l); + + if (!value) + return 0; + if (n == 0) + return 0; + + /* Adds the value n times to l */ + + k = strv_length(*l); + if (n >= SIZE_MAX - k) + return -ENOMEM; + + nl = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(k + n + 1), sizeof(char *)); + if (!nl) + return -ENOMEM; + + *l = nl; + + for (i = k; i < k + n; i++) { + nl[i] = strdup(value); + if (!nl[i]) + goto rollback; + } + nl[i] = NULL; + + return 0; + +rollback: + for (size_t j = k; j < i; j++) + free(nl[j]); + nl[k] = NULL; + + return -ENOMEM; +} + +int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { + bool b = false; + int r; + + /* Like fputs(), but for strv, and with a less stupid argument order */ + + if (!space) + space = &b; + + STRV_FOREACH(s, l) { + r = fputs_with_space(f, *s, separator, space); + if (r < 0) + return r; + } + + return 0; +} + +static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { + char **l; + int r; + + l = hashmap_get(h, key); + if (l) { + /* A list for this key already exists, let's append to it if it is not listed yet */ + if (strv_contains(l, value)) + return 0; + + r = strv_extend(&l, value); + if (r < 0) + return r; + + assert_se(hashmap_update(h, key, l) >= 0); + } else { + /* No list for this key exists yet, create one */ + _cleanup_strv_free_ char **l2 = NULL; + _cleanup_free_ char *t = NULL; + + t = strdup(key); + if (!t) + return -ENOMEM; + + r = strv_extend(&l2, value); + if (r < 0) + return r; + + r = hashmap_put(h, t, l2); + if (r < 0) + return r; + TAKE_PTR(t); + TAKE_PTR(l2); + } + + return 1; +} + +int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) { + int r; + + r = _hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + if (r < 0) + return r; + + return string_strv_hashmap_put_internal(*h, key, value); +} + +int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) { + int r; + + r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + if (r < 0) + return r; + + return string_strv_hashmap_put_internal(PLAIN_HASHMAP(*h), key, value); +} + +DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free); |