Commit | Line | Data |
---|---|---|
b7cdc182 | 1 | /* SPDX-License-Identifier: (GPL-2.0-only or LGPL-2.1-only) |
231b5333 | 2 | * |
9f36eaed | 3 | * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com> |
231b5333 PP |
4 | */ |
5 | ||
6 | #include <linux/types.h> | |
c190d76e | 7 | #include <wrapper/compiler_attributes.h> |
231b5333 | 8 | |
2df37e95 | 9 | #include <lttng/string-utils.h> |
231b5333 PP |
10 | |
11 | enum star_glob_pattern_type_flags { | |
12 | STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0, | |
13 | STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = (1U << 0), | |
14 | STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = (1U << 1), | |
15 | }; | |
16 | ||
17 | static | |
18 | enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern) | |
19 | { | |
20 | enum star_glob_pattern_type_flags ret = | |
21 | STAR_GLOB_PATTERN_TYPE_FLAG_NONE; | |
22 | const char *p; | |
23 | ||
24 | for (p = pattern; *p != '\0'; p++) { | |
25 | switch (*p) { | |
26 | case '*': | |
27 | ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN; | |
28 | ||
29 | if (p[1] == '\0') { | |
30 | ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY; | |
31 | } | |
32 | goto end; | |
33 | case '\\': | |
34 | p++; | |
35 | ||
36 | if (*p == '\0') { | |
37 | goto end; | |
38 | } | |
39 | break; | |
40 | default: | |
41 | break; | |
42 | } | |
43 | } | |
44 | ||
45 | end: | |
46 | return ret; | |
47 | } | |
48 | ||
49 | /* | |
50 | * Returns true if `pattern` is a star-only globbing pattern, that is, | |
51 | * it contains at least one non-escaped `*`. | |
52 | */ | |
53 | bool strutils_is_star_glob_pattern(const char *pattern) | |
54 | { | |
55 | return strutils_test_glob_pattern(pattern) & | |
56 | STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN; | |
57 | } | |
58 | ||
59 | /* | |
60 | * Returns true if `pattern` is a globbing pattern with a globbing, | |
61 | * non-escaped star only at its very end. | |
62 | */ | |
63 | bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern) | |
64 | { | |
65 | return strutils_test_glob_pattern(pattern) & | |
66 | STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY; | |
67 | } | |
68 | ||
69 | struct string_with_len { | |
70 | const char *str; | |
71 | size_t len; | |
72 | }; | |
73 | ||
74 | static | |
75 | char string_get_char_at_cb(size_t at, void *data) | |
76 | { | |
77 | struct string_with_len *string_with_len = data; | |
78 | ||
79 | if (at >= string_with_len->len) { | |
80 | return '\0'; | |
81 | } | |
82 | ||
83 | return string_with_len->str[at]; | |
84 | } | |
85 | ||
86 | /* | |
87 | * Globbing matching function with the star feature only (`?` and | |
88 | * character sets are not supported). This matches `candidate` (plain | |
89 | * string) against `pattern`. A literal star can be escaped with `\` in | |
90 | * `pattern`. | |
91 | * | |
92 | * `pattern_len` or `candidate_len` can be greater than the actual | |
93 | * string length of `pattern` or `candidate` if the string is | |
94 | * null-terminated. | |
95 | */ | |
96 | bool strutils_star_glob_match(const char *pattern, size_t pattern_len, | |
97 | const char *candidate, size_t candidate_len) { | |
98 | struct string_with_len pattern_with_len = { | |
99 | pattern, pattern_len | |
100 | }; | |
101 | struct string_with_len candidate_with_len = { | |
102 | candidate, candidate_len | |
103 | }; | |
104 | ||
105 | return strutils_star_glob_match_char_cb(string_get_char_at_cb, | |
106 | &pattern_with_len, string_get_char_at_cb, | |
107 | &candidate_with_len); | |
108 | } | |
109 | ||
110 | bool strutils_star_glob_match_char_cb( | |
111 | strutils_get_char_at_cb pattern_get_char_at_cb, | |
112 | void *pattern_get_char_at_cb_data, | |
113 | strutils_get_char_at_cb candidate_get_char_at_cb, | |
114 | void *candidate_get_char_at_cb_data) | |
115 | { | |
116 | size_t retry_p_at = 0, retry_c_at = 0, c_at, p_at; | |
117 | char c, p, prev_p; | |
118 | bool got_a_star = false; | |
119 | ||
120 | retry: | |
121 | c_at = retry_c_at; | |
122 | c = candidate_get_char_at_cb(c_at, candidate_get_char_at_cb_data); | |
123 | p_at = retry_p_at; | |
124 | p = pattern_get_char_at_cb(p_at, pattern_get_char_at_cb_data); | |
125 | ||
126 | /* | |
127 | * The concept here is to retry a match in the specific case | |
128 | * where we already got a star. The retry position for the | |
129 | * pattern is just after the most recent star, and the retry | |
130 | * position for the candidate is the character following the | |
131 | * last try's first character. | |
132 | * | |
133 | * Example: | |
134 | * | |
135 | * candidate: hi ev every onyx one | |
e755fa6d | 136 | * ^ |
231b5333 | 137 | * pattern: hi*every*one |
e755fa6d | 138 | * ^ |
231b5333 PP |
139 | * |
140 | * candidate: hi ev every onyx one | |
e755fa6d | 141 | * ^ |
231b5333 | 142 | * pattern: hi*every*one |
e755fa6d | 143 | * ^ |
231b5333 PP |
144 | * |
145 | * candidate: hi ev every onyx one | |
e755fa6d | 146 | * ^ |
231b5333 | 147 | * pattern: hi*every*one |
e755fa6d | 148 | * ^ |
231b5333 PP |
149 | * |
150 | * candidate: hi ev every onyx one | |
e755fa6d | 151 | * ^ |
231b5333 | 152 | * pattern: hi*every*one |
e755fa6d | 153 | * ^ MISMATCH |
231b5333 PP |
154 | * |
155 | * candidate: hi ev every onyx one | |
e755fa6d | 156 | * ^ |
231b5333 | 157 | * pattern: hi*every*one |
e755fa6d | 158 | * ^ |
231b5333 PP |
159 | * |
160 | * candidate: hi ev every onyx one | |
e755fa6d | 161 | * ^^ |
231b5333 | 162 | * pattern: hi*every*one |
e755fa6d | 163 | * ^^ |
231b5333 PP |
164 | * |
165 | * candidate: hi ev every onyx one | |
e755fa6d | 166 | * ^ ^ |
231b5333 | 167 | * pattern: hi*every*one |
e755fa6d | 168 | * ^ ^ MISMATCH |
231b5333 PP |
169 | * |
170 | * candidate: hi ev every onyx one | |
e755fa6d | 171 | * ^ |
231b5333 | 172 | * pattern: hi*every*one |
e755fa6d | 173 | * ^ MISMATCH |
231b5333 PP |
174 | * |
175 | * candidate: hi ev every onyx one | |
e755fa6d | 176 | * ^ |
231b5333 | 177 | * pattern: hi*every*one |
e755fa6d | 178 | * ^ MISMATCH |
231b5333 PP |
179 | * |
180 | * candidate: hi ev every onyx one | |
e755fa6d | 181 | * ^ |
231b5333 | 182 | * pattern: hi*every*one |
e755fa6d | 183 | * ^ |
231b5333 PP |
184 | * |
185 | * candidate: hi ev every onyx one | |
e755fa6d | 186 | * ^^ |
231b5333 | 187 | * pattern: hi*every*one |
e755fa6d | 188 | * ^^ |
231b5333 PP |
189 | * |
190 | * candidate: hi ev every onyx one | |
e755fa6d | 191 | * ^ ^ |
231b5333 | 192 | * pattern: hi*every*one |
e755fa6d | 193 | * ^ ^ |
231b5333 PP |
194 | * |
195 | * candidate: hi ev every onyx one | |
e755fa6d | 196 | * ^ ^ |
231b5333 | 197 | * pattern: hi*every*one |
e755fa6d | 198 | * ^ ^ |
231b5333 PP |
199 | * |
200 | * candidate: hi ev every onyx one | |
e755fa6d | 201 | * ^ ^ |
231b5333 | 202 | * pattern: hi*every*one |
e755fa6d | 203 | * ^ ^ |
231b5333 PP |
204 | * |
205 | * candidate: hi ev every onyx one | |
e755fa6d | 206 | * ^ |
231b5333 | 207 | * pattern: hi*every*one |
e755fa6d | 208 | * ^ |
231b5333 PP |
209 | * |
210 | * candidate: hi ev every onyx one | |
e755fa6d | 211 | * ^ |
231b5333 | 212 | * pattern: hi*every*one |
e755fa6d | 213 | * ^ MISMATCH |
231b5333 PP |
214 | * |
215 | * candidate: hi ev every onyx one | |
e755fa6d | 216 | * ^ |
231b5333 | 217 | * pattern: hi*every*one |
e755fa6d | 218 | * ^ |
231b5333 PP |
219 | * |
220 | * candidate: hi ev every onyx one | |
e755fa6d | 221 | * ^^ |
231b5333 | 222 | * pattern: hi*every*one |
e755fa6d | 223 | * ^^ |
231b5333 PP |
224 | * |
225 | * candidate: hi ev every onyx one | |
e755fa6d | 226 | * ^ ^ |
231b5333 | 227 | * pattern: hi*every*one |
e755fa6d | 228 | * ^ ^ MISMATCH |
231b5333 PP |
229 | * |
230 | * candidate: hi ev every onyx one | |
e755fa6d | 231 | * ^ |
231b5333 | 232 | * pattern: hi*every*one |
e755fa6d | 233 | * ^ MISMATCH |
231b5333 PP |
234 | * |
235 | * candidate: hi ev every onyx one | |
e755fa6d | 236 | * ^ |
231b5333 | 237 | * pattern: hi*every*one |
e755fa6d | 238 | * ^ MISMATCH |
231b5333 PP |
239 | * |
240 | * candidate: hi ev every onyx one | |
e755fa6d | 241 | * ^ |
231b5333 | 242 | * pattern: hi*every*one |
e755fa6d | 243 | * ^ MISMATCH |
231b5333 PP |
244 | * |
245 | * candidate: hi ev every onyx one | |
e755fa6d | 246 | * ^ |
231b5333 | 247 | * pattern: hi*every*one |
e755fa6d | 248 | * ^ MISMATCH |
231b5333 PP |
249 | * |
250 | * candidate: hi ev every onyx one | |
e755fa6d | 251 | * ^ |
231b5333 | 252 | * pattern: hi*every*one |
e755fa6d | 253 | * ^ |
231b5333 PP |
254 | * |
255 | * candidate: hi ev every onyx one | |
e755fa6d | 256 | * ^^ |
231b5333 | 257 | * pattern: hi*every*one |
e755fa6d | 258 | * ^^ |
231b5333 PP |
259 | * |
260 | * candidate: hi ev every onyx one | |
e755fa6d | 261 | * ^ ^ |
231b5333 | 262 | * pattern: hi*every*one |
e755fa6d | 263 | * ^ ^ |
231b5333 PP |
264 | * |
265 | * candidate: hi ev every onyx one | |
e755fa6d | 266 | * ^ ^ |
231b5333 | 267 | * pattern: hi*every*one |
e755fa6d | 268 | * ^ ^ SUCCESS |
231b5333 PP |
269 | */ |
270 | while (c != '\0') { | |
271 | if (p == '\0') { | |
272 | goto end_of_pattern; | |
273 | } | |
274 | ||
275 | switch (p) { | |
276 | case '*': | |
277 | { | |
278 | char retry_p; | |
279 | got_a_star = true; | |
280 | ||
281 | /* | |
282 | * Our first try starts at the current candidate | |
283 | * character and after the star in the pattern. | |
284 | */ | |
285 | retry_c_at = c_at; | |
286 | retry_p_at = p_at + 1; | |
287 | retry_p = pattern_get_char_at_cb(retry_p_at, | |
288 | pattern_get_char_at_cb_data); | |
289 | ||
290 | if (retry_p == '\0') { | |
291 | /* | |
292 | * Star at the end of the pattern at | |
293 | * this point: automatic match. | |
294 | */ | |
295 | return true; | |
296 | } | |
297 | ||
298 | goto retry; | |
299 | } | |
300 | case '\\': | |
301 | /* Go to escaped character. */ | |
302 | p_at++; | |
303 | p = pattern_get_char_at_cb(p_at, | |
304 | pattern_get_char_at_cb_data); | |
305 | ||
c190d76e | 306 | lttng_fallthrough; |
4e47802a | 307 | default: |
231b5333 | 308 | /* |
4e47802a MD |
309 | * Default case which will compare the escaped |
310 | * character now. | |
231b5333 | 311 | */ |
231b5333 PP |
312 | if (p == '\0' || c != p) { |
313 | end_of_pattern: | |
314 | /* Character mismatch OR end of pattern. */ | |
315 | if (!got_a_star) { | |
316 | /* | |
317 | * We didn't get any star yet, | |
318 | * so this first mismatch | |
319 | * automatically makes the whole | |
320 | * test fail. | |
321 | */ | |
322 | return false; | |
323 | } | |
324 | ||
325 | /* | |
326 | * Next try: next candidate character, | |
327 | * original pattern character (following | |
328 | * the most recent star). | |
329 | */ | |
330 | retry_c_at++; | |
331 | goto retry; | |
332 | } | |
333 | break; | |
334 | } | |
335 | ||
336 | /* Next pattern and candidate characters. */ | |
337 | c_at++; | |
338 | c = candidate_get_char_at_cb(c_at, | |
339 | candidate_get_char_at_cb_data); | |
340 | p_at++; | |
341 | p = pattern_get_char_at_cb(p_at, pattern_get_char_at_cb_data); | |
342 | } | |
343 | ||
344 | /* | |
345 | * We checked every candidate character and we're still in a | |
346 | * success state: the only pattern character allowed to remain | |
347 | * is a star. | |
348 | */ | |
349 | if (p == '\0') { | |
350 | return true; | |
351 | } | |
352 | ||
353 | prev_p = p; | |
354 | p_at++; | |
355 | p = pattern_get_char_at_cb(p_at, pattern_get_char_at_cb_data); | |
356 | return prev_p == '*' && p == '\0'; | |
357 | } |