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