find Linux kernels with an embedded os-release file
authorKay Sievers <kay@vrfy.org>
Wed, 21 Jan 2015 23:03:42 +0000 (00:03 +0100)
committerKay Sievers <kay@vrfy.org>
Fri, 23 Jan 2015 17:54:02 +0000 (18:54 +0100)
Look for EFI executables at /EFI/Linux/*.efi, and add them to the
menu if the PE file contains an .osrel section which carries an
os-release file with the expected information.

Makefile.am
configure.ac
src/efi/graphics.c
src/efi/gummiboot.c
src/efi/pefile.c [new file with mode: 0644]
src/efi/pefile.h [new file with mode: 0644]
src/efi/util.c
src/efi/util.h
test/test-create-disk.sh

index bf6ef17..c4cda7f 100644 (file)
@@ -79,12 +79,14 @@ efi_sources = \
        src/efi/util.c \
        src/efi/console.c \
        src/efi/graphics.c \
+       src/efi/pefile.c \
        src/efi/gummiboot.c
 
 efi_headers = \
        src/efi/util.h \
        src/efi/console.h \
-       src/efi/graphics.h
+       src/efi/graphics.h \
+       src/efi/pefile.h
 
 efi_cppflags = \
        $(EFI_CPPFLAGS) \
index c3f57ad..13306f2 100644 (file)
@@ -68,7 +68,7 @@ AC_SUBST([MACHINE_TYPE_NAME])
 AS_IF([test x"$cross_compiling" = "xyes"], [], [
         AC_PATH_PROG([QEMU], [qemu-system-x86_64])
         AC_CHECK_FILE([/usr/share/qemu/bios-ovmf.bin], [QEMU_BIOS=/usr/share/qemu/bios-ovmf.bin])
-        AC_CHECK_FILE([/usr/share/qemu-ovmf/bios],     [QEMU_BIOS=/usr/share/qemu-ovmf/bios/bios.bin])
+        AC_CHECK_FILE([/usr/share/qemu-ovmf/bios.bin], [QEMU_BIOS=/usr/share/qemu-ovmf/bios.bin])
         AC_SUBST([QEMU_BIOS])
 ])
 
index 81b57e0..0810538 100644 (file)
@@ -340,7 +340,7 @@ EFI_STATUS graphics_splash(EFI_FILE *root_dir, CHAR16 *path,
         if (EFI_ERROR(err))
                 return err;
 
-        len = file_read(root_dir, path, &content);
+        len = file_read(root_dir, path, 0, 0, &content);
         if (len < 0)
                 return EFI_LOAD_ERROR;
 
index 7ab291c..f9da90d 100644 (file)
@@ -31,6 +31,7 @@
 #include "util.h"
 #include "console.h"
 #include "graphics.h"
+#include "pefile.h"
 
 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
@@ -991,7 +992,7 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
         return StrCmp(os1, os2);
 }
 
-static CHAR8 *line_get_key_value(CHAR8 *content, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) {
+static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) {
         CHAR8 *line;
         UINTN linelen;
         CHAR8 *value;
@@ -1024,7 +1025,7 @@ skip:
         }
 
         /* remove trailing whitespace */
-        while (linelen > 0 && strchra((CHAR8 *)" \t", line[linelen-1]))
+        while (linelen > 0 && strchra(sep, line[linelen-1]))
                 linelen--;
         line[linelen] = '\0';
 
@@ -1033,15 +1034,21 @@ skip:
 
         /* split key/value */
         value = line;
-        while (*value && !strchra((CHAR8 *)" \t", *value))
+        while (*value && !strchra(sep, *value))
                 value++;
         if (*value == '\0')
                 goto skip;
         *value = '\0';
         value++;
-        while (*value && strchra((CHAR8 *)" \t", *value))
+        while (*value && strchra(sep, *value))
                 value++;
 
+        /* unquote */
+        if (value[0] == '\"' && line[linelen-1] == '\"') {
+                value++;
+                line[linelen-1] = '\0';
+        }
+
         *key_ret = line;
         *value_ret = value;
         return line;
@@ -1053,7 +1060,7 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
         CHAR8 *key, *value;
 
         line = content;
-        while ((line = line_get_key_value(content, &pos, &key, &value))) {
+        while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) {
                 if (strcmpa((CHAR8 *)"timeout", key) == 0) {
                         CHAR16 *s;
 
@@ -1119,7 +1126,7 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
         entry = AllocateZeroPool(sizeof(ConfigEntry));
 
         line = content;
-        while ((line = line_get_key_value(content, &pos, &key, &value))) {
+        while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) {
                 if (strcmpa((CHAR8 *)"title", key) == 0) {
                         FreePool(entry->title);
                         entry->title = stra_to_str(value);
@@ -1290,7 +1297,7 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir,
         UINTN len;
         UINTN i;
 
-        len = file_read(root_dir, L"\\loader\\loader.conf", &content);
+        len = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content);
         if (len > 0)
                 config_defaults_load_from_file(config, content);
         FreePool(content);
@@ -1327,7 +1334,7 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir,
                         if (StriCmp(f->FileName + len - 5, L".conf") != 0)
                                 continue;
 
-                        len = file_read(entries_dir, f->FileName, &content);
+                        len = file_read(entries_dir, f->FileName, 0, 0, &content);
                         if (len > 0)
                                 config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path);
                         FreePool(content);
@@ -1562,11 +1569,26 @@ static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (
         return TRUE;
 }
 
-static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
-                                       CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
+static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
+        ConfigEntry *entry;
+
+        entry = AllocateZeroPool(sizeof(ConfigEntry));
+        entry->title = StrDuplicate(title);
+        entry->device = device;
+        entry->loader = StrDuplicate(loader);
+        entry->file = StrDuplicate(file);
+        StrLwr(entry->file);
+        entry->key = key;
+        config_add_entry(config, entry);
+
+        return entry;
+}
+
+static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
+                                         CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
         EFI_FILE_HANDLE handle;
-        EFI_STATUS err;
         ConfigEntry *entry;
+        EFI_STATUS err;
 
         /* do not add an entry for ourselves */
         if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0)
@@ -1578,29 +1600,16 @@ static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_F
                 return FALSE;
         uefi_call_wrapper(handle->Close, 1, handle);
 
-        entry = AllocateZeroPool(sizeof(ConfigEntry));
-        entry->title = StrDuplicate(title);
-        entry->device = device;
-        entry->loader = StrDuplicate(loader);
-        entry->file = StrDuplicate(file);
-        StrLwr(entry->file);
-        entry->key = key;
-        config_add_entry(config, entry);
+        entry = config_entry_add_loader(config, device, file, key, title, loader);
+        if (!entry)
+                return FALSE;
 
-        /* do not boot right away into aut-detected entries */
+        /* do not boot right away into auto-detected entries */
         entry->no_autoselect = TRUE;
 
         /* do not show a splash; they do not need one, or they draw their own */
         entry->splash = StrDuplicate(L"");
 
-        return TRUE;
-}
-
-static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
-                                         CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
-        if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, key, title, loader))
-                return FALSE;
-
         /* export identifiers of automatically added entries */
         if (config->entries_auto) {
                 CHAR16 *s;
@@ -1641,11 +1650,98 @@ static VOID config_entry_add_osx(Config *config) {
         }
 }
 
-static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) {
+static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) {
+        EFI_FILE_HANDLE linux_dir;
         EFI_STATUS err;
+
+        err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL);
+        if (!EFI_ERROR(err)) {
+                for (;;) {
+                        CHAR16 buf[256];
+                        UINTN bufsize;
+                        EFI_FILE_INFO *f;
+                        CHAR8 *sections[2] = { (UINT8 *)".osrel", NULL };
+                        UINTN offs[1] = {};
+                        UINTN szs[1] = {};
+                        UINTN addrs[1] = {};
+                        CHAR8 *content = NULL;
+                        UINTN len;
+                        CHAR8 *line;
+                        UINTN pos = 0;
+                        CHAR8 *key, *value;
+                        CHAR16 *os_name = NULL;
+                        CHAR16 *os_id = NULL;
+                        CHAR16 *os_version = NULL;
+
+                        bufsize = sizeof(buf);
+                        err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf);
+                        if (bufsize == 0 || EFI_ERROR(err))
+                                break;
+
+                        f = (EFI_FILE_INFO *) buf;
+                        if (f->FileName[0] == '.')
+                                continue;
+                        if (f->Attribute & EFI_FILE_DIRECTORY)
+                                continue;
+                        len = StrLen(f->FileName);
+                        if (len < 5)
+                                continue;
+                        if (StriCmp(f->FileName + len - 4, L".efi") != 0)
+                                continue;
+
+                        /* look for an .osrel section in the .efi binary */
+                        err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs);
+                        if (EFI_ERROR(err))
+                                continue;
+
+                        len = file_read(linux_dir, f->FileName, offs[0], szs[0], &content);
+                        if (len <= 0)
+                                continue;
+
+                        /* read properties from the embedded os-release file */
+                        line = content;
+                        while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) {
+                                if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) {
+                                        os_name = stra_to_str(value);
+                                        continue;
+                                }
+
+                                if (strcmpa((CHAR8 *)"ID", key) == 0) {
+                                        os_id = stra_to_str(value);
+                                        continue;
+                                }
+
+                                if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) {
+                                        os_version = stra_to_str(value);
+                                        continue;
+                                }
+                        }
+
+                        if (os_name && os_id && os_version) {
+                                CHAR16 *conf;
+                                CHAR16 *path;
+
+                                conf = PoolPrint(L"%s-%s", os_id, os_version);
+                                path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName);
+                                config_entry_add_loader(config, loaded_image->DeviceHandle, conf, 'l', os_name, path);
+                                FreePool(conf);
+                                FreePool(path);
+                                FreePool(os_name);
+                                FreePool(os_id);
+                                FreePool(os_version);
+                        }
+
+                        FreePool(content);
+                }
+                uefi_call_wrapper(linux_dir->Close, 1, linux_dir);
+        }
+}
+
+static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) {
         EFI_HANDLE image;
         EFI_DEVICE_PATH *path;
         CHAR16 *options;
+        EFI_STATUS err;
 
         path = FileDevicePath(entry->device, entry->loader);
         if (!path) {
@@ -1796,6 +1892,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                 return EFI_LOAD_ERROR;
         }
 
+
         /* the filesystem path to this image, to prevent adding ourselves to the menu */
         loaded_image_path = DevicePathToStr(loaded_image->FilePath);
         efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE);
@@ -1814,6 +1911,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         }
 
         /* if we find some well-known loaders, add them to the end of the list */
+        config_entry_add_linux(&config, loaded_image, root_dir);
         config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
                                      L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
         config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
diff --git a/src/efi/pefile.c b/src/efi/pefile.c
new file mode 100644 (file)
index 0000000..6ac28da
--- /dev/null
@@ -0,0 +1,174 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2012 Harald Hoyer <harald@redhat.com>
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "util.h"
+#include "pefile.h"
+
+struct DosFileHeader {
+        UINT8   Magic[2];
+        UINT16  LastSize;
+        UINT16  nBlocks;
+        UINT16  nReloc;
+        UINT16  HdrSize;
+        UINT16  MinAlloc;
+        UINT16  MaxAlloc;
+        UINT16  ss;
+        UINT16  sp;
+        UINT16  Checksum;
+        UINT16  ip;
+        UINT16  cs;
+        UINT16  RelocPos;
+        UINT16  nOverlay;
+        UINT16  reserved[4];
+        UINT16  OEMId;
+        UINT16  OEMInfo;
+        UINT16  reserved2[10];
+        UINT32  ExeHeader;
+} __attribute__((packed));
+
+#define PE_HEADER_MACHINE_I386          0x014c
+#define PE_HEADER_MACHINE_X64           0x8664
+struct PeFileHeader {
+        UINT16  Machine;
+        UINT16  NumberOfSections;
+        UINT32  TimeDateStamp;
+        UINT32  PointerToSymbolTable;
+        UINT32  NumberOfSymbols;
+        UINT16  SizeOfOptionalHeader;
+        UINT16  Characteristics;
+} __attribute__((packed));
+
+struct PeSectionHeader {
+        UINT8   Name[8];
+        UINT32  VirtualSize;
+        UINT32  VirtualAddress;
+        UINT32  SizeOfRawData;
+        UINT32  PointerToRawData;
+        UINT32  PointerToRelocations;
+        UINT32  PointerToLinenumbers;
+        UINT16  NumberOfRelocations;
+        UINT16  NumberOfLinenumbers;
+        UINT32  Characteristics;
+} __attribute__((packed));
+
+
+EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes) {
+        EFI_FILE_HANDLE handle;
+        struct DosFileHeader dos;
+        uint8_t magic[4];
+        struct PeFileHeader pe;
+        UINTN len;
+        UINTN i;
+        EFI_STATUS err;
+
+        err = uefi_call_wrapper(dir->Open, 5, dir, &handle, path, EFI_FILE_MODE_READ, 0ULL);
+        if (EFI_ERROR(err))
+                return err;
+
+        /* MS-DOS stub */
+        len = sizeof(dos);
+        err = uefi_call_wrapper(handle->Read, 3, handle, &len, &dos);
+        if (EFI_ERROR(err))
+                goto out;
+        if (len != sizeof(dos)) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        if (CompareMem(dos.Magic, "MZ", 2) != 0) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader);
+        if (EFI_ERROR(err))
+                goto out;
+
+        /* PE header */
+        len = sizeof(magic);
+        err = uefi_call_wrapper(handle->Read, 3, handle, &len, &magic);
+        if (EFI_ERROR(err))
+                goto out;
+        if (len != sizeof(magic)) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        if (CompareMem(magic, "PE\0\0", 2) != 0) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        len = sizeof(pe);
+        err = uefi_call_wrapper(handle->Read, 3, handle, &len, &pe);
+        if (EFI_ERROR(err))
+                goto out;
+        if (len != sizeof(pe)) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        /* PE32+ Subsystem type */
+        if (pe.Machine != PE_HEADER_MACHINE_X64 &&
+            pe.Machine != PE_HEADER_MACHINE_I386) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        if (pe.NumberOfSections > 96) {
+                err = EFI_LOAD_ERROR;
+                goto out;
+        }
+
+        /* the sections start directly after the headers */
+        err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader + sizeof(magic) + sizeof(pe) + pe.SizeOfOptionalHeader);
+        if (EFI_ERROR(err))
+                goto out;
+
+        for (i = 0; i < pe.NumberOfSections; i++) {
+                struct PeSectionHeader sect;
+                UINTN j;
+
+                len = sizeof(sect);
+                err = uefi_call_wrapper(handle->Read, 3, handle, &len, &sect);
+                if (EFI_ERROR(err))
+                        goto out;
+                if (len != sizeof(sect)) {
+                        err = EFI_LOAD_ERROR;
+                        goto out;
+                }
+
+                for (j = 0; sections[j]; j++) {
+                        if (strcmpa(sections[j], sect.Name) != 0)
+                                continue;
+
+                        if (addrs)
+                                addrs[j] = (UINTN)sect.VirtualAddress;
+                        if (offsets)
+                                offsets[j] = (UINTN)sect.PointerToRawData;
+                        if (sizes)
+                                sizes[j] = (UINTN)sect.VirtualSize;
+                }
+        }
+
+out:
+        uefi_call_wrapper(handle->Close, 1, handle);
+        return err;
+}
diff --git a/src/efi/pefile.h b/src/efi/pefile.h
new file mode 100644 (file)
index 0000000..3adf1b0
--- /dev/null
@@ -0,0 +1,22 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * Copyright (C) 2015 Kay Sievers <kay@vrfy.org>
+ */
+
+#ifndef __GUMMIBOOT_PEFILE_H
+#define __GUMMIBOOT_PEFILE_H
+
+EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path,
+                                  CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes);
+#endif
index 7cb8e0f..191c071 100644 (file)
@@ -280,9 +280,8 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
         return NULL;
 }
 
-INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
+INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content) {
         EFI_FILE_HANDLE handle;
-        EFI_FILE_INFO *info;
         CHAR8 *buf;
         UINTN buflen;
         EFI_STATUS err;
@@ -292,10 +291,22 @@ INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
         if (EFI_ERROR(err))
                 return err;
 
-        info = LibFileInfo(handle);
-        buflen = info->FileSize+1;
-        buf = AllocatePool(buflen);
+        if (size == 0) {
+                EFI_FILE_INFO *info;
+
+                info = LibFileInfo(handle);
+                buflen = info->FileSize+1;
+                FreePool(info);
+        } else
+                buflen = size;
 
+        if (off > 0) {
+                err = uefi_call_wrapper(handle->SetPosition, 2, handle, off);
+                if (EFI_ERROR(err))
+                        return err;
+        }
+
+        buf = AllocatePool(buflen);
         err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
         if (!EFI_ERROR(err)) {
                 buf[buflen] = '\0';
@@ -306,7 +317,6 @@ INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
                 FreePool(buf);
         }
 
-        FreePool(info);
         uefi_call_wrapper(handle->Close, 1, handle);
         return len;
 }
index ce767bb..91757c4 100644 (file)
@@ -40,5 +40,5 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c);
 CHAR16 *stra_to_path(CHAR8 *stra);
 CHAR16 *stra_to_str(CHAR8 *stra);
 
-INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content);
+INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content);
 #endif
index ead5129..3982223 100755 (executable)
@@ -18,6 +18,10 @@ cp test/splash.bmp mnt/EFI/gummiboot/
 
 [ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/
 
+mkdir mnt/EFI/Linux
+objcopy --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \
+  gummibootx64.efi mnt/EFI/Linux/Test-Linux.efi
+
 # install entries
 mkdir -p mnt/loader/entries
 echo -e "timeout 3\nsplash /EFI/gummiboot/splash.bmp\n" > mnt/loader/loader.conf