Cleanup: formatting in strutils_star_glob_match explanation
[lttng-ust.git] / liblttng-ust / string-utils.c
CommitLineData
e85a0294
PP
1/*
2 * Copyright (C) 2017 - Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23#define _LGPL_SOURCE
24#include <stdlib.h>
25#include <string.h>
26#include <stdbool.h>
27#include <assert.h>
28
29#include "string-utils.h"
30
31enum star_glob_pattern_type_flags {
32 STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
33 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
34 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
35};
36
37static
38enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
39{
40 enum star_glob_pattern_type_flags ret =
41 STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
42 const char *p;
43
44 assert(pattern);
45
46 for (p = pattern; *p != '\0'; p++) {
47 switch (*p) {
48 case '*':
49 ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
50
51 if (p[1] == '\0') {
52 ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
53 }
54
55 goto end;
56 case '\\':
57 p++;
58
59 if (*p == '\0') {
60 goto end;
61 }
62 break;
63 default:
64 break;
65 }
66 }
67
68end:
69 return ret;
70}
71
72/*
73 * Returns true if `pattern` is a star-only globbing pattern, that is,
74 * it contains at least one non-escaped `*`.
75 */
76bool strutils_is_star_glob_pattern(const char *pattern)
77{
78 return strutils_test_glob_pattern(pattern) &
79 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
80}
81
82/*
83 * Returns true if `pattern` is a globbing pattern with a globbing,
84 * non-escaped star only at its very end.
85 */
86bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
87{
88 return strutils_test_glob_pattern(pattern) &
89 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
90}
91
92static inline
93bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len)
94{
95 return (p - pattern) == pattern_len || *p == '\0';
96}
97
98/*
99 * Globbing matching function with the star feature only (`?` and
100 * character sets are not supported). This matches `candidate` (plain
101 * string) against `pattern`. A literal star can be escaped with `\` in
102 * `pattern`.
103 *
104 * `pattern_len` or `candidate_len` can be greater than the actual
105 * string length of `pattern` or `candidate` if the string is
106 * null-terminated.
107 */
108bool strutils_star_glob_match(const char *pattern, size_t pattern_len,
109 const char *candidate, size_t candidate_len) {
110 const char *retry_c = candidate, *retry_p = pattern, *c, *p;
111 bool got_a_star = false;
112
113retry:
114 c = retry_c;
115 p = retry_p;
116
117 /*
118 * The concept here is to retry a match in the specific case
119 * where we already got a star. The retry position for the
120 * pattern is just after the most recent star, and the retry
121 * position for the candidate is the character following the
122 * last try's first character.
123 *
124 * Example:
125 *
126 * candidate: hi ev every onyx one
d0cba1f1 127 * ^
e85a0294 128 * pattern: hi*every*one
d0cba1f1 129 * ^
e85a0294
PP
130 *
131 * candidate: hi ev every onyx one
d0cba1f1 132 * ^
e85a0294 133 * pattern: hi*every*one
d0cba1f1 134 * ^
e85a0294
PP
135 *
136 * candidate: hi ev every onyx one
d0cba1f1 137 * ^
e85a0294 138 * pattern: hi*every*one
d0cba1f1 139 * ^
e85a0294
PP
140 *
141 * candidate: hi ev every onyx one
d0cba1f1 142 * ^
e85a0294 143 * pattern: hi*every*one
d0cba1f1 144 * ^ MISMATCH
e85a0294
PP
145 *
146 * candidate: hi ev every onyx one
d0cba1f1 147 * ^
e85a0294 148 * pattern: hi*every*one
d0cba1f1 149 * ^
e85a0294
PP
150 *
151 * candidate: hi ev every onyx one
d0cba1f1 152 * ^^
e85a0294 153 * pattern: hi*every*one
d0cba1f1 154 * ^^
e85a0294
PP
155 *
156 * candidate: hi ev every onyx one
d0cba1f1 157 * ^ ^
e85a0294 158 * pattern: hi*every*one
d0cba1f1 159 * ^ ^ MISMATCH
e85a0294
PP
160 *
161 * candidate: hi ev every onyx one
d0cba1f1 162 * ^
e85a0294 163 * pattern: hi*every*one
d0cba1f1 164 * ^ MISMATCH
e85a0294
PP
165 *
166 * candidate: hi ev every onyx one
d0cba1f1 167 * ^
e85a0294 168 * pattern: hi*every*one
d0cba1f1 169 * ^ MISMATCH
e85a0294
PP
170 *
171 * candidate: hi ev every onyx one
d0cba1f1 172 * ^
e85a0294 173 * pattern: hi*every*one
d0cba1f1 174 * ^
e85a0294
PP
175 *
176 * candidate: hi ev every onyx one
d0cba1f1 177 * ^^
e85a0294 178 * pattern: hi*every*one
d0cba1f1 179 * ^^
e85a0294
PP
180 *
181 * candidate: hi ev every onyx one
d0cba1f1 182 * ^ ^
e85a0294 183 * pattern: hi*every*one
d0cba1f1 184 * ^ ^
e85a0294
PP
185 *
186 * candidate: hi ev every onyx one
d0cba1f1 187 * ^ ^
e85a0294 188 * pattern: hi*every*one
d0cba1f1 189 * ^ ^
e85a0294
PP
190 *
191 * candidate: hi ev every onyx one
d0cba1f1 192 * ^ ^
e85a0294 193 * pattern: hi*every*one
d0cba1f1 194 * ^ ^
e85a0294
PP
195 *
196 * candidate: hi ev every onyx one
d0cba1f1 197 * ^
e85a0294 198 * pattern: hi*every*one
d0cba1f1 199 * ^
e85a0294
PP
200 *
201 * candidate: hi ev every onyx one
d0cba1f1 202 * ^
e85a0294 203 * pattern: hi*every*one
d0cba1f1 204 * ^ MISMATCH
e85a0294
PP
205 *
206 * candidate: hi ev every onyx one
d0cba1f1 207 * ^
e85a0294 208 * pattern: hi*every*one
d0cba1f1 209 * ^
e85a0294
PP
210 *
211 * candidate: hi ev every onyx one
d0cba1f1 212 * ^^
e85a0294 213 * pattern: hi*every*one
d0cba1f1 214 * ^^
e85a0294
PP
215 *
216 * candidate: hi ev every onyx one
d0cba1f1 217 * ^ ^
e85a0294 218 * pattern: hi*every*one
d0cba1f1 219 * ^ ^ MISMATCH
e85a0294
PP
220 *
221 * candidate: hi ev every onyx one
d0cba1f1 222 * ^
e85a0294 223 * pattern: hi*every*one
d0cba1f1 224 * ^ MISMATCH
e85a0294
PP
225 *
226 * candidate: hi ev every onyx one
d0cba1f1 227 * ^
e85a0294 228 * pattern: hi*every*one
d0cba1f1 229 * ^ MISMATCH
e85a0294
PP
230 *
231 * candidate: hi ev every onyx one
d0cba1f1 232 * ^
e85a0294 233 * pattern: hi*every*one
d0cba1f1 234 * ^ MISMATCH
e85a0294
PP
235 *
236 * candidate: hi ev every onyx one
d0cba1f1 237 * ^
e85a0294 238 * pattern: hi*every*one
d0cba1f1 239 * ^ MISMATCH
e85a0294
PP
240 *
241 * candidate: hi ev every onyx one
d0cba1f1 242 * ^
e85a0294 243 * pattern: hi*every*one
d0cba1f1 244 * ^
e85a0294
PP
245 *
246 * candidate: hi ev every onyx one
d0cba1f1 247 * ^^
e85a0294 248 * pattern: hi*every*one
d0cba1f1 249 * ^^
e85a0294
PP
250 *
251 * candidate: hi ev every onyx one
d0cba1f1 252 * ^ ^
e85a0294 253 * pattern: hi*every*one
d0cba1f1 254 * ^ ^
e85a0294
PP
255 *
256 * candidate: hi ev every onyx one
d0cba1f1 257 * ^ ^
e85a0294 258 * pattern: hi*every*one
d0cba1f1 259 * ^ ^ SUCCESS
e85a0294
PP
260 */
261 while ((c - candidate) < candidate_len && *c != '\0') {
262 assert(*c);
263
264 if (at_end_of_pattern(p, pattern, pattern_len)) {
265 goto end_of_pattern;
266 }
267
268 switch (*p) {
269 case '*':
270 got_a_star = true;
271
272 /*
273 * Our first try starts at the current candidate
274 * character and after the star in the pattern.
275 */
276 retry_c = c;
277 retry_p = p + 1;
278
279 if (at_end_of_pattern(retry_p, pattern, pattern_len)) {
280 /*
281 * Star at the end of the pattern at
282 * this point: automatic match.
283 */
284 return true;
285 }
286
287 goto retry;
288 case '\\':
289 /* Go to escaped character. */
290 p++;
291
292 /*
293 * Fall through the default case which will
294 * compare the escaped character now.
295 */
296 default:
297 if (at_end_of_pattern(p, pattern, pattern_len) ||
298 *c != *p) {
299end_of_pattern:
300 /* Character mismatch OR end of pattern. */
301 if (!got_a_star) {
302 /*
303 * We didn't get any star yet,
304 * so this first mismatch
305 * automatically makes the whole
306 * test fail.
307 */
308 return false;
309 }
310
311 /*
312 * Next try: next candidate character,
313 * original pattern character (following
314 * the most recent star).
315 */
316 retry_c++;
317 goto retry;
318 }
319 break;
320 }
321
322 /* Next pattern and candidate characters. */
323 c++;
324 p++;
325 }
326
327 /*
328 * We checked every candidate character and we're still in a
329 * success state: the only pattern character allowed to remain
330 * is a star.
331 */
332 if (at_end_of_pattern(p, pattern, pattern_len)) {
333 return true;
334 }
335
336 p++;
337 return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len);
338}
This page took 0.037216 seconds and 4 git commands to generate.