diff --git a/contrib/debian/fwupd.install b/contrib/debian/fwupd.install index 1c5cd4405..69fae6086 100644 --- a/contrib/debian/fwupd.install +++ b/contrib/debian/fwupd.install @@ -9,6 +9,7 @@ usr/share/locale usr/share/metainfo/* usr/lib/*/fwupd usr/lib/*/fwupdtool +usr/lib/*/fwupdate usr/lib/*/efi usr/share/man/man1/* lib/systemd/system/* diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index f7da09004..8a3d7386d 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -189,6 +189,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg %{_libexecdir}/fwupd/fwupdtool %if 0%{?have_uefi} %{_libexecdir}/fwupd/efi/*.efi +%{_libexecdir}/fwupd/fwupdate %endif %{_bindir}/dfu-tool %{_bindir}/fwupdmgr diff --git a/plugins/uefi/fu-uefi-device.c b/plugins/uefi/fu-uefi-device.c index 5cf3d69e2..0724e279d 100644 --- a/plugins/uefi/fu-uefi-device.c +++ b/plugins/uefi/fu-uefi-device.c @@ -452,3 +452,12 @@ fu_uefi_device_new_from_dev (FuDevice *dev) g_assert (self->fw_class != NULL); return self; } + +FuUefiDevice * +fu_uefi_device_new_from_guid (const gchar *guid) +{ + FuUefiDevice *self; + self = g_object_new (FU_TYPE_UEFI_DEVICE, NULL); + self->fw_class = g_strdup (guid); + return self; +} diff --git a/plugins/uefi/fu-uefi-device.h b/plugins/uefi/fu-uefi-device.h index d353e9131..ea29e0698 100644 --- a/plugins/uefi/fu-uefi-device.h +++ b/plugins/uefi/fu-uefi-device.h @@ -40,6 +40,7 @@ typedef enum { FU_UEFI_DEVICE_STATUS_LAST } FuUefiDeviceStatus; +FuUefiDevice *fu_uefi_device_new_from_guid (const gchar *guid); FuUefiDevice *fu_uefi_device_new_from_entry (const gchar *entry_path); FuUefiDevice *fu_uefi_device_new_from_dev (FuDevice *dev); gboolean fu_uefi_device_clear_status (FuUefiDevice *self, diff --git a/plugins/uefi/fu-uefi-tool.c b/plugins/uefi/fu-uefi-tool.c new file mode 100644 index 000000000..14e2787bb --- /dev/null +++ b/plugins/uefi/fu-uefi-tool.c @@ -0,0 +1,322 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "fu-ucs2.h" +#include "fu-uefi-common.h" +#include "fu-uefi-device.h" +#include "fu-uefi-vars.h" + +/* custom return code */ +#define EXIT_NOTHING_TO_DO 2 + +typedef struct { + GCancellable *cancellable; + GMainLoop *loop; + GOptionContext *context; +} FuUtilPrivate; + +static void +fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) +{ +} + +static void +fu_util_private_free (FuUtilPrivate *priv) +{ + if (priv->context != NULL) + g_option_context_free (priv->context); + g_free (priv); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) +#pragma clang diagnostic pop + +static const gchar * +fu_uefi_update_info_status_to_string (guint32 update_info_status) +{ + if (update_info_status == EFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE) + return "attempt-update"; + if (update_info_status == EFI_UPDATE_INFO_STATUS_ATTEMPTED) + return "attempted"; + return "unknown"; +} + +int +main (int argc, char *argv[]) +{ + gboolean action_enable = FALSE; + gboolean action_info = FALSE; + gboolean action_list = FALSE; + gboolean action_log = FALSE; + gboolean action_set_debug = FALSE; + gboolean action_supported = FALSE; + gboolean action_unset_debug = FALSE; + gboolean action_version = FALSE; + gboolean ret; + gboolean verbose = FALSE; + g_autofree gchar *apply = FALSE; + g_autofree gchar *esp_path = NULL; + g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) devices = NULL; + const GOptionEntry options[] = { + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + /* TRANSLATORS: command line option */ + _("Show extra debugging information"), NULL }, + { "version", '\0', 0, G_OPTION_ARG_NONE, &action_version, + /* TRANSLATORS: command line option */ + _("Display version"), NULL }, + { "log", 'L', 0, G_OPTION_ARG_NONE, &action_log, + /* TRANSLATORS: command line option */ + _("Show the debug log from the last attempted update"), NULL }, + { "list", 'l', 0, G_OPTION_ARG_NONE, &action_list, + /* TRANSLATORS: command line option */ + _("List supported firmware updates"), NULL }, + { "supported", 's', 0, G_OPTION_ARG_NONE, &action_supported, + /* TRANSLATORS: command line option */ + _("Query for firmware update support"), NULL }, + { "info", 'i', 0, G_OPTION_ARG_NONE, &action_info, + /* TRANSLATORS: command line option */ + _("Show the information of firmware update status"), NULL }, + { "enable", 'e', 0, G_OPTION_ARG_NONE, &action_enable, + /* TRANSLATORS: command line option */ + _("Enable firmware update support on supported systems"), NULL }, + { "esp-path", 'p', 0, G_OPTION_ARG_STRING, &esp_path, + /* TRANSLATORS: command line option */ + _("Override the default ESP path"), "PATH" }, + { "set-debug", 'd', 0, G_OPTION_ARG_NONE, &action_set_debug, + /* TRANSLATORS: command line option */ + _("Set the debugging flag during update"), NULL }, + { "unset-debug", 'D', 0, G_OPTION_ARG_NONE, &action_unset_debug, + /* TRANSLATORS: command line option */ + _("Unset the debugging flag during update"), NULL }, + { "apply", 'a', 0, G_OPTION_ARG_STRING, &apply, + /* TRANSLATORS: command line option */ + _("Apply firmware updates"), "GUID" }, + { NULL} + }; + + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + /* ensure root user */ + if (getuid () != 0 || geteuid () != 0) + /* TRANSLATORS: we're poking around as a power user */ + g_printerr ("%s\n", _("This program may only work correctly as root")); + + /* get a action_list of the commands */ + priv->context = g_option_context_new (NULL); + g_option_context_set_description (priv->context, + "This tool allows an administrator to debug UpdateCapsule operation."); + + /* TRANSLATORS: program name */ + g_set_application_name (_("UEFI Firmware Utility")); + g_option_context_add_main_entries (priv->context, options, NULL); + ret = g_option_context_parse (priv->context, &argc, &argv, &error); + if (!ret) { + /* TRANSLATORS: the user didn't read the man page */ + g_print ("%s: %s\n", _("Failed to parse arguments"), + error->message); + return EXIT_FAILURE; + } + + /* set verbose? */ + if (verbose) { + g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); + } else { + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + fu_util_ignore_cb, NULL); + } + + /* nothing specified */ + if (!action_enable && !action_info && !action_list && !action_log && + !action_set_debug && !action_supported && !action_unset_debug && + !action_version) { + g_autofree gchar *tmp = NULL; + tmp = g_option_context_get_help (priv->context, TRUE, NULL); + g_printerr ("%s\n\n%s", _("No action specified!"), tmp); + return EXIT_FAILURE; + } + + /* action_version first */ + if (action_version) + g_print ("fwupd version: %s\n", PACKAGE_VERSION); + + /* override the default ESP path */ + if (esp_path == NULL) { + esp_path = fu_uefi_guess_esp_path (); + if (esp_path == NULL) { + g_printerr ("Unable to determine EFI system partition " + "location, override using --esp-path\n"); + return EXIT_FAILURE; + } + } + g_debug ("ESP mountpoint set as %s", esp_path); + + /* show the debug action_log from the last attempted update */ + if (action_log) { + gsize sz = 0; + g_autofree guint8 *buf = NULL; + g_autofree guint16 *buf_ucs2 = NULL; + g_autofree gchar *str = NULL; + g_autoptr(GError) error_local = NULL; + if (!fu_uefi_vars_get_data (FU_UEFI_VARS_GUID_FWUPDATE, + "FWUPDATE_DEBUG_LOG", + &buf, &sz, NULL, + &error_local)) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + buf_ucs2 = g_new0 (guint16, (sz / 2) + 1); + memcpy (buf_ucs2, buf, sz); + str = fu_ucs2_to_uft8 (buf_ucs2, sz / 2); + g_print ("%s", str); + } + + if (action_list || action_supported || action_info) { + g_autoptr(GPtrArray) entries = NULL; + g_autofree gchar *esrt_path = NULL; + g_autofree gchar *sysfsfwdir = NULL; + g_autoptr(GError) error_local = NULL; + + /* get the directory of ESRT entries */ + sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW); + esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL); + entries = fu_uefi_get_esrt_entry_paths (esrt_path, &error_local); + if (entries == NULL) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + + /* add each device */ + devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + for (guint i = 0; i < entries->len; i++) { + const gchar *path = g_ptr_array_index (entries, i); + g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_entry (path); + fu_device_set_metadata (FU_DEVICE (dev), "EspPath", esp_path); + g_ptr_array_add (devices, g_object_ref (dev)); + } + } + + /* action_list action_supported firmware updates */ + if (action_list) { + for (guint i = 0; i < devices->len; i++) { + FuUefiDevice *dev = g_ptr_array_index (devices, i); + g_print ("%s type, {%s} action_version %" G_GUINT32_FORMAT " can be updated " + "to any action_version above %" G_GUINT32_FORMAT "\n", + fu_uefi_device_kind_to_string (fu_uefi_device_get_kind (dev)), + fu_uefi_device_get_guid (dev), + fu_uefi_device_get_version (dev), + fu_uefi_device_get_version_lowest (dev) - 1); + } + } + + /* query for firmware update support */ + if (action_supported) { + if (devices->len > 0) { + g_print ("%s\n", _("Firmware updates are supported on this machine.")); + } else { + g_print ("%s\n", _("Firmware updates are not supported on this machine.")); + } + } + + /* show the information of firmware update status */ + if (action_info) { + efi_update_info_t info; + g_autofree gchar *guid = NULL; + for (guint i = 0; i < devices->len; i++) { + FuUefiDevice *dev = g_ptr_array_index (devices, i); + efi_guid_t guid_tmp; + g_autoptr(GError) error_local = NULL; + if (!fu_uefi_device_get_update_info (dev, &info, &error_local)) { + g_printerr ("failed: %s\n", error_local->message); + continue; + } + memcpy (&guid_tmp, &info.guid, sizeof(efi_guid_t)); + if (efi_guid_to_str (&guid_tmp, &guid) < 0) { + g_printerr ("failed to convert GUID: %s\n", + error_local->message); + continue; + } + g_print ("Information for the update status entry %u:\n", i); + g_print (" Information Version: %" G_GUINT32_FORMAT "\n", info.update_info_version); + g_print (" Firmware GUID: {%s}\n", guid); + g_print (" Capsule Flags: 0x%08" G_GUINT32_FORMAT "x\n", info.capsule_flags); + g_print (" Hardware Instance: %" G_GUINT64_FORMAT "\n", info.hw_inst); + g_print (" Update Status: %s\n", fu_uefi_update_info_status_to_string (info.status)); + //g_print (" Capsule File Path: %s\n\n", info.capsule_fn); + } + } + + /* action_enable firmware update support on action_supported systems */ + if (action_enable) { + g_printerr ("Unsupported, use `fwupdmgr unlock`\n"); + return EXIT_FAILURE; + } + + /* set the debugging flag during update */ + if (action_set_debug) { + const guint8 data = 1; + g_autoptr(GError) error_local = NULL; + if (!fu_uefi_vars_set_data (FU_UEFI_VARS_GUID_FWUPDATE, + "FWUPDATE_VERBOSE", + &data, sizeof(data), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &error_local)) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + g_print ("%s\n", _("Enabled fwupdate debugging")); + } + + /* unset the debugging flag during update */ + if (action_unset_debug) { + g_autoptr(GError) error_local = NULL; + if (!fu_uefi_vars_delete (FU_UEFI_VARS_GUID_FWUPDATE, + "FWUPDATE_VERBOSE", + &error_local)) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + g_print ("%s\n", _("Disabled fwupdate debugging")); + } + + /* apply firmware updates */ + if (apply != NULL) { + g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_guid (apply); + g_autoptr(GError) error_local = NULL; + g_autoptr(GBytes) fw = fu_common_get_contents_bytes (argv[1], &error_local); + if (fw == NULL) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + if (!fu_device_write_firmware (FU_DEVICE (dev), fw, &error_local)) { + g_printerr ("failed: %s\n", error_local->message); + return EXIT_FAILURE; + } + } + + /* success */ + return EXIT_SUCCESS; +} diff --git a/plugins/uefi/meson.build b/plugins/uefi/meson.build index 65e30ce0d..d4e6750e5 100644 --- a/plugins/uefi/meson.build +++ b/plugins/uefi/meson.build @@ -28,6 +28,38 @@ shared_module('fu_plugin_uefi', ], ) +executable( + 'fwupdate', + resources_src, + sources : [ + 'fu-uefi-tool.c', + 'fu-uefi-bgrt.c', + 'fu-ucs2.c', + 'fu-uefi-bootmgr.c', + 'fu-uefi-common.c', + 'fu-uefi-device.c', + 'fu-uefi-vars.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../../src'), + include_directories('../../libfwupd'), + ], + dependencies : [ + giounix, + gusb, + efivar, + efiboot, + ], + link_with : [ + fwupd, + libfwupdprivate, + ], + install : true, + install_dir : join_paths(libexecdir, 'fwupd'), + c_args : cargs, +) + install_data(['uefi.conf'], install_dir: join_paths(sysconfdir, 'fwupd') )