2 * Simple UEFI boot loader which executes configured EFI images, where the
3 * default entry is selected by a configured pattern (glob) or an on-screen
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * Copyright (C) 2012 Kay Sievers <kay.sievers@vrfy.org>
17 * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
19 * "Any intelligent fool can make things bigger, more complex, and more violent."
27 * Allocated random UUID, intended to be shared across tools that implement
28 * the (ESP)\loader\entries\<vendor>-<revision>.conf convention and the
29 * associated EFI variables.
31 static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} };
42 enum loader_type type;
45 BOOLEAN no_autoselect;
49 ConfigEntry **entries;
52 INTN idx_default_efivar;
54 UINTN timeout_sec_config;
55 INTN timeout_sec_efivar;
56 CHAR16 *entry_default_pattern;
61 static UINT64 ticks_read() {
63 __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
67 static UINT64 ticks_read() { return 0; }
70 static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) {
73 flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
75 flags |= EFI_VARIABLE_NON_VOLATILE;
77 return uefi_call_wrapper(RT->SetVariable, 5, name, &loader_guid, flags,
78 value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, value);
81 static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
86 size = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
87 val = AllocatePool(size);
89 return EFI_OUT_OF_RESOURCES;
91 err = uefi_call_wrapper(RT->GetVariable, 5, name, &loader_guid, NULL, &size, val);
92 if (EFI_ERROR(err) == 0)
100 static EFI_STATUS efivar_set_int(CHAR16 *name, INTN i, BOOLEAN persistent) {
103 SPrint(str, 32, L"%d", i);
104 return efivar_set(name, str, persistent);
107 static EFI_STATUS efivar_get_int(CHAR16 *name, INTN *i) {
111 err = efivar_get(name, &val);
112 if (EFI_ERROR(err) == 0) {
119 static VOID efivar_set_ticks(CHAR16 *name, UINT64 ticks) {
123 ticks = ticks_read();
127 SPrint(str, 32, L"%ld", ticks ? ticks : ticks_read());
128 efivar_set(name, str, FALSE);
131 static void cursor_left(UINTN *cursor, UINTN *first)
135 else if ((*first) > 0)
139 static void cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len)
141 if ((*cursor)+2 < x_max)
143 else if ((*first) + (*cursor) < len)
147 static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) {
159 size = StrLen(line_in) + 1024;
160 line = AllocatePool(size * sizeof(CHAR16));
161 StrCpy(line, line_in);
163 print = AllocatePool(x_max * sizeof(CHAR16));
165 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
180 CopyMem(print, line + first, i * sizeof(CHAR16));
184 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
185 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
186 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
188 uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
189 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
193 switch (key.ScanCode) {
203 if (cursor >= x_max) {
205 first = len - (x_max-2);
209 while((first + cursor) && line[first + cursor] == ' ')
210 cursor_left(&cursor, &first);
211 while((first + cursor) && line[first + cursor] != ' ')
212 cursor_left(&cursor, &first);
213 while((first + cursor) && line[first + cursor] == ' ')
214 cursor_left(&cursor, &first);
215 if (first + cursor != len && first + cursor)
216 cursor_right(&cursor, &first, x_max, len);
217 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
220 while(line[first + cursor] && line[first + cursor] == ' ')
221 cursor_right(&cursor, &first, x_max, len);
222 while(line[first + cursor] && line[first + cursor] != ' ')
223 cursor_right(&cursor, &first, x_max, len);
224 while(line[first + cursor] && line[first + cursor] == ' ')
225 cursor_right(&cursor, &first, x_max, len);
226 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
229 if (first + cursor == len)
231 cursor_right(&cursor, &first, x_max, len);
232 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
235 cursor_left(&cursor, &first);
236 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
241 if (first + cursor == len)
243 for (i = first + cursor; i < len; i++)
250 switch (key.UnicodeChar) {
252 case CHAR_CARRIAGE_RETURN:
253 if (StrCmp(line, line_in) != 0) {
263 if (first == 0 && cursor == 0)
265 for (i = first + cursor-1; i < len; i++)
270 if (cursor > 0 || first == 0)
272 /* show full line if it fits */
278 /* jump left to see what we delete */
289 case 0x80 ... 0xffff:
292 for (i = len; i > first + cursor; i--)
294 line[first + cursor] = key.UnicodeChar;
297 if (cursor+2 < x_max)
299 else if (first + cursor < len)
305 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
310 static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry) {
314 INTN idx_highlight_prev;
327 BOOLEAN exit = FALSE;
330 uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
331 uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
332 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
333 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
335 err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max);
336 if (EFI_ERROR(err)) {
341 /* we check 10 times per second for a keystroke */
342 if (config->timeout_sec > 0)
343 timeout_remain = config->timeout_sec * 10;
347 idx_highlight = config->idx_default;
348 idx_highlight_prev = 0;
350 visible_max = y_max - 2;
352 if (config->idx_default >= visible_max)
353 idx_first = config->idx_default-1;
357 idx_last = idx_first + visible_max-1;
362 /* length of higlighted selector bar */
364 for (i = 0; i < config->entry_count; i++) {
367 entry_len = StrLen(config->entries[i]->title);
368 if (line_width < entry_len)
369 line_width = entry_len;
371 if (line_width > x_max)
374 /* menu entries title lines */
375 lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
376 for (i = 0; i < config->entry_count; i++)
377 lines[i] = PoolPrint(L" %-.*s ", line_width, config->entries[i]->title);
380 clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
381 for (i = 0; i < x_max; i++)
389 for (i = 0; i < config->entry_count; i++) {
390 if (i < idx_first || i > idx_last)
392 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
393 if (i == idx_highlight)
394 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
395 EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
397 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
398 EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
399 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]);
400 if (i == config->idx_default_efivar) {
401 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
402 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
406 } else if (highlight) {
407 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
408 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
409 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
410 if (idx_highlight_prev == config->idx_default_efivar) {
411 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
412 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
415 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
416 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
417 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]);
418 if (idx_highlight == config->idx_default_efivar) {
419 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
420 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
425 if (timeout_remain > 0) {
427 status = PoolPrint(L"Boot in %d seconds.", (timeout_remain + 5) / 10);
430 /* print status at last line of screen */
432 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
433 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
434 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
435 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + StrLen(status));
438 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
439 if (err == EFI_NOT_READY) {
442 if (timeout_remain == 0) {
446 if (timeout_remain > 0) {
447 uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
451 uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
456 /* clear status after keystroke */
460 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
461 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
462 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
465 idx_highlight_prev = idx_highlight;
467 switch (key.ScanCode) {
469 if (idx_highlight > 0)
473 if (idx_highlight < config->entry_count-1)
477 if (idx_highlight > 0) {
483 if (idx_highlight < config->entry_count-1) {
485 idx_highlight = config->entry_count-1;
489 idx_highlight -= visible_max;
490 if (idx_highlight < 0)
494 idx_highlight += visible_max;
495 if (idx_highlight > config->entry_count-1)
496 idx_highlight = config->entry_count-1;
499 status = StrDuplicate(L"(d)efault, (+/-)timeout, (e)dit, (v)ersion (q)uit");
503 if (idx_highlight > idx_last) {
504 idx_last = idx_highlight;
505 idx_first = 1 + idx_highlight - visible_max;
508 if (idx_highlight < idx_first) {
509 idx_first = idx_highlight;
510 idx_last = idx_highlight + visible_max-1;
513 idx_last = idx_first + visible_max-1;
515 if (!refresh && idx_highlight != idx_highlight_prev)
518 switch (key.UnicodeChar) {
520 case CHAR_CARRIAGE_RETURN:
528 if (config->idx_default_efivar != idx_highlight) {
529 /* store the selected entry in a persistent EFI variable */
530 efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE);
531 config->idx_default_efivar = idx_highlight;
532 status = StrDuplicate(L"Default boot entry permanently stored.");
534 /* clear the default entry EFI variable */
535 efivar_set(L"LoaderEntryDefault", NULL, TRUE);
536 config->idx_default_efivar = -1;
537 status = StrDuplicate(L"Default boot entry cleared.");
542 if (config->timeout_sec_efivar > 0) {
543 config->timeout_sec_efivar--;
544 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
545 if (config->timeout_sec_efivar > 0)
546 status = PoolPrint(L"Menu timeout of %d sec permanently stored.",
547 config->timeout_sec_efivar);
549 status = StrDuplicate(L"Menu permanently disabled. "
550 "Hold down key at bootup to show menu.");
551 } else if (config->timeout_sec_efivar <= 0){
552 config->timeout_sec_efivar = -1;
553 efivar_set(L"LoaderConfigTimeout", NULL, TRUE);
554 if (config->timeout_sec_config > 0)
555 status = PoolPrint(L"Menu timeout of %d sec defined by configuration file.",
556 config->timeout_sec_config);
558 status = StrDuplicate(L"Menu permanently disabled. "
559 "Hold down key at bootup to show menu.");
563 if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0)
564 config->timeout_sec_efivar++;
565 config->timeout_sec_efivar++;
566 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
567 if (config->timeout_sec_efivar > 0)
568 status = PoolPrint(L"Menu timeout of %d sec permanently stored.",
569 config->timeout_sec_efivar);
571 status = StrDuplicate(L"Menu permanently disabled. "
572 "Hold down key at bootup to show menu.");
575 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
576 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
577 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
578 if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max, y_max-1))
580 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
581 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
584 status = PoolPrint(L"gummiboot %d, UEFI %d.%02d, %s %d.%02d",
586 ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff,
587 ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
592 for (i = 0; i < config->entry_count; i++)
596 *chosen_entry = config->entries[idx_highlight];
598 uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK);
599 uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
603 static VOID config_add_entry(Config *config, ConfigEntry *entry) {
604 if ((config->entry_count & 15) == 0) {
607 i = config->entry_count + 16;
608 if (config->entry_count == 0)
609 config->entries = AllocatePool(sizeof(VOID *) * i);
611 config->entries = ReallocatePool(config->entries,
612 sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i);
614 config->entries[config->entry_count++] = entry;
617 static VOID config_entry_free(ConfigEntry *entry) {
618 FreePool(entry->title);
619 FreePool(entry->loader);
620 FreePool(entry->options);
623 static BOOLEAN is_digit(CHAR16 c)
625 return (c >= '0') && (c <= '9');
628 static UINTN c_order(CHAR16 c)
634 else if ((c >= 'a') && (c <= 'z'))
640 static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
648 while ((*s2 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
651 order = c_order(*s1) - c_order(*s2);
664 while (is_digit(*s1) && is_digit(*s2)) {
680 return StrCmp(os1, os2);
683 static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) {
690 else if ((stra[0] & 0xe0) == 0xc0)
692 else if ((stra[0] & 0xf0) == 0xe0)
694 else if ((stra[0] & 0xf8) == 0xf0)
696 else if ((stra[0] & 0xfc) == 0xf8)
698 else if ((stra[0] & 0xfe) == 0xfc)
708 unichar = stra[0] & 0x1f;
711 unichar = stra[0] & 0x0f;
714 unichar = stra[0] & 0x07;
717 unichar = stra[0] & 0x03;
720 unichar = stra[0] & 0x01;
724 for (i = 1; i < len; i++) {
725 if ((stra[i] & 0xc0) != 0x80)
728 unichar |= stra[i] & 0x3f;
735 CHAR16 *stra_to_str(CHAR8 *stra) {
742 str = AllocatePool((len + 1) * sizeof(CHAR16));
749 utf8len = utf8_to_16(stra + i, str + strlen);
751 /* invalid utf8 sequence, skip the garbage */
763 CHAR16 *stra_to_path(CHAR8 *stra) {
770 str = AllocatePool((len + 2) * sizeof(CHAR16));
778 utf8len = utf8_to_16(stra + i, str + strlen);
780 /* invalid utf8 sequence, skip the garbage */
785 if (str[strlen] == '/')
787 if (str[strlen] == '\\' && str[strlen-1] == '\\') {
788 /* skip double slashes */
800 static CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
808 static CHAR8 *line_get_key_value(CHAR8 *line, CHAR8 **key_ret, CHAR8 **value_ret) {
816 while (*next && !strchra((CHAR8 *)"\n\r", *next))
820 linelen = next - line;
826 while (*next && strchra((CHAR8 *)"\n\r", *next))
829 /* trailing whitespace */
830 while (linelen && strchra((CHAR8 *)" \t", line[linelen-1]))
832 line[linelen] = '\0';
834 /* leading whitespace */
835 while (strchra((CHAR8 *)" \t", *line))
844 /* split key/value */
846 while (*value && !strchra((CHAR8 *)" \t", *value))
852 while (*value && strchra((CHAR8 *)" \t", *value))
860 static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
865 while ((line = line_get_key_value(line, &key, &value))) {
866 if (strcmpa((CHAR8 *)"timeout", key) == 0) {
869 s = stra_to_str(value);
870 config->timeout_sec_config = Atoi(s);
871 config->timeout_sec = config->timeout_sec_config;
875 if (strcmpa((CHAR8 *)"default", key) == 0) {
876 config->entry_default_pattern = stra_to_str(value);
877 StrLwr(config->entry_default_pattern);
883 static VOID config_entry_add_from_file(Config *config, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) {
888 CHAR16 *initrd = NULL;
890 entry = AllocateZeroPool(sizeof(ConfigEntry));
893 while ((line = line_get_key_value(line, &key, &value))) {
894 if (strcmpa((CHAR8 *)"title", key) == 0) {
895 FreePool(entry->title);
896 entry->title = stra_to_str(value);
900 if (strcmpa((CHAR8 *)"linux", key) == 0) {
901 FreePool(entry->loader);
902 entry->type = LOADER_LINUX;
903 entry->loader = stra_to_path(value);
907 if (strcmpa((CHAR8 *)"efi", key) == 0) {
908 entry->type = LOADER_EFI;
909 FreePool(entry->loader);
910 entry->loader = stra_to_path(value);
912 /* do not add an entry for ourselves */
913 if (StrCmp(entry->loader, loaded_image_path) == 0) {
914 entry->type = LOADER_UNDEFINED;
920 if (strcmpa((CHAR8 *)"initrd", key) == 0) {
923 new = stra_to_path(value);
927 s = PoolPrint(L"%s initrd=%s", initrd, new);
931 initrd = PoolPrint(L"initrd=%s", new);
936 if (strcmpa((CHAR8 *)"options", key) == 0) {
939 new = stra_to_str(value);
940 if (entry->options) {
943 s = PoolPrint(L"%s %s", entry->options, new);
944 FreePool(entry->options);
947 entry->options = new;
955 if (entry->type == LOADER_UNDEFINED) {
956 config_entry_free(entry);
962 /* add initrd= to options */
963 if (entry->type == LOADER_LINUX && initrd) {
964 if (entry->options) {
967 s = PoolPrint(L"%s %s", initrd, entry->options);
968 FreePool(entry->options);
971 entry->options = initrd;
977 entry->file = StrDuplicate(file);
978 len = StrLen(entry->file);
981 entry->file[len - 5] = '\0';
985 entry->title = StrDuplicate(entry->loader);
987 config_add_entry(config, entry);
990 static UINTN file_read(Config *config, EFI_FILE_HANDLE dir, const CHAR16 *name, CHAR8 **content) {
991 EFI_FILE_HANDLE handle;
998 err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0);
1002 info = LibFileInfo(handle);
1003 buflen = info->FileSize+1;
1004 buf = AllocatePool(buflen);
1006 err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
1007 if (EFI_ERROR(err) == EFI_SUCCESS) {
1015 uefi_call_wrapper(handle->Close, 1, handle);
1020 static VOID config_load(Config *config, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
1021 EFI_FILE_HANDLE entries_dir;
1028 len = file_read(config, root_dir, L"\\loader\\loader.conf", &content);
1030 config_defaults_load_from_file(config, content);
1032 err = efivar_get_int(L"LoaderConfigTimeout", &sec);
1033 if (EFI_ERROR(err) == EFI_SUCCESS) {
1034 config->timeout_sec_efivar = sec;
1035 config->timeout_sec = sec;
1037 config->timeout_sec_efivar = -1;
1039 err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0);
1040 if (EFI_ERROR(err) == EFI_SUCCESS) {
1048 bufsize = sizeof(buf);
1049 err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf);
1050 if (bufsize == 0 || EFI_ERROR(err))
1053 f = (EFI_FILE_INFO *) buf;
1054 if (f->FileName[0] == '.')
1056 if (f->Attribute & EFI_FILE_DIRECTORY)
1058 len = StrLen(f->FileName);
1061 if (StriCmp(f->FileName + len - 5, L".conf") != 0)
1064 len = file_read(config, entries_dir, f->FileName, &content);
1066 config_entry_add_from_file(config, f->FileName, content, loaded_image_path);
1068 uefi_call_wrapper(entries_dir->Close, 1, entries_dir);
1071 /* sort entries after version number */
1072 for (i = 1; i < config->entry_count; i++) {
1077 for (j = 0; j < config->entry_count - i; j++) {
1080 if (str_verscmp(config->entries[j]->file, config->entries[j+1]->file) <= 0)
1082 entry = config->entries[j];
1083 config->entries[j] = config->entries[j+1];
1084 config->entries[j+1] = entry;
1092 static VOID config_default_entry_select(Config *config) {
1097 * The EFI variable to specify a boot entry for the next, and only the
1098 * next reboot. The variable is always cleared directly after it is read.
1100 err = efivar_get(L"LoaderEntryOneShot", &var);
1101 if (EFI_ERROR(err) == EFI_SUCCESS) {
1102 BOOLEAN found = FALSE;
1105 for (i = 0; i < config->entry_count; i++) {
1106 if (!config->entries[i]->file)
1108 if (StrCmp(config->entries[i]->file, var) == 0) {
1109 config->idx_default = i;
1114 efivar_set(L"LoaderEntryOneShot", NULL, TRUE);
1121 * The EFI variable to select the default boot entry overrides the
1122 * configured pattern. The variable can be set and cleared by pressing
1123 * the 'd' key in the loader selection menu, the entry is marked with
1126 err = efivar_get(L"LoaderEntryDefault", &var);
1127 if (EFI_ERROR(err) == EFI_SUCCESS) {
1128 BOOLEAN found = FALSE;
1131 for (i = 0; i < config->entry_count; i++) {
1132 if (!config->entries[i]->file)
1134 if (StrCmp(config->entries[i]->file, var) == 0) {
1135 config->idx_default = i;
1136 config->idx_default_efivar = i;
1145 config->idx_default_efivar = -1;
1148 * Match the pattern from the end of the list to the start, find last
1149 * entry (largest number) matching the given pattern.
1151 if (config->entry_default_pattern) {
1154 for (i = config->entry_count-1; i >= 0; i--) {
1155 if (!config->entries[i]->file)
1157 if (config->entries[i]->no_autoselect)
1159 if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) {
1160 config->idx_default = i;
1166 /* select the last entry */
1167 if (config->entry_count)
1168 config->idx_default = config->entry_count-1;
1171 static VOID config_entry_add_loader(Config *config, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
1172 CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
1173 EFI_FILE_HANDLE handle;
1177 /* check existence */
1178 err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0);
1181 uefi_call_wrapper(handle->Close, 1, handle);
1183 /* do not add an entry for ourselves */
1184 if (StrCmp(loader, loaded_image_path) == 0)
1187 entry = AllocateZeroPool(sizeof(ConfigEntry));
1188 entry->title = StrDuplicate(title);
1189 entry->loader = StrDuplicate(loader);
1191 entry->file = StrDuplicate(file);
1192 entry->no_autoselect = TRUE;
1193 config_add_entry(config, entry);
1196 static EFI_STATUS image_start(EFI_HANDLE parent_image, EFI_LOADED_IMAGE *parent_loaded_image,
1197 const Config *config, const ConfigEntry *entry) {
1200 EFI_DEVICE_PATH *path;
1203 path = FileDevicePath(parent_loaded_image->DeviceHandle, entry->loader);
1205 Print(L"Error getting device path.");
1206 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1207 return EFI_INVALID_PARAMETER;
1210 err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image);
1211 if (EFI_ERROR(err)) {
1212 Print(L"Error loading %s: %r", entry->loader, err);
1213 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1217 if (config->options_edit)
1218 options = config->options_edit;
1219 else if (entry->options)
1220 options = entry->options;
1224 EFI_LOADED_IMAGE *loaded_image;
1226 err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
1227 parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
1228 if (EFI_ERROR(err)) {
1229 Print(L"Error getting LoadedImageProtocol handle: %r", err);
1230 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1233 loaded_image->LoadOptions = options;
1234 loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16);
1237 efivar_set_ticks(L"LoaderTicksExec", 0);
1238 err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL);
1240 uefi_call_wrapper(BS->UnloadImage, 1, image);
1246 static VOID config_free(Config *config) {
1249 for (i = 0; i < config->entry_count; i++)
1250 config_entry_free(config->entries[i]);
1251 FreePool(config->entries);
1252 FreePool(config->entry_default_pattern);
1253 FreePool(config->options_edit);
1256 EFI_STATUS EFIAPI efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
1257 EFI_LOADED_IMAGE *loaded_image;
1259 CHAR16 *loaded_image_path;
1260 EFI_DEVICE_PATH *device_path;
1264 BOOLEAN menu = FALSE;
1266 ticks = ticks_read();
1267 InitializeLib(image, sys_table);
1268 efivar_set_ticks(L"LoaderTicksInit", ticks);
1270 err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
1271 image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
1272 if (EFI_ERROR(err)) {
1273 Print(L"Error getting a LoadedImageProtocol handle: %r ", err);
1274 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1278 /* export the device path this image is started from */
1279 device_path = DevicePathFromHandle(loaded_image->DeviceHandle);
1281 efivar_set(L"LoaderDeviceIdentifier", DevicePathToStr(device_path), FALSE);
1283 root_dir = LibOpenRoot(loaded_image->DeviceHandle);
1285 Print(L"Unable to open root directory: %r ", err);
1286 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1287 return EFI_LOAD_ERROR;
1290 /* the filesystem path to this image, to prevent adding ourselves to the menu */
1291 loaded_image_path = DevicePathToStr(loaded_image->FilePath);
1293 /* scan "\loader\entries\*.conf" files */
1294 ZeroMem(&config, sizeof(Config));
1295 config_load(&config, root_dir, loaded_image_path);
1297 /* if we find some well-known loaders, add them to the end of the list */
1298 config_entry_add_loader(&config, root_dir, loaded_image_path,
1299 L"loader-bootmgfw", L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
1300 config_entry_add_loader(&config, root_dir, loaded_image_path,
1301 L"loader-bootx86", L"EFI default loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
1302 FreePool(loaded_image_path);
1304 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/
1305 config_default_entry_select(&config);
1307 /* show menu when key is pressed or timeout is set */
1308 if (config.timeout_sec == 0) {
1311 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
1312 menu = err != EFI_NOT_READY;
1319 entry = config.entries[config.idx_default];
1321 efivar_set_ticks(L"LoaderTicksStartMenu", 0);
1322 if (!menu_run(&config, &entry))
1326 /* export the selected boot entry to the system */
1327 err = efivar_set(L"LoaderEntrySelected", entry->file, FALSE);
1328 if (EFI_ERROR(err)) {
1329 Print(L"Error storing LoaderEntrySelected variable: %r ", err);
1330 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
1333 image_start(image, loaded_image, &config, entry);
1336 config.timeout_sec = 0;
1339 config_free(&config);
1340 uefi_call_wrapper(root_dir->Close, 1, root_dir);
1341 uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL);
1342 return uefi_call_wrapper(BS->Exit, 4, image, EFI_SUCCESS, 0, NULL);