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.
###
diff --git a/Makefile.am b/Makefile.am
index bf6ef17..c4cda7f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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) \
diff --git a/configure.ac b/configure.ac
index c3f57ad..13306f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
])
diff --git a/src/efi/graphics.c b/src/efi/graphics.c
index 81b57e0..0810538 100644
--- a/src/efi/graphics.c
+++ b/src/efi/graphics.c
@@ -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;
diff --git a/src/efi/gummiboot.c b/src/efi/gummiboot.c
index 7ab291c..f9da90d 100644
--- a/src/efi/gummiboot.c
+++ b/src/efi/gummiboot.c
@@ -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
index 0000000..6ac28da
--- /dev/null
+++ b/src/efi/pefile.c
@@ -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
+ * Copyright (C) 2012 Harald Hoyer
+ */
+
+#include
+#include
+
+#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, §);
+ 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
index 0000000..3adf1b0
--- /dev/null
+++ b/src/efi/pefile.h
@@ -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
+ */
+
+#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
diff --git a/src/efi/util.c b/src/efi/util.c
index 7cb8e0f..191c071 100644
--- a/src/efi/util.c
+++ b/src/efi/util.c
@@ -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;
}
diff --git a/src/efi/util.h b/src/efi/util.h
index ce767bb..91757c4 100644
--- a/src/efi/util.h
+++ b/src/efi/util.h
@@ -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
diff --git a/test/test-create-disk.sh b/test/test-create-disk.sh
index ead5129..3982223 100755
--- a/test/test-create-disk.sh
+++ b/test/test-create-disk.sh
@@ -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
83c71c0 find Linux kernels with an embedded os-release file
Makefile.am | 4 +-
configure.ac | 2 +-
src/efi/graphics.c | 2 +-
src/efi/gummiboot.c | 156 +++++++++++++++++++++++++++++++++--------
src/efi/pefile.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++
src/efi/pefile.h | 22 ++++++
src/efi/util.c | 22 ++++--
src/efi/util.h | 2 +-
test/test-create-disk.sh | 4 ++
9 files changed, 349 insertions(+), 39 deletions(-)
Upstream: cgit.freedesktop.org