properly terminate edited line
[platform/upstream/gummiboot.git] / gummiboot.c
1 /*
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
4  * menu.
5  *
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.
10  *
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.
15  *
16  * Copyright (C) 2012 Kay Sievers <kay.sievers@vrfy.org>
17  * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
18  *
19  * "Any intelligent fool can make things bigger, more complex, and more violent."
20  *   -- Albert Einstein
21  */
22
23 #include "efi.h"
24 #include "efilib.h"
25
26 /*
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.
30  */
31 static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} };
32
33 enum loader_type {
34         LOADER_UNDEFINED,
35         LOADER_EFI,
36         LOADER_LINUX
37 };
38
39 typedef struct {
40         CHAR16 *file;
41         CHAR16 *title;
42         enum loader_type type;
43         CHAR16 *loader;
44         CHAR16 *initrd;
45         CHAR16 *options;
46         BOOLEAN no_default;
47 } ConfigEntry;
48
49 typedef struct {
50         ConfigEntry **entries;
51         UINTN entry_count;
52         UINTN idx_default;
53         INTN idx_default_efivar;
54         UINTN timeout_sec;
55         UINTN timeout_sec_config;
56         INTN timeout_sec_efivar;
57         CHAR16 *entry_default_pattern;
58         CHAR16 *options_edit;
59         BOOLEAN no_initrd;
60 } Config;
61
62 #ifdef __x86_64__
63 static UINT64 tsc() {
64         UINT64 a, d;
65         __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
66         return (d << 32) | a;
67 }
68 #else
69 static UINT64 tsc() { return 0; }
70 #endif
71
72 static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) {
73         UINT32 flags;
74
75         flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
76         if (persistent)
77                 flags |= EFI_VARIABLE_NON_VOLATILE;
78
79         return uefi_call_wrapper(RT->SetVariable, 5, name, &loader_guid, flags,
80                                  value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, value);
81 }
82
83 static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
84         CHAR16 *val;
85         UINTN size;
86         EFI_STATUS err;
87
88         size = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
89         val = AllocatePool(size);
90         if (!val)
91                 return EFI_OUT_OF_RESOURCES;
92
93         err = uefi_call_wrapper(RT->GetVariable, 5, name, &loader_guid, NULL, &size, val);
94         if (EFI_ERROR(err) == 0)
95                 *value = val;
96         else
97                 FreePool(val);
98         return err;
99
100 }
101
102 static EFI_STATUS efivar_set_int(CHAR16 *name, INTN i, BOOLEAN persistent) {
103         CHAR16 str[32];
104
105         SPrint(str, 32, L"%d", i);
106         return efivar_set(name, str, persistent);
107 }
108
109 static EFI_STATUS efivar_get_int(CHAR16 *name, INTN *i) {
110         CHAR16 *val;
111         EFI_STATUS err;
112
113         err = efivar_get(name, &val);
114         if (EFI_ERROR(err) == 0) {
115                 *i = Atoi(val);
116                 FreePool(val);
117         }
118         return err;
119 }
120
121 static VOID efivar_set_ticks(CHAR16 *name, UINT64 ticks) {
122         CHAR16 str[32];
123
124         SPrint(str, 32, L"%ld", ticks ? ticks : tsc());
125         efivar_set(name, str, FALSE);
126 }
127
128 static BOOLEAN edit_line(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) {
129         CHAR16 *line;
130         UINTN size;
131         UINTN len;
132         UINTN first;
133         CHAR16 *print;
134         UINTN cursor;
135         BOOLEAN exit;
136         BOOLEAN edit;
137
138         if (!line_in)
139                 line_in = L"";
140         size = StrLen(line_in) + 1024;
141         line = AllocatePool(size * sizeof(CHAR16));
142         StrCpy(line, line_in);
143         len = StrLen(line);
144         print = AllocatePool(x_max * sizeof(CHAR16));
145
146         uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
147
148         first = 0;
149         cursor = 0;
150         edit = FALSE;
151         exit = FALSE;
152         while (!exit) {
153                 UINTN index;
154                 EFI_STATUS err;
155                 EFI_INPUT_KEY key;
156                 UINTN i;
157
158                 i = len - first;
159                 if (i >= x_max-2)
160                         i = x_max-2;
161                 CopyMem(print, line + first, i * sizeof(CHAR16));
162                 print[i++] = ' ';
163                 print[i] = '\0';
164
165                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
166                 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
167                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
168
169                 uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
170                 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
171                 if (EFI_ERROR(err))
172                         continue;
173
174                 switch (key.ScanCode) {
175                 case SCAN_ESC:
176                         exit = TRUE;
177                         break;
178                 case SCAN_HOME:
179                 case SCAN_UP:
180                         cursor = 0;
181                         first = 0;
182                         continue;
183                 case SCAN_END:
184                 case SCAN_DOWN:
185                         cursor = len;
186                         if (cursor >= x_max) {
187                                 cursor = x_max-2;
188                                 first = len - (x_max-2);
189                         }
190                         continue;
191                 case SCAN_RIGHT:
192                         if (first + cursor == len)
193                                 continue;
194                         if (cursor+2 < x_max)
195                                 cursor++;
196                         else if (first + cursor < len)
197                                 first++;
198                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
199                         continue;
200                 case SCAN_LEFT:
201                         if (cursor > 0)
202                                 cursor--;
203                         else if (first > 0)
204                                 first--;
205                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
206                         continue;
207                 case SCAN_DELETE:
208                         if (len == 0)
209                                 continue;
210                         if (first + cursor == len)
211                                 continue;
212                         for (i = first + cursor; i < len; i++)
213                                 line[i] = line[i+1];
214                         line[len-1] = ' ';
215                         len--;
216                         continue;
217                 }
218
219                 switch (key.UnicodeChar) {
220                 case CHAR_LINEFEED:
221                 case CHAR_CARRIAGE_RETURN:
222                         if (StrCmp(line, line_in) != 0) {
223                                 edit = TRUE;
224                                 *line_out = line;
225                                 line = NULL;
226                         }
227                         exit = TRUE;
228                         break;
229                 case CHAR_BACKSPACE:
230                         if (len == 0)
231                                 continue;
232                         if (first == 0 && cursor == 0)
233                                 continue;
234                         for (i = first + cursor-1; i < len; i++)
235                                 line[i] = line[i+1];
236                         len--;
237                         if (cursor > 0)
238                                 cursor--;
239                         if (cursor > 0 || first == 0)
240                                 continue;
241                         /* show full line if it fits */
242                         if (len < x_max-2) {
243                                 cursor = first;
244                                 first = 0;
245                                 continue;
246                         }
247                         /* jump left to see what we delete */
248                         if (first > 10) {
249                                 first -= 10;
250                                 cursor = 10;
251                         } else {
252                                 cursor = first;
253                                 first = 0;
254                         }
255                         continue;
256                 case '\t':
257                 case ' ' ... '~':
258                 case 0x80 ... 0xffff:
259                         if (len+1 == size)
260                                 continue;
261                         for (i = len; i > first + cursor; i--)
262                                 line[i] = line[i-1];
263                         line[first + cursor] = key.UnicodeChar;
264                         len++;
265                         line[len] = '\0';
266                         if (cursor+2 < x_max)
267                                 cursor++;
268                         else if (first + cursor < len)
269                                 first++;
270                         continue;
271                 }
272         }
273
274         uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
275         FreePool(line);
276         return edit;
277 }
278
279 static VOID menu_run(Config *config, ConfigEntry **chosen_entry) {
280         EFI_STATUS err;
281         INTN visible_max;
282         INTN idx_highlight;
283         INTN idx_highlight_prev;
284         INTN idx_first;
285         INTN idx_last;
286         BOOLEAN refresh;
287         BOOLEAN highlight;
288         INTN i;
289         UINTN line_width;
290         CHAR16 **lines;
291         UINTN x_max;
292         UINTN y_max;
293         CHAR16 *status;
294         CHAR16 *clearline;
295         INTN timeout_remain;
296         BOOLEAN exit = FALSE;
297
298         uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
299         uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
300         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
301         uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
302
303         err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max);
304         if (EFI_ERROR(err)) {
305                 x_max = 80;
306                 y_max = 25;
307         }
308
309         /* we check 10 times per second for a keystroke */
310         if (config->timeout_sec > 0)
311                 timeout_remain = config->timeout_sec * 10;
312         else
313                 timeout_remain = -1;
314
315         idx_highlight = config->idx_default;
316         idx_highlight_prev = 0;
317
318         visible_max = y_max - 2;
319
320         if (config->idx_default >= visible_max)
321                 idx_first = config->idx_default-1;
322         else
323                 idx_first = 0;
324
325         idx_last = idx_first + visible_max-1;
326
327         refresh = TRUE;
328         highlight = FALSE;
329
330         /* length of higlighted selector bar */
331         line_width = 20;
332         for (i = 0; i < config->entry_count; i++) {
333                 UINTN entry_len;
334
335                 entry_len = StrLen(config->entries[i]->title);
336                 if (line_width < entry_len)
337                         line_width = entry_len;
338         }
339         if (line_width > x_max)
340                 line_width = x_max;
341
342         /* menu entries title lines */
343         lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count);
344         for (i = 0; i < config->entry_count; i++)
345                 lines[i] = PoolPrint(L"  %-.*s ", line_width, config->entries[i]->title);
346
347         status = NULL;
348         clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
349         for (i = 0; i < x_max; i++)
350                 clearline[i] = ' ';
351         clearline[i] = 0;
352
353         while (!exit) {
354                 EFI_INPUT_KEY key;
355
356                 if (refresh) {
357                         for (i = 0; i < config->entry_count; i++) {
358                                 if (i < idx_first || i > idx_last)
359                                         continue;
360                                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
361                                 if (i == idx_highlight)
362                                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
363                                                           EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
364                                 else
365                                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
366                                                           EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
367                                 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]);
368                                 if (i == config->idx_default_efivar) {
369                                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, i - idx_first);
370                                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
371                                 }
372                         }
373                         refresh = FALSE;
374                 } else if (highlight) {
375                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
376                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
377                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
378                         if (idx_highlight_prev == config->idx_default_efivar) {
379                                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight_prev - idx_first);
380                                 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
381                         }
382
383                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
384                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
385                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]);
386                         if (idx_highlight == config->idx_default_efivar) {
387                                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, idx_highlight - idx_first);
388                                 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"*");
389                         }
390                         highlight = FALSE;
391                 }
392
393                 if (timeout_remain > 0) {
394                         FreePool(status);
395                         status = PoolPrint(L"Boot in %d seconds.", (timeout_remain + 5) / 10);
396                 }
397
398                 /* print status at last line of screen */
399                 if (status) {
400                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
401                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
402                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
403                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + StrLen(status));
404                 }
405
406                 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
407                 if (err == EFI_NOT_READY) {
408                         UINTN index;
409
410                         if (timeout_remain == 0) {
411                                 exit = TRUE;
412                                 break;
413                         }
414                         if (timeout_remain > 0) {
415                                 uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
416                                 timeout_remain--;
417                                 continue;
418                         }
419                         uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
420                         continue;
421                 }
422                 timeout_remain = -1;
423
424                 /* clear status after keystroke */
425                 if (status) {
426                         FreePool(status);
427                         status = NULL;
428                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
429                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
430                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
431                 }
432
433                 idx_highlight_prev = idx_highlight;
434
435                 switch (key.ScanCode) {
436                 case SCAN_UP:
437                         if (idx_highlight > 0)
438                                 idx_highlight--;
439                         break;
440                 case SCAN_DOWN:
441                         if (idx_highlight < config->entry_count-1)
442                                 idx_highlight++;
443                         break;
444                 case SCAN_HOME:
445                         if (idx_highlight > 0) {
446                                 refresh = TRUE;
447                                 idx_highlight = 0;
448                         }
449                         break;
450                 case SCAN_END:
451                         if (idx_highlight < config->entry_count-1) {
452                                 refresh = TRUE;
453                                 idx_highlight = config->entry_count-1;
454                         }
455                         break;
456                 case SCAN_PAGE_UP:
457                         idx_highlight -= visible_max;
458                         if (idx_highlight < 0)
459                                 idx_highlight = 0;
460                         break;
461                 case SCAN_PAGE_DOWN:
462                         idx_highlight += visible_max;
463                         if (idx_highlight > config->entry_count-1)
464                                 idx_highlight = config->entry_count-1;
465                         break;
466                 case SCAN_F1:
467                         status = StrDuplicate(L"(d)efault, (+/-)timeout, (o)ptions, (i)nitrd, (v)ersion");
468                         break;
469                 }
470
471                 if (idx_highlight > idx_last) {
472                         idx_last = idx_highlight;
473                         idx_first = 1 + idx_highlight - visible_max;
474                         refresh = TRUE;
475                 }
476                 if (idx_highlight < idx_first) {
477                         idx_first = idx_highlight;
478                         idx_last = idx_highlight + visible_max-1;
479                         refresh = TRUE;
480                 }
481                 idx_last = idx_first + visible_max-1;
482
483                 if (!refresh && idx_highlight != idx_highlight_prev)
484                         highlight = TRUE;
485
486                 switch (key.UnicodeChar) {
487                 case CHAR_LINEFEED:
488                 case CHAR_CARRIAGE_RETURN:
489                         exit = TRUE;
490                         break;
491                 case 'd':
492                         if (config->idx_default_efivar != idx_highlight) {
493                                 /* store the selected entry in a persistent EFI variable */
494                                 efivar_set(L"LoaderConfigDefault", config->entries[idx_highlight]->file, TRUE);
495                                 config->idx_default_efivar = idx_highlight;
496                                 status = StrDuplicate(L"Default boot entry permanently stored.");
497                         } else {
498                                 /* clear the default entry EFI variable */
499                                 efivar_set(L"LoaderConfigDefault", NULL, TRUE);
500                                 config->idx_default_efivar = -1;
501                                 status = StrDuplicate(L"Default boot entry cleared.");
502                         }
503                         refresh = TRUE;
504                         break;
505                 case '-':
506                         if (config->timeout_sec_efivar > 0) {
507                                 config->timeout_sec_efivar--;
508                                 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
509                                 if (config->timeout_sec_efivar > 0)
510                                         status = PoolPrint(L"Timeout of %d sec permanently stored.",
511                                                            config->timeout_sec_efivar);
512                                 else
513                                         status = StrDuplicate(L"Menu permanently disabled. "
514                                                               "Hold down key at bootup to show menu.");
515                         } else if (config->timeout_sec_efivar <= 0){
516                                 config->timeout_sec_efivar = -1;
517                                 efivar_set(L"LoaderConfigTimeout", NULL, TRUE);
518                                 status = PoolPrint(L"Timeout permanantly set to the configured value of %d sec.",
519                                                    config->timeout_sec_config);
520                         }
521                         break;
522                 case '+':
523                         config->timeout_sec_efivar++;
524                         efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
525                         if (config->timeout_sec_efivar)
526                                 status = PoolPrint(L"Timeout of %d sec permanently stored.",
527                                                    config->timeout_sec_efivar);
528                         else
529                                 status = StrDuplicate(L"Menu permanently disabled. "
530                                                       "Hold down key at bootup to show menu.");
531                         break;
532                 case 'i':
533                         if (!config->entries[idx_highlight]->initrd)
534                                 break;
535                         if (config->no_initrd) {
536                                 config->no_initrd = FALSE;
537                                 status = StrDuplicate(L"Initrd enabled for this bootup.");
538                         } else {
539                                 config->no_initrd = TRUE;
540                                 status = StrDuplicate(L"Initrd disabled for this bootup.");
541                         }
542                         break;
543                 case 'o':
544                         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
545                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
546                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
547                         if (edit_line(config->entries[idx_highlight]->options, &config->options_edit, x_max, y_max-1))
548                                 exit = TRUE;
549                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
550                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
551                         break;
552                 case 'v':
553                         status = PoolPrint(L"gummiboot %d, EFI %d.%02d", VERSION,
554                                            ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
555                         break;
556                 }
557         }
558
559         for (i = 0; i < config->entry_count; i++)
560                 FreePool(lines[i]);
561         FreePool(lines);
562         FreePool(clearline);
563         *chosen_entry = config->entries[idx_highlight];
564
565         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK);
566         uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
567 }
568
569 static VOID config_add_entry(Config *config, ConfigEntry *entry) {
570         if ((config->entry_count & 15) == 0) {
571                 UINTN i;
572
573                 i = config->entry_count + 16;
574                 if (config->entry_count == 0)
575                         config->entries = AllocatePool(sizeof(VOID *) * i);
576                 else
577                         config->entries = ReallocatePool(config->entries,
578                                                          sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i);
579         }
580         config->entries[config->entry_count++] = entry;
581 }
582
583 static BOOLEAN is_digit(CHAR16 c)
584 {
585         return (c >= '0') && (c <= '9');
586 }
587
588 static UINTN c_order(CHAR16 c)
589 {
590         if (c == '\0')
591                 return 0;
592         if (is_digit(c))
593                 return 0;
594         else if ((c >= 'a') && (c <= 'z'))
595                 return c;
596         else
597                 return c + 0x10000;
598 }
599
600 static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
601 {
602         CHAR16 *os1;
603         CHAR16 *os2;
604
605         os1 = s1;
606         os2 = s2;
607         while (*s1 || *s2) {
608                 INTN first;
609
610                 while ((*s2 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
611                         INTN order;
612
613                         order = c_order(*s1) - c_order(*s2);
614                         if (order)
615                                 return order;
616                         s1++;
617                         s2++;
618                 }
619
620                 while (*s1 == '0')
621                         s1++;
622                 while (*s2 == '0')
623                         s2++;
624
625                 first = 0;
626                 while (is_digit(*s1) && is_digit(*s2)) {
627                         if (first == 0)
628                                 first = *s1 - *s2;
629                         s1++;
630                         s2++;
631                 }
632
633                 if (is_digit(*s1))
634                         return 1;
635                 if (is_digit(*s2))
636                         return -1;
637
638                 if (first)
639                         return first;
640         }
641
642         return StrCmp(os1, os2);
643 }
644
645 CHAR16 *stra_to_str(CHAR8 *stra) {
646         UINTN len;
647         UINTN i;
648         CHAR16 *str;
649
650         len = strlena(stra);
651         str = AllocatePool((len + 1) * sizeof(CHAR16));
652
653         for (i = 0; i < len; i++)
654                 str[i] = stra[i];
655         str[i] = '\0';
656
657         return str;
658 }
659
660 CHAR16 *stra_to_path(CHAR8 *stra) {
661         CHAR16 *str;
662         UINTN strlen;
663         UINTN len;
664         UINTN i;
665
666         len = strlena(stra);
667         str = AllocatePool((len + 2) * sizeof(CHAR16));
668
669         str[0] = '\\';
670         strlen = 1;
671         for (i = 0; i < len; i++) {
672                 if (stra[i] == '/' || stra[i] == '\\') {
673                         if (str[strlen-1] == '\\')
674                                 continue;
675                         str[strlen++] = '\\';
676                         continue;
677                 }
678                 str[strlen++] = stra[i];
679         }
680         str[strlen] = '\0';
681
682         return str;
683 }
684
685 static CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
686         do {
687                 if (*s == c)
688                         return s;
689         } while (*s++);
690         return NULL;
691 }
692
693 static CHAR8 *line_get_key_value(CHAR8 *line, CHAR8 **key_ret, CHAR8 **value_ret) {
694         CHAR8 *next;
695         CHAR8 *key, *value;
696         UINTN linelen;
697
698         /* terminate */
699 skip:
700         next = line;
701         while (*next && !strchra((CHAR8 *)"\n\r", *next))
702                 next++;
703         *next = '\0';
704
705         linelen = next - line;
706         if (linelen == 0)
707                 return NULL;
708
709         /* next line */
710         next++;
711         while (*next && strchra((CHAR8 *)"\n\r", *next))
712                 next++;
713
714         /* trailing whitespace */
715         while (linelen && strchra((CHAR8 *)" \t", line[linelen-1]))
716                 linelen--;
717         line[linelen] = '\0';
718
719         /* leading whitespace */
720         while (strchra((CHAR8 *)" \t", *line))
721                 line++;
722
723         key = line;
724         line = next;
725
726         if (*key == '#')
727                 goto skip;
728
729         /* split key/value */
730         value = key;
731         while (*value && !strchra((CHAR8 *)" \t", *value))
732                 value++;
733         if (*value == '\0')
734                 goto skip;
735         *value = '\0';
736         value++;
737         while (*value && strchra((CHAR8 *)" \t", *value))
738                 value++;
739
740         *key_ret = key;
741         *value_ret = value;
742         return next;
743 }
744
745 static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
746         CHAR8 *line;
747         CHAR8 *key, *value;
748
749         line = content;
750         while ((line = line_get_key_value(line, &key, &value))) {
751                 if (strcmpa((CHAR8 *)"timeout", key) == 0) {
752                         CHAR16 *s;
753
754                         s = stra_to_str(value);
755                         config->timeout_sec_config = Atoi(s);
756                         config->timeout_sec = config->timeout_sec_config;
757                         FreePool(s);
758                         continue;
759                 }
760                 if (strcmpa((CHAR8 *)"default", key) == 0) {
761                         config->entry_default_pattern = stra_to_str(value);
762                         StrLwr(config->entry_default_pattern);
763                         continue;
764                 }
765         }
766 }
767
768 static VOID config_entry_add_from_file(Config *config, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) {
769         ConfigEntry *entry;
770         CHAR8 *line;
771         CHAR8 *key, *value;
772         UINTN len;
773
774         entry = AllocateZeroPool(sizeof(ConfigEntry));
775
776         line = content;
777         while ((line = line_get_key_value(line, &key, &value))) {
778                 if (strcmpa((CHAR8 *)"title", key) == 0) {
779                         entry->title = stra_to_str(value);
780                         continue;
781                 }
782
783                 if (strcmpa((CHAR8 *)"linux", key) == 0) {
784                         entry->type = LOADER_LINUX;
785                         entry->loader = stra_to_path(value);
786                         continue;
787                 }
788
789                 if (strcmpa((CHAR8 *)"efi", key) == 0) {
790                         entry->type = LOADER_EFI;
791                         entry->loader = stra_to_path(value);
792                         /* do not add an entry for ourselves */
793                         if (StrCmp(entry->loader, loaded_image_path) == 0) {
794                                 entry->type = LOADER_UNDEFINED;
795                                 break;
796                         }
797                         continue;
798                 }
799
800                 if (strcmpa((CHAR8 *)"initrd", key) == 0) {
801                         entry->initrd = stra_to_path(value);
802                         continue;
803                 }
804
805                 if (strcmpa((CHAR8 *)"options", key) == 0) {
806                         entry->options = stra_to_str(value);
807                         continue;
808                 }
809         }
810
811         if (entry->type == LOADER_UNDEFINED) {
812                 FreePool(entry->title);
813                 FreePool(entry->loader);
814                 FreePool(entry->initrd);
815                 FreePool(entry->options);
816                 FreePool(entry);
817                 return;
818         }
819
820         entry->file = StrDuplicate(file);
821         len = StrLen(entry->file);
822         /* remove ".conf" */
823         if (len > 5)
824                 entry->file[len - 5] = '\0';
825         StrLwr(entry->file);
826
827         if (!entry->title)
828                 entry->title = StrDuplicate(entry->loader);
829
830         config_add_entry(config, entry);
831 }
832
833 static UINTN file_read(Config *config, EFI_FILE_HANDLE dir, const CHAR16 *name, CHAR8 **content) {
834         EFI_FILE_HANDLE handle;
835         EFI_FILE_INFO *info;
836         CHAR8 *buf;
837         UINTN buflen;
838         EFI_STATUS err;
839         UINTN len = 0;
840
841         err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0);
842         if (EFI_ERROR(err))
843                 goto out;
844
845         info = LibFileInfo(handle);
846         buflen = info->FileSize+1;
847         buf = AllocatePool(buflen);
848
849         err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
850         if (EFI_ERROR(err) == EFI_SUCCESS) {
851                 buf[buflen] = '\0';
852                 *content = buf;
853                 len = buflen;
854         } else
855                 FreePool(buf);
856
857         FreePool(info);
858         uefi_call_wrapper(handle->Close, 1, handle);
859 out:
860         return len;
861 }
862
863 static VOID config_load(Config *config, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
864         EFI_FILE_HANDLE entries_dir;
865         EFI_STATUS err;
866         CHAR8 *content;
867         INTN sec;
868         UINTN len;
869         UINTN i;
870
871         len = file_read(config, root_dir, L"\\loader\\loader.conf", &content);
872         if (len > 0)
873                 config_defaults_load_from_file(config, content);
874
875         err = efivar_get_int(L"LoaderConfigTimeout", &sec);
876         if (EFI_ERROR(err) == EFI_SUCCESS) {
877                 config->timeout_sec_efivar = sec;
878                 config->timeout_sec = sec;
879         } else
880                 config->timeout_sec_efivar = -1;
881
882         err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0);
883         if (EFI_ERROR(err) == EFI_SUCCESS) {
884                 for (;;) {
885                         CHAR16 buf[256];
886                         UINTN bufsize;
887                         EFI_FILE_INFO *f;
888                         CHAR8 *content;
889                         UINTN len;
890
891                         bufsize = sizeof(buf);
892                         err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf);
893                         if (bufsize == 0 || EFI_ERROR(err))
894                                 break;
895
896                         f = (EFI_FILE_INFO *) buf;
897                         if (f->FileName[0] == '.')
898                                 continue;
899                         if (f->Attribute & EFI_FILE_DIRECTORY)
900                                 continue;
901                         len = StrLen(f->FileName);
902                         if (len < 6)
903                                 continue;
904                         if (StriCmp(f->FileName + len - 5, L".conf") != 0)
905                                 continue;
906
907                         len = file_read(config, entries_dir, f->FileName, &content);
908                         if (len > 0)
909                                 config_entry_add_from_file(config, f->FileName, content, loaded_image_path);
910                 }
911                 uefi_call_wrapper(entries_dir->Close, 1, entries_dir);
912         }
913
914         /* sort entries after version number */
915         for (i = 1; i < config->entry_count; i++) {
916                 BOOLEAN more;
917                 UINTN j;
918
919                 more = FALSE;
920                 for (j = 0; j < config->entry_count - i; j++) {
921                         ConfigEntry *entry;
922
923                         if (str_verscmp(config->entries[j]->file, config->entries[j+1]->file) <= 0)
924                                 continue;
925                         entry = config->entries[j];
926                         config->entries[j] = config->entries[j+1];
927                         config->entries[j+1] = entry;
928                         more = TRUE;
929                 }
930                 if (!more)
931                         break;
932         }
933 }
934
935 static VOID config_default_entry_select(Config *config) {
936         CHAR16 *var;
937         EFI_STATUS err;
938
939         /*
940          * The EFI variable to specify a boot entry for the next, and only the
941          * next reboot. The variable is always cleared directly after it is read.
942          */
943         err = efivar_get(L"LoaderEntryOneShot", &var);
944         if (EFI_ERROR(err) == EFI_SUCCESS) {
945                 BOOLEAN found = FALSE;
946                 UINTN i;
947
948                 for (i = 0; i < config->entry_count; i++) {
949                         if (!config->entries[i]->file)
950                                 continue;
951                         if (StrCmp(config->entries[i]->file, var) == 0) {
952                                 config->idx_default = i;
953                                 found = TRUE;
954                                 break;
955                         }
956                 }
957                 efivar_set(L"LoaderEntryOneShot", NULL, TRUE);
958                 FreePool(var);
959                 if (found)
960                         return;
961         }
962
963         /*
964          * The EFI variable to select the default boot entry overrides the
965          * configured pattern. The variable can be set and cleared by pressing
966          * the 'd' key in the loader selection menu, the entry is marked with
967          * an '*'.
968          */
969         err = efivar_get(L"LoaderConfigDefault", &var);
970         if (EFI_ERROR(err) == EFI_SUCCESS) {
971                 BOOLEAN found = FALSE;
972                 UINTN i;
973
974                 for (i = 0; i < config->entry_count; i++) {
975                         if (!config->entries[i]->file)
976                                 continue;
977                         if (StrCmp(config->entries[i]->file, var) == 0) {
978                                 config->idx_default = i;
979                                 config->idx_default_efivar = i;
980                                 found = TRUE;
981                                 break;
982                         }
983                 }
984                 FreePool(var);
985                 if (found)
986                         return;
987         }
988         config->idx_default_efivar = -1;
989
990         /*
991          * Match the pattern from the end of the list to the start, find last
992          * entry (largest number) matching the given pattern.
993          */
994         if (config->entry_default_pattern) {
995                 UINTN i;
996
997                 for (i = config->entry_count-1; i >= 0; i--) {
998                         if (!config->entries[i]->file)
999                                 continue;
1000                         if (config->entries[i]->no_default)
1001                                 continue;
1002                         if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) {
1003                                 config->idx_default = i;
1004                                 return;
1005                         }
1006                 }
1007         }
1008
1009         /* select the last entry */
1010         if (config->entry_count)
1011                 config->idx_default = config->entry_count-1;
1012 }
1013
1014 static VOID config_entry_add_loader(Config *config, EFI_FILE *root_dir, CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
1015         EFI_FILE_HANDLE handle;
1016         EFI_STATUS err;
1017         ConfigEntry *entry;
1018
1019         /* check existence */
1020         err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0);
1021         if (EFI_ERROR(err))
1022                 return;
1023         uefi_call_wrapper(handle->Close, 1, handle);
1024
1025         entry = AllocateZeroPool(sizeof(ConfigEntry));
1026         entry->title = StrDuplicate(title);
1027         entry->loader = StrDuplicate(loader);
1028         if (file)
1029                 entry->file = StrDuplicate(file);
1030         entry->no_default = TRUE;
1031         config_add_entry(config, entry);
1032 }
1033
1034 static EFI_STATUS image_start(EFI_HANDLE parent_image, EFI_LOADED_IMAGE *parent_loaded_image,
1035                               const Config *config, const ConfigEntry *entry) {
1036         EFI_STATUS err;
1037         EFI_HANDLE image;
1038         EFI_DEVICE_PATH *path;
1039         EFI_LOADED_IMAGE *loaded_image;
1040         CHAR16 *options;
1041
1042         path = FileDevicePath(parent_loaded_image->DeviceHandle, entry->loader);
1043         if (!path) {
1044                 Print(L"Error getting device path.");
1045                 uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1046                 return EFI_INVALID_PARAMETER;
1047         }
1048
1049         err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image);
1050         if (EFI_ERROR(err)) {
1051                 Print(L"Error loading %s: %r", entry->loader, err);
1052                 uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1053                 goto out;
1054         }
1055
1056         if (config->options_edit)
1057                 options = config->options_edit;
1058         else if (entry->options)
1059                 options = entry->options;
1060         else
1061                 options = NULL;
1062         if (options || (entry->initrd && !config->no_initrd)) {
1063                 err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
1064                                         parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
1065                 if (EFI_ERROR(err)) {
1066                         Print(L"Error getting LoadedImageProtocol handle: %r", err);
1067                         uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1068                         goto out_unload;
1069                 }
1070                 if (entry->type == LOADER_LINUX && entry->initrd && !config->no_initrd) {
1071                         if (options)
1072                                 loaded_image->LoadOptions = PoolPrint(L"initrd=%s %s", entry->initrd, options);
1073                         else
1074                                 loaded_image->LoadOptions = PoolPrint(L"initrd=%s", entry->initrd);
1075                 } else
1076                         loaded_image->LoadOptions = options;
1077                 loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16);
1078         }
1079
1080         efivar_set_ticks(L"LoaderTicksStartImage", 0);
1081         err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL);
1082 out_unload:
1083         uefi_call_wrapper(BS->UnloadImage, 1, image);
1084 out:
1085         FreePool(path);
1086         return err;
1087 }
1088
1089 EFI_STATUS EFIAPI efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
1090         EFI_LOADED_IMAGE *loaded_image;
1091         EFI_FILE *root_dir;
1092         CHAR16 *loaded_image_path;
1093         EFI_STATUS err;
1094         Config config;
1095         UINT64 ticks;
1096         BOOLEAN menu = FALSE;
1097
1098         ticks = tsc();
1099         InitializeLib(image, sys_table);
1100         efivar_set_ticks(L"LoaderTicksInit", ticks);
1101
1102         err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, &loaded_image,
1103                                 image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
1104         if (EFI_ERROR(err)) {
1105                 Print(L"Error getting a LoadedImageProtocol handle: %r ", err);
1106                 uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1107                 return err;
1108         }
1109
1110         root_dir = LibOpenRoot(loaded_image->DeviceHandle);
1111         if (!root_dir) {
1112                 Print(L"Unable to open root directory: %r ", err);
1113                 uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1114                 return EFI_LOAD_ERROR;
1115         }
1116
1117         ZeroMem(&config, sizeof(Config));
1118
1119         /* scan "\loader\entries\*.conf" files */
1120         loaded_image_path = DevicePathToStr(loaded_image->FilePath);
1121         config_load(&config, root_dir, loaded_image_path);
1122         FreePool(loaded_image_path);
1123
1124         /* add fallback entry to the end of the list */
1125         config_entry_add_loader(&config, root_dir, L"fallback", L"EFI default loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
1126
1127         /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/
1128         config_default_entry_select(&config);
1129
1130         /* show menu when key is pressed or timeout is set */
1131         if (config.timeout_sec == 0) {
1132                 EFI_INPUT_KEY key;
1133
1134                 err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
1135                 menu = err != EFI_NOT_READY;
1136         } else
1137                 menu = TRUE;
1138
1139         for (;;) {
1140                 ConfigEntry *entry;
1141
1142                 entry = config.entries[config.idx_default];
1143                 if (menu) {
1144                         efivar_set_ticks(L"LoaderTicksStartMenu", 0);
1145                         menu_run(&config, &entry);
1146                 }
1147
1148                 /* export the selected boot entry to the system */
1149                 err = efivar_set(L"LoaderEntrySelected",  entry->file, FALSE);
1150                 if (EFI_ERROR(err)) {
1151                        Print(L"Error storing LoaderEntrySelected variable: %r ", err);
1152                        uefi_call_wrapper(BS->Stall, 1, 1000 * 1000);
1153                 }
1154
1155                 image_start(image, loaded_image, &config, entry);
1156
1157                 menu = TRUE;
1158                 config.timeout_sec = 0;
1159         }
1160
1161         uefi_call_wrapper(root_dir->Close, 1, root_dir);
1162         uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL);
1163         return EFI_SUCCESS;
1164 }