Run clang-format on the whole tree
[lttng-tools.git] / src / common / string-utils / string-utils.cpp
CommitLineData
9c55c241 1/*
ab5be9fa 2 * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com>
9c55c241 3 *
c922647d 4 * SPDX-License-Identifier: LGPL-2.1-only
9c55c241 5 *
9c55c241
PP
6 */
7
8#define _LGPL_SOURCE
28ab034a
JG
9#include "../macros.hpp"
10#include "string-utils.hpp"
11
4ff75060
SM
12#include <assert.h>
13#include <errno.h>
d50d200a 14#include <stdarg.h>
28ab034a
JG
15#include <stdbool.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <type_traits>
9c55c241
PP
20
21enum star_glob_pattern_type_flags {
22 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
23 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
24 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
25};
26
28ab034a
JG
27static star_glob_pattern_type_flags& operator|=(star_glob_pattern_type_flags& l,
28 star_glob_pattern_type_flags r)
6e53c52d
SM
29{
30 using T = std::underlying_type<star_glob_pattern_type_flags>::type;
28ab034a 31 l = static_cast<star_glob_pattern_type_flags>(static_cast<T>(l) | static_cast<T>(r));
6e53c52d
SM
32 return l;
33}
34
9c55c241
PP
35/*
36 * Normalizes the star-only globbing pattern `pattern`, that is, crushes
37 * consecutive `*` characters into a single `*`, avoiding `\*`.
38 */
9c55c241
PP
39void strutils_normalize_star_glob_pattern(char *pattern)
40{
41 const char *p;
42 char *np;
43 bool got_star = false;
44
a0377dfe 45 LTTNG_ASSERT(pattern);
9c55c241
PP
46
47 for (p = pattern, np = pattern; *p != '\0'; p++) {
48 switch (*p) {
49 case '*':
50 if (got_star) {
51 /* Avoid consecutive stars. */
52 continue;
53 }
54
55 got_star = true;
56 break;
57 case '\\':
58 /* Copy backslash character. */
59 *np = *p;
60 np++;
61 p++;
62
63 if (*p == '\0') {
64 goto end;
65 }
66
30eb3927 67 /* fall through */
9c55c241
PP
68 default:
69 got_star = false;
70 break;
71 }
72
73 /* Copy single character. */
74 *np = *p;
75 np++;
76 }
77
78end:
79 *np = '\0';
80}
81
28ab034a 82static enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
9c55c241 83{
28ab034a 84 enum star_glob_pattern_type_flags ret = STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
9c55c241
PP
85 const char *p;
86
a0377dfe 87 LTTNG_ASSERT(pattern);
9c55c241
PP
88
89 for (p = pattern; *p != '\0'; p++) {
90 switch (*p) {
91 case '*':
92 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
93
94 if (p[1] == '\0') {
95 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
96 }
97
98 goto end;
99 case '\\':
100 p++;
101
102 if (*p == '\0') {
103 goto end;
104 }
105 break;
106 default:
107 break;
108 }
109 }
110
111end:
112 return ret;
113}
114
115/*
116 * Returns true if `pattern` is a star-only globbing pattern, that is,
117 * it contains at least one non-escaped `*`.
118 */
119bool strutils_is_star_glob_pattern(const char *pattern)
120{
28ab034a 121 return strutils_test_glob_pattern(pattern) & STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
9c55c241
PP
122}
123
124/*
125 * Returns true if `pattern` is a globbing pattern with a globbing,
126 * non-escaped star only at its very end.
127 */
128bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
129{
28ab034a 130 return strutils_test_glob_pattern(pattern) & STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
9c55c241
PP
131}
132
133/*
134 * Unescapes the input string `input`, that is, in a `\x` sequence,
135 * removes `\`. If `only_char` is not 0, only this character is
136 * escaped.
137 */
9c55c241
PP
138char *strutils_unescape_string(const char *input, char only_char)
139{
140 char *output;
141 char *o;
142 const char *i;
143
a0377dfe 144 LTTNG_ASSERT(input);
64803277 145 output = calloc<char>(strlen(input) + 1);
9c55c241
PP
146 if (!output) {
147 goto end;
148 }
149
150 for (i = input, o = output; *i != '\0'; i++) {
151 switch (*i) {
152 case '\\':
153 if (only_char && i[1] != only_char) {
154 break;
155 }
156
157 i++;
158
159 if (*i == '\0') {
160 /* Copy last `\`. */
161 *o = '\\';
162 o++;
163 goto end;
164 }
165 default:
166 break;
167 }
168
169 /* Copy single character. */
170 *o = *i;
171 o++;
172 }
173
174end:
175 return output;
176}
177
178/*
179 * Frees a null-terminated array of strings, including each contained
180 * string.
181 */
9c55c241
PP
182void 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 *
e358ddd5
JG
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().
9c55c241
PP
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 *
e358ddd5 245 * Returns -1 if there's an error.
9c55c241 246 */
e358ddd5 247int strutils_split(const char *input,
28ab034a
JG
248 char delim,
249 bool escape_delim,
250 struct lttng_dynamic_pointer_array *out_strings)
9c55c241 251{
e358ddd5 252 int ret;
9c55c241
PP
253 size_t at;
254 size_t number_of_substrings = 1;
255 size_t longest_substring_len = 0;
256 const char *s;
257 const char *last;
9c55c241 258
a0377dfe
FD
259 LTTNG_ASSERT(input);
260 LTTNG_ASSERT(!(escape_delim && delim == '\\'));
261 LTTNG_ASSERT(delim != '\0');
e358ddd5 262 lttng_dynamic_pointer_array_init(out_strings, free);
9c55c241
PP
263
264 /* First pass: count the number of substrings. */
265 for (s = input, last = input - 1; *s != '\0'; s++) {
266 if (escape_delim && *s == '\\') {
267 /* Ignore following (escaped) character. */
268 s++;
269
270 if (*s == '\0') {
271 break;
272 }
273
274 continue;
275 }
276
277 if (*s == delim) {
278 size_t last_len = s - last - 1;
279 last = s;
280 number_of_substrings++;
281
282 if (last_len > longest_substring_len) {
283 longest_substring_len = last_len;
284 }
285 }
286 }
287
288 if ((s - last - 1) > longest_substring_len) {
289 longest_substring_len = s - last - 1;
290 }
291
9c55c241
PP
292 /* Second pass: actually split and copy substrings. */
293 for (at = 0, s = input; at < number_of_substrings; at++) {
294 const char *ss;
295 char *d;
64803277 296 char *substring = calloc<char>(longest_substring_len + 1);
e358ddd5
JG
297
298 if (!substring) {
299 goto error;
300 }
9c55c241 301
28ab034a 302 ret = lttng_dynamic_pointer_array_add_pointer(out_strings, substring);
e358ddd5
JG
303 if (ret) {
304 free(substring);
9c55c241
PP
305 goto error;
306 }
307
308 /*
309 * Copy characters to substring until we find the next
310 * delimiter or the end of the input string.
311 */
e358ddd5 312 for (ss = s, d = substring; *ss != '\0'; ss++) {
9c55c241
PP
313 if (escape_delim && *ss == '\\') {
314 if (ss[1] == delim) {
315 /*
316 * '\' followed by delimiter and
317 * we need to escape this ('\'
318 * won't be part of the
319 * resulting substring).
320 */
321 ss++;
322 *d = *ss;
323 d++;
324 continue;
325 } else {
326 /*
327 * Copy '\' and the following
328 * character.
329 */
330 *d = *ss;
331 d++;
332 ss++;
333
334 if (*ss == '\0') {
335 break;
336 }
337 }
338 } else if (*ss == delim) {
339 /* We're done with this substring. */
340 break;
341 }
342
343 *d = *ss;
344 d++;
345 }
346
347 /* Next substring starts after the last delimiter. */
348 s = ss + 1;
349 }
350
e358ddd5 351 ret = 0;
9c55c241
PP
352 goto end;
353
354error:
e358ddd5 355 ret = -1;
9c55c241 356end:
e358ddd5 357 return ret;
9c55c241
PP
358}
359
28ab034a 360size_t strutils_array_of_strings_len(char *const *array)
9c55c241 361{
28ab034a 362 char *const *item;
9c55c241
PP
363 size_t count = 0;
364
a0377dfe 365 LTTNG_ASSERT(array);
9c55c241
PP
366
367 for (item = array; *item; item++) {
368 count++;
369 }
370
371 return count;
372}
4ff75060
SM
373
374int strutils_append_str(char **s, const char *append)
375{
376 char *old = *s;
377 char *new_str;
378 size_t oldlen = (old == NULL) ? 0 : strlen(old);
379 size_t appendlen = strlen(append);
380
64803277 381 new_str = zmalloc<char>(oldlen + appendlen + 1);
4ff75060
SM
382 if (!new_str) {
383 return -ENOMEM;
384 }
385 if (oldlen) {
386 strcpy(new_str, old);
387 }
388 strcat(new_str, append);
389 *s = new_str;
390 free(old);
391 return 0;
392}
d50d200a
SM
393
394int strutils_appendf(char **s, const char *fmt, ...)
395{
396 char *new_str;
397 size_t oldlen = (*s) ? strlen(*s) : 0;
398 int ret;
399 va_list args;
400
401 /* Compute length of formatted string we append. */
402 va_start(args, fmt);
403 ret = vsnprintf(NULL, 0, fmt, args);
404 va_end(args);
405
406 if (ret == -1) {
407 goto end;
408 }
409
410 /* Allocate space for old string + new string + \0. */
64803277 411 new_str = zmalloc<char>(oldlen + ret + 1);
d50d200a
SM
412 if (!new_str) {
413 ret = -ENOMEM;
414 goto end;
415 }
416
417 /* Copy old string, if there was one. */
418 if (oldlen) {
419 strcpy(new_str, *s);
420 }
421
422 /* Format new string in-place. */
423 va_start(args, fmt);
28ab034a 424 ret = vsprintf(&new_str[oldlen], fmt, args);
d50d200a
SM
425 va_end(args);
426
427 if (ret == -1) {
428 ret = -1;
429 goto end;
430 }
431
432 free(*s);
433 *s = new_str;
434 new_str = NULL;
435
436end:
437 return ret;
438}
This page took 0.06798 seconds and 4 git commands to generate.