| /* |
| * Copyright 2015 Richard Hughes <richard@hughsie.com> |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #define G_LOG_DOMAIN "FuMain" |
| |
| #include "config.h" |
| |
| #include <fwupdplugin.h> |
| |
| #include <fcntl.h> |
| #include <glib/gi18n.h> |
| #include <glib/gstdio.h> |
| #ifdef HAVE_GIO_UNIX |
| #include <gio/gunixfdlist.h> |
| #include <glib-unix.h> |
| #endif |
| #include <locale.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include "fu-console.h" |
| #include "fu-polkit-agent.h" |
| #include "fu-util-bios-setting.h" |
| #include "fu-util-common.h" |
| |
| #ifdef HAVE_SYSTEMD |
| #include "fu-systemd.h" |
| #endif |
| |
| typedef enum { |
| FU_UTIL_OPERATION_UNKNOWN, |
| FU_UTIL_OPERATION_UPDATE, |
| FU_UTIL_OPERATION_DOWNGRADE, |
| FU_UTIL_OPERATION_INSTALL, |
| FU_UTIL_OPERATION_LAST |
| } FuUtilOperation; |
| |
| struct FuUtil { |
| GCancellable *cancellable; |
| GMainContext *main_ctx; |
| GMainLoop *loop; |
| GOptionContext *context; |
| FwupdInstallFlags flags; |
| FwupdClientDownloadFlags download_flags; |
| FwupdClient *client; |
| FuConsole *console; |
| gboolean no_remote_check; |
| gboolean no_metadata_check; |
| gboolean no_reboot_check; |
| gboolean no_unreported_check; |
| gboolean no_safety_check; |
| gboolean no_device_prompt; |
| gboolean no_emulation_check; |
| gboolean no_security_fix; |
| gboolean assume_yes; |
| gboolean sign; |
| gboolean show_all; |
| gboolean disable_ssl_strict; |
| gboolean as_json; |
| /* only valid in update and downgrade */ |
| FuUtilOperation current_operation; |
| FwupdDevice *current_device; |
| GPtrArray *post_requests; |
| FwupdDeviceFlags completion_flags; |
| FwupdDeviceFlags filter_device_include; |
| FwupdDeviceFlags filter_device_exclude; |
| FwupdReleaseFlags filter_release_include; |
| FwupdReleaseFlags filter_release_exclude; |
| }; |
| |
| static gboolean |
| fu_util_report_history(FuUtil *self, gchar **values, GError **error); |
| static FwupdDevice * |
| fu_util_get_device_by_id(FuUtil *self, const gchar *id, GError **error); |
| |
| static void |
| fu_util_client_notify_cb(GObject *object, GParamSpec *pspec, FuUtil *self) |
| { |
| if (self->as_json) |
| return; |
| fu_console_set_progress(self->console, |
| fwupd_client_get_status(self->client), |
| fwupd_client_get_percentage(self->client)); |
| } |
| |
| static void |
| fu_util_update_device_request_cb(FwupdClient *client, FwupdRequest *request, FuUtil *self) |
| { |
| /* 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_console_color_format(_("Action Required:"), FU_CONSOLE_COLOR_RED); |
| tmp = g_strdup_printf("%s %s", fmt, fwupd_request_get_message(request)); |
| fu_console_set_progress_title(self->console, tmp); |
| fu_console_beep(self->console, 5); |
| } |
| |
| /* save for later */ |
| if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST) |
| g_ptr_array_add(self->post_requests, g_object_ref(request)); |
| } |
| |
| static void |
| fu_util_update_device_changed_cb(FwupdClient *client, FwupdDevice *device, FuUtil *self) |
| { |
| g_autofree gchar *str = NULL; |
| |
| /* action has not been assigned yet */ |
| if (self->current_operation == FU_UTIL_OPERATION_UNKNOWN) |
| return; |
| |
| /* allowed to set whenever the device has changed */ |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| |
| /* same as last time, so ignore */ |
| if (self->current_device == NULL || |
| g_strcmp0(fwupd_device_get_composite_id(self->current_device), |
| fwupd_device_get_composite_id(device)) == 0) { |
| g_set_object(&self->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 console */ |
| if (self->current_operation == FU_UTIL_OPERATION_UPDATE) { |
| /* TRANSLATORS: %1 is a device name */ |
| str = g_strdup_printf(_("Updating %s…"), fwupd_device_get_name(device)); |
| fu_console_set_progress_title(self->console, str); |
| } else if (self->current_operation == FU_UTIL_OPERATION_DOWNGRADE) { |
| /* TRANSLATORS: %1 is a device name */ |
| str = g_strdup_printf(_("Downgrading %s…"), fwupd_device_get_name(device)); |
| fu_console_set_progress_title(self->console, str); |
| } else if (self->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_console_set_progress_title(self->console, str); |
| } else { |
| g_warning("no FuUtilOperation set"); |
| } |
| g_set_object(&self->current_device, device); |
| } |
| |
| static FwupdDevice * |
| fu_util_prompt_for_device(FuUtil *self, GPtrArray *devices, GError **error) |
| { |
| FwupdDevice *dev; |
| guint idx; |
| g_autoptr(GPtrArray) devices_filtered = NULL; |
| |
| /* filter results */ |
| devices_filtered = fwupd_device_array_filter_flags(devices, |
| self->filter_device_include, |
| self->filter_device_exclude, |
| error); |
| if (devices_filtered == NULL) |
| return NULL; |
| |
| /* exactly one */ |
| if (devices_filtered->len == 1) { |
| dev = g_ptr_array_index(devices_filtered, 0); |
| if (!self->as_json) { |
| fu_console_print( |
| self->console, |
| "%s: %s", |
| /* TRANSLATORS: device has been chosen by the daemon for the user */ |
| _("Selected device"), |
| fwupd_device_get_name(dev)); |
| } |
| return g_object_ref(dev); |
| } |
| |
| /* no questions */ |
| if (self->no_device_prompt) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "can't prompt for devices"); |
| return NULL; |
| } |
| |
| /* TRANSLATORS: this is to abort the interactive prompt */ |
| fu_console_print(self->console, "0.\t%s", _("Cancel")); |
| for (guint i = 0; i < devices_filtered->len; i++) { |
| dev = g_ptr_array_index(devices_filtered, i); |
| fu_console_print(self->console, |
| "%u.\t%s (%s)", |
| i + 1, |
| fwupd_device_get_id(dev), |
| fwupd_device_get_name(dev)); |
| } |
| /* TRANSLATORS: get interactive prompt */ |
| idx = fu_console_input_uint(self->console, devices_filtered->len, "%s", _("Choose device")); |
| 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 gboolean |
| fu_util_perhaps_show_unreported(FuUtil *self, GError **error) |
| { |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_failed = g_ptr_array_new(); |
| g_autoptr(GPtrArray) devices_success = g_ptr_array_new(); |
| g_autoptr(GPtrArray) remotes = NULL; |
| g_autoptr(GHashTable) remote_id_uri_map = NULL; |
| gboolean all_automatic = FALSE; |
| |
| /* we don't want to ask anything */ |
| if (self->no_unreported_check || self->as_json) { |
| g_debug("skipping unreported check"); |
| return TRUE; |
| } |
| |
| /* get all devices from the history database */ |
| devices = fwupd_client_get_history(self->client, self->cancellable, &error_local); |
| if (devices == NULL) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) |
| return TRUE; |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| |
| /* create a map of RemoteID to RemoteURI */ |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, error); |
| if (remotes == NULL) |
| return FALSE; |
| remote_id_uri_map = g_hash_table_new(g_str_hash, g_str_equal); |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| gboolean remote_automatic; |
| if (fwupd_remote_get_id(remote) == NULL) |
| continue; |
| if (fwupd_remote_get_report_uri(remote) == NULL) |
| continue; |
| g_debug("adding %s for %s", |
| fwupd_remote_get_report_uri(remote), |
| fwupd_remote_get_id(remote)); |
| g_hash_table_insert(remote_id_uri_map, |
| (gpointer)fwupd_remote_get_id(remote), |
| (gpointer)fwupd_remote_get_report_uri(remote)); |
| remote_automatic = |
| fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); |
| g_debug("%s is %d", fwupd_remote_get_title(remote), remote_automatic); |
| if (remote_automatic && !all_automatic) |
| all_automatic = TRUE; |
| if (!remote_automatic && all_automatic) { |
| all_automatic = FALSE; |
| break; |
| } |
| } |
| |
| /* check that they can be reported */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| FwupdRelease *rel = fwupd_device_get_release_default(dev); |
| const gchar *remote_id; |
| const gchar *remote_uri; |
| |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) |
| continue; |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) |
| continue; |
| |
| /* find the RemoteURI to use for the device */ |
| remote_id = fwupd_release_get_remote_id(rel); |
| if (remote_id == NULL) { |
| g_debug("%s has no RemoteID", fwupd_device_get_id(dev)); |
| continue; |
| } |
| remote_uri = g_hash_table_lookup(remote_id_uri_map, remote_id); |
| if (remote_uri == NULL) { |
| g_debug("%s has no RemoteURI", remote_id); |
| continue; |
| } |
| |
| /* only send success and failure */ |
| if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) { |
| g_ptr_array_add(devices_failed, dev); |
| } else if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS) { |
| g_ptr_array_add(devices_success, dev); |
| } else { |
| g_debug("ignoring %s with UpdateState %s", |
| fwupd_device_get_id(dev), |
| fwupd_update_state_to_string(fwupd_device_get_update_state(dev))); |
| } |
| } |
| |
| /* nothing to do */ |
| if (devices_failed->len == 0 && devices_success->len == 0) { |
| g_debug("no unreported devices"); |
| return TRUE; |
| } |
| |
| g_debug("all automatic: %d", all_automatic); |
| /* show the success and failures */ |
| if (!self->assume_yes && !all_automatic) { |
| /* delimit */ |
| fu_console_line(self->console, 48); |
| |
| /* failures */ |
| if (devices_failed->len > 0) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: a list of failed updates */ |
| _("Devices that were not updated correctly:")); |
| for (guint i = 0; i < devices_failed->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices_failed, i); |
| FwupdRelease *rel = fwupd_device_get_release_default(dev); |
| fu_console_print(self->console, |
| " • %s (%s → %s)", |
| fwupd_device_get_name(dev), |
| fwupd_device_get_version(dev), |
| fwupd_release_get_version(rel)); |
| } |
| } |
| |
| /* success */ |
| if (devices_success->len > 0) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: a list of successful updates */ |
| _("Devices that have been updated successfully:")); |
| for (guint i = 0; i < devices_success->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices_success, i); |
| FwupdRelease *rel = fwupd_device_get_release_default(dev); |
| fu_console_print(self->console, |
| " • %s (%s → %s)", |
| fwupd_device_get_name(dev), |
| fwupd_device_get_version(dev), |
| fwupd_release_get_version(rel)); |
| } |
| } |
| |
| /* ask for permission */ |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: explain why we want to upload */ |
| _("Uploading firmware reports helps hardware vendors " |
| "to quickly identify failing and successful updates " |
| "on real devices.")); |
| if (!fu_console_input_bool(self->console, |
| TRUE, |
| "%s (%s)", |
| /* TRANSLATORS: ask the user to upload */ |
| _("Review and upload report now?"), |
| /* TRANSLATORS: metadata is downloaded */ |
| _("Requires internet connection"))) { |
| if (fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: offer to disable this nag */ |
| _("Do you want to disable this feature " |
| "for future updates?"))) { |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| const gchar *remote_id = fwupd_remote_get_id(remote); |
| if (fwupd_remote_get_report_uri(remote) == NULL) |
| continue; |
| if (!fwupd_client_modify_remote(self->client, |
| remote_id, |
| "ReportURI", |
| "", |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| } |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Declined upload"); |
| return FALSE; |
| } |
| } |
| |
| /* upload */ |
| if (!fu_util_report_history(self, NULL, error)) |
| return FALSE; |
| |
| /* offer to make automatic */ |
| if (!self->assume_yes && !all_automatic) { |
| if (fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: offer to stop asking the question */ |
| _("Do you want to upload reports automatically for " |
| "future updates?"))) { |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| const gchar *remote_id = fwupd_remote_get_id(remote); |
| if (fwupd_remote_get_report_uri(remote) == NULL) |
| continue; |
| if (fwupd_remote_has_flag(remote, |
| FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) |
| continue; |
| if (!fwupd_client_modify_remote(self->client, |
| remote_id, |
| "AutomaticReports", |
| "true", |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| } |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_util_build_device_tree(FuUtil *self, FuUtilNode *root, GPtrArray *devs, FwupdDevice *dev) |
| { |
| for (guint i = 0; i < devs->len; i++) { |
| FwupdDevice *dev_tmp = g_ptr_array_index(devs, i); |
| if (!fwupd_device_match_flags(dev_tmp, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| if (!self->show_all && !fu_util_is_interesting_device(devs, dev_tmp)) |
| continue; |
| if (fwupd_device_get_parent(dev_tmp) == dev) { |
| FuUtilNode *child = g_node_append_data(root, g_object_ref(dev_tmp)); |
| fu_util_build_device_tree(self, child, devs, dev_tmp); |
| } |
| } |
| } |
| |
| static gboolean |
| fu_util_get_releases_as_json(FuUtil *self, GPtrArray *rels, GError **error) |
| { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| json_builder_set_member_name(builder, "Releases"); |
| json_builder_begin_array(builder); |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| json_builder_begin_object(builder); |
| fwupd_codec_to_json(FWUPD_CODEC(rel), builder, FWUPD_CODEC_FLAG_NONE); |
| json_builder_end_object(builder); |
| } |
| json_builder_end_array(builder); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_get_devices_as_json(FuUtil *self, GPtrArray *devs, GError **error) |
| { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| json_builder_set_member_name(builder, "Devices"); |
| json_builder_begin_array(builder); |
| for (guint i = 0; i < devs->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devs, i); |
| g_autoptr(GPtrArray) rels = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| /* filter */ |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| |
| /* add all releases that could be applied */ |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| &error_local); |
| if (rels == NULL) { |
| g_debug("not adding releases to device: %s", error_local->message); |
| } else { |
| for (guint j = 0; j < rels->len; j++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, j); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| fwupd_device_add_release(dev, rel); |
| } |
| } |
| |
| /* add to builder */ |
| json_builder_begin_object(builder); |
| fwupd_codec_to_json(FWUPD_CODEC(dev), builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| } |
| json_builder_end_array(builder); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_check_reboot_needed(FuUtil *self, gchar **values, GError **error) |
| { |
| /* handle both forms */ |
| if (g_strv_length(values) == 0) { |
| g_autoptr(GPtrArray) devices = |
| fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; |
| } |
| } else { |
| for (guint i = 0; values[i] != NULL; i++) { |
| g_autoptr(FwupdDevice) device = NULL; |
| device = fu_util_get_device_by_id(self, values[i], error); |
| if (device == NULL) |
| return FALSE; |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; |
| } |
| } |
| |
| if (!(self->completion_flags & |
| (FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN | FWUPD_DEVICE_FLAG_NEEDS_REBOOT))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: no rebooting needed */ |
| _("No reboot is necessary")); |
| return FALSE; |
| } |
| |
| if (self->as_json) |
| return TRUE; |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_get_devices(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| g_autoptr(GPtrArray) devs = NULL; |
| |
| /* get results from daemon */ |
| if (g_strv_length(values) > 0) { |
| devs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; values[i] != NULL; i++) { |
| FwupdDevice *device = fu_util_get_device_by_id(self, values[i], error); |
| if (device == NULL) |
| return FALSE; |
| g_ptr_array_add(devs, device); |
| } |
| } else { |
| devs = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devs == NULL) |
| return FALSE; |
| } |
| |
| /* not for human consumption */ |
| if (self->as_json) |
| return fu_util_get_devices_as_json(self, devs, error); |
| |
| /* print */ |
| if (devs->len > 0) |
| fu_util_build_device_tree(self, root, devs, NULL); |
| if (g_node_n_children(root) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: nothing attached that can be upgraded */ |
| _("No hardware detected with firmware update capability")); |
| return FALSE; |
| } |
| fu_util_print_node(self->console, self->client, root); |
| |
| /* nag? */ |
| if (!fu_util_perhaps_show_unreported(self, error)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_get_plugins(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) plugins = NULL; |
| |
| /* get results from daemon */ |
| plugins = fwupd_client_get_plugins(self->client, self->cancellable, error); |
| g_ptr_array_sort(plugins, (GCompareFunc)fu_util_plugin_name_sort_cb); |
| if (plugins == NULL) |
| return FALSE; |
| if (self->as_json) { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| fwupd_codec_array_to_json(plugins, "Plugins", builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, 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); |
| fu_console_print_literal(self->console, str); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gchar * |
| fu_util_download_if_required(FuUtil *self, const gchar *perhapsfn, GError **error) |
| { |
| g_autofree gchar *filename = NULL; |
| g_autoptr(GBytes) blob = 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 (g_file_test(filename, G_FILE_TEST_EXISTS)) |
| return g_steal_pointer(&filename); |
| if (!fu_path_mkdir_parent(filename, error)) |
| return NULL; |
| blob = fwupd_client_download_bytes(self->client, |
| perhapsfn, |
| self->download_flags, |
| self->cancellable, |
| error); |
| if (blob == NULL) |
| return NULL; |
| |
| /* save file to cache */ |
| if (!fu_bytes_set_contents(filename, blob, error)) |
| return NULL; |
| return g_steal_pointer(&filename); |
| } |
| |
| static void |
| fu_util_display_current_message(FuUtil *self) |
| { |
| if (self->as_json) |
| return; |
| |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully installed firmware")); |
| |
| /* print all POST requests */ |
| for (guint i = 0; i < self->post_requests->len; i++) { |
| FwupdRequest *request = g_ptr_array_index(self->post_requests, i); |
| fu_console_print_literal(self->console, fu_util_request_get_message(request)); |
| } |
| } |
| |
| typedef struct { |
| guint nr_success; |
| guint nr_missing; |
| guint nr_skipped; |
| JsonBuilder *builder; |
| const gchar *name; |
| gboolean use_emulation; |
| GHashTable *report_metadata; |
| } FuUtilDeviceTestHelper; |
| |
| static void |
| fu_util_device_test_helper_free(FuUtilDeviceTestHelper *helper) |
| { |
| if (helper->report_metadata != NULL) |
| g_hash_table_unref(helper->report_metadata); |
| if (helper->builder != NULL) |
| g_object_unref(helper->builder); |
| g_free(helper); |
| } |
| |
| static FuUtilDeviceTestHelper * |
| fu_util_device_test_helper_new(void) |
| { |
| FuUtilDeviceTestHelper *helper = g_new0(FuUtilDeviceTestHelper, 1); |
| helper->builder = json_builder_new(); |
| return helper; |
| } |
| |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilDeviceTestHelper, fu_util_device_test_helper_free) |
| |
| static GPtrArray * |
| fu_util_filter_devices(FuUtil *self, GPtrArray *devices, GError **error) |
| { |
| g_autoptr(GPtrArray) devices_filtered = |
| g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| g_ptr_array_add(devices_filtered, g_object_ref(dev)); |
| } |
| if (devices_filtered->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "failed to find any devices"); |
| return NULL; |
| } |
| return g_steal_pointer(&devices_filtered); |
| } |
| |
| static gboolean |
| fu_util_device_test_component(FuUtil *self, |
| FuUtilDeviceTestHelper *helper, |
| JsonObject *json_obj, |
| GError **error) |
| { |
| JsonArray *json_array; |
| const gchar *name = "component"; |
| const gchar *protocol = NULL; |
| g_autoptr(FwupdDevice) device = NULL; |
| |
| /* some elements are optional */ |
| if (json_object_has_member(json_obj, "name")) { |
| name = json_object_get_string_member(json_obj, "name"); |
| json_builder_set_member_name(helper->builder, "name"); |
| json_builder_add_string_value(helper->builder, name); |
| } |
| if (json_object_has_member(json_obj, "protocol")) { |
| protocol = json_object_get_string_member(json_obj, "protocol"); |
| json_builder_set_member_name(helper->builder, "protocol"); |
| json_builder_add_string_value(helper->builder, protocol); |
| } |
| |
| /* find the device with any of the matching GUIDs */ |
| if (!json_object_has_member(json_obj, "guids")) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "JSON invalid as has no 'guids'"); |
| return FALSE; |
| } |
| json_array = json_object_get_array_member(json_obj, "guids"); |
| json_builder_set_member_name(helper->builder, "guids"); |
| json_builder_begin_array(helper->builder); |
| for (guint i = 0; i < json_array_get_length(json_array); i++) { |
| JsonNode *json_node = json_array_get_element(json_array, i); |
| FwupdDevice *device_tmp; |
| const gchar *guid = json_node_get_string(json_node); |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_filtered = NULL; |
| |
| g_debug("looking for guid %s", guid); |
| devices = |
| fwupd_client_get_devices_by_guid(self->client, guid, self->cancellable, NULL); |
| if (devices == NULL) |
| continue; |
| devices_filtered = fu_util_filter_devices(self, devices, NULL); |
| if (devices_filtered == NULL) |
| continue; |
| if (devices_filtered->len > 1) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "multiple devices with GUID %s", |
| guid); |
| return FALSE; |
| } |
| device_tmp = g_ptr_array_index(devices_filtered, 0); |
| if (protocol != NULL && !fwupd_device_has_protocol(device_tmp, protocol)) |
| continue; |
| device = g_object_ref(device_tmp); |
| json_builder_add_string_value(helper->builder, guid); |
| break; |
| } |
| json_builder_end_array(helper->builder); |
| if (device == NULL) { |
| if (!self->as_json) { |
| g_autofree gchar *msg = NULL; |
| msg = fu_console_color_format( |
| /* TRANSLATORS: this is for the device tests */ |
| _("Did not find any devices with matching GUIDs"), |
| FU_CONSOLE_COLOR_RED); |
| fu_console_print(self->console, "%s: %s", name, msg); |
| } |
| g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no devices found"); |
| return FALSE; |
| } |
| |
| /* verify the version matches what we expected */ |
| if (json_object_has_member(json_obj, "version")) { |
| const gchar *version = json_object_get_string_member(json_obj, "version"); |
| if (g_strcmp0(version, fwupd_device_get_version(device)) != 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "version did not match: got %s, expected %s", |
| fwupd_device_get_version(device), |
| version); |
| return FALSE; |
| } |
| } |
| |
| /* verify the bootloader version matches what we expected */ |
| if (json_object_has_member(json_obj, "version-bootloader")) { |
| const gchar *version = |
| json_object_get_string_member(json_obj, "version-bootloader"); |
| if (g_strcmp0(version, fwupd_device_get_version_bootloader(device)) != 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "bootloader version did not match: got %s, expected %s", |
| fwupd_device_get_version_bootloader(device), |
| version); |
| return FALSE; |
| } |
| } |
| |
| /* verify the branch matches what we expected */ |
| if (json_object_has_member(json_obj, "branch")) { |
| const gchar *version = json_object_get_string_member(json_obj, "branch"); |
| if (g_strcmp0(version, fwupd_device_get_branch(device)) != 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "branch did not match: got %s, expected %s", |
| fwupd_device_get_branch(device), |
| version); |
| return FALSE; |
| } |
| } |
| |
| /* success */ |
| if (!self->as_json) { |
| g_autofree gchar *msg = NULL; |
| /* TRANSLATORS: this is for the device tests */ |
| msg = fu_console_color_format(_("OK!"), FU_CONSOLE_COLOR_GREEN); |
| if (g_strcmp0(name, "component") != 0) { |
| fu_console_print(self->console, "%s [%s]: %s", helper->name, name, msg); |
| } else { |
| fu_console_print(self->console, "%s: %s", helper->name, msg); |
| } |
| } |
| helper->nr_success++; |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_device_test_remove_emulated_devices(FuUtil *self, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| g_autoptr(GError) error_local = NULL; |
| if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) |
| continue; |
| if (!fwupd_client_modify_device(self->client, |
| fwupd_device_get_id(device), |
| "Flags", |
| "~emulated", |
| self->cancellable, |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
| g_debug("ignoring: %s", error_local->message); |
| continue; |
| } |
| g_propagate_prefixed_error(error, |
| g_steal_pointer(&error_local), |
| "failed to modify device: "); |
| return FALSE; |
| } |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gchar * |
| fu_util_maybe_expand_basename(FuUtil *self, const gchar *maybe_basename, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| |
| if (g_str_has_prefix(maybe_basename, "https://")) |
| return g_strdup(maybe_basename); |
| if (g_str_has_prefix(maybe_basename, "/")) |
| return g_strdup(maybe_basename); |
| |
| /* find LVFS remote */ |
| remote = fwupd_client_get_remote_by_id(self->client, "lvfs", self->cancellable, error); |
| if (remote == NULL) |
| return NULL; |
| if (fwupd_remote_get_firmware_base_uri(remote)) { |
| g_debug("no FirmwareBaseURI set in lvfs.conf, using default"); |
| return g_strdup_printf("https://fwupd.org/downloads/%s", maybe_basename); |
| } |
| return g_strdup_printf("%s/%s", fwupd_remote_get_firmware_base_uri(remote), maybe_basename); |
| } |
| |
| static gboolean |
| fu_util_device_test_step(FuUtil *self, |
| FuUtilDeviceTestHelper *helper, |
| JsonObject *json_obj, |
| GError **error) |
| { |
| JsonArray *json_array; |
| |
| /* send this data to the daemon */ |
| if (helper->use_emulation) { |
| g_autofree gchar *emulation_filename = NULL; |
| g_autofree gchar *emulation_url = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| /* just ignore anything without emulation data */ |
| if (json_object_has_member(json_obj, "emulation-url")) { |
| const gchar *url_tmp = |
| json_object_get_string_member(json_obj, "emulation-url"); |
| |
| emulation_url = fu_util_maybe_expand_basename(self, url_tmp, error); |
| if (emulation_url == NULL) |
| return FALSE; |
| emulation_filename = |
| fu_util_download_if_required(self, emulation_url, error); |
| if (emulation_filename == NULL) { |
| g_prefix_error(error, "failed to download %s: ", emulation_url); |
| return FALSE; |
| } |
| } else if (json_object_has_member(json_obj, "emulation-file")) { |
| emulation_filename = |
| g_strdup(json_object_get_string_member(json_obj, "emulation-file")); |
| } else { |
| return TRUE; |
| } |
| |
| /* log */ |
| if (emulation_url != NULL) { |
| json_builder_set_member_name(helper->builder, "emulation-url"); |
| json_builder_add_string_value(helper->builder, emulation_url); |
| } |
| json_builder_set_member_name(helper->builder, "emulation-file"); |
| json_builder_add_string_value(helper->builder, emulation_filename); |
| if (!fwupd_client_emulation_load(self->client, |
| emulation_filename, |
| self->cancellable, |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_debug("ignoring: %s", error_local->message); |
| helper->nr_skipped++; |
| return TRUE; |
| } |
| g_propagate_prefixed_error(error, |
| g_steal_pointer(&error_local), |
| "failed to load %s: ", |
| emulation_filename); |
| return FALSE; |
| } |
| } |
| |
| /* download file if required */ |
| if (json_object_has_member(json_obj, "url")) { |
| const gchar *url_tmp = json_object_get_string_member(json_obj, "url"); |
| g_autofree gchar *filename = NULL; |
| g_autofree gchar *url = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| url = fu_util_maybe_expand_basename(self, url_tmp, error); |
| if (url == NULL) |
| return FALSE; |
| filename = fu_util_download_if_required(self, url, error); |
| if (filename == NULL) { |
| g_prefix_error(error, "failed to download %s: ", url); |
| return FALSE; |
| } |
| |
| /* log */ |
| json_builder_set_member_name(helper->builder, "url"); |
| json_builder_add_string_value(helper->builder, url); |
| |
| /* install file */ |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; |
| if (!fwupd_client_install(self->client, |
| FWUPD_DEVICE_ID_ANY, |
| filename, |
| self->flags, |
| self->cancellable, |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
| if (self->as_json) { |
| json_builder_set_member_name(helper->builder, "info"); |
| json_builder_add_string_value(helper->builder, |
| error_local->message); |
| } else { |
| g_autofree gchar *msg = NULL; |
| msg = fu_console_color_format(error_local->message, |
| FU_CONSOLE_COLOR_YELLOW); |
| fu_console_print(self->console, |
| "%s: %s", |
| helper->name, |
| msg); |
| } |
| helper->nr_missing++; |
| return TRUE; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| } |
| |
| /* process each step */ |
| if (!json_object_has_member(json_obj, "components")) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "JSON invalid as has no 'components'"); |
| return FALSE; |
| } |
| json_array = json_object_get_array_member(json_obj, "components"); |
| for (guint i = 0; i < json_array_get_length(json_array); i++) { |
| JsonNode *json_node = json_array_get_element(json_array, i); |
| JsonObject *json_obj_tmp = json_node_get_object(json_node); |
| if (!fu_util_device_test_component(self, helper, json_obj_tmp, error)) |
| return FALSE; |
| } |
| |
| /* remove emulated devices */ |
| if (helper->use_emulation) { |
| if (!fu_util_device_test_remove_emulated_devices(self, error)) { |
| g_prefix_error_literal(error, "failed to remove emulated devices: "); |
| return FALSE; |
| } |
| } |
| |
| /* success */ |
| json_builder_set_member_name(helper->builder, "success"); |
| json_builder_add_boolean_value(helper->builder, TRUE); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_device_test_filename(FuUtil *self, |
| FuUtilDeviceTestHelper *helper, |
| const gchar *filename, |
| GError **error) |
| { |
| JsonNode *json_root; |
| JsonNode *json_steps; |
| JsonObject *json_obj; |
| guint repeat = 1; |
| g_autoptr(JsonParser) parser = json_parser_new(); |
| |
| /* log */ |
| json_builder_set_member_name(helper->builder, "filename"); |
| json_builder_add_string_value(helper->builder, filename); |
| |
| /* parse JSON */ |
| if (!json_parser_load_from_file(parser, filename, error)) { |
| g_prefix_error_literal(error, "test not in JSON format: "); |
| return FALSE; |
| } |
| json_root = json_parser_get_root(parser); |
| if (json_root == NULL || !JSON_NODE_HOLDS_OBJECT(json_root)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "JSON invalid as has no root"); |
| return FALSE; |
| } |
| json_obj = json_node_get_object(json_root); |
| if (!json_object_has_member(json_obj, "steps")) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "JSON invalid as has no 'steps'"); |
| return FALSE; |
| } |
| json_steps = json_object_get_member(json_obj, "steps"); |
| if (!JSON_NODE_HOLDS_ARRAY(json_steps)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "JSON invalid as has 'steps' is not an array"); |
| return FALSE; |
| } |
| |
| /* some elements are optional */ |
| if (json_object_has_member(json_obj, "name")) { |
| helper->name = json_object_get_string_member(json_obj, "name"); |
| json_builder_set_member_name(helper->builder, "name"); |
| json_builder_add_string_value(helper->builder, helper->name); |
| } |
| if (json_object_has_member(json_obj, "interactive")) { |
| gboolean interactive = json_object_get_boolean_member(json_obj, "interactive"); |
| json_builder_set_member_name(helper->builder, "interactive"); |
| json_builder_add_boolean_value(helper->builder, interactive); |
| } |
| if (json_object_has_member(json_obj, "cpu-architectures")) { |
| JsonArray *json_array = json_object_get_array_member(json_obj, "cpu-architectures"); |
| gboolean matched = FALSE; |
| const gchar *arch = g_hash_table_lookup(helper->report_metadata, "CpuArchitecture"); |
| for (guint i = 0; i < json_array_get_length(json_array); i++) { |
| const gchar *arch_tmp = json_array_get_string_element(json_array, i); |
| if (g_strcmp0(arch, arch_tmp) == 0) { |
| matched = TRUE; |
| break; |
| } |
| } |
| if (!matched) { |
| helper->nr_skipped++; |
| return TRUE; |
| } |
| } |
| if (json_object_has_member(json_obj, "platform-architectures")) { |
| JsonArray *json_array = |
| json_object_get_array_member(json_obj, "platform-architectures"); |
| gboolean matched = FALSE; |
| const gchar *arch = |
| g_hash_table_lookup(helper->report_metadata, "PlatformArchitecture"); |
| for (guint i = 0; i < json_array_get_length(json_array); i++) { |
| const gchar *arch_tmp = json_array_get_string_element(json_array, i); |
| if (g_strcmp0(arch, arch_tmp) == 0) { |
| matched = TRUE; |
| break; |
| } |
| } |
| if (!matched) { |
| helper->nr_skipped++; |
| return TRUE; |
| } |
| } |
| |
| /* process each step */ |
| if (json_object_has_member(json_obj, "repeat")) { |
| repeat = json_object_get_int_member(json_obj, "repeat"); |
| json_builder_set_member_name(helper->builder, "repeat"); |
| json_builder_add_int_value(helper->builder, repeat); |
| } |
| json_builder_set_member_name(helper->builder, "steps"); |
| json_builder_begin_array(helper->builder); |
| for (guint j = 0; j < repeat; j++) { |
| JsonArray *json_array = json_node_get_array(json_steps); |
| for (guint i = 0; i < json_array_get_length(json_array); i++) { |
| JsonNode *json_node = json_array_get_element(json_array, i); |
| json_obj = json_node_get_object(json_node); |
| json_builder_begin_object(helper->builder); |
| if (!fu_util_device_test_step(self, helper, json_obj, error)) |
| return FALSE; |
| json_builder_end_object(helper->builder); |
| } |
| } |
| json_builder_end_array(helper->builder); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| typedef struct { |
| FuUtil *self; |
| gchar *inhibit_id; |
| } FuUtilInhibitHelper; |
| |
| static void |
| fu_util_inhibit_helper_free(FuUtilInhibitHelper *helper) |
| { |
| g_free(helper->inhibit_id); |
| g_free(helper); |
| } |
| |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilInhibitHelper, fu_util_inhibit_helper_free) |
| |
| static gboolean |
| fu_util_inhibit_timeout_cb(FuUtilInhibitHelper *helper) |
| { |
| FuUtil *self = helper->self; |
| g_autoptr(GError) error_local = NULL; |
| |
| if (!fwupd_client_uninhibit(self->client, |
| helper->inhibit_id, |
| self->cancellable, |
| &error_local)) { |
| g_warning("failed to auto-uninhibit: %s", error_local->message); |
| } |
| g_main_loop_quit(self->loop); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static gboolean |
| fu_util_inhibit(FuUtil *self, gchar **values, GError **error) |
| { |
| const gchar *reason = "not set"; |
| guint64 timeout_ms = 0; |
| g_autoptr(FuUtilInhibitHelper) helper = g_new0(FuUtilInhibitHelper, 1); |
| g_autoptr(GString) str = g_string_new(NULL); |
| |
| if (g_strv_length(values) > 0) |
| reason = values[0]; |
| if (g_strv_length(values) > 1) { |
| if (!fu_strtoull(values[1], |
| &timeout_ms, |
| 0, |
| G_MAXUINT32, |
| FU_INTEGER_BASE_AUTO, |
| error)) |
| return FALSE; |
| } |
| |
| /* inhibit then wait */ |
| helper->self = self; |
| helper->inhibit_id = fwupd_client_inhibit(self->client, reason, self->cancellable, error); |
| if (helper->inhibit_id == NULL) |
| return FALSE; |
| if (timeout_ms > 0) { |
| g_autoptr(GSource) source = g_timeout_source_new(timeout_ms); |
| g_source_set_callback(source, |
| (GSourceFunc)fu_util_inhibit_timeout_cb, |
| helper, |
| NULL); |
| g_source_attach(source, self->main_ctx); |
| } |
| |
| /* TRANSLATORS: the inhibit ID is a short string like dbus-123456 */ |
| g_string_append_printf(str, _("Inhibit ID is %s."), helper->inhibit_id); |
| g_string_append(str, "\n"); |
| if (timeout_ms > 0) { |
| g_string_append_printf(str, |
| /* TRANSLATORS: we can auto-uninhibit after a timeout */ |
| _("Automatically uninhibiting in %ums…"), |
| (guint)timeout_ms); |
| g_string_append(str, "\n"); |
| } |
| /* TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the program */ |
| g_string_append(str, _("Use CTRL^C to cancel.")); |
| /* TRANSLATORS: this CLI tool is now preventing system updates */ |
| fu_console_box(self->console, _("System Update Inhibited"), str->str, 80); |
| g_main_loop_run(self->loop); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_uninhibit(FuUtil *self, gchar **values, GError **error) |
| { |
| /* one argument required */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments, expected INHIBIT-ID"); |
| return FALSE; |
| } |
| |
| /* just uninhibit with the token */ |
| return fwupd_client_uninhibit(self->client, values[0], self->cancellable, error); |
| } |
| |
| typedef struct { |
| FuUtil *self; |
| const gchar *value; |
| FwupdDevice *device; /* no-ref */ |
| } FuUtilWaitHelper; |
| |
| static void |
| fu_util_device_wait_added_cb(FwupdClient *client, FwupdDevice *device, FuUtilWaitHelper *helper) |
| { |
| FuUtil *self = helper->self; |
| if (g_strcmp0(fwupd_device_get_id(device), helper->value) == 0 || |
| fwupd_device_has_guid(device, helper->value)) { |
| helper->device = device; |
| g_main_loop_quit(self->loop); |
| return; |
| } |
| } |
| |
| static gboolean |
| fu_util_device_wait_timeout_cb(gpointer user_data) |
| { |
| FuUtil *self = (FuUtil *)user_data; |
| g_main_loop_quit(self->loop); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static gboolean |
| fu_util_device_wait(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) device = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GSource) source = g_timeout_source_new_seconds(30); |
| g_autoptr(GTimer) timer = g_timer_new(); |
| FuUtilWaitHelper helper = {.self = self, .value = values[0]}; |
| |
| /* one argument required */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments, expected GUID|DEVICE-ID"); |
| return FALSE; |
| } |
| |
| /* check if the device already exists */ |
| device = fwupd_client_get_device_by_id(self->client, helper.value, NULL, NULL); |
| if (device != NULL) { |
| /* TRANSLATORS: the device is already connected */ |
| fu_console_print_literal(self->console, _("Device already exists")); |
| return TRUE; |
| } |
| devices = fwupd_client_get_devices_by_guid(self->client, helper.value, NULL, NULL); |
| if (devices != NULL) { |
| /* TRANSLATORS: the device is already connected */ |
| fu_console_print_literal(self->console, _("Device already exists")); |
| return TRUE; |
| } |
| |
| /* wait for device to show up */ |
| fu_console_set_progress(self->console, FWUPD_STATUS_IDLE, 0); |
| g_signal_connect(FWUPD_CLIENT(self->client), |
| "device-added", |
| G_CALLBACK(fu_util_device_wait_added_cb), |
| &helper); |
| g_source_set_callback(source, fu_util_device_wait_timeout_cb, self, NULL); |
| g_source_attach(source, self->main_ctx); |
| g_main_loop_run(self->loop); |
| |
| /* timed out */ |
| if (helper.device == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Stopped waiting for %s after %.0fms", |
| helper.value, |
| g_timer_elapsed(timer, NULL) * 1000.f); |
| return FALSE; |
| } |
| |
| /* success */ |
| fu_console_print(self->console, |
| /* TRANSLATORS: the device showed up in time */ |
| _("Successfully waited %.0fms for device"), |
| g_timer_elapsed(timer, NULL) * 1000.f); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_quit(FuUtil *self, gchar **values, GError **error) |
| { |
| /* success */ |
| return fwupd_client_quit(self->client, self->cancellable, error); |
| } |
| |
| static gboolean |
| fu_util_device_test_full(FuUtil *self, |
| gchar **values, |
| FuUtilDeviceTestHelper *helper, |
| GError **error) |
| { |
| /* required for interactive devices */ |
| self->current_operation = FU_UTIL_OPERATION_UPDATE; |
| |
| /* at least one argument required */ |
| if (g_strv_length(values) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* get the report metadata */ |
| helper->report_metadata = |
| fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (helper->report_metadata == NULL) |
| return FALSE; |
| |
| /* prepare to save the data as JSON */ |
| json_builder_begin_object(helper->builder); |
| |
| /* process all the files */ |
| json_builder_set_member_name(helper->builder, "results"); |
| json_builder_begin_array(helper->builder); |
| for (guint i = 0; values[i] != NULL; i++) { |
| json_builder_begin_object(helper->builder); |
| if (!fu_util_device_test_filename(self, helper, values[i], error)) { |
| g_prefix_error(error, "%s failed: ", values[i]); |
| return FALSE; |
| } |
| json_builder_end_object(helper->builder); |
| } |
| json_builder_end_array(helper->builder); |
| |
| /* dump to screen as JSON format */ |
| json_builder_end_object(helper->builder); |
| if (self->as_json) { |
| if (!fu_util_print_builder(self->console, helper->builder, error)) |
| return FALSE; |
| } |
| |
| /* just warning */ |
| if (helper->nr_skipped > 0) { |
| g_autoptr(GString) str = g_string_new(NULL); |
| g_string_append_printf( |
| str, |
| /* TRANSLATORS: device tests can be specific to a CPU type */ |
| ngettext("%u test was skipped", "%u tests were skipped", helper->nr_skipped), |
| helper->nr_skipped); |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_WARNING, |
| "%s\n", |
| str->str); |
| } |
| |
| /* we need all to pass for a zero return code */ |
| if (helper->nr_missing > 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "%u devices required for %u tests were not found", |
| helper->nr_missing, |
| g_strv_length(values)); |
| return FALSE; |
| } |
| if (helper->nr_success == 0 && helper->nr_skipped == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "None of the tests were successful"); |
| return FALSE; |
| } |
| |
| /* nag? */ |
| if (!fu_util_perhaps_show_unreported(self, error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_device_emulate(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FuUtilDeviceTestHelper) helper = fu_util_device_test_helper_new(); |
| helper->use_emulation = TRUE; |
| self->flags |= FWUPD_INSTALL_FLAG_ONLY_EMULATED; |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_EMULATED; |
| return fu_util_device_test_full(self, values, helper, error); |
| } |
| |
| static gboolean |
| fu_util_device_test(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FuUtilDeviceTestHelper) helper = fu_util_device_test_helper_new(); |
| self->filter_device_exclude |= FWUPD_DEVICE_FLAG_EMULATED; |
| return fu_util_device_test_full(self, values, helper, error); |
| } |
| |
| static gboolean |
| fu_util_download(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autofree gchar *basename = NULL; |
| g_autoptr(GBytes) blob = NULL; |
| |
| /* one argument required */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* file already exists */ |
| basename = g_path_get_basename(values[0]); |
| if ((self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && |
| g_file_test(basename, G_FILE_TEST_EXISTS)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "%s already exists", |
| basename); |
| return FALSE; |
| } |
| blob = fwupd_client_download_bytes(self->client, |
| values[0], |
| self->download_flags, |
| self->cancellable, |
| error); |
| if (blob == NULL) |
| return FALSE; |
| return g_file_set_contents(basename, |
| g_bytes_get_data(blob, NULL), |
| g_bytes_get_size(blob), |
| error); |
| } |
| |
| static gboolean |
| fu_util_local_install(FuUtil *self, gchar **values, GError **error) |
| { |
| const gchar *id; |
| g_autofree gchar *filename = NULL; |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| /* handle both forms */ |
| if (g_strv_length(values) == 1) { |
| id = FWUPD_DEVICE_ID_ANY; |
| } else if (g_strv_length(values) == 2) { |
| dev = fu_util_get_device_by_id(self, values[1], error); |
| if (dev == NULL) |
| return FALSE; |
| id = fwupd_device_get_id(dev); |
| } else { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| self->current_operation = FU_UTIL_OPERATION_INSTALL; |
| |
| /* install with flags chosen by the user */ |
| filename = fu_util_download_if_required(self, values[0], error); |
| if (filename == NULL) |
| return FALSE; |
| |
| /* detect bitlocker */ |
| if (dev != NULL && !self->no_safety_check && !self->assume_yes) { |
| if (!fu_util_prompt_warning_fde(self->console, dev, error)) |
| return FALSE; |
| } |
| |
| if (!fwupd_client_install(self->client, |
| id, |
| filename, |
| self->flags, |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| fu_util_display_current_message(self); |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| /* show reboot if needed */ |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_get_details(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) array = NULL; |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| |
| /* 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 */ |
| self->show_all = TRUE; |
| |
| array = fwupd_client_get_details(self->client, values[0], self->cancellable, error); |
| if (array == NULL) |
| return FALSE; |
| if (self->as_json) { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| fwupd_codec_array_to_json(array, "Devices", builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| fu_util_build_device_tree(self, root, array, NULL); |
| fu_util_print_node(self->console, self->client, root); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_history_for_remote(FuUtil *self, |
| GPtrArray *devices, |
| FwupdRemote *remote_filter, |
| FwupdRemote *remote_upload, |
| GError **error) |
| { |
| g_autofree gchar *data = NULL; |
| g_autofree gchar *report_uri = NULL; |
| g_autofree gchar *sig = NULL; |
| g_autofree gchar *uri = NULL; |
| g_autoptr(GHashTable) metadata = NULL; |
| |
| /* convert to JSON */ |
| metadata = fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (metadata == NULL) |
| return FALSE; |
| data = fwupd_client_build_report_history(self->client, |
| devices, |
| remote_filter, |
| metadata, |
| error); |
| if (data == NULL) |
| return FALSE; |
| |
| /* self sign data */ |
| if (self->sign) { |
| sig = fwupd_client_self_sign(self->client, |
| data, |
| FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, |
| self->cancellable, |
| error); |
| if (sig == NULL) |
| return FALSE; |
| } |
| |
| /* ask for permission */ |
| report_uri = fwupd_remote_build_report_uri(remote_upload, error); |
| if (report_uri == NULL) |
| return FALSE; |
| if (!self->assume_yes && |
| !fwupd_remote_has_flag(remote_upload, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) { |
| fu_console_print_kv(self->console, _("Target"), report_uri); |
| fu_console_print_kv(self->console, _("Payload"), data); |
| if (sig != NULL) |
| fu_console_print_kv(self->console, _("Signature"), sig); |
| if (!fu_console_input_bool(self->console, TRUE, "%s", _("Proceed with upload?"))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_PERMISSION_DENIED, |
| "User declined action"); |
| return FALSE; |
| } |
| } |
| |
| /* POST request and parse reply */ |
| uri = fwupd_client_upload_report(self->client, |
| report_uri, |
| data, |
| sig, |
| FWUPD_CLIENT_UPLOAD_FLAG_NONE, |
| self->cancellable, |
| error); |
| if (uri == NULL) |
| return FALSE; |
| |
| /* server wanted us to see a message */ |
| if (g_strcmp0(uri, "") != 0) { |
| fu_console_print( |
| self->console, |
| "%s %s", |
| /* TRANSLATORS: the server sent the user a small message */ |
| _("Update failure is a known issue, visit this URL for more information:"), |
| uri); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_history_force(FuUtil *self, GPtrArray *devices, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote_upload = NULL; |
| g_autoptr(GString) str = g_string_new(NULL); |
| |
| /* just assume every report goes to this remote */ |
| remote_upload = |
| fwupd_client_get_remote_by_id(self->client, "lvfs", self->cancellable, error); |
| if (remote_upload == NULL) |
| return FALSE; |
| if (!fu_util_report_history_for_remote(self, |
| devices, |
| NULL, /* no filter */ |
| remote_upload, |
| error)) |
| return FALSE; |
| |
| /* mark each device as reported */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| g_debug("setting flag on %s", fwupd_device_get_id(device)); |
| if (!fwupd_client_modify_device(self->client, |
| fwupd_device_get_id(device), |
| "Flags", |
| "reported", |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| |
| /* success */ |
| g_string_append_printf(str, |
| /* TRANSLATORS: success message -- where the user has uploaded |
| * success and/or failure reports to the remote server */ |
| ngettext("Successfully uploaded %u report", |
| "Successfully uploaded %u reports", |
| devices->len), |
| devices->len); |
| fu_console_print_literal(self->console, str->str); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_export(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GHashTable) metadata = NULL; |
| g_autoptr(GPtrArray) devices_filtered = |
| g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* get all devices from the history database, then filter them and export to JSON */ |
| devices = fwupd_client_get_history(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| g_debug("%u devices with history", devices->len); |
| |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| 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]; |
| const gchar *device_id = fwupd_device_get_id(dev); |
| 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; |
| |
| /* filter, if not forcing */ |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| if ((self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) { |
| g_debug("%s has already been reported", fwupd_device_get_id(dev)); |
| continue; |
| } |
| } |
| |
| /* only send success and failure */ |
| if (fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED && |
| fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_SUCCESS) { |
| g_debug("ignoring %s with UpdateState %s", |
| fwupd_device_get_id(dev), |
| fwupd_update_state_to_string(fwupd_device_get_update_state(dev))); |
| continue; |
| } |
| g_ptr_array_add(devices_filtered, g_object_ref(dev)); |
| } |
| |
| /* nothing to report, but try harder with --force */ |
| if (devices_filtered->len == 0 && (self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No reports require uploading"); |
| return FALSE; |
| } |
| |
| /* get metadata */ |
| metadata = fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (metadata == NULL) |
| return FALSE; |
| |
| /* write each device report as a new file */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| g_autofree gchar *data = NULL; |
| g_autofree gchar *filename = NULL; |
| g_autoptr(FuFirmware) archive = fu_archive_firmware_new(); |
| g_autoptr(FuFirmware) payload_img = NULL; |
| g_autoptr(GBytes) payload_blob = NULL; |
| g_autoptr(GFile) file = NULL; |
| g_autoptr(GPtrArray) devices_tmp = g_ptr_array_new(); |
| |
| /* convert single device to JSON */ |
| g_ptr_array_add(devices_tmp, dev); |
| data = fwupd_client_build_report_history(self->client, |
| devices, |
| NULL, /* remote */ |
| metadata, |
| error); |
| if (data == NULL) |
| return FALSE; |
| payload_blob = g_bytes_new(data, strlen(data)); |
| payload_img = fu_firmware_new_from_bytes(payload_blob); |
| fu_firmware_set_id(payload_img, "report.json"); |
| if (!fu_firmware_add_image(archive, payload_img, error)) |
| return FALSE; |
| |
| /* self sign data */ |
| if (self->sign) { |
| g_autofree gchar *sig = NULL; |
| g_autoptr(FuFirmware) sig_img = NULL; |
| g_autoptr(GBytes) sig_blob = NULL; |
| |
| sig = fwupd_client_self_sign(self->client, |
| data, |
| FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, |
| self->cancellable, |
| error); |
| if (sig == NULL) |
| return FALSE; |
| sig_blob = g_bytes_new(sig, strlen(sig)); |
| sig_img = fu_firmware_new_from_bytes(sig_blob); |
| fu_firmware_set_id(sig_img, "report.json.p7c"); |
| if (!fu_firmware_add_image(archive, sig_img, error)) |
| return FALSE; |
| } |
| |
| /* save to local file */ |
| fu_archive_firmware_set_format(FU_ARCHIVE_FIRMWARE(archive), FU_ARCHIVE_FORMAT_ZIP); |
| fu_archive_firmware_set_compression(FU_ARCHIVE_FIRMWARE(archive), |
| FU_ARCHIVE_COMPRESSION_GZIP); |
| filename = g_strdup_printf("%s.fwupdreport", fwupd_device_get_id(dev)); |
| file = g_file_new_for_path(filename); |
| if (!fu_firmware_write_file(archive, file, error)) |
| return FALSE; |
| |
| /* TRANSLATORS: key for a offline report filename */ |
| fu_console_print_kv(self->console, _("Saved report"), filename); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_history_full(FuUtil *self, gboolean only_automatic_reports, GError **error) |
| { |
| guint cnt = 0; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) remotes = NULL; |
| |
| /* get all devices from the history database, then filter them, |
| * adding to a hash map of report-ids */ |
| devices = fwupd_client_get_history(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| g_debug("%u devices with history", devices->len); |
| |
| /* ignore the previous reported flag */ |
| if (self->flags & FWUPD_INSTALL_FLAG_FORCE) { |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| fwupd_device_remove_flag(dev, FWUPD_DEVICE_FLAG_REPORTED); |
| } |
| } |
| |
| /* needs an extra action, show something to the user */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { |
| g_autofree gchar *cmd = g_strdup_printf("%s activate", g_get_prgname()); |
| fu_console_print( |
| self->console, |
| /* TRANSLATORS: %1 is a device name, e.g. "ThinkPad Universal |
| * ThunderBolt 4 Dock" and %2 is "fwupdmgr activate" */ |
| _("%s is pending activation; use %s to complete the update."), |
| fwupd_device_get_name(dev), |
| cmd); |
| } |
| } |
| |
| /* get all remotes */ |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, error); |
| if (remotes == NULL) |
| return FALSE; |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| g_autoptr(GError) error_local = NULL; |
| |
| /* filter this so we can use it from fwupd-refresh */ |
| if (only_automatic_reports && |
| !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) { |
| g_debug("%s has no AutomaticReports set", fwupd_remote_get_id(remote)); |
| continue; |
| } |
| |
| /* try to upload */ |
| if (!fu_util_report_history_for_remote(self, |
| devices, |
| remote, /* filter */ |
| remote, /* upload */ |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) |
| continue; |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| |
| /* keep track to make sure *something* worked */ |
| cnt += 1; |
| } |
| |
| /* nothing to report, but try harder with --force */ |
| if (cnt == 0) { |
| if (!only_automatic_reports && self->flags & FWUPD_INSTALL_FLAG_FORCE) |
| return fu_util_report_history_force(self, devices, error); |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No reports require uploading"); |
| return FALSE; |
| } |
| |
| /* mark each device as reported */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| g_debug("setting flag on %s", fwupd_device_get_id(dev)); |
| if (!fwupd_client_modify_device(self->client, |
| fwupd_device_get_id(dev), |
| "Flags", |
| "reported", |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| |
| /* TRANSLATORS: where the user has uploaded success and/or failure report to the server */ |
| fu_console_print_literal(self->console, "Successfully uploaded report"); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_history(FuUtil *self, gchar **values, GError **error) |
| { |
| if (values != NULL && g_strv_length(values) != 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| return fu_util_report_history_full(self, FALSE, error); |
| } |
| |
| static gboolean |
| fu_util_get_history(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| |
| /* get all devices from the history database */ |
| devices = fwupd_client_get_history(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| |
| /* not for human consumption */ |
| if (self->as_json) { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| fwupd_codec_array_to_json(devices, "Devices", builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| /* show each device */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| FwupdRelease *rel; |
| FuUtilNode *child; |
| |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| child = g_node_append_data(root, g_object_ref(dev)); |
| |
| rel = fwupd_device_get_release_default(dev); |
| if (rel == NULL) |
| continue; |
| g_node_append_data(child, g_object_ref(rel)); |
| } |
| |
| fu_util_print_node(self->console, self->client, root); |
| |
| return TRUE; |
| } |
| |
| static FwupdDevice * |
| fu_util_get_device_by_id(FuUtil *self, const gchar *id, GError **error) |
| { |
| if (fwupd_guid_is_valid(id)) { |
| g_autoptr(GPtrArray) devices = NULL; |
| devices = |
| fwupd_client_get_devices_by_guid(self->client, id, self->cancellable, error); |
| if (devices == NULL) |
| return NULL; |
| return fu_util_prompt_for_device(self, 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 fwupd_client_get_device_by_id(self->client, id, self->cancellable, error); |
| } |
| |
| static FwupdDevice * |
| fu_util_get_device_or_prompt(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* get device to use */ |
| if (g_strv_length(values) >= 1) { |
| if (g_strv_length(values) > 1) { |
| for (guint i = 1; i < g_strv_length(values); i++) |
| g_debug("ignoring extra input %s", values[i]); |
| } |
| return fu_util_get_device_by_id(self, values[0], error); |
| } |
| |
| if (self->as_json) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "device ID required"); |
| return NULL; |
| } |
| |
| /* get all devices from daemon */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return NULL; |
| return fu_util_prompt_for_device(self, devices, error); |
| } |
| |
| static FwupdRelease * |
| fu_util_get_release_for_device_version(FuUtil *self, |
| FwupdDevice *device, |
| const gchar *version, |
| GError **error) |
| { |
| g_autoptr(GPtrArray) releases = NULL; |
| |
| /* get all releases */ |
| releases = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(device), |
| self->cancellable, |
| error); |
| if (releases == NULL) |
| return NULL; |
| |
| /* find using vercmp */ |
| for (guint j = 0; j < releases->len; j++) { |
| FwupdRelease *release = g_ptr_array_index(releases, j); |
| if (fu_version_compare(fwupd_release_get_version(release), |
| version, |
| fwupd_device_get_version_format(device)) == 0) { |
| return g_object_ref(release); |
| } |
| } |
| |
| /* did not find */ |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "Unable to locate release %s for %s", |
| version, |
| fwupd_device_get_name(device)); |
| return NULL; |
| } |
| |
| static gboolean |
| fu_util_clear_results(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| return fwupd_client_clear_results(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_verify_update(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| if (!fwupd_client_verify_update(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error)) { |
| g_prefix_error(error, "failed to verify update %s: ", fwupd_device_get_name(dev)); |
| return FALSE; |
| } |
| |
| /* TRANSLATORS: success message when user refreshes device checksums */ |
| fu_console_print_literal(self->console, _("Successfully updated device checksums")); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_download_metadata_enable_lvfs(FuUtil *self, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| |
| /* is the LVFS available but disabled? */ |
| remote = fwupd_client_get_remote_by_id(self->client, "lvfs", self->cancellable, error); |
| if (remote == NULL) |
| return TRUE; |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: explain why no metadata available */ |
| _("No remotes are currently enabled so no metadata is available.")); |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: explain why no metadata available */ |
| _("Metadata can be obtained from the Linux Vendor Firmware Service.")); |
| |
| /* TRANSLATORS: Turn on the remote */ |
| if (!fu_console_input_bool(self->console, TRUE, "%s", _("Enable this remote?"))) |
| return TRUE; |
| if (!fwupd_client_modify_remote(self->client, |
| fwupd_remote_get_id(remote), |
| "Enabled", |
| "true", |
| self->cancellable, |
| error)) |
| return FALSE; |
| if (!fu_util_modify_remote_warning(self->console, remote, self->assume_yes, error)) |
| return FALSE; |
| |
| /* refresh the newly-enabled remote */ |
| return fwupd_client_refresh_remote(self->client, |
| remote, |
| self->download_flags, |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_check_oldest_remote(FuUtil *self, guint64 *age_oldest, GError **error) |
| { |
| g_autoptr(GPtrArray) remotes = NULL; |
| gboolean checked = FALSE; |
| |
| /* get the age of the oldest enabled remotes */ |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, 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_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) |
| continue; |
| if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) |
| continue; |
| checked = TRUE; |
| if (!fwupd_remote_needs_refresh(remote)) |
| continue; |
| g_debug("%s is age %u", |
| fwupd_remote_get_id(remote), |
| (guint)fwupd_remote_get_age(remote)); |
| if (fwupd_remote_get_age(remote) > *age_oldest) |
| *age_oldest = fwupd_remote_get_age(remote); |
| } |
| if (!checked) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: error message for a user who ran fwupdmgr |
| refresh recently but no remotes */ |
| "No remotes enabled."); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_download_metadata(FuUtil *self, GError **error) |
| { |
| gboolean download_remote_enabled = FALSE; |
| guint devices_supported_cnt = 0; |
| guint devices_updatable_cnt = 0; |
| guint refresh_cnt = 0; |
| g_autoptr(GPtrArray) devs = NULL; |
| g_autoptr(GPtrArray) remotes = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, 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_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) |
| continue; |
| if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) |
| continue; |
| download_remote_enabled = TRUE; |
| if ((self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && |
| !fwupd_remote_needs_refresh(remote)) { |
| g_debug("skipping as remote %s age is %us", |
| fwupd_remote_get_id(remote), |
| (guint)fwupd_remote_get_age(remote)); |
| continue; |
| } |
| if (!self->as_json) |
| fu_console_print(self->console, |
| "%s %s", |
| _("Updating"), |
| fwupd_remote_get_id(remote)); |
| if (!fwupd_client_refresh_remote(self->client, |
| remote, |
| self->download_flags, |
| self->cancellable, |
| error)) |
| return FALSE; |
| refresh_cnt++; |
| } |
| |
| /* no web remote is declared; try to enable LVFS */ |
| if (!download_remote_enabled) { |
| /* we don't want to ask anything */ |
| if (self->no_remote_check) { |
| g_debug("skipping remote check"); |
| return TRUE; |
| } |
| |
| if (!fu_util_download_metadata_enable_lvfs(self, error)) |
| return FALSE; |
| } |
| |
| /* metadata refreshed recently */ |
| if ((self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && refresh_cnt == 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: error message for a user who ran fwupdmgr |
| * refresh recently -- %1 is '--force' */ |
| _("Metadata is up to date; use %s to refresh again."), |
| "--force"); |
| return FALSE; |
| } |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* get devices from daemon */ |
| devs = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devs == NULL) |
| return FALSE; |
| |
| /* get results */ |
| for (guint i = 0; i < devs->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devs, i); |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) |
| devices_supported_cnt++; |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) |
| devices_updatable_cnt++; |
| } |
| |
| /* TRANSLATORS: success message -- where 'metadata' is information |
| * about available firmware on the remote server */ |
| fu_console_print_literal(self->console, _("Successfully downloaded new metadata:")); |
| |
| if (devices_updatable_cnt == 0) { |
| fu_console_print_full( |
| self->console, |
| FU_CONSOLE_PRINT_FLAG_LIST_ITEM | FU_CONSOLE_PRINT_FLAG_NEWLINE, |
| "%s", |
| /* TRANSLATORS: no devices that can be upgraded with new firmware */ |
| _("No devices are updatable")); |
| } else { |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_LIST_ITEM | |
| FU_CONSOLE_PRINT_FLAG_NEWLINE, |
| /* TRANSLATORS: how many devices could be updated in theory if |
| we had the firmware locally */ |
| ngettext("%u device is updatable", |
| "%u devices are updatable", |
| devices_updatable_cnt), |
| devices_updatable_cnt); |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_LIST_ITEM | |
| FU_CONSOLE_PRINT_FLAG_NEWLINE, |
| /* TRANSLATORS: how many devices have published updates on |
| something like the LVFS */ |
| ngettext("%u device is supported in the enabled remotes (an " |
| "update has been published)", |
| "%u devices are supported in the enabled remotes " |
| "(an update has been published)", |
| devices_supported_cnt), |
| devices_supported_cnt); |
| } |
| |
| /* auto-upload any reports */ |
| if (!fu_util_report_history_full(self, TRUE, &error_local)) { |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| g_debug("failed to auto-upload reports: %s", error_local->message); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_refresh(FuUtil *self, gchar **values, GError **error) |
| { |
| if (g_strv_length(values) == 0) |
| return fu_util_download_metadata(self, error); |
| if (g_strv_length(values) != 3) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* open file */ |
| if (!fwupd_client_update_metadata(self->client, |
| values[2], |
| values[0], |
| values[1], |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: success message -- the user can do this by-hand too */ |
| fu_console_print_literal(self->console, _("Successfully refreshed metadata manually")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_get_results(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autofree gchar *tmp = NULL; |
| g_autoptr(FwupdDevice) dev = NULL; |
| g_autoptr(FwupdDevice) rel = NULL; |
| |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| rel = fwupd_client_get_results(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rel == NULL) |
| return FALSE; |
| if (self->as_json) { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| fwupd_codec_to_json(FWUPD_CODEC(rel), builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| tmp = fu_util_device_to_string(self->client, rel, 0); |
| fu_console_print_literal(self->console, tmp); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_get_releases(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* get the releases for this device */ |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rels == NULL) |
| return FALSE; |
| |
| /* not for human consumption */ |
| if (self->as_json) |
| return fu_util_get_releases_as_json(self, rels, error); |
| |
| if (rels->len == 0) { |
| /* TRANSLATORS: no repositories to download from */ |
| fu_console_print_literal(self->console, _("No releases available")); |
| return TRUE; |
| } |
| if (g_getenv("FWUPD_VERBOSE") != NULL) { |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| g_autofree gchar *tmp = NULL; |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| tmp = fwupd_codec_to_string(FWUPD_CODEC(rel)); |
| fu_console_print_literal(self->console, tmp); |
| } |
| } else { |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| g_node_append_data(root, g_object_ref(rel)); |
| } |
| fu_util_print_node(self->console, self->client, root); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_search(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| /* sanity check */ |
| if (g_strv_length(values) < 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments, expected WORD"); |
| return FALSE; |
| } |
| |
| /* get the releases for this device */ |
| rels = fwupd_client_search(self->client, values[0], self->cancellable, error); |
| if (rels == NULL) |
| return FALSE; |
| |
| /* not for human consumption */ |
| if (self->as_json) |
| return fu_util_get_releases_as_json(self, rels, error); |
| |
| if (rels->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| /* TRANSLATORS: no repositories to download from */ |
| _("No matching releases for search token")); |
| return FALSE; |
| } |
| if (g_getenv("FWUPD_VERBOSE") != NULL) { |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| g_autofree gchar *tmp = NULL; |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| tmp = fwupd_codec_to_string(FWUPD_CODEC(rel)); |
| fu_console_print_literal(self->console, tmp); |
| } |
| } else { |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| g_node_append_data(root, g_object_ref(rel)); |
| } |
| fu_util_print_node(self->console, self->client, root); |
| } |
| |
| return TRUE; |
| } |
| |
| static FwupdRelease * |
| fu_util_prompt_for_release(FuUtil *self, GPtrArray *rels_unfiltered, GError **error) |
| { |
| FwupdRelease *rel; |
| guint idx; |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| /* filter */ |
| rels = fwupd_release_array_filter_flags(rels_unfiltered, |
| self->filter_release_include, |
| self->filter_release_exclude, |
| error); |
| if (rels == NULL) |
| return NULL; |
| |
| /* exactly one */ |
| if (rels->len == 1) { |
| rel = g_ptr_array_index(rels, 0); |
| return g_object_ref(rel); |
| } |
| |
| /* TRANSLATORS: this is to abort the interactive prompt */ |
| fu_console_print(self->console, "0.\t%s", _("Cancel")); |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel_tmp = g_ptr_array_index(rels, i); |
| fu_console_print(self->console, |
| "%u.\t%s", |
| i + 1, |
| fwupd_release_get_version(rel_tmp)); |
| } |
| /* TRANSLATORS: get interactive prompt */ |
| idx = fu_console_input_uint(self->console, rels->len, "%s", _("Choose release")); |
| if (idx == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Request canceled"); |
| return NULL; |
| } |
| rel = g_ptr_array_index(rels, idx - 1); |
| return g_object_ref(rel); |
| } |
| |
| static gboolean |
| fu_util_verify(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| if (!fwupd_client_verify(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error)) { |
| g_prefix_error(error, "failed to verify %s: ", fwupd_device_get_name(dev)); |
| return FALSE; |
| } |
| |
| /* TRANSLATORS: success message when user verified device checksums */ |
| fu_console_print_literal(self->console, _("Successfully verified device checksums")); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_unlock(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_LOCKED; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| if (!fwupd_client_unlock(self->client, fwupd_device_get_id(dev), self->cancellable, error)) |
| return FALSE; |
| |
| /* check flags after unlocking in case the operation changes them */ |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; |
| if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_perhaps_refresh_remotes(FuUtil *self, GError **error) |
| { |
| guint64 age_oldest = 0; |
| const guint64 age_limit_days = 30; |
| |
| /* we don't want to ask anything */ |
| if (self->no_metadata_check || self->as_json) { |
| g_debug("skipping metadata check"); |
| return TRUE; |
| } |
| |
| if (!fu_util_check_oldest_remote(self, &age_oldest, NULL)) |
| return TRUE; |
| |
| /* metadata is new enough */ |
| if (age_oldest < 60 * 60 * 24 * age_limit_days) |
| return TRUE; |
| |
| /* ask for permission */ |
| if (!self->assume_yes) { |
| fu_console_print( |
| self->console, |
| /* TRANSLATORS: the metadata is very out of date; %u is a number > 1 */ |
| ngettext("Firmware metadata has not been updated for %u" |
| " day and may not be up to date.", |
| "Firmware metadata has not been updated for %u" |
| " days and may not be up to date.", |
| (gint)age_limit_days), |
| (guint)age_limit_days); |
| if (!fu_console_input_bool(self->console, |
| FALSE, |
| "%s (%s)", |
| /* TRANSLATORS: ask if we can update metadata */ |
| _("Update now?"), |
| /* TRANSLATORS: metadata is downloaded */ |
| _("Requires internet connection"))) |
| return TRUE; |
| } |
| |
| /* downloads new metadata */ |
| return fu_util_download_metadata(self, error); |
| } |
| |
| static gboolean |
| fu_util_get_updates_as_json(FuUtil *self, GPtrArray *devices, GError **error) |
| { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| json_builder_set_member_name(builder, "Devices"); |
| json_builder_begin_array(builder); |
| 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; |
| |
| /* not going to have results, so save a D-Bus round-trip */ |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) |
| continue; |
| |
| /* get the releases for this device and filter for validity */ |
| rels = fwupd_client_get_upgrades(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| &error_local); |
| if (rels == NULL) { |
| g_debug("no upgrades: %s", error_local->message); |
| continue; |
| } |
| for (guint j = 0; j < rels->len; j++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, j); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| fwupd_device_add_release(dev, rel); |
| } |
| |
| /* add to builder */ |
| json_builder_begin_object(builder); |
| fwupd_codec_to_json(FWUPD_CODEC(dev), builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| } |
| json_builder_end_array(builder); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_get_updates(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| gboolean supported = FALSE; |
| g_autoptr(FuUtilNode) 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(); |
| |
| /* are the remotes very old */ |
| if (!fu_util_perhaps_refresh_remotes(self, error)) |
| return FALSE; |
| |
| /* handle both forms */ |
| if (g_strv_length(values) == 0) { |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| } else { |
| devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint idx = 0; idx < g_strv_length(values); idx++) { |
| FwupdDevice *device = fu_util_get_device_by_id(self, values[idx], error); |
| if (device == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "'%s' is not a valid GUID nor DEVICE-ID", |
| values[idx]); |
| return FALSE; |
| } |
| g_ptr_array_add(devices, device); |
| } |
| } |
| g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb); |
| |
| /* not for human consumption */ |
| if (self->as_json) |
| return fu_util_get_updates_as_json(self, devices, error); |
| |
| 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; |
| FuUtilNode *child; |
| |
| /* not going to have results, so save a D-Bus round-trip */ |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && |
| !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) |
| continue; |
| if (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { |
| g_ptr_array_add(devices_no_support, dev); |
| continue; |
| } |
| supported = TRUE; |
| |
| /* get the releases for this device and filter for validity */ |
| rels = fwupd_client_get_upgrades(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| &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, g_object_ref(dev)); |
| |
| /* add all releases */ |
| for (guint j = 0; j < rels->len; j++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, j); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| g_node_append_data(child, g_object_ref(rel)); |
| } |
| } |
| |
| /* devices that have no updates available for whatever reason */ |
| if (devices_no_support->len > 0) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: message letting the user know no device |
| * upgrade available due to missing on LVFS */ |
| _("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); |
| fu_console_print(self->console, " • %s", fwupd_device_get_name(dev)); |
| } |
| } |
| if (devices_no_upgrades->len > 0) { |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: message letting the user know no device upgrade available */ |
| _("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); |
| fu_console_print(self->console, " • %s", fwupd_device_get_name(dev)); |
| } |
| } |
| |
| /* nag? */ |
| if (!fu_util_perhaps_show_unreported(self, error)) |
| return FALSE; |
| |
| /* no devices supported by LVFS or all are filtered */ |
| if (!supported) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: this is an error string */ |
| _("No updatable devices")); |
| return FALSE; |
| } |
| /* no updates available */ |
| if (g_node_n_nodes(root, G_TRAVERSE_ALL) <= 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: this is an error string */ |
| _("No updates available")); |
| return FALSE; |
| } |
| |
| fu_util_print_node(self->console, self->client, root); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_get_remotes(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FuUtilNode) root = g_node_new(NULL); |
| g_autoptr(GPtrArray) remotes = NULL; |
| |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, error); |
| if (remotes == NULL) |
| return FALSE; |
| if (self->as_json) { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| fwupd_codec_array_to_json(remotes, "Remotes", builder, FWUPD_CODEC_FLAG_TRUSTED); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| if (remotes->len == 0) { |
| /* TRANSLATORS: no repositories to download from */ |
| fu_console_print_literal(self->console, _("No remotes available")); |
| return TRUE; |
| } |
| |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote_tmp = g_ptr_array_index(remotes, i); |
| g_node_append_data(root, g_object_ref(remote_tmp)); |
| } |
| fu_util_print_node(self->console, self->client, root); |
| |
| return TRUE; |
| } |
| |
| static FwupdRelease * |
| fu_util_get_release_with_tag(FuUtil *self, FwupdDevice *dev, const gchar *host_bkc, GError **error) |
| { |
| g_autoptr(GPtrArray) rels = NULL; |
| g_auto(GStrv) host_bkcs = g_strsplit(host_bkc, ",", -1); |
| |
| /* find the newest release that matches */ |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rels == NULL) |
| return NULL; |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| for (guint j = 0; host_bkcs[j] != NULL; j++) { |
| if (fwupd_release_has_tag(rel, host_bkcs[j])) |
| return g_object_ref(rel); |
| } |
| } |
| |
| /* no match */ |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no matching releases for device"); |
| return NULL; |
| } |
| |
| static FwupdRelease * |
| fu_util_get_release_with_branch(FuUtil *self, FwupdDevice *dev, const gchar *branch, GError **error) |
| { |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| /* find the newest release that matches */ |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rels == NULL) |
| return NULL; |
| for (guint i = 0; i < rels->len; i++) { |
| FwupdRelease *rel = g_ptr_array_index(rels, i); |
| if (!fwupd_release_match_flags(rel, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| if (g_strcmp0(branch, fwupd_release_get_branch(rel)) == 0) |
| return g_object_ref(rel); |
| } |
| |
| /* no match */ |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no matching releases for device"); |
| return NULL; |
| } |
| |
| static gboolean |
| fu_util_prompt_warning_bkc(FuUtil *self, FwupdDevice *dev, FwupdRelease *rel, GError **error) |
| { |
| const gchar *host_bkc = fwupd_client_get_host_bkc(self->client); |
| g_autofree gchar *cmd = g_strdup_printf("%s sync", g_get_prgname()); |
| g_autoptr(FwupdRelease) rel_bkc = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GString) str = g_string_new(NULL); |
| |
| /* nothing to do */ |
| if (host_bkc == NULL) |
| return TRUE; |
| |
| /* get the release that corresponds with the host BKC */ |
| rel_bkc = fu_util_get_release_with_tag(self, dev, host_bkc, &error_local); |
| if (rel_bkc == NULL) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || |
| g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_debug("ignoring %s: %s", fwupd_device_get_id(dev), error_local->message); |
| return TRUE; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| |
| /* device is already on a different release */ |
| if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) != 0) |
| return TRUE; |
| |
| /* TRANSLATORS: BKC is the industry name for the best known configuration and is a set |
| * of firmware that works together */ |
| g_string_append_printf(str, _("Your system is set up to the BKC of %s."), host_bkc); |
| g_string_append(str, "\n\n"); |
| g_string_append_printf( |
| str, |
| /* TRANSLATORS: %1 is the current device version number, and %2 is the |
| command name, e.g. `fwupdmgr sync` */ |
| _("This device will be reverted back to %s when the %s command is performed."), |
| fwupd_release_get_version(rel), |
| cmd); |
| |
| fu_console_box( |
| self->console, |
| /* TRANSLATORS: the best known configuration is a set of software that we know works |
| * well together. In the OEM and ODM industries it is often called a BKC */ |
| _("Deviate from the best known configuration?"), |
| str->str, |
| 80); |
| |
| /* TRANSLATORS: prompt to apply the update */ |
| if (!fu_console_input_bool(self->console, TRUE, "%s", _("Perform operation?"))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Request canceled"); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_prompt_warning_composite(FuUtil *self, FwupdDevice *dev, FwupdRelease *rel, GError **error) |
| { |
| const gchar *rel_csum; |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* get the default checksum */ |
| rel_csum = fwupd_checksum_get_best(fwupd_release_get_checksums(rel)); |
| if (rel_csum == NULL) { |
| g_debug("no checksum for release!"); |
| return TRUE; |
| } |
| |
| /* find other devices matching the composite ID and the release checksum */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev_tmp = g_ptr_array_index(devices, i); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| /* not the parent device */ |
| if (g_strcmp0(fwupd_device_get_id(dev), fwupd_device_get_id(dev_tmp)) == 0) |
| continue; |
| |
| /* not the same composite device */ |
| if (g_strcmp0(fwupd_device_get_composite_id(dev), |
| fwupd_device_get_composite_id(dev_tmp)) != 0) |
| continue; |
| |
| /* get releases */ |
| if (!fwupd_device_has_flag(dev_tmp, FWUPD_DEVICE_FLAG_UPDATABLE)) |
| continue; |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev_tmp), |
| self->cancellable, |
| &error_local); |
| if (rels == NULL) { |
| g_debug("ignoring: %s", error_local->message); |
| continue; |
| } |
| |
| /* do any releases match this checksum */ |
| for (guint j = 0; j < rels->len; j++) { |
| FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); |
| g_autofree gchar *title = NULL; |
| gint vercmp; |
| |
| if (!fwupd_release_has_checksum(rel_tmp, rel_csum)) |
| continue; |
| vercmp = fu_version_compare(fwupd_release_get_version(rel_tmp), |
| fu_device_get_version(dev_tmp), |
| fwupd_device_get_version_format(dev_tmp)); |
| if ((self->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0 && vercmp == 0) |
| continue; |
| title = g_strdup_printf("%s %s", |
| fwupd_client_get_host_product(self->client), |
| fwupd_client_get_host_product(self->client)); |
| if (!fu_util_prompt_warning(self->console, dev_tmp, rel_tmp, title, error)) |
| return FALSE; |
| break; |
| } |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_update_device_with_release(FuUtil *self, |
| FwupdDevice *dev, |
| FwupdRelease *rel, |
| GError **error) |
| { |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) { |
| const gchar *name = fwupd_device_get_name(dev); |
| g_autofree gchar *str = NULL; |
| |
| /* TRANSLATORS: the device has a reason it can't update, e.g. laptop lid closed */ |
| str = g_strdup_printf(_("%s is not currently updatable"), name); |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "%s: %s", |
| str, |
| fwupd_device_get_update_error(dev)); |
| return FALSE; |
| } |
| if (!self->as_json && !self->no_safety_check && !self->assume_yes) { |
| const gchar *title = fwupd_client_get_host_product(self->client); |
| if (!fu_util_prompt_warning(self->console, dev, rel, title, error)) |
| return FALSE; |
| if (!fu_util_prompt_warning_fde(self->console, dev, error)) |
| return FALSE; |
| if (!fu_util_prompt_warning_composite(self, dev, rel, error)) |
| return FALSE; |
| if (!fu_util_prompt_warning_bkc(self, dev, rel, error)) |
| return FALSE; |
| } |
| return fwupd_client_install_release(self->client, |
| dev, |
| rel, |
| self->flags, |
| self->download_flags, |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_maybe_send_reports(FuUtil *self, FwupdRelease *rel, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| g_autoptr(GError) error_local = NULL; |
| if (fwupd_release_get_remote_id(rel) == NULL) { |
| g_debug("not sending reports, no remote"); |
| return TRUE; |
| } |
| remote = fwupd_client_get_remote_by_id(self->client, |
| fwupd_release_get_remote_id(rel), |
| self->cancellable, |
| error); |
| if (remote == NULL) |
| return FALSE; |
| if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) { |
| if (!fu_util_report_history(self, NULL, &error_local)) |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) |
| g_warning("%s", error_local->message); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_update(FuUtil *self, gchar **values, GError **error) |
| { |
| gboolean supported = FALSE; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_latest = g_ptr_array_new(); |
| g_autoptr(GPtrArray) devices_pending = g_ptr_array_new(); |
| g_autoptr(GPtrArray) devices_unsupported = g_ptr_array_new(); |
| |
| if (self->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 (self->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; |
| } |
| |
| /* 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; |
| } |
| } |
| |
| /* get devices from daemon */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| self->current_operation = FU_UTIL_OPERATION_UPDATE; |
| 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); |
| const gchar *device_id = fwupd_device_get_id(dev); |
| g_autoptr(FwupdRelease) rel = NULL; |
| g_autoptr(GPtrArray) rels = NULL; |
| g_autoptr(GError) error_install = NULL; |
| g_autoptr(GError) error_report = NULL; |
| gboolean dev_skip_byid = TRUE; |
| gboolean ret; |
| |
| /* not going to have results, so save a D-Bus round-trip */ |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && |
| !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) |
| continue; |
| if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { |
| g_ptr_array_add(devices_unsupported, dev); |
| continue; |
| } |
| |
| /* 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 (!fwupd_device_match_flags(dev, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| supported = TRUE; |
| |
| /* get the releases for this device and filter for validity */ |
| rels = fwupd_client_get_upgrades(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| &error_install); |
| if (rels == NULL) { |
| g_ptr_array_add(devices_latest, dev); |
| /* discard the actual reason from user, but leave for debugging */ |
| g_debug("%s", error_install->message); |
| continue; |
| } |
| for (guint j = 0; j < rels->len; j++) { |
| FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); |
| if (!fwupd_release_match_flags(rel_tmp, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| rel = g_object_ref(rel_tmp); |
| break; |
| } |
| if (rel == NULL) |
| continue; |
| |
| /* something is wrong */ |
| if (fwupd_device_get_problems(dev) != FWUPD_DEVICE_PROBLEM_NONE) { |
| g_ptr_array_add(devices_pending, dev); |
| continue; |
| } |
| |
| ret = fu_util_update_device_with_release(self, dev, rel, &error_install); |
| if (!ret && |
| g_error_matches(error_install, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_debug("ignoring %s: %s", |
| fwupd_device_get_id(dev), |
| error_install->message); |
| continue; |
| } |
| if (ret) |
| fu_util_display_current_message(self); |
| |
| /* send report if we're supposed to */ |
| if (!fu_util_maybe_send_reports(self, rel, &error_report)) { |
| /* install failed, report failed */ |
| if (!ret) { |
| g_warning("%s", error_report->message); |
| /* install succeeded, but report failed */ |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_report)); |
| return FALSE; |
| } |
| } |
| |
| if (!ret) { |
| g_propagate_error(error, g_steal_pointer(&error_install)); |
| return FALSE; |
| } |
| } |
| |
| /* show warnings */ |
| if (devices_latest->len > 0 && !self->as_json) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: message letting the user know no device |
| * upgrade available */ |
| _("Devices with the latest available firmware version:")); |
| for (guint i = 0; i < devices_latest->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices_latest, i); |
| fu_console_print(self->console, " • %s", fwupd_device_get_name(dev)); |
| } |
| } |
| if (devices_unsupported->len > 0 && !self->as_json) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: message letting the user know no |
| * device upgrade available due to missing on LVFS */ |
| _("Devices with no available firmware updates:")); |
| for (guint i = 0; i < devices_unsupported->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices_unsupported, i); |
| fu_console_print(self->console, " • %s", fwupd_device_get_name(dev)); |
| } |
| } |
| if (devices_pending->len > 0 && !self->as_json) { |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: message letting the user there is an update |
| * waiting, but there is a reason it cannot be deployed */ |
| _("Devices with firmware updates that need user action:")); |
| for (guint i = 0; i < devices_pending->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices_pending, i); |
| fu_console_print(self->console, " • %s", fwupd_device_get_name(dev)); |
| for (guint j = 0; j < 64; j++) { |
| FwupdDeviceProblem problem = (guint64)1 << j; |
| g_autofree gchar *desc = NULL; |
| if (!fwupd_device_has_problem(dev, problem)) |
| continue; |
| desc = fu_util_device_problem_to_string(self->client, dev, problem); |
| if (desc == NULL) |
| continue; |
| fu_console_print(self->console, " ‣ %s", desc); |
| } |
| } |
| } |
| |
| /* no devices supported by LVFS or all are filtered */ |
| if (!supported) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No updatable devices"); |
| return FALSE; |
| } |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check || self->as_json) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_remote_modify(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| if (g_strv_length(values) < 3) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* ensure the remote exists */ |
| remote = fwupd_client_get_remote_by_id(self->client, values[0], self->cancellable, error); |
| if (remote == NULL) |
| return FALSE; |
| if (!fwupd_client_modify_remote(self->client, |
| fwupd_remote_get_id(remote), |
| values[1], |
| values[2], |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: success message for a per-remote setting change */ |
| fu_console_print_literal(self->console, _("Successfully modified remote")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_remote_enable(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| remote = fwupd_client_get_remote_by_id(self->client, values[0], self->cancellable, error); |
| if (remote == NULL) |
| return FALSE; |
| if (!fu_util_modify_remote_warning(self->console, remote, self->assume_yes, error)) |
| return FALSE; |
| if (!fwupd_client_modify_remote(self->client, |
| fwupd_remote_get_id(remote), |
| "Enabled", |
| "true", |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* ask for permission to refresh */ |
| if (self->no_remote_check || fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) { |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully enabled remote")); |
| return TRUE; |
| } |
| if (!self->assume_yes) { |
| if (!fu_console_input_bool(self->console, |
| TRUE, |
| "%s (%s)", |
| /* TRANSLATORS: ask if we can update the metadata */ |
| _("Do you want to refresh this remote now?"), |
| /* TRANSLATORS: metadata is downloaded */ |
| _("Requires internet connection"))) { |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully enabled remote")); |
| return TRUE; |
| } |
| } |
| if (!fwupd_client_refresh_remote(self->client, |
| remote, |
| self->download_flags, |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully enabled and refreshed remote")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_remote_clean(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| remote = fwupd_client_get_remote_by_id(self->client, values[0], self->cancellable, error); |
| if (remote == NULL) |
| return FALSE; |
| if (!fwupd_client_clean_remote(self->client, |
| fwupd_remote_get_id(remote), |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully cleaned remote")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_remote_disable(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdRemote) remote = NULL; |
| |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* ensure the remote exists */ |
| remote = fwupd_client_get_remote_by_id(self->client, values[0], self->cancellable, error); |
| if (remote == NULL) |
| return FALSE; |
| if (!fwupd_client_modify_remote(self->client, |
| values[0], |
| "Enabled", |
| "false", |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: success message */ |
| fu_console_print_literal(self->console, _("Successfully disabled remote")); |
| |
| /* delete the now-unused cache files? */ |
| if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD && |
| fwupd_remote_get_age(remote) != G_MAXUINT64) { |
| if (self->assume_yes || |
| fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: this is now useless */ |
| _("Delete the now-unused remote cache files?"))) { |
| if (!fwupd_client_clean_remote(self->client, |
| values[0], |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: success message */ |
| _("Successfully cleaned remote")); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_downgrade(FuUtil *self, gchar **values, GError **error) |
| { |
| gboolean ret; |
| g_autoptr(FwupdDevice) dev = NULL; |
| g_autoptr(FwupdRelease) rel = NULL; |
| g_autoptr(GPtrArray) rels = NULL; |
| g_autoptr(GError) error_report = NULL; |
| |
| if (self->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; |
| } |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* get the releases for this device and filter for validity */ |
| rels = fwupd_client_get_downgrades(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rels == NULL) { |
| g_autofree gchar *downgrade_str = |
| /* TRANSLATORS: message letting the user know no device downgrade available |
| * %1 is the device name */ |
| g_strdup_printf(_("No downgrades for %s"), fwupd_device_get_name(dev)); |
| g_prefix_error(error, "%s: ", downgrade_str); /* nocheck:error */ |
| return FALSE; |
| } |
| |
| /* get the chosen release */ |
| rel = fu_util_prompt_for_release(self, rels, error); |
| if (rel == NULL) |
| return FALSE; |
| |
| /* update the console if composite devices are also updated */ |
| self->current_operation = FU_UTIL_OPERATION_DOWNGRADE; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; |
| ret = fu_util_update_device_with_release(self, dev, rel, error); |
| if (ret) |
| fu_util_display_current_message(self); |
| |
| /* send report if we're supposed to */ |
| if (!fu_util_maybe_send_reports(self, rel, &error_report)) { |
| /* install failed, report failed */ |
| if (!ret) { |
| g_warning("%s", error_report->message); |
| /* install succeeded, but report failed */ |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_report)); |
| return FALSE; |
| } |
| } |
| |
| /* install failed */ |
| if (!ret) |
| return FALSE; |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_reinstall(FuUtil *self, gchar **values, GError **error) |
| { |
| gboolean ret; |
| g_autoptr(FwupdRelease) rel = NULL; |
| g_autoptr(FwupdDevice) dev = NULL; |
| g_autoptr(GError) error_report = NULL; |
| |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* try to lookup/match release from client */ |
| rel = |
| fu_util_get_release_for_device_version(self, dev, fwupd_device_get_version(dev), error); |
| if (rel == NULL) |
| return FALSE; |
| |
| /* update the console if composite devices are also updated */ |
| self->current_operation = FU_UTIL_OPERATION_INSTALL; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; |
| ret = fu_util_update_device_with_release(self, dev, rel, error); |
| if (ret) |
| fu_util_display_current_message(self); |
| |
| /* send report if we're supposed to */ |
| if (!fu_util_maybe_send_reports(self, rel, &error_report)) { |
| /* install failed, report failed */ |
| if (!ret) { |
| g_warning("%s", error_report->message); |
| /* install succeeded, but report failed */ |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_report)); |
| return FALSE; |
| } |
| } |
| |
| /* install failed */ |
| if (!ret) |
| return FALSE; |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_install(FuUtil *self, gchar **values, GError **error) |
| { |
| gboolean ret; |
| g_autoptr(FwupdDevice) dev = NULL; |
| g_autoptr(FwupdRelease) rel = NULL; |
| g_autoptr(GError) error_report = NULL; |
| |
| /* fall back for CLI compatibility */ |
| if (g_strv_length(values) >= 1) { |
| if (g_file_test(values[0], G_FILE_TEST_EXISTS) || fu_util_is_url(values[0])) |
| return fu_util_local_install(self, values, error); |
| } |
| |
| /* find device */ |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* find release */ |
| if (g_strv_length(values) >= 2) { |
| rel = fu_util_get_release_for_device_version(self, dev, values[1], error); |
| if (rel == NULL) |
| return FALSE; |
| } else { |
| g_autoptr(GPtrArray) rels = NULL; |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| error); |
| if (rels == NULL) |
| return FALSE; |
| rel = fu_util_prompt_for_release(self, rels, error); |
| if (rel == NULL) |
| return FALSE; |
| } |
| |
| /* allow all actions */ |
| self->current_operation = FU_UTIL_OPERATION_INSTALL; |
| ret = fu_util_update_device_with_release(self, dev, rel, error); |
| if (ret) |
| fu_util_display_current_message(self); |
| |
| /* send report if we're supposed to */ |
| if (!fu_util_maybe_send_reports(self, rel, &error_report)) { |
| /* install failed, report failed */ |
| if (!ret) { |
| g_warning("%s", error_report->message); |
| /* install succeeded, but report failed */ |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_report)); |
| return FALSE; |
| } |
| } |
| |
| /* install failed */ |
| if (!ret) |
| return FALSE; |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| _g_str_equal0(gconstpointer str1, gconstpointer str2) |
| { |
| return g_strcmp0(str1, str2) == 0; |
| } |
| |
| static gboolean |
| fu_util_switch_branch(FuUtil *self, gchar **values, GError **error) |
| { |
| const gchar *branch; |
| gboolean ret; |
| 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(FwupdDevice) dev = NULL; |
| g_autoptr(GError) error_report = NULL; |
| |
| /* find the device and check it has multiple branches */ |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_UPDATABLE; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* get all releases, including the alternate branch versions */ |
| rels = fwupd_client_get_releases(self->client, |
| fwupd_device_get_id(dev), |
| self->cancellable, |
| 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 (!fwupd_release_match_flags(rel_tmp, |
| self->filter_release_include, |
| self->filter_release_exclude)) |
| continue; |
| if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL)) |
| continue; |
| 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: this is to abort the interactive prompt */ |
| fu_console_print(self->console, "0.\t%s", _("Cancel")); |
| for (guint i = 0; i < branches->len; i++) { |
| const gchar *branch_tmp = g_ptr_array_index(branches, i); |
| fu_console_print(self->console, |
| "%u.\t%s", |
| i + 1, |
| fu_util_branch_for_display(branch_tmp)); |
| } |
| /* TRANSLATORS: get interactive prompt, where branch is the |
| * supplier of the firmware, e.g. "non-free" or "free" */ |
| idx = fu_console_input_uint(self->console, branches->len, "%s", _("Choose branch")); |
| 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, fwupd_device_get_branch(dev)) == 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "Device %s is already on branch %s", |
| fwupd_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(self->console, dev, rel, self->assume_yes, error)) |
| return FALSE; |
| |
| /* update the console if composite devices are also updated */ |
| self->current_operation = FU_UTIL_OPERATION_INSTALL; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; |
| ret = fu_util_update_device_with_release(self, dev, rel, error); |
| if (ret) |
| fu_util_display_current_message(self); |
| |
| /* send report if we're supposed to */ |
| if (!fu_util_maybe_send_reports(self, rel, &error_report)) { |
| /* install failed, report failed */ |
| if (!ret) { |
| g_warning("%s", error_report->message); |
| /* install succeeded, but report failed */ |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_report)); |
| return FALSE; |
| } |
| } |
| |
| /* install failed */ |
| if (!ret) |
| return FALSE; |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_activate(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| gboolean has_pending = FALSE; |
| |
| /* handle both forms */ |
| if (g_strv_length(values) == 0) { |
| /* activate anything with _NEEDS_ACTIVATION */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { |
| has_pending = TRUE; |
| break; |
| } |
| } |
| } else if (g_strv_length(values) == 1) { |
| FwupdDevice *device = fu_util_get_device_by_id(self, 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); |
| if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) |
| has_pending = TRUE; |
| } else { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments"); |
| return FALSE; |
| } |
| |
| /* nothing to do */ |
| if (!has_pending) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No firmware to activate"); |
| return FALSE; |
| } |
| |
| /* activate anything with _NEEDS_ACTIVATION */ |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| if (!fwupd_device_match_flags(device, |
| self->filter_device_include, |
| self->filter_device_exclude)) |
| continue; |
| if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) |
| continue; |
| fu_console_print( |
| self->console, |
| "%s %s…", |
| /* TRANSLATORS: shown when shutting down to switch to the new version */ |
| _("Activating firmware update for"), |
| fwupd_device_get_name(device)); |
| if (!fwupd_client_activate(self->client, |
| self->cancellable, |
| fwupd_device_get_id(device), |
| error)) |
| return FALSE; |
| } |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: success message -- where activation is making the new |
| * firmware take effect, usually after updating offline */ |
| fu_console_print_literal(self->console, _("Successfully activated all devices")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_set_approved_firmware(FuUtil *self, gchar **values, GError **error) |
| { |
| g_auto(GStrv) checksums = NULL; |
| |
| /* check args */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments: filename or list of checksums expected"); |
| return FALSE; |
| } |
| |
| /* filename */ |
| if (g_file_test(values[0], G_FILE_TEST_EXISTS)) { |
| g_autofree gchar *data = NULL; |
| if (!g_file_get_contents(values[0], &data, NULL, error)) |
| return FALSE; |
| checksums = g_strsplit(data, "\n", -1); |
| } else { |
| checksums = g_strsplit(values[0], ",", -1); |
| } |
| |
| /* call into daemon */ |
| return fwupd_client_set_approved_firmware(self->client, |
| checksums, |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_get_checksums_as_json(FuUtil *self, gchar **csums, GError **error) |
| { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| json_builder_set_member_name(builder, "Checksums"); |
| json_builder_begin_array(builder); |
| for (guint i = 0; csums[i] != NULL; i++) |
| json_builder_add_string_value(builder, csums[i]); |
| json_builder_end_array(builder); |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_get_approved_firmware(FuUtil *self, gchar **values, GError **error) |
| { |
| g_auto(GStrv) checksums = NULL; |
| |
| /* check args */ |
| if (g_strv_length(values) != 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments: none expected"); |
| return FALSE; |
| } |
| |
| /* call into daemon */ |
| checksums = fwupd_client_get_approved_firmware(self->client, self->cancellable, error); |
| if (checksums == NULL) |
| return FALSE; |
| if (self->as_json) |
| return fu_util_get_checksums_as_json(self, checksums, error); |
| if (g_strv_length(checksums) == 0) { |
| /* TRANSLATORS: approved firmware has been checked by |
| * the domain administrator */ |
| fu_console_print_literal(self->console, _("There is no approved firmware.")); |
| } else { |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: approved firmware has been checked by |
| * the domain administrator */ |
| ngettext("Approved firmware:", "Approved firmware:", g_strv_length(checksums))); |
| for (guint i = 0; checksums[i] != NULL; i++) |
| fu_console_print(self->console, " * %s", checksums[i]); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_modify_config(FuUtil *self, gchar **values, GError **error) |
| { |
| /* check args */ |
| if (g_strv_length(values) == 3) { |
| if (!fwupd_client_modify_config(self->client, |
| values[0], |
| values[1], |
| values[2], |
| self->cancellable, |
| error)) |
| return FALSE; |
| } else if (g_strv_length(values) == 2) { |
| if (!fwupd_client_modify_config(self->client, |
| "fwupd", |
| values[0], |
| values[1], |
| self->cancellable, |
| error)) |
| return FALSE; |
| } else { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments: [SECTION] KEY VALUE expected"); |
| return FALSE; |
| } |
| |
| if (self->as_json) |
| return TRUE; |
| |
| if (!self->assume_yes) { |
| if (!fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: changes only take effect on restart */ |
| _("Restart the daemon to make the change effective?"))) |
| return TRUE; |
| } |
| |
| if (!fu_util_quit(self, NULL, error)) |
| return FALSE; |
| if (!fwupd_client_connect(self->client, self->cancellable, error)) |
| return FALSE; |
| |
| /* TRANSLATORS: success message -- a per-system setting value */ |
| fu_console_print_literal(self->console, _("Successfully modified configuration value")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_reset_config(FuUtil *self, gchar **values, GError **error) |
| { |
| /* check args */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments: SECTION expected"); |
| return FALSE; |
| } |
| if (!fwupd_client_reset_config(self->client, values[0], self->cancellable, error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| if (!self->assume_yes) { |
| if (!fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: changes only take effect on restart */ |
| _("Restart the daemon to make the change effective?"))) |
| return TRUE; |
| } |
| if (!fu_util_quit(self, NULL, error)) |
| return FALSE; |
| if (!fwupd_client_connect(self->client, self->cancellable, error)) |
| return FALSE; |
| |
| /* TRANSLATORS: success message -- a per-system setting value */ |
| fu_console_print_literal(self->console, _("Successfully reset configuration values")); |
| return TRUE; |
| } |
| |
| static FwupdRemote * |
| fu_util_get_remote_with_report_uri(FuUtil *self, GError **error) |
| { |
| g_autoptr(GPtrArray) remotes = NULL; |
| |
| /* get all remotes */ |
| remotes = fwupd_client_get_remotes(self->client, self->cancellable, error); |
| if (remotes == NULL) |
| return NULL; |
| |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) |
| continue; |
| if (fwupd_remote_get_report_uri(remote) != NULL) |
| return g_object_ref(remote); |
| } |
| |
| /* failed */ |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "No remotes specified ReportURI"); |
| return NULL; |
| } |
| |
| static gboolean |
| fu_util_upload_security(FuUtil *self, GPtrArray *attrs, GError **error) |
| { |
| g_autofree gchar *data = NULL; |
| g_autofree gchar *report_uri = NULL; |
| g_autofree gchar *sig = NULL; |
| g_autofree gchar *uri = NULL; |
| g_autoptr(FwupdRemote) remote = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GHashTable) metadata = NULL; |
| |
| /* can we find a remote with a security attr */ |
| remote = fu_util_get_remote_with_report_uri(self, &error_local); |
| if (remote == NULL) { |
| g_debug("failed to find suitable remote: %s", error_local->message); |
| return TRUE; |
| } |
| |
| /* export as a string */ |
| metadata = fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (metadata == NULL) |
| return FALSE; |
| data = fwupd_client_build_report_security(self->client, attrs, metadata, error); |
| if (data == NULL) |
| return FALSE; |
| |
| /* ask for permission */ |
| if (!self->assume_yes && |
| !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { |
| if (!fu_console_input_bool(self->console, |
| FALSE, |
| /* TRANSLATORS: ask the user to share, %s is something |
| * like: "Linux Vendor Firmware Service" */ |
| _("Upload these anonymous results to the %s to help " |
| "other users?"), |
| fwupd_remote_get_title(remote))) { |
| return TRUE; |
| } |
| } |
| |
| /* self sign data */ |
| if (self->sign) { |
| sig = fwupd_client_self_sign(self->client, |
| data, |
| FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, |
| self->cancellable, |
| error); |
| if (sig == NULL) |
| return FALSE; |
| } |
| |
| /* ask for permission */ |
| if (!self->assume_yes && |
| !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { |
| fu_console_print_kv(self->console, |
| _("Target"), |
| fwupd_remote_get_report_uri(remote)); |
| fu_console_print_kv(self->console, _("Payload"), data); |
| if (sig != NULL) |
| fu_console_print_kv(self->console, _("Signature"), sig); |
| if (!fu_console_input_bool(self->console, TRUE, "%s", _("Proceed with upload?"))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_PERMISSION_DENIED, |
| "User declined action"); |
| return FALSE; |
| } |
| } |
| |
| /* POST request */ |
| report_uri = fwupd_remote_build_report_uri(remote, error); |
| if (report_uri == NULL) |
| return FALSE; |
| uri = fwupd_client_upload_report(self->client, |
| report_uri, |
| data, |
| sig, |
| FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART, |
| self->cancellable, |
| error); |
| if (uri == NULL) |
| return FALSE; |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: success, so say thank you to the user */ |
| _("Host Security ID attributes uploaded successfully, thanks!")); |
| |
| /* as this worked, ask if the user want to do this every time */ |
| if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { |
| if (fu_console_input_bool(self->console, |
| FALSE, |
| "%s", |
| /* TRANSLATORS: can we JFDI? */ |
| _("Automatically upload every time?"))) { |
| if (!fwupd_client_modify_remote(self->client, |
| fwupd_remote_get_id(remote), |
| "AutomaticSecurityReports", |
| "true", |
| self->cancellable, |
| error)) |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_security_as_json(FuUtil *self, |
| GPtrArray *attrs, |
| GPtrArray *events, |
| GPtrArray *devices, |
| GError **error) |
| { |
| g_autoptr(GPtrArray) devices_issues = NULL; |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| |
| json_builder_begin_object(builder); |
| |
| /* attrs */ |
| fwupd_codec_array_to_json(attrs, "SecurityAttributes", builder, FWUPD_CODEC_FLAG_TRUSTED); |
| |
| /* events */ |
| if (events != NULL && events->len > 0) { |
| fwupd_codec_array_to_json(events, |
| "SecurityEvents", |
| builder, |
| FWUPD_CODEC_FLAG_TRUSTED); |
| } |
| |
| /* devices */ |
| devices_issues = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; devices != NULL && i < devices->len; i++) { |
| FwupdDevice *device = g_ptr_array_index(devices, i); |
| GPtrArray *issues = fwupd_device_get_issues(device); |
| if (issues->len == 0) |
| continue; |
| g_ptr_array_add(devices_issues, g_object_ref(device)); |
| } |
| if (devices_issues->len > 0) { |
| fwupd_codec_array_to_json(devices_issues, |
| "Devices", |
| builder, |
| FWUPD_CODEC_FLAG_TRUSTED); |
| } |
| |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_sync(FuUtil *self, gchar **values, GError **error) |
| { |
| const gchar *host_bkc = fwupd_client_get_host_bkc(self->client); |
| guint cnt = 0; |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* update the console if composite devices are also updated */ |
| self->current_operation = FU_UTIL_OPERATION_INSTALL; |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; |
| |
| devices = fwupd_client_get_devices(self->client, NULL, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FwupdDevice *dev = g_ptr_array_index(devices, i); |
| g_autoptr(FwupdRelease) rel = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| /* find the release that matches the tag */ |
| if (host_bkc != NULL) { |
| rel = fu_util_get_release_with_tag(self, dev, host_bkc, &error_local); |
| } else if (fu_device_get_branch(dev) != NULL) { |
| rel = fu_util_get_release_with_branch(self, |
| dev, |
| fu_device_get_branch(dev), |
| &error_local); |
| } else { |
| g_set_error_literal(&error_local, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "No device branch or system HostBkc set"); |
| /* nocheck:error-false-return */ |
| } |
| if (rel == NULL) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || |
| g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_debug("ignoring %s: %s", |
| fwupd_device_get_id(dev), |
| error_local->message); |
| continue; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| |
| /* ignore if already on that release */ |
| if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) == 0) |
| continue; |
| |
| /* install this new release */ |
| g_debug("need to move %s from %s to %s", |
| fwupd_device_get_id(dev), |
| fwupd_device_get_version(dev), |
| fwupd_release_get_version(rel)); |
| if (!fu_util_update_device_with_release(self, dev, rel, &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_debug("ignoring %s: %s", |
| fwupd_device_get_id(dev), |
| error_local->message); |
| continue; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| fu_util_display_current_message(self); |
| cnt++; |
| } |
| |
| /* nothing was done */ |
| if (cnt == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No devices required modification"); |
| return FALSE; |
| } |
| |
| /* we don't want to ask anything */ |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| /* show reboot if needed */ |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_security_fix_attr(FuUtil *self, FwupdSecurityAttr *attr, GError **error) |
| { |
| g_autoptr(GString) body = g_string_new(NULL); |
| g_autoptr(GString) title = g_string_new(NULL); |
| |
| g_string_append_printf(title, |
| "%s: %s", |
| /* TRANSLATORS: title prefix for the BIOS settings dialog */ |
| _("Configuration Change Suggested"), |
| fwupd_security_attr_get_title(attr)); |
| |
| g_string_append(body, fwupd_security_attr_get_description(attr)); |
| |
| if (fwupd_security_attr_get_bios_setting_id(attr) != NULL && |
| fwupd_security_attr_get_bios_setting_current_value(attr) != NULL && |
| fwupd_security_attr_get_bios_setting_target_value(attr) != NULL) { |
| g_string_append(body, "\n\n"); |
| g_string_append_printf(body, |
| /* TRANSLATORS: the %1 is a BIOS setting name. |
| * %2 and %3 are the values, e.g. "True" or "Windows10" */ |
| _("This tool can change the BIOS setting '%s' from '%s' " |
| "to '%s' automatically, but it will only be active after " |
| "restarting the computer."), |
| fwupd_security_attr_get_bios_setting_id(attr), |
| fwupd_security_attr_get_bios_setting_current_value(attr), |
| fwupd_security_attr_get_bios_setting_target_value(attr)); |
| g_string_append(body, "\n\n"); |
| g_string_append(body, |
| /* TRANSLATORS: the user has to manually recover; we can't do it */ |
| _("You should ensure you are comfortable restoring the setting " |
| "from the system firmware setup, as this change may cause the " |
| "system to not boot into Linux or cause other system " |
| "instability.")); |
| } else if (fwupd_security_attr_get_kernel_target_value(attr) != NULL) { |
| g_string_append(body, "\n\n"); |
| if (fwupd_security_attr_get_kernel_current_value(attr) != NULL) { |
| g_string_append_printf( |
| body, |
| /* TRANSLATORS: the %1 is a kernel command line key=value */ |
| _("This tool can change the kernel argument from '%s' to '%s', but " |
| "it will only be active after restarting the computer."), |
| fwupd_security_attr_get_kernel_current_value(attr), |
| fwupd_security_attr_get_kernel_target_value(attr)); |
| } else { |
| g_string_append_printf( |
| body, |
| /* TRANSLATORS: the %1 is a kernel command line key=value */ |
| _("This tool can add a kernel argument of '%s', but it will " |
| "only be active after restarting the computer."), |
| fwupd_security_attr_get_kernel_target_value(attr)); |
| } |
| g_string_append(body, "\n\n"); |
| g_string_append(body, |
| /* TRANSLATORS: the user has to manually recover; we can't do it */ |
| _("You should ensure you are comfortable restoring the setting " |
| "from a recovery or installation disk, as this change may cause " |
| "the system to not boot into Linux or cause other system " |
| "instability.")); |
| } |
| |
| fu_console_box(self->console, title->str, body->str, 80); |
| |
| /* TRANSLATORS: prompt to apply the update */ |
| if (!fu_console_input_bool(self->console, FALSE, "%s", _("Perform operation?"))) |
| return TRUE; |
| if (!fwupd_client_fix_host_security_attr(self->client, |
| fwupd_security_attr_get_appstream_id(attr), |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| /* do not offer to upload the report */ |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_security(FuUtil *self, gchar **values, GError **error) |
| { |
| FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE; |
| g_autoptr(GPtrArray) attrs = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) events = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autofree gchar *str = NULL; |
| |
| #ifndef HAVE_HSI |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| /* TRANSLATORS: error message for unsupported feature */ |
| _("Host Security ID (HSI) is not supported")); |
| return FALSE; |
| #endif /* HAVE_HSI */ |
| |
| /* the "why" */ |
| attrs = fwupd_client_get_host_security_attrs(self->client, self->cancellable, error); |
| if (attrs == NULL) |
| return FALSE; |
| |
| /* the "when" */ |
| events = fwupd_client_get_host_security_events(self->client, |
| 10, |
| self->cancellable, |
| &error_local); |
| if (events == NULL) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_debug("ignoring failed events: %s", error_local->message); |
| } else { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| } |
| |
| /* the "also" */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, &error_local); |
| if (devices == NULL) { |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| } |
| |
| /* not for human consumption */ |
| if (self->as_json) |
| return fu_util_security_as_json(self, attrs, events, devices, error); |
| |
| fu_console_print(self->console, |
| "%s \033[1m%s\033[0m", |
| /* TRANSLATORS: this is a string like 'HSI:2-U' */ |
| _("Host Security ID:"), |
| fwupd_client_get_host_security_id(self->client)); |
| |
| /* show or hide different elements */ |
| if (self->show_all) { |
| flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES; |
| flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS; |
| } |
| str = fu_util_security_attrs_to_string(attrs, flags); |
| fu_console_print_literal(self->console, str); |
| |
| /* events */ |
| if (events != NULL && events->len > 0) { |
| g_autofree gchar *estr = fu_util_security_events_to_string(events, flags); |
| if (estr != NULL) |
| fu_console_print_literal(self->console, estr); |
| } |
| |
| /* known CVEs */ |
| if (devices != NULL && devices->len > 0) { |
| g_autofree gchar *estr = fu_util_security_issues_to_string(devices); |
| if (estr != NULL) |
| fu_console_print_literal(self->console, estr); |
| } |
| |
| /* host emulation */ |
| for (guint j = 0; j < attrs->len; j++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(attrs, j); |
| if (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), |
| FWUPD_SECURITY_ATTR_ID_HOST_EMULATION) == 0) { |
| self->no_unreported_check = TRUE; |
| break; |
| } |
| } |
| |
| /* any things we can fix? */ |
| if (!self->no_security_fix) { |
| for (guint j = 0; j < attrs->len; j++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(attrs, j); |
| if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX) && |
| !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { |
| if (!fu_util_security_fix_attr(self, attr, error)) |
| return FALSE; |
| } |
| } |
| } |
| |
| /* upload, with confirmation */ |
| if (!self->no_unreported_check) { |
| if (!fu_util_upload_security(self, attrs, error)) |
| return FALSE; |
| } |
| |
| /* reboot is required? */ |
| if (!self->no_reboot_check && |
| (self->completion_flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) > 0) { |
| if (!fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error)) |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_util_ignore_cb(const gchar *log_domain, |
| GLogLevelFlags log_level, |
| const gchar *message, |
| gpointer user_data) |
| { |
| } |
| |
| #ifdef HAVE_GIO_UNIX |
| static gboolean |
| fu_util_sigint_cb(gpointer user_data) |
| { |
| FuUtil *self = (FuUtil *)user_data; |
| g_debug("handling SIGINT"); |
| g_cancellable_cancel(self->cancellable); |
| return FALSE; |
| } |
| #endif |
| |
| static void |
| fu_util_setup_signal_handlers(FuUtil *self) |
| { |
| #ifdef HAVE_GIO_UNIX |
| g_autoptr(GSource) source = g_unix_signal_source_new(SIGINT); |
| g_source_set_callback(source, fu_util_sigint_cb, self, NULL); |
| g_source_attach(g_steal_pointer(&source), self->main_ctx); |
| #endif |
| } |
| |
| static void |
| fu_util_private_free(FuUtil *self) |
| { |
| if (self->client != NULL) { |
| /* when destroying GDBusProxy in a custom GMainContext, the context must be |
| * iterated enough after finalization of the proxies that any pending D-Bus traffic |
| * can be freed */ |
| fwupd_client_disconnect(self->client, NULL); |
| while (g_main_context_iteration(self->main_ctx, FALSE)) { |
| /* nothing needs to be done here */ |
| }; |
| g_object_unref(self->client); |
| } |
| if (self->current_device != NULL) |
| g_object_unref(self->current_device); |
| g_ptr_array_unref(self->post_requests); |
| g_main_loop_unref(self->loop); |
| g_main_context_unref(self->main_ctx); |
| g_object_unref(self->cancellable); |
| g_object_unref(self->console); |
| g_option_context_free(self->context); |
| g_free(self); |
| } |
| |
| static gboolean |
| fu_util_check_daemon_version(FuUtil *self, GError **error) |
| { |
| const gchar *daemon = fwupd_client_get_daemon_version(self->client); |
| |
| if (daemon == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| /* TRANSLATORS: error message */ |
| _("Unable to connect to service")); |
| return FALSE; |
| } |
| |
| if (g_strcmp0(daemon, PACKAGE_VERSION) != 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| /* TRANSLATORS: error message */ |
| _("Unsupported daemon version %s, client version is %s"), |
| daemon, |
| PACKAGE_VERSION); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_check_polkit_actions(GError **error) |
| { |
| #ifdef HAVE_POLKIT |
| g_autofree gchar *directory = NULL; |
| g_autofree gchar *filename = NULL; |
| |
| if (g_getenv("FWUPD_POLKIT_NOCHECK") != NULL) |
| return TRUE; |
| |
| directory = fu_path_from_kind(FU_PATH_KIND_POLKIT_ACTIONS); |
| filename = g_build_filename(directory, "org.freedesktop.fwupd.policy", NULL); |
| if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { |
| g_set_error_literal( |
| error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_AUTH_FAILED, |
| "PolicyKit files are missing, see " |
| "https://github.com/fwupd/fwupd/wiki/PolicyKit-files-are-missing"); |
| return FALSE; |
| } |
| #endif |
| |
| return TRUE; |
| } |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wunused-function" |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtil, fu_util_private_free) |
| #pragma clang diagnostic pop |
| |
| static gchar * |
| fu_util_get_history_checksum(FuUtil *self, GError **error) |
| { |
| const gchar *csum; |
| g_autoptr(FwupdDevice) device = NULL; |
| g_autoptr(FwupdRelease) release = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| devices = fwupd_client_get_history(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return NULL; |
| device = fu_util_prompt_for_device(self, devices, error); |
| if (device == NULL) |
| return NULL; |
| release = fu_util_prompt_for_release(self, fwupd_device_get_releases(device), error); |
| if (release == NULL) |
| return NULL; |
| csum = fwupd_checksum_get_best(fwupd_release_get_checksums(release)); |
| if (csum == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No suitable checksums"); |
| return NULL; |
| } |
| return g_strdup(csum); |
| } |
| |
| static gboolean |
| fu_util_block_firmware(FuUtil *self, gchar **values, GError **error) |
| { |
| guint idx = 0; |
| g_autofree gchar *csum = NULL; |
| g_auto(GStrv) csums_new = NULL; |
| g_auto(GStrv) csums = NULL; |
| |
| /* get existing checksums */ |
| csums = fwupd_client_get_blocked_firmware(self->client, self->cancellable, error); |
| if (csums == NULL) |
| return FALSE; |
| |
| /* get new value */ |
| if (g_strv_length(values) == 0) { |
| csum = fu_util_get_history_checksum(self, error); |
| if (csum == NULL) |
| return FALSE; |
| } else { |
| csum = g_strdup(values[0]); |
| } |
| |
| /* ensure it's not already there */ |
| if (g_strv_contains((const gchar *const *)csums, csum)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: user selected something not possible */ |
| _("Firmware is already blocked")); |
| return FALSE; |
| } |
| |
| /* TRANSLATORS: we will not offer this firmware to the user */ |
| fu_console_print(self->console, "%s %s", _("Blocking firmware:"), csum); |
| |
| /* remove it from the new list */ |
| csums_new = g_new0(gchar *, g_strv_length(csums) + 2); |
| for (guint i = 0; csums[i] != NULL; i++) { |
| if (g_strcmp0(csums[i], csum) != 0) |
| csums_new[idx++] = g_strdup(csums[i]); |
| } |
| csums_new[idx] = g_strdup(csum); |
| return fwupd_client_set_blocked_firmware(self->client, csums_new, self->cancellable, error); |
| } |
| |
| static gboolean |
| fu_util_unblock_firmware(FuUtil *self, gchar **values, GError **error) |
| { |
| guint idx = 0; |
| g_auto(GStrv) csums = NULL; |
| g_auto(GStrv) csums_new = NULL; |
| g_autofree gchar *csum = NULL; |
| |
| /* get existing checksums */ |
| csums = fwupd_client_get_blocked_firmware(self->client, self->cancellable, error); |
| if (csums == NULL) |
| return FALSE; |
| |
| /* empty list */ |
| if (g_strv_length(csums) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: nothing to show */ |
| _("There are no blocked firmware files")); |
| return FALSE; |
| } |
| |
| /* get new value */ |
| if (g_strv_length(values) == 0) { |
| csum = fu_util_get_history_checksum(self, error); |
| if (csum == NULL) |
| return FALSE; |
| } else { |
| csum = g_strdup(values[0]); |
| } |
| |
| /* ensure it's there */ |
| if (!g_strv_contains((const gchar *const *)csums, csum)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| /* TRANSLATORS: user selected something not possible */ |
| _("Firmware is not already blocked")); |
| return FALSE; |
| } |
| |
| /* TRANSLATORS: we will now offer this firmware to the user */ |
| fu_console_print(self->console, "%s %s", _("Unblocking firmware:"), csum); |
| |
| /* remove it from the new list */ |
| csums_new = g_new0(gchar *, g_strv_length(csums)); |
| for (guint i = 0; csums[i] != NULL; i++) { |
| if (g_strcmp0(csums[i], csum) != 0) |
| csums_new[idx++] = g_strdup(csums[i]); |
| } |
| return fwupd_client_set_blocked_firmware(self->client, csums_new, self->cancellable, error); |
| } |
| |
| static gboolean |
| fu_util_get_blocked_firmware(FuUtil *self, gchar **values, GError **error) |
| { |
| g_auto(GStrv) csums = NULL; |
| |
| /* get checksums */ |
| csums = fwupd_client_get_blocked_firmware(self->client, self->cancellable, error); |
| if (csums == NULL) |
| return FALSE; |
| if (self->as_json) |
| return fu_util_get_checksums_as_json(self, csums, error); |
| |
| /* empty list */ |
| if (g_strv_length(csums) == 0) { |
| /* TRANSLATORS: nothing to show */ |
| fu_console_print_literal(self->console, _("There are no blocked firmware files")); |
| return TRUE; |
| } |
| |
| /* TRANSLATORS: there follows a list of hashes */ |
| fu_console_print_literal(self->console, _("Blocked firmware files:")); |
| for (guint i = 0; csums[i] != NULL; i++) { |
| fu_console_print(self->console, "%u.\t%s", i + 1, csums[i]); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_util_show_plugin_warnings(FuUtil *self) |
| { |
| FwupdPluginFlags flags = FWUPD_PLUGIN_FLAG_NONE; |
| g_autoptr(GPtrArray) plugins = NULL; |
| |
| if (self->as_json) |
| return; |
| |
| /* get plugins from daemon, ignoring if the daemon is too old */ |
| plugins = fwupd_client_get_plugins(self->client, self->cancellable, NULL); |
| if (plugins == NULL) |
| return; |
| |
| /* get a superset so we do not show the same message more than once */ |
| 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; |
| flags &= ~FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; |
| flags &= ~FWUPD_PLUGIN_FLAG_READY; |
| |
| /* print */ |
| for (guint i = 0; i < 64; i++) { |
| FwupdPluginFlags flag = (guint64)1 << i; |
| g_autofree gchar *tmp = 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(flag); |
| if (tmp == NULL) |
| continue; |
| g_string_append_printf(str, "%s\n", tmp); |
| url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s", |
| fwupd_plugin_flag_to_string(flag)); |
| /* TRANSLATORS: %s is a link to a website */ |
| g_string_append_printf(str, _("See %s for more information."), url); |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_WARNING, |
| "%s\n", |
| str->str); |
| } |
| } |
| |
| static gboolean |
| fu_util_set_bios_setting(FuUtil *self, gchar **input, GError **error) |
| { |
| g_autoptr(GHashTable) settings = NULL; |
| |
| settings = fu_util_bios_settings_parse_argv(input, error); |
| if (settings == NULL) |
| return FALSE; |
| if (!fwupd_client_modify_bios_setting(self->client, settings, self->cancellable, error)) { |
| g_prefix_error_literal(error, "failed to set BIOS setting: "); |
| return FALSE; |
| } |
| |
| if (!self->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); |
| fu_console_print_literal(self->console, msg); |
| } |
| } |
| self->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; |
| |
| if (self->no_reboot_check) { |
| g_debug("skipping reboot check"); |
| return TRUE; |
| } |
| |
| return fu_util_prompt_complete(self->console, self->completion_flags, TRUE, error); |
| } |
| |
| static gboolean |
| fu_util_get_bios_setting(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(GPtrArray) attrs = NULL; |
| gboolean found = FALSE; |
| |
| attrs = fwupd_client_get_bios_settings(self->client, self->cancellable, error); |
| if (attrs == NULL) |
| return FALSE; |
| if (self->as_json) |
| return fu_util_bios_setting_console_print(self->console, values, attrs, error); |
| |
| for (guint i = 0; i < attrs->len; i++) { |
| FwupdBiosSetting *attr = g_ptr_array_index(attrs, i); |
| if (fu_util_bios_setting_matches_args(attr, values)) { |
| g_autofree gchar *tmp = fu_util_bios_setting_to_string(attr, 0); |
| fu_console_print_literal(self->console, tmp); |
| found = TRUE; |
| } |
| } |
| if (attrs->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_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| /* TRANSLATORS: error message */ |
| _("Unable to find attribute")); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_security_fix(FuUtil *self, gchar **values, GError **error) |
| { |
| #ifndef HAVE_HSI |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| /* TRANSLATORS: error message for unsupported feature */ |
| _("Host Security ID (HSI) is not supported")); |
| return FALSE; |
| #endif /* HAVE_HSI */ |
| |
| if (g_strv_length(values) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| /* TRANSLATOR: This is the error message for |
| * incorrect parameter */ |
| _("Invalid arguments, expected an AppStream ID")); |
| return FALSE; |
| } |
| if (!fwupd_client_fix_host_security_attr(self->client, values[0], self->cancellable, error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: we've fixed a security problem on the machine */ |
| fu_console_print_literal(self->console, _("Fixed successfully")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_hwids_as_json(FuUtil *self, GStrv hwids_keys, GStrv hwids_values, GError **error) |
| { |
| g_autoptr(JsonBuilder) builder = json_builder_new(); |
| json_builder_begin_object(builder); |
| for (guint i = 0; hwids_keys[i] != NULL; i++) { |
| json_builder_set_member_name(builder, hwids_keys[i]); |
| json_builder_add_string_value(builder, hwids_values[i]); |
| } |
| json_builder_end_object(builder); |
| return fu_util_print_builder(self->console, builder, error); |
| } |
| |
| static gboolean |
| fu_util_hwids(FuUtil *self, gchar **values, GError **error) |
| { |
| g_auto(GStrv) hwids_keys = NULL; |
| g_auto(GStrv) hwids_values = NULL; |
| |
| fwupd_client_get_hwids(self->client, &hwids_keys, &hwids_values); |
| if (self->as_json) |
| return fu_util_hwids_as_json(self, hwids_keys, hwids_values, error); |
| |
| /* show debug output */ |
| fu_console_print_literal(self->console, "Computer Information"); |
| fu_console_print_literal(self->console, "--------------------"); |
| for (guint i = 0; hwids_keys[i] != NULL; i++) { |
| if (fwupd_guid_is_valid(hwids_values[i])) |
| continue; |
| fu_console_print(self->console, "%s: %s", hwids_keys[i], hwids_values[i]); |
| } |
| |
| /* show GUIDs */ |
| fu_console_print_literal(self->console, "Hardware IDs"); |
| fu_console_print_literal(self->console, "------------"); |
| for (guint i = 0; hwids_keys[i] != NULL; i++) { |
| g_autofree gchar *hwids_keys_real = NULL; |
| g_auto(GStrv) hwids_keys_strv = NULL; |
| if (!fwupd_guid_is_valid(hwids_values[i])) |
| continue; |
| hwids_keys_strv = g_strsplit(hwids_keys[i], "&", -1); |
| hwids_keys_real = g_strjoinv(" + ", hwids_keys_strv); |
| fu_console_print(self->console, "{%s} <- %s", hwids_values[i], hwids_keys_real); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_report_devices(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autofree gchar *data = NULL; |
| g_autofree gchar *report_uri = NULL; |
| g_autofree gchar *uri = NULL; |
| g_autoptr(FwupdRemote) remote = NULL; |
| g_autoptr(GHashTable) metadata = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* we only know how to upload to the LVFS */ |
| remote = fwupd_client_get_remote_by_id(self->client, "lvfs", self->cancellable, error); |
| if (remote == NULL) |
| return FALSE; |
| report_uri = fwupd_remote_build_report_uri(remote, error); |
| if (report_uri == NULL) |
| return FALSE; |
| |
| /* include all the devices */ |
| devices = fwupd_client_get_devices(self->client, self->cancellable, error); |
| if (devices == NULL) |
| return FALSE; |
| metadata = fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (metadata == NULL) |
| return FALSE; |
| data = fwupd_client_build_report_devices(self->client, devices, metadata, error); |
| if (data == NULL) |
| return FALSE; |
| |
| if (self->as_json) { |
| if (!self->assume_yes) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "pass --yes to enable uploads"); |
| return FALSE; |
| } |
| } else { |
| /* show the user the entire data blob */ |
| fu_console_print_kv(self->console, _("Target"), report_uri); |
| fu_console_print_kv(self->console, _("Payload"), data); |
| fu_console_print( |
| self->console, |
| /* TRANSLATORS: explain why we want to upload */ |
| _("Uploading a device list allows the %s team to know what hardware " |
| "exists, and allows us to put pressure on vendors that do not upload " |
| "firmware updates for their hardware."), |
| fwupd_remote_get_title(remote)); |
| if (!fu_console_input_bool(self->console, |
| TRUE, |
| "%s (%s)", |
| /* TRANSLATORS: ask the user to upload */ |
| _("Upload data now?"), |
| /* TRANSLATORS: metadata is downloaded */ |
| _("Requires internet connection"))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Declined upload"); |
| return FALSE; |
| } |
| } |
| |
| /* send to the LVFS */ |
| uri = fwupd_client_upload_report(self->client, |
| report_uri, |
| data, |
| NULL, |
| FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART, |
| self->cancellable, |
| error); |
| if (uri == NULL) |
| return FALSE; |
| |
| /* success */ |
| if (!self->as_json) { |
| fu_console_print_literal(self->console, |
| /* TRANSLATORS: success, so say thank you to the user */ |
| _("Device list uploaded successfully, thanks!")); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_security_undo(FuUtil *self, gchar **values, GError **error) |
| { |
| #ifndef HAVE_HSI |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| /* TRANSLATORS: error message for unsupported feature */ |
| _("Host Security ID (HSI) is not supported")); |
| return FALSE; |
| #endif /* HAVE_HSI */ |
| |
| if (g_strv_length(values) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| /* TRANSLATOR: This is the error message for |
| * incorrect parameter */ |
| _("Invalid arguments, expected an AppStream ID")); |
| return FALSE; |
| } |
| if (!fwupd_client_undo_host_security_attr(self->client, |
| values[0], |
| self->cancellable, |
| error)) |
| return FALSE; |
| |
| if (self->as_json) |
| return TRUE; |
| |
| /* TRANSLATORS: we've fixed a security problem on the machine */ |
| fu_console_print_literal(self->console, _("Fix reverted successfully")); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_emulation_tag(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| /* set the flag */ |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_EMULATION_TAG; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| return fwupd_client_modify_device(self->client, |
| fwupd_device_get_id(dev), |
| "Flags", |
| "emulation-tag", |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_emulation_untag(FuUtil *self, gchar **values, GError **error) |
| { |
| g_autoptr(FwupdDevice) dev = NULL; |
| |
| /* set the flag */ |
| self->filter_device_include |= FWUPD_DEVICE_FLAG_EMULATION_TAG; |
| dev = fu_util_get_device_or_prompt(self, values, error); |
| if (dev == NULL) |
| return FALSE; |
| return fwupd_client_modify_device(self->client, |
| fwupd_device_get_id(dev), |
| "Flags", |
| "~emulation-tag", |
| self->cancellable, |
| error); |
| } |
| |
| static gboolean |
| fu_util_emulation_save(FuUtil *self, gchar **values, GError **error) |
| { |
| /* check args */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments, expected FILENAME"); |
| return FALSE; |
| } |
| |
| /* save */ |
| return fwupd_client_emulation_save(self->client, values[0], self->cancellable, error); |
| } |
| |
| static gboolean |
| fu_util_emulation_load(FuUtil *self, gchar **values, GError **error) |
| { |
| /* check args */ |
| if (g_strv_length(values) != 1) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_ARGS, |
| "Invalid arguments, expected FILENAME"); |
| return FALSE; |
| } |
| return fwupd_client_emulation_load(self->client, values[0], self->cancellable, error); |
| } |
| |
| static gboolean |
| fu_util_version(FuUtil *self, GError **error) |
| { |
| g_autoptr(GHashTable) metadata = NULL; |
| g_autofree gchar *str = NULL; |
| |
| /* get metadata */ |
| metadata = fwupd_client_get_report_metadata(self->client, self->cancellable, error); |
| if (metadata == NULL) |
| return FALSE; |
| |
| /* dump to the screen in the most appropriate format */ |
| if (self->as_json) |
| return fu_util_project_versions_as_json(self->console, metadata, error); |
| str = fu_util_project_versions_to_string(metadata); |
| fu_console_print_literal(self->console, str); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_util_setup_interactive(FuUtil *self, GError **error) |
| { |
| if (self->as_json) { |
| g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "using --json"); |
| return FALSE; |
| } |
| return fu_console_setup(self->console, error); |
| } |
| |
| static void |
| fu_util_cancelled_cb(GCancellable *cancellable, gpointer user_data) |
| { |
| FuUtil *self = (FuUtil *)user_data; |
| if (!g_main_loop_is_running(self->loop)) |
| return; |
| /* TRANSLATORS: this is from ctrl+c */ |
| fu_console_print_literal(self->console, _("Cancelled")); |
| g_main_loop_quit(self->loop); |
| } |
| |
| static void |
| fu_util_print_error(FuUtil *self, const GError *error) |
| { |
| if (self->as_json) { |
| fu_util_print_error_as_json(self->console, error); |
| return; |
| } |
| fu_console_print_full(self->console, FU_CONSOLE_PRINT_FLAG_STDERR, "%s\n", error->message); |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { /* nocheck:lines */ |
| gboolean force = FALSE; |
| gboolean allow_branch_switch = FALSE; |
| gboolean allow_older = FALSE; |
| gboolean allow_reinstall = FALSE; |
| gboolean only_emulated = FALSE; |
| gboolean only_p2p = FALSE; |
| gboolean is_interactive = FALSE; |
| gboolean no_history = FALSE; |
| gboolean no_authenticate = FALSE; |
| gboolean ret; |
| gboolean verbose = FALSE; |
| gboolean version = FALSE; |
| guint download_retries = 0; |
| g_autoptr(FuUtil) self = g_new0(FuUtil, 1); |
| g_autoptr(GDateTime) dt_now = g_date_time_new_now_utc(); |
| g_autoptr(GError) error = NULL; |
| g_autoptr(GError) error_console = NULL; |
| g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new(); |
| #ifdef HAVE_POLKIT |
| g_autoptr(FuPolkitAgent) polkit_agent = fu_polkit_agent_new(); |
| #endif |
| g_autofree gchar *cmd_descriptions = NULL; |
| g_autofree gchar *filter_device = NULL; |
| g_autofree gchar *filter_release = NULL; |
| const GOptionEntry options[] = { |
| {"verbose", |
| 'v', |
| 0, |
| G_OPTION_ARG_NONE, |
| &verbose, |
| /* TRANSLATORS: command line option */ |
| N_("Show extra debugging information"), |
| NULL}, |
| {"version", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &version, |
| /* TRANSLATORS: command line option */ |
| N_("Show client and daemon versions"), |
| NULL}, |
| {"download-retries", |
| '\0', |
| 0, |
| G_OPTION_ARG_INT, |
| &download_retries, |
| /* TRANSLATORS: command line option */ |
| N_("Set the download retries for transient errors"), |
| 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}, |
| {"only-emulated", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &only_emulated, |
| /* TRANSLATORS: command line option */ |
| N_("Only install onto emulated devices"), |
| NULL}, |
| {"force", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &force, |
| /* TRANSLATORS: command line option */ |
| N_("Force the action by relaxing some runtime checks"), |
| NULL}, |
| {"assume-yes", |
| 'y', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->assume_yes, |
| /* TRANSLATORS: command line option */ |
| N_("Answer yes to all questions"), |
| NULL}, |
| {"sign", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->sign, |
| /* TRANSLATORS: command line option */ |
| N_("Sign the uploaded data with the client certificate"), |
| NULL}, |
| {"no-unreported-check", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->no_unreported_check, |
| /* TRANSLATORS: command line option */ |
| N_("Do not check for unreported history"), |
| NULL}, |
| {"no-metadata-check", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->no_metadata_check, |
| /* TRANSLATORS: command line option */ |
| N_("Do not check for old metadata"), |
| NULL}, |
| {"no-remote-check", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->no_remote_check, |
| /* TRANSLATORS: command line option */ |
| N_("Do not check if download remotes should be enabled"), |
| NULL}, |
| {"no-reboot-check", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->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, |
| &self->no_safety_check, |
| /* TRANSLATORS: command line option */ |
| N_("Do not perform device safety checks"), |
| NULL}, |
| {"no-device-prompt", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->no_device_prompt, |
| /* TRANSLATORS: command line option */ |
| N_("Do not prompt for devices"), |
| NULL}, |
| {"no-history", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &no_history, |
| /* TRANSLATORS: command line option */ |
| N_("Do not write to the history database"), |
| NULL}, |
| {"show-all", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->show_all, |
| /* TRANSLATORS: command line option */ |
| N_("Show all results"), |
| NULL}, |
| {"show-all-devices", |
| '\0', |
| G_OPTION_FLAG_HIDDEN, |
| G_OPTION_ARG_NONE, |
| &self->show_all, |
| /* TRANSLATORS: command line option */ |
| N_("Show devices that are not updatable"), |
| NULL}, |
| {"disable-ssl-strict", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->disable_ssl_strict, |
| /* TRANSLATORS: command line option */ |
| N_("Ignore SSL strict checks when downloading files"), |
| NULL}, |
| {"p2p", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &only_p2p, |
| /* TRANSLATORS: command line option */ |
| N_("Only use peer-to-peer networking when downloading files"), |
| NULL}, |
| {"filter", |
| '\0', |
| 0, |
| G_OPTION_ARG_STRING, |
| &filter_device, |
| /* TRANSLATORS: command line option */ |
| N_("Filter with a set of device flags using a ~ prefix to " |
| "exclude, e.g. 'internal,~needs-reboot'"), |
| NULL}, |
| {"filter-release", |
| '\0', |
| 0, |
| G_OPTION_ARG_STRING, |
| &filter_release, |
| /* TRANSLATORS: command line option */ |
| N_("Filter with a set of release flags using a ~ prefix to " |
| "exclude, e.g. 'trusted-release,~trusted-metadata'"), |
| NULL}, |
| {"json", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->as_json, |
| /* TRANSLATORS: command line option */ |
| N_("Output in JSON format (disables all interactive prompts)"), |
| NULL}, |
| {"no-security-fix", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &self->no_security_fix, |
| /* TRANSLATORS: command line option */ |
| N_("Do not prompt to fix security issues"), |
| NULL}, |
| {"no-authenticate", |
| '\0', |
| 0, |
| G_OPTION_ARG_NONE, |
| &no_authenticate, |
| /* TRANSLATORS: command line option */ |
| N_("Don't prompt for authentication (less details may be shown)"), |
| NULL}, |
| {NULL}}; |
| FwupdFeatureFlags feature_flags = |
| FWUPD_FEATURE_FLAG_CAN_REPORT | FWUPD_FEATURE_FLAG_SWITCH_BRANCH | |
| FWUPD_FEATURE_FLAG_FDE_WARNING | FWUPD_FEATURE_FLAG_COMMUNITY_TEXT | |
| FWUPD_FEATURE_FLAG_SHOW_PROBLEMS; |
| |
| #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); |
| g_set_prgname(fu_util_get_prgname(argv[0])); |
| |
| /* ensure D-Bus errors are registered */ |
| (void)fwupd_error_quark(); |
| |
| /* create helper object */ |
| self->main_ctx = g_main_context_new(); |
| self->loop = g_main_loop_new(self->main_ctx, FALSE); |
| self->console = fu_console_new(); |
| self->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| fu_console_set_main_context(self->console, self->main_ctx); |
| |
| /* add commands */ |
| fu_util_cmd_array_add(cmd_array, |
| "check-reboot-needed", |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Check if any devices are pending a reboot to complete update"), |
| fu_util_check_reboot_needed); |
| 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-history", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Show history of firmware updates"), |
| fu_util_get_history); |
| fu_util_cmd_array_add(cmd_array, |
| "report-history", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Share firmware history with the developers"), |
| fu_util_report_history); |
| fu_util_cmd_array_add(cmd_array, |
| "report-export", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Export firmware history for manual upload"), |
| fu_util_report_export); |
| fu_util_cmd_array_add(cmd_array, |
| "install", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID] [VERSION]"), |
| /* TRANSLATORS: command description */ |
| _("Install a specific firmware file on all devices that match"), |
| fu_util_install); |
| fu_util_cmd_array_add(cmd_array, |
| "local-install", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("FILE [DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Install a firmware file in cabinet format on this hardware"), |
| fu_util_local_install); |
| 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-updates,get-upgrades", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Gets the list of updates for all specified devices, or all " |
| "devices if unspecified"), |
| fu_util_get_updates); |
| 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, |
| "verify", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Checks cryptographic hash matches firmware"), |
| fu_util_verify); |
| fu_util_cmd_array_add(cmd_array, |
| "unlock", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("DEVICE-ID|GUID"), |
| /* TRANSLATORS: command description */ |
| _("Unlocks the device for firmware access"), |
| fu_util_unlock); |
| fu_util_cmd_array_add(cmd_array, |
| "clear-results", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("DEVICE-ID|GUID"), |
| /* TRANSLATORS: command description */ |
| _("Clears the results from the last update"), |
| fu_util_clear_results); |
| fu_util_cmd_array_add(cmd_array, |
| "get-results", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("DEVICE-ID|GUID"), |
| /* TRANSLATORS: command description */ |
| _("Gets the results from the last update"), |
| fu_util_get_results); |
| fu_util_cmd_array_add(cmd_array, |
| "get-releases", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Gets the releases for a device"), |
| fu_util_get_releases); |
| fu_util_cmd_array_add(cmd_array, |
| "search", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("WORD"), |
| /* TRANSLATORS: command description */ |
| _("Finds firmware releases from the metadata"), |
| fu_util_search); |
| 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, |
| "downgrade", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Downgrades the firmware on a device"), |
| fu_util_downgrade); |
| fu_util_cmd_array_add(cmd_array, |
| "refresh", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[FILE FILE_SIG REMOTE-ID]"), |
| /* TRANSLATORS: command description */ |
| _("Refresh metadata from remote server"), |
| fu_util_refresh); |
| fu_util_cmd_array_add(cmd_array, |
| "verify-update", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Update the stored cryptographic hash with current ROM contents"), |
| fu_util_verify_update); |
| fu_util_cmd_array_add(cmd_array, |
| "modify-remote", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("REMOTE-ID KEY VALUE"), |
| /* TRANSLATORS: command description */ |
| _("Modifies a given remote"), |
| fu_util_remote_modify); |
| fu_util_cmd_array_add(cmd_array, |
| "enable-remote", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("REMOTE-ID"), |
| /* TRANSLATORS: command description */ |
| _("Enables a given remote"), |
| fu_util_remote_enable); |
| fu_util_cmd_array_add(cmd_array, |
| "clean-remote", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("REMOTE-ID"), |
| /* TRANSLATORS: command description */ |
| _("Cleans a given remote"), |
| fu_util_remote_clean); |
| fu_util_cmd_array_add(cmd_array, |
| "disable-remote", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("REMOTE-ID"), |
| /* TRANSLATORS: command description */ |
| _("Disables a given remote"), |
| fu_util_remote_disable); |
| fu_util_cmd_array_add(cmd_array, |
| "activate", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Activate devices"), |
| fu_util_activate); |
| fu_util_cmd_array_add(cmd_array, |
| "get-approved-firmware", |
| NULL, |
| /* TRANSLATORS: firmware approved by the admin */ |
| _("Gets the list of approved firmware"), |
| fu_util_get_approved_firmware); |
| fu_util_cmd_array_add(cmd_array, |
| "set-approved-firmware", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]"), |
| /* TRANSLATORS: firmware approved by the admin */ |
| _("Sets the list of approved firmware"), |
| fu_util_set_approved_firmware); |
| fu_util_cmd_array_add(cmd_array, |
| "modify-config", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[SECTION] KEY VALUE"), |
| /* TRANSLATORS: sets something in the daemon configuration file */ |
| _("Modifies a daemon configuration value"), |
| fu_util_modify_config); |
| fu_util_cmd_array_add(cmd_array, |
| "reset-config", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("SECTION"), |
| /* TRANSLATORS: sets something in the daemon configuration file */ |
| _("Resets a daemon configuration section"), |
| fu_util_reset_config); |
| fu_util_cmd_array_add(cmd_array, |
| "reinstall", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Reinstall current firmware on the device"), |
| fu_util_reinstall); |
| 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, |
| "security", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Gets the host security attributes"), |
| fu_util_security); |
| fu_util_cmd_array_add(cmd_array, |
| "sync,sync-bkc", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Sync firmware versions to the chosen configuration"), |
| fu_util_sync); |
| fu_util_cmd_array_add(cmd_array, |
| "block-firmware", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[CHECKSUM]"), |
| /* TRANSLATORS: command description */ |
| _("Blocks a specific firmware from being installed"), |
| fu_util_block_firmware); |
| fu_util_cmd_array_add(cmd_array, |
| "unblock-firmware", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[CHECKSUM]"), |
| /* TRANSLATORS: command description */ |
| _("Unblocks a specific firmware from being installed"), |
| fu_util_unblock_firmware); |
| fu_util_cmd_array_add(cmd_array, |
| "get-blocked-firmware", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Gets the list of blocked firmware"), |
| fu_util_get_blocked_firmware); |
| 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, |
| "download", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("LOCATION"), |
| /* TRANSLATORS: command description */ |
| _("Download a file"), |
| fu_util_download); |
| fu_util_cmd_array_add(cmd_array, |
| "device-test", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[FILENAME1] [FILENAME2]"), |
| /* TRANSLATORS: command description */ |
| _("Test a device using a JSON manifest"), |
| fu_util_device_test); |
| fu_util_cmd_array_add(cmd_array, |
| "device-emulate", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[FILENAME1] [FILENAME2]"), |
| /* TRANSLATORS: command description */ |
| _("Emulate a device using a JSON manifest"), |
| fu_util_device_emulate); |
| fu_util_cmd_array_add(cmd_array, |
| "inhibit", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[REASON] [TIMEOUT]"), |
| /* TRANSLATORS: command description */ |
| _("Inhibit the system to prevent upgrades"), |
| fu_util_inhibit); |
| fu_util_cmd_array_add(cmd_array, |
| "uninhibit", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("INHIBIT-ID"), |
| /* TRANSLATORS: command description */ |
| _("Uninhibit the system to allow upgrades"), |
| fu_util_uninhibit); |
| fu_util_cmd_array_add(cmd_array, |
| "device-wait", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("GUID|DEVICE-ID"), |
| /* TRANSLATORS: command description */ |
| _("Wait for a device to appear"), |
| fu_util_device_wait); |
| fu_util_cmd_array_add(cmd_array, |
| "quit", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Asks the daemon to quit"), |
| fu_util_quit); |
| fu_util_cmd_array_add( |
| cmd_array, |
| "get-bios-settings,get-bios-setting", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[SETTING1] [SETTING2] [--no-authenticate]"), |
| /* 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 */ |
| _("SETTING1 VALUE1 [SETTING2] [VALUE2]"), |
| /* TRANSLATORS: command description */ |
| _("Sets one or more BIOS settings"), |
| fu_util_set_bios_setting); |
| fu_util_cmd_array_add(cmd_array, |
| "emulation-load", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("FILENAME"), |
| /* TRANSLATORS: command description */ |
| _("Load device emulation data"), |
| fu_util_emulation_load); |
| fu_util_cmd_array_add(cmd_array, |
| "emulation-save", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("FILENAME"), |
| /* TRANSLATORS: command description */ |
| _("Save device emulation data"), |
| fu_util_emulation_save); |
| fu_util_cmd_array_add(cmd_array, |
| "emulation-tag", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Adds devices to watch for future emulation"), |
| fu_util_emulation_tag); |
| fu_util_cmd_array_add(cmd_array, |
| "emulation-untag", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[DEVICE-ID|GUID]"), |
| /* TRANSLATORS: command description */ |
| _("Removes devices to watch for future emulation"), |
| fu_util_emulation_untag); |
| fu_util_cmd_array_add(cmd_array, |
| "security-fix", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[APPSTREAM_ID]"), |
| /* TRANSLATORS: command description */ |
| _("Fix a specific host security attribute"), |
| fu_util_security_fix); |
| fu_util_cmd_array_add(cmd_array, |
| "security-undo", |
| /* TRANSLATORS: command argument: uppercase, spaces->dashes */ |
| _("[APPSTREAM_ID]"), |
| /* TRANSLATORS: command description */ |
| _("Undo the host security attribute fix"), |
| fu_util_security_undo); |
| fu_util_cmd_array_add(cmd_array, |
| "report-devices", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Upload the list of updatable devices to a remote server"), |
| fu_util_report_devices); |
| fu_util_cmd_array_add(cmd_array, |
| "hwids", |
| NULL, |
| /* TRANSLATORS: command description */ |
| _("Return all the hardware IDs for the machine"), |
| fu_util_hwids); |
| |
| /* do stuff on ctrl+c */ |
| self->cancellable = g_cancellable_new(); |
| g_signal_connect(G_CANCELLABLE(self->cancellable), |
| "cancelled", |
| G_CALLBACK(fu_util_cancelled_cb), |
| self); |
| |
| /* sort by command name */ |
| fu_util_cmd_array_sort(cmd_array); |
| |
| /* non-TTY consoles cannot answer questions */ |
| if (!fu_util_setup_interactive(self, &error_console)) { |
| g_info("failed to initialize interactive console: %s", error_console->message); |
| self->no_unreported_check = TRUE; |
| self->no_metadata_check = TRUE; |
| self->no_reboot_check = TRUE; |
| self->no_safety_check = TRUE; |
| self->no_remote_check = TRUE; |
| self->no_device_prompt = TRUE; |
| self->no_emulation_check = TRUE; |
| self->no_security_fix = TRUE; |
| } else { |
| is_interactive = TRUE; |
| } |
| fu_console_set_interactive(self->console, is_interactive); |
| |
| /* get a list of the commands */ |
| self->context = g_option_context_new(NULL); |
| cmd_descriptions = fu_util_cmd_array_to_string(cmd_array); |
| g_option_context_set_summary(self->context, cmd_descriptions); |
| g_option_context_set_description( |
| self->context, |
| /* TRANSLATORS: CLI description */ |
| _("This tool allows an administrator to query and control the " |
| "fwupd daemon, allowing them to perform actions such as " |
| "installing or downgrading firmware.")); |
| |
| /* TRANSLATORS: program name */ |
| g_set_application_name(_("Firmware Utility")); |
| g_option_context_add_main_entries(self->context, options, NULL); |
| ret = g_option_context_parse(self->context, &argc, &argv, &error); |
| if (!ret) { |
| fu_console_print(self->console, |
| "%s: %s", |
| /* TRANSLATORS: the user didn't read the man page */ |
| _("Failed to parse arguments"), |
| error->message); |
| return EXIT_FAILURE; |
| } |
| |
| /* allow disabling SSL strict mode for broken corporate proxies */ |
| if (self->disable_ssl_strict) { |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_WARNING, |
| "%s\n", |
| /* 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); |
| } |
| |
| /* this doesn't have to be precise (e.g. using the build-year) as we just |
| * want to check the clock is not set to the default of 1970-01-01... */ |
| if (g_date_time_get_year(dt_now) < 2021) { |
| fu_console_print_full( |
| self->console, |
| FU_CONSOLE_PRINT_FLAG_WARNING, |
| "%s\n", |
| /* TRANSLATORS: try to help */ |
| _("The system clock has not been set correctly and downloading " |
| "files may fail.")); |
| } |
| |
| /* parse filter flags */ |
| if (filter_device != NULL) { |
| if (!fu_util_parse_filter_device_flags(filter_device, |
| &self->filter_device_include, |
| &self->filter_device_exclude, |
| &error)) { |
| g_autofree gchar *str = |
| /* TRANSLATORS: the user didn't read the man page, %1 is '--filter' */ |
| g_strdup_printf(_("Failed to parse flags for %s"), "--filter"); |
| g_prefix_error(&error, "%s: ", str); |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| } |
| if (filter_release != NULL) { |
| if (!fu_util_parse_filter_release_flags(filter_release, |
| &self->filter_release_include, |
| &self->filter_release_exclude, |
| &error)) { |
| g_autofree gchar *str = |
| /* TRANSLATORS: the user didn't read the man page, |
| * %1 is '--filter-release' */ |
| g_strdup_printf(_("Failed to parse flags for %s"), "--filter-release"); |
| g_prefix_error(&error, "%s: ", str); |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| /* set verbose? */ |
| if (verbose) { |
| (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); |
| (void)g_setenv("FWUPD_VERBOSE", "1", FALSE); |
| } else { |
| g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); |
| } |
| |
| /* set up ctrl+c */ |
| fu_util_setup_signal_handlers(self); |
| |
| /* set flags */ |
| if (allow_reinstall) |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; |
| if (allow_older) |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; |
| if (allow_branch_switch) |
| self->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; |
| if (only_emulated) |
| self->flags |= FWUPD_INSTALL_FLAG_ONLY_EMULATED; |
| if (force) { |
| self->flags |= FWUPD_INSTALL_FLAG_FORCE; |
| self->flags |= FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS; |
| } |
| if (no_history) |
| self->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; |
| |
| /* use peer-to-peer for metadata and firmware *only* if specified */ |
| if (only_p2p) |
| self->download_flags |= FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P; |
| |
| #ifdef HAVE_POLKIT |
| /* start polkit tty agent to listen for password requests */ |
| if (is_interactive) { |
| g_autoptr(GError) error_polkit = NULL; |
| if (!fu_polkit_agent_open(polkit_agent, &error_polkit)) { |
| fu_console_print(self->console, |
| "Failed to open polkit agent: %s", |
| error_polkit->message); |
| } |
| } |
| #endif |
| |
| /* connect to the daemon */ |
| self->client = fwupd_client_new(); |
| fwupd_client_set_main_context(self->client, self->main_ctx); |
| fwupd_client_download_set_retries(self->client, download_retries); |
| g_signal_connect(FWUPD_CLIENT(self->client), |
| "notify::percentage", |
| G_CALLBACK(fu_util_client_notify_cb), |
| self); |
| g_signal_connect(FWUPD_CLIENT(self->client), |
| "notify::status", |
| G_CALLBACK(fu_util_client_notify_cb), |
| self); |
| g_signal_connect(FWUPD_CLIENT(self->client), |
| "device-changed", |
| G_CALLBACK(fu_util_update_device_changed_cb), |
| self); |
| g_signal_connect(FWUPD_CLIENT(self->client), |
| "device-request", |
| G_CALLBACK(fu_util_update_device_request_cb), |
| self); |
| |
| /* show a warning if the daemon is tainted */ |
| if (!fwupd_client_connect(self->client, self->cancellable, &error)) { |
| #ifdef _WIN32 |
| fu_console_print_literal( |
| self->console, |
| /* TRANSLATORS: error message for Windows */ |
| _("Failed to connect to Windows service, please ensure it's running.")); |
| g_debug("%s", error->message); |
| #else |
| /* TRANSLATORS: could not contact the fwupd service over D-Bus */ |
| g_prefix_error(&error, "%s: ", _("Failed to connect to daemon")); |
| fu_util_print_error(self, error); |
| #endif |
| return EXIT_FAILURE; |
| } |
| if (fwupd_client_get_tainted(self->client)) { |
| fu_console_print_full(self->console, |
| FU_CONSOLE_PRINT_FLAG_WARNING, |
| "%s\n", |
| /* TRANSLATORS: the user is SOL for support... */ |
| _("The daemon has loaded 3rd party code and " |
| "is no longer supported by the upstream developers!")); |
| } |
| |
| /* just show versions and exit */ |
| if (version) { |
| if (!fu_util_version(self, &error)) { |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| return EXIT_SUCCESS; |
| } |
| |
| if (!self->as_json) { |
| /* show user-visible warnings from the plugins */ |
| fu_util_show_plugin_warnings(self); |
| |
| /* show any unsupported warnings */ |
| fu_util_show_unsupported_warning(self->console); |
| } |
| |
| /* we know the runtime daemon version now */ |
| fwupd_client_set_user_agent_for_package(self->client, g_get_prgname(), PACKAGE_VERSION); |
| |
| /* check that we have at least this version daemon running */ |
| if ((self->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && |
| !fu_util_check_daemon_version(self, &error)) { |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| |
| /* make sure polkit actions were installed */ |
| if (!fu_util_check_polkit_actions(&error)) { |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| |
| /* send our implemented feature set */ |
| if (is_interactive) { |
| feature_flags |= |
| FWUPD_FEATURE_FLAG_REQUESTS | FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC | |
| FWUPD_FEATURE_FLAG_UPDATE_ACTION | FWUPD_FEATURE_FLAG_DETACH_ACTION; |
| if (!no_authenticate) |
| feature_flags |= FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION; |
| } |
| if (!fwupd_client_set_feature_flags(self->client, |
| feature_flags, |
| self->cancellable, |
| &error)) { |
| /* TRANSLATORS: a feature is something like "can show an image" */ |
| g_prefix_error(&error, "%s: ", _("Failed to set front-end features")); |
| fu_util_print_error(self, error); |
| return EXIT_FAILURE; |
| } |
| |
| /* run the specified command */ |
| ret = fu_util_cmd_array_run(cmd_array, self, 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 |
| fu_util_print_error(self, error); |
| if (!self->as_json && |
| g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { |
| g_autofree gchar *cmd = g_strdup_printf("%s --help", g_get_prgname()); |
| g_autoptr(GString) str = g_string_new("\n"); |
| /* TRANSLATORS: explain how to get help, |
| * where $1 is something like 'fwupdmgr --help' */ |
| g_string_append_printf(str, _("Use %s for help"), cmd); |
| fu_console_print_literal(self->console, str->str); |
| } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) |
| return EXIT_NOTHING_TO_DO; |
| else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_REACHABLE)) |
| return EXIT_NOT_REACHABLE; |
| else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) |
| return EXIT_NOT_FOUND; |
| return EXIT_FAILURE; |
| } |
| |
| /* success */ |
| return EXIT_SUCCESS; |
| } |