argpar: sync with upstream - adjust to iterator API
[lttng-tools.git] / src / common / string-utils / string-utils.c
1 /*
2 * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 */
7
8 #define _LGPL_SOURCE
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdbool.h>
13 #include <assert.h>
14 #include <errno.h>
15 #include <stdarg.h>
16
17 #include "string-utils.h"
18 #include "../macros.h"
19
20 enum star_glob_pattern_type_flags {
21 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
22 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
23 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
24 };
25
26 /*
27 * Normalizes the star-only globbing pattern `pattern`, that is, crushes
28 * consecutive `*` characters into a single `*`, avoiding `\*`.
29 */
30 LTTNG_HIDDEN
31 void strutils_normalize_star_glob_pattern(char *pattern)
32 {
33 const char *p;
34 char *np;
35 bool got_star = false;
36
37 assert(pattern);
38
39 for (p = pattern, np = pattern; *p != '\0'; p++) {
40 switch (*p) {
41 case '*':
42 if (got_star) {
43 /* Avoid consecutive stars. */
44 continue;
45 }
46
47 got_star = true;
48 break;
49 case '\\':
50 /* Copy backslash character. */
51 *np = *p;
52 np++;
53 p++;
54
55 if (*p == '\0') {
56 goto end;
57 }
58
59 /* Fall through default case. */
60 default:
61 got_star = false;
62 break;
63 }
64
65 /* Copy single character. */
66 *np = *p;
67 np++;
68 }
69
70 end:
71 *np = '\0';
72 }
73
74 static
75 enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
76 {
77 enum star_glob_pattern_type_flags ret =
78 STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
79 const char *p;
80
81 assert(pattern);
82
83 for (p = pattern; *p != '\0'; p++) {
84 switch (*p) {
85 case '*':
86 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
87
88 if (p[1] == '\0') {
89 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
90 }
91
92 goto end;
93 case '\\':
94 p++;
95
96 if (*p == '\0') {
97 goto end;
98 }
99 break;
100 default:
101 break;
102 }
103 }
104
105 end:
106 return ret;
107 }
108
109 /*
110 * Returns true if `pattern` is a star-only globbing pattern, that is,
111 * it contains at least one non-escaped `*`.
112 */
113 LTTNG_HIDDEN
114 bool strutils_is_star_glob_pattern(const char *pattern)
115 {
116 return strutils_test_glob_pattern(pattern) &
117 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
118 }
119
120 /*
121 * Returns true if `pattern` is a globbing pattern with a globbing,
122 * non-escaped star only at its very end.
123 */
124 LTTNG_HIDDEN
125 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
126 {
127 return strutils_test_glob_pattern(pattern) &
128 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
129 }
130
131 /*
132 * Unescapes the input string `input`, that is, in a `\x` sequence,
133 * removes `\`. If `only_char` is not 0, only this character is
134 * escaped.
135 */
136 LTTNG_HIDDEN
137 char *strutils_unescape_string(const char *input, char only_char)
138 {
139 char *output;
140 char *o;
141 const char *i;
142
143 assert(input);
144 output = zmalloc(strlen(input) + 1);
145 if (!output) {
146 goto end;
147 }
148
149 for (i = input, o = output; *i != '\0'; i++) {
150 switch (*i) {
151 case '\\':
152 if (only_char && i[1] != only_char) {
153 break;
154 }
155
156 i++;
157
158 if (*i == '\0') {
159 /* Copy last `\`. */
160 *o = '\\';
161 o++;
162 goto end;
163 }
164 default:
165 break;
166 }
167
168 /* Copy single character. */
169 *o = *i;
170 o++;
171 }
172
173 end:
174 return output;
175 }
176
177 /*
178 * Frees a null-terminated array of strings, including each contained
179 * string.
180 */
181 LTTNG_HIDDEN
182 void strutils_free_null_terminated_array_of_strings(char **array)
183 {
184 char **item;
185
186 if (!array) {
187 return;
188 }
189
190 for (item = array; *item; item++) {
191 free(*item);
192 }
193
194 free(array);
195 }
196
197 /*
198 * Splits the input string `input` using the given delimiter `delim`.
199 *
200 * The return value is a dynamic pointer array that is assumed to be empty. The
201 * array must be discarded by the caller by invoking
202 * lttng_dynamic_pointer_array_reset().
203 *
204 * Empty substrings are part of the result. For example:
205 *
206 * Input: ,hello,,there,
207 * Result:
208 * ``
209 * `hello`
210 * ``
211 * `there`
212 * ``
213 *
214 * If `escape_delim` is true, then `\,`, where `,` is the delimiter,
215 * escapes the delimiter and is copied as `,` only in the resulting
216 * substring. For example:
217 *
218 * Input: hello\,world,zoom,\,hi
219 * Result:
220 * `hello,world`
221 * `zoom`
222 * `,hi`
223 *
224 * Other characters are not escaped (this is the caller's job if
225 * needed). However they are considering during the parsing, that is,
226 * `\x`, where `x` is any character, is copied as is to the resulting
227 * substring, e.g.:
228 *
229 * Input: hello\,wo\rld\\,zoom\,
230 * Result:
231 * `hello,wo\rld\\`
232 * `zoom,`
233 *
234 * If `escape_delim` is false, nothing at all is escaped, and `delim`,
235 * when found in `input`, is always a delimiter, e.g.:
236 *
237 * Input: hello\,world,zoom,\,hi
238 * Result:
239 * `hello\`
240 * `world`
241 * `zoom`
242 * `\`
243 * `hi`
244 *
245 * Returns -1 if there's an error.
246 */
247 LTTNG_HIDDEN
248 int strutils_split(const char *input,
249 char delim,
250 bool escape_delim,
251 struct lttng_dynamic_pointer_array *out_strings)
252 {
253 int ret;
254 size_t at;
255 size_t number_of_substrings = 1;
256 size_t longest_substring_len = 0;
257 const char *s;
258 const char *last;
259
260 assert(input);
261 assert(!(escape_delim && delim == '\\'));
262 assert(delim != '\0');
263 lttng_dynamic_pointer_array_init(out_strings, free);
264
265 /* First pass: count the number of substrings. */
266 for (s = input, last = input - 1; *s != '\0'; s++) {
267 if (escape_delim && *s == '\\') {
268 /* Ignore following (escaped) character. */
269 s++;
270
271 if (*s == '\0') {
272 break;
273 }
274
275 continue;
276 }
277
278 if (*s == delim) {
279 size_t last_len = s - last - 1;
280 last = s;
281 number_of_substrings++;
282
283 if (last_len > longest_substring_len) {
284 longest_substring_len = last_len;
285 }
286 }
287 }
288
289 if ((s - last - 1) > longest_substring_len) {
290 longest_substring_len = s - last - 1;
291 }
292
293 /* Second pass: actually split and copy substrings. */
294 for (at = 0, s = input; at < number_of_substrings; at++) {
295 const char *ss;
296 char *d;
297 char *substring = zmalloc(longest_substring_len + 1);
298
299 if (!substring) {
300 goto error;
301 }
302
303 ret = lttng_dynamic_pointer_array_add_pointer(
304 out_strings, substring);
305 if (ret) {
306 free(substring);
307 goto error;
308 }
309
310 /*
311 * Copy characters to substring until we find the next
312 * delimiter or the end of the input string.
313 */
314 for (ss = s, d = substring; *ss != '\0'; ss++) {
315 if (escape_delim && *ss == '\\') {
316 if (ss[1] == delim) {
317 /*
318 * '\' followed by delimiter and
319 * we need to escape this ('\'
320 * won't be part of the
321 * resulting substring).
322 */
323 ss++;
324 *d = *ss;
325 d++;
326 continue;
327 } else {
328 /*
329 * Copy '\' and the following
330 * character.
331 */
332 *d = *ss;
333 d++;
334 ss++;
335
336 if (*ss == '\0') {
337 break;
338 }
339 }
340 } else if (*ss == delim) {
341 /* We're done with this substring. */
342 break;
343 }
344
345 *d = *ss;
346 d++;
347 }
348
349 /* Next substring starts after the last delimiter. */
350 s = ss + 1;
351 }
352
353 ret = 0;
354 goto end;
355
356 error:
357 ret = -1;
358 end:
359 return ret;
360 }
361
362 LTTNG_HIDDEN
363 size_t strutils_array_of_strings_len(char * const *array)
364 {
365 char * const *item;
366 size_t count = 0;
367
368 assert(array);
369
370 for (item = array; *item; item++) {
371 count++;
372 }
373
374 return count;
375 }
376
377 LTTNG_HIDDEN
378 int strutils_append_str(char **s, const char *append)
379 {
380 char *old = *s;
381 char *new;
382 size_t oldlen = (old == NULL) ? 0 : strlen(old);
383 size_t appendlen = strlen(append);
384
385 new = calloc(oldlen + appendlen + 1, 1);
386 if (!new) {
387 return -ENOMEM;
388 }
389 if (oldlen) {
390 strcpy(new, old);
391 }
392 strcat(new, append);
393 *s = new;
394 free(old);
395 return 0;
396 }
397
398 LTTNG_HIDDEN
399 int strutils_appendf(char **s, const char *fmt, ...)
400 {
401 char *new_str;
402 size_t oldlen = (*s) ? strlen(*s) : 0;
403 int ret;
404 va_list args;
405
406 /* Compute length of formatted string we append. */
407 va_start(args, fmt);
408 ret = vsnprintf(NULL, 0, fmt, args);
409 va_end(args);
410
411 if (ret == -1) {
412 goto end;
413 }
414
415 /* Allocate space for old string + new string + \0. */
416 new_str = calloc(oldlen + ret + 1, 1);
417 if (!new_str) {
418 ret = -ENOMEM;
419 goto end;
420 }
421
422 /* Copy old string, if there was one. */
423 if (oldlen) {
424 strcpy(new_str, *s);
425 }
426
427 /* Format new string in-place. */
428 va_start(args, fmt);
429 ret = vsprintf(&new_str[oldlen], fmt, args);
430 va_end(args);
431
432 if (ret == -1) {
433 ret = -1;
434 goto end;
435 }
436
437 free(*s);
438 *s = new_str;
439 new_str = NULL;
440
441 end:
442 return ret;
443 }
This page took 0.03981 seconds and 4 git commands to generate.