]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/strv.c
Use consistent spelling of systemd.condition_first_boot argument
[thirdparty/systemd.git] / src / basic / strv.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fnmatch.h>
5 #include <stdarg.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8
9 #include "alloc-util.h"
10 #include "env-util.h"
11 #include "escape.h"
12 #include "extract-word.h"
13 #include "fileio.h"
14 #include "gunicode.h"
15 #include "memory-util.h"
16 #include "nulstr-util.h"
17 #include "sort-util.h"
18 #include "string-util.h"
19 #include "strv.h"
20 #include "utf8.h"
21
22 char* strv_find(char * const *l, const char *name) {
23 assert(name);
24
25 STRV_FOREACH(i, l)
26 if (streq(*i, name))
27 return *i;
28
29 return NULL;
30 }
31
32 char* strv_find_case(char * const *l, const char *name) {
33 assert(name);
34
35 STRV_FOREACH(i, l)
36 if (strcaseeq(*i, name))
37 return *i;
38
39 return NULL;
40 }
41
42 char* strv_find_prefix(char * const *l, const char *name) {
43 assert(name);
44
45 STRV_FOREACH(i, l)
46 if (startswith(*i, name))
47 return *i;
48
49 return NULL;
50 }
51
52 char* strv_find_startswith(char * const *l, const char *name) {
53 assert(name);
54
55 /* Like strv_find_prefix, but actually returns only the
56 * suffix, not the whole item */
57
58 STRV_FOREACH(i, l) {
59 char *e;
60
61 e = startswith(*i, name);
62 if (e)
63 return e;
64 }
65
66 return NULL;
67 }
68
69 char* strv_find_first_field(char * const *needles, char * const *haystack) {
70 STRV_FOREACH(k, needles) {
71 char *value = strv_env_pairs_get((char **)haystack, *k);
72 if (value)
73 return value;
74 }
75
76 return NULL;
77 }
78
79 char** strv_free(char **l) {
80 STRV_FOREACH(k, l)
81 free(*k);
82
83 return mfree(l);
84 }
85
86 char** strv_free_erase(char **l) {
87 STRV_FOREACH(i, l)
88 erase_and_freep(i);
89
90 return mfree(l);
91 }
92
93 void strv_free_many(char ***strvs, size_t n) {
94 assert(strvs || n == 0);
95
96 FOREACH_ARRAY (i, strvs, n)
97 strv_free(*i);
98
99 free(strvs);
100 }
101
102 char** strv_copy_n(char * const *l, size_t m) {
103 _cleanup_strv_free_ char **result = NULL;
104 char **k;
105
106 result = new(char*, MIN(strv_length(l), m) + 1);
107 if (!result)
108 return NULL;
109
110 k = result;
111 STRV_FOREACH(i, l) {
112 if (m == 0)
113 break;
114
115 *k = strdup(*i);
116 if (!*k)
117 return NULL;
118 k++;
119
120 if (m != SIZE_MAX)
121 m--;
122 }
123
124 *k = NULL;
125 return TAKE_PTR(result);
126 }
127
128 int strv_copy_unless_empty(char * const *l, char ***ret) {
129 assert(ret);
130
131 if (strv_isempty(l)) {
132 *ret = NULL;
133 return 0;
134 }
135
136 char **copy = strv_copy(l);
137 if (!copy)
138 return -ENOMEM;
139
140 *ret = TAKE_PTR(copy);
141 return 1;
142 }
143
144 size_t strv_length(char * const *l) {
145 size_t n = 0;
146
147 STRV_FOREACH(i, l)
148 n++;
149
150 return n;
151 }
152
153 char** strv_new_ap(const char *x, va_list ap) {
154 _cleanup_strv_free_ char **a = NULL;
155 size_t n = 0, i = 0;
156 va_list aq;
157
158 /* As a special trick we ignore all listed strings that equal
159 * STRV_IGNORE. This is supposed to be used with the
160 * STRV_IFNOTNULL() macro to include possibly NULL strings in
161 * the string list. */
162
163 va_copy(aq, ap);
164 for (const char *s = x; s; s = va_arg(aq, const char*)) {
165 if (s == STRV_IGNORE)
166 continue;
167
168 n++;
169 }
170 va_end(aq);
171
172 a = new(char*, n+1);
173 if (!a)
174 return NULL;
175
176 for (const char *s = x; s; s = va_arg(ap, const char*)) {
177 if (s == STRV_IGNORE)
178 continue;
179
180 a[i] = strdup(s);
181 if (!a[i])
182 return NULL;
183
184 i++;
185 }
186
187 a[i] = NULL;
188
189 return TAKE_PTR(a);
190 }
191
192 char** strv_new_internal(const char *x, ...) {
193 char **r;
194 va_list ap;
195
196 va_start(ap, x);
197 r = strv_new_ap(x, ap);
198 va_end(ap);
199
200 return r;
201 }
202
203 int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) {
204 size_t p, q, i = 0;
205 char **t;
206
207 assert(a);
208
209 if (strv_isempty(b))
210 return 0;
211
212 p = strv_length(*a);
213 q = strv_length(b);
214
215 if (p >= SIZE_MAX - q)
216 return -ENOMEM;
217
218 t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *));
219 if (!t)
220 return -ENOMEM;
221
222 t[p] = NULL;
223 *a = t;
224
225 STRV_FOREACH(s, b) {
226 if (filter_duplicates && strv_contains(t, *s))
227 continue;
228
229 t[p+i] = strdup(*s);
230 if (!t[p+i])
231 goto rollback;
232
233 i++;
234 t[p+i] = NULL;
235 }
236
237 assert(i <= q);
238
239 return (int) i;
240
241 rollback:
242 free_many_charp(t + p, i);
243 t[p] = NULL;
244 return -ENOMEM;
245 }
246
247 int strv_extend_strv_biconcat(char ***a, const char *prefix, const char* const *b, const char *suffix) {
248 int r;
249
250 STRV_FOREACH(s, b) {
251 char *v;
252
253 v = strjoin(strempty(prefix), *s, suffix);
254 if (!v)
255 return -ENOMEM;
256
257 r = strv_consume(a, v);
258 if (r < 0)
259 return r;
260 }
261
262 return 0;
263 }
264
265 int strv_split_newlines_full(char ***ret, const char *s, ExtractFlags flags) {
266 _cleanup_strv_free_ char **l = NULL;
267 size_t n;
268 int r;
269
270 assert(s);
271
272 /* Special version of strv_split_full() that splits on newlines and
273 * suppresses an empty string at the end. */
274
275 r = strv_split_full(&l, s, NEWLINE, flags);
276 if (r < 0)
277 return r;
278
279 n = strv_length(l);
280 if (n > 0 && isempty(l[n - 1])) {
281 l[n - 1] = mfree(l[n - 1]);
282 n--;
283 }
284
285 *ret = TAKE_PTR(l);
286 return n;
287 }
288
289 int strv_split_full(char ***t, const char *s, const char *separators, ExtractFlags flags) {
290 _cleanup_strv_free_ char **l = NULL;
291 size_t n = 0;
292 int r;
293
294 assert(t);
295 assert(s);
296
297 for (;;) {
298 _cleanup_free_ char *word = NULL;
299
300 r = extract_first_word(&s, &word, separators, flags);
301 if (r < 0)
302 return r;
303 if (r == 0)
304 break;
305
306 if (!GREEDY_REALLOC(l, n + 2))
307 return -ENOMEM;
308
309 l[n++] = TAKE_PTR(word);
310 l[n] = NULL;
311 }
312
313 if (!l) {
314 l = new0(char*, 1);
315 if (!l)
316 return -ENOMEM;
317 }
318
319 *t = TAKE_PTR(l);
320
321 return (int) n;
322 }
323
324 int strv_split_and_extend_full(char ***t, const char *s, const char *separators, bool filter_duplicates, ExtractFlags flags) {
325 _cleanup_strv_free_ char **l = NULL;
326 int r;
327
328 assert(t);
329 assert(s);
330
331 r = strv_split_full(&l, s, separators, flags);
332 if (r < 0)
333 return r;
334
335 r = strv_extend_strv(t, l, filter_duplicates);
336 if (r < 0)
337 return r;
338
339 return (int) strv_length(*t);
340 }
341
342 int strv_split_colon_pairs(char ***t, const char *s) {
343 _cleanup_strv_free_ char **l = NULL;
344 size_t n = 0;
345 int r;
346
347 assert(t);
348 assert(s);
349
350 for (;;) {
351 _cleanup_free_ char *first = NULL, *second = NULL, *tuple = NULL, *second_or_empty = NULL;
352
353 r = extract_first_word(&s, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
354 if (r < 0)
355 return r;
356 if (r == 0)
357 break;
358
359 const char *p = tuple;
360 r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS,
361 &first, &second);
362 if (r < 0)
363 return r;
364 if (r == 0)
365 continue;
366 /* Enforce that at most 2 colon-separated words are contained in each group */
367 if (!isempty(p))
368 return -EINVAL;
369
370 second_or_empty = strdup(strempty(second));
371 if (!second_or_empty)
372 return -ENOMEM;
373
374 if (!GREEDY_REALLOC(l, n + 3))
375 return -ENOMEM;
376
377 l[n++] = TAKE_PTR(first);
378 l[n++] = TAKE_PTR(second_or_empty);
379
380 l[n] = NULL;
381 }
382
383 if (!l) {
384 l = new0(char*, 1);
385 if (!l)
386 return -ENOMEM;
387 }
388
389 *t = TAKE_PTR(l);
390
391 return (int) n;
392 }
393
394 char* strv_join_full(char * const *l, const char *separator, const char *prefix, bool escape_separator) {
395 char *r, *e;
396 size_t n, k, m;
397
398 if (!separator)
399 separator = " ";
400
401 k = strlen(separator);
402 m = strlen_ptr(prefix);
403
404 if (escape_separator) /* If the separator was multi-char, we wouldn't know how to escape it. */
405 assert(k == 1);
406
407 n = 0;
408 STRV_FOREACH(s, l) {
409 if (s != l)
410 n += k;
411
412 bool needs_escaping = escape_separator && strchr(*s, *separator);
413
414 n += m + strlen(*s) * (1 + needs_escaping);
415 }
416
417 r = new(char, n+1);
418 if (!r)
419 return NULL;
420
421 e = r;
422 STRV_FOREACH(s, l) {
423 if (s != l)
424 e = stpcpy(e, separator);
425
426 if (prefix)
427 e = stpcpy(e, prefix);
428
429 bool needs_escaping = escape_separator && strchr(*s, *separator);
430
431 if (needs_escaping)
432 for (size_t i = 0; (*s)[i]; i++) {
433 if ((*s)[i] == *separator)
434 *(e++) = '\\';
435 *(e++) = (*s)[i];
436 }
437 else
438 e = stpcpy(e, *s);
439 }
440
441 *e = 0;
442
443 return r;
444 }
445
446 int strv_push_with_size(char ***l, size_t *n, char *value) {
447 /* n is a pointer to a variable to store the size of l.
448 * If not given (i.e. n is NULL or *n is SIZE_MAX), size will be calculated using strv_length().
449 * If n is not NULL, the size after the push will be returned.
450 * If value is empty, no action is taken and *n is not set. */
451
452 if (!value)
453 return 0;
454
455 size_t size = n ? *n : SIZE_MAX;
456 if (size == SIZE_MAX)
457 size = strv_length(*l);
458
459 /* Check for overflow */
460 if (size > SIZE_MAX-2)
461 return -ENOMEM;
462
463 char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(size + 2), sizeof(char*));
464 if (!c)
465 return -ENOMEM;
466
467 c[size] = value;
468 c[size+1] = NULL;
469
470 *l = c;
471 if (n)
472 *n = size + 1;
473 return 0;
474 }
475
476 int strv_push_pair(char ***l, char *a, char *b) {
477 char **c;
478 size_t n;
479
480 if (!a && !b)
481 return 0;
482
483 n = strv_length(*l);
484
485 /* Check for overflow */
486 if (n > SIZE_MAX-3)
487 return -ENOMEM;
488
489 /* increase and check for overflow */
490 c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + !!a + !!b + 1), sizeof(char*));
491 if (!c)
492 return -ENOMEM;
493
494 if (a)
495 c[n++] = a;
496 if (b)
497 c[n++] = b;
498 c[n] = NULL;
499
500 *l = c;
501 return 0;
502 }
503
504 int strv_insert(char ***l, size_t position, char *value) {
505 char **c;
506 size_t n, m;
507
508 assert(l);
509
510 if (!value)
511 return 0;
512
513 n = strv_length(*l);
514 position = MIN(position, n);
515
516 /* check for overflow and increase*/
517 if (n > SIZE_MAX - 2)
518 return -ENOMEM;
519 m = n + 2;
520
521 c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*));
522 if (!c)
523 return -ENOMEM;
524
525 if (n > position)
526 memmove(c + position + 1, c + position, (n - position) * sizeof(char*));
527
528 c[position] = value;
529 c[n + 1] = NULL;
530
531 *l = c;
532 return 0;
533 }
534
535 int strv_consume_with_size(char ***l, size_t *n, char *value) {
536 int r;
537
538 r = strv_push_with_size(l, n, value);
539 if (r < 0)
540 free(value);
541
542 return r;
543 }
544
545 int strv_consume_pair(char ***l, char *a, char *b) {
546 int r;
547
548 r = strv_push_pair(l, a, b);
549 if (r < 0) {
550 free(a);
551 free(b);
552 }
553
554 return r;
555 }
556
557 int strv_consume_prepend(char ***l, char *value) {
558 int r;
559
560 r = strv_push_prepend(l, value);
561 if (r < 0)
562 free(value);
563
564 return r;
565 }
566
567 int strv_prepend(char ***l, const char *value) {
568 char *v;
569
570 if (!value)
571 return 0;
572
573 v = strdup(value);
574 if (!v)
575 return -ENOMEM;
576
577 return strv_consume_prepend(l, v);
578 }
579
580 int strv_extend_with_size(char ***l, size_t *n, const char *value) {
581 char *v;
582
583 if (!value)
584 return 0;
585
586 v = strdup(value);
587 if (!v)
588 return -ENOMEM;
589
590 return strv_consume_with_size(l, n, v);
591 }
592
593 int strv_extend_many_internal(char ***l, const char *value, ...) {
594 va_list ap;
595 size_t n, m;
596 int r;
597
598 assert(l);
599
600 m = n = strv_length(*l);
601
602 r = 0;
603 va_start(ap, value);
604 for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
605 if (!s)
606 continue;
607
608 if (m > SIZE_MAX-1) { /* overflow */
609 r = -ENOMEM;
610 break;
611 }
612 m++;
613 }
614 va_end(ap);
615
616 if (r < 0)
617 return r;
618 if (m > SIZE_MAX-1)
619 return -ENOMEM;
620
621 char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*));
622 if (!c)
623 return -ENOMEM;
624 *l = c;
625
626 r = 0;
627 size_t i = n;
628 va_start(ap, value);
629 for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
630 if (!s)
631 continue;
632
633 c[i] = strdup(s);
634 if (!c[i]) {
635 r = -ENOMEM;
636 break;
637 }
638 i++;
639 }
640 va_end(ap);
641
642 if (r < 0) {
643 /* rollback on error */
644 for (size_t j = n; j < i; j++)
645 c[j] = mfree(c[j]);
646 return r;
647 }
648
649 c[i] = NULL;
650 return 0;
651 }
652
653 char** strv_uniq(char **l) {
654 /* Drops duplicate entries. The first identical string will be
655 * kept, the others dropped */
656
657 STRV_FOREACH(i, l)
658 strv_remove(i+1, *i);
659
660 return l;
661 }
662
663 bool strv_is_uniq(char * const *l) {
664 STRV_FOREACH(i, l)
665 if (strv_contains(i+1, *i))
666 return false;
667
668 return true;
669 }
670
671 char** strv_remove(char **l, const char *s) {
672 char **f, **t;
673
674 if (!l)
675 return NULL;
676
677 assert(s);
678
679 /* Drops every occurrence of s in the string list, edits
680 * in-place. */
681
682 for (f = t = l; *f; f++)
683 if (streq(*f, s))
684 free(*f);
685 else
686 *(t++) = *f;
687
688 *t = NULL;
689 return l;
690 }
691
692 bool strv_overlap(char * const *a, char * const *b) {
693 STRV_FOREACH(i, a)
694 if (strv_contains(b, *i))
695 return true;
696
697 return false;
698 }
699
700 static int str_compare(char * const *a, char * const *b) {
701 return strcmp(*a, *b);
702 }
703
704 char** strv_sort(char **l) {
705 typesafe_qsort(l, strv_length(l), str_compare);
706 return l;
707 }
708
709 char** strv_sort_uniq(char **l) {
710 if (strv_isempty(l))
711 return l;
712
713 char **tail = strv_sort(l), *prev = NULL;
714 STRV_FOREACH(i, l)
715 if (streq_ptr(*i, prev))
716 free(*i);
717 else
718 *(tail++) = prev = *i;
719
720 *tail = NULL;
721 return l;
722 }
723
724 int strv_compare(char * const *a, char * const *b) {
725 int r;
726
727 if (strv_isempty(a)) {
728 if (strv_isempty(b))
729 return 0;
730 else
731 return -1;
732 }
733
734 if (strv_isempty(b))
735 return 1;
736
737 for ( ; *a || *b; ++a, ++b) {
738 r = strcmp_ptr(*a, *b);
739 if (r != 0)
740 return r;
741 }
742
743 return 0;
744 }
745
746 void strv_print_full(char * const *l, const char *prefix) {
747 STRV_FOREACH(s, l)
748 printf("%s%s\n", strempty(prefix), *s);
749 }
750
751 int strv_extendf(char ***l, const char *format, ...) {
752 va_list ap;
753 char *x;
754 int r;
755
756 va_start(ap, format);
757 r = vasprintf(&x, format, ap);
758 va_end(ap);
759
760 if (r < 0)
761 return -ENOMEM;
762
763 return strv_consume(l, x);
764 }
765
766 char* startswith_strv(const char *s, char * const *l) {
767 STRV_FOREACH(i, l) {
768 char *found = startswith(s, *i);
769 if (found)
770 return found;
771 }
772
773 return NULL;
774 }
775
776 char* endswith_strv(const char *s, char * const *l) {
777 STRV_FOREACH(i, l) {
778 char *found = endswith(s, *i);
779 if (found)
780 return found;
781 }
782
783 return NULL;
784 }
785
786 char** strv_reverse(char **l) {
787 size_t n;
788
789 n = strv_length(l);
790 if (n <= 1)
791 return l;
792
793 for (size_t i = 0; i < n / 2; i++)
794 SWAP_TWO(l[i], l[n-1-i]);
795
796 return l;
797 }
798
799 char** strv_shell_escape(char **l, const char *bad) {
800 /* Escapes every character in every string in l that is in bad,
801 * edits in-place, does not roll-back on error. */
802
803 STRV_FOREACH(s, l) {
804 char *v;
805
806 v = shell_escape(*s, bad);
807 if (!v)
808 return NULL;
809
810 free_and_replace(*s, v);
811 }
812
813 return l;
814 }
815
816 bool strv_fnmatch_full(
817 char* const* patterns,
818 const char *s,
819 int flags,
820 size_t *ret_matched_pos) {
821
822 assert(s);
823
824 if (patterns)
825 for (size_t i = 0; patterns[i]; i++)
826 /* NB: We treat all fnmatch() errors as equivalent to FNM_NOMATCH, i.e. if fnmatch() fails to
827 * process the pattern for some reason we'll consider this equivalent to non-matching. */
828 if (fnmatch(patterns[i], s, flags) == 0) {
829 if (ret_matched_pos)
830 *ret_matched_pos = i;
831 return true;
832 }
833
834 if (ret_matched_pos)
835 *ret_matched_pos = SIZE_MAX;
836
837 return false;
838 }
839
840 char** strv_skip(char **l, size_t n) {
841
842 while (n > 0) {
843 if (strv_isempty(l))
844 return l;
845
846 l++, n--;
847 }
848
849 return l;
850 }
851
852 int strv_extend_n(char ***l, const char *value, size_t n) {
853 size_t i, k;
854 char **nl;
855
856 assert(l);
857
858 if (!value)
859 return 0;
860 if (n == 0)
861 return 0;
862
863 /* Adds the value n times to l */
864
865 k = strv_length(*l);
866 if (n >= SIZE_MAX - k)
867 return -ENOMEM;
868
869 nl = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(k + n + 1), sizeof(char *));
870 if (!nl)
871 return -ENOMEM;
872
873 *l = nl;
874
875 for (i = k; i < k + n; i++) {
876 nl[i] = strdup(value);
877 if (!nl[i])
878 goto rollback;
879 }
880 nl[i] = NULL;
881
882 return 0;
883
884 rollback:
885 for (size_t j = k; j < i; j++)
886 free(nl[j]);
887 nl[k] = NULL;
888
889 return -ENOMEM;
890 }
891
892 int strv_extend_assignment(char ***l, const char *lhs, const char *rhs) {
893 char *j;
894
895 assert(l);
896 assert(lhs);
897
898 if (!rhs) /* value is optional, in which case we suppress the field */
899 return 0;
900
901 j = strjoin(lhs, "=", rhs);
902 if (!j)
903 return -ENOMEM;
904
905 return strv_consume(l, j);
906 }
907
908 int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
909 bool b = false;
910 int r;
911
912 assert(f);
913
914 /* Like fputs(), but for strv, and with a less stupid argument order */
915
916 if (!space)
917 space = &b;
918
919 STRV_FOREACH(s, l) {
920 r = fputs_with_separator(f, *s, separator, space);
921 if (r < 0)
922 return r;
923 }
924
925 return 0;
926 }
927
928 static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) {
929 char **l;
930 int r;
931
932 l = hashmap_get(h, key);
933 if (l) {
934 /* A list for this key already exists, let's append to it if it is not listed yet */
935 if (strv_contains(l, value))
936 return 0;
937
938 r = strv_extend(&l, value);
939 if (r < 0)
940 return r;
941
942 assert_se(hashmap_update(h, key, l) >= 0);
943 } else {
944 /* No list for this key exists yet, create one */
945 _cleanup_strv_free_ char **l2 = NULL;
946 _cleanup_free_ char *t = NULL;
947
948 t = strdup(key);
949 if (!t)
950 return -ENOMEM;
951
952 r = strv_extend(&l2, value);
953 if (r < 0)
954 return r;
955
956 r = hashmap_put(h, t, l2);
957 if (r < 0)
958 return r;
959 TAKE_PTR(t);
960 TAKE_PTR(l2);
961 }
962
963 return 1;
964 }
965
966 int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) {
967 int r;
968
969 r = _hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS);
970 if (r < 0)
971 return r;
972
973 return string_strv_hashmap_put_internal(*h, key, value);
974 }
975
976 int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) {
977 int r;
978
979 r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS);
980 if (r < 0)
981 return r;
982
983 return string_strv_hashmap_put_internal(PLAIN_HASHMAP(*h), key, value);
984 }
985
986 DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free);
987
988 int strv_rebreak_lines(char **l, size_t width, char ***ret) {
989 _cleanup_strv_free_ char **broken = NULL;
990 int r;
991
992 assert(ret);
993
994 /* Implements a simple UTF-8 line breaking algorithm
995 *
996 * Goes through all entries in *l, and line-breaks each line that is longer than the specified
997 * character width. Breaks at the end of words/beginning of whitespace. Lines that do not contain whitespace are not
998 * broken. Retains whitespace at beginning of lines, removes it at end of lines. */
999
1000 if (width == SIZE_MAX) { /* NOP? */
1001 broken = strv_copy(l);
1002 if (!broken)
1003 return -ENOMEM;
1004
1005 *ret = TAKE_PTR(broken);
1006 return 0;
1007 }
1008
1009 STRV_FOREACH(i, l) {
1010 const char *start = *i, *whitespace_begin = NULL, *whitespace_end = NULL;
1011 bool in_prefix = true; /* still in the whitespace in the beginning of the line? */
1012 size_t w = 0;
1013
1014 for (const char *p = start; *p != 0; p = utf8_next_char(p)) {
1015 if (strchr(NEWLINE, *p)) {
1016 in_prefix = true;
1017 whitespace_begin = whitespace_end = NULL;
1018 w = 0;
1019 } else if (strchr(WHITESPACE, *p)) {
1020 if (!in_prefix && (!whitespace_begin || whitespace_end)) {
1021 whitespace_begin = p;
1022 whitespace_end = NULL;
1023 }
1024 } else {
1025 if (whitespace_begin && !whitespace_end)
1026 whitespace_end = p;
1027
1028 in_prefix = false;
1029 }
1030
1031 int cw = utf8_char_console_width(p);
1032 if (cw < 0) {
1033 log_debug_errno(cw, "Comment to line break contains invalid UTF-8, ignoring.");
1034 cw = 1;
1035 }
1036
1037 w += cw;
1038
1039 if (w > width && whitespace_begin && whitespace_end) {
1040 _cleanup_free_ char *truncated = NULL;
1041
1042 truncated = strndup(start, whitespace_begin - start);
1043 if (!truncated)
1044 return -ENOMEM;
1045
1046 r = strv_consume(&broken, TAKE_PTR(truncated));
1047 if (r < 0)
1048 return r;
1049
1050 p = start = whitespace_end;
1051 whitespace_begin = whitespace_end = NULL;
1052 w = cw;
1053 }
1054 }
1055
1056 /* Process rest of the line */
1057 assert(start);
1058 if (in_prefix) /* Never seen anything non-whitespace? Generate empty line! */
1059 r = strv_extend(&broken, "");
1060 else if (whitespace_begin && !whitespace_end) { /* Ends in whitespace? Chop it off! */
1061 _cleanup_free_ char *truncated = strndup(start, whitespace_begin - start);
1062 if (!truncated)
1063 return -ENOMEM;
1064
1065 r = strv_consume(&broken, TAKE_PTR(truncated));
1066 } else /* Otherwise use line as is */
1067 r = strv_extend(&broken, start);
1068 if (r < 0)
1069 return r;
1070 }
1071
1072 *ret = TAKE_PTR(broken);
1073 return 0;
1074 }