fwupd/plugins/uefi/fu-uefi-common.c
Mario Limonciello 25b49b6a6f uefi: Add 'autofs' to supported filesystems (Closes: #660)
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.
2018-08-20 07:44:17 -05:00

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;
}