722686678cf5e070e990b3b8e6b5bf830215f781
[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: LGPL-2.1-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
14 #include "string-utils.h"
15 #include "../macros.h"
16
17 enum star_glob_pattern_type_flags {
18 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
19 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
20 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
21 };
22
23 /*
24 * Normalizes the star-only globbing pattern `pattern`, that is, crushes
25 * consecutive `*` characters into a single `*`, avoiding `\*`.
26 */
27 LTTNG_HIDDEN
28 void strutils_normalize_star_glob_pattern(char *pattern)
29 {
30 const char *p;
31 char *np;
32 bool got_star = false;
33
34 assert(pattern);
35
36 for (p = pattern, np = pattern; *p != '\0'; p++) {
37 switch (*p) {
38 case '*':
39 if (got_star) {
40 /* Avoid consecutive stars. */
41 continue;
42 }
43
44 got_star = true;
45 break;
46 case '\\':
47 /* Copy backslash character. */
48 *np = *p;
49 np++;
50 p++;
51
52 if (*p == '\0') {
53 goto end;
54 }
55
56 /* Fall through default case. */
57 default:
58 got_star = false;
59 break;
60 }
61
62 /* Copy single character. */
63 *np = *p;
64 np++;
65 }
66
67 end:
68 *np = '\0';
69 }
70
71 static
72 enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
73 {
74 enum star_glob_pattern_type_flags ret =
75 STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
76 const char *p;
77
78 assert(pattern);
79
80 for (p = pattern; *p != '\0'; p++) {
81 switch (*p) {
82 case '*':
83 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
84
85 if (p[1] == '\0') {
86 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
87 }
88
89 goto end;
90 case '\\':
91 p++;
92
93 if (*p == '\0') {
94 goto end;
95 }
96 break;
97 default:
98 break;
99 }
100 }
101
102 end:
103 return ret;
104 }
105
106 /*
107 * Returns true if `pattern` is a star-only globbing pattern, that is,
108 * it contains at least one non-escaped `*`.
109 */
110 LTTNG_HIDDEN
111 bool strutils_is_star_glob_pattern(const char *pattern)
112 {
113 return strutils_test_glob_pattern(pattern) &
114 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
115 }
116
117 /*
118 * Returns true if `pattern` is a globbing pattern with a globbing,
119 * non-escaped star only at its very end.
120 */
121 LTTNG_HIDDEN
122 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
123 {
124 return strutils_test_glob_pattern(pattern) &
125 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
126 }
127
128 /*
129 * Unescapes the input string `input`, that is, in a `\x` sequence,
130 * removes `\`. If `only_char` is not 0, only this character is
131 * escaped.
132 */
133 LTTNG_HIDDEN
134 char *strutils_unescape_string(const char *input, char only_char)
135 {
136 char *output;
137 char *o;
138 const char *i;
139
140 assert(input);
141 output = zmalloc(strlen(input) + 1);
142 if (!output) {
143 goto end;
144 }
145
146 for (i = input, o = output; *i != '\0'; i++) {
147 switch (*i) {
148 case '\\':
149 if (only_char && i[1] != only_char) {
150 break;
151 }
152
153 i++;
154
155 if (*i == '\0') {
156 /* Copy last `\`. */
157 *o = '\\';
158 o++;
159 goto end;
160 }
161 default:
162 break;
163 }
164
165 /* Copy single character. */
166 *o = *i;
167 o++;
168 }
169
170 end:
171 return output;
172 }
173
174 /*
175 * Frees a null-terminated array of strings, including each contained
176 * string.
177 */
178 LTTNG_HIDDEN
179 void strutils_free_null_terminated_array_of_strings(char **array)
180 {
181 char **item;
182
183 if (!array) {
184 return;
185 }
186
187 for (item = array; *item; item++) {
188 free(*item);
189 }
190
191 free(array);
192 }
193
194 /*
195 * Splits the input string `input` using the given delimiter `delim`.
196 *
197 * The return value is an allocated null-terminated array of the
198 * resulting substrings (also allocated). You can free this array and
199 * its content with strutils_free_null_terminated_array_of_strings(). You
200 * can get the number of substrings in it with
201 * strutils_array_of_strings_len().
202 *
203 * Empty substrings are part of the result. For example:
204 *
205 * Input: ,hello,,there,
206 * Result:
207 * ``
208 * `hello`
209 * ``
210 * `there`
211 * ``
212 *
213 * If `escape_delim` is true, then `\,`, where `,` is the delimiter,
214 * escapes the delimiter and is copied as `,` only in the resulting
215 * substring. For example:
216 *
217 * Input: hello\,world,zoom,\,hi
218 * Result:
219 * `hello,world`
220 * `zoom`
221 * `,hi`
222 *
223 * Other characters are not escaped (this is the caller's job if
224 * needed). However they are considering during the parsing, that is,
225 * `\x`, where `x` is any character, is copied as is to the resulting
226 * substring, e.g.:
227 *
228 * Input: hello\,wo\rld\\,zoom\,
229 * Result:
230 * `hello,wo\rld\\`
231 * `zoom,`
232 *
233 * If `escape_delim` is false, nothing at all is escaped, and `delim`,
234 * when found in `input`, is always a delimiter, e.g.:
235 *
236 * Input: hello\,world,zoom,\,hi
237 * Result:
238 * `hello\`
239 * `world`
240 * `zoom`
241 * `\`
242 * `hi`
243 *
244 * Returns NULL if there's an error.
245 */
246 LTTNG_HIDDEN
247 char **strutils_split(const char *input, char delim, bool escape_delim)
248 {
249 size_t at;
250 size_t number_of_substrings = 1;
251 size_t longest_substring_len = 0;
252 const char *s;
253 const char *last;
254 char **substrings = NULL;
255
256 assert(input);
257 assert(!(escape_delim && delim == '\\'));
258 assert(delim != '\0');
259
260 /* First pass: count the number of substrings. */
261 for (s = input, last = input - 1; *s != '\0'; s++) {
262 if (escape_delim && *s == '\\') {
263 /* Ignore following (escaped) character. */
264 s++;
265
266 if (*s == '\0') {
267 break;
268 }
269
270 continue;
271 }
272
273 if (*s == delim) {
274 size_t last_len = s - last - 1;
275 last = s;
276 number_of_substrings++;
277
278 if (last_len > longest_substring_len) {
279 longest_substring_len = last_len;
280 }
281 }
282 }
283
284 if ((s - last - 1) > longest_substring_len) {
285 longest_substring_len = s - last - 1;
286 }
287
288 substrings = calloc(number_of_substrings + 1, sizeof(*substrings));
289 if (!substrings) {
290 goto error;
291 }
292
293 /* Second pass: actually split and copy substrings. */
294 for (at = 0, s = input; at < number_of_substrings; at++) {
295 const char *ss;
296 char *d;
297
298 substrings[at] = zmalloc(longest_substring_len + 1);
299 if (!substrings[at]) {
300 goto error;
301 }
302
303 /*
304 * Copy characters to substring until we find the next
305 * delimiter or the end of the input string.
306 */
307 for (ss = s, d = substrings[at]; *ss != '\0'; ss++) {
308 if (escape_delim && *ss == '\\') {
309 if (ss[1] == delim) {
310 /*
311 * '\' followed by delimiter and
312 * we need to escape this ('\'
313 * won't be part of the
314 * resulting substring).
315 */
316 ss++;
317 *d = *ss;
318 d++;
319 continue;
320 } else {
321 /*
322 * Copy '\' and the following
323 * character.
324 */
325 *d = *ss;
326 d++;
327 ss++;
328
329 if (*ss == '\0') {
330 break;
331 }
332 }
333 } else if (*ss == delim) {
334 /* We're done with this substring. */
335 break;
336 }
337
338 *d = *ss;
339 d++;
340 }
341
342 /* Next substring starts after the last delimiter. */
343 s = ss + 1;
344 }
345
346 goto end;
347
348 error:
349 strutils_free_null_terminated_array_of_strings(substrings);
350 substrings = NULL;
351 end:
352 return substrings;
353 }
354
355 LTTNG_HIDDEN
356 size_t strutils_array_of_strings_len(char * const *array)
357 {
358 char * const *item;
359 size_t count = 0;
360
361 assert(array);
362
363 for (item = array; *item; item++) {
364 count++;
365 }
366
367 return count;
368 }
This page took 0.051981 seconds and 4 git commands to generate.