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