63de5267442721ac861ffb666c6a6b42d332ccb4
[lttng-tools.git] / src / common / string-utils / string-utils.cpp
1 /*
2 * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only
5 *
6 */
7
8 #define _LGPL_SOURCE
9 #include "../macros.hpp"
10 #include "string-utils.hpp"
11
12 #include <assert.h>
13 #include <errno.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <type_traits>
20
21 enum 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
27 static star_glob_pattern_type_flags& operator|=(star_glob_pattern_type_flags& l,
28 star_glob_pattern_type_flags r)
29 {
30 using T = std::underlying_type<star_glob_pattern_type_flags>::type;
31 l = static_cast<star_glob_pattern_type_flags>(static_cast<T>(l) | static_cast<T>(r));
32 return l;
33 }
34
35 /*
36 * Normalizes the star-only globbing pattern `pattern`, that is, crushes
37 * consecutive `*` characters into a single `*`, avoiding `\*`.
38 */
39 void strutils_normalize_star_glob_pattern(char *pattern)
40 {
41 const char *p;
42 char *np;
43 bool got_star = false;
44
45 LTTNG_ASSERT(pattern);
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
67 /* fall through */
68 default:
69 got_star = false;
70 break;
71 }
72
73 /* Copy single character. */
74 *np = *p;
75 np++;
76 }
77
78 end:
79 *np = '\0';
80 }
81
82 static enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
83 {
84 enum star_glob_pattern_type_flags ret = STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
85 const char *p;
86
87 LTTNG_ASSERT(pattern);
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
111 end:
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 */
119 bool strutils_is_star_glob_pattern(const char *pattern)
120 {
121 return strutils_test_glob_pattern(pattern) & STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
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 */
128 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
129 {
130 return strutils_test_glob_pattern(pattern) & STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
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 */
138 char *strutils_unescape_string(const char *input, char only_char)
139 {
140 char *output;
141 char *o;
142 const char *i;
143
144 LTTNG_ASSERT(input);
145 output = calloc<char>(strlen(input) + 1);
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
174 end:
175 return output;
176 }
177
178 /*
179 * Frees a null-terminated array of strings, including each contained
180 * string.
181 */
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 int strutils_split(const char *input,
248 char delim,
249 bool escape_delim,
250 struct lttng_dynamic_pointer_array *out_strings)
251 {
252 int ret;
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;
258
259 LTTNG_ASSERT(input);
260 LTTNG_ASSERT(!(escape_delim && delim == '\\'));
261 LTTNG_ASSERT(delim != '\0');
262 lttng_dynamic_pointer_array_init(out_strings, free);
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
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;
296 char *substring = calloc<char>(longest_substring_len + 1);
297
298 if (!substring) {
299 goto error;
300 }
301
302 ret = lttng_dynamic_pointer_array_add_pointer(out_strings, substring);
303 if (ret) {
304 free(substring);
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 */
312 for (ss = s, d = substring; *ss != '\0'; ss++) {
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
351 ret = 0;
352 goto end;
353
354 error:
355 ret = -1;
356 end:
357 return ret;
358 }
359
360 size_t strutils_array_of_strings_len(char *const *array)
361 {
362 char *const *item;
363 size_t count = 0;
364
365 LTTNG_ASSERT(array);
366
367 for (item = array; *item; item++) {
368 count++;
369 }
370
371 return count;
372 }
373
374 int strutils_append_str(char **s, const char *append)
375 {
376 char *old = *s;
377 char *new_str;
378 size_t oldlen = (old == nullptr) ? 0 : strlen(old);
379 size_t appendlen = strlen(append);
380
381 new_str = zmalloc<char>(oldlen + appendlen + 1);
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 }
393
394 int 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(nullptr, 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. */
411 new_str = zmalloc<char>(oldlen + ret + 1);
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);
424 ret = vsprintf(&new_str[oldlen], fmt, args);
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 = nullptr;
435
436 end:
437 return ret;
438 }
This page took 0.037152 seconds and 4 git commands to generate.