526604d14e42a6f0d907fab86676ca2f2435e46f
[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 <string.h>
11 #include <stdbool.h>
12 #include <assert.h>
13 #include <errno.h>
14
15 #include "string-utils.h"
16 #include "../macros.h"
17
18 enum star_glob_pattern_type_flags {
19 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
20 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
21 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
22 };
23
24 /*
25 * Normalizes the star-only globbing pattern `pattern`, that is, crushes
26 * consecutive `*` characters into a single `*`, avoiding `\*`.
27 */
28 LTTNG_HIDDEN
29 void strutils_normalize_star_glob_pattern(char *pattern)
30 {
31 const char *p;
32 char *np;
33 bool got_star = false;
34
35 assert(pattern);
36
37 for (p = pattern, np = pattern; *p != '\0'; p++) {
38 switch (*p) {
39 case '*':
40 if (got_star) {
41 /* Avoid consecutive stars. */
42 continue;
43 }
44
45 got_star = true;
46 break;
47 case '\\':
48 /* Copy backslash character. */
49 *np = *p;
50 np++;
51 p++;
52
53 if (*p == '\0') {
54 goto end;
55 }
56
57 /* Fall through default case. */
58 default:
59 got_star = false;
60 break;
61 }
62
63 /* Copy single character. */
64 *np = *p;
65 np++;
66 }
67
68 end:
69 *np = '\0';
70 }
71
72 static
73 enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
74 {
75 enum star_glob_pattern_type_flags ret =
76 STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
77 const char *p;
78
79 assert(pattern);
80
81 for (p = pattern; *p != '\0'; p++) {
82 switch (*p) {
83 case '*':
84 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
85
86 if (p[1] == '\0') {
87 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
88 }
89
90 goto end;
91 case '\\':
92 p++;
93
94 if (*p == '\0') {
95 goto end;
96 }
97 break;
98 default:
99 break;
100 }
101 }
102
103 end:
104 return ret;
105 }
106
107 /*
108 * Returns true if `pattern` is a star-only globbing pattern, that is,
109 * it contains at least one non-escaped `*`.
110 */
111 LTTNG_HIDDEN
112 bool strutils_is_star_glob_pattern(const char *pattern)
113 {
114 return strutils_test_glob_pattern(pattern) &
115 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
116 }
117
118 /*
119 * Returns true if `pattern` is a globbing pattern with a globbing,
120 * non-escaped star only at its very end.
121 */
122 LTTNG_HIDDEN
123 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
124 {
125 return strutils_test_glob_pattern(pattern) &
126 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
127 }
128
129 /*
130 * Unescapes the input string `input`, that is, in a `\x` sequence,
131 * removes `\`. If `only_char` is not 0, only this character is
132 * escaped.
133 */
134 LTTNG_HIDDEN
135 char *strutils_unescape_string(const char *input, char only_char)
136 {
137 char *output;
138 char *o;
139 const char *i;
140
141 assert(input);
142 output = zmalloc(strlen(input) + 1);
143 if (!output) {
144 goto end;
145 }
146
147 for (i = input, o = output; *i != '\0'; i++) {
148 switch (*i) {
149 case '\\':
150 if (only_char && i[1] != only_char) {
151 break;
152 }
153
154 i++;
155
156 if (*i == '\0') {
157 /* Copy last `\`. */
158 *o = '\\';
159 o++;
160 goto end;
161 }
162 default:
163 break;
164 }
165
166 /* Copy single character. */
167 *o = *i;
168 o++;
169 }
170
171 end:
172 return output;
173 }
174
175 /*
176 * Frees a null-terminated array of strings, including each contained
177 * string.
178 */
179 LTTNG_HIDDEN
180 void strutils_free_null_terminated_array_of_strings(char **array)
181 {
182 char **item;
183
184 if (!array) {
185 return;
186 }
187
188 for (item = array; *item; item++) {
189 free(*item);
190 }
191
192 free(array);
193 }
194
195 /*
196 * Splits the input string `input` using the given delimiter `delim`.
197 *
198 * The return value is a dynamic pointer array that is assumed to be empty. The
199 * array must be discarded by the caller by invoking
200 * lttng_dynamic_pointer_array_reset().
201 *
202 * Empty substrings are part of the result. For example:
203 *
204 * Input: ,hello,,there,
205 * Result:
206 * ``
207 * `hello`
208 * ``
209 * `there`
210 * ``
211 *
212 * If `escape_delim` is true, then `\,`, where `,` is the delimiter,
213 * escapes the delimiter and is copied as `,` only in the resulting
214 * substring. For example:
215 *
216 * Input: hello\,world,zoom,\,hi
217 * Result:
218 * `hello,world`
219 * `zoom`
220 * `,hi`
221 *
222 * Other characters are not escaped (this is the caller's job if
223 * needed). However they are considering during the parsing, that is,
224 * `\x`, where `x` is any character, is copied as is to the resulting
225 * substring, e.g.:
226 *
227 * Input: hello\,wo\rld\\,zoom\,
228 * Result:
229 * `hello,wo\rld\\`
230 * `zoom,`
231 *
232 * If `escape_delim` is false, nothing at all is escaped, and `delim`,
233 * when found in `input`, is always a delimiter, e.g.:
234 *
235 * Input: hello\,world,zoom,\,hi
236 * Result:
237 * `hello\`
238 * `world`
239 * `zoom`
240 * `\`
241 * `hi`
242 *
243 * Returns -1 if there's an error.
244 */
245 LTTNG_HIDDEN
246 int strutils_split(const char *input,
247 char delim,
248 bool escape_delim,
249 struct lttng_dynamic_pointer_array *out_strings)
250 {
251 int ret;
252 size_t at;
253 size_t number_of_substrings = 1;
254 size_t longest_substring_len = 0;
255 const char *s;
256 const char *last;
257
258 assert(input);
259 assert(!(escape_delim && delim == '\\'));
260 assert(delim != '\0');
261 lttng_dynamic_pointer_array_init(out_strings, free);
262
263 /* First pass: count the number of substrings. */
264 for (s = input, last = input - 1; *s != '\0'; s++) {
265 if (escape_delim && *s == '\\') {
266 /* Ignore following (escaped) character. */
267 s++;
268
269 if (*s == '\0') {
270 break;
271 }
272
273 continue;
274 }
275
276 if (*s == delim) {
277 size_t last_len = s - last - 1;
278 last = s;
279 number_of_substrings++;
280
281 if (last_len > longest_substring_len) {
282 longest_substring_len = last_len;
283 }
284 }
285 }
286
287 if ((s - last - 1) > longest_substring_len) {
288 longest_substring_len = s - last - 1;
289 }
290
291 /* Second pass: actually split and copy substrings. */
292 for (at = 0, s = input; at < number_of_substrings; at++) {
293 const char *ss;
294 char *d;
295 char *substring = zmalloc(longest_substring_len + 1);
296
297 if (!substring) {
298 goto error;
299 }
300
301 ret = lttng_dynamic_pointer_array_add_pointer(
302 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 LTTNG_HIDDEN
361 size_t strutils_array_of_strings_len(char * const *array)
362 {
363 char * const *item;
364 size_t count = 0;
365
366 assert(array);
367
368 for (item = array; *item; item++) {
369 count++;
370 }
371
372 return count;
373 }
374
375 LTTNG_HIDDEN
376 int strutils_append_str(char **s, const char *append)
377 {
378 char *old = *s;
379 char *new;
380 size_t oldlen = (old == NULL) ? 0 : strlen(old);
381 size_t appendlen = strlen(append);
382
383 new = calloc(oldlen + appendlen + 1, 1);
384 if (!new) {
385 return -ENOMEM;
386 }
387 if (oldlen) {
388 strcpy(new, old);
389 }
390 strcat(new, append);
391 *s = new;
392 free(old);
393 return 0;
394 }
This page took 0.053954 seconds and 3 git commands to generate.