]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/efi-string.c
boot: Add efi_fnmatch
[thirdparty/systemd.git] / src / boot / efi / efi-string.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <stdbool.h>
4 #include <stdint.h>
5
6 #include "efi-string.h"
7
8 #ifdef SD_BOOT
9 # include "util.h"
10 # define xmalloc(n) xallocate_pool(n)
11 #else
12 # include <stdlib.h>
13 # include "macro.h"
14 # define xmalloc(n) ASSERT_SE_PTR(malloc(n))
15 #endif
16
17 /* String functions for both char and char16_t that should behave the same way as their respective
18 * counterpart in userspace. Where it makes sense, these accept NULL and do something sensible whereas
19 * userspace does not allow for this (strlen8(NULL) returns 0 like strlen_ptr(NULL) for example). To make it
20 * easier to tell in code which kind of string they work on, we use 8/16 suffixes. This also makes is easier
21 * to unit test them. */
22
23 #define DEFINE_STRNLEN(type, name) \
24 size_t name(const type *s, size_t n) { \
25 if (!s) \
26 return 0; \
27 \
28 size_t len = 0; \
29 while (len < n && *s) { \
30 s++; \
31 len++; \
32 } \
33 \
34 return len; \
35 }
36
37 DEFINE_STRNLEN(char, strnlen8);
38 DEFINE_STRNLEN(char16_t, strnlen16);
39
40 #define TOLOWER(c) \
41 ({ \
42 typeof(c) _c = (c); \
43 (_c >= 'A' && _c <= 'Z') ? _c + ('a' - 'A') : _c; \
44 })
45
46 #define DEFINE_STRTOLOWER(type, name) \
47 void name(type *s) { \
48 if (!s) \
49 return; \
50 for (; *s; s++) \
51 *s = TOLOWER(*s); \
52 }
53
54 DEFINE_STRTOLOWER(char, strtolower8);
55 DEFINE_STRTOLOWER(char16_t, strtolower16);
56
57 #define DEFINE_STRNCASECMP(type, name, tolower) \
58 int name(const type *s1, const type *s2, size_t n) { \
59 if (!s1 || !s2) \
60 return CMP(s1, s2); \
61 \
62 while (n > 0) { \
63 type c1 = *s1, c2 = *s2; \
64 if (tolower) { \
65 c1 = TOLOWER(c1); \
66 c2 = TOLOWER(c2); \
67 } \
68 if (!c1 || c1 != c2) \
69 return CMP(c1, c2); \
70 \
71 s1++; \
72 s2++; \
73 n--; \
74 } \
75 \
76 return 0; \
77 }
78
79 DEFINE_STRNCASECMP(char, strncmp8, false);
80 DEFINE_STRNCASECMP(char16_t, strncmp16, false);
81 DEFINE_STRNCASECMP(char, strncasecmp8, true);
82 DEFINE_STRNCASECMP(char16_t, strncasecmp16, true);
83
84 #define DEFINE_STRCPY(type, name) \
85 type *name(type * restrict dest, const type * restrict src) { \
86 assert(dest); \
87 type *ret = dest; \
88 \
89 if (!src) { \
90 *dest = '\0'; \
91 return ret; \
92 } \
93 \
94 while (*src) { \
95 *dest = *src; \
96 dest++; \
97 src++; \
98 } \
99 \
100 *dest = '\0'; \
101 return ret; \
102 }
103
104 DEFINE_STRCPY(char, strcpy8);
105 DEFINE_STRCPY(char16_t, strcpy16);
106
107 #define DEFINE_STRCHR(type, name) \
108 type *name(const type *s, type c) { \
109 if (!s) \
110 return NULL; \
111 \
112 while (*s) { \
113 if (*s == c) \
114 return (type *) s; \
115 s++; \
116 } \
117 \
118 return NULL; \
119 }
120
121 DEFINE_STRCHR(char, strchr8);
122 DEFINE_STRCHR(char16_t, strchr16);
123
124 #define DEFINE_STRNDUP(type, name, len_func) \
125 type *name(const type *s, size_t n) { \
126 if (!s) \
127 return NULL; \
128 \
129 size_t len = len_func(s, n); \
130 size_t size = len * sizeof(type); \
131 \
132 type *dup = xmalloc(size + sizeof(type)); \
133 efi_memcpy(dup, s, size); \
134 dup[len] = '\0'; \
135 \
136 return dup; \
137 }
138
139 DEFINE_STRNDUP(char, xstrndup8, strnlen8);
140 DEFINE_STRNDUP(char16_t, xstrndup16, strnlen16);
141
142 /* Patterns are fnmatch-compatible (with reduced feature support). */
143 static bool efi_fnmatch_internal(const char16_t *p, const char16_t *h, int max_depth) {
144 assert(p);
145 assert(h);
146
147 if (max_depth == 0)
148 return false;
149
150 for (;; p++, h++)
151 switch (*p) {
152 case '\0':
153 /* End of pattern. Check that haystack is now empty. */
154 return *h == '\0';
155
156 case '\\':
157 p++;
158 if (*p == '\0' || *p != *h)
159 /* Trailing escape or no match. */
160 return false;
161 break;
162
163 case '?':
164 if (*h == '\0')
165 /* Early end of haystack. */
166 return false;
167 break;
168
169 case '*':
170 /* No need to recurse for consecutive '*'. */
171 while (*p == '*')
172 p++;
173
174 do {
175 /* Try matching haystack with remaining pattern. */
176 if (efi_fnmatch_internal(p, h, max_depth - 1))
177 return true;
178
179 /* Otherwise, we match one char here. */
180 h++;
181 } while (*h != '\0');
182
183 /* End of haystack. Pattern needs to be empty too for a match. */
184 return *p == '\0';
185
186 case '[':
187 if (*h == '\0')
188 /* Early end of haystack. */
189 return false;
190
191 bool first = true, can_range = true, match = false;
192 for (;; first = false) {
193 p++;
194 if (*p == '\0')
195 return false;
196
197 if (*p == '\\') {
198 p++;
199 if (*p == '\0')
200 return false;
201 match |= *p == *h;
202 can_range = true;
203 continue;
204 }
205
206 /* End of set unless it's the first char. */
207 if (*p == ']' && !first)
208 break;
209
210 /* Range pattern if '-' is not first or last in set. */
211 if (*p == '-' && can_range && !first && *(p + 1) != ']') {
212 char16_t low = *(p - 1);
213 p++;
214 if (*p == '\\')
215 p++;
216 if (*p == '\0')
217 return false;
218
219 if (low <= *h && *h <= *p)
220 match = true;
221
222 /* Ranges cannot be chained: [a-c-f] == [-abcf] */
223 can_range = false;
224 continue;
225 }
226
227 if (*p == *h)
228 match = true;
229 can_range = true;
230 }
231
232 if (!match)
233 return false;
234 break;
235
236 default:
237 if (*p != *h)
238 /* Single char mismatch. */
239 return false;
240 }
241 }
242
243 bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) {
244 return efi_fnmatch_internal(pattern, haystack, 32);
245 }
246
247 int efi_memcmp(const void *p1, const void *p2, size_t n) {
248 const uint8_t *up1 = p1, *up2 = p2;
249 int r;
250
251 if (!p1 || !p2)
252 return CMP(p1, p2);
253
254 while (n > 0) {
255 r = CMP(*up1, *up2);
256 if (r != 0)
257 return r;
258
259 up1++;
260 up2++;
261 n--;
262 }
263
264 return 0;
265 }
266
267 void *efi_memcpy(void * restrict dest, const void * restrict src, size_t n) {
268 if (!dest || !src || n == 0)
269 return dest;
270
271 uint8_t *d = dest;
272 const uint8_t *s = src;
273
274 while (n > 0) {
275 *d = *s;
276 d++;
277 s++;
278 n--;
279 }
280
281 return dest;
282 }
283
284 void *efi_memset(void *p, int c, size_t n) {
285 if (!p || n == 0)
286 return p;
287
288 uint8_t *q = p;
289 while (n > 0) {
290 *q = c;
291 q++;
292 n--;
293 }
294
295 return p;
296 }
297
298 #ifdef SD_BOOT
299 # undef memcmp
300 # undef memcpy
301 # undef memset
302 /* Provide the actual implementation for the builtins. To prevent a linker error, we mark memcpy/memset as
303 * weak, because gnu-efi is currently providing them. */
304 __attribute__((alias("efi_memcmp"))) int memcmp(const void *p1, const void *p2, size_t n);
305 __attribute__((weak, alias("efi_memcpy"))) void *memcpy(void * restrict dest, const void * restrict src, size_t n);
306 __attribute__((weak, alias("efi_memset"))) void *memset(void *p, int c, size_t n);
307 #endif