fwupd/plugins/uefi/fu-plugin-uefi.c
Richard Hughes 4a036018f7 Set the progress and state on the FuDevice, not the FuPlugin
This makes more sense; we're updating the device, not the plugin itself.

This also means we don't need to funnel everything through callbacks like
GFileProgressCallback and we can also update the state without adding an
explicit callback to each derived device type.
2017-11-30 20:51:52 +00:00

618 lines
17 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2016-2017 Richard Hughes <richard@hughsie.com>
*
* Licensed under the GNU General Public License Version 2
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <appstream-glib.h>
#include <fwup.h>
#include <fcntl.h>
#include <glib/gi18n.h>
#include "fu-plugin.h"
#include "fu-plugin-vfuncs.h"
#ifndef UX_CAPSULE_GUID
#define UX_CAPSULE_GUID EFI_GUID(0x3b8c8162,0x188c,0x46a4,0xaec9,0xbe,0x43,0xf1,0xd6,0x56,0x97)
#endif
void
fu_plugin_init (FuPlugin *plugin)
{
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower");
}
static gchar *
fu_plugin_uefi_guid_to_string (efi_guid_t *guid_raw)
{
g_autofree gchar *guid = g_strdup ("00000000-0000-0000-0000-000000000000");
if (efi_guid_to_str (guid_raw, &guid) < 0)
return NULL;
return g_steal_pointer (&guid);
}
static fwup_resource *
fu_plugin_uefi_find (fwup_resource_iter *iter, const gchar *guid_str, GError **error)
{
efi_guid_t *guid_raw;
fwup_resource *re_matched = NULL;
fwup_resource *re = NULL;
/* get the hardware we're referencing */
while (fwup_resource_iter_next (iter, &re) > 0) {
g_autofree gchar *guid_tmp = NULL;
/* convert to strings */
fwup_get_guid (re, &guid_raw);
guid_tmp = fu_plugin_uefi_guid_to_string (guid_raw);
if (guid_tmp == NULL) {
g_warning ("failed to convert guid to string");
continue;
}
/* FIXME: also match hardware_instance too */
if (g_strcmp0 (guid_str, guid_tmp) == 0) {
re_matched = re;
break;
}
}
/* paradoxically, no hardware matched */
if (re_matched == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No UEFI firmware matched %s",
guid_str);
}
return re_matched;
}
static fwup_resource *
fu_plugin_uefi_find_raw (fwup_resource_iter *iter, efi_guid_t *guid)
{
fwup_resource *re_matched = NULL;
fwup_resource *re = NULL;
while (fwup_resource_iter_next (iter, &re) > 0) {
efi_guid_t *guid_tmp;
fwup_get_guid (re, &guid_tmp);
if (efi_guid_cmp (guid_tmp, guid) == 0) {
re_matched = re;
break;
}
}
return re_matched;
}
static void
_fwup_resource_iter_free (fwup_resource_iter *iter)
{
fwup_resource_iter_destroy (&iter);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(fwup_resource_iter, _fwup_resource_iter_free);
gboolean
fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error)
{
fwup_resource *re = NULL;
g_autoptr(fwup_resource_iter) iter = NULL;
/* get the hardware we're referencing */
fwup_resource_iter_create (&iter);
re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error);
if (re == NULL)
return FALSE;
if (fwup_clear_status (re) < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Cannot create clear UEFI status for %s",
fu_device_get_guid_default (device));
return FALSE;
}
return TRUE;
}
gboolean
fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error)
{
const gchar *tmp;
fwup_resource *re = NULL;
guint32 status = 0;
guint32 version = 0;
time_t when = 0;
g_autoptr(fwup_resource_iter) iter = NULL;
/* get the hardware we're referencing */
fwup_resource_iter_create (&iter);
re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error);
if (re == NULL)
return FALSE;
if (fwup_get_last_attempt_info (re, &version, &status, &when) < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Cannot get UEFI status for %s",
fu_device_get_guid_default (device));
return FALSE;
}
if (status == FWUP_LAST_ATTEMPT_STATUS_SUCCESS) {
fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS);
} else {
g_autofree gchar *err_msg = NULL;
g_autofree gchar *version_str = g_strdup_printf ("%u", version);
fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED);
tmp = fwup_last_attempt_status_to_string (status);
if (tmp == NULL) {
err_msg = g_strdup_printf ("failed to update to %s",
version_str);
} else {
err_msg = g_strdup_printf ("failed to update to %s: %s",
version_str, tmp);
}
fu_device_set_update_error (device, err_msg);
}
return TRUE;
}
static gboolean
fu_plugin_uefi_update_resource (fwup_resource *re,
guint64 hardware_instance,
GBytes *blob,
GError **error)
{
int rc;
rc = fwup_set_up_update_with_buf (re, hardware_instance,
g_bytes_get_data (blob, NULL),
g_bytes_get_size (blob));
if (rc < 0) {
g_autoptr(GString) str = g_string_new (NULL);
rc = 1;
for (int i = 0; rc > 0; i++) {
char *filename = NULL;
char *function = NULL;
char *message = NULL;
int line = 0;
int 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_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"UEFI firmware update failed: %s",
str->str);
return FALSE;
}
return TRUE;
}
static GBytes *
fu_plugin_uefi_get_splash_data (guint width, guint height, GError **error)
{
const gchar * const *langs = g_get_language_names ();
const gchar *localedir = LOCALEDIR;
const gsize chunk_size = 1024 * 1024;
gsize buf_idx = 0;
gsize buf_sz = chunk_size;
gssize len;
g_autofree gchar *basename = NULL;
g_autofree guint8 *buf = NULL;
g_autoptr(GBytes) compressed_data = NULL;
g_autoptr(GConverter) conv = NULL;
g_autoptr(GInputStream) stream_compressed = NULL;
g_autoptr(GInputStream) stream_raw = NULL;
/* ensure this is sane */
if (!g_str_has_prefix (localedir, "/"))
localedir = "/usr/share/locale";
/* find the closest locale match, falling back to `en` and `C` */
basename = g_strdup_printf ("fwupd-%u-%u.bmp.gz", width, height);
for (guint i = 0; langs[i] != NULL; i++) {
g_autofree gchar *fn = NULL;
if (g_str_has_suffix (langs[i], ".UTF-8"))
continue;
fn = g_build_filename (localedir, langs[i],
"LC_IMAGES", basename, NULL);
if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
compressed_data = fu_common_get_contents_bytes (fn, error);
if (compressed_data == NULL)
return NULL;
break;
}
g_debug ("no %s found", fn);
}
/* we found nothing */
if (compressed_data == NULL) {
g_autofree gchar *tmp = g_strjoinv (",", (gchar **) langs);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to get splash file for %s in %s",
tmp, localedir);
return NULL;
}
/* decompress data */
stream_compressed = g_memory_input_stream_new_from_bytes (compressed_data);
conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
stream_raw = g_converter_input_stream_new (stream_compressed, conv);
buf = g_malloc0 (buf_sz);
while ((len = g_input_stream_read (stream_raw,
buf + buf_idx,
buf_sz - buf_idx,
NULL, error)) > 0) {
buf_idx += len;
if (buf_sz - buf_idx < chunk_size) {
buf_sz += chunk_size;
buf = g_realloc (buf, buf_sz);
}
}
if (len < 0) {
g_prefix_error (error, "failed to decompress file: ");
return NULL;
}
g_debug ("decompressed image to %" G_GSIZE_FORMAT "kb", buf_idx / 1024);
return g_bytes_new_take (g_steal_pointer (&buf), buf_idx);
}
static gboolean
fu_plugin_uefi_update_splash (GError **error)
{
fwup_resource *re = NULL;
guint best_idx = G_MAXUINT;
guint32 lowest_border_pixels = G_MAXUINT;
#ifdef HAVE_FWUP_GET_BGRT_INFO
int rc;
#endif
guint32 screen_height = 768;
guint32 screen_width = 1024;
g_autoptr(fwup_resource_iter) iter = NULL;
g_autoptr(GBytes) image_bmp = NULL;
struct {
guint32 width;
guint32 height;
} sizes[] = {
{ 640, 480 }, /* matching the sizes in po/make-images */
{ 800, 600 },
{ 1024, 768 },
{ 1920, 1080 },
{ 3840, 2160 },
{ 5120, 2880 },
{ 5688, 3200 },
{ 7680, 4320 },
{ 0, 0 }
};
/* is this supported? */
fwup_resource_iter_create (&iter);
re = fu_plugin_uefi_find_raw (iter, &UX_CAPSULE_GUID);
if (re == NULL)
return TRUE;
/* get the boot graphics resource table data */
#ifdef HAVE_FWUP_GET_BGRT_INFO
rc = fwup_get_ux_capsule_info (&screen_width, &screen_height);
if (rc < 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to get BGRT screen size");
return FALSE;
}
g_debug ("BGRT screen size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT,
screen_width, screen_height);
#endif
/* find the 'best sized' pre-generated image */
for (guint i = 0; sizes[i].width != 0; i++) {
guint32 border_pixels;
/* disregard any images that are bigger than the screen */
if (sizes[i].width > screen_width)
continue;
if (sizes[i].height > screen_height)
continue;
/* is this the best fit for the display */
border_pixels = (screen_width * screen_height) -
(sizes[i].width * sizes[i].height);
if (border_pixels < lowest_border_pixels) {
lowest_border_pixels = border_pixels;
best_idx = i;
}
}
if (best_idx == G_MAXUINT) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to find a suitable image to use");
return FALSE;
}
/* get the raw data */
image_bmp = fu_plugin_uefi_get_splash_data (sizes[best_idx].width,
sizes[best_idx].height,
error);
if (image_bmp == NULL)
return FALSE;
/* perform the upload */
return fu_plugin_uefi_update_resource (re, 0, image_bmp, error);
}
gboolean
fu_plugin_update (FuPlugin *plugin,
FuDevice *device,
GBytes *blob_fw,
FwupdInstallFlags flags,
GError **error)
{
fwup_resource *re = NULL;
guint64 hardware_instance = 0; /* FIXME */
g_autoptr(fwup_resource_iter) iter = NULL;
const gchar *str;
g_autofree gchar *efibootmgr_path = NULL;
g_autofree gchar *boot_variables = NULL;
g_autoptr(GError) error_splash = NULL;
/* get the hardware we're referencing */
fwup_resource_iter_create (&iter);
re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error);
if (re == NULL)
return FALSE;
/* TRANSLATORS: this is shown when updating the firmware after the reboot */
str = _("Installing firmware update…");
g_assert (str != NULL);
/* perform the update */
g_debug ("Performing UEFI capsule update");
fu_device_set_status (device, FWUPD_STATUS_SCHEDULING);
if (!fu_plugin_uefi_update_splash (&error_splash)) {
g_warning ("failed to upload BGRT splash text: %s",
error_splash->message);
}
if (!fu_plugin_uefi_update_resource (re, hardware_instance, blob_fw, error))
return FALSE;
/* record boot information to system log for future debugging */
efibootmgr_path = g_find_program_in_path ("efibootmgr");
if (efibootmgr_path != NULL) {
if (!g_spawn_command_line_sync ("efibootmgr -v",
&boot_variables, NULL, NULL, error))
return FALSE;
g_message ("Boot Information:\n%s", boot_variables);
}
return TRUE;
}
static AsVersionParseFlag
fu_plugin_uefi_get_version_format_for_type (FuPlugin *plugin, guint32 uefi_type)
{
const gchar *content;
const gchar *quirk;
/* we have no information for devices */
if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE)
return AS_VERSION_PARSE_FLAG_USE_TRIPLET;
content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER);
if (content == NULL)
return AS_VERSION_PARSE_FLAG_USE_TRIPLET;
/* any quirks match */
quirk = fu_plugin_lookup_quirk_by_id (plugin,
FU_QUIRKS_UEFI_VERSION_FORMAT,
content);
if (g_strcmp0 (quirk, "none") == 0)
return AS_VERSION_PARSE_FLAG_NONE;
/* fall back */
return AS_VERSION_PARSE_FLAG_USE_TRIPLET;
}
gboolean
fu_plugin_unlock (FuPlugin *plugin,
FuDevice *device,
GError **error)
{
gint rc;
g_debug ("unlocking UEFI device %s", fu_device_get_id (device));
rc = fwup_enable_esrt();
if (rc <= 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to unlock UEFI device");
return FALSE;
} else if (rc == 1)
g_debug ("UEFI device is already unlocked");
else if (rc == 2)
g_debug ("Successfully unlocked UEFI device");
else if (rc == 3)
g_debug ("UEFI device will be unlocked on next reboot");
return TRUE;
}
static const gchar *
fu_plugin_uefi_uefi_type_to_string (guint32 uefi_type)
{
if (uefi_type == FWUP_RESOURCE_TYPE_UNKNOWN)
return "Unknown Firmware";
if (uefi_type == FWUP_RESOURCE_TYPE_SYSTEM_FIRMWARE)
return "System Firmware";
if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE)
return "Device Firmware";
if (uefi_type == FWUP_RESOURCE_TYPE_UEFI_DRIVER)
return "UEFI Driver";
if (uefi_type == FWUP_RESOURCE_TYPE_FMP)
return "Firmware Management Protocol";
return NULL;
}
static gchar *
fu_plugin_uefi_get_name_for_type (FuPlugin *plugin, guint32 uefi_type)
{
GString *display_name;
/* set Display Name prefix for capsules that are not PCI cards */
display_name = g_string_new (fu_plugin_uefi_uefi_type_to_string (uefi_type));
if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) {
g_string_prepend (display_name, "UEFI ");
} else {
const gchar *tmp;
tmp = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME);
if (tmp != NULL && tmp[0] != '\0') {
g_string_prepend (display_name, " ");
g_string_prepend (display_name, tmp);
}
}
return g_string_free (display_name, FALSE);
}
static void
fu_plugin_uefi_coldplug_resource (FuPlugin *plugin, fwup_resource *re)
{
AsVersionParseFlag parse_flags;
efi_guid_t *guid_raw;
guint32 uefi_type;
guint32 version_raw;
guint64 hardware_instance = 0; /* FIXME */
g_autofree gchar *guid = NULL;
g_autofree gchar *id = NULL;
g_autofree gchar *name = NULL;
g_autofree gchar *version_lowest = NULL;
g_autofree gchar *version = NULL;
g_autoptr(FuDevice) dev = NULL;
/* detect the fake GUID used for uploading the image */
fwup_get_guid (re, &guid_raw);
if (efi_guid_cmp (guid_raw, &UX_CAPSULE_GUID) == 0) {
g_debug ("skipping entry, detected fake BGRT");
return;
}
/* convert to strings */
guid = fu_plugin_uefi_guid_to_string (guid_raw);
if (guid == NULL) {
g_warning ("failed to convert guid to string");
return;
}
fwup_get_fw_type (re, &uefi_type);
parse_flags = fu_plugin_uefi_get_version_format_for_type (plugin, uefi_type);
fwup_get_fw_version (re, &version_raw);
version = as_utils_version_from_uint32 (version_raw, parse_flags);
id = g_strdup_printf ("UEFI-%s-dev%" G_GUINT64_FORMAT,
guid, hardware_instance);
dev = fu_device_new ();
if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) {
/* nothing better in the icon naming spec */
fu_device_add_icon (dev, "audio-card");
} else {
/* this is probably system firmware */
fu_device_add_icon (dev, "computer");
}
fu_device_set_id (dev, id);
fu_device_add_guid (dev, guid);
fu_device_set_version (dev, version);
name = fu_plugin_uefi_get_name_for_type (plugin, uefi_type);
if (name != NULL)
fu_device_set_name (dev, name);
fwup_get_lowest_supported_fw_version (re, &version_raw);
if (version_raw != 0) {
version_lowest = as_utils_version_from_uint32 (version_raw,
parse_flags);
fu_device_set_version_lowest (dev, version_lowest);
}
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL);
if (g_file_test ("/sys/firmware/efi/efivars", G_FILE_TEST_IS_DIR) ||
g_file_test ("/sys/firmware/efi/vars", G_FILE_TEST_IS_DIR)) {
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
} else {
g_warning ("Kernel support for EFI variables missing");
}
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC);
fu_plugin_device_add (plugin, dev);
}
gboolean
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
{
fwup_resource *re;
gint supported;
g_autoptr(fwup_resource_iter) iter = NULL;
/* supported = 0 : ESRT unspported
supported = 1 : unlocked, ESRT supported
supported = 2 : it is locked but can be unlocked to support ESRT
supported = 3 : it is locked, has been marked to be unlocked on next boot
calling unlock again is OK.
*/
supported = fwup_supported ();
if (supported == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"UEFI firmware updating not supported");
return FALSE;
}
if (supported == 2) {
g_autoptr(FuDevice) dev = fu_device_new ();
fu_device_set_id (dev, "UEFI-dummy-dev0");
fu_device_add_guid (dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
fu_device_set_version (dev, "0");
fu_device_add_icon (dev, "computer");
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_LOCKED);
fu_plugin_device_add (plugin, dev);
return TRUE;
}
/* add each device */
if (fwup_resource_iter_create (&iter) < 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Cannot create fwup iter");
return FALSE;
}
while (fwup_resource_iter_next (iter, &re) > 0)
fu_plugin_uefi_coldplug_resource (plugin, re);
return TRUE;
}