8dcc6b2aae9ce9fab7b3beeefbea0b495fc15db4
[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: GPL-2.0-only
5 *
6 */
7
8 #define _LGPL_SOURCE
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdbool.h>
12 #include <type_traits>
13 #include <assert.h>
14 #include <errno.h>
15
16 #include "string-utils.h"
17 #include "../macros.h"
18
19 enum star_glob_pattern_type_flags {
20 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
21 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
22 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
23 };
24
25 static
26 star_glob_pattern_type_flags &operator|=(star_glob_pattern_type_flags &l,
27 star_glob_pattern_type_flags r)
28 {
29 using T = std::underlying_type<star_glob_pattern_type_flags>::type;
30 l = static_cast<star_glob_pattern_type_flags> (
31 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 default case. */
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
83 enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
84 {
85 enum star_glob_pattern_type_flags ret =
86 STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
87 const char *p;
88
89 LTTNG_ASSERT(pattern);
90
91 for (p = pattern; *p != '\0'; p++) {
92 switch (*p) {
93 case '*':
94 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
95
96 if (p[1] == '\0') {
97 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
98 }
99
100 goto end;
101 case '\\':
102 p++;
103
104 if (*p == '\0') {
105 goto end;
106 }
107 break;
108 default:
109 break;
110 }
111 }
112
113 end:
114 return ret;
115 }
116
117 /*
118 * Returns true if `pattern` is a star-only globbing pattern, that is,
119 * it contains at least one non-escaped `*`.
120 */
121 bool strutils_is_star_glob_pattern(const char *pattern)
122 {
123 return strutils_test_glob_pattern(pattern) &
124 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
125 }
126
127 /*
128 * Returns true if `pattern` is a globbing pattern with a globbing,
129 * non-escaped star only at its very end.
130 */
131 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
132 {
133 return strutils_test_glob_pattern(pattern) &
134 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
135 }
136
137 /*
138 * Unescapes the input string `input`, that is, in a `\x` sequence,
139 * removes `\`. If `only_char` is not 0, only this character is
140 * escaped.
141 */
142 char *strutils_unescape_string(const char *input, char only_char)
143 {
144 char *output;
145 char *o;
146 const char *i;
147
148 LTTNG_ASSERT(input);
149 output = (char *) zmalloc(strlen(input) + 1);
150 if (!output) {
151 goto end;
152 }
153
154 for (i = input, o = output; *i != '\0'; i++) {
155 switch (*i) {
156 case '\\':
157 if (only_char && i[1] != only_char) {
158 break;
159 }
160
161 i++;
162
163 if (*i == '\0') {
164 /* Copy last `\`. */
165 *o = '\\';
166 o++;
167 goto end;
168 }
169 default:
170 break;
171 }
172
173 /* Copy single character. */
174 *o = *i;
175 o++;
176 }
177
178 end:
179 return output;
180 }
181
182 /*
183 * Frees a null-terminated array of strings, including each contained
184 * string.
185 */
186 void strutils_free_null_terminated_array_of_strings(char **array)
187 {
188 char **item;
189
190 if (!array) {
191 return;
192 }
193
194 for (item = array; *item; item++) {
195 free(*item);
196 }
197
198 free(array);
199 }
200
201 /*
202 * Splits the input string `input` using the given delimiter `delim`.
203 *
204 * The return value is a dynamic pointer array that is assumed to be empty. The
205 * array must be discarded by the caller by invoking
206 * lttng_dynamic_pointer_array_reset().
207 *
208 * Empty substrings are part of the result. For example:
209 *
210 * Input: ,hello,,there,
211 * Result:
212 * ``
213 * `hello`
214 * ``
215 * `there`
216 * ``
217 *
218 * If `escape_delim` is true, then `\,`, where `,` is the delimiter,
219 * escapes the delimiter and is copied as `,` only in the resulting
220 * substring. For example:
221 *
222 * Input: hello\,world,zoom,\,hi
223 * Result:
224 * `hello,world`
225 * `zoom`
226 * `,hi`
227 *
228 * Other characters are not escaped (this is the caller's job if
229 * needed). However they are considering during the parsing, that is,
230 * `\x`, where `x` is any character, is copied as is to the resulting
231 * substring, e.g.:
232 *
233 * Input: hello\,wo\rld\\,zoom\,
234 * Result:
235 * `hello,wo\rld\\`
236 * `zoom,`
237 *
238 * If `escape_delim` is false, nothing at all is escaped, and `delim`,
239 * when found in `input`, is always a delimiter, e.g.:
240 *
241 * Input: hello\,world,zoom,\,hi
242 * Result:
243 * `hello\`
244 * `world`
245 * `zoom`
246 * `\`
247 * `hi`
248 *
249 * Returns -1 if there's an error.
250 */
251 int strutils_split(const char *input,
252 char delim,
253 bool escape_delim,
254 struct lttng_dynamic_pointer_array *out_strings)
255 {
256 int ret;
257 size_t at;
258 size_t number_of_substrings = 1;
259 size_t longest_substring_len = 0;
260 const char *s;
261 const char *last;
262
263 LTTNG_ASSERT(input);
264 LTTNG_ASSERT(!(escape_delim && delim == '\\'));
265 LTTNG_ASSERT(delim != '\0');
266 lttng_dynamic_pointer_array_init(out_strings, free);
267
268 /* First pass: count the number of substrings. */
269 for (s = input, last = input - 1; *s != '\0'; s++) {
270 if (escape_delim && *s == '\\') {
271 /* Ignore following (escaped) character. */
272 s++;
273
274 if (*s == '\0') {
275 break;
276 }
277
278 continue;
279 }
280
281 if (*s == delim) {
282 size_t last_len = s - last - 1;
283 last = s;
284 number_of_substrings++;
285
286 if (last_len > longest_substring_len) {
287 longest_substring_len = last_len;
288 }
289 }
290 }
291
292 if ((s - last - 1) > longest_substring_len) {
293 longest_substring_len = s - last - 1;
294 }
295
296 /* Second pass: actually split and copy substrings. */
297 for (at = 0, s = input; at < number_of_substrings; at++) {
298 const char *ss;
299 char *d;
300 char *substring = (char *) zmalloc(longest_substring_len + 1);
301
302 if (!substring) {
303 goto error;
304 }
305
306 ret = lttng_dynamic_pointer_array_add_pointer(
307 out_strings, substring);
308 if (ret) {
309 free(substring);
310 goto error;
311 }
312
313 /*
314 * Copy characters to substring until we find the next
315 * delimiter or the end of the input string.
316 */
317 for (ss = s, d = substring; *ss != '\0'; ss++) {
318 if (escape_delim && *ss == '\\') {
319 if (ss[1] == delim) {
320 /*
321 * '\' followed by delimiter and
322 * we need to escape this ('\'
323 * won't be part of the
324 * resulting substring).
325 */
326 ss++;
327 *d = *ss;
328 d++;
329 continue;
330 } else {
331 /*
332 * Copy '\' and the following
333 * character.
334 */
335 *d = *ss;
336 d++;
337 ss++;
338
339 if (*ss == '\0') {
340 break;
341 }
342 }
343 } else if (*ss == delim) {
344 /* We're done with this substring. */
345 break;
346 }
347
348 *d = *ss;
349 d++;
350 }
351
352 /* Next substring starts after the last delimiter. */
353 s = ss + 1;
354 }
355
356 ret = 0;
357 goto end;
358
359 error:
360 ret = -1;
361 end:
362 return ret;
363 }
364
365 size_t strutils_array_of_strings_len(char * const *array)
366 {
367 char * const *item;
368 size_t count = 0;
369
370 LTTNG_ASSERT(array);
371
372 for (item = array; *item; item++) {
373 count++;
374 }
375
376 return count;
377 }
378
379 int strutils_append_str(char **s, const char *append)
380 {
381 char *old = *s;
382 char *new_str;
383 size_t oldlen = (old == NULL) ? 0 : strlen(old);
384 size_t appendlen = strlen(append);
385
386 new_str = (char *) zmalloc(oldlen + appendlen + 1);
387 if (!new_str) {
388 return -ENOMEM;
389 }
390 if (oldlen) {
391 strcpy(new_str, old);
392 }
393 strcat(new_str, append);
394 *s = new_str;
395 free(old);
396 return 0;
397 }
This page took 0.036209 seconds and 4 git commands to generate.