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

systemd-automount will unmount the ESP when not in use for some people. This causes automatic ESP detection to fail. In this case the ESP will need to be added to the conf file and then this commit will let it keep working.
364 lines
9.4 KiB
C
364 lines
9.4 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2015-2017 Peter Jones <pjones@redhat.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <efivar.h>
|
|
#include <gio/gio.h>
|
|
#include <gio/gunixmounts.h>
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-uefi-common.h"
|
|
#include "fu-uefi-vars.h"
|
|
|
|
#include "fwupd-common.h"
|
|
#include "fwupd-error.h"
|
|
|
|
#ifndef HAVE_GIO_2_55_0
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free)
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
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 (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 (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_uefi_secure_boot_enabled ())
|
|
extension = ".signed";
|
|
if (g_file_test (EFI_APP_LOCATION_BUILD, G_FILE_TEST_EXISTS))
|
|
return g_strdup_printf ("%s%s", EFI_APP_LOCATION_BUILD, extension);
|
|
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) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"blob was too small %" G_GSIZE_FORMAT, bufsz);
|
|
return FALSE;
|
|
}
|
|
if (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 */
|
|
ui32 = fu_common_read_uint32 (buf + 10, G_LITTLE_ENDIAN);
|
|
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 */
|
|
ui32 = fu_common_read_uint32 (buf + 14, G_LITTLE_ENDIAN);
|
|
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)
|
|
*width = fu_common_read_uint32 (buf + 18, G_LITTLE_ENDIAN);
|
|
if (height != NULL)
|
|
*height = fu_common_read_uint32 (buf + 22, G_LITTLE_ENDIAN);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_secure_boot_enabled (void)
|
|
{
|
|
gsize data_size = 0;
|
|
g_autofree guint8 *data = NULL;
|
|
|
|
if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_EFI_GLOBAL, "SecureBoot",
|
|
&data, &data_size, NULL, NULL))
|
|
return FALSE;
|
|
if (data_size >= 1 && data[0] & 1)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
fu_uefi_strcmp_sort_cb (gconstpointer a, gconstpointer b)
|
|
{
|
|
const gchar *stra = *((const gchar **) a);
|
|
const gchar *strb = *((const gchar **) b);
|
|
return g_strcmp0 (stra, strb);
|
|
}
|
|
|
|
GPtrArray *
|
|
fu_uefi_get_esrt_entry_paths (const gchar *esrt_path, GError **error)
|
|
{
|
|
GPtrArray *entries = g_ptr_array_new_with_free_func (g_free);
|
|
const gchar *fn;
|
|
g_autofree gchar *esrt_entries = NULL;
|
|
g_autoptr(GDir) dir = NULL;
|
|
|
|
/* search ESRT */
|
|
esrt_entries = g_build_filename (esrt_path, "entries", NULL);
|
|
dir = g_dir_open (esrt_entries, 0, error);
|
|
if (dir == NULL)
|
|
return NULL;
|
|
while ((fn = g_dir_read_name (dir)) != NULL)
|
|
g_ptr_array_add (entries, g_build_filename (esrt_entries, fn, NULL));
|
|
|
|
/* sort by name */
|
|
g_ptr_array_sort (entries, fu_uefi_strcmp_sort_cb);
|
|
return entries;
|
|
}
|
|
|
|
gchar *
|
|
fu_uefi_get_esp_path_for_os (const gchar *esp_path)
|
|
{
|
|
const gchar *os_release_id = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GHashTable) os_release = fwupd_get_os_release (&error_local);
|
|
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";
|
|
return g_build_filename (esp_path, "EFI", os_release_id, NULL);
|
|
}
|
|
|
|
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;
|
|
if (g_str_has_prefix (data, "0x"))
|
|
return g_ascii_strtoull (data + 2, NULL, 16);
|
|
return g_ascii_strtoull (data, NULL, 10);
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_check_esp_path (const gchar *path, GError **error)
|
|
{
|
|
const gchar *fs_types[] = { "vfat", "ntfs", "exfat", "autofs", NULL };
|
|
g_autoptr(GUnixMountEntry) mount = g_unix_mount_at (path, NULL);
|
|
if (mount == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"%s was not mounted", path);
|
|
return FALSE;
|
|
}
|
|
/* /boot is a special case because systemd sandboxing marks
|
|
* it read-only, but we need to write to /boot/EFI
|
|
*/
|
|
if (g_strcmp0 (path, "/boot") == 0) {
|
|
if (!g_file_test ("/boot/EFI", G_FILE_TEST_IS_DIR)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s/EFI does not exist", path);
|
|
return FALSE;
|
|
}
|
|
} else if (g_unix_mount_is_readonly (mount)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s is read only", path);
|
|
return FALSE;
|
|
}
|
|
if (!g_strv_contains (fs_types, g_unix_mount_get_fs_type (mount))) {
|
|
g_autofree gchar *supported = g_strjoinv ("|", (gchar **) fs_types);
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s has an invalid type, expected %s",
|
|
path, supported);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gchar *
|
|
fu_uefi_guess_esp_path (void)
|
|
{
|
|
const gchar *paths[] = {"/boot/efi", "/boot", "/efi", NULL};
|
|
const gchar *path_tmp;
|
|
|
|
/* for the test suite use local directory for ESP */
|
|
path_tmp = g_getenv ("FWUPD_UEFI_ESP_PATH");
|
|
if (path_tmp != NULL)
|
|
return g_strdup (path_tmp);
|
|
|
|
for (guint i = 0; paths[i] != NULL; i++) {
|
|
g_autoptr(GError) error = NULL;
|
|
if (!fu_uefi_check_esp_path (paths[i], &error)) {
|
|
g_debug ("ignoring ESP path: %s", error->message);
|
|
continue;
|
|
}
|
|
return g_strdup (paths[i]);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_prefix_efi_errors (GError **error)
|
|
{
|
|
g_autoptr(GString) str = g_string_new (NULL);
|
|
gint rc = 1;
|
|
for (gint i = 0; rc > 0; i++) {
|
|
gchar *filename = NULL;
|
|
gchar *function = NULL;
|
|
gchar *message = NULL;
|
|
gint line = 0;
|
|
gint err = 0;
|
|
|
|
rc = efi_error_get (i, &filename, &function, &line,
|
|
&message, &err);
|
|
if (rc <= 0)
|
|
break;
|
|
g_string_append_printf (str, "{error #%d} %s:%d %s(): %s: %s\t",
|
|
i, filename, line, function,
|
|
message, strerror (err));
|
|
}
|
|
if (str->len > 1)
|
|
g_string_truncate (str, str->len - 1);
|
|
g_prefix_error (error, "%s: ", str->str);
|
|
return FALSE;
|
|
}
|