summaryrefslogtreecommitdiffstats
path: root/src/boot/efi/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/boot/efi/util.c')
-rw-r--r--src/boot/efi/util.c794
1 files changed, 794 insertions, 0 deletions
diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c
new file mode 100644
index 0000000..66056f0
--- /dev/null
+++ b/src/boot/efi/util.c
@@ -0,0 +1,794 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include <efilib.h>
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+#endif
+
+#include "ticks.h"
+#include "util.h"
+
+EFI_STATUS parse_boolean(const char *v, bool *b) {
+ assert(b);
+
+ if (!v)
+ return EFI_INVALID_PARAMETER;
+
+ if (streq8(v, "1") || streq8(v, "yes") || streq8(v, "y") || streq8(v, "true") || streq8(v, "t") ||
+ streq8(v, "on")) {
+ *b = true;
+ return EFI_SUCCESS;
+ }
+
+ if (streq8(v, "0") || streq8(v, "no") || streq8(v, "n") || streq8(v, "false") || streq8(v, "f") ||
+ streq8(v, "off")) {
+ *b = false;
+ return EFI_SUCCESS;
+ }
+
+ return EFI_INVALID_PARAMETER;
+}
+
+EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, UINTN size, uint32_t flags) {
+ assert(vendor);
+ assert(name);
+ assert(buf || size == 0);
+
+ flags |= EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS;
+ return RT->SetVariable((char16_t *) name, (EFI_GUID *) vendor, flags, size, (void *) buf);
+}
+
+EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags) {
+ assert(vendor);
+ assert(name);
+
+ return efivar_set_raw(vendor, name, value, value ? strsize16(value) : 0, flags);
+}
+
+EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, UINTN i, uint32_t flags) {
+ char16_t str[32];
+
+ assert(vendor);
+ assert(name);
+
+ /* Note that SPrint has no native sized length specifier and will always use ValueToString()
+ * regardless of what sign we tell it to use. Therefore, UINTN_MAX will come out as -1 on
+ * 64bit machines. */
+ ValueToString(str, false, i);
+ return efivar_set(vendor, name, str, flags);
+}
+
+EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t value, uint32_t flags) {
+ uint8_t buf[4];
+
+ assert(vendor);
+ assert(name);
+
+ buf[0] = (uint8_t)(value >> 0U & 0xFF);
+ buf[1] = (uint8_t)(value >> 8U & 0xFF);
+ buf[2] = (uint8_t)(value >> 16U & 0xFF);
+ buf[3] = (uint8_t)(value >> 24U & 0xFF);
+
+ return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
+}
+
+EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags) {
+ uint8_t buf[8];
+
+ assert(vendor);
+ assert(name);
+
+ buf[0] = (uint8_t)(value >> 0U & 0xFF);
+ buf[1] = (uint8_t)(value >> 8U & 0xFF);
+ buf[2] = (uint8_t)(value >> 16U & 0xFF);
+ buf[3] = (uint8_t)(value >> 24U & 0xFF);
+ buf[4] = (uint8_t)(value >> 32U & 0xFF);
+ buf[5] = (uint8_t)(value >> 40U & 0xFF);
+ buf[6] = (uint8_t)(value >> 48U & 0xFF);
+ buf[7] = (uint8_t)(value >> 56U & 0xFF);
+
+ return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
+}
+
+EFI_STATUS efivar_get(const EFI_GUID *vendor, const char16_t *name, char16_t **value) {
+ _cleanup_free_ char16_t *buf = NULL;
+ EFI_STATUS err;
+ char16_t *val;
+ UINTN size;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, (char **) &buf, &size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Make sure there are no incomplete characters in the buffer */
+ if ((size % sizeof(char16_t)) != 0)
+ return EFI_INVALID_PARAMETER;
+
+ if (!value)
+ return EFI_SUCCESS;
+
+ /* Return buffer directly if it happens to be NUL terminated already */
+ if (size >= sizeof(char16_t) && buf[size / sizeof(char16_t) - 1] == 0) {
+ *value = TAKE_PTR(buf);
+ return EFI_SUCCESS;
+ }
+
+ /* Make sure a terminating NUL is available at the end */
+ val = xmalloc(size + sizeof(char16_t));
+
+ memcpy(val, buf, size);
+ val[size / sizeof(char16_t) - 1] = 0; /* NUL terminate */
+
+ *value = val;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_uint_string(const EFI_GUID *vendor, const char16_t *name, UINTN *i) {
+ _cleanup_free_ char16_t *val = NULL;
+ EFI_STATUS err;
+ uint64_t u;
+
+ assert(vendor);
+ assert(name);
+ assert(i);
+
+ err = efivar_get(vendor, name, &val);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (!parse_number16(val, &u, NULL) || u > UINTN_MAX)
+ return EFI_INVALID_PARAMETER;
+
+ *i = u;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+ UINTN size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, &buf, &size);
+ if (err == EFI_SUCCESS && ret) {
+ if (size != sizeof(uint32_t))
+ return EFI_BUFFER_TOO_SMALL;
+
+ *ret = (uint32_t) buf[0] << 0U | (uint32_t) buf[1] << 8U | (uint32_t) buf[2] << 16U |
+ (uint32_t) buf[3] << 24U;
+ }
+
+ return err;
+}
+
+EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+ UINTN size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, &buf, &size);
+ if (err == EFI_SUCCESS && ret) {
+ if (size != sizeof(uint64_t))
+ return EFI_BUFFER_TOO_SMALL;
+
+ *ret = (uint64_t) buf[0] << 0U | (uint64_t) buf[1] << 8U | (uint64_t) buf[2] << 16U |
+ (uint64_t) buf[3] << 24U | (uint64_t) buf[4] << 32U | (uint64_t) buf[5] << 40U |
+ (uint64_t) buf[6] << 48U | (uint64_t) buf[7] << 56U;
+ }
+
+ return err;
+}
+
+EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, char **buffer, UINTN *size) {
+ _cleanup_free_ char *buf = NULL;
+ UINTN l;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ l = sizeof(char16_t *) * EFI_MAXIMUM_VARIABLE_SIZE;
+ buf = xmalloc(l);
+
+ err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &l, buf);
+ if (err == EFI_SUCCESS) {
+
+ if (buffer)
+ *buffer = TAKE_PTR(buf);
+
+ if (size)
+ *size = l;
+ }
+
+ return err;
+}
+
+EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, bool *ret) {
+ _cleanup_free_ char *b = NULL;
+ UINTN size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+ assert(ret);
+
+ err = efivar_get_raw(vendor, name, &b, &size);
+ if (err == EFI_SUCCESS)
+ *ret = *b > 0;
+
+ return err;
+}
+
+void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec) {
+ char16_t str[32];
+
+ assert(vendor);
+ assert(name);
+
+ if (usec == 0)
+ usec = time_usec();
+ if (usec == 0)
+ return;
+
+ /* See comment on ValueToString in efivar_set_uint_string(). */
+ ValueToString(str, false, usec);
+ efivar_set(vendor, name, str, 0);
+}
+
+void convert_efi_path(char16_t *path) {
+ assert(path);
+
+ for (size_t i = 0, fixed = 0;; i++) {
+ /* Fix device path node separator. */
+ path[fixed] = (path[i] == '/') ? '\\' : path[i];
+
+ /* Double '\' is not allowed in EFI file paths. */
+ if (fixed > 0 && path[fixed - 1] == '\\' && path[fixed] == '\\')
+ continue;
+
+ if (path[i] == '\0')
+ break;
+
+ fixed++;
+ }
+}
+
+char16_t *xstr8_to_path(const char *str8) {
+ assert(str8);
+ char16_t *path = xstr8_to_16(str8);
+ convert_efi_path(path);
+ return path;
+}
+
+void mangle_stub_cmdline(char16_t *cmdline) {
+ char16_t *p = cmdline;
+
+ for (; *cmdline != '\0'; cmdline++)
+ /* Convert ASCII control characters to spaces. */
+ if (*cmdline <= 0x1F)
+ *cmdline = ' ';
+
+ /* chomp the trailing whitespaces */
+ while (cmdline != p) {
+ --cmdline;
+
+ if (*cmdline != ' ')
+ break;
+
+ *cmdline = '\0';
+ }
+}
+
+EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) {
+ EFI_STATUS err;
+
+ assert(file);
+ assert(size);
+ assert(buf);
+
+ /* This is a drop-in replacement for EFI_FILE->Read() with the same API behavior.
+ * Some broken firmwares cannot handle large file reads and will instead return
+ * an error. As a workaround, read such files in small chunks.
+ * Note that we cannot just try reading the whole file first on such firmware as
+ * that will permanently break the handle even if it is re-opened.
+ *
+ * https://github.com/systemd/systemd/issues/25911 */
+
+ if (*size == 0)
+ return EFI_SUCCESS;
+
+ size_t read = 0, remaining = *size;
+ while (remaining > 0) {
+ size_t chunk = MIN(1024U * 1024U, remaining);
+
+ err = file->Read(file, &chunk, (uint8_t *) buf + read);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (chunk == 0)
+ /* Caller requested more bytes than are in file. */
+ break;
+
+ assert(chunk <= remaining);
+ read += chunk;
+ remaining -= chunk;
+ }
+
+ *size = read;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size, char **ret, UINTN *ret_size) {
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ _cleanup_free_ char *buf = NULL;
+ EFI_STATUS err;
+
+ assert(dir);
+ assert(name);
+ assert(ret);
+
+ err = dir->Open(dir, &handle, (char16_t*) name, EFI_FILE_MODE_READ, 0ULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (size == 0) {
+ _cleanup_free_ EFI_FILE_INFO *info = NULL;
+
+ err = get_file_info_harder(handle, &info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ size = info->FileSize;
+ }
+
+ if (off > 0) {
+ err = handle->SetPosition(handle, off);
+ if (err != EFI_SUCCESS)
+ return err;
+ }
+
+ /* Allocate some extra bytes to guarantee the result is NUL-terminated for char and char16_t strings. */
+ UINTN extra = size % sizeof(char16_t) + sizeof(char16_t);
+
+ buf = xmalloc(size + extra);
+ err = chunked_read(handle, &size, buf);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Note that chunked_read() changes size to reflect the actual bytes read. */
+ memset(buf + size, 0, extra);
+
+ *ret = TAKE_PTR(buf);
+ if (ret_size)
+ *ret_size = size;
+
+ return err;
+}
+
+void log_error_stall(const char16_t *fmt, ...) {
+ va_list args;
+
+ assert(fmt);
+
+ int32_t attr = ST->ConOut->Mode->Attribute;
+ ST->ConOut->SetAttribute(ST->ConOut, EFI_LIGHTRED|EFI_BACKGROUND_BLACK);
+
+ if (ST->ConOut->Mode->CursorColumn > 0)
+ Print(L"\n");
+
+ va_start(args, fmt);
+ VPrint(fmt, args);
+ va_end(args);
+
+ Print(L"\n");
+
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+
+ /* Give the user a chance to see the message. */
+ BS->Stall(3 * 1000 * 1000);
+}
+
+EFI_STATUS log_oom(void) {
+ log_error_stall(L"Out of memory.");
+ return EFI_OUT_OF_RESOURCES;
+}
+
+void print_at(UINTN x, UINTN y, UINTN attr, const char16_t *str) {
+ assert(str);
+ ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) str);
+}
+
+void clear_screen(UINTN attr) {
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+ ST->ConOut->ClearScreen(ST->ConOut);
+}
+
+void sort_pointer_array(
+ void **array,
+ UINTN n_members,
+ compare_pointer_func_t compare) {
+
+ assert(array || n_members == 0);
+ assert(compare);
+
+ if (n_members <= 1)
+ return;
+
+ for (UINTN i = 1; i < n_members; i++) {
+ UINTN k;
+ void *entry = array[i];
+
+ for (k = i; k > 0; k--) {
+ if (compare(array[k - 1], entry) <= 0)
+ break;
+
+ array[k] = array[k - 1];
+ }
+
+ array[k] = entry;
+ }
+}
+
+EFI_STATUS get_file_info_harder(
+ EFI_FILE *handle,
+ EFI_FILE_INFO **ret,
+ UINTN *ret_size) {
+
+ UINTN size = offsetof(EFI_FILE_INFO, FileName) + 256;
+ _cleanup_free_ EFI_FILE_INFO *fi = NULL;
+ EFI_STATUS err;
+
+ assert(handle);
+ assert(ret);
+
+ /* A lot like LibFileInfo() but with useful error propagation */
+
+ fi = xmalloc(size);
+ err = handle->GetInfo(handle, &GenericFileInfo, &size, fi);
+ if (err == EFI_BUFFER_TOO_SMALL) {
+ free(fi);
+ fi = xmalloc(size); /* GetInfo tells us the required size, let's use that now */
+ err = handle->GetInfo(handle, &GenericFileInfo, &size, fi);
+ }
+
+ if (err != EFI_SUCCESS)
+ return err;
+
+ *ret = TAKE_PTR(fi);
+
+ if (ret_size)
+ *ret_size = size;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS readdir_harder(
+ EFI_FILE *handle,
+ EFI_FILE_INFO **buffer,
+ UINTN *buffer_size) {
+
+ EFI_STATUS err;
+ UINTN sz;
+
+ assert(handle);
+ assert(buffer);
+ assert(buffer_size);
+
+ /* buffer/buffer_size are both in and output parameters. Should be zero-initialized initially, and
+ * the specified buffer needs to be freed by caller, after final use. */
+
+ if (!*buffer) {
+ /* Some broken firmware violates the EFI spec by still advancing the readdir
+ * position when returning EFI_BUFFER_TOO_SMALL, effectively skipping over any files when
+ * the buffer was too small. Therefore, start with a buffer that should handle FAT32 max
+ * file name length.
+ * As a side effect, most readdir_harder() calls will now be slightly faster. */
+ sz = sizeof(EFI_FILE_INFO) + 256 * sizeof(char16_t);
+ *buffer = xmalloc(sz);
+ *buffer_size = sz;
+ } else
+ sz = *buffer_size;
+
+ err = handle->Read(handle, &sz, *buffer);
+ if (err == EFI_BUFFER_TOO_SMALL) {
+ free(*buffer);
+ *buffer = xmalloc(sz);
+ *buffer_size = sz;
+ err = handle->Read(handle, &sz, *buffer);
+ }
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (sz == 0) {
+ /* End of directory */
+ free(*buffer);
+ *buffer = NULL;
+ *buffer_size = 0;
+ }
+
+ return EFI_SUCCESS;
+}
+
+bool is_ascii(const char16_t *f) {
+ if (!f)
+ return false;
+
+ for (; *f != 0; f++)
+ if (*f > 127)
+ return false;
+
+ return true;
+}
+
+char16_t **strv_free(char16_t **v) {
+ if (!v)
+ return NULL;
+
+ for (char16_t **i = v; *i; i++)
+ free(*i);
+
+ free(v);
+ return NULL;
+}
+
+EFI_STATUS open_directory(
+ EFI_FILE *root,
+ const char16_t *path,
+ EFI_FILE **ret) {
+
+ _cleanup_(file_closep) EFI_FILE *dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *file_info = NULL;
+ EFI_STATUS err;
+
+ assert(root);
+
+ /* Opens a file, and then verifies it is actually a directory */
+
+ err = root->Open(root, &dir, (char16_t *) path, EFI_FILE_MODE_READ, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = get_file_info_harder(dir, &file_info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (!FLAGS_SET(file_info->Attribute, EFI_FILE_DIRECTORY))
+ return EFI_LOAD_ERROR;
+
+ *ret = TAKE_PTR(dir);
+ return EFI_SUCCESS;
+}
+
+uint64_t get_os_indications_supported(void) {
+ uint64_t osind;
+ EFI_STATUS err;
+
+ /* Returns the supported OS indications. If we can't acquire it, returns a zeroed out mask, i.e. no
+ * supported features. */
+
+ err = efivar_get_uint64_le(EFI_GLOBAL_GUID, L"OsIndicationsSupported", &osind);
+ if (err != EFI_SUCCESS)
+ return 0;
+
+ return osind;
+}
+
+#ifdef EFI_DEBUG
+__attribute__((noinline)) void debug_break(void) {
+ /* This is a poor programmer's breakpoint to wait until a debugger
+ * has attached to us. Just "set variable wait = 0" or "return" to continue. */
+ volatile bool wait = true;
+ while (wait)
+ /* Prefer asm based stalling so that gdb has a source location to present. */
+#if defined(__i386__) || defined(__x86_64__)
+ asm volatile("pause");
+#elif defined(__aarch64__)
+ asm volatile("wfi");
+#else
+ BS->Stall(5000);
+#endif
+}
+#endif
+
+
+#ifdef EFI_DEBUG
+void hexdump(const char16_t *prefix, const void *data, UINTN size) {
+ static const char hex[16] = "0123456789abcdef";
+ _cleanup_free_ char16_t *buf = NULL;
+ const uint8_t *d = data;
+
+ assert(prefix);
+ assert(data || size == 0);
+
+ /* Debugging helper — please keep this around, even if not used */
+
+ buf = xnew(char16_t, size*2+1);
+
+ for (UINTN i = 0; i < size; i++) {
+ buf[i*2] = hex[d[i] >> 4];
+ buf[i*2+1] = hex[d[i] & 0x0F];
+ }
+
+ buf[size*2] = 0;
+
+ log_error_stall(L"%s[%" PRIuN "]: %s", prefix, size, buf);
+}
+#endif
+
+#if defined(__i386__) || defined(__x86_64__)
+static inline uint8_t inb(uint16_t port) {
+ uint8_t value;
+ asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
+ return value;
+}
+
+static inline void outb(uint16_t port, uint8_t value) {
+ asm volatile("outb %0, %1" : : "a"(value), "Nd"(port));
+}
+
+void beep(UINTN beep_count) {
+ enum {
+ PITCH = 500,
+ BEEP_DURATION_USEC = 100 * 1000,
+ WAIT_DURATION_USEC = 400 * 1000,
+
+ PIT_FREQUENCY = 0x1234dd,
+ SPEAKER_CONTROL_PORT = 0x61,
+ SPEAKER_ON_MASK = 0x03,
+ TIMER_PORT_MAGIC = 0xB6,
+ TIMER_CONTROL_PORT = 0x43,
+ TIMER_CONTROL2_PORT = 0x42,
+ };
+
+ /* Set frequency. */
+ uint32_t counter = PIT_FREQUENCY / PITCH;
+ outb(TIMER_CONTROL_PORT, TIMER_PORT_MAGIC);
+ outb(TIMER_CONTROL2_PORT, counter & 0xFF);
+ outb(TIMER_CONTROL2_PORT, (counter >> 8) & 0xFF);
+
+ uint8_t value = inb(SPEAKER_CONTROL_PORT);
+
+ while (beep_count > 0) {
+ /* Turn speaker on. */
+ value |= SPEAKER_ON_MASK;
+ outb(SPEAKER_CONTROL_PORT, value);
+
+ BS->Stall(BEEP_DURATION_USEC);
+
+ /* Turn speaker off. */
+ value &= ~SPEAKER_ON_MASK;
+ outb(SPEAKER_CONTROL_PORT, value);
+
+ beep_count--;
+ if (beep_count > 0)
+ BS->Stall(WAIT_DURATION_USEC);
+ }
+}
+#endif
+
+EFI_STATUS open_volume(EFI_HANDLE device, EFI_FILE **ret_file) {
+ EFI_STATUS err;
+ EFI_FILE *file;
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *volume;
+
+ assert(ret_file);
+
+ err = BS->HandleProtocol(device, &FileSystemProtocol, (void **) &volume);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = volume->OpenVolume(volume, &file);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ *ret_file = file;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp) {
+ EFI_STATUS err;
+ EFI_DEVICE_PATH *dp;
+
+ assert(file);
+ assert(ret_dp);
+
+ err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &dp);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ EFI_DEVICE_PATH *end_node = dp;
+ while (!IsDevicePathEnd(end_node))
+ end_node = NextDevicePathNode(end_node);
+
+ size_t file_size = strsize16(file);
+ size_t dp_size = (uint8_t *) end_node - (uint8_t *) dp;
+
+ /* Make a copy that can also hold a file media device path. */
+ *ret_dp = xmalloc(dp_size + file_size + SIZE_OF_FILEPATH_DEVICE_PATH + END_DEVICE_PATH_LENGTH);
+ dp = mempcpy(*ret_dp, dp, dp_size);
+
+ /* Replace end node with file media device path. Use memcpy() in case dp is unaligned (if accessed as
+ * FILEPATH_DEVICE_PATH). */
+ dp->Type = MEDIA_DEVICE_PATH;
+ dp->SubType = MEDIA_FILEPATH_DP;
+ memcpy((uint8_t *) dp + offsetof(FILEPATH_DEVICE_PATH, PathName), file, file_size);
+ SetDevicePathNodeLength(dp, offsetof(FILEPATH_DEVICE_PATH, PathName) + file_size);
+
+ dp = NextDevicePathNode(dp);
+ SetDevicePathEndNode(dp);
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret) {
+ EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *dp_to_text;
+ EFI_STATUS err;
+ _cleanup_free_ char16_t *str = NULL;
+
+ assert(dp);
+ assert(ret);
+
+ err = BS->LocateProtocol(&(EFI_GUID) EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID, NULL, (void **) &dp_to_text);
+ if (err != EFI_SUCCESS) {
+ /* If the device path to text protocol is not available we can still do a best-effort attempt
+ * to convert it ourselves if we are given filepath-only device path. */
+
+ size_t size = 0;
+ for (const EFI_DEVICE_PATH *node = dp; !IsDevicePathEnd(node);
+ node = NextDevicePathNode(node)) {
+
+ if (DevicePathType(node) != MEDIA_DEVICE_PATH ||
+ DevicePathSubType(node) != MEDIA_FILEPATH_DP)
+ return err;
+
+ size_t path_size = DevicePathNodeLength(node);
+ if (path_size <= offsetof(FILEPATH_DEVICE_PATH, PathName) || path_size % sizeof(char16_t))
+ return EFI_INVALID_PARAMETER;
+ path_size -= offsetof(FILEPATH_DEVICE_PATH, PathName);
+
+ _cleanup_free_ char16_t *old = str;
+ str = xmalloc(size + path_size);
+ if (old) {
+ memcpy(str, old, size);
+ str[size / sizeof(char16_t) - 1] = '\\';
+ }
+
+ memcpy(str + (size / sizeof(char16_t)),
+ ((uint8_t *) node) + offsetof(FILEPATH_DEVICE_PATH, PathName),
+ path_size);
+ size += path_size;
+ }
+
+ *ret = TAKE_PTR(str);
+ return EFI_SUCCESS;
+ }
+
+ str = dp_to_text->ConvertDevicePathToText(dp, false, false);
+ if (!str)
+ return EFI_OUT_OF_RESOURCES;
+
+ *ret = TAKE_PTR(str);
+ return EFI_SUCCESS;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+bool in_hypervisor(void) {
+ uint32_t eax, ebx, ecx, edx;
+
+ /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
+ * environment. */
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
+ return false;
+
+ return !!(ecx & 0x80000000U);
+}
+#endif