common: move append_str to string-utils
[lttng-tools.git] / src / common / string-utils / string-utils.c
CommitLineData
9c55c241 1/*
ab5be9fa 2 * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com>
9c55c241 3 *
ab5be9fa 4 * SPDX-License-Identifier: GPL-2.0-only
9c55c241 5 *
9c55c241
PP
6 */
7
8#define _LGPL_SOURCE
9#include <stdlib.h>
10#include <string.h>
11#include <stdbool.h>
12#include <assert.h>
5b770521 13#include <errno.h>
9c55c241
PP
14
15#include "string-utils.h"
16#include "../macros.h"
17
18enum 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 */
28LTTNG_HIDDEN
29void 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
68end:
69 *np = '\0';
70}
71
72static
73enum 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
103end:
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 */
f181fa5a 111LTTNG_HIDDEN
9c55c241
PP
112bool 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 */
f181fa5a 122LTTNG_HIDDEN
9c55c241
PP
123bool 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 */
134LTTNG_HIDDEN
135char *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
171end:
172 return output;
173}
174
175/*
176 * Frees a null-terminated array of strings, including each contained
177 * string.
178 */
179LTTNG_HIDDEN
180void 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 *
f78de773
JG
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().
9c55c241
PP
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 *
f78de773 243 * Returns -1 if there's an error.
9c55c241
PP
244 */
245LTTNG_HIDDEN
f78de773
JG
246int strutils_split(const char *input,
247 char delim,
248 bool escape_delim,
249 struct lttng_dynamic_pointer_array *out_strings)
9c55c241 250{
f78de773 251 int ret;
9c55c241
PP
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;
9c55c241
PP
257
258 assert(input);
259 assert(!(escape_delim && delim == '\\'));
260 assert(delim != '\0');
f78de773 261 lttng_dynamic_pointer_array_init(out_strings, free);
9c55c241
PP
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
9c55c241
PP
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;
f78de773
JG
295 char *substring = zmalloc(longest_substring_len + 1);
296
297 if (!substring) {
298 goto error;
299 }
9c55c241 300
f78de773
JG
301 ret = lttng_dynamic_pointer_array_add_pointer(
302 out_strings, substring);
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 */
f78de773 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
f78de773 351 ret = 0;
9c55c241
PP
352 goto end;
353
354error:
f78de773 355 ret = -1;
9c55c241 356end:
f78de773 357 return ret;
9c55c241
PP
358}
359
360LTTNG_HIDDEN
361size_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}
5b770521
SM
374
375LTTNG_HIDDEN
376int 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.06374 seconds and 4 git commands to generate.