mirror of
https://git.proxmox.com/git/fwupd
synced 2026-03-28 17:07:13 +00:00
A user can place a JSON file in /etc/fwupd/bios-settings.d/ with the default desired policy for the machine. fwupd will load this policy on startup to ensure BIOS settings are set as desired by the system administrator.
4026 lines
117 KiB
C
4026 lines
117 KiB
C
/*
|
|
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "FuMain"
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <glib/gstdio.h>
|
|
#ifdef HAVE_GIO_UNIX
|
|
#include <glib-unix.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <jcat.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fwupd-bios-setting-private.h"
|
|
#include "fwupd-common-private.h"
|
|
#include "fwupd-device-private.h"
|
|
#include "fwupd-plugin-private.h"
|
|
|
|
#include "fu-bios-settings-private.h"
|
|
#include "fu-cabinet.h"
|
|
#include "fu-context-private.h"
|
|
#include "fu-debug.h"
|
|
#include "fu-device-private.h"
|
|
#include "fu-engine.h"
|
|
#include "fu-history.h"
|
|
#include "fu-hwids.h"
|
|
#include "fu-plugin-private.h"
|
|
#include "fu-progressbar.h"
|
|
#include "fu-security-attr-common.h"
|
|
#include "fu-security-attrs-private.h"
|
|
#include "fu-smbios-private.h"
|
|
#include "fu-util-bios-setting.h"
|
|
#include "fu-util-common.h"
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
#include "fu-systemd.h"
|
|
#endif
|
|
|
|
/* custom return code */
|
|
#define EXIT_NOTHING_TO_DO 2
|
|
|
|
typedef enum {
|
|
FU_UTIL_OPERATION_UNKNOWN,
|
|
FU_UTIL_OPERATION_UPDATE,
|
|
FU_UTIL_OPERATION_INSTALL,
|
|
FU_UTIL_OPERATION_READ,
|
|
FU_UTIL_OPERATION_LAST
|
|
} FuUtilOperation;
|
|
|
|
struct FuUtilPrivate {
|
|
GCancellable *cancellable;
|
|
GMainContext *main_ctx;
|
|
GMainLoop *loop;
|
|
GOptionContext *context;
|
|
FuEngine *engine;
|
|
FuEngineRequest *request;
|
|
FuProgress *progress;
|
|
FuProgressbar *progressbar;
|
|
FwupdClient *client;
|
|
gboolean as_json;
|
|
gboolean no_reboot_check;
|
|
gboolean no_safety_check;
|
|
gboolean no_device_prompt;
|
|
gboolean prepare_blob;
|
|
gboolean cleanup_blob;
|
|
gboolean enable_json_state;
|
|
gboolean interactive;
|
|
FwupdInstallFlags flags;
|
|
gboolean show_all;
|
|
gboolean disable_ssl_strict;
|
|
gint lock_fd;
|
|
/* only valid in update and downgrade */
|
|
FuUtilOperation current_operation;
|
|
FwupdDevice *current_device;
|
|
GPtrArray *post_requests;
|
|
FwupdDeviceFlags completion_flags;
|
|
FwupdDeviceFlags filter_include;
|
|
FwupdDeviceFlags filter_exclude;
|
|
};
|
|
|
|
static void
|
|
fu_util_client_notify_cb(GObject *object, GParamSpec *pspec, FuUtilPrivate *priv)
|
|
{
|
|
if (priv->as_json)
|
|
return;
|
|
fu_progressbar_update(priv->progressbar,
|
|
fwupd_client_get_status(priv->client),
|
|
fwupd_client_get_percentage(priv->client));
|
|
}
|
|
|
|
static void
|
|
fu_util_show_plugin_warnings(FuUtilPrivate *priv)
|
|
{
|
|
FwupdPluginFlags flags = FWUPD_PLUGIN_FLAG_NONE;
|
|
GPtrArray *plugins;
|
|
|
|
/* get a superset so we do not show the same message more than once */
|
|
plugins = fu_engine_get_plugins(priv->engine);
|
|
for (guint i = 0; i < plugins->len; i++) {
|
|
FwupdPlugin *plugin = g_ptr_array_index(plugins, i);
|
|
if (fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED))
|
|
continue;
|
|
if (!fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING))
|
|
continue;
|
|
flags |= fwupd_plugin_get_flags(plugin);
|
|
}
|
|
|
|
/* never show these, they're way too generic */
|
|
flags &= ~FWUPD_PLUGIN_FLAG_DISABLED;
|
|
flags &= ~FWUPD_PLUGIN_FLAG_NO_HARDWARE;
|
|
flags &= ~FWUPD_PLUGIN_FLAG_REQUIRE_HWID;
|
|
|
|
/* print */
|
|
for (guint i = 0; i < 64; i++) {
|
|
FwupdPluginFlags flag = (guint64)1 << i;
|
|
const gchar *tmp;
|
|
g_autofree gchar *fmt = NULL;
|
|
g_autofree gchar *url = NULL;
|
|
g_autoptr(GString) str = g_string_new(NULL);
|
|
if ((flags & flag) == 0)
|
|
continue;
|
|
tmp = fu_util_plugin_flag_to_string((guint64)1 << i);
|
|
if (tmp == NULL)
|
|
continue;
|
|
/* TRANSLATORS: this is a prefix on the console */
|
|
fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED);
|
|
g_string_append_printf(str, "%s %s\n", fmt, tmp);
|
|
|
|
url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s",
|
|
fwupd_plugin_flag_to_string(flag));
|
|
g_string_append(str, " ");
|
|
/* TRANSLATORS: %s is a link to a website */
|
|
g_string_append_printf(str, _("See %s for more information."), url);
|
|
g_string_append(str, "\n");
|
|
g_printerr("%s", str->str);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_lock(FuUtilPrivate *priv, GError **error)
|
|
{
|
|
#ifdef HAVE_WRLCK
|
|
struct flock lockp = {
|
|
.l_type = F_WRLCK,
|
|
.l_whence = SEEK_SET,
|
|
};
|
|
g_autofree gchar *lockfn = NULL;
|
|
gboolean use_user = FALSE;
|
|
|
|
#ifdef HAVE_GETUID
|
|
if (getuid() != 0 || geteuid() != 0)
|
|
use_user = TRUE;
|
|
#endif
|
|
|
|
/* open file */
|
|
if (use_user) {
|
|
lockfn = fu_util_get_user_cache_path("fwupdtool");
|
|
} else {
|
|
g_autofree gchar *lockdir = fu_path_from_kind(FU_PATH_KIND_LOCKDIR);
|
|
lockfn = g_build_filename(lockdir, "fwupdtool", NULL);
|
|
}
|
|
if (!fu_path_mkdir_parent(lockfn, error))
|
|
return FALSE;
|
|
priv->lock_fd = g_open(lockfn, O_RDWR | O_CREAT, S_IRWXU);
|
|
if (priv->lock_fd < 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"failed to open %s",
|
|
lockfn);
|
|
return FALSE;
|
|
}
|
|
|
|
/* write lock */
|
|
#ifdef HAVE_OFD
|
|
if (fcntl(priv->lock_fd, F_OFD_SETLK, &lockp) < 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"another instance has locked %s",
|
|
lockfn);
|
|
return FALSE;
|
|
}
|
|
#else
|
|
if (fcntl(priv->lock_fd, F_SETLK, &lockp) < 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"another instance has locked %s",
|
|
lockfn);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/* success */
|
|
g_debug("locked %s", lockfn);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_start_engine(FuUtilPrivate *priv,
|
|
FuEngineLoadFlags flags,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
if (!fu_util_lock(priv, error)) {
|
|
/* TRANSLATORS: another fwupdtool instance is already running */
|
|
g_prefix_error(error, "%s: ", _("Failed to lock"));
|
|
return FALSE;
|
|
}
|
|
#ifdef HAVE_SYSTEMD
|
|
if (getuid() != 0 || geteuid() != 0) {
|
|
g_debug("not attempting to stop daemon when running as user");
|
|
} else {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_systemd_unit_stop(fu_util_get_systemd_unit(), &error_local))
|
|
g_debug("Failed to stop daemon: %s", error_local->message);
|
|
}
|
|
#endif
|
|
flags |= FU_ENGINE_LOAD_FLAG_NO_IDLE_SOURCES;
|
|
if (!fu_engine_load(priv->engine, flags, progress, error))
|
|
return FALSE;
|
|
if (fu_engine_get_tainted(priv->engine)) {
|
|
g_autofree gchar *fmt = NULL;
|
|
|
|
/* TRANSLATORS: this is a prefix on the console */
|
|
fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED);
|
|
g_printerr("%s This tool has loaded 3rd party code and "
|
|
"is no longer supported by the upstream developers!\n",
|
|
fmt);
|
|
}
|
|
fu_util_show_plugin_warnings(priv);
|
|
fu_util_show_unsupported_warn();
|
|
|
|
/* copy properties from engine to client */
|
|
g_object_set(priv->client,
|
|
"host-vendor",
|
|
fu_engine_get_host_vendor(priv->engine),
|
|
"host-product",
|
|
fu_engine_get_host_product(priv->engine),
|
|
"battery-level",
|
|
fu_context_get_battery_level(fu_engine_get_context(priv->engine)),
|
|
"battery-threshold",
|
|
fu_context_get_battery_threshold(fu_engine_get_context(priv->engine)),
|
|
NULL);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_util_maybe_prefix_sandbox_error(const gchar *value, GError **error)
|
|
{
|
|
g_autofree gchar *path = g_path_get_dirname(value);
|
|
if (!g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
|
|
g_prefix_error(error,
|
|
"Unable to access %s. You may need to copy %s to %s: ",
|
|
path,
|
|
value,
|
|
g_getenv("HOME"));
|
|
}
|
|
}
|
|
|
|
static void
|
|
fu_util_cancelled_cb(GCancellable *cancellable, gpointer user_data)
|
|
{
|
|
FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
|
|
/* TRANSLATORS: this is when a device ctrl+c's a watch */
|
|
g_print("%s\n", _("Cancelled"));
|
|
g_main_loop_quit(priv->loop);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_smbios_dump(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autofree gchar *tmp = NULL;
|
|
g_autoptr(FuSmbios) smbios = NULL;
|
|
if (g_strv_length(values) < 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
smbios = fu_smbios_new();
|
|
if (!fu_smbios_setup_from_file(smbios, values[0], error))
|
|
return FALSE;
|
|
tmp = fu_firmware_to_string(FU_FIRMWARE(smbios));
|
|
g_print("%s\n", tmp);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_GIO_UNIX
|
|
static gboolean
|
|
fu_util_sigint_cb(gpointer user_data)
|
|
{
|
|
FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
|
|
g_debug("Handling SIGINT");
|
|
g_cancellable_cancel(priv->cancellable);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
fu_util_setup_signal_handlers(FuUtilPrivate *priv)
|
|
{
|
|
#ifdef HAVE_GIO_UNIX
|
|
g_autoptr(GSource) source = g_unix_signal_source_new(SIGINT);
|
|
g_source_set_callback(source, fu_util_sigint_cb, priv, NULL);
|
|
g_source_attach(g_steal_pointer(&source), priv->main_ctx);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fu_util_private_free(FuUtilPrivate *priv)
|
|
{
|
|
if (priv->current_device != NULL)
|
|
g_object_unref(priv->current_device);
|
|
if (priv->engine != NULL)
|
|
g_object_unref(priv->engine);
|
|
if (priv->request != NULL)
|
|
g_object_unref(priv->request);
|
|
if (priv->client != NULL)
|
|
g_object_unref(priv->client);
|
|
if (priv->main_ctx != NULL)
|
|
g_main_context_unref(priv->main_ctx);
|
|
if (priv->loop != NULL)
|
|
g_main_loop_unref(priv->loop);
|
|
if (priv->cancellable != NULL)
|
|
g_object_unref(priv->cancellable);
|
|
if (priv->progressbar != NULL)
|
|
g_object_unref(priv->progressbar);
|
|
if (priv->progress != NULL)
|
|
g_object_unref(priv->progress);
|
|
if (priv->context != NULL)
|
|
g_option_context_free(priv->context);
|
|
if (priv->lock_fd != 0)
|
|
g_close(priv->lock_fd, NULL);
|
|
g_ptr_array_unref(priv->post_requests);
|
|
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 void
|
|
fu_util_update_device_request_cb(FwupdClient *client, FwupdRequest *request, FuUtilPrivate *priv)
|
|
{
|
|
/* action has not been assigned yet */
|
|
if (priv->current_operation == FU_UTIL_OPERATION_UNKNOWN)
|
|
return;
|
|
|
|
/* nothing sensible to show */
|
|
if (fwupd_request_get_message(request) == NULL)
|
|
return;
|
|
|
|
/* show this now */
|
|
if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_IMMEDIATE) {
|
|
g_autofree gchar *fmt = NULL;
|
|
g_autofree gchar *tmp = NULL;
|
|
|
|
/* TRANSLATORS: the user needs to do something, e.g. remove the device */
|
|
fmt = fu_util_term_format(_("Action Required:"), FU_UTIL_TERM_COLOR_RED);
|
|
tmp = g_strdup_printf("%s %s", fmt, fwupd_request_get_message(request));
|
|
fu_progressbar_set_title(priv->progressbar, tmp);
|
|
}
|
|
|
|
/* save for later */
|
|
if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST)
|
|
g_ptr_array_add(priv->post_requests, g_object_ref(request));
|
|
}
|
|
|
|
static void
|
|
fu_main_engine_device_added_cb(FuEngine *engine, FuDevice *device, FuUtilPrivate *priv)
|
|
{
|
|
g_autofree gchar *tmp = fu_device_to_string(device);
|
|
g_debug("ADDED:\n%s", tmp);
|
|
}
|
|
|
|
static void
|
|
fu_main_engine_device_removed_cb(FuEngine *engine, FuDevice *device, FuUtilPrivate *priv)
|
|
{
|
|
g_autofree gchar *tmp = fu_device_to_string(device);
|
|
g_debug("REMOVED:\n%s", tmp);
|
|
}
|
|
|
|
static void
|
|
fu_main_engine_status_changed_cb(FuEngine *engine, FwupdStatus status, FuUtilPrivate *priv)
|
|
{
|
|
if (priv->as_json)
|
|
return;
|
|
fu_progressbar_update(priv->progressbar, status, 0);
|
|
}
|
|
|
|
static void
|
|
fu_util_progress_percentage_changed_cb(FuProgress *progress, guint percentage, FuUtilPrivate *priv)
|
|
{
|
|
if (priv->as_json)
|
|
return;
|
|
fu_progressbar_update(priv->progressbar, fu_progress_get_status(progress), percentage);
|
|
}
|
|
|
|
static void
|
|
fu_util_progress_status_changed_cb(FuProgress *progress, FwupdStatus status, FuUtilPrivate *priv)
|
|
{
|
|
if (priv->as_json)
|
|
return;
|
|
fu_progressbar_update(priv->progressbar, status, fu_progress_get_percentage(progress));
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_watch(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, priv->progress, error))
|
|
return FALSE;
|
|
g_main_loop_run(priv->loop);
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
fu_util_plugin_name_sort_cb(FuPlugin **item1, FuPlugin **item2)
|
|
{
|
|
return fu_plugin_name_compare(*item1, *item2);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_plugins_as_json(FuUtilPrivate *priv, GPtrArray *plugins, GError **error)
|
|
{
|
|
g_autoptr(JsonBuilder) builder = json_builder_new();
|
|
json_builder_begin_object(builder);
|
|
|
|
json_builder_set_member_name(builder, "Plugins");
|
|
json_builder_begin_array(builder);
|
|
for (guint i = 0; i < plugins->len; i++) {
|
|
FwupdPlugin *plugin = g_ptr_array_index(plugins, i);
|
|
json_builder_begin_object(builder);
|
|
fwupd_plugin_to_json(plugin, builder);
|
|
json_builder_end_object(builder);
|
|
}
|
|
json_builder_end_array(builder);
|
|
json_builder_end_object(builder);
|
|
return fu_util_print_builder(builder, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_plugins(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
GPtrArray *plugins;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_HWINFO, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* print */
|
|
plugins = fu_engine_get_plugins(priv->engine);
|
|
g_ptr_array_sort(plugins, (GCompareFunc)fu_util_plugin_name_sort_cb);
|
|
if (priv->as_json)
|
|
return fu_util_get_plugins_as_json(priv, plugins, error);
|
|
|
|
/* print */
|
|
for (guint i = 0; i < plugins->len; i++) {
|
|
FuPlugin *plugin = g_ptr_array_index(plugins, i);
|
|
g_autofree gchar *str = fu_util_plugin_to_string(FWUPD_PLUGIN(plugin), 0);
|
|
g_print("%s\n", str);
|
|
}
|
|
if (plugins->len == 0) {
|
|
/* TRANSLATORS: nothing found */
|
|
g_print("%s\n", _("No plugins found"));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_filter_device(FuUtilPrivate *priv, FwupdDevice *dev)
|
|
{
|
|
if (priv->filter_include != FWUPD_DEVICE_FLAG_NONE) {
|
|
if (!fwupd_device_has_flag(dev, priv->filter_include))
|
|
return FALSE;
|
|
}
|
|
if (priv->filter_exclude != FWUPD_DEVICE_FLAG_NONE) {
|
|
if (fwupd_device_has_flag(dev, priv->filter_exclude))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static FuDevice *
|
|
fu_util_prompt_for_device(FuUtilPrivate *priv, GPtrArray *devices_opt, GError **error)
|
|
{
|
|
FuDevice *dev;
|
|
guint idx;
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GPtrArray) devices_filtered = NULL;
|
|
|
|
/* get devices from daemon */
|
|
if (devices_opt != NULL) {
|
|
devices = g_ptr_array_ref(devices_opt);
|
|
} else {
|
|
devices = fu_engine_get_devices(priv->engine, error);
|
|
if (devices == NULL)
|
|
return NULL;
|
|
}
|
|
fwupd_device_array_ensure_parents(devices);
|
|
|
|
/* filter results */
|
|
devices_filtered = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
dev = g_ptr_array_index(devices, i);
|
|
if (!fu_util_filter_device(priv, FWUPD_DEVICE(dev)))
|
|
continue;
|
|
g_ptr_array_add(devices_filtered, g_object_ref(dev));
|
|
}
|
|
|
|
/* nothing */
|
|
if (devices_filtered->len == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"No supported devices");
|
|
return NULL;
|
|
}
|
|
|
|
/* exactly one */
|
|
if (devices_filtered->len == 1) {
|
|
dev = g_ptr_array_index(devices_filtered, 0);
|
|
if (!priv->as_json) {
|
|
/* TRANSLATORS: device has been chosen by the daemon for the user */
|
|
g_print("%s: %s\n", _("Selected device"), fu_device_get_name(dev));
|
|
}
|
|
return g_object_ref(dev);
|
|
}
|
|
|
|
/* no questions */
|
|
if (priv->no_device_prompt) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"can't prompt for devices");
|
|
return NULL;
|
|
}
|
|
|
|
/* TRANSLATORS: get interactive prompt */
|
|
g_print("%s\n", _("Choose a device:"));
|
|
/* TRANSLATORS: this is to abort the interactive prompt */
|
|
g_print("0.\t%s\n", _("Cancel"));
|
|
for (guint i = 0; i < devices_filtered->len; i++) {
|
|
dev = g_ptr_array_index(devices_filtered, i);
|
|
g_print("%u.\t%s (%s)\n", i + 1, fu_device_get_id(dev), fu_device_get_name(dev));
|
|
}
|
|
idx = fu_util_prompt_for_number(devices_filtered->len);
|
|
if (idx == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"Request canceled");
|
|
return NULL;
|
|
}
|
|
dev = g_ptr_array_index(devices_filtered, idx - 1);
|
|
return g_object_ref(dev);
|
|
}
|
|
|
|
static FuDevice *
|
|
fu_util_get_device(FuUtilPrivate *priv, const gchar *id, GError **error)
|
|
{
|
|
if (fwupd_guid_is_valid(id)) {
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
devices = fu_engine_get_devices_by_guid(priv->engine, id, error);
|
|
if (devices == NULL)
|
|
return NULL;
|
|
return fu_util_prompt_for_device(priv, devices, error);
|
|
}
|
|
|
|
/* did this look like a GUID? */
|
|
for (guint i = 0; id[i] != '\0'; i++) {
|
|
if (id[i] == '-') {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return NULL;
|
|
}
|
|
}
|
|
return fu_engine_get_device(priv->engine, id, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_updates(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GNode) root = g_node_new(NULL);
|
|
g_autoptr(GPtrArray) devices_no_support = g_ptr_array_new();
|
|
g_autoptr(GPtrArray) devices_no_upgrades = g_ptr_array_new();
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* parse arguments */
|
|
if (g_strv_length(values) == 0) {
|
|
devices = fu_engine_get_devices(priv->engine, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
} else if (g_strv_length(values) == 1) {
|
|
FuDevice *device;
|
|
device = fu_util_get_device(priv, values[0], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
g_ptr_array_add(devices, device);
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
fwupd_device_array_ensure_parents(devices);
|
|
g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb);
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
FwupdDevice *dev = g_ptr_array_index(devices, i);
|
|
g_autoptr(GPtrArray) rels = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
GNode *child;
|
|
|
|
/* not going to have results, so save a engine round-trip */
|
|
if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) &&
|
|
!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN))
|
|
continue;
|
|
if (!fu_util_filter_device(priv, dev))
|
|
continue;
|
|
if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) {
|
|
g_ptr_array_add(devices_no_support, dev);
|
|
continue;
|
|
}
|
|
|
|
/* get the releases for this device and filter for validity */
|
|
rels = fu_engine_get_upgrades(priv->engine,
|
|
priv->request,
|
|
fwupd_device_get_id(dev),
|
|
&error_local);
|
|
if (rels == NULL) {
|
|
g_ptr_array_add(devices_no_upgrades, dev);
|
|
/* discard the actual reason from user, but leave for debugging */
|
|
g_debug("%s", error_local->message);
|
|
continue;
|
|
}
|
|
child = g_node_append_data(root, dev);
|
|
|
|
for (guint j = 0; j < rels->len; j++) {
|
|
FwupdRelease *rel = g_ptr_array_index(rels, j);
|
|
g_node_append_data(child, g_object_ref(rel));
|
|
}
|
|
}
|
|
|
|
/* devices that have no updates available for whatever reason */
|
|
if (devices_no_support->len > 0) {
|
|
/* TRANSLATORS: message letting the user know no device upgrade
|
|
* available due to missing on LVFS */
|
|
g_printerr("%s\n", _("Devices with no available firmware updates: "));
|
|
for (guint i = 0; i < devices_no_support->len; i++) {
|
|
FwupdDevice *dev = g_ptr_array_index(devices_no_support, i);
|
|
g_printerr(" • %s\n", fwupd_device_get_name(dev));
|
|
}
|
|
}
|
|
if (devices_no_upgrades->len > 0) {
|
|
/* TRANSLATORS: message letting the user know no device upgrade available */
|
|
g_printerr("%s\n", _("Devices with the latest available firmware version:"));
|
|
for (guint i = 0; i < devices_no_upgrades->len; i++) {
|
|
FwupdDevice *dev = g_ptr_array_index(devices_no_upgrades, i);
|
|
g_printerr(" • %s\n", fwupd_device_get_name(dev));
|
|
}
|
|
}
|
|
|
|
/* updates */
|
|
if (g_node_n_nodes(root, G_TRAVERSE_ALL) <= 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
_("No updates available for remaining devices"));
|
|
return FALSE;
|
|
}
|
|
|
|
fu_util_print_tree(priv->client, root);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_details(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) array = NULL;
|
|
g_autoptr(GNode) root = g_node_new(NULL);
|
|
gint fd;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* implied, important for get-details on a device not in your system */
|
|
priv->show_all = TRUE;
|
|
|
|
/* open file */
|
|
fd = open(values[0], O_RDONLY);
|
|
if (fd < 0) {
|
|
fu_util_maybe_prefix_sandbox_error(values[0], error);
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"failed to open %s",
|
|
values[0]);
|
|
return FALSE;
|
|
}
|
|
array = fu_engine_get_details(priv->engine, priv->request, fd, error);
|
|
close(fd);
|
|
|
|
if (array == NULL)
|
|
return FALSE;
|
|
for (guint i = 0; i < array->len; i++) {
|
|
FwupdDevice *dev = g_ptr_array_index(array, i);
|
|
FwupdRelease *rel;
|
|
GNode *child;
|
|
if (!fu_util_filter_device(priv, dev))
|
|
continue;
|
|
child = g_node_append_data(root, dev);
|
|
rel = fwupd_device_get_release_default(dev);
|
|
if (rel != NULL)
|
|
g_node_append_data(child, rel);
|
|
}
|
|
fu_util_print_tree(priv->client, root);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_device_flags(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GString) str = g_string_new(NULL);
|
|
|
|
for (FwupdDeviceFlags i = FWUPD_DEVICE_FLAG_INTERNAL; i < FWUPD_DEVICE_FLAG_UNKNOWN;
|
|
i <<= 1) {
|
|
const gchar *tmp = fwupd_device_flag_to_string(i);
|
|
if (tmp == NULL)
|
|
break;
|
|
if (i != FWUPD_DEVICE_FLAG_INTERNAL)
|
|
g_string_append(str, " ");
|
|
g_string_append(str, tmp);
|
|
g_string_append(str, " ~");
|
|
g_string_append(str, tmp);
|
|
}
|
|
g_print("%s\n", str->str);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_util_build_device_tree(FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev)
|
|
{
|
|
for (guint i = 0; i < devs->len; i++) {
|
|
FuDevice *dev_tmp = g_ptr_array_index(devs, i);
|
|
if (!fu_util_filter_device(priv, FWUPD_DEVICE(dev_tmp)))
|
|
continue;
|
|
if (!priv->show_all && !fu_util_is_interesting_device(FWUPD_DEVICE(dev_tmp)))
|
|
continue;
|
|
if (fu_device_get_parent(dev_tmp) == dev) {
|
|
GNode *child = g_node_append_data(root, dev_tmp);
|
|
fu_util_build_device_tree(priv, child, devs, dev_tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_devices(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GNode) root = g_node_new(NULL);
|
|
g_autoptr(GPtrArray) devs = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* get devices and build tree */
|
|
devs = fu_engine_get_devices(priv->engine, error);
|
|
if (devs == NULL)
|
|
return FALSE;
|
|
if (devs->len > 0) {
|
|
fwupd_device_array_ensure_parents(devs);
|
|
fu_util_build_device_tree(priv, root, devs, NULL);
|
|
}
|
|
|
|
/* print */
|
|
if (g_node_n_children(root) == 0) {
|
|
/* TRANSLATORS: nothing attached that can be upgraded */
|
|
g_print("%s\n", _("No hardware detected with firmware update capability"));
|
|
return TRUE;
|
|
}
|
|
fu_util_print_tree(priv->client, root);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_util_update_device_changed_cb(FwupdClient *client, FwupdDevice *device, FuUtilPrivate *priv)
|
|
{
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* allowed to set whenever the device has changed */
|
|
if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN))
|
|
priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN;
|
|
if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT))
|
|
priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT;
|
|
|
|
/* same as last time, so ignore */
|
|
if (priv->current_device == NULL ||
|
|
g_strcmp0(fwupd_device_get_composite_id(priv->current_device),
|
|
fwupd_device_get_composite_id(device)) == 0) {
|
|
g_set_object(&priv->current_device, device);
|
|
return;
|
|
}
|
|
|
|
/* ignore indirect devices that might have changed */
|
|
if (fwupd_device_get_status(device) == FWUPD_STATUS_IDLE ||
|
|
fwupd_device_get_status(device) == FWUPD_STATUS_UNKNOWN) {
|
|
g_debug("ignoring %s with status %s",
|
|
fwupd_device_get_name(device),
|
|
fwupd_status_to_string(fwupd_device_get_status(device)));
|
|
return;
|
|
}
|
|
|
|
/* show message in progressbar */
|
|
if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) {
|
|
/* TRANSLATORS: %1 is a device name */
|
|
str = g_strdup_printf(_("Updating %s…"), fwupd_device_get_name(device));
|
|
fu_progressbar_set_title(priv->progressbar, str);
|
|
} else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) {
|
|
/* TRANSLATORS: %1 is a device name */
|
|
str = g_strdup_printf(_("Installing on %s…"), fwupd_device_get_name(device));
|
|
fu_progressbar_set_title(priv->progressbar, str);
|
|
} else if (priv->current_operation == FU_UTIL_OPERATION_READ) {
|
|
/* TRANSLATORS: %1 is a device name */
|
|
str = g_strdup_printf(_("Reading from %s…"), fwupd_device_get_name(device));
|
|
fu_progressbar_set_title(priv->progressbar, str);
|
|
} else {
|
|
g_warning("no FuUtilOperation set");
|
|
}
|
|
g_set_object(&priv->current_device, device);
|
|
}
|
|
|
|
static void
|
|
fu_util_display_current_message(FuUtilPrivate *priv)
|
|
{
|
|
/* print all POST requests */
|
|
for (guint i = 0; i < priv->post_requests->len; i++) {
|
|
FwupdRequest *request = g_ptr_array_index(priv->post_requests, i);
|
|
g_print("%s\n", fwupd_request_get_message(request));
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_install_blob(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(GBytes) blob_fw = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 2, "parse");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 30, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_WRITE, 68, NULL);
|
|
|
|
/* invalid args */
|
|
if (g_strv_length(values) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse blob */
|
|
blob_fw = fu_bytes_get_contents(values[0], error);
|
|
if (blob_fw == NULL) {
|
|
fu_util_maybe_prefix_sandbox_error(values[0], error);
|
|
return FALSE;
|
|
}
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_UPDATABLE;
|
|
if (g_strv_length(values) >= 2) {
|
|
device = fu_util_get_device(priv, values[1], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
priv->current_operation = FU_UTIL_OPERATION_INSTALL;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
|
|
/* write bare firmware */
|
|
if (priv->prepare_blob) {
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
g_ptr_array_add(devices, g_object_ref(device));
|
|
if (!fu_engine_composite_prepare(priv->engine, devices, error)) {
|
|
g_prefix_error(error, "failed to prepare composite action: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY;
|
|
if (!fu_engine_install_blob(priv->engine,
|
|
device,
|
|
blob_fw,
|
|
fu_progress_get_child(priv->progress),
|
|
priv->flags,
|
|
fu_engine_request_get_feature_flags(priv->request),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* cleanup */
|
|
if (priv->cleanup_blob) {
|
|
g_autoptr(FuDevice) device_new = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* get the possibly new device from the old ID */
|
|
device_new = fu_util_get_device(priv, fu_device_get_id(device), &error_local);
|
|
if (device_new == NULL) {
|
|
g_debug("failed to find new device: %s", error_local->message);
|
|
} else {
|
|
g_autoptr(GPtrArray) devices_new =
|
|
g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
g_ptr_array_add(devices_new, g_steal_pointer(&device_new));
|
|
if (!fu_engine_composite_cleanup(priv->engine, devices_new, error)) {
|
|
g_prefix_error(error, "failed to cleanup composite action: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
fu_util_display_current_message(priv);
|
|
|
|
/* success */
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_sign(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
|
|
g_autoptr(GBytes) archive_blob_new = NULL;
|
|
g_autoptr(GBytes) archive_blob_old = NULL;
|
|
g_autoptr(GBytes) cert = NULL;
|
|
g_autoptr(GBytes) privkey = NULL;
|
|
|
|
/* invalid args */
|
|
if (g_strv_length(values) != 3) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments, expected firmware.cab "
|
|
"certificate.pem privatekey.pfx");
|
|
return FALSE;
|
|
}
|
|
|
|
/* load arguments */
|
|
archive_blob_old = fu_bytes_get_contents(values[0], error);
|
|
if (archive_blob_old == NULL)
|
|
return FALSE;
|
|
cert = fu_bytes_get_contents(values[1], error);
|
|
if (cert == NULL)
|
|
return FALSE;
|
|
privkey = fu_bytes_get_contents(values[2], error);
|
|
if (privkey == NULL)
|
|
return FALSE;
|
|
|
|
/* load, sign, export */
|
|
if (!fu_cabinet_parse(cabinet, archive_blob_old, FU_CABINET_PARSE_FLAG_NONE, error))
|
|
return FALSE;
|
|
if (!fu_cabinet_sign(cabinet, cert, privkey, FU_CABINET_SIGN_FLAG_NONE, error))
|
|
return FALSE;
|
|
archive_blob_new = fu_cabinet_export(cabinet, FU_CABINET_EXPORT_FLAG_NONE, error);
|
|
if (archive_blob_new == NULL)
|
|
return FALSE;
|
|
return fu_bytes_set_contents(values[0], archive_blob_new, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_dump(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(GBytes) blob_empty = g_bytes_new(NULL, 0);
|
|
g_autoptr(GBytes) blob_fw = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_READ, 5, NULL);
|
|
|
|
/* invalid args */
|
|
if (g_strv_length(values) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* file already exists */
|
|
if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 &&
|
|
g_file_test(values[0], G_FILE_TEST_EXISTS)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Filename already exists");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write a zero length file to ensure the destination is writable to
|
|
* avoid failing at the end of a potentially lengthy operation */
|
|
if (!fu_bytes_set_contents(values[0], blob_empty, error))
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE;
|
|
if (g_strv_length(values) >= 2) {
|
|
device = fu_util_get_device(priv, values[1], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
}
|
|
priv->current_operation = FU_UTIL_OPERATION_READ;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
|
|
/* dump firmware */
|
|
blob_fw = fu_engine_firmware_dump(priv->engine,
|
|
device,
|
|
fu_progress_get_child(priv->progress),
|
|
priv->flags,
|
|
error);
|
|
if (blob_fw == NULL)
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
return fu_bytes_set_contents(values[0], blob_fw, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_read(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(FuFirmware) fw = NULL;
|
|
g_autoptr(GBytes) blob_empty = g_bytes_new(NULL, 0);
|
|
g_autoptr(GBytes) blob_fw = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_READ, 5, NULL);
|
|
|
|
/* invalid args */
|
|
if (g_strv_length(values) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* file already exists */
|
|
if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 &&
|
|
g_file_test(values[0], G_FILE_TEST_EXISTS)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Filename already exists");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write a zero length file to ensure the destination is writable to
|
|
* avoid failing at the end of a potentially lengthy operation */
|
|
if (!fu_bytes_set_contents(values[0], blob_empty, error))
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE;
|
|
if (g_strv_length(values) >= 2) {
|
|
device = fu_util_get_device(priv, values[1], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
}
|
|
priv->current_operation = FU_UTIL_OPERATION_READ;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
|
|
/* read firmware into the container format */
|
|
fw = fu_engine_firmware_read(priv->engine,
|
|
device,
|
|
fu_progress_get_child(priv->progress),
|
|
priv->flags,
|
|
error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
blob_fw = fu_firmware_write(fw, error);
|
|
if (blob_fw == NULL)
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
return fu_bytes_set_contents(values[0], blob_fw, error);
|
|
}
|
|
|
|
static gint
|
|
fu_util_release_sort_cb(gconstpointer a, gconstpointer b)
|
|
{
|
|
FuRelease *release1 = *((FuRelease **)a);
|
|
FuRelease *release2 = *((FuRelease **)b);
|
|
return fu_release_compare(release1, release2);
|
|
}
|
|
|
|
static gchar *
|
|
fu_util_download_if_required(FuUtilPrivate *priv, const gchar *perhapsfn, GError **error)
|
|
{
|
|
g_autofree gchar *filename = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
/* a local file */
|
|
if (g_file_test(perhapsfn, G_FILE_TEST_EXISTS))
|
|
return g_strdup(perhapsfn);
|
|
if (!fu_util_is_url(perhapsfn))
|
|
return g_strdup(perhapsfn);
|
|
|
|
/* download the firmware to a cachedir */
|
|
filename = fu_util_get_user_cache_path(perhapsfn);
|
|
if (!fu_path_mkdir_parent(filename, error))
|
|
return NULL;
|
|
file = g_file_new_for_path(filename);
|
|
if (!fwupd_client_download_file(priv->client,
|
|
perhapsfn,
|
|
file,
|
|
FWUPD_CLIENT_DOWNLOAD_FLAG_NONE,
|
|
priv->cancellable,
|
|
error))
|
|
return NULL;
|
|
return g_steal_pointer(&filename);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_install(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autofree gchar *filename = NULL;
|
|
g_autoptr(GBytes) blob_cab = NULL;
|
|
g_autoptr(GPtrArray) components = NULL;
|
|
g_autoptr(GPtrArray) devices_possible = NULL;
|
|
g_autoptr(GPtrArray) errors = NULL;
|
|
g_autoptr(GPtrArray) releases = NULL;
|
|
g_autoptr(XbSilo) silo = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 50, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* handle both forms */
|
|
if (g_strv_length(values) == 1) {
|
|
devices_possible = fu_engine_get_devices(priv->engine, error);
|
|
if (devices_possible == NULL)
|
|
return FALSE;
|
|
fwupd_device_array_ensure_parents(devices_possible);
|
|
} else if (g_strv_length(values) == 2) {
|
|
FuDevice *device = fu_util_get_device(priv, values[1], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
if (!priv->no_safety_check) {
|
|
if (!fu_util_prompt_warning_fde(FWUPD_DEVICE(device), error))
|
|
return FALSE;
|
|
}
|
|
devices_possible =
|
|
fu_engine_get_devices_by_composite_id(priv->engine,
|
|
fu_device_get_composite_id(device),
|
|
error);
|
|
if (devices_possible == NULL)
|
|
return FALSE;
|
|
|
|
g_ptr_array_add(devices_possible, device);
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* download if required */
|
|
filename = fu_util_download_if_required(priv, values[0], error);
|
|
if (filename == NULL)
|
|
return FALSE;
|
|
|
|
/* parse silo */
|
|
blob_cab = fu_bytes_get_contents(filename, error);
|
|
if (blob_cab == NULL) {
|
|
fu_util_maybe_prefix_sandbox_error(filename, error);
|
|
return FALSE;
|
|
}
|
|
silo = fu_engine_get_silo_from_blob(priv->engine, blob_cab, error);
|
|
if (silo == NULL)
|
|
return FALSE;
|
|
components = xb_silo_query(silo, "components/component", 0, error);
|
|
if (components == NULL)
|
|
return FALSE;
|
|
|
|
/* for each component in the silo */
|
|
errors = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free);
|
|
releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
for (guint i = 0; i < components->len; i++) {
|
|
XbNode *component = g_ptr_array_index(components, i);
|
|
|
|
/* do any devices pass the requirements */
|
|
for (guint j = 0; j < devices_possible->len; j++) {
|
|
FuDevice *device = g_ptr_array_index(devices_possible, j);
|
|
g_autoptr(FuRelease) release = fu_release_new();
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* is this component valid for the device */
|
|
fu_release_set_device(release, device);
|
|
fu_release_set_request(release, priv->request);
|
|
if (!fu_release_load(release, component, NULL, priv->flags, &error_local)) {
|
|
g_debug("loading release failed on %s:%s failed: %s",
|
|
fu_device_get_id(device),
|
|
xb_node_query_text(component, "id", NULL),
|
|
error_local->message);
|
|
g_ptr_array_add(errors, g_steal_pointer(&error_local));
|
|
continue;
|
|
}
|
|
if (!fu_engine_check_requirements(priv->engine,
|
|
release,
|
|
priv->flags | FWUPD_INSTALL_FLAG_FORCE,
|
|
&error_local)) {
|
|
g_debug("first pass requirement on %s:%s failed: %s",
|
|
fu_device_get_id(device),
|
|
xb_node_query_text(component, "id", NULL),
|
|
error_local->message);
|
|
g_ptr_array_add(errors, g_steal_pointer(&error_local));
|
|
continue;
|
|
}
|
|
|
|
/* make a second pass using possibly updated version format now */
|
|
fu_engine_md_refresh_device_from_component(priv->engine, device, component);
|
|
if (!fu_engine_check_requirements(priv->engine,
|
|
release,
|
|
priv->flags,
|
|
&error_local)) {
|
|
g_debug("second pass requirement on %s:%s failed: %s",
|
|
fu_device_get_id(device),
|
|
xb_node_query_text(component, "id", NULL),
|
|
error_local->message);
|
|
g_ptr_array_add(errors, g_steal_pointer(&error_local));
|
|
continue;
|
|
}
|
|
|
|
/* if component should have an update message from CAB */
|
|
fu_device_incorporate_from_component(device, component);
|
|
|
|
/* success */
|
|
g_ptr_array_add(releases, g_steal_pointer(&release));
|
|
}
|
|
}
|
|
|
|
/* order the install tasks by the device priority */
|
|
g_ptr_array_sort(releases, fu_util_release_sort_cb);
|
|
|
|
/* nothing suitable */
|
|
if (releases->len == 0) {
|
|
GError *error_tmp = fu_engine_error_array_get_best(errors);
|
|
g_propagate_error(error, error_tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
priv->current_operation = FU_UTIL_OPERATION_INSTALL;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
|
|
/* install all the tasks */
|
|
if (!fu_engine_install_releases(priv->engine,
|
|
priv->request,
|
|
releases,
|
|
blob_cab,
|
|
fu_progress_get_child(priv->progress),
|
|
priv->flags,
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
fu_util_display_current_message(priv);
|
|
|
|
/* we don't want to ask anything */
|
|
if (priv->no_reboot_check) {
|
|
g_debug("skipping reboot check");
|
|
return TRUE;
|
|
}
|
|
|
|
/* success */
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_install_release(FuUtilPrivate *priv, FwupdRelease *rel, GError **error)
|
|
{
|
|
FwupdRemote *remote;
|
|
GPtrArray *locations;
|
|
const gchar *remote_id;
|
|
const gchar *uri_tmp;
|
|
g_auto(GStrv) argv = NULL;
|
|
|
|
/* get the default release only until other parts of fwupd can cope */
|
|
locations = fwupd_release_get_locations(rel);
|
|
if (locations->len == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"release missing URI");
|
|
return FALSE;
|
|
}
|
|
uri_tmp = g_ptr_array_index(locations, 0);
|
|
remote_id = fwupd_release_get_remote_id(rel);
|
|
if (remote_id == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"failed to find remote for %s",
|
|
uri_tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
remote = fu_engine_get_remote_by_id(priv->engine, remote_id, error);
|
|
if (remote == NULL)
|
|
return FALSE;
|
|
|
|
argv = g_new0(gchar *, 2);
|
|
/* local remotes may have the firmware already */
|
|
if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL && !fu_util_is_url(uri_tmp)) {
|
|
const gchar *fn_cache = fwupd_remote_get_filename_cache(remote);
|
|
g_autofree gchar *path = g_path_get_dirname(fn_cache);
|
|
argv[0] = g_build_filename(path, uri_tmp, NULL);
|
|
} else if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) {
|
|
argv[0] = g_strdup(uri_tmp + 7);
|
|
/* web remote, fu_util_install will download file */
|
|
} else {
|
|
argv[0] = fwupd_remote_build_firmware_uri(remote, uri_tmp, error);
|
|
}
|
|
return fu_util_install(priv, argv, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_update(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
gboolean no_updates_header = FALSE;
|
|
gboolean latest_header = FALSE;
|
|
|
|
if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"--allow-older is not supported for this command");
|
|
return FALSE;
|
|
}
|
|
|
|
if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"--allow-reinstall is not supported for this command");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* DEVICE-ID and GUID are acceptable args to update */
|
|
for (guint idx = 0; idx < g_strv_length(values); idx++) {
|
|
if (!fwupd_guid_is_valid(values[idx]) && !fwupd_device_id_is_valid(values[idx])) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"'%s' is not a valid GUID nor DEVICE-ID",
|
|
values[idx]);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
priv->current_operation = FU_UTIL_OPERATION_UPDATE;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
|
|
devices = fu_engine_get_devices(priv->engine, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
fwupd_device_array_ensure_parents(devices);
|
|
g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb);
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
FwupdDevice *dev = g_ptr_array_index(devices, i);
|
|
FwupdRelease *rel;
|
|
const gchar *device_id = fu_device_get_id(dev);
|
|
g_autoptr(GPtrArray) rels = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
gboolean dev_skip_byid = TRUE;
|
|
|
|
/* only process particular DEVICE-ID or GUID if specified */
|
|
for (guint idx = 0; idx < g_strv_length(values); idx++) {
|
|
const gchar *tmpid = values[idx];
|
|
if (fwupd_device_has_guid(dev, tmpid) || g_strcmp0(device_id, tmpid) == 0) {
|
|
dev_skip_byid = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (g_strv_length(values) > 0 && dev_skip_byid)
|
|
continue;
|
|
if (!fu_util_is_interesting_device(dev))
|
|
continue;
|
|
/* only show stuff that has metadata available */
|
|
if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE))
|
|
continue;
|
|
if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) {
|
|
if (!no_updates_header) {
|
|
g_printerr("%s\n",
|
|
/* TRANSLATORS: message letting the user know no device
|
|
* upgrade available due to missing on LVFS */
|
|
_("Devices with no available firmware updates: "));
|
|
no_updates_header = TRUE;
|
|
}
|
|
g_printerr(" • %s\n", fwupd_device_get_name(dev));
|
|
continue;
|
|
}
|
|
if (!fu_util_filter_device(priv, dev))
|
|
continue;
|
|
|
|
rels = fu_engine_get_upgrades(priv->engine, priv->request, device_id, &error_local);
|
|
if (rels == NULL) {
|
|
if (!latest_header) {
|
|
g_printerr(
|
|
"%s\n",
|
|
/* TRANSLATORS: message letting the user know no device upgrade
|
|
* available */
|
|
_("Devices with the latest available firmware version:"));
|
|
latest_header = TRUE;
|
|
}
|
|
g_printerr(" • %s\n", fwupd_device_get_name(dev));
|
|
/* discard the actual reason from user, but leave for debugging */
|
|
g_debug("%s", error_local->message);
|
|
continue;
|
|
}
|
|
|
|
rel = g_ptr_array_index(rels, 0);
|
|
if (!priv->no_safety_check) {
|
|
g_autofree gchar *title =
|
|
g_strdup_printf("%s %s",
|
|
fu_engine_get_host_vendor(priv->engine),
|
|
fu_engine_get_host_product(priv->engine));
|
|
if (!fu_util_prompt_warning(dev, rel, title, error))
|
|
return FALSE;
|
|
if (!fu_util_prompt_warning_fde(dev, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_util_install_release(priv, rel, &error_local)) {
|
|
g_printerr("%s\n", error_local->message);
|
|
continue;
|
|
}
|
|
fu_util_display_current_message(priv);
|
|
}
|
|
|
|
/* we don't want to ask anything */
|
|
if (priv->no_reboot_check) {
|
|
g_debug("skipping reboot check");
|
|
return TRUE;
|
|
}
|
|
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_reinstall(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FwupdRelease) rel = NULL;
|
|
g_autoptr(GPtrArray) rels = NULL;
|
|
g_autoptr(FuDevice) dev = NULL;
|
|
|
|
if (g_strv_length(values) != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
dev = fu_util_get_device(priv, values[0], error);
|
|
if (dev == NULL)
|
|
return FALSE;
|
|
|
|
/* try to lookup/match release from client */
|
|
rels = fu_engine_get_releases_for_device(priv->engine, priv->request, dev, error);
|
|
if (rels == NULL)
|
|
return FALSE;
|
|
|
|
for (guint j = 0; j < rels->len; j++) {
|
|
FwupdRelease *rel_tmp = g_ptr_array_index(rels, j);
|
|
if (fu_version_compare(fwupd_release_get_version(rel_tmp),
|
|
fu_device_get_version(dev),
|
|
fu_device_get_version_format(dev)) == 0) {
|
|
rel = g_object_ref(rel_tmp);
|
|
break;
|
|
}
|
|
}
|
|
if (rel == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Unable to locate release for %s version %s",
|
|
fu_device_get_name(dev),
|
|
fu_device_get_version(dev));
|
|
return FALSE;
|
|
}
|
|
|
|
/* update the console if composite devices are also updated */
|
|
priv->current_operation = FU_UTIL_OPERATION_INSTALL;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
|
|
if (!fu_util_install_release(priv, rel, error))
|
|
return FALSE;
|
|
fu_util_display_current_message(priv);
|
|
|
|
/* we don't want to ask anything */
|
|
if (priv->no_reboot_check) {
|
|
g_debug("skipping reboot check");
|
|
return TRUE;
|
|
}
|
|
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_detach(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_exclude |= FWUPD_DEVICE_FLAG_IS_BOOTLOADER;
|
|
if (g_strv_length(values) >= 1) {
|
|
device = fu_util_get_device(priv, values[0], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* run vfunc */
|
|
locker = fu_device_locker_new(device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
if (!fu_device_detach_full(device, fu_progress_get_child(priv->progress), error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_unbind_driver(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* get device */
|
|
if (g_strv_length(values) == 1) {
|
|
device = fu_util_get_device(priv, values[0], error);
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
}
|
|
if (device == NULL)
|
|
return FALSE;
|
|
|
|
/* run vfunc */
|
|
locker = fu_device_locker_new(device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
return fu_device_unbind_driver(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_bind_driver(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* get device */
|
|
if (g_strv_length(values) == 3) {
|
|
device = fu_util_get_device(priv, values[2], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else if (g_strv_length(values) == 2) {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* run vfunc */
|
|
locker = fu_device_locker_new(device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
return fu_device_bind_driver(device, values[0], values[1], error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_attach(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuDevice) device = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_IS_BOOTLOADER;
|
|
if (g_strv_length(values) >= 1) {
|
|
device = fu_util_get_device(priv, values[0], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
} else {
|
|
device = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* run vfunc */
|
|
locker = fu_device_locker_new(device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
if (!fu_device_attach_full(device, fu_progress_get_child(priv->progress), error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_check_activation_needed(FuUtilPrivate *priv, GError **error)
|
|
{
|
|
gboolean has_pending = FALSE;
|
|
g_autoptr(FuHistory) history = fu_history_new();
|
|
g_autoptr(GPtrArray) devices = fu_history_get_devices(history, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
|
|
/* only start up the plugins needed */
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
FuDevice *dev = g_ptr_array_index(devices, i);
|
|
if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) {
|
|
fu_engine_add_plugin_filter(priv->engine, fu_device_get_plugin(dev));
|
|
has_pending = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!has_pending) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"No devices to activate");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_activate(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
gboolean has_pending = FALSE;
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
|
|
/* check the history database before starting the daemon */
|
|
if (!fu_util_check_activation_needed(priv, error))
|
|
return FALSE;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_COLDPLUG |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_HWINFO,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* parse arguments */
|
|
if (g_strv_length(values) == 0) {
|
|
devices = fu_engine_get_devices(priv->engine, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
} else if (g_strv_length(values) == 1) {
|
|
FuDevice *device;
|
|
device = fu_util_get_device(priv, values[0], error);
|
|
if (device == NULL)
|
|
return FALSE;
|
|
devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
g_ptr_array_add(devices, device);
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
|
|
/* activate anything with _NEEDS_ACTIVATION */
|
|
/* order by device priority */
|
|
g_ptr_array_sort(devices, fu_util_device_order_sort_cb);
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
FuDevice *device = g_ptr_array_index(devices, i);
|
|
if (!fu_util_filter_device(priv, FWUPD_DEVICE(device)))
|
|
continue;
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION))
|
|
continue;
|
|
has_pending = TRUE;
|
|
/* TRANSLATORS: shown when shutting down to switch to the new version */
|
|
g_print("%s %s…\n", _("Activating firmware update"), fu_device_get_name(device));
|
|
if (!fu_engine_activate(priv->engine,
|
|
fu_device_get_id(device),
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
}
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
if (!has_pending) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"No devices to activate");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_export_hwids(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuHwids) hwids = fu_hwids_new();
|
|
g_autoptr(FuSmbios) smbios = fu_smbios_new();
|
|
g_autoptr(GKeyFile) kf = g_key_file_new();
|
|
g_autoptr(GPtrArray) hwid_keys = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments, expected HWIDS-FILE");
|
|
return FALSE;
|
|
}
|
|
|
|
/* setup default hwids */
|
|
if (!fu_smbios_setup(smbios, error))
|
|
return FALSE;
|
|
if (!fu_hwids_setup(hwids, smbios, error))
|
|
return FALSE;
|
|
|
|
/* save all keys */
|
|
hwid_keys = fu_hwids_get_keys(hwids);
|
|
for (guint i = 0; i < hwid_keys->len; i++) {
|
|
const gchar *hwid_key = g_ptr_array_index(hwid_keys, i);
|
|
const gchar *value = fu_hwids_get_value(hwids, hwid_key);
|
|
g_key_file_set_string(kf, "HwIds", hwid_key, value);
|
|
}
|
|
|
|
/* success */
|
|
return g_key_file_save_to_file(kf, values[0], error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_hwids(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuSmbios) smbios = NULL;
|
|
g_autoptr(FuHwids) hwids = fu_hwids_new();
|
|
g_autoptr(GPtrArray) hwid_keys = fu_hwids_get_keys(hwids);
|
|
|
|
/* read DMI data */
|
|
if (g_strv_length(values) == 0) {
|
|
smbios = fu_smbios_new();
|
|
if (!fu_smbios_setup(smbios, error))
|
|
return FALSE;
|
|
} else if (g_strv_length(values) == 1) {
|
|
/* a keyfile with overrides */
|
|
g_autoptr(GKeyFile) kf = g_key_file_new();
|
|
if (g_key_file_load_from_file(kf, values[0], G_KEY_FILE_NONE, NULL)) {
|
|
for (guint i = 0; i < hwid_keys->len; i++) {
|
|
const gchar *hwid_key = g_ptr_array_index(hwid_keys, i);
|
|
g_autofree gchar *tmp = NULL;
|
|
tmp = g_key_file_get_string(kf, "HwIds", hwid_key, NULL);
|
|
fu_hwids_add_smbios_override(hwids, hwid_key, tmp);
|
|
}
|
|
/* a DMI blob */
|
|
} else {
|
|
smbios = fu_smbios_new();
|
|
if (!fu_smbios_setup_from_file(smbios, values[0], error))
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
return FALSE;
|
|
}
|
|
if (!fu_hwids_setup(hwids, smbios, error))
|
|
return FALSE;
|
|
|
|
/* show debug output */
|
|
g_print("Computer Information\n");
|
|
g_print("--------------------\n");
|
|
for (guint i = 0; i < hwid_keys->len; i++) {
|
|
const gchar *hwid_key = g_ptr_array_index(hwid_keys, i);
|
|
const gchar *value = fu_hwids_get_value(hwids, hwid_key);
|
|
if (value == NULL)
|
|
continue;
|
|
if (g_strcmp0(hwid_key, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 ||
|
|
g_strcmp0(hwid_key, FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) {
|
|
guint64 val = g_ascii_strtoull(value, NULL, 16);
|
|
g_print("%s: %" G_GUINT64_FORMAT "\n", hwid_key, val);
|
|
} else {
|
|
g_print("%s: %s\n", hwid_key, value);
|
|
}
|
|
}
|
|
|
|
/* show GUIDs */
|
|
g_print("\nHardware IDs\n");
|
|
g_print("------------\n");
|
|
for (guint i = 0; i < 15; i++) {
|
|
const gchar *keys = NULL;
|
|
g_autofree gchar *guid = NULL;
|
|
g_autofree gchar *key = NULL;
|
|
g_autofree gchar *keys_str = NULL;
|
|
g_auto(GStrv) keysv = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* get the GUID */
|
|
key = g_strdup_printf("HardwareID-%u", i);
|
|
keys = fu_hwids_get_replace_keys(hwids, key);
|
|
guid = fu_hwids_get_guid(hwids, key, &error_local);
|
|
if (guid == NULL) {
|
|
g_print("%s\n", error_local->message);
|
|
continue;
|
|
}
|
|
|
|
/* show what makes up the GUID */
|
|
keysv = g_strsplit(keys, "&", -1);
|
|
keys_str = g_strjoinv(" + ", keysv);
|
|
g_print("{%s} <- %s\n", guid, keys_str);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_self_sign(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autofree gchar *sig = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: value expected");
|
|
return FALSE;
|
|
}
|
|
|
|
/* start engine */
|
|
if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_NONE, priv->progress, error))
|
|
return FALSE;
|
|
sig = fu_engine_self_sign(priv->engine,
|
|
values[0],
|
|
JCAT_SIGN_FLAG_ADD_TIMESTAMP | JCAT_SIGN_FLAG_ADD_CERT,
|
|
error);
|
|
if (sig == NULL)
|
|
return FALSE;
|
|
g_print("%s\n", sig);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_util_device_added_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data)
|
|
{
|
|
FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
|
|
g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0);
|
|
/* TRANSLATORS: this is when a device is hotplugged */
|
|
g_print("%s\n%s", _("Device added:"), tmp);
|
|
}
|
|
|
|
static void
|
|
fu_util_device_removed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data)
|
|
{
|
|
FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
|
|
g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0);
|
|
/* TRANSLATORS: this is when a device is hotplugged */
|
|
g_print("%s\n%s", _("Device removed:"), tmp);
|
|
}
|
|
|
|
static void
|
|
fu_util_device_changed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data)
|
|
{
|
|
FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
|
|
g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0);
|
|
/* TRANSLATORS: this is when a device has been updated */
|
|
g_print("%s\n%s", _("Device changed:"), tmp);
|
|
}
|
|
|
|
static void
|
|
fu_util_changed_cb(FwupdClient *client, gpointer user_data)
|
|
{
|
|
/* TRANSLATORS: this is when the daemon state changes */
|
|
g_print("%s\n", _("Changed"));
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_monitor(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
/* get all the devices */
|
|
if (!fwupd_client_connect(priv->client, priv->cancellable, error))
|
|
return FALSE;
|
|
|
|
/* watch for any hotplugged device */
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"changed",
|
|
G_CALLBACK(fu_util_changed_cb),
|
|
priv);
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"device-added",
|
|
G_CALLBACK(fu_util_device_added_cb),
|
|
priv);
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"device-removed",
|
|
G_CALLBACK(fu_util_device_removed_cb),
|
|
priv);
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_device_changed_cb),
|
|
priv);
|
|
g_signal_connect(G_CANCELLABLE(priv->cancellable),
|
|
"cancelled",
|
|
G_CALLBACK(fu_util_cancelled_cb),
|
|
priv);
|
|
g_main_loop_run(priv->loop);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_firmware_types(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) firmware_types = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
firmware_types = fu_context_get_firmware_gtype_ids(fu_engine_get_context(priv->engine));
|
|
for (guint i = 0; i < firmware_types->len; i++) {
|
|
const gchar *id = g_ptr_array_index(firmware_types, i);
|
|
g_print("%s\n", id);
|
|
}
|
|
if (firmware_types->len == 0) {
|
|
/* TRANSLATORS: nothing found */
|
|
g_print("%s\n", _("No firmware IDs found"));
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
fu_util_prompt_for_firmware_type(FuUtilPrivate *priv, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) firmware_types = NULL;
|
|
guint idx;
|
|
firmware_types = fu_context_get_firmware_gtype_ids(fu_engine_get_context(priv->engine));
|
|
|
|
/* TRANSLATORS: get interactive prompt */
|
|
g_print("%s\n", _("Choose a firmware type:"));
|
|
/* TRANSLATORS: this is to abort the interactive prompt */
|
|
g_print("0.\t%s\n", _("Cancel"));
|
|
for (guint i = 0; i < firmware_types->len; i++) {
|
|
const gchar *id = g_ptr_array_index(firmware_types, i);
|
|
g_print("%u.\t%s\n", i + 1, id);
|
|
}
|
|
idx = fu_util_prompt_for_number(firmware_types->len);
|
|
if (idx == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"Request canceled");
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup(g_ptr_array_index(firmware_types, idx - 1));
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_parse(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
GType gtype;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autofree gchar *firmware_type = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) == 0 || g_strv_length(values) > 2) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: filename required");
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_strv_length(values) == 2)
|
|
firmware_type = g_strdup(values[1]);
|
|
|
|
/* load file */
|
|
blob = fu_bytes_get_contents(values[0], error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* find the GType to use */
|
|
if (firmware_type == NULL)
|
|
firmware_type = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type == NULL)
|
|
return FALSE;
|
|
gtype =
|
|
fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), firmware_type);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type);
|
|
return FALSE;
|
|
}
|
|
|
|
/* does firmware specify an internal size */
|
|
firmware = g_object_new(gtype, NULL);
|
|
if (fu_firmware_has_flag(firmware, FU_FIRMWARE_FLAG_HAS_STORED_SIZE)) {
|
|
g_autoptr(FuFirmware) firmware_linear = fu_linear_firmware_new(gtype);
|
|
g_autoptr(GPtrArray) imgs = NULL;
|
|
if (!fu_firmware_parse(firmware_linear, blob, priv->flags, error))
|
|
return FALSE;
|
|
imgs = fu_firmware_get_images(firmware_linear);
|
|
if (imgs->len == 1) {
|
|
g_set_object(&firmware, g_ptr_array_index(imgs, 0));
|
|
} else {
|
|
g_set_object(&firmware, firmware_linear);
|
|
}
|
|
} else {
|
|
if (!fu_firmware_parse(firmware, blob, priv->flags, error))
|
|
return FALSE;
|
|
}
|
|
|
|
str = fu_firmware_to_string(firmware);
|
|
g_print("%s", str);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_export(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
FuFirmwareExportFlags flags = FU_FIRMWARE_EXPORT_FLAG_NONE;
|
|
GType gtype;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autofree gchar *firmware_type = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) == 0 || g_strv_length(values) > 2) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: filename required");
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_strv_length(values) == 2)
|
|
firmware_type = g_strdup(values[1]);
|
|
|
|
/* load file */
|
|
blob = fu_bytes_get_contents(values[0], error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* find the GType to use */
|
|
if (firmware_type == NULL)
|
|
firmware_type = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type == NULL)
|
|
return FALSE;
|
|
gtype =
|
|
fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), firmware_type);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type);
|
|
return FALSE;
|
|
}
|
|
firmware = g_object_new(gtype, NULL);
|
|
if (!fu_firmware_parse(firmware, blob, priv->flags, error))
|
|
return FALSE;
|
|
if (priv->show_all)
|
|
flags |= FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG;
|
|
str = fu_firmware_export_to_xml(firmware, flags, error);
|
|
if (str == NULL)
|
|
return FALSE;
|
|
g_print("%s", str);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_extract(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
GType gtype;
|
|
g_autofree gchar *firmware_type = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(GPtrArray) images = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) == 0 || g_strv_length(values) > 2) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: filename required");
|
|
return FALSE;
|
|
}
|
|
if (g_strv_length(values) == 2)
|
|
firmware_type = g_strdup(values[1]);
|
|
|
|
/* load file */
|
|
blob = fu_bytes_get_contents(values[0], error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* find the GType to use */
|
|
if (firmware_type == NULL)
|
|
firmware_type = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type == NULL)
|
|
return FALSE;
|
|
gtype =
|
|
fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), firmware_type);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type);
|
|
return FALSE;
|
|
}
|
|
firmware = g_object_new(gtype, NULL);
|
|
if (!fu_firmware_parse(firmware, blob, priv->flags, error))
|
|
return FALSE;
|
|
str = fu_firmware_to_string(firmware);
|
|
g_print("%s", str);
|
|
images = fu_firmware_get_images(firmware);
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(images, i);
|
|
g_autofree gchar *fn = NULL;
|
|
g_autoptr(GBytes) blob_img = NULL;
|
|
|
|
/* get raw image without generated header, footer or crc */
|
|
blob_img = fu_firmware_get_bytes(img, error);
|
|
if (blob_img == NULL)
|
|
return FALSE;
|
|
if (g_bytes_get_size(blob_img) == 0)
|
|
continue;
|
|
|
|
/* use suitable filename */
|
|
if (fu_firmware_get_filename(img) != NULL) {
|
|
fn = g_strdup(fu_firmware_get_filename(img));
|
|
} else if (fu_firmware_get_id(img) != NULL) {
|
|
fn = g_strdup_printf("id-%s.fw", fu_firmware_get_id(img));
|
|
} else if (fu_firmware_get_idx(img) != 0x0) {
|
|
fn = g_strdup_printf("idx-0x%x.fw", (guint)fu_firmware_get_idx(img));
|
|
} else {
|
|
fn = g_strdup_printf("img-0x%x.fw", i);
|
|
}
|
|
/* TRANSLATORS: decompressing images from a container firmware */
|
|
g_print("%s : %s\n", _("Writing file:"), fn);
|
|
if (!fu_bytes_set_contents(fn, blob_img, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_build(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
GType gtype = FU_TYPE_FIRMWARE;
|
|
const gchar *tmp;
|
|
g_autofree gchar *str = NULL;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autoptr(FuFirmware) firmware_dst = NULL;
|
|
g_autoptr(GBytes) blob_dst = NULL;
|
|
g_autoptr(GBytes) blob_src = NULL;
|
|
g_autoptr(XbBuilder) builder = xb_builder_new();
|
|
g_autoptr(XbBuilderSource) source = xb_builder_source_new();
|
|
g_autoptr(XbNode) n = NULL;
|
|
g_autoptr(XbSilo) silo = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) != 2) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: filename required");
|
|
return FALSE;
|
|
}
|
|
|
|
/* load file */
|
|
blob_src = fu_bytes_get_contents(values[0], error);
|
|
if (blob_src == NULL)
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* parse XML */
|
|
if (!xb_builder_source_load_bytes(source, blob_src, XB_BUILDER_SOURCE_FLAG_NONE, error)) {
|
|
g_prefix_error(error, "could not parse XML: ");
|
|
return FALSE;
|
|
}
|
|
xb_builder_import_source(builder, source);
|
|
silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
|
|
if (silo == NULL)
|
|
return FALSE;
|
|
|
|
/* create FuFirmware of specific GType */
|
|
n = xb_silo_query_first(silo, "firmware", error);
|
|
if (n == NULL)
|
|
return FALSE;
|
|
tmp = xb_node_get_attr(n, "gtype");
|
|
if (tmp != NULL) {
|
|
gtype = g_type_from_name(tmp);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not registered",
|
|
tmp);
|
|
return FALSE;
|
|
}
|
|
}
|
|
tmp = xb_node_get_attr(n, "id");
|
|
if (tmp != NULL) {
|
|
gtype =
|
|
fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), tmp);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
tmp);
|
|
return FALSE;
|
|
}
|
|
}
|
|
firmware = g_object_new(gtype, NULL);
|
|
if (!fu_firmware_build(firmware, n, error))
|
|
return FALSE;
|
|
|
|
/* write new file */
|
|
blob_dst = fu_firmware_write(firmware, error);
|
|
if (blob_dst == NULL)
|
|
return FALSE;
|
|
if (!fu_bytes_set_contents(values[1], blob_dst, error))
|
|
return FALSE;
|
|
|
|
/* show what we wrote */
|
|
firmware_dst = g_object_new(gtype, NULL);
|
|
if (!fu_firmware_parse(firmware_dst, blob_dst, priv->flags, error))
|
|
return FALSE;
|
|
str = fu_firmware_to_string(firmware_dst);
|
|
g_print("%s", str);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_convert(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
FuContext *ctx = fu_engine_get_context(priv->engine);
|
|
GType gtype_dst;
|
|
GType gtype_src;
|
|
g_autofree gchar *firmware_type_dst = NULL;
|
|
g_autofree gchar *firmware_type_src = NULL;
|
|
g_autofree gchar *str_dst = NULL;
|
|
g_autofree gchar *str_src = NULL;
|
|
g_autoptr(FuFirmware) firmware_dst = NULL;
|
|
g_autoptr(FuFirmware) firmware_src = NULL;
|
|
g_autoptr(GBytes) blob_dst = NULL;
|
|
g_autoptr(GBytes) blob_src = NULL;
|
|
g_autoptr(GPtrArray) images = NULL;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) < 2 || g_strv_length(values) > 4) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments: filename required");
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_strv_length(values) > 2)
|
|
firmware_type_src = g_strdup(values[2]);
|
|
if (g_strv_length(values) > 3)
|
|
firmware_type_dst = g_strdup(values[3]);
|
|
|
|
/* load file */
|
|
blob_src = fu_bytes_get_contents(values[0], error);
|
|
if (blob_src == NULL)
|
|
return FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* find the GType to use */
|
|
if (firmware_type_src == NULL)
|
|
firmware_type_src = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type_src == NULL)
|
|
return FALSE;
|
|
if (firmware_type_dst == NULL)
|
|
firmware_type_dst = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type_dst == NULL)
|
|
return FALSE;
|
|
gtype_src = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine),
|
|
firmware_type_src);
|
|
if (gtype_src == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type_src);
|
|
return FALSE;
|
|
}
|
|
firmware_src = g_object_new(gtype_src, NULL);
|
|
if (!fu_firmware_parse(firmware_src, blob_src, priv->flags, error))
|
|
return FALSE;
|
|
gtype_dst = fu_context_get_firmware_gtype_by_id(ctx, firmware_type_dst);
|
|
if (gtype_dst == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type_dst);
|
|
return FALSE;
|
|
}
|
|
str_src = fu_firmware_to_string(firmware_src);
|
|
g_print("%s", str_src);
|
|
|
|
/* copy images */
|
|
firmware_dst = g_object_new(gtype_dst, NULL);
|
|
images = fu_firmware_get_images(firmware_src);
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *img = g_ptr_array_index(images, i);
|
|
fu_firmware_add_image(firmware_dst, img);
|
|
}
|
|
|
|
/* write new file */
|
|
blob_dst = fu_firmware_write(firmware_dst, error);
|
|
if (blob_dst == NULL)
|
|
return FALSE;
|
|
if (!fu_bytes_set_contents(values[1], blob_dst, error))
|
|
return FALSE;
|
|
str_dst = fu_firmware_to_string(firmware_dst);
|
|
g_print("%s", str_dst);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_util_hex_string_to_bytes(const gchar *val, GError **error)
|
|
{
|
|
gsize valsz;
|
|
g_autoptr(GByteArray) buf = g_byte_array_new();
|
|
|
|
/* sanity check */
|
|
if (val == NULL) {
|
|
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "nothing to parse");
|
|
return NULL;
|
|
}
|
|
|
|
/* parse each hex byte */
|
|
valsz = strlen(val);
|
|
for (guint i = 0; i < valsz; i += 2) {
|
|
guint8 tmp = 0;
|
|
if (!fu_firmware_strparse_uint8_safe(val, valsz, i, &tmp, error))
|
|
return NULL;
|
|
fu_byte_array_append_uint8(buf, tmp);
|
|
}
|
|
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_firmware_patch(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
FuContext *ctx = fu_engine_get_context(priv->engine);
|
|
GType gtype;
|
|
g_autofree gchar *firmware_type = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autoptr(GBytes) blob_dst = NULL;
|
|
g_autoptr(GBytes) blob_src = NULL;
|
|
g_autoptr(GBytes) patch = NULL;
|
|
guint64 offset = 0;
|
|
|
|
/* check args */
|
|
if (g_strv_length(values) != 3 && g_strv_length(values) != 4) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
"Invalid arguments, expected %s",
|
|
"FILENAME OFFSET DATA [FIRMWARE-TYPE]");
|
|
return FALSE;
|
|
}
|
|
|
|
/* hardcoded */
|
|
if (g_strv_length(values) == 4)
|
|
firmware_type = g_strdup(values[3]);
|
|
|
|
/* load file */
|
|
blob_src = fu_bytes_get_contents(values[0], error);
|
|
if (blob_src == NULL)
|
|
return FALSE;
|
|
|
|
/* parse offset */
|
|
if (!fu_strtoull(values[1], &offset, 0x0, G_MAXUINT32, error)) {
|
|
g_prefix_error(error, "failed to parse offset: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse blob */
|
|
patch = fu_util_hex_string_to_bytes(values[2], error);
|
|
if (patch == NULL)
|
|
return FALSE;
|
|
if (g_bytes_get_size(patch) == 0) {
|
|
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "no data provided");
|
|
return FALSE;
|
|
}
|
|
|
|
/* load engine */
|
|
if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* find the GType to use */
|
|
if (firmware_type == NULL)
|
|
firmware_type = fu_util_prompt_for_firmware_type(priv, error);
|
|
if (firmware_type == NULL)
|
|
return FALSE;
|
|
gtype = fu_context_get_firmware_gtype_by_id(ctx, firmware_type);
|
|
if (gtype == G_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"GType %s not supported",
|
|
firmware_type);
|
|
return FALSE;
|
|
}
|
|
firmware = g_object_new(gtype, NULL);
|
|
if (!fu_firmware_parse(firmware, blob_src, priv->flags, error))
|
|
return FALSE;
|
|
|
|
/* add patch */
|
|
fu_firmware_add_patch(firmware, offset, patch);
|
|
|
|
/* write new file */
|
|
blob_dst = fu_firmware_write(firmware, error);
|
|
if (blob_dst == NULL)
|
|
return FALSE;
|
|
if (!fu_bytes_set_contents(values[0], blob_dst, error))
|
|
return FALSE;
|
|
str = fu_firmware_to_string(firmware);
|
|
g_print("%s", str);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_verify_update(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autofree gchar *str = NULL;
|
|
g_autoptr(FuDevice) dev = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(priv->progress, G_STRLOC);
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 50, "start-engine");
|
|
fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_VERIFY, 50, "verify-update");
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* get device */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_UPDATABLE;
|
|
if (g_strv_length(values) == 1) {
|
|
dev = fu_util_get_device(priv, values[0], error);
|
|
if (dev == NULL)
|
|
return FALSE;
|
|
} else {
|
|
dev = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (dev == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* add checksums */
|
|
if (!fu_engine_verify_update(priv->engine,
|
|
fu_device_get_id(dev),
|
|
fu_progress_get_child(priv->progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(priv->progress);
|
|
|
|
/* show checksums */
|
|
str = fu_device_to_string(dev);
|
|
g_print("%s\n", str);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_history(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GNode) root = g_node_new(NULL);
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* get all devices from the history database */
|
|
devices = fu_engine_get_history(priv->engine, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
|
|
/* show each device */
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
g_autoptr(GPtrArray) rels = NULL;
|
|
FwupdDevice *dev = g_ptr_array_index(devices, i);
|
|
FwupdRelease *rel;
|
|
const gchar *remote;
|
|
GNode *child;
|
|
|
|
if (!fu_util_filter_device(priv, dev))
|
|
continue;
|
|
child = g_node_append_data(root, dev);
|
|
|
|
rel = fwupd_device_get_release_default(dev);
|
|
if (rel == NULL)
|
|
continue;
|
|
remote = fwupd_release_get_remote_id(rel);
|
|
|
|
/* doesn't actually map to remote */
|
|
if (remote == NULL) {
|
|
g_node_append_data(child, rel);
|
|
continue;
|
|
}
|
|
|
|
/* try to lookup releases from client */
|
|
rels = fu_engine_get_releases(priv->engine,
|
|
priv->request,
|
|
fwupd_device_get_id(dev),
|
|
error);
|
|
if (rels == NULL)
|
|
return FALSE;
|
|
|
|
/* map to a release in client */
|
|
for (guint j = 0; j < rels->len; j++) {
|
|
FwupdRelease *rel2 = g_ptr_array_index(rels, j);
|
|
if (g_strcmp0(remote, fwupd_release_get_remote_id(rel2)) != 0)
|
|
continue;
|
|
if (g_strcmp0(fwupd_release_get_version(rel),
|
|
fwupd_release_get_version(rel2)) != 0)
|
|
continue;
|
|
g_node_append_data(child, g_object_ref(rel2));
|
|
rel = NULL;
|
|
break;
|
|
}
|
|
|
|
/* didn't match anything */
|
|
if (rels->len == 0 || rel != NULL) {
|
|
g_node_append_data(child, rel);
|
|
continue;
|
|
}
|
|
}
|
|
fu_util_print_tree(priv->client, root);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_refresh_remote(FuUtilPrivate *priv, FwupdRemote *remote, GError **error)
|
|
{
|
|
const gchar *metadata_uri = NULL;
|
|
g_autoptr(GBytes) bytes_raw = NULL;
|
|
g_autoptr(GBytes) bytes_sig = NULL;
|
|
|
|
/* signature */
|
|
metadata_uri = fwupd_remote_get_metadata_uri_sig(remote);
|
|
if (metadata_uri == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"no metadata signature URI available for %s",
|
|
fwupd_remote_get_id(remote));
|
|
return FALSE;
|
|
}
|
|
bytes_sig = fwupd_client_download_bytes(priv->client,
|
|
metadata_uri,
|
|
FWUPD_CLIENT_DOWNLOAD_FLAG_NONE,
|
|
priv->cancellable,
|
|
error);
|
|
if (bytes_sig == NULL)
|
|
return FALSE;
|
|
if (!fwupd_remote_load_signature_bytes(remote, bytes_sig, error))
|
|
return FALSE;
|
|
|
|
/* payload */
|
|
metadata_uri = fwupd_remote_get_metadata_uri(remote);
|
|
if (metadata_uri == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"no metadata URI available for %s",
|
|
fwupd_remote_get_id(remote));
|
|
return FALSE;
|
|
}
|
|
bytes_raw = fwupd_client_download_bytes(priv->client,
|
|
metadata_uri,
|
|
FWUPD_CLIENT_DOWNLOAD_FLAG_NONE,
|
|
priv->cancellable,
|
|
error);
|
|
if (bytes_raw == NULL)
|
|
return FALSE;
|
|
|
|
/* send to daemon */
|
|
g_debug("updating %s", fwupd_remote_get_id(remote));
|
|
return fu_engine_update_metadata_bytes(priv->engine,
|
|
fwupd_remote_get_id(remote),
|
|
bytes_raw,
|
|
bytes_sig,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_refresh(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) remotes = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* download new metadata */
|
|
remotes = fu_engine_get_remotes(priv->engine, error);
|
|
if (remotes == NULL)
|
|
return FALSE;
|
|
for (guint i = 0; i < remotes->len; i++) {
|
|
FwupdRemote *remote = g_ptr_array_index(remotes, i);
|
|
if (!fwupd_remote_get_enabled(remote))
|
|
continue;
|
|
if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD)
|
|
continue;
|
|
if (!fu_util_refresh_remote(priv, remote, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_remotes(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(GNode) root = g_node_new(NULL);
|
|
g_autoptr(GPtrArray) remotes = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* list remotes */
|
|
remotes = fu_engine_get_remotes(priv->engine, error);
|
|
if (remotes == NULL)
|
|
return FALSE;
|
|
if (remotes->len == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"no remotes available");
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; i < remotes->len; i++) {
|
|
FwupdRemote *remote_tmp = g_ptr_array_index(remotes, i);
|
|
g_node_append_data(root, remote_tmp);
|
|
}
|
|
fu_util_print_tree(priv->client, root);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE;
|
|
g_autoptr(FuSecurityAttrs) attrs = NULL;
|
|
g_autoptr(FuSecurityAttrs) events = NULL;
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GPtrArray) items = NULL;
|
|
g_autoptr(GPtrArray) events_array = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* show or hide different elements */
|
|
if (priv->show_all) {
|
|
flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES;
|
|
flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS;
|
|
}
|
|
|
|
attrs = fu_engine_get_host_security_attrs(priv->engine);
|
|
items = fu_security_attrs_get_all(attrs);
|
|
for (guint j = 0; j < items->len; j++) {
|
|
FwupdSecurityAttr *attr = g_ptr_array_index(items, j);
|
|
|
|
if (!fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA))
|
|
continue;
|
|
if (priv->flags & FWUPD_INSTALL_FLAG_FORCE)
|
|
continue;
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Not enough data was provided to make an HSI calculation. "
|
|
"To ignore this warning, use --force.");
|
|
return FALSE;
|
|
}
|
|
|
|
/* print the "why" */
|
|
if (priv->as_json) {
|
|
str = fu_security_attrs_to_json_string(attrs, error);
|
|
if (str == NULL)
|
|
return FALSE;
|
|
g_print("%s\n", str);
|
|
return TRUE;
|
|
}
|
|
|
|
g_print("%s \033[1m%s\033[0m\n",
|
|
/* TRANSLATORS: this is a string like 'HSI:2-U' */
|
|
_("Host Security ID:"),
|
|
fu_engine_get_host_security_id(priv->engine));
|
|
|
|
str = fu_util_security_attrs_to_string(items, flags);
|
|
g_print("%s\n", str);
|
|
|
|
/* print the "when" */
|
|
events = fu_engine_get_host_security_events(priv->engine, 10, error);
|
|
if (events == NULL)
|
|
return FALSE;
|
|
events_array = fu_security_attrs_get_all(attrs);
|
|
if (events_array->len > 0) {
|
|
g_autofree gchar *estr = fu_util_security_events_to_string(events_array, flags);
|
|
if (estr != NULL)
|
|
g_print("%s\n", estr);
|
|
}
|
|
|
|
/* print the "also" */
|
|
devices = fu_engine_get_devices(priv->engine, error);
|
|
if (devices == NULL)
|
|
return FALSE;
|
|
if (devices->len > 0) {
|
|
g_autofree gchar *estr = fu_util_security_issues_to_string(devices);
|
|
if (estr != NULL)
|
|
g_print("%s\n", estr);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static FuVolume *
|
|
fu_util_prompt_for_volume(GError **error)
|
|
{
|
|
FuVolume *volume;
|
|
guint idx;
|
|
gboolean is_fallback = FALSE;
|
|
g_autoptr(GPtrArray) volumes = NULL;
|
|
g_autoptr(GPtrArray) volumes_vfat = g_ptr_array_new();
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* exactly one */
|
|
volumes = fu_volume_new_by_kind(FU_VOLUME_KIND_ESP, &error_local);
|
|
if (volumes == NULL) {
|
|
is_fallback = TRUE;
|
|
g_debug("%s, falling back to %s", error_local->message, FU_VOLUME_KIND_BDP);
|
|
volumes = fu_volume_new_by_kind(FU_VOLUME_KIND_BDP, error);
|
|
if (volumes == NULL) {
|
|
g_prefix_error(error, "%s: ", error_local->message);
|
|
return NULL;
|
|
}
|
|
}
|
|
/* on fallback: only add internal vfat partitions */
|
|
for (guint i = 0; i < volumes->len; i++) {
|
|
FuVolume *vol = g_ptr_array_index(volumes, i);
|
|
g_autofree gchar *type = fu_volume_get_id_type(vol);
|
|
if (type == NULL)
|
|
continue;
|
|
if (is_fallback && !fu_volume_is_internal(vol))
|
|
continue;
|
|
if (g_strcmp0(type, "vfat") == 0)
|
|
g_ptr_array_add(volumes_vfat, vol);
|
|
}
|
|
if (volumes_vfat->len == 1) {
|
|
volume = g_ptr_array_index(volumes_vfat, 0);
|
|
/* TRANSLATORS: Volume has been chosen by the user */
|
|
g_print("%s: %s\n", _("Selected volume"), fu_volume_get_id(volume));
|
|
return g_object_ref(volume);
|
|
}
|
|
|
|
/* TRANSLATORS: get interactive prompt */
|
|
g_print("%s\n", _("Choose a volume:"));
|
|
/* TRANSLATORS: this is to abort the interactive prompt */
|
|
g_print("0.\t%s\n", _("Cancel"));
|
|
for (guint i = 0; i < volumes_vfat->len; i++) {
|
|
volume = g_ptr_array_index(volumes_vfat, i);
|
|
g_print("%u.\t%s\n", i + 1, fu_volume_get_id(volume));
|
|
}
|
|
idx = fu_util_prompt_for_number(volumes_vfat->len);
|
|
if (idx == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"Request canceled");
|
|
return NULL;
|
|
}
|
|
volume = g_ptr_array_index(volumes_vfat, idx - 1);
|
|
return g_object_ref(volume);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_esp_mount(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuVolume) volume = NULL;
|
|
volume = fu_util_prompt_for_volume(error);
|
|
if (volume == NULL)
|
|
return FALSE;
|
|
return fu_volume_mount(volume, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_esp_unmount(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuVolume) volume = NULL;
|
|
volume = fu_util_prompt_for_volume(error);
|
|
if (volume == NULL)
|
|
return FALSE;
|
|
return fu_volume_unmount(volume, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_esp_list(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autofree gchar *mount_point = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(FuVolume) volume = NULL;
|
|
g_autoptr(GPtrArray) files = NULL;
|
|
|
|
volume = fu_util_prompt_for_volume(error);
|
|
if (volume == NULL)
|
|
return FALSE;
|
|
locker = fu_volume_locker(volume, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
mount_point = fu_volume_get_mount_point(volume);
|
|
files = fu_path_get_files(mount_point, error);
|
|
if (files == NULL)
|
|
return FALSE;
|
|
for (guint i = 0; i < files->len; i++) {
|
|
const gchar *fn = g_ptr_array_index(files, i);
|
|
g_print("%s\n", fn);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_g_str_equal0(gconstpointer str1, gconstpointer str2)
|
|
{
|
|
return g_strcmp0(str1, str2) == 0;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_switch_branch(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
const gchar *branch;
|
|
g_autoptr(FwupdRelease) rel = NULL;
|
|
g_autoptr(GPtrArray) rels = NULL;
|
|
g_autoptr(GPtrArray) branches = g_ptr_array_new_with_free_func(g_free);
|
|
g_autoptr(FuDevice) dev = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO |
|
|
FU_ENGINE_LOAD_FLAG_REMOTES,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* find the device and check it has multiple branches */
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES;
|
|
priv->filter_include |= FWUPD_DEVICE_FLAG_UPDATABLE;
|
|
if (g_strv_length(values) == 1)
|
|
dev = fu_util_get_device(priv, values[1], error);
|
|
else
|
|
dev = fu_util_prompt_for_device(priv, NULL, error);
|
|
if (dev == NULL)
|
|
return FALSE;
|
|
if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Multiple branches not available");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get all releases, including the alternate branch versions */
|
|
rels = fu_engine_get_releases(priv->engine, priv->request, fu_device_get_id(dev), error);
|
|
if (rels == NULL)
|
|
return FALSE;
|
|
|
|
/* get all the unique branches */
|
|
for (guint i = 0; i < rels->len; i++) {
|
|
FwupdRelease *rel_tmp = g_ptr_array_index(rels, i);
|
|
const gchar *branch_tmp = fwupd_release_get_branch(rel_tmp);
|
|
#if GLIB_CHECK_VERSION(2, 54, 3)
|
|
if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL))
|
|
continue;
|
|
#endif
|
|
g_ptr_array_add(branches, g_strdup(branch_tmp));
|
|
}
|
|
|
|
/* branch name is optional */
|
|
if (g_strv_length(values) > 1) {
|
|
branch = values[1];
|
|
} else if (branches->len == 1) {
|
|
branch = g_ptr_array_index(branches, 0);
|
|
} else {
|
|
guint idx;
|
|
|
|
/* TRANSLATORS: get interactive prompt, where branch is the
|
|
* supplier of the firmware, e.g. "non-free" or "free" */
|
|
g_print("%s\n", _("Choose a branch:"));
|
|
/* TRANSLATORS: this is to abort the interactive prompt */
|
|
g_print("0.\t%s\n", _("Cancel"));
|
|
for (guint i = 0; i < branches->len; i++) {
|
|
const gchar *branch_tmp = g_ptr_array_index(branches, i);
|
|
g_print("%u.\t%s\n", i + 1, fu_util_branch_for_display(branch_tmp));
|
|
}
|
|
idx = fu_util_prompt_for_number(branches->len);
|
|
if (idx == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"Request canceled");
|
|
return FALSE;
|
|
}
|
|
branch = g_ptr_array_index(branches, idx - 1);
|
|
}
|
|
|
|
/* sanity check */
|
|
if (g_strcmp0(branch, fu_device_get_branch(dev)) == 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Device %s is already on branch %s",
|
|
fu_device_get_name(dev),
|
|
fu_util_branch_for_display(branch));
|
|
return FALSE;
|
|
}
|
|
|
|
/* the releases are ordered by version */
|
|
for (guint j = 0; j < rels->len; j++) {
|
|
FwupdRelease *rel_tmp = g_ptr_array_index(rels, j);
|
|
if (g_strcmp0(fwupd_release_get_branch(rel_tmp), branch) == 0) {
|
|
rel = g_object_ref(rel_tmp);
|
|
break;
|
|
}
|
|
}
|
|
if (rel == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"No releases for branch %s",
|
|
fu_util_branch_for_display(branch));
|
|
return FALSE;
|
|
}
|
|
|
|
/* we're switching branch */
|
|
if (!fu_util_switch_branch_warning(FWUPD_DEVICE(dev), rel, FALSE, error))
|
|
return FALSE;
|
|
|
|
/* update the console if composite devices are also updated */
|
|
priv->current_operation = FU_UTIL_OPERATION_INSTALL;
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-changed",
|
|
G_CALLBACK(fu_util_update_device_changed_cb),
|
|
priv);
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH;
|
|
if (!fu_util_install_release(priv, rel, error))
|
|
return FALSE;
|
|
fu_util_display_current_message(priv);
|
|
|
|
/* we don't want to ask anything */
|
|
if (priv->no_reboot_check) {
|
|
g_debug("skipping reboot check");
|
|
return TRUE;
|
|
}
|
|
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_set_bios_setting(FuUtilPrivate *priv, gchar **input, GError **error)
|
|
{
|
|
g_autoptr(GHashTable) settings = fu_util_bios_settings_parse_argv(input, error);
|
|
|
|
if (settings == NULL)
|
|
return FALSE;
|
|
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_COLDPLUG,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (!fu_engine_modify_bios_settings(priv->engine, settings, FALSE, error)) {
|
|
if (!g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO))
|
|
g_prefix_error(error, "failed to set BIOS setting: ");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!priv->as_json) {
|
|
gpointer key, value;
|
|
GHashTableIter iter;
|
|
|
|
g_hash_table_iter_init(&iter, settings);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
g_autofree gchar *msg =
|
|
/* TRANSLATORS: Configured a BIOS setting to a value */
|
|
g_strdup_printf(_("Set BIOS setting '%s' using '%s'."),
|
|
(const gchar *)key,
|
|
(const gchar *)value);
|
|
g_print("\n%s\n", msg);
|
|
}
|
|
}
|
|
priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT;
|
|
|
|
if (priv->no_reboot_check) {
|
|
g_debug("skipping reboot check");
|
|
return TRUE;
|
|
}
|
|
|
|
return fu_util_prompt_complete(priv->completion_flags, TRUE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_get_bios_setting(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuBiosSettings) attrs = NULL;
|
|
g_autoptr(GPtrArray) items = NULL;
|
|
FuContext *ctx = fu_engine_get_context(priv->engine);
|
|
gboolean found = FALSE;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv,
|
|
FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_COLDPLUG,
|
|
priv->progress,
|
|
error))
|
|
return FALSE;
|
|
|
|
attrs = fu_context_get_bios_settings(ctx);
|
|
items = fu_bios_settings_get_all(attrs);
|
|
if (priv->as_json)
|
|
return fu_util_get_bios_setting_as_json(values, items, error);
|
|
|
|
for (guint i = 0; i < items->len; i++) {
|
|
FwupdBiosSetting *attr = g_ptr_array_index(items, i);
|
|
if (fu_util_bios_setting_matches_args(attr, values)) {
|
|
g_autofree gchar *tmp = fu_util_bios_setting_to_string(attr, 0);
|
|
g_print("%s\n", tmp);
|
|
found = TRUE;
|
|
}
|
|
}
|
|
if (items->len == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
/* TRANSLATORS: error message */
|
|
_("This system doesn't support firmware settings"));
|
|
return FALSE;
|
|
}
|
|
if (!found) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_ARGS,
|
|
/* TRANSLATORS: error message */
|
|
"Unable to find attribute '%s'",
|
|
values[0]);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_version(FuUtilPrivate *priv, GError **error)
|
|
{
|
|
g_autoptr(GHashTable) metadata = NULL;
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* load engine */
|
|
if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error))
|
|
return FALSE;
|
|
|
|
/* get metadata */
|
|
metadata = fu_engine_get_report_metadata(priv->engine, error);
|
|
if (metadata == NULL)
|
|
return FALSE;
|
|
|
|
/* dump to the screen in the most appropriate format */
|
|
if (priv->as_json)
|
|
return fu_util_project_versions_as_json(metadata, error);
|
|
str = fu_util_project_versions_to_string(metadata);
|
|
g_print("%s", str);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_clear_history(FuUtilPrivate *priv, gchar **values, GError **error)
|
|
{
|
|
g_autoptr(FuHistory) history = fu_history_new();
|
|
return fu_history_remove_all(history, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_util_setup_interactive(FuUtilPrivate *priv, GError **error)
|
|
{
|
|
if (priv->as_json) {
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "using --json");
|
|
return FALSE;
|
|
}
|
|
return fu_util_setup_interactive_console(error);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
gboolean allow_branch_switch = FALSE;
|
|
gboolean allow_older = FALSE;
|
|
gboolean allow_reinstall = FALSE;
|
|
gboolean force = FALSE;
|
|
gboolean ret;
|
|
gboolean version = FALSE;
|
|
gboolean ignore_checksum = FALSE;
|
|
gboolean ignore_vid_pid = FALSE;
|
|
g_auto(GStrv) plugin_glob = NULL;
|
|
g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1);
|
|
g_autoptr(GError) error_console = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new();
|
|
g_autofree gchar *cmd_descriptions = NULL;
|
|
g_autofree gchar *filter = NULL;
|
|
const GOptionEntry options[] = {
|
|
{"version",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&version,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Show client and daemon versions"),
|
|
NULL},
|
|
{"allow-reinstall",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&allow_reinstall,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Allow reinstalling existing firmware versions"),
|
|
NULL},
|
|
{"allow-older",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&allow_older,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Allow downgrading firmware versions"),
|
|
NULL},
|
|
{"allow-branch-switch",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&allow_branch_switch,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Allow switching firmware branch"),
|
|
NULL},
|
|
{"force",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&force,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Force the action by relaxing some runtime checks"),
|
|
NULL},
|
|
{"ignore-checksum",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&ignore_checksum,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Ignore firmware checksum failures"),
|
|
NULL},
|
|
{"ignore-vid-pid",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&ignore_vid_pid,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Ignore firmware hardware mismatch failures"),
|
|
NULL},
|
|
{"no-reboot-check",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->no_reboot_check,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Do not check or prompt for reboot after update"),
|
|
NULL},
|
|
{"no-safety-check",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->no_safety_check,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Do not perform device safety checks"),
|
|
NULL},
|
|
{"no-device-prompt",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->no_device_prompt,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Do not prompt for devices"),
|
|
NULL},
|
|
{"show-all",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->show_all,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Show all results"),
|
|
NULL},
|
|
{"show-all-devices",
|
|
'\0',
|
|
G_OPTION_FLAG_HIDDEN,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->show_all,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Show devices that are not updatable"),
|
|
NULL},
|
|
{"plugins",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_STRING_ARRAY,
|
|
&plugin_glob,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Manually enable specific plugins"),
|
|
NULL},
|
|
{"plugin-whitelist",
|
|
'\0',
|
|
G_OPTION_FLAG_HIDDEN,
|
|
G_OPTION_ARG_STRING_ARRAY,
|
|
&plugin_glob,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Manually enable specific plugins"),
|
|
NULL},
|
|
{"prepare",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->prepare_blob,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Run the plugin composite prepare routine when using install-blob"),
|
|
NULL},
|
|
{"cleanup",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->cleanup_blob,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Run the plugin composite cleanup routine when using install-blob"),
|
|
NULL},
|
|
{"disable-ssl-strict",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->disable_ssl_strict,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Ignore SSL strict checks when downloading files"),
|
|
NULL},
|
|
{"filter",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_STRING,
|
|
&filter,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Filter with a set of device flags using a ~ prefix to "
|
|
"exclude, e.g. 'internal,~needs-reboot'"),
|
|
NULL},
|
|
{"json",
|
|
'\0',
|
|
0,
|
|
G_OPTION_ARG_NONE,
|
|
&priv->as_json,
|
|
/* TRANSLATORS: command line option */
|
|
N_("Output in JSON format"),
|
|
NULL},
|
|
{NULL}};
|
|
|
|
#ifdef _WIN32
|
|
/* workaround Windows setting the codepage to 1252 */
|
|
(void)g_setenv("LANG", "C.UTF-8", FALSE);
|
|
#endif
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR);
|
|
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
|
|
textdomain(GETTEXT_PACKAGE);
|
|
|
|
/* create helper object */
|
|
priv->main_ctx = g_main_context_new();
|
|
priv->loop = g_main_loop_new(priv->main_ctx, FALSE);
|
|
priv->progressbar = fu_progressbar_new();
|
|
priv->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
fu_progressbar_set_main_context(priv->progressbar, priv->main_ctx);
|
|
priv->request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE);
|
|
|
|
/* used for monitoring and downloading */
|
|
priv->client = fwupd_client_new();
|
|
fwupd_client_set_main_context(priv->client, priv->main_ctx);
|
|
fwupd_client_set_user_agent_for_package(priv->client, "fwupdtool", PACKAGE_VERSION);
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"notify::percentage",
|
|
G_CALLBACK(fu_util_client_notify_cb),
|
|
priv);
|
|
g_signal_connect(FWUPD_CLIENT(priv->client),
|
|
"notify::status",
|
|
G_CALLBACK(fu_util_client_notify_cb),
|
|
priv);
|
|
|
|
/* when not using the engine */
|
|
priv->progress = fu_progress_new(G_STRLOC);
|
|
g_signal_connect(priv->progress,
|
|
"percentage-changed",
|
|
G_CALLBACK(fu_util_progress_percentage_changed_cb),
|
|
priv);
|
|
g_signal_connect(priv->progress,
|
|
"status-changed",
|
|
G_CALLBACK(fu_util_progress_status_changed_cb),
|
|
priv);
|
|
|
|
/* add commands */
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"smbios-dump",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILE"),
|
|
/* TRANSLATORS: command description */
|
|
_("Dump SMBIOS data from a file"),
|
|
fu_util_smbios_dump);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-plugins",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Get all enabled plugins registered with the system"),
|
|
fu_util_get_plugins);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-details",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILE"),
|
|
/* TRANSLATORS: command description */
|
|
_("Gets details about a firmware file"),
|
|
fu_util_get_details);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-history",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Show history of firmware updates"),
|
|
fu_util_get_history);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-updates,get-upgrades",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Gets the list of updates for connected hardware"),
|
|
fu_util_get_updates);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-devices,get-topology",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Get all devices that support firmware updates"),
|
|
fu_util_get_devices);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-device-flags",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Get all device flags supported by fwupd"),
|
|
fu_util_get_device_flags);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"watch",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Watch for hardware changes"),
|
|
fu_util_watch);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"install-blob",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME DEVICE-ID"),
|
|
/* TRANSLATORS: command description */
|
|
_("Install a firmware blob on a device"),
|
|
fu_util_install_blob);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"install",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILE [DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Install a specific firmware on a device, all possible devices"
|
|
" will also be installed once the CAB matches"),
|
|
fu_util_install);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"reinstall",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("DEVICE-ID|GUID"),
|
|
/* TRANSLATORS: command description */
|
|
_("Reinstall firmware on a device"),
|
|
fu_util_reinstall);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"attach",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("DEVICE-ID|GUID"),
|
|
/* TRANSLATORS: command description */
|
|
_("Attach to firmware mode"),
|
|
fu_util_attach);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"detach",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("DEVICE-ID|GUID"),
|
|
/* TRANSLATORS: command description */
|
|
_("Detach to bootloader mode"),
|
|
fu_util_detach);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"unbind-driver",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Unbind current driver"),
|
|
fu_util_unbind_driver);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"bind-driver",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("SUBSYSTEM DRIVER [DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Bind new kernel driver"),
|
|
fu_util_bind_driver);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"activate",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Activate pending devices"),
|
|
fu_util_activate);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"hwids",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[SMBIOS-FILE|HWIDS-FILE]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Return all the hardware IDs for the machine"),
|
|
fu_util_hwids);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"export-hwids",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("HWIDS-FILE"),
|
|
/* TRANSLATORS: command description */
|
|
_("Save a file that allows generation of hardware IDs"),
|
|
fu_util_export_hwids);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"monitor",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Monitor the daemon for events"),
|
|
fu_util_monitor);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"update,upgrade",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Updates all specified devices to latest firmware version, or all "
|
|
"devices if unspecified"),
|
|
fu_util_update);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"self-sign",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("TEXT"),
|
|
/* TRANSLATORS: command description */
|
|
C_("command-description", "Sign data using the client certificate"),
|
|
fu_util_self_sign);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"verify-update",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Update the stored metadata with current contents"),
|
|
fu_util_verify_update);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-sign",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME CERTIFICATE PRIVATE-KEY"),
|
|
/* TRANSLATORS: command description */
|
|
_("Sign a firmware with a new key"),
|
|
fu_util_firmware_sign);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-dump",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME [DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Read a firmware blob from a device"),
|
|
fu_util_firmware_dump);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-read",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME [DEVICE-ID|GUID]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Read a firmware from a device"),
|
|
fu_util_firmware_read);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-patch",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME OFFSET DATA [FIRMWARE-TYPE]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Patch a firmware blob at a known offset"),
|
|
fu_util_firmware_patch);
|
|
fu_util_cmd_array_add(
|
|
cmd_array,
|
|
"firmware-convert",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Convert a firmware file"),
|
|
fu_util_firmware_convert);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-build",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("BUILDER-XML FILENAME-DST"),
|
|
/* TRANSLATORS: command description */
|
|
_("Build a firmware file"),
|
|
fu_util_firmware_build);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-parse",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME [FIRMWARE-TYPE]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Parse and show details about a firmware file"),
|
|
fu_util_firmware_parse);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-export",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME [FIRMWARE-TYPE]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Export a firmware file structure to XML"),
|
|
fu_util_firmware_export);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"firmware-extract",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("FILENAME [FIRMWARE-TYPE]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Extract a firmware blob to images"),
|
|
fu_util_firmware_extract);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-firmware-types",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("List the available firmware types"),
|
|
fu_util_get_firmware_types);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"get-remotes",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Gets the configured remotes"),
|
|
fu_util_get_remotes);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"refresh",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Refresh metadata from remote server"),
|
|
fu_util_refresh);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"security",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Gets the host security attributes"),
|
|
fu_util_security);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"esp-mount",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Mounts the ESP"),
|
|
fu_util_esp_mount);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"esp-unmount",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Unmounts the ESP"),
|
|
fu_util_esp_unmount);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"esp-list",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Lists files on the ESP"),
|
|
fu_util_esp_list);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"switch-branch",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[DEVICE-ID|GUID] [BRANCH]"),
|
|
/* TRANSLATORS: command description */
|
|
_("Switch the firmware branch on the device"),
|
|
fu_util_switch_branch);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"clear-history",
|
|
NULL,
|
|
/* TRANSLATORS: command description */
|
|
_("Erase all firmware update history"),
|
|
fu_util_clear_history);
|
|
fu_util_cmd_array_add(
|
|
cmd_array,
|
|
"get-bios-settings,get-bios-setting",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("[SETTING1] [ SETTING2]..."),
|
|
/* TRANSLATORS: command description */
|
|
_("Retrieve BIOS settings. If no arguments are passed all settings are returned"),
|
|
fu_util_get_bios_setting);
|
|
fu_util_cmd_array_add(cmd_array,
|
|
"set-bios-setting",
|
|
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
|
|
_("SETTING VALUE"),
|
|
/* TRANSLATORS: command description */
|
|
_("Set a BIOS setting"),
|
|
fu_util_set_bios_setting);
|
|
|
|
/* do stuff on ctrl+c */
|
|
priv->cancellable = g_cancellable_new();
|
|
fu_util_setup_signal_handlers(priv);
|
|
g_signal_connect(G_CANCELLABLE(priv->cancellable),
|
|
"cancelled",
|
|
G_CALLBACK(fu_util_cancelled_cb),
|
|
priv);
|
|
|
|
/* sort by command name */
|
|
fu_util_cmd_array_sort(cmd_array);
|
|
|
|
/* non-TTY consoles cannot answer questions */
|
|
if (!fu_util_setup_interactive(priv, &error_console)) {
|
|
g_debug("failed to initialize interactive console: %s", error_console->message);
|
|
priv->no_reboot_check = TRUE;
|
|
priv->no_safety_check = TRUE;
|
|
priv->no_device_prompt = TRUE;
|
|
} else {
|
|
priv->interactive = TRUE;
|
|
/* set our implemented feature set */
|
|
fu_engine_request_set_feature_flags(
|
|
priv->request,
|
|
FWUPD_FEATURE_FLAG_DETACH_ACTION | FWUPD_FEATURE_FLAG_SWITCH_BRANCH |
|
|
FWUPD_FEATURE_FLAG_FDE_WARNING | FWUPD_FEATURE_FLAG_UPDATE_ACTION |
|
|
FWUPD_FEATURE_FLAG_COMMUNITY_TEXT | FWUPD_FEATURE_FLAG_SHOW_PROBLEMS |
|
|
FWUPD_FEATURE_FLAG_REQUESTS);
|
|
}
|
|
fu_progressbar_set_interactive(priv->progressbar, priv->interactive);
|
|
|
|
/* get a list of the commands */
|
|
priv->context = g_option_context_new(NULL);
|
|
cmd_descriptions = fu_util_cmd_array_to_string(cmd_array);
|
|
g_option_context_set_summary(priv->context, cmd_descriptions);
|
|
g_option_context_set_description(
|
|
priv->context,
|
|
/* TRANSLATORS: CLI description */
|
|
_("This tool allows an administrator to use the fwupd plugins "
|
|
"without being installed on the host system."));
|
|
|
|
/* TRANSLATORS: program name */
|
|
g_set_application_name(_("Firmware Utility"));
|
|
g_option_context_add_main_entries(priv->context, options, NULL);
|
|
g_option_context_add_group(priv->context, fu_debug_get_option_group());
|
|
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;
|
|
}
|
|
fu_progress_set_profile(priv->progress, g_getenv("FWUPD_VERBOSE") != NULL);
|
|
|
|
/* allow disabling SSL strict mode for broken corporate proxies */
|
|
if (priv->disable_ssl_strict) {
|
|
g_autofree gchar *fmt = NULL;
|
|
/* TRANSLATORS: this is a prefix on the console */
|
|
fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED);
|
|
g_printerr("%s %s\n",
|
|
fmt,
|
|
/* TRANSLATORS: try to help */
|
|
_("Ignoring SSL strict checks, "
|
|
"to do this automatically in the future "
|
|
"export DISABLE_SSL_STRICT in your environment"));
|
|
(void)g_setenv("DISABLE_SSL_STRICT", "1", TRUE);
|
|
}
|
|
|
|
/* parse filter flags */
|
|
if (filter != NULL) {
|
|
if (!fu_util_parse_filter_flags(filter,
|
|
&priv->filter_include,
|
|
&priv->filter_exclude,
|
|
&error)) {
|
|
g_print("%s: %s\n",
|
|
/* TRANSLATORS: the user didn't read the man page */
|
|
_("Failed to parse flags for --filter"),
|
|
error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* set flags */
|
|
if (allow_reinstall)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
|
|
if (allow_older)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER;
|
|
if (allow_branch_switch)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH;
|
|
if (force)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_FORCE;
|
|
else
|
|
priv->flags |= FWUPD_INSTALL_FLAG_NO_SEARCH;
|
|
if (ignore_checksum)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM;
|
|
if (ignore_vid_pid)
|
|
priv->flags |= FWUPD_INSTALL_FLAG_IGNORE_VID_PID;
|
|
|
|
/* load engine */
|
|
priv->engine = fu_engine_new();
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-request",
|
|
G_CALLBACK(fu_util_update_device_request_cb),
|
|
priv);
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-added",
|
|
G_CALLBACK(fu_main_engine_device_added_cb),
|
|
priv);
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"device-removed",
|
|
G_CALLBACK(fu_main_engine_device_removed_cb),
|
|
priv);
|
|
g_signal_connect(FU_ENGINE(priv->engine),
|
|
"status-changed",
|
|
G_CALLBACK(fu_main_engine_status_changed_cb),
|
|
priv);
|
|
|
|
/* just show versions and exit */
|
|
if (version) {
|
|
if (!fu_util_version(priv, &error)) {
|
|
g_printerr("%s\n", error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/* any plugin allowlist specified */
|
|
for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++)
|
|
fu_engine_add_plugin_filter(priv->engine, plugin_glob[i]);
|
|
|
|
/* run the specified command */
|
|
ret = fu_util_cmd_array_run(cmd_array, priv, argv[1], (gchar **)&argv[2], &error);
|
|
if (!ret) {
|
|
#ifdef SUPPORTED_BUILD
|
|
/* sanity check */
|
|
if (error == NULL) {
|
|
g_critical("exec failed but no error set!");
|
|
return EXIT_FAILURE;
|
|
}
|
|
#endif
|
|
g_printerr("%s\n", error->message);
|
|
if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) {
|
|
/* TRANSLATORS: error message explaining command on how to get help */
|
|
g_printerr("\n%s\n", _("Use fwupdtool --help for help"));
|
|
} else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
|
|
g_debug("%s\n", error->message);
|
|
return EXIT_NOTHING_TO_DO;
|
|
}
|
|
#ifdef HAVE_GETUID
|
|
/* if not root, then notify users on the error path */
|
|
if (priv->interactive && (getuid() != 0 || geteuid() != 0)) {
|
|
/* TRANSLATORS: we're poking around as a power user */
|
|
g_printerr("%s\n", _("NOTE: This program may only work correctly as root"));
|
|
}
|
|
#endif
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* a good place to do the traceback */
|
|
if (fu_progress_get_profile(priv->progress)) {
|
|
g_autofree gchar *str = fu_progress_traceback(priv->progress);
|
|
if (str != NULL)
|
|
g_print("\n%s\n", str);
|
|
}
|
|
|
|
/* success */
|
|
return EXIT_SUCCESS;
|
|
}
|