]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/util.c
stub: Ignore the boot counter when looking for .extra.d directory
[thirdparty/systemd.git] / src / boot / efi / util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "device-path-util.h"
4 #include "memory-util-fundamental.h"
5 #include "proto/device-path.h"
6 #include "proto/simple-text-io.h"
7 #include "ticks.h"
8 #include "util.h"
9 #include "version.h"
10
11 EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags) {
12 assert(vendor);
13 assert(name);
14 assert(buf || size == 0);
15
16 flags |= EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS;
17 return RT->SetVariable((char16_t *) name, (EFI_GUID *) vendor, flags, size, (void *) buf);
18 }
19
20 EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags) {
21 assert(vendor);
22 assert(name);
23
24 return efivar_set_raw(vendor, name, value, value ? strsize16(value) : 0, flags);
25 }
26
27 EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t i, uint32_t flags) {
28 assert(vendor);
29 assert(name);
30
31 _cleanup_free_ char16_t *str = xasprintf("%zu", i);
32 return efivar_set(vendor, name, str, flags);
33 }
34
35 EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t value, uint32_t flags) {
36 uint8_t buf[4];
37
38 assert(vendor);
39 assert(name);
40
41 buf[0] = (uint8_t)(value >> 0U & 0xFF);
42 buf[1] = (uint8_t)(value >> 8U & 0xFF);
43 buf[2] = (uint8_t)(value >> 16U & 0xFF);
44 buf[3] = (uint8_t)(value >> 24U & 0xFF);
45
46 return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
47 }
48
49 EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags) {
50 uint8_t buf[8];
51
52 assert(vendor);
53 assert(name);
54
55 buf[0] = (uint8_t)(value >> 0U & 0xFF);
56 buf[1] = (uint8_t)(value >> 8U & 0xFF);
57 buf[2] = (uint8_t)(value >> 16U & 0xFF);
58 buf[3] = (uint8_t)(value >> 24U & 0xFF);
59 buf[4] = (uint8_t)(value >> 32U & 0xFF);
60 buf[5] = (uint8_t)(value >> 40U & 0xFF);
61 buf[6] = (uint8_t)(value >> 48U & 0xFF);
62 buf[7] = (uint8_t)(value >> 56U & 0xFF);
63
64 return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
65 }
66
67 EFI_STATUS efivar_unset(const EFI_GUID *vendor, const char16_t *name, uint32_t flags) {
68 EFI_STATUS err;
69
70 assert(vendor);
71 assert(name);
72
73 /* We could be wiping a non-volatile variable here and the spec makes no guarantees that won't incur
74 * in an extra write (and thus wear out). So check and clear only if needed. */
75 err = efivar_get_raw(vendor, name, NULL, NULL);
76 if (err == EFI_SUCCESS)
77 return efivar_set_raw(vendor, name, NULL, 0, flags);
78
79 return err;
80 }
81
82 EFI_STATUS efivar_get(const EFI_GUID *vendor, const char16_t *name, char16_t **ret) {
83 _cleanup_free_ char16_t *buf = NULL;
84 EFI_STATUS err;
85 char16_t *val;
86 size_t size;
87
88 assert(vendor);
89 assert(name);
90
91 err = efivar_get_raw(vendor, name, (char **) &buf, &size);
92 if (err != EFI_SUCCESS)
93 return err;
94
95 /* Make sure there are no incomplete characters in the buffer */
96 if ((size % sizeof(char16_t)) != 0)
97 return EFI_INVALID_PARAMETER;
98
99 if (!ret)
100 return EFI_SUCCESS;
101
102 /* Return buffer directly if it happens to be NUL terminated already */
103 if (size >= sizeof(char16_t) && buf[size / sizeof(char16_t) - 1] == 0) {
104 *ret = TAKE_PTR(buf);
105 return EFI_SUCCESS;
106 }
107
108 /* Make sure a terminating NUL is available at the end */
109 val = xmalloc(size + sizeof(char16_t));
110
111 memcpy(val, buf, size);
112 val[size / sizeof(char16_t) - 1] = 0; /* NUL terminate */
113
114 *ret = val;
115 return EFI_SUCCESS;
116 }
117
118 EFI_STATUS efivar_get_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t *ret) {
119 _cleanup_free_ char16_t *val = NULL;
120 EFI_STATUS err;
121 uint64_t u;
122
123 assert(vendor);
124 assert(name);
125
126 err = efivar_get(vendor, name, &val);
127 if (err != EFI_SUCCESS)
128 return err;
129
130 if (!parse_number16(val, &u, NULL) || u > SIZE_MAX)
131 return EFI_INVALID_PARAMETER;
132
133 if (ret)
134 *ret = u;
135 return EFI_SUCCESS;
136 }
137
138 EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret) {
139 _cleanup_free_ char *buf = NULL;
140 size_t size;
141 EFI_STATUS err;
142
143 assert(vendor);
144 assert(name);
145
146 err = efivar_get_raw(vendor, name, &buf, &size);
147 if (err != EFI_SUCCESS)
148 return err;
149
150 if (size != sizeof(uint32_t))
151 return EFI_BUFFER_TOO_SMALL;
152
153 if (ret)
154 *ret = (uint32_t) buf[0] << 0U | (uint32_t) buf[1] << 8U | (uint32_t) buf[2] << 16U |
155 (uint32_t) buf[3] << 24U;
156
157 return EFI_SUCCESS;
158 }
159
160 EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret) {
161 _cleanup_free_ char *buf = NULL;
162 size_t size;
163 EFI_STATUS err;
164
165 assert(vendor);
166 assert(name);
167
168 err = efivar_get_raw(vendor, name, &buf, &size);
169 if (err != EFI_SUCCESS)
170 return err;
171
172 if (size != sizeof(uint64_t))
173 return EFI_BUFFER_TOO_SMALL;
174
175 if (ret)
176 *ret = (uint64_t) buf[0] << 0U | (uint64_t) buf[1] << 8U | (uint64_t) buf[2] << 16U |
177 (uint64_t) buf[3] << 24U | (uint64_t) buf[4] << 32U | (uint64_t) buf[5] << 40U |
178 (uint64_t) buf[6] << 48U | (uint64_t) buf[7] << 56U;
179
180 return EFI_SUCCESS;
181 }
182
183 EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, char **ret, size_t *ret_size) {
184 EFI_STATUS err;
185
186 assert(vendor);
187 assert(name);
188
189 size_t size = 0;
190 err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, NULL);
191 if (err != EFI_BUFFER_TOO_SMALL)
192 return err;
193
194 _cleanup_free_ void *buf = xmalloc(size);
195 err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, buf);
196 if (err != EFI_SUCCESS)
197 return err;
198
199 if (ret)
200 *ret = TAKE_PTR(buf);
201 if (ret_size)
202 *ret_size = size;
203
204 return EFI_SUCCESS;
205 }
206
207 EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, bool *ret) {
208 _cleanup_free_ char *b = NULL;
209 size_t size;
210 EFI_STATUS err;
211
212 assert(vendor);
213 assert(name);
214
215 err = efivar_get_raw(vendor, name, &b, &size);
216 if (err != EFI_SUCCESS)
217 return err;
218
219 if (ret)
220 *ret = *b > 0;
221
222 return EFI_SUCCESS;
223 }
224
225 void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec) {
226 assert(vendor);
227 assert(name);
228
229 if (usec == 0)
230 usec = time_usec();
231 if (usec == 0)
232 return;
233
234 _cleanup_free_ char16_t *str = xasprintf("%" PRIu64, usec);
235 efivar_set(vendor, name, str, 0);
236 }
237
238 void convert_efi_path(char16_t *path) {
239 assert(path);
240
241 for (size_t i = 0, fixed = 0;; i++) {
242 /* Fix device path node separator. */
243 path[fixed] = (path[i] == '/') ? '\\' : path[i];
244
245 /* Double '\' is not allowed in EFI file paths. */
246 if (fixed > 0 && path[fixed - 1] == '\\' && path[fixed] == '\\')
247 continue;
248
249 if (path[i] == '\0')
250 break;
251
252 fixed++;
253 }
254 }
255
256 char16_t *xstr8_to_path(const char *str8) {
257 assert(str8);
258 char16_t *path = xstr8_to_16(str8);
259 convert_efi_path(path);
260 return path;
261 }
262
263 static bool shall_be_whitespace(char16_t c) {
264 return c <= 0x20U || c == 0x7FU; /* All control characters + space */
265 }
266
267 char16_t* mangle_stub_cmdline(char16_t *cmdline) {
268 char16_t *p, *q, *e;
269
270 if (!cmdline)
271 return cmdline;
272
273 p = q = cmdline;
274
275 /* Skip initial whitespace */
276 while (shall_be_whitespace(*p))
277 p++;
278
279 /* Turn inner control characters into proper spaces */
280 for (e = p; *p != 0; p++) {
281 if (shall_be_whitespace(*p)) {
282 *(q++) = ' ';
283 continue;
284 }
285
286 *(q++) = *p;
287 e = q; /* remember last non-whitespace char */
288 }
289
290 /* Chop off trailing whitespace */
291 *e = 0;
292 return cmdline;
293 }
294
295 EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) {
296 EFI_STATUS err;
297
298 assert(file);
299 assert(size);
300 assert(buf);
301
302 /* This is a drop-in replacement for EFI_FILE->Read() with the same API behavior.
303 * Some broken firmwares cannot handle large file reads and will instead return
304 * an error. As a workaround, read such files in small chunks.
305 * Note that we cannot just try reading the whole file first on such firmware as
306 * that will permanently break the handle even if it is re-opened.
307 *
308 * https://github.com/systemd/systemd/issues/25911 */
309
310 if (*size == 0)
311 return EFI_SUCCESS;
312
313 size_t read = 0, remaining = *size;
314 while (remaining > 0) {
315 size_t chunk = MIN(1024U * 1024U, remaining);
316
317 err = file->Read(file, &chunk, (uint8_t *) buf + read);
318 if (err != EFI_SUCCESS)
319 return err;
320 if (chunk == 0)
321 /* Caller requested more bytes than are in file. */
322 break;
323
324 assert(chunk <= remaining);
325 read += chunk;
326 remaining -= chunk;
327 }
328
329 *size = read;
330 return EFI_SUCCESS;
331 }
332
333 EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **ret, size_t *ret_size) {
334 _cleanup_(file_closep) EFI_FILE *handle = NULL;
335 _cleanup_free_ char *buf = NULL;
336 EFI_STATUS err;
337
338 assert(dir);
339 assert(name);
340 assert(ret);
341
342 err = dir->Open(dir, &handle, (char16_t*) name, EFI_FILE_MODE_READ, 0ULL);
343 if (err != EFI_SUCCESS)
344 return err;
345
346 if (size == 0) {
347 _cleanup_free_ EFI_FILE_INFO *info = NULL;
348
349 err = get_file_info(handle, &info, NULL);
350 if (err != EFI_SUCCESS)
351 return err;
352
353 size = info->FileSize;
354 }
355
356 if (off > 0) {
357 err = handle->SetPosition(handle, off);
358 if (err != EFI_SUCCESS)
359 return err;
360 }
361
362 /* Allocate some extra bytes to guarantee the result is NUL-terminated for char and char16_t strings. */
363 size_t extra = size % sizeof(char16_t) + sizeof(char16_t);
364
365 buf = xmalloc(size + extra);
366 err = chunked_read(handle, &size, buf);
367 if (err != EFI_SUCCESS)
368 return err;
369
370 /* Note that chunked_read() changes size to reflect the actual bytes read. */
371 memzero(buf + size, extra);
372
373 *ret = TAKE_PTR(buf);
374 if (ret_size)
375 *ret_size = size;
376
377 return err;
378 }
379
380 void print_at(size_t x, size_t y, size_t attr, const char16_t *str) {
381 assert(str);
382 ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
383 ST->ConOut->SetAttribute(ST->ConOut, attr);
384 ST->ConOut->OutputString(ST->ConOut, (char16_t *) str);
385 }
386
387 void clear_screen(size_t attr) {
388 log_wait();
389 ST->ConOut->SetAttribute(ST->ConOut, attr);
390 ST->ConOut->ClearScreen(ST->ConOut);
391 }
392
393 void sort_pointer_array(
394 void **array,
395 size_t n_members,
396 compare_pointer_func_t compare) {
397
398 assert(array || n_members == 0);
399 assert(compare);
400
401 if (n_members <= 1)
402 return;
403
404 for (size_t i = 1; i < n_members; i++) {
405 size_t k;
406 void *entry = array[i];
407
408 for (k = i; k > 0; k--) {
409 if (compare(array[k - 1], entry) <= 0)
410 break;
411
412 array[k] = array[k - 1];
413 }
414
415 array[k] = entry;
416 }
417 }
418
419 EFI_STATUS get_file_info(EFI_FILE *handle, EFI_FILE_INFO **ret, size_t *ret_size) {
420 size_t size = EFI_FILE_INFO_MIN_SIZE;
421 _cleanup_free_ EFI_FILE_INFO *fi = NULL;
422 EFI_STATUS err;
423
424 assert(handle);
425 assert(ret);
426
427 fi = xmalloc(size);
428 err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), &size, fi);
429 if (err == EFI_BUFFER_TOO_SMALL) {
430 free(fi);
431 fi = xmalloc(size); /* GetInfo tells us the required size, let's use that now */
432 err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), &size, fi);
433 }
434
435 if (err != EFI_SUCCESS)
436 return err;
437
438 *ret = TAKE_PTR(fi);
439
440 if (ret_size)
441 *ret_size = size;
442
443 return EFI_SUCCESS;
444 }
445
446 EFI_STATUS readdir(
447 EFI_FILE *handle,
448 EFI_FILE_INFO **buffer,
449 size_t *buffer_size) {
450
451 EFI_STATUS err;
452 size_t sz;
453
454 assert(handle);
455 assert(buffer);
456 assert(buffer_size);
457
458 /* buffer/buffer_size are both in and output parameters. Should be zero-initialized initially, and
459 * the specified buffer needs to be freed by caller, after final use. */
460
461 if (!*buffer) {
462 sz = EFI_FILE_INFO_MIN_SIZE;
463 *buffer = xmalloc(sz);
464 *buffer_size = sz;
465 } else
466 sz = *buffer_size;
467
468 err = handle->Read(handle, &sz, *buffer);
469 if (err == EFI_BUFFER_TOO_SMALL) {
470 free(*buffer);
471 *buffer = xmalloc(sz);
472 *buffer_size = sz;
473 err = handle->Read(handle, &sz, *buffer);
474 }
475 if (err != EFI_SUCCESS)
476 return err;
477
478 if (sz == 0) {
479 /* End of directory */
480 free(*buffer);
481 *buffer = NULL;
482 *buffer_size = 0;
483 }
484
485 return EFI_SUCCESS;
486 }
487
488 bool is_ascii(const char16_t *f) {
489 if (!f)
490 return false;
491
492 for (; *f != 0; f++)
493 if (*f > 127)
494 return false;
495
496 return true;
497 }
498
499 char16_t **strv_free(char16_t **v) {
500 if (!v)
501 return NULL;
502
503 for (char16_t **i = v; *i; i++)
504 free(*i);
505
506 free(v);
507 return NULL;
508 }
509
510 EFI_STATUS open_directory(
511 EFI_FILE *root,
512 const char16_t *path,
513 EFI_FILE **ret) {
514
515 _cleanup_(file_closep) EFI_FILE *dir = NULL;
516 _cleanup_free_ EFI_FILE_INFO *file_info = NULL;
517 EFI_STATUS err;
518
519 assert(root);
520
521 /* Opens a file, and then verifies it is actually a directory */
522
523 err = root->Open(root, &dir, (char16_t *) path, EFI_FILE_MODE_READ, 0);
524 if (err != EFI_SUCCESS)
525 return err;
526
527 err = get_file_info(dir, &file_info, NULL);
528 if (err != EFI_SUCCESS)
529 return err;
530 if (!FLAGS_SET(file_info->Attribute, EFI_FILE_DIRECTORY))
531 return EFI_LOAD_ERROR;
532
533 *ret = TAKE_PTR(dir);
534 return EFI_SUCCESS;
535 }
536
537 uint64_t get_os_indications_supported(void) {
538 uint64_t osind;
539 EFI_STATUS err;
540
541 /* Returns the supported OS indications. If we can't acquire it, returns a zeroed out mask, i.e. no
542 * supported features. */
543
544 err = efivar_get_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndicationsSupported", &osind);
545 if (err != EFI_SUCCESS)
546 return 0;
547
548 return osind;
549 }
550
551 __attribute__((noinline)) void notify_debugger(const char *identity, volatile bool wait) {
552 #ifdef EFI_DEBUG
553 printf("%s@%p %s\n", identity, __executable_start, GIT_VERSION);
554 if (wait)
555 printf("Waiting for debugger to attach...\n");
556
557 /* This is a poor programmer's breakpoint to wait until a debugger
558 * has attached to us. Just "set variable wait = 0" or "return" to continue. */
559 while (wait)
560 /* Prefer asm based stalling so that gdb has a source location to present. */
561 # if defined(__i386__) || defined(__x86_64__)
562 asm volatile("pause");
563 # elif defined(__aarch64__)
564 asm volatile("wfi");
565 # else
566 BS->Stall(5000);
567 # endif
568 #endif
569 }
570
571 #if defined(__i386__) || defined(__x86_64__)
572 static uint8_t inb(uint16_t port) {
573 uint8_t value;
574 asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
575 return value;
576 }
577
578 static void outb(uint16_t port, uint8_t value) {
579 asm volatile("outb %0, %1" : : "a"(value), "Nd"(port));
580 }
581
582 void beep(unsigned beep_count) {
583 enum {
584 PITCH = 500,
585 BEEP_DURATION_USEC = 100 * 1000,
586 WAIT_DURATION_USEC = 400 * 1000,
587
588 PIT_FREQUENCY = 0x1234dd,
589 SPEAKER_CONTROL_PORT = 0x61,
590 SPEAKER_ON_MASK = 0x03,
591 TIMER_PORT_MAGIC = 0xB6,
592 TIMER_CONTROL_PORT = 0x43,
593 TIMER_CONTROL2_PORT = 0x42,
594 };
595
596 /* Set frequency. */
597 uint32_t counter = PIT_FREQUENCY / PITCH;
598 outb(TIMER_CONTROL_PORT, TIMER_PORT_MAGIC);
599 outb(TIMER_CONTROL2_PORT, counter & 0xFF);
600 outb(TIMER_CONTROL2_PORT, (counter >> 8) & 0xFF);
601
602 uint8_t value = inb(SPEAKER_CONTROL_PORT);
603
604 while (beep_count > 0) {
605 /* Turn speaker on. */
606 value |= SPEAKER_ON_MASK;
607 outb(SPEAKER_CONTROL_PORT, value);
608
609 BS->Stall(BEEP_DURATION_USEC);
610
611 /* Turn speaker off. */
612 value &= ~SPEAKER_ON_MASK;
613 outb(SPEAKER_CONTROL_PORT, value);
614
615 beep_count--;
616 if (beep_count > 0)
617 BS->Stall(WAIT_DURATION_USEC);
618 }
619 }
620 #endif
621
622 EFI_STATUS open_volume(EFI_HANDLE device, EFI_FILE **ret_file) {
623 EFI_STATUS err;
624 EFI_FILE *file;
625 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *volume;
626
627 assert(ret_file);
628
629 err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), (void **) &volume);
630 if (err != EFI_SUCCESS)
631 return err;
632
633 err = volume->OpenVolume(volume, &file);
634 if (err != EFI_SUCCESS)
635 return err;
636
637 *ret_file = file;
638 return EFI_SUCCESS;
639 }
640
641 void *find_configuration_table(const EFI_GUID *guid) {
642 for (size_t i = 0; i < ST->NumberOfTableEntries; i++)
643 if (efi_guid_equal(&ST->ConfigurationTable[i].VendorGuid, guid))
644 return ST->ConfigurationTable[i].VendorTable;
645
646 return NULL;
647 }
648
649 static void remove_boot_count(char16_t *path) {
650 char16_t *prefix_end;
651 const char16_t *tail;
652 uint64_t ignored;
653
654 assert(path);
655
656 prefix_end = strchr16(path, '+');
657 if (!prefix_end)
658 return;
659
660 tail = prefix_end + 1;
661
662 if (!parse_number16(tail, &ignored, &tail))
663 return;
664
665 if (*tail == '-') {
666 ++tail;
667 if (!parse_number16(tail, &ignored, &tail))
668 return;
669 }
670
671 if (!IN_SET(*tail, '\0', '.'))
672 return;
673
674 strcpy16(prefix_end, tail);
675 }
676
677 char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
678 if (!file_path)
679 return NULL;
680
681 /* A device path is allowed to have more than one file path node. If that is the case they are
682 * supposed to be concatenated. Unfortunately, the device path to text protocol simply converts the
683 * nodes individually and then combines those with the usual '/' for device path nodes. But this does
684 * not create a legal EFI file path that the file protocol can use. */
685
686 /* Make sure we really only got file paths. */
687 for (const EFI_DEVICE_PATH *node = file_path; !device_path_is_end(node);
688 node = device_path_next_node(node))
689 if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
690 return NULL;
691
692 _cleanup_free_ char16_t *file_path_str = NULL;
693 if (device_path_to_str(file_path, &file_path_str) != EFI_SUCCESS)
694 return NULL;
695
696 convert_efi_path(file_path_str);
697 remove_boot_count(file_path_str);
698 return xasprintf("%ls.extra.d", file_path_str);
699 }