mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-23 23:05:39 +00:00

Shim has had a hard time with loading updates from arguments in the most recent release and this isn't the first time that happened. Give distros and users an escape hatch that will allow using GRUB instead.
290 lines
7.9 KiB
C
290 lines
7.9 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2015 Peter Jones <pjones@redhat.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <efivar.h>
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-uefi-common.h"
|
|
#include "fu-uefi-device.h"
|
|
|
|
static const gchar *
|
|
fu_uefi_bootmgr_get_suffix (GError **error)
|
|
{
|
|
guint64 firmware_bits;
|
|
struct {
|
|
guint64 bits;
|
|
const gchar *arch;
|
|
} suffixes[] = {
|
|
#if defined(__x86_64__)
|
|
{ 64, "x64" },
|
|
#elif defined(__aarch64__)
|
|
{ 64, "aa64" },
|
|
#endif
|
|
#if defined(__x86_64__) || defined(__i386__) || defined(__i686__)
|
|
{ 32, "ia32" },
|
|
#endif
|
|
{ 0, NULL }
|
|
};
|
|
g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
|
|
g_autofree gchar *sysfsefidir = g_build_filename (sysfsfwdir, "efi", NULL);
|
|
firmware_bits = fu_uefi_read_file_as_uint64 (sysfsefidir, "fw_platform_size");
|
|
if (firmware_bits == 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"%s/fw_platform_size cannot be found",
|
|
sysfsefidir);
|
|
return NULL;
|
|
}
|
|
for (guint i = 0; suffixes[i].arch != NULL; i++) {
|
|
if (firmware_bits != suffixes[i].bits)
|
|
continue;
|
|
return suffixes[i].arch;
|
|
}
|
|
|
|
/* this should exist */
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"%s/fw_platform_size has unknown value %" G_GUINT64_FORMAT,
|
|
sysfsefidir, firmware_bits);
|
|
return NULL;
|
|
}
|
|
|
|
gchar *
|
|
fu_uefi_get_esp_app_path (FuDevice *device,
|
|
const gchar *esp_path,
|
|
const gchar *cmd,
|
|
GError **error)
|
|
{
|
|
const gchar *suffix = fu_uefi_bootmgr_get_suffix (error);
|
|
g_autofree gchar *base = NULL;
|
|
if (suffix == NULL)
|
|
return NULL;
|
|
base = fu_uefi_get_esp_path_for_os (device, esp_path);
|
|
return g_strdup_printf ("%s/%s%s.efi", base, cmd, suffix);
|
|
}
|
|
|
|
gchar *
|
|
fu_uefi_get_built_app_path (GError **error)
|
|
{
|
|
const gchar *extension = "";
|
|
const gchar *suffix;
|
|
g_autofree gchar *source_path = NULL;
|
|
g_autofree gchar *prefix = NULL;
|
|
if (fu_efivar_secure_boot_enabled ())
|
|
extension = ".signed";
|
|
suffix = fu_uefi_bootmgr_get_suffix (error);
|
|
if (suffix == NULL)
|
|
return NULL;
|
|
prefix = fu_common_get_path (FU_PATH_KIND_EFIAPPDIR);
|
|
source_path = g_strdup_printf ("%s/fwupd%s.efi%s",
|
|
prefix,
|
|
suffix,
|
|
extension);
|
|
if (!g_file_test (source_path, G_FILE_TEST_EXISTS)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"%s cannot be found",
|
|
source_path);
|
|
return NULL;
|
|
}
|
|
return g_steal_pointer (&source_path);
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_get_framebuffer_size (guint32 *width, guint32 *height, GError **error)
|
|
{
|
|
guint32 height_tmp;
|
|
guint32 width_tmp;
|
|
g_autofree gchar *sysfsdriverdir = NULL;
|
|
g_autofree gchar *fbdir = NULL;
|
|
|
|
sysfsdriverdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_DRIVERS);
|
|
fbdir = g_build_filename (sysfsdriverdir, "efi-framebuffer", "efi-framebuffer.0", NULL);
|
|
if (!g_file_test (fbdir, G_FILE_TEST_EXISTS)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"EFI framebuffer not found");
|
|
return FALSE;
|
|
}
|
|
height_tmp = fu_uefi_read_file_as_uint64 (fbdir, "height");
|
|
width_tmp = fu_uefi_read_file_as_uint64 (fbdir, "width");
|
|
if (width_tmp == 0 || height_tmp == 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"EFI framebuffer has invalid size "
|
|
"%"G_GUINT32_FORMAT"x%"G_GUINT32_FORMAT,
|
|
width_tmp, height_tmp);
|
|
return FALSE;
|
|
}
|
|
if (width != NULL)
|
|
*width = width_tmp;
|
|
if (height != NULL)
|
|
*height = height_tmp;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_get_bitmap_size (const guint8 *buf,
|
|
gsize bufsz,
|
|
guint32 *width,
|
|
guint32 *height,
|
|
GError **error)
|
|
{
|
|
guint32 ui32;
|
|
|
|
g_return_val_if_fail (buf != NULL, FALSE);
|
|
|
|
/* check header */
|
|
if (bufsz < 26 || memcmp (buf, "BM", 2) != 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid BMP header signature");
|
|
return FALSE;
|
|
}
|
|
|
|
/* starting address */
|
|
if (!fu_common_read_uint32_safe (buf, bufsz, 10, &ui32, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
if (ui32 < 26) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"BMP header invalid @ %"G_GUINT32_FORMAT"x", ui32);
|
|
return FALSE;
|
|
}
|
|
|
|
/* BITMAPINFOHEADER header */
|
|
if (!fu_common_read_uint32_safe (buf, bufsz, 14, &ui32, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
if (ui32 < 26 - 14) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"BITMAPINFOHEADER invalid @ %"G_GUINT32_FORMAT"x", ui32);
|
|
return FALSE;
|
|
}
|
|
|
|
/* dimensions */
|
|
if (width != NULL) {
|
|
if (!fu_common_read_uint32_safe (buf, bufsz, 18, width,
|
|
G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
}
|
|
if (height != NULL) {
|
|
if (!fu_common_read_uint32_safe (buf, bufsz, 22, height,
|
|
G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gchar *
|
|
fu_uefi_get_esp_path_for_os (FuDevice *device, const gchar *base)
|
|
{
|
|
#ifndef EFI_OS_DIR
|
|
const gchar *os_release_id = NULL;
|
|
const gchar *id_like = NULL;
|
|
g_autofree gchar *esp_path = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GHashTable) os_release = fwupd_get_os_release (&error_local);
|
|
/* try to lookup /etc/os-release ID key */
|
|
if (os_release != NULL) {
|
|
os_release_id = g_hash_table_lookup (os_release, "ID");
|
|
} else {
|
|
g_debug ("failed to get ID: %s", error_local->message);
|
|
}
|
|
if (os_release_id == NULL)
|
|
os_release_id = "unknown";
|
|
/* if ID key points at something existing return it */
|
|
esp_path = g_build_filename (base, "EFI", os_release_id, NULL);
|
|
if (g_file_test (esp_path, G_FILE_TEST_IS_DIR) || os_release == NULL)
|
|
return g_steal_pointer (&esp_path);
|
|
/* if ID key doesn't exist, try ID_LIKE */
|
|
id_like = g_hash_table_lookup (os_release, "ID_LIKE");
|
|
if (id_like != NULL) {
|
|
g_auto(GStrv) split = g_strsplit (id_like, " ", -1);
|
|
for (guint i = 0; split[i] != NULL; i++) {
|
|
g_autofree gchar *id_like_path = g_build_filename (base, "EFI", split[i], NULL);
|
|
if (g_file_test (id_like_path, G_FILE_TEST_IS_DIR)) {
|
|
g_debug ("Using ID_LIKE key from os-release");
|
|
return g_steal_pointer (&id_like_path);
|
|
}
|
|
}
|
|
}
|
|
/* try to fallback to use UEFI removable path if ID_LIKE path doesn't exist */
|
|
if (fu_device_has_private_flag (device, FU_UEFI_DEVICE_FLAG_FALLBACK_TO_REMOVABLE_PATH)) {
|
|
esp_path = g_build_filename (base, "EFI", "boot", NULL);
|
|
if (!g_file_test (esp_path, G_FILE_TEST_IS_DIR))
|
|
g_debug ("failed to fallback due to missing %s", esp_path);
|
|
}
|
|
return g_steal_pointer (&esp_path);
|
|
#else
|
|
return g_build_filename (base, "EFI", EFI_OS_DIR, NULL);
|
|
#endif
|
|
}
|
|
|
|
guint64
|
|
fu_uefi_read_file_as_uint64 (const gchar *path, const gchar *attr_name)
|
|
{
|
|
g_autofree gchar *data = NULL;
|
|
g_autofree gchar *fn = g_build_filename (path, attr_name, NULL);
|
|
if (!g_file_get_contents (fn, &data, NULL, NULL))
|
|
return 0x0;
|
|
return fu_common_strtoull (data);
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_cmp_asset(const gchar *source, const gchar *target)
|
|
{
|
|
gsize len = 0;
|
|
g_autofree gchar *source_csum = NULL;
|
|
g_autofree gchar *source_data = NULL;
|
|
g_autofree gchar *target_csum = NULL;
|
|
g_autofree gchar *target_data = NULL;
|
|
|
|
/* nothing in target yet */
|
|
if (!g_file_test(target, G_FILE_TEST_EXISTS))
|
|
return FALSE;
|
|
|
|
/* test if the file needs to be updated */
|
|
if (!g_file_get_contents(source, &source_data, &len, NULL))
|
|
return FALSE;
|
|
source_csum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)source_data, len);
|
|
if (!g_file_get_contents(target, &target_data, &len, NULL))
|
|
return FALSE;
|
|
target_csum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)target_data, len);
|
|
return g_strcmp0(target_csum, source_csum) == 0;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_copy_asset(const gchar *source, const gchar *target, GError **error)
|
|
{
|
|
g_autoptr(GFile) source_file = g_file_new_for_path(source);
|
|
g_autoptr(GFile) target_file = g_file_new_for_path(target);
|
|
|
|
if (!g_file_copy(source_file,
|
|
target_file,
|
|
G_FILE_COPY_OVERWRITE,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to copy %s to %s: ", source, target);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|