| /* |
| * Copyright 2015 Richard Hughes <richard@hughsie.com> |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #define G_LOG_DOMAIN "FuEngine" |
| |
| #include "config.h" |
| |
| #include <fcntl.h> |
| |
| #ifdef HAVE_GIO_UNIX |
| #include <gio/gunixinputstream.h> |
| #endif |
| #ifdef HAVE_PASSIM |
| #include <passim.h> |
| #endif |
| #include <string.h> |
| #ifdef HAVE_UTSNAME_H |
| #include <sys/utsname.h> |
| #endif |
| #ifdef HAVE_AUXV_H |
| #include <sys/auxv.h> |
| #endif |
| |
| #ifdef _WIN32 |
| #include <sysinfoapi.h> |
| #include <winerror.h> |
| #include <winreg.h> |
| #endif |
| |
| #include <fwupdplugin.h> |
| |
| #include "fwupd-enums-private.h" |
| #include "fwupd-remote-private.h" |
| #include "fwupd-resources.h" |
| #include "fwupd-security-attr-private.h" |
| |
| #include "fu-bios-setting.h" |
| #include "fu-bios-settings-private.h" |
| #include "fu-config-private.h" |
| #include "fu-context-private.h" |
| #include "fu-device-list.h" |
| #include "fu-device-private.h" |
| #include "fu-device-progress.h" |
| #include "fu-engine-emulator.h" |
| #include "fu-engine-helper.h" |
| #include "fu-engine-request.h" |
| #include "fu-engine-requirements.h" |
| #include "fu-engine.h" |
| #include "fu-history.h" |
| #include "fu-idle.h" |
| #include "fu-plugin-builtin.h" |
| #include "fu-plugin-list.h" |
| #include "fu-plugin-private.h" |
| #include "fu-release.h" |
| #include "fu-remote-list.h" |
| #include "fu-remote.h" |
| #include "fu-security-attr-common.h" |
| #include "fu-security-attrs-private.h" |
| #include "fu-udev-device-private.h" |
| #include "fu-uefi-backend.h" |
| #include "fu-usb-backend.h" |
| |
| #ifdef HAVE_GIO_UNIX |
| #include "fu-unix-seekable-input-stream.h" |
| #endif |
| #ifdef HAVE_UDEV |
| #include "fu-udev-backend.h" |
| #endif |
| #ifdef HAVE_BLUEZ |
| #include "fu-bluez-backend.h" |
| #endif |
| |
| /* only needed until we hard depend on jcat 0.1.3 */ |
| #include <libjcat/jcat-version.h> |
| |
| #ifdef HAVE_SYSTEMD |
| #include "fu-systemd.h" |
| #endif |
| |
| #define MINIMUM_BATTERY_PERCENTAGE_FALLBACK 10 |
| |
| #define FU_ENGINE_UPDATE_MOTD_DELAY 5 /* s */ |
| |
| #define FU_ENGINE_MAX_METADATA_SIZE 0x2000000 /* 32MB */ |
| #define FU_ENGINE_MAX_SIGNATURE_SIZE 0x100000 /* 1MB */ |
| |
| static void |
| fu_engine_constructed(GObject *obj); |
| static void |
| fu_engine_finalize(GObject *obj); |
| static void |
| fu_engine_ensure_security_attrs(FuEngine *self); |
| static void |
| fu_engine_md_refresh_device(FuEngine *self, FuDevice *device); |
| static void |
| fu_engine_metadata_changed(FuEngine *self); |
| |
| struct _FuEngine { |
| GObject parent_instance; |
| FuEngineConfig *config; |
| FuRemoteList *remote_list; |
| FuDeviceList *device_list; |
| gboolean write_history; |
| gboolean host_emulation; |
| guint percentage; |
| FuHistory *history; |
| FuIdle *idle; |
| XbSilo *silo; |
| XbQuery *query_component_by_guid; |
| XbQuery *query_container_checksum1; /* container checksum -> release */ |
| XbQuery *query_container_checksum2; /* artifact checksum -> release */ |
| XbQuery *query_tag_by_guid_version; |
| GPtrArray *search_queries; /* (element-type XbQuery) */ |
| FuPluginList *plugin_list; |
| GPtrArray *plugin_filter; |
| FuContext *ctx; |
| GHashTable *approved_firmware; /* (nullable) */ |
| GHashTable *blocked_firmware; /* (nullable) */ |
| FuEngineEmulator *emulation; |
| GHashTable *device_changed_allowlist; /* (element-type str int) */ |
| gchar *host_machine_id; |
| JcatContext *jcat_context; |
| FuSecurityAttrs *host_security_attrs; |
| GPtrArray *local_monitors; /* (element-type GFileMonitor) */ |
| GMainLoop *acquiesce_loop; |
| guint acquiesce_id; |
| guint acquiesce_delay; |
| guint update_motd_id; |
| FuEngineEmulatorPhase emulator_phase; |
| guint emulator_write_cnt; |
| FuEngineLoadFlags load_flags; |
| #ifdef HAVE_PASSIM |
| PassimClient *passim_client; |
| #endif |
| }; |
| |
| enum { PROP_0, PROP_CONTEXT, PROP_LAST }; |
| |
| enum { |
| SIGNAL_CHANGED, |
| SIGNAL_DEVICE_ADDED, |
| SIGNAL_DEVICE_REMOVED, |
| SIGNAL_DEVICE_CHANGED, |
| SIGNAL_DEVICE_REQUEST, |
| SIGNAL_STATUS_CHANGED, |
| SIGNAL_LAST |
| }; |
| |
| static guint signals[SIGNAL_LAST] = {0}; |
| |
| enum { |
| QUARK_AUTO_PARENT_CHILDREN, |
| QUARK_HOST_FIRMWARE, |
| QUARK_HOST_FIRMWARE_CHILD, |
| QUARK_HOST_CPU, |
| QUARK_HOST_CPU_CHILD, |
| QUARK_LAST, |
| }; |
| |
| static guint quarks[QUARK_LAST] = {0}; |
| |
| G_DEFINE_TYPE(FuEngine, fu_engine, G_TYPE_OBJECT) |
| |
| gboolean |
| fu_engine_get_loaded(FuEngine *self) |
| { |
| return (self->load_flags & FU_ENGINE_LOAD_FLAG_READY) > 0; |
| } |
| |
| static gboolean |
| fu_engine_update_motd_timeout_cb(gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| g_autoptr(GError) error_local = NULL; |
| |
| /* busy */ |
| if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS)) |
| return G_SOURCE_CONTINUE; |
| |
| /* update now */ |
| if (!fu_engine_update_motd(self, &error_local)) |
| g_info("failed to update MOTD: %s", error_local->message); |
| self->update_motd_id = 0; |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| fu_engine_update_motd_reset(FuEngine *self) |
| { |
| g_info("resetting update motd timeout"); |
| if (self->update_motd_id != 0) |
| g_source_remove(self->update_motd_id); |
| self->update_motd_id = g_timeout_add_seconds(FU_ENGINE_UPDATE_MOTD_DELAY, |
| fu_engine_update_motd_timeout_cb, |
| self); |
| } |
| |
| static void |
| fu_engine_emit_changed(FuEngine *self) |
| { |
| g_autoptr(GError) error = NULL; |
| |
| /* do nothing */ |
| if ((self->load_flags & FU_ENGINE_LOAD_FLAG_READY) == 0) |
| return; |
| |
| g_signal_emit(self, signals[SIGNAL_CHANGED], 0); |
| fu_engine_idle_reset(self); |
| |
| /* update the motd */ |
| if (fu_engine_config_get_update_motd(self->config)) |
| fu_engine_update_motd_reset(self); |
| |
| /* update the list of devices */ |
| if (!fu_engine_update_devices_file(self, &error)) |
| g_info("failed to update list of devices: %s", error->message); |
| } |
| |
| static void |
| fu_engine_emit_device_changed_safe(FuEngine *self, FuDevice *device) |
| { |
| /* do nothing */ |
| if ((self->load_flags & FU_ENGINE_LOAD_FLAG_READY) == 0) |
| return; |
| |
| /* invalidate host security attributes */ |
| fu_security_attrs_remove_all(self->host_security_attrs); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_CHANGED], 0, device); |
| } |
| |
| /* get the latest version of the device */ |
| static void |
| fu_engine_emit_device_changed(FuEngine *self, const gchar *device_id) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GError) error = NULL; |
| |
| /* get the latest version of this */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, &error); |
| if (device == NULL) { |
| g_warning("cannot emit device-changed: %s", error->message); |
| return; |
| } |
| fu_engine_emit_device_changed_safe(self, device); |
| } |
| |
| FuContext * |
| fu_engine_get_context(FuEngine *self) |
| { |
| return self->ctx; |
| } |
| |
| static void |
| fu_engine_set_status(FuEngine *self, FwupdStatus status) |
| { |
| /* emit changed */ |
| g_signal_emit(self, signals[SIGNAL_STATUS_CHANGED], 0, status); |
| } |
| |
| static void |
| fu_engine_generic_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) |
| { |
| if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS) && |
| !g_hash_table_contains(self->device_changed_allowlist, fu_device_get_id(device))) { |
| g_debug("suppressing notification from %s as transaction is in progress", |
| fu_device_get_id(device)); |
| return; |
| } |
| fu_engine_emit_device_changed(self, fu_device_get_id(device)); |
| } |
| |
| static void |
| fu_engine_ensure_device_problem_priority_full(FuEngine *self, |
| FuDevice *device, |
| FuDevice *device_tmp) |
| { |
| /* not important */ |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) || |
| !fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_UPDATABLE)) |
| return; |
| |
| /* not a match */ |
| if (g_strcmp0(fu_device_get_id(device_tmp), fu_device_get_equivalent_id(device)) != 0 && |
| g_strcmp0(fu_device_get_equivalent_id(device_tmp), fu_device_get_id(device)) != 0) |
| return; |
| |
| /* new device is better */ |
| if (fu_device_get_priority(device_tmp) < fu_device_get_priority(device)) { |
| fu_device_add_problem(device_tmp, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| return; |
| } |
| |
| /* old device is better */ |
| if (fu_device_get_priority(device_tmp) > fu_device_get_priority(device)) { |
| fu_device_remove_problem(device_tmp, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| return; |
| } |
| |
| /* the plugin needs to tell us which one is better! */ |
| g_warning("no priority difference, unsetting both"); |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| fu_device_remove_problem(device_tmp, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY); |
| } |
| |
| static void |
| fu_engine_ensure_device_problem_priority(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (g_strcmp0(fu_device_get_id(device_tmp), fu_device_get_id(device)) == 0) |
| continue; |
| fu_engine_ensure_device_problem_priority_full(self, device, device_tmp); |
| } |
| } |
| |
| static void |
| fu_engine_device_equivalent_id_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) |
| { |
| /* make sure the lower priority equivalent device has the problem */ |
| fu_engine_ensure_device_problem_priority(self, device); |
| } |
| |
| static void |
| fu_engine_history_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) |
| { |
| if (self->write_history) { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_history_modify_device(self->history, device, &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
| g_debug("ignoring: %s", error_local->message); |
| } else { |
| g_warning("failed to record history for %s: %s", |
| fu_device_get_id(device), |
| error_local->message); |
| } |
| } |
| } |
| fu_engine_emit_device_changed(self, fu_device_get_id(device)); |
| } |
| |
| static void |
| fu_engine_device_request_cb(FuDevice *device, FwupdRequest *request, FuEngine *self) |
| { |
| g_info("Emitting DeviceRequest('Message'='%s')", fwupd_request_get_message(request)); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); |
| } |
| |
| static void |
| fu_engine_set_emulator_phase(FuEngine *self, FuEngineEmulatorPhase emulator_phase) |
| { |
| g_info("install phase now %s", fu_engine_emulator_phase_to_string(emulator_phase)); |
| self->emulator_phase = emulator_phase; |
| } |
| |
| static void |
| fu_engine_watch_device(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(FuDevice) device_old = fu_device_list_get_old(self->device_list, device); |
| if (device_old != NULL) { |
| g_signal_handlers_disconnect_by_func(device_old, fu_engine_generic_notify_cb, self); |
| g_signal_handlers_disconnect_by_func(device_old, fu_engine_history_notify_cb, self); |
| g_signal_handlers_disconnect_by_func(device_old, fu_engine_device_request_cb, self); |
| } |
| g_signal_connect(FU_DEVICE(device), |
| "notify::flags", |
| G_CALLBACK(fu_engine_generic_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::problems", |
| G_CALLBACK(fu_engine_generic_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::update-message", |
| G_CALLBACK(fu_engine_generic_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::update-image", |
| G_CALLBACK(fu_engine_generic_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::update-state", |
| G_CALLBACK(fu_engine_history_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::update-error", |
| G_CALLBACK(fu_engine_history_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "notify::equivalent-id", |
| G_CALLBACK(fu_engine_device_equivalent_id_notify_cb), |
| self); |
| g_signal_connect(FU_DEVICE(device), |
| "request", |
| G_CALLBACK(fu_engine_device_request_cb), |
| self); |
| } |
| |
| static void |
| fu_engine_ensure_device_power_inhibit(FuEngine *self, FuDevice *device) |
| { |
| if (fu_engine_config_get_ignore_power(self->config)) |
| return; |
| |
| if (fu_device_is_updatable(device) && |
| fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| !fu_power_state_is_ac(fu_context_get_power_state(self->ctx))) { |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER); |
| } else { |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER); |
| } |
| if (fu_device_is_updatable(device) && |
| !fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_IGNORE_SYSTEM_POWER) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| fu_context_get_battery_level(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && |
| fu_context_get_battery_threshold(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && |
| fu_context_get_battery_level(self->ctx) < fu_context_get_battery_threshold(self->ctx)) { |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW); |
| } else { |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW); |
| } |
| } |
| |
| static void |
| fu_engine_ensure_device_lid_inhibit(FuEngine *self, FuDevice *device) |
| { |
| if (fu_device_is_updatable(device) && |
| fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_NO_LID_CLOSED) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| fu_context_get_lid_state(self->ctx) == FU_LID_STATE_CLOSED) { |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED); |
| return; |
| } |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED); |
| } |
| |
| static void |
| fu_engine_ensure_device_display_required_inhibit(FuEngine *self, FuDevice *device) |
| { |
| if (fu_device_is_updatable(device) && |
| fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_DISPLAY_REQUIRED) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| fu_context_get_display_state(self->ctx) == FU_DISPLAY_STATE_DISCONNECTED) { |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED); |
| return; |
| } |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED); |
| } |
| |
| static void |
| fu_engine_ensure_device_maybe_remove_affects_fde(FuEngine *self, FuDevice *device) |
| { |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_AFFECTS_FDE)) |
| return; |
| if (!fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_FDE_BITLOCKER) && |
| !fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_FDE_SNAPD)) { |
| g_debug("removing affects-fde from %s as no FDE detected", |
| fu_device_get_id(device)); |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_AFFECTS_FDE); |
| } |
| } |
| |
| static void |
| fu_engine_ensure_device_system_inhibit(FuEngine *self, FuDevice *device) |
| { |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); |
| return; |
| } |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); |
| } |
| |
| static gboolean |
| fu_engine_acquiesce_timeout_cb(gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| g_info("system acquiesced after %ums", self->acquiesce_delay); |
| g_main_loop_quit(self->acquiesce_loop); |
| self->acquiesce_id = 0; |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| fu_engine_acquiesce_reset(FuEngine *self) |
| { |
| if (!g_main_loop_is_running(self->acquiesce_loop)) |
| return; |
| g_info("resetting system acquiesce timeout"); |
| if (self->acquiesce_id != 0) |
| g_source_remove(self->acquiesce_id); |
| self->acquiesce_id = |
| g_timeout_add(self->acquiesce_delay, fu_engine_acquiesce_timeout_cb, self); |
| } |
| |
| static void |
| fu_engine_wait_for_acquiesce(FuEngine *self, guint acquiesce_delay) |
| { |
| if (acquiesce_delay == 0) |
| return; |
| self->acquiesce_delay = acquiesce_delay; |
| self->acquiesce_id = g_timeout_add(acquiesce_delay, fu_engine_acquiesce_timeout_cb, self); |
| g_main_loop_run(self->acquiesce_loop); |
| } |
| |
| static void |
| fu_engine_ensure_context_flag_save_events(FuEngine *self) |
| { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_history_has_emulation_tag(self->history, NULL, &error_local)) { |
| g_debug("ignoring: %s", error_local->message); |
| fu_context_remove_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); |
| return; |
| } |
| fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); |
| } |
| |
| static void |
| fu_engine_device_added_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) |
| { |
| fu_engine_watch_device(self, device); |
| fu_engine_ensure_device_problem_priority(self, device); |
| fu_engine_ensure_device_power_inhibit(self, device); |
| fu_engine_ensure_device_lid_inhibit(self, device); |
| fu_engine_ensure_device_display_required_inhibit(self, device); |
| fu_engine_ensure_device_system_inhibit(self, device); |
| fu_engine_ensure_device_maybe_remove_affects_fde(self, device); |
| fu_engine_acquiesce_reset(self); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_ADDED], 0, device); |
| } |
| |
| static void |
| fu_engine_device_runner_device_removed(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| fu_plugin_runner_device_removed(plugin_tmp, device); |
| } |
| } |
| |
| static void |
| fu_engine_device_removed_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) |
| { |
| fu_engine_device_runner_device_removed(self, device); |
| fu_engine_acquiesce_reset(self); |
| g_signal_handlers_disconnect_by_data(device, self); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_REMOVED], 0, device); |
| } |
| |
| static void |
| fu_engine_device_changed_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) |
| { |
| fu_engine_watch_device(self, device); |
| fu_engine_emit_device_changed(self, fu_device_get_id(device)); |
| fu_engine_acquiesce_reset(self); |
| } |
| |
| /* add any client-side BKC tags */ |
| static gboolean |
| fu_engine_add_local_release_metadata(FuEngine *self, FuRelease *release, GError **error) |
| { |
| FuDevice *dev = fu_release_get_device(release); |
| GPtrArray *guids; |
| |
| /* no device matched */ |
| if (dev == NULL) |
| return TRUE; |
| |
| /* not set up */ |
| if (self->query_tag_by_guid_version == NULL) |
| return TRUE; |
| |
| /* use prepared query for each GUID */ |
| guids = fu_device_get_guids(dev); |
| for (guint i = 0; i < guids->len; i++) { |
| const gchar *guid = g_ptr_array_index(guids, i); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) tags = NULL; |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| |
| /* bind GUID and then query */ |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), |
| 1, |
| fu_release_get_version(release), |
| NULL); |
| tags = xb_silo_query_with_context(self->silo, |
| self->query_tag_by_guid_version, |
| &context, |
| &error_local); |
| if (tags == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || |
| g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) |
| continue; |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| for (guint j = 0; j < tags->len; j++) { |
| XbNode *tag = g_ptr_array_index(tags, j); |
| fu_release_add_tag(release, xb_node_get_text(tag)); |
| } |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /* private, for self tests */ |
| void |
| fu_engine_add_remote(FuEngine *self, FwupdRemote *remote) |
| { |
| g_return_if_fail(FU_IS_ENGINE(self)); |
| g_return_if_fail(FWUPD_IS_REMOTE(remote)); |
| fu_remote_list_add_remote(self->remote_list, remote); |
| } |
| |
| static void |
| fu_engine_release_remote_id_changed_cb(FuRelease *release, GParamSpec *pspec, FuEngine *self) |
| { |
| FwupdRemote *remote; |
| const gchar *remote_id = fwupd_release_get_remote_id(FWUPD_RELEASE(release)); |
| |
| if (remote_id == NULL) |
| return; |
| remote = fu_remote_list_get_by_id(self->remote_list, remote_id); |
| if (remote == NULL) { |
| g_warning("no remote found for %s", remote_id); |
| return; |
| } |
| fu_release_set_remote(release, remote); |
| } |
| |
| static gboolean |
| fu_engine_compare_report_trusted(FwupdReport *report_trusted, FwupdReport *report) |
| { |
| if (fwupd_report_has_flag(report_trusted, FWUPD_REPORT_FLAG_FROM_OEM) && |
| !fwupd_report_has_flag(report, FWUPD_REPORT_FLAG_FROM_OEM)) |
| return FALSE; |
| if (fwupd_report_has_flag(report_trusted, FWUPD_REPORT_FLAG_IS_UPGRADE) && |
| !fwupd_report_has_flag(report, FWUPD_REPORT_FLAG_IS_UPGRADE)) |
| return FALSE; |
| if (fwupd_report_get_vendor_id(report_trusted) != 0) { |
| if (fwupd_report_get_vendor_id(report_trusted) != |
| fwupd_report_get_vendor_id(report)) |
| return FALSE; |
| } |
| if (fwupd_report_get_distro_id(report_trusted) != NULL) { |
| if (g_strcmp0(fwupd_report_get_distro_id(report_trusted), |
| fwupd_report_get_distro_id(report)) != 0) |
| return FALSE; |
| } |
| if (fwupd_report_get_distro_version(report_trusted) != NULL) { |
| if (g_strcmp0(fwupd_report_get_distro_version(report_trusted), |
| fwupd_report_get_distro_version(report)) != 0) |
| return FALSE; |
| } |
| if (fwupd_report_get_distro_variant(report_trusted) != NULL) { |
| if (g_strcmp0(fwupd_report_get_distro_variant(report_trusted), |
| fwupd_report_get_distro_variant(report)) != 0) |
| return FALSE; |
| } |
| if (fwupd_report_get_remote_id(report_trusted) != NULL) { |
| if (g_strcmp0(fwupd_report_get_remote_id(report_trusted), |
| fwupd_report_get_remote_id(report)) != 0) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_add_trusted_report(FuEngine *self, FuRelease *release) |
| { |
| GPtrArray *reports = fu_release_get_reports(release); |
| GPtrArray *trusted_reports = fu_engine_config_get_trusted_reports(self->config); |
| |
| for (guint i = 0; i < reports->len; i++) { |
| FwupdReport *report = g_ptr_array_index(reports, i); |
| for (guint j = 0; j < trusted_reports->len; j++) { |
| FwupdReport *trusted_report = g_ptr_array_index(trusted_reports, j); |
| if (fu_engine_compare_report_trusted(trusted_report, report)) { |
| g_autofree gchar *str = |
| fwupd_codec_to_string(FWUPD_CODEC(trusted_report)); |
| g_debug("add trusted-report to %s:%s as trusted: %s", |
| fu_release_get_appstream_id(release), |
| fu_release_get_version(release), |
| str); |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_TRUSTED_REPORT); |
| return; |
| } |
| } |
| } |
| } |
| |
| gboolean |
| fu_engine_load_release(FuEngine *self, |
| FuRelease *release, |
| FuCabinet *cabinet, |
| XbNode *component, |
| XbNode *rel, |
| FwupdInstallFlags install_flags, |
| GError **error) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(FU_IS_RELEASE(release), FALSE); |
| g_return_val_if_fail(cabinet == NULL || FU_IS_CABINET(cabinet), FALSE); |
| g_return_val_if_fail(XB_IS_NODE(component), FALSE); |
| g_return_val_if_fail(rel == NULL || XB_IS_NODE(rel), FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* load release from XML */ |
| fu_release_set_config(release, self->config); |
| |
| /* set the FwupdRemote when the remote ID is set */ |
| g_signal_connect(FU_RELEASE(release), |
| "notify::remote-id", |
| G_CALLBACK(fu_engine_release_remote_id_changed_cb), |
| self); |
| |
| /* requirements we can check without the daemon */ |
| if (!fu_release_load(release, cabinet, component, rel, install_flags, error)) |
| return FALSE; |
| |
| /* relax these */ |
| if (fu_engine_config_get_ignore_requirements(self->config)) |
| install_flags |= FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS; |
| |
| /* additional requirements */ |
| if (!fu_engine_requirements_check(self, release, install_flags, error)) |
| return FALSE; |
| |
| /* match component properties */ |
| if (fu_release_has_flag(release, FWUPD_RELEASE_FLAG_TRUSTED_METADATA)) { |
| FuDevice *device = fu_release_get_device(release); |
| if (device != NULL) { |
| fu_device_ensure_from_component(device, component); |
| if (rel != NULL) |
| fu_device_ensure_from_release(device, rel); |
| } |
| } |
| |
| /* post-ensure checks */ |
| if (!fu_release_check_version(release, component, install_flags, error)) |
| return FALSE; |
| |
| /* add any client-side BKC tags */ |
| if (!fu_engine_add_local_release_metadata(self, release, error)) |
| return FALSE; |
| |
| /* add the trusted report metadata if appropriate */ |
| fu_engine_add_trusted_report(self, release); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /* finds the releases for all firmware in the silo that matches this |
| * container or artifact checksum */ |
| static GPtrArray * |
| fu_engine_get_releases_for_container_checksum(FuEngine *self, const gchar *csum) |
| { |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, csum, NULL); |
| if (self->query_container_checksum1 != NULL) { |
| g_autoptr(GPtrArray) rels = |
| xb_silo_query_with_context(self->silo, |
| self->query_container_checksum1, |
| &context, |
| NULL); |
| if (rels != NULL) |
| return g_steal_pointer(&rels); |
| } |
| if (self->query_container_checksum2 != NULL) { |
| g_autoptr(GPtrArray) rels = |
| xb_silo_query_with_context(self->silo, |
| self->query_container_checksum2, |
| &context, |
| NULL); |
| if (rels != NULL) |
| return g_steal_pointer(&rels); |
| } |
| |
| /* failed */ |
| return NULL; |
| } |
| |
| /* does this exist in any enabled remote */ |
| gchar * |
| fu_engine_get_remote_id_for_stream(FuEngine *self, GInputStream *stream) |
| { |
| GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
| |
| for (guint i = 0; checksum_types[i] != 0; i++) { |
| g_autofree gchar *csum = NULL; |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| csum = fu_input_stream_compute_checksum(stream, checksum_types[i], NULL); |
| if (csum == NULL) |
| continue; |
| rels = fu_engine_get_releases_for_container_checksum(self, csum); |
| if (rels == NULL) |
| continue; |
| for (guint j = 0; j < rels->len; j++) { |
| XbNode *rel = g_ptr_array_index(rels, j); |
| const gchar *remote_id = |
| xb_node_query_text(rel, |
| "../../../custom/value[@key='fwupd::RemoteId']", |
| NULL); |
| if (remote_id != NULL) |
| return g_strdup(remote_id); |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * fu_engine_unlock: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Unlocks a device. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_unlock(FuEngine *self, const gchar *device_id, GError **error) |
| { |
| FuPlugin *plugin; |
| g_autoptr(FuDevice) device = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check the device exists */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| |
| /* get the plugin */ |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* run the correct plugin that added this */ |
| if (!fu_plugin_runner_unlock(plugin, device, error)) |
| return FALSE; |
| |
| /* make the UI update */ |
| fu_engine_emit_device_changed_safe(self, device); |
| fu_engine_emit_changed(self); |
| return TRUE; |
| } |
| |
| gboolean |
| fu_engine_reset_config(FuEngine *self, const gchar *section, GError **error) |
| { |
| /* reset, effective next reboot */ |
| return fu_config_reset_defaults(FU_CONFIG(self->config), section, error); |
| } |
| |
| gboolean |
| fu_engine_modify_config(FuEngine *self, |
| const gchar *section, |
| const gchar *key, |
| const gchar *value, |
| GError **error) |
| { |
| FuPlugin *plugin; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(section != NULL, FALSE); |
| g_return_val_if_fail(key != NULL, FALSE); |
| g_return_val_if_fail(value != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check keys are valid */ |
| if (g_strcmp0(section, "fwupd") == 0) { |
| const gchar *keys[] = { |
| "ArchiveSizeMax", |
| "ApprovedFirmware", |
| "BlockedFirmware", |
| "DisabledDevices", |
| "DisabledPlugins", |
| "EnumerateAllDevices", |
| "EspLocation", |
| "HostBkc", |
| "IdleTimeout", |
| "IgnorePower", |
| "OnlyTrusted", |
| "P2pPolicy", |
| "ReleaseDedupe", |
| "ReleasePriority", |
| "RequireImmutableEnumeration", |
| "ShowDevicePrivate", |
| "TestDevices", |
| "TrustedReports", |
| "TrustedUids", |
| "UpdateMotd", |
| "UriSchemes", |
| "VerboseDomains", |
| NULL, |
| }; |
| if (!g_strv_contains(keys, key)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "key %s not supported for [%s]", |
| key, |
| section); |
| return FALSE; |
| } |
| |
| /* many options need a reboot after this */ |
| if (!fu_config_set_value(FU_CONFIG(self->config), section, key, value, error)) |
| return FALSE; |
| |
| /* reload remotes */ |
| if (g_strcmp0(key, "TestDevices") == 0 && |
| !fu_remote_list_set_testing_remote_enabled( |
| self->remote_list, |
| fu_engine_config_get_test_devices(self->config), |
| error)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* handled per-plugin */ |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, section, error); |
| if (plugin == NULL) |
| return FALSE; |
| return fu_plugin_runner_modify_config(plugin, key, value, error); |
| } |
| |
| /** |
| * fu_engine_modify_remote: |
| * @self: a #FuEngine |
| * @remote_id: a remote ID |
| * @key: the key, e.g. `Enabled` |
| * @value: the key, e.g. `true` |
| * @error: (nullable): optional return location for an error |
| * |
| * Updates the verification silo entry for a specific device. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_modify_remote(FuEngine *self, |
| const gchar *remote_id, |
| const gchar *key, |
| const gchar *value, |
| GError **error) |
| { |
| const gchar *keys[] = { |
| "ApprovalRequired", |
| "AutomaticReports", |
| "AutomaticSecurityReports", |
| "Enabled", |
| "FirmwareBaseURI", |
| "MetadataURI", |
| "ReportURI", |
| "Username", |
| "Password", |
| NULL, |
| }; |
| |
| /* check keys are valid */ |
| if (!g_strv_contains(keys, key)) { |
| g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "key %s not supported", key); |
| return FALSE; |
| } |
| return fu_remote_list_set_key_value(self->remote_list, remote_id, key, value, error); |
| } |
| |
| /** |
| * fu_engine_clean_remote: |
| * @self: a #FuEngine |
| * @remote_id: a remote ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Cleans a remote, deleting downloaded metadata files. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_clean_remote(FuEngine *self, const gchar *remote_id, GError **error) |
| { |
| FwupdRemote *remote = fu_remote_list_get_by_id(self->remote_list, remote_id); |
| if (remote == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "remote %s not found", |
| remote_id); |
| return FALSE; |
| } |
| if (!fu_remote_clean(remote, error)) |
| return FALSE; |
| fu_engine_metadata_changed(self); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_modify_single_bios_setting(FuEngine *self, |
| const gchar *key, |
| const gchar *value, |
| gboolean force_ro, |
| GError **error) |
| { |
| FwupdBiosSetting *attr = fu_context_get_bios_setting(self->ctx, key); |
| if (attr == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "attribute not found"); |
| return FALSE; |
| } |
| if (!fwupd_bios_setting_write_value(attr, value, error)) |
| return FALSE; |
| if (force_ro) |
| fwupd_bios_setting_set_read_only(attr, TRUE); |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_modify_bios_settings: |
| * @self: a #FuEngine |
| * @settings: Hashtable of settings/values to configure |
| * @force_ro: a #gboolean indicating if BIOS settings should also be made read-only |
| * @error: (nullable): optional return location for an error |
| * |
| * Use the kernel API to set one or more BIOS settings. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_modify_bios_settings(FuEngine *self, |
| GHashTable *settings, |
| gboolean force_ro, |
| GError **error) |
| { |
| g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); |
| gboolean changed = FALSE; |
| GHashTableIter iter; |
| gpointer key, value; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(settings != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| g_hash_table_iter_init(&iter, settings); |
| while (g_hash_table_iter_next(&iter, &key, &value)) { |
| g_autoptr(GError) error_local = NULL; |
| if (value == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "attribute %s missing value", |
| (const gchar *)key); |
| return FALSE; |
| } |
| if (g_strcmp0(key, FWUPD_BIOS_SETTING_SELF_TEST) == 0) { |
| if (fu_bios_settings_get_attr(bios_settings, key) == NULL) { |
| g_autoptr(FwupdBiosSetting) attr = fu_bios_setting_new(); |
| fwupd_bios_setting_set_name(attr, key); |
| fu_bios_settings_add_attribute(bios_settings, attr); |
| } |
| changed = TRUE; |
| continue; |
| } |
| if (!fu_engine_modify_single_bios_setting(self, |
| key, |
| value, |
| force_ro, |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { |
| g_debug("%s", error_local->message); |
| continue; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| changed = TRUE; |
| } |
| |
| if (!changed) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "no BIOS settings needed to be changed"); |
| return FALSE; |
| } |
| if (fu_bios_settings_get_attr(bios_settings, FWUPD_BIOS_SETTING_PENDING_REBOOT) != NULL) { |
| if (!fu_bios_settings_get_pending_reboot(bios_settings, &changed, error)) |
| return FALSE; |
| g_info("pending_reboot is now %d", changed); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_remove_device_flag(FuEngine *self, |
| const gchar *device_id, |
| FwupdDeviceFlags flag, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) { |
| device = fu_history_get_device_by_id(self->history, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| fu_device_remove_flag(device, flag); |
| return fu_history_modify_device(self->history, device, error); |
| } |
| if (flag == FWUPD_DEVICE_FLAG_EMULATED) { |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device %s is not emulated", |
| fu_device_get_id(device)); |
| return FALSE; |
| } |
| if (fu_device_get_backend(device) == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device %s requires backend", |
| fu_device_get_id(device)); |
| return FALSE; |
| } |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); |
| fu_backend_device_removed(fu_device_get_backend(device), device); |
| return TRUE; |
| } |
| if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) { |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device %s is not tagged for emulation", |
| fu_device_get_id(device)); |
| return FALSE; |
| } |
| fu_device_remove_flag(device, flag); |
| if (!fu_history_remove_emulation_tag(self->history, |
| fu_device_get_id(device), |
| error)) |
| return FALSE; |
| fu_engine_ensure_context_flag_save_events(self); |
| return TRUE; |
| } |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "flag cannot be removed from client"); |
| return FALSE; |
| } |
| |
| static void |
| fu_engine_emit_device_request_replug_and_install(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(FwupdRequest) request = fwupd_request_new(); |
| fwupd_request_set_id(request, FWUPD_REQUEST_ID_REPLUG_INSTALL); |
| fwupd_request_set_device_id(request, fu_device_get_id(device)); |
| fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); |
| fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); |
| fwupd_request_set_message(request, |
| "Unplug and replug the device, then install the firmware."); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); |
| } |
| |
| static void |
| fu_engine_emit_device_request_restart_daemon(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(FwupdRequest) request = fwupd_request_new(); |
| fwupd_request_set_id(request, FWUPD_REQUEST_ID_RESTART_DAEMON); |
| fwupd_request_set_device_id(request, fu_device_get_id(device)); |
| fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); |
| fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); |
| fwupd_request_set_message( |
| request, |
| "Please restart the fwupd service so device enumeration is recorded."); |
| g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); |
| } |
| |
| static gboolean |
| fu_engine_add_device_flag(FuEngine *self, |
| const gchar *device_id, |
| FwupdDeviceFlags flag, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| if (flag == FWUPD_DEVICE_FLAG_REPORTED || flag == FWUPD_DEVICE_FLAG_NOTIFIED) { |
| device = fu_history_get_device_by_id(self->history, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| fu_device_add_flag(device, flag); |
| return fu_history_modify_device(self->history, device, error); |
| } |
| if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) { |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_EMULATION_TAG)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device %s cannot be tagged for emulation", |
| fu_device_get_id(device)); |
| return FALSE; |
| } |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device %s is already tagged for emulation", |
| fu_device_get_id(device)); |
| return FALSE; |
| } |
| fu_device_add_flag(device, flag); |
| if (!fu_history_add_emulation_tag(self->history, fu_device_get_id(device), error)) |
| return FALSE; |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INTERNAL)) { |
| fu_engine_emit_device_request_restart_daemon(self, device); |
| } else { |
| fu_engine_emit_device_request_replug_and_install(self, device); |
| } |
| fu_engine_ensure_context_flag_save_events(self); |
| return TRUE; |
| } |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "flag cannot be added from client"); |
| return FALSE; |
| } |
| |
| static gboolean |
| fu_engine_modify_device_flags(FuEngine *self, |
| const gchar *device_id, |
| const gchar *value, |
| GError **error) |
| { |
| /* add or remove a subset of device flags */ |
| if (g_str_has_prefix(value, "~")) { |
| return fu_engine_remove_device_flag(self, |
| device_id, |
| fwupd_device_flag_from_string(value + 1), |
| error); |
| } |
| return fu_engine_add_device_flag(self, |
| device_id, |
| fwupd_device_flag_from_string(value), |
| error); |
| } |
| |
| /** |
| * fu_engine_modify_device: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @key: the key, e.g. `Flags` |
| * @value: the key, e.g. `reported` |
| * @error: (nullable): optional return location for an error |
| * |
| * Sets the reported flag for a specific device. This ensures that other |
| * front-end clients for fwupd do not report the same event. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_modify_device(FuEngine *self, |
| const gchar *device_id, |
| const gchar *key, |
| const gchar *value, |
| GError **error) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(key != NULL, FALSE); |
| g_return_val_if_fail(value != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| if (g_strcmp0(key, "Flags") == 0) |
| return fu_engine_modify_device_flags(self, device_id, value, error); |
| g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not supported", key); |
| return FALSE; |
| } |
| |
| static const gchar * |
| fu_engine_checksum_type_to_string(GChecksumType checksum_type) |
| { |
| if (checksum_type == G_CHECKSUM_SHA1) |
| return "sha1"; |
| if (checksum_type == G_CHECKSUM_SHA256) |
| return "sha256"; |
| if (checksum_type == G_CHECKSUM_SHA512) |
| return "sha512"; |
| return "sha1"; |
| } |
| |
| /** |
| * fu_engine_verify_update: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Updates the verification silo entry for a specific device. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_verify_update(FuEngine *self, |
| const gchar *device_id, |
| FuProgress *progress, |
| GError **error) |
| { |
| FuPlugin *plugin; |
| GPtrArray *checksums; |
| GPtrArray *guids; |
| g_autofree gchar *fn = NULL; |
| g_autofree gchar *localstatedir = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GFile) file = NULL; |
| g_autoptr(XbBuilder) builder = xb_builder_new(); |
| g_autoptr(XbBuilderNode) component = NULL; |
| g_autoptr(XbBuilderNode) provides = NULL; |
| g_autoptr(XbBuilderNode) release = NULL; |
| g_autoptr(XbBuilderNode) releases = NULL; |
| g_autoptr(XbSilo) silo = NULL; |
| g_autoptr(FuDeviceProgress) device_progress = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check the devices still exists */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| device_progress = fu_device_progress_new(device, progress); |
| g_return_val_if_fail(device_progress != NULL, FALSE); |
| |
| /* get the plugin */ |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* get the checksum */ |
| checksums = fu_device_get_checksums(device); |
| if (checksums->len == 0) { |
| if (!fu_plugin_runner_verify(plugin, |
| device, |
| progress, |
| FU_PLUGIN_VERIFY_FLAG_NONE, |
| error)) |
| return FALSE; |
| fu_engine_emit_device_changed_safe(self, device); |
| } |
| |
| /* we got nothing */ |
| if (checksums->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device verification not supported"); |
| return FALSE; |
| } |
| |
| /* build XML */ |
| component = xb_builder_node_insert(NULL, "component", "type", "firmware", NULL); |
| provides = xb_builder_node_insert(component, "provides", NULL); |
| guids = fu_device_get_guids(device); |
| for (guint i = 0; i < guids->len; i++) { |
| const gchar *guid = g_ptr_array_index(guids, i); |
| g_autoptr(XbBuilderNode) provide = NULL; |
| provide = xb_builder_node_insert(provides, "firmware", "type", "flashed", NULL); |
| xb_builder_node_set_text(provide, guid, -1); |
| } |
| releases = xb_builder_node_insert(component, "releases", NULL); |
| release = xb_builder_node_insert(releases, |
| "release", |
| "version", |
| fu_device_get_version(device), |
| NULL); |
| for (guint i = 0; i < checksums->len; i++) { |
| const gchar *checksum = g_ptr_array_index(checksums, i); |
| GChecksumType kind = fwupd_checksum_guess_kind(checksum); |
| g_autoptr(XbBuilderNode) csum = NULL; |
| csum = xb_builder_node_insert(release, |
| "checksum", |
| "type", |
| fu_engine_checksum_type_to_string(kind), |
| "target", |
| "content", |
| NULL); |
| xb_builder_node_set_text(csum, checksum, -1); |
| } |
| xb_builder_import_node(builder, component); |
| |
| /* save silo */ |
| localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); |
| fn = g_strdup_printf("%s/verify/%s.xml", localstatedir, device_id); |
| if (!fu_path_mkdir_parent(fn, error)) |
| return FALSE; |
| file = g_file_new_for_path(fn); |
| silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); |
| if (silo == NULL) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| if (!xb_silo_export_file(silo, file, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE, NULL, error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static XbNode * |
| fu_engine_get_component_by_guid(FuEngine *self, const gchar *guid) |
| { |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(XbNode) component = NULL; |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| |
| /* no components in silo */ |
| if (self->query_component_by_guid == NULL) |
| return NULL; |
| |
| xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); |
| component = xb_silo_query_first_with_context(self->silo, |
| self->query_component_by_guid, |
| &context, |
| &error_local); |
| if (component == NULL) { |
| if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && |
| !g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) |
| g_warning("ignoring: %s", error_local->message); |
| return NULL; |
| } |
| return g_object_ref(component); |
| } |
| |
| XbNode * |
| fu_engine_get_component_by_guids(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *guids = fu_device_get_guids(device); |
| XbNode *component = NULL; |
| fu_device_convert_instance_ids(device); |
| for (guint i = 0; i < guids->len; i++) { |
| const gchar *guid = g_ptr_array_index(guids, i); |
| component = fu_engine_get_component_by_guid(self, guid); |
| if (component != NULL) |
| break; |
| } |
| return component; |
| } |
| |
| static XbNode * |
| fu_engine_verify_from_local_metadata(FuEngine *self, FuDevice *device, GError **error) |
| { |
| g_autofree gchar *fn = NULL; |
| g_autofree gchar *localstatedir = NULL; |
| g_autofree gchar *xpath = NULL; |
| g_autoptr(GFile) file = NULL; |
| g_autoptr(XbBuilder) builder = xb_builder_new(); |
| g_autoptr(XbBuilderSource) source = xb_builder_source_new(); |
| g_autoptr(XbNode) release = NULL; |
| g_autoptr(XbSilo) silo = NULL; |
| |
| localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); |
| fn = g_strdup_printf("%s/verify/%s.xml", localstatedir, fu_device_get_id(device)); |
| file = g_file_new_for_path(fn); |
| if (!g_file_query_exists(file, NULL)) { |
| g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", fn); |
| return NULL; |
| } |
| |
| if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| xb_builder_import_source(builder, source); |
| silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); |
| if (silo == NULL) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| xpath = g_strdup_printf("component/releases/release[@version='%s']", |
| fu_device_get_version(device)); |
| release = xb_silo_query_first(silo, xpath, error); |
| if (release == NULL) |
| return NULL; |
| |
| /* silo has to have same lifecycle as node */ |
| g_object_set_data_full(G_OBJECT(release), |
| "XbSilo", |
| g_steal_pointer(&silo), |
| (GDestroyNotify)g_object_unref); |
| return g_steal_pointer(&release); |
| } |
| |
| static XbNode * |
| fu_engine_verify_from_system_metadata(FuEngine *self, FuDevice *device, GError **error) |
| { |
| FwupdVersionFormat fmt = fu_device_get_version_format(device); |
| GPtrArray *guids = fu_device_get_guids(device); |
| g_autoptr(XbQuery) query = NULL; |
| |
| /* prepare query with bound GUID parameter */ |
| query = xb_query_new_full(self->silo, |
| "components/component[@type='firmware']/" |
| "provides/firmware[@type='flashed'][text()=?]/" |
| "../../releases/release", |
| XB_QUERY_FLAG_OPTIMIZE | XB_QUERY_FLAG_USE_INDEXES, |
| error); |
| if (query == NULL) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| |
| /* use prepared query for each GUID */ |
| for (guint i = 0; i < guids->len; i++) { |
| const gchar *guid = g_ptr_array_index(guids, i); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| |
| /* bind GUID and then query */ |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); |
| releases = xb_silo_query_with_context(self->silo, query, &context, &error_local); |
| if (releases == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || |
| g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { |
| g_debug("could not find %s: %s", guid, error_local->message); |
| continue; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| for (guint j = 0; j < releases->len; j++) { |
| XbNode *rel = g_ptr_array_index(releases, j); |
| const gchar *rel_ver = xb_node_get_attr(rel, "version"); |
| g_autofree gchar *tmp_ver = fu_version_parse_from_format(rel_ver, fmt); |
| if (fu_version_compare(tmp_ver, fu_device_get_version(device), fmt) == 0) |
| return g_object_ref(rel); |
| } |
| } |
| |
| /* not found */ |
| g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find release"); |
| return NULL; |
| } |
| |
| /** |
| * fu_engine_verify: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Verifies a device firmware checksum using the verification silo entry. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_verify(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) |
| { |
| FuPlugin *plugin; |
| GPtrArray *checksums; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GString) xpath_csum = g_string_new(NULL); |
| g_autoptr(XbNode) csum = NULL; |
| g_autoptr(XbNode) release = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check the id exists */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| |
| /* get the plugin */ |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* update the device firmware hashes if possible */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) { |
| if (!fu_plugin_runner_verify(plugin, |
| device, |
| progress, |
| FU_PLUGIN_VERIFY_FLAG_NONE, |
| error)) |
| return FALSE; |
| } |
| |
| /* find component in local metadata */ |
| release = fu_engine_verify_from_local_metadata(self, device, &error_local); |
| if (release == NULL) { |
| if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && |
| !g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| } |
| |
| /* try again with the system metadata */ |
| if (release == NULL) { |
| g_autoptr(GError) error_system = NULL; |
| release = fu_engine_verify_from_system_metadata(self, device, &error_system); |
| if (release == NULL) { |
| if (!g_error_matches(error_system, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND) && |
| !g_error_matches(error_system, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA)) { |
| g_propagate_error(error, g_steal_pointer(&error_system)); |
| return FALSE; |
| } |
| } |
| } |
| if (release == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "No release found for version %s", |
| fu_device_get_version(device)); |
| return FALSE; |
| } |
| |
| /* get the matching checksum */ |
| checksums = fu_device_get_checksums(device); |
| if (checksums->len == 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "No device checksums for %s", |
| fu_device_get_version(device)); |
| return FALSE; |
| } |
| |
| /* do any of the checksums in the release match any in the device */ |
| for (guint j = 0; j < checksums->len; j++) { |
| const gchar *hash_tmp = g_ptr_array_index(checksums, j); |
| xb_string_append_union(xpath_csum, |
| "checksum[@target='device'][text()='%s']", |
| hash_tmp); |
| xb_string_append_union(xpath_csum, |
| "checksum[@target='content'][text()='%s']", |
| hash_tmp); |
| } |
| csum = xb_node_query_first(release, xpath_csum->str, NULL); |
| if (csum == NULL) { |
| g_autofree gchar *checksums_device = fu_strjoin("|", checksums); |
| g_autoptr(GString) checksums_metadata = g_string_new(NULL); |
| g_autoptr(GPtrArray) csums = NULL; |
| g_autoptr(GString) xpath = g_string_new(NULL); |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| |
| /* get all checksums to display a useful error */ |
| xb_string_append_union(xpath, "checksum[@target='device']"); |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) |
| xb_string_append_union(xpath, "checksum[@target='content']"); |
| csums = xb_node_query(release, xpath->str, 0, NULL); |
| if (csums == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "No stored checksums for %s", |
| fu_device_get_version(device)); |
| return FALSE; |
| } |
| for (guint i = 0; i < csums->len; i++) { |
| XbNode *csum_tmp = g_ptr_array_index(csums, i); |
| xb_string_append_union(checksums_metadata, |
| "%s", |
| xb_node_get_text(csum_tmp)); |
| } |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "For %s %s expected %s, got %s", |
| id_display, |
| fu_device_get_version(device), |
| checksums_metadata->str, |
| checksums_device); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| gboolean |
| fu_engine_check_trust(FuEngine *self, FuRelease *release, GError **error) |
| { |
| g_autofree gchar *str = fu_release_to_string(release); |
| |
| g_debug("checking trust of %s", str); |
| if (fu_engine_config_get_only_trusted(self->config) && |
| !fu_release_has_flag(release, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) { |
| g_autofree gchar *sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); |
| g_autofree gchar *fn = g_build_filename(sysconfdir, "fwupd.conf", NULL); |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "firmware signature missing or not trusted; " |
| "set OnlyTrusted=false in %s ONLY if you are a firmware developer", |
| fn); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| void |
| fu_engine_idle_reset(FuEngine *self) |
| { |
| fu_idle_reset(self->idle); |
| } |
| |
| guint32 |
| fu_engine_idle_inhibit(FuEngine *self, FuIdleInhibit inhibit, const gchar *reason) |
| { |
| return fu_idle_inhibit(self->idle, inhibit, reason); |
| } |
| |
| void |
| fu_engine_idle_uninhibit(FuEngine *self, guint32 token) |
| { |
| fu_idle_uninhibit(self->idle, token); |
| } |
| |
| static gchar * |
| fu_engine_get_boot_time(void) |
| { |
| g_autofree gchar *buf = NULL; |
| g_auto(GStrv) lines = NULL; |
| if (!g_file_get_contents("/proc/stat", &buf, NULL, NULL)) |
| return NULL; |
| lines = g_strsplit(buf, "\n", -1); |
| for (guint i = 0; lines[i] != NULL; i++) { |
| if (g_str_has_prefix(lines[i], "btime ")) |
| return g_strdup(lines[i] + 6); |
| } |
| return NULL; |
| } |
| |
| static FuDevice * |
| fu_engine_get_cpu_device(FuEngine *self) |
| { |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| if (fu_device_has_private_flag_quark(device, quarks[QUARK_HOST_CPU])) |
| return g_object_ref(device); |
| } |
| return NULL; |
| } |
| |
| static void |
| fu_engine_get_report_metadata_cpu_device(FuEngine *self, GHashTable *hash) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| device = fu_engine_get_cpu_device(self); |
| if (device == NULL) { |
| g_info("failed to find CPU device"); |
| return; |
| } |
| if (fu_device_get_vendor(device) == NULL || fu_device_get_name(device) == NULL) { |
| g_info("not enough data to include CpuModel"); |
| return; |
| } |
| g_hash_table_insert( |
| hash, |
| g_strdup("CpuModel"), |
| g_strdup_printf("%s %s", fu_device_get_vendor(device), fu_device_get_name(device))); |
| } |
| |
| static gboolean |
| fu_engine_get_report_metadata_os_release(GHashTable *hash, GError **error) |
| { |
| #ifdef HOST_MACHINE_SYSTEM_DARWIN |
| g_autofree gchar *stdout = NULL; |
| g_autofree gchar *sw_vers = g_find_program_in_path("sw_vers"); |
| g_auto(GStrv) split = NULL; |
| struct { |
| const gchar *key; |
| const gchar *val; |
| } kvs[] = {{"ProductName:", "DistroName"}, |
| {"ProductVersion:", FWUPD_RESULT_KEY_DISTRO_VERSION}, |
| {"BuildVersion:", FWUPD_RESULT_KEY_DISTRO_VARIANT}, |
| {NULL, NULL}}; |
| |
| /* macOS */ |
| if (sw_vers == NULL) { |
| g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "No os-release found"); |
| return FALSE; |
| } |
| |
| /* parse from format: |
| * ProductName: Mac OS X |
| * ProductVersion: 10.14.6 |
| * BuildVersion: 18G103 |
| */ |
| if (!g_spawn_command_line_sync(sw_vers, &stdout, NULL, NULL, error)) |
| return FALSE; |
| split = g_strsplit(stdout, "\n", -1); |
| for (guint j = 0; split[j] != NULL; j++) { |
| for (guint i = 0; kvs[i].key != NULL; i++) { |
| if (g_str_has_prefix(split[j], kvs[i].key)) { |
| g_autofree gchar *tmp = g_strdup(split[j] + strlen(kvs[i].key)); |
| g_hash_table_insert(hash, |
| g_strdup(kvs[i].val), |
| g_strdup(g_strstrip(tmp))); |
| } |
| } |
| } |
| g_hash_table_insert(hash, g_strdup(FWUPD_RESULT_KEY_DISTRO_ID), g_strdup("macos")); |
| #else |
| struct { |
| const gchar *key; |
| const gchar *val; |
| } distro_kv[] = {{G_OS_INFO_KEY_ID, FWUPD_RESULT_KEY_DISTRO_ID}, |
| {G_OS_INFO_KEY_NAME, "DistroName"}, |
| {G_OS_INFO_KEY_PRETTY_NAME, "DistroPrettyName"}, |
| {G_OS_INFO_KEY_VERSION_ID, FWUPD_RESULT_KEY_DISTRO_VERSION}, |
| {"VARIANT_ID", FWUPD_RESULT_KEY_DISTRO_VARIANT}, |
| {NULL, NULL}}; |
| |
| /* get all required os-release keys */ |
| for (guint i = 0; distro_kv[i].key != NULL; i++) { |
| g_autofree gchar *tmp = g_get_os_info(distro_kv[i].key); |
| if (tmp != NULL) { |
| g_hash_table_insert(hash, |
| g_strdup(distro_kv[i].val), |
| g_steal_pointer(&tmp)); |
| } |
| } |
| #endif |
| return TRUE; |
| } |
| |
| static GHashTable * |
| fu_engine_load_os_release(const gchar *filename, GError **error) |
| { |
| g_autofree gchar *buf = NULL; |
| g_autofree gchar *filename2 = g_strdup(filename); |
| g_auto(GStrv) lines = NULL; |
| g_autoptr(GHashTable) hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
| |
| /* load each line */ |
| if (!g_file_get_contents(filename2, &buf, NULL, error)) |
| return NULL; |
| lines = g_strsplit(buf, "\n", -1); |
| for (guint i = 0; lines[i] != NULL; i++) { |
| gsize len, off = 0; |
| g_auto(GStrv) split = NULL; |
| |
| /* split up into sections */ |
| split = g_strsplit(lines[i], "=", 2); |
| if (g_strv_length(split) < 2) |
| continue; |
| |
| /* remove double quotes if set both ends */ |
| len = strlen(split[1]); |
| if (len == 0) |
| continue; |
| if (split[1][0] == '\"' && split[1][len - 1] == '\"') { |
| off++; |
| len -= 2; |
| } |
| g_hash_table_insert(hash, g_strdup(split[0]), g_strndup(split[1] + off, len)); |
| } |
| return g_steal_pointer(&hash); |
| } |
| |
| static gboolean |
| fu_engine_get_report_metadata_lsb_release(GHashTable *hash, GError **error) |
| { |
| const gchar *fn = "/etc/lsb-release"; |
| g_autoptr(GHashTable) os_release = NULL; |
| struct { |
| const gchar *key; |
| const gchar *val; |
| } distro_kv[] = {{"CHROMEOS_RELEASE_TRACK", "DistroReleaseTrack"}, |
| {"CHROMEOS_RELEASE_BOARD", "DistroReleaseBoard"}, |
| {NULL, NULL}}; |
| if (!g_file_test(fn, G_FILE_TEST_EXISTS)) |
| return TRUE; |
| os_release = fu_engine_load_os_release(fn, error); |
| if (os_release == NULL) |
| return FALSE; |
| for (guint i = 0; distro_kv[i].key != NULL; i++) { |
| const gchar *tmp = g_hash_table_lookup(os_release, distro_kv[i].key); |
| if (tmp != NULL) |
| g_hash_table_insert(hash, g_strdup(distro_kv[i].val), g_strdup(tmp)); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_get_report_metadata_kernel_cmdline(GHashTable *hash, GError **error) |
| { |
| g_autofree gchar *cmdline = NULL; |
| cmdline = fu_common_get_kernel_cmdline(error); |
| if (cmdline == NULL) |
| return FALSE; |
| if (cmdline[0] != '\0') |
| g_hash_table_insert(hash, g_strdup("KernelCmdline"), g_steal_pointer(&cmdline)); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_get_report_metadata_selinux(GHashTable *hash, GError **error) |
| { |
| g_autofree gchar *buf = NULL; |
| g_autofree gchar *sysfsdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR); |
| g_autofree gchar *filename = g_build_filename(sysfsdir, "fs", "selinux", "enforce", NULL); |
| |
| if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { |
| g_debug("no %s, skipping", filename); |
| return TRUE; |
| } |
| if (!g_file_get_contents(filename, &buf, NULL, error)) |
| return FALSE; |
| if (g_strcmp0(buf, "1") == 0) { |
| g_hash_table_insert(hash, g_strdup("SELinux"), g_strdup("enforcing")); |
| return TRUE; |
| } |
| g_hash_table_insert(hash, g_strdup("SELinux"), g_strdup("permissive")); |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_add_report_metadata_bool(GHashTable *hash, const gchar *key, gboolean value) |
| { |
| g_hash_table_insert(hash, g_strdup(key), g_strdup(value ? "True" : "False")); |
| } |
| |
| #ifdef HAVE_PASSIM |
| static void |
| fu_engine_ensure_passim_client(FuEngine *self) |
| { |
| g_autoptr(GError) error_local = NULL; |
| |
| /* disabled */ |
| if (fu_engine_config_get_p2p_policy(self->config) == FU_P2P_POLICY_NOTHING) |
| return; |
| |
| /* already loaded */ |
| if (passim_client_get_version(self->passim_client) != NULL) |
| return; |
| |
| /* connect to passimd */ |
| if (!passim_client_load(self->passim_client, &error_local)) |
| g_debug("failed to load Passim: %s", error_local->message); |
| if (passim_client_get_version(self->passim_client) != NULL) { |
| fu_engine_add_runtime_version(self, |
| "org.freedesktop.Passim", |
| passim_client_get_version(self->passim_client)); |
| } |
| } |
| #endif |
| |
| GHashTable * |
| fu_engine_get_report_metadata(FuEngine *self, GError **error) |
| { |
| FuEfivars *efivars = fu_context_get_efivars(self->ctx); |
| GHashTable *compile_versions = fu_context_get_compile_versions(self->ctx); |
| GHashTable *runtime_versions = fu_context_get_runtime_versions(self->ctx); |
| const gchar *tmp; |
| gchar *btime; |
| guint64 nvram_total; |
| #ifdef HAVE_UTSNAME_H |
| struct utsname name_tmp = {0}; |
| #endif |
| g_autoptr(GHashTable) hash = NULL; |
| g_autoptr(GList) compile_keys = g_hash_table_get_keys(compile_versions); |
| g_autoptr(GList) runtime_keys = g_hash_table_get_keys(runtime_versions); |
| |
| /* convert all the runtime and compile-time versions */ |
| hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
| for (GList *l = compile_keys; l != NULL; l = l->next) { |
| const gchar *id = l->data; |
| const gchar *version = g_hash_table_lookup(compile_versions, id); |
| g_hash_table_insert(hash, |
| g_strdup_printf("CompileVersion(%s)", id), |
| g_strdup(version)); |
| } |
| for (GList *l = runtime_keys; l != NULL; l = l->next) { |
| const gchar *id = l->data; |
| const gchar *version = g_hash_table_lookup(runtime_versions, id); |
| g_hash_table_insert(hash, |
| g_strdup_printf("RuntimeVersion(%s)", id), |
| g_strdup(version)); |
| } |
| fu_engine_get_report_metadata_cpu_device(self, hash); |
| if (!fu_engine_get_report_metadata_os_release(hash, error)) |
| return NULL; |
| if (!fu_engine_get_report_metadata_lsb_release(hash, error)) |
| return NULL; |
| if (!fu_engine_get_report_metadata_kernel_cmdline(hash, error)) |
| return NULL; |
| if (!fu_engine_get_report_metadata_selinux(hash, error)) |
| return NULL; |
| |
| /* useful for almost all plugins */ |
| nvram_total = fu_efivars_space_used(efivars, NULL); |
| if (nvram_total != G_MAXUINT64) { |
| g_hash_table_insert(hash, |
| g_strdup("EfivarsNvramUsed"), |
| g_strdup_printf("%" G_GUINT64_FORMAT, nvram_total)); |
| } |
| nvram_total = fu_efivars_space_free(efivars, NULL); |
| if (nvram_total != G_MAXUINT64) { |
| g_hash_table_insert(hash, |
| g_strdup("EfivarsNvramFree"), |
| g_strdup_printf("%" G_GUINT64_FORMAT, nvram_total)); |
| } |
| |
| /* these affect the report credibility */ |
| #ifdef SUPPORTED_BUILD |
| fu_engine_add_report_metadata_bool(hash, "FwupdSupported", TRUE); |
| #else |
| fu_engine_add_report_metadata_bool(hash, "FwupdSupported", FALSE); |
| #endif |
| |
| /* find out what BKC is being targeted to understand "odd" upgrade paths */ |
| tmp = fu_engine_config_get_host_bkc(self->config); |
| if (tmp != NULL) |
| g_hash_table_insert(hash, g_strdup("HostBkc"), g_strdup(tmp)); |
| |
| #ifdef HAVE_PASSIM |
| /* this is useful to know if passim support is actually helping bandwidth use */ |
| fu_engine_ensure_passim_client(self); |
| g_hash_table_insert( |
| hash, |
| g_strdup("PassimDownloadSaving"), |
| g_strdup_printf("%" G_GUINT64_FORMAT, |
| passim_client_get_download_saving(self->passim_client))); |
| #endif |
| |
| /* DMI data */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) { |
| struct { |
| const gchar *hwid; |
| const gchar *name; |
| } keys[] = {{FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, "HostBaseboardManufacturer"}, |
| {FU_HWIDS_KEY_BASEBOARD_PRODUCT, "HostBaseboardProduct"}, |
| {FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, "HostBiosMajorRelease"}, |
| {FU_HWIDS_KEY_BIOS_MINOR_RELEASE, "HostBiosMinorRelease"}, |
| {FU_HWIDS_KEY_BIOS_VENDOR, "HostBiosVendor"}, |
| {FU_HWIDS_KEY_BIOS_VERSION, "HostBiosVersion"}, |
| {FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE, "HostFirmwareMajorRelease"}, |
| {FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE, "HostFirmwareMinorRelease"}, |
| {FU_HWIDS_KEY_ENCLOSURE_KIND, "HostEnclosureKind"}, |
| {FU_HWIDS_KEY_FAMILY, "HostFamily"}, |
| {FU_HWIDS_KEY_MANUFACTURER, "HostVendor"}, |
| {FU_HWIDS_KEY_PRODUCT_NAME, "HostProduct"}, |
| {FU_HWIDS_KEY_PRODUCT_SKU, "HostSku"}, |
| {NULL, NULL}}; |
| for (guint i = 0; keys[i].hwid != NULL; i++) { |
| tmp = fu_context_get_hwid_value(self->ctx, keys[i].hwid); |
| if (tmp != NULL) |
| g_hash_table_insert(hash, g_strdup(keys[i].name), g_strdup(tmp)); |
| } |
| } |
| |
| /* kernel version is often important for debugging failures */ |
| #ifdef HAVE_UTSNAME_H |
| if (uname(&name_tmp) >= 0) { |
| g_hash_table_insert(hash, g_strdup("CpuArchitecture"), g_strdup(name_tmp.machine)); |
| g_hash_table_insert(hash, g_strdup("KernelName"), g_strdup(name_tmp.sysname)); |
| g_hash_table_insert(hash, g_strdup("KernelVersion"), g_strdup(name_tmp.release)); |
| } |
| #endif |
| #if defined(HAVE_AUXV_H) && !defined(__FreeBSD__) |
| /* this is the architecture of the userspace, e.g. i686 would be returned for |
| * glibc-2.40-17.fc41.i686 on kernel-6.12.9-200.fc41.x86_64 */ |
| g_hash_table_insert(hash, |
| g_strdup("PlatformArchitecture"), |
| g_strdup((const gchar *)getauxval(AT_PLATFORM))); |
| #endif |
| |
| /* add the kernel boot time so we can detect a reboot */ |
| btime = fu_engine_get_boot_time(); |
| if (btime != NULL) |
| g_hash_table_insert(hash, g_strdup("BootTime"), btime); |
| |
| /* add context information */ |
| g_hash_table_insert( |
| hash, |
| g_strdup("PowerState"), |
| g_strdup(fu_power_state_to_string(fu_context_get_power_state(self->ctx)))); |
| g_hash_table_insert( |
| hash, |
| g_strdup("DisplayState"), |
| g_strdup(fu_display_state_to_string(fu_context_get_display_state(self->ctx)))); |
| g_hash_table_insert(hash, |
| g_strdup("LidState"), |
| g_strdup(fu_lid_state_to_string(fu_context_get_lid_state(self->ctx)))); |
| g_hash_table_insert(hash, |
| g_strdup("BatteryLevel"), |
| g_strdup_printf("%u", fu_context_get_battery_level(self->ctx))); |
| g_hash_table_insert(hash, |
| g_strdup("BatteryThreshold"), |
| g_strdup_printf("%u", fu_context_get_battery_threshold(self->ctx))); |
| |
| return g_steal_pointer(&hash); |
| } |
| |
| /** |
| * fu_engine_composite_prepare: |
| * @self: a #FuEngine |
| * @devices: (element-type #FuDevice): devices that will be updated |
| * @error: (nullable): optional return location for an error |
| * |
| * Calls into the plugin loader, informing each plugin of the pending upgrade(s). |
| * |
| * Any failure in any plugin will abort all of the actions before they are started. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_composite_prepare(FuEngine *self, GPtrArray *devices, GError **error) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| gboolean any_emulated = FALSE; |
| |
| /* we are emulating a device */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) |
| any_emulated = TRUE; |
| } |
| if (any_emulated) { |
| if (!fu_engine_emulator_load_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| if (!fu_plugin_runner_composite_prepare(plugin_tmp, devices, error)) |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !any_emulated) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for composite prepare: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_composite_cleanup: |
| * @self: a #FuEngine |
| * @devices: (element-type #FuDevice): devices that will be updated |
| * @error: (nullable): optional return location for an error |
| * |
| * Calls into the plugin loader, informing each plugin of the pending upgrade(s). |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_composite_cleanup(FuEngine *self, GPtrArray *devices, GError **error) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| gboolean any_emulated = FALSE; |
| |
| /* we are emulating a device */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) |
| any_emulated = TRUE; |
| } |
| if (any_emulated) { |
| if (!fu_engine_emulator_load_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| if (!fu_plugin_runner_composite_cleanup(plugin_tmp, devices, error)) |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !any_emulated) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for composite cleanup: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gint |
| fu_engine_sort_release_device_order_release_version_cb(gconstpointer a, gconstpointer b) |
| { |
| FuRelease *na = *((FuRelease **)a); |
| FuRelease *nb = *((FuRelease **)b); |
| return fu_release_compare(na, nb); |
| } |
| |
| static gboolean |
| fu_engine_publish_release(FuEngine *self, FuRelease *release, GError **error) |
| { |
| #ifdef HAVE_PASSIM |
| FuDevice *device = fu_release_get_device(release); |
| GInputStream *stream = fu_release_get_stream(release); |
| |
| /* lazy load */ |
| fu_engine_ensure_passim_client(self); |
| |
| /* send to passimd, if enabled and running */ |
| if (passim_client_get_version(self->passim_client) != NULL && |
| fu_engine_config_get_p2p_policy(self->config) & FU_P2P_POLICY_FIRMWARE) { |
| gsize streamsz = 0; |
| g_autofree gchar *basename = g_path_get_basename(fu_release_get_filename(release)); |
| g_autofree gchar *checksum = NULL; |
| g_autoptr(GError) error_passim = NULL; |
| g_autoptr(PassimItem) passim_item = passim_item_new(); |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) || |
| fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) |
| passim_item_add_flag(passim_item, PASSIM_ITEM_FLAG_NEXT_REBOOT); |
| passim_item_set_max_age(passim_item, 30 * 24 * 60 * 60); |
| passim_item_set_share_limit(passim_item, 50); |
| passim_item_set_basename(passim_item, basename); |
| checksum = fu_input_stream_compute_checksum(stream, G_CHECKSUM_SHA256, error); |
| if (checksum == NULL) |
| return FALSE; |
| if (!fu_input_stream_size(stream, &streamsz, error)) |
| return FALSE; |
| passim_item_set_size(passim_item, streamsz); |
| passim_item_set_stream(passim_item, stream); |
| passim_item_set_hash(passim_item, checksum); |
| if (!passim_client_publish(self->passim_client, passim_item, &error_passim)) { |
| if (!g_error_matches(error_passim, G_IO_ERROR, G_IO_ERROR_EXISTS)) { |
| g_warning("failed to publish firmware to Passim: %s", |
| error_passim->message); |
| } |
| } else { |
| g_debug("published %s to Passim", passim_item_get_hash(passim_item)); |
| } |
| } |
| #endif |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_install_release_version_check(FuEngine *self, |
| FuRelease *release, |
| FuDevice *device, |
| GError **error) |
| { |
| FwupdVersionFormat fmt = fu_device_get_version_format(device); |
| const gchar *version_rel = fu_release_get_version(release); |
| const gchar *version_old = fu_release_get_device_version_old(release); |
| if (version_rel != NULL && fu_version_compare(version_old, version_rel, fmt) != 0 && |
| fu_version_compare(version_old, fu_device_get_version(device), fmt) == 0 && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INSTALL_SKIP_VERSION_CHECK) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "device version not updated on success, %s != %s", |
| version_rel, |
| fu_device_get_version(device)); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_install_releases: |
| * @self: a #FuEngine |
| * @request: a #FuEngineRequest |
| * @releases: (element-type FuRelease): a device |
| * @cabinet: a #FuCabinet |
| * @flags: install flags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE |
| * @error: (nullable): optional return location for an error |
| * |
| * Installs a specific firmware file on one or more install tasks. |
| * |
| * By this point all the requirements and tests should have been done in |
| * fu_engine_requirements_check() so this should not fail before running |
| * the plugin loader. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_install_releases(FuEngine *self, |
| FuEngineRequest *request, |
| GPtrArray *releases, |
| FuCabinet *cabinet, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuIdleLocker) locker = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_new = NULL; |
| |
| /* do not allow auto-shutdown during this time */ |
| locker = fu_idle_locker_new(self->idle, |
| FU_IDLE_INHIBIT_TIMEOUT | FU_IDLE_INHIBIT_SIGNALS, |
| "update"); |
| g_return_val_if_fail(locker != NULL, FALSE); |
| |
| /* use an allow-list for device-changed signals -- only allow any of the composite update |
| * devices to emit signals for the duration of the install */ |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| FuDevice *device = fu_release_get_device(release); |
| g_hash_table_insert(self->device_changed_allowlist, |
| g_strdup(fu_device_get_id(device)), |
| GUINT_TO_POINTER(1)); |
| } |
| |
| /* install these in the right order */ |
| g_ptr_array_sort(releases, fu_engine_sort_release_device_order_release_version_cb); |
| |
| /* notify the plugins about the composite action */ |
| devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| FuDevice *device = fu_release_get_device(release); |
| const gchar *logical_id = fu_device_get_logical_id(device); |
| g_info("composite update %u: %s %s->%s (%s, order:%i: priority:%u)", |
| i + 1, |
| fu_device_get_id(device), |
| fu_device_get_version(device), |
| fu_release_get_version(release), |
| logical_id != NULL ? logical_id : "n/a", |
| fu_device_get_order(device), |
| (guint)fu_release_get_priority(release)); |
| g_ptr_array_add(devices, g_object_ref(device)); |
| } |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_COMPOSITE_PREPARE); |
| if (!fu_engine_composite_prepare(self, devices, error)) { |
| g_prefix_error_literal(error, "failed to prepare composite action: "); |
| return FALSE; |
| } |
| |
| /* all authenticated, so install all the things */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, releases->len); |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| if (!fu_engine_install_release(self, |
| release, |
| fu_progress_get_child(progress), |
| flags, |
| error)) { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_engine_composite_cleanup(self, devices, &error_local)) { |
| g_warning("failed to cleanup failed composite action: %s", |
| error_local->message); |
| } |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| } |
| |
| /* set all the device statuses back to unknown */ |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| FuDevice *device = fu_release_get_device(release); |
| fwupd_device_set_status(FWUPD_DEVICE(device), FWUPD_STATUS_UNKNOWN); |
| } |
| |
| /* get a new list of devices in case they replugged */ |
| devices_new = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device; |
| g_autoptr(FuDevice) device_new = NULL; |
| g_autoptr(GError) error_local = NULL; |
| device = g_ptr_array_index(devices, i); |
| device_new = fu_device_list_get_by_id(self->device_list, |
| fu_device_get_id(device), |
| &error_local); |
| if (device_new == NULL) { |
| g_info("failed to find new device: %s", error_local->message); |
| continue; |
| } |
| g_ptr_array_add(devices_new, g_steal_pointer(&device_new)); |
| } |
| |
| /* notify the plugins about the composite action */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_COMPOSITE_CLEANUP); |
| if (!fu_engine_composite_cleanup(self, devices_new, error)) { |
| g_prefix_error_literal(error, "failed to cleanup composite action: "); |
| return FALSE; |
| } |
| |
| /* for online updates, verify the version changed if not a re-install */ |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| FuDevice *device = fu_release_get_device(release); |
| g_autoptr(FuDevice) device_new = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| device_new = fu_device_list_get_by_id(self->device_list, |
| fu_device_get_id(device), |
| &error_local); |
| if (device_new == NULL) { |
| g_info("failed to find new device: %s", error_local->message); |
| continue; |
| } |
| if (!fu_engine_install_release_version_check(self, release, device_new, error)) |
| return FALSE; |
| } |
| |
| /* upload to Passim */ |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = g_ptr_array_index(releases, i); |
| if (!fu_engine_publish_release(self, release, error)) |
| return FALSE; |
| } |
| |
| /* allow capturing setup again */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_SETUP); |
| |
| /* make the UI update */ |
| fu_engine_emit_changed(self); |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_update_release_integrity(FuEngine *self, FuRelease *release, const gchar *key) |
| { |
| g_autoptr(GHashTable) integrity = fu_engine_integrity_new(self->ctx, NULL); |
| if (integrity != NULL) { |
| g_autofree gchar *str = fu_engine_integrity_to_string(integrity); |
| fu_release_add_metadata_item(release, key, str); |
| } |
| } |
| |
| static gboolean |
| fu_engine_add_release_metadata(FuEngine *self, FuRelease *release, GError **error) |
| { |
| g_autoptr(GHashTable) metadata_device = NULL; |
| g_autoptr(GHashTable) metadata_hash = NULL; |
| |
| /* build the version metadata */ |
| metadata_hash = fu_engine_get_report_metadata(self, error); |
| if (metadata_hash == NULL) |
| return FALSE; |
| fu_release_add_metadata(release, metadata_hash); |
| metadata_device = fu_device_report_metadata_pre(fu_release_get_device(release)); |
| if (metadata_device != NULL) |
| fu_release_add_metadata(release, metadata_device); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_add_release_plugin_metadata(FuEngine *self, |
| FuRelease *release, |
| FuPlugin *plugin, |
| GError **error) |
| { |
| GPtrArray *metadata_sources; |
| |
| /* build the version metadata */ |
| if (fu_plugin_get_report_metadata(plugin) != NULL) |
| fu_release_add_metadata(release, fu_plugin_get_report_metadata(plugin)); |
| |
| /* allow other plugins to contribute metadata too */ |
| metadata_sources = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_METADATA_SOURCE); |
| if (metadata_sources != NULL) { |
| for (guint i = 0; i < metadata_sources->len; i++) { |
| FuPlugin *plugin_tmp; |
| const gchar *plugin_name = g_ptr_array_index(metadata_sources, i); |
| g_autoptr(GError) error_local = NULL; |
| |
| plugin_tmp = fu_plugin_list_find_by_name(self->plugin_list, |
| plugin_name, |
| &error_local); |
| if (plugin_tmp == NULL) { |
| g_debug("could not add metadata for %s: %s", |
| plugin_name, |
| error_local->message); |
| continue; |
| } |
| if (fu_plugin_get_report_metadata(plugin_tmp) != NULL) { |
| fwupd_release_add_metadata( |
| FWUPD_RELEASE(release), |
| fu_plugin_get_report_metadata(plugin_tmp)); |
| } |
| } |
| } |
| |
| /* measure the "old" system state */ |
| if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) { |
| fu_engine_update_release_integrity(self, release, "SystemIntegrityOld"); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_save_into_backup_remote(FuEngine *self, GBytes *fw, GError **error) |
| { |
| FwupdRemote *remote_tmp = fu_remote_list_get_by_id(self->remote_list, "backup"); |
| g_autofree gchar *localstatepkg = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); |
| g_autofree gchar *backupdir = g_build_filename(localstatepkg, "backup", NULL); |
| g_autofree gchar *backupdir_uri = g_strdup_printf("file://%s", backupdir); |
| g_autofree gchar *remotes_path = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_REMOTES); |
| g_autofree gchar *remotes_fn = g_build_filename(remotes_path, "backup.conf", NULL); |
| g_autofree gchar *archive_checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, fw); |
| g_autofree gchar *archive_basename = g_strdup_printf("%s.cab", archive_checksum); |
| g_autofree gchar *archive_fn = g_build_filename(backupdir, archive_basename, NULL); |
| g_autoptr(FwupdRemote) remote = fwupd_remote_new(); |
| |
| /* save archive if required */ |
| if (!g_file_test(archive_fn, G_FILE_TEST_EXISTS)) { |
| g_info("saving archive to %s", archive_fn); |
| if (!fu_bytes_set_contents(archive_fn, fw, error)) |
| return FALSE; |
| } |
| |
| /* already exists as an enabled remote */ |
| if (remote_tmp != NULL && fwupd_remote_has_flag(remote_tmp, FWUPD_REMOTE_FLAG_ENABLED)) |
| return TRUE; |
| |
| /* just enable */ |
| if (remote_tmp != NULL) { |
| g_info("enabling remote %s", fwupd_remote_get_id(remote_tmp)); |
| fwupd_remote_add_flag(remote_tmp, FWUPD_REMOTE_FLAG_ENABLED); |
| return fu_remote_save_to_filename(remote_tmp, remotes_fn, NULL, error); |
| } |
| |
| /* create a new remote we can use for re-installing */ |
| g_info("creating new backup remote"); |
| fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ENABLED); |
| fwupd_remote_set_title(remote, "Backup"); |
| fwupd_remote_set_metadata_uri(remote, backupdir_uri); |
| return fu_remote_save_to_filename(remote, remotes_fn, NULL, error); |
| } |
| |
| static gboolean |
| fu_engine_create_reboot_required_file(GError **error) |
| { |
| g_autofree gchar *rundir = fu_path_from_kind(FU_PATH_KIND_RUNDIR); |
| g_autofree gchar *reboot_required_path = g_build_filename(rundir, "reboot-required", NULL); |
| g_autofree gchar *reboot_required_pkgs_path = |
| g_build_filename(rundir, "reboot-required.pkgs", NULL); |
| g_autoptr(GString) new_content = g_string_new(NULL); |
| |
| if (!g_file_test(rundir, G_FILE_TEST_IS_DIR)) |
| return TRUE; |
| |
| if (!g_file_set_contents(reboot_required_path, "", 0, error)) |
| return FALSE; |
| |
| if (g_file_test(reboot_required_pkgs_path, G_FILE_TEST_EXISTS)) { |
| g_autofree gchar *existing_content = NULL; |
| |
| if (!g_file_get_contents(reboot_required_pkgs_path, &existing_content, NULL, error)) |
| return FALSE; |
| if (existing_content != NULL) { |
| g_auto(GStrv) lines = g_strsplit(existing_content, "\n", -1); |
| for (guint i = 0; lines[i] != NULL; i++) { |
| if (g_strcmp0(lines[i], "fwupd") == 0) |
| return TRUE; |
| } |
| } |
| g_string_append(new_content, existing_content); |
| } |
| |
| g_string_append(new_content, "fwupd\n"); |
| |
| return g_file_set_contents(reboot_required_pkgs_path, |
| new_content->str, |
| new_content->len, |
| error); |
| } |
| |
| /** |
| * fu_engine_install_release: |
| * @self: a #FuEngine |
| * @release: a #FuRelease |
| * @progress: a #FuProgress |
| * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_OLDER |
| * @error: (nullable): optional return location for an error |
| * |
| * Installs a specific release on a device. |
| * |
| * By this point all the requirements and tests should have been done in |
| * fu_engine_requirements_check() so this should not fail before running |
| * the plugin loader. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_install_release(FuEngine *self, |
| FuRelease *release, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| FuDevice *device_orig = fu_release_get_device(release); |
| FuEngineRequest *request = fu_release_get_request(release); |
| FuPlugin *plugin; |
| FwupdFeatureFlags feature_flags = FWUPD_FEATURE_FLAG_NONE; |
| GInputStream *stream = fu_release_get_stream(release); |
| const gchar *tmp; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(FuDevice) device_tmp = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(FU_IS_RELEASE(release), FALSE); |
| g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* sanity check */ |
| if (stream == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no stream for release"); |
| return FALSE; |
| } |
| |
| /* optional for tests */ |
| if (request != NULL) |
| feature_flags = fu_engine_request_get_feature_flags(request); |
| |
| /* add the checksum of the container blob if not already set */ |
| if (fwupd_release_get_checksums(FWUPD_RELEASE(release))->len == 0) { |
| GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; |
| for (guint i = 0; checksum_types[i] != 0; i++) { |
| g_autofree gchar *checksum = |
| fu_input_stream_compute_checksum(stream, checksum_types[i], error); |
| if (checksum == NULL) |
| return FALSE; |
| fwupd_release_add_checksum(FWUPD_RELEASE(release), checksum); |
| } |
| } |
| |
| /* not in bootloader mode */ |
| device = g_object_ref(fu_release_get_device(release)); |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { |
| /* both optional; the plugin can specify a fallback */ |
| tmp = fwupd_release_get_detach_caption(FWUPD_RELEASE(release)); |
| if (tmp != NULL) |
| fu_device_set_update_message(device, tmp); |
| tmp = fwupd_release_get_detach_image(FWUPD_RELEASE(release)); |
| if (tmp != NULL) |
| fu_device_set_update_image(device, tmp); |
| } |
| |
| /* save to persistent storage so that the device can recover without a network */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_SAVE_INTO_BACKUP_REMOTE)) { |
| g_autoptr(GBytes) blob_cab = |
| fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error); |
| if (blob_cab == NULL) |
| return FALSE; |
| if (!fu_engine_save_into_backup_remote(self, blob_cab, error)) |
| return FALSE; |
| } |
| |
| /* set this for the callback */ |
| self->write_history = (flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0; |
| |
| /* get the plugin */ |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* add device to database */ |
| if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { |
| if (!fu_engine_add_release_metadata(self, release, error)) |
| return FALSE; |
| if (!fu_engine_add_release_plugin_metadata(self, release, plugin, error)) |
| return FALSE; |
| if (!fu_history_add_device(self->history, device, release, error)) |
| return FALSE; |
| } |
| |
| /* install firmware blob */ |
| if (!fu_engine_install_blob(self, |
| device, |
| release, |
| progress, |
| flags, |
| feature_flags, |
| &error_local)) { |
| FwupdUpdateState state = fu_device_get_update_state(device); |
| if (state != FWUPD_UPDATE_STATE_FAILED && |
| state != FWUPD_UPDATE_STATE_FAILED_TRANSIENT) |
| fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_FAILED); |
| else |
| fu_device_set_update_state(device_orig, state); |
| fu_device_set_update_error(device_orig, error_local->message); |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| |
| /* the device may have changed */ |
| device_tmp = fu_device_list_get_by_id(self->device_list, fu_device_get_id(device), error); |
| if (device_tmp == NULL) { |
| g_prefix_error_literal(error, "failed to get device after install: "); |
| return FALSE; |
| } |
| g_set_object(&device, device_tmp); |
| |
| /* update state (which updates the database if required) */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) || |
| fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) { |
| if (!fu_engine_create_reboot_required_file(error)) |
| return FALSE; |
| if (g_strcmp0(fu_device_get_plugin(device), "test") == 0) { |
| g_debug("not setting needs-reboot for test device"); |
| } else { |
| fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_NEEDS_REBOOT); |
| } |
| return TRUE; |
| } |
| |
| /* mark success unless needs a reboot */ |
| if (fu_device_get_update_state(device) != FWUPD_UPDATE_STATE_NEEDS_REBOOT) |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); |
| |
| /* wait for the system to acquiesce if required */ |
| if (fu_device_get_acquiesce_delay(device_orig) > 0 && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); |
| fu_engine_wait_for_acquiesce(self, fu_device_get_acquiesce_delay(device_orig)); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_get_plugins: |
| * @self: a #FuPluginList |
| * |
| * Gets all the plugins that have been added. |
| * |
| * Returns: (transfer none) (element-type FuPlugin): the plugins |
| * |
| * Since: 1.0.8 |
| **/ |
| GPtrArray * |
| fu_engine_get_plugins(FuEngine *self) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| return fu_plugin_list_get_all(self->plugin_list); |
| } |
| |
| /** |
| * fu_engine_get_plugin_by_name: |
| * @self: a #FuPluginList |
| * @name: a plugin name, e.g. `dfu` |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets a specific plugin. |
| * |
| * Returns: (transfer none): a plugin, or %NULL |
| * |
| * Since: 1.9.6 |
| **/ |
| FuPlugin * |
| fu_engine_get_plugin_by_name(FuEngine *self, const gchar *name, GError **error) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| return fu_plugin_list_find_by_name(self->plugin_list, name, error); |
| } |
| |
| gboolean |
| fu_engine_emulation_load(FuEngine *self, GInputStream *stream, GError **error) |
| { |
| gsize streamsz = 0; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* sanity check */ |
| if (!fu_input_stream_size(stream, &streamsz, error)) |
| return FALSE; |
| if (streamsz > fu_common_get_memory_size() / 10) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "skipping large emulation due to memory constraint"); |
| return FALSE; |
| } |
| |
| return fu_engine_emulator_load(self->emulation, stream, error); |
| } |
| |
| gboolean |
| fu_engine_emulation_save(FuEngine *self, GOutputStream *stream, GError **error) |
| { |
| return fu_engine_emulator_save(self->emulation, stream, error); |
| } |
| |
| /** |
| * fu_engine_get_device: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets a specific device, optionally loading an emulated phase. |
| * |
| * Returns: (transfer full): a device, or %NULL if not found |
| **/ |
| FuDevice * |
| fu_engine_get_device(FuEngine *self, const gchar *device_id, GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* we are emulating a device */ |
| if (self->emulator_phase != FU_ENGINE_EMULATOR_PHASE_SETUP) { |
| g_autoptr(FuDevice) device_old = NULL; |
| device_old = fu_device_list_get_by_id(self->device_list, device_id, NULL); |
| if (device_old != NULL && |
| fu_device_has_flag(device_old, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_load_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| error)) |
| return NULL; |
| } |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for device: "); |
| return NULL; |
| } |
| |
| /* get the new device */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return NULL; |
| |
| /* success */ |
| return g_steal_pointer(&device); |
| } |
| |
| /* same as FuDevice->prepare, but with the device open */ |
| static gboolean |
| fu_engine_device_prepare(FuEngine *self, |
| FuDevice *device, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); |
| if (locker == NULL) { |
| g_prefix_error_literal(error, "failed to open device for prepare: "); |
| return FALSE; |
| } |
| |
| /* check battery level is sane */ |
| if (fu_device_get_battery_level(device) > 0 && |
| fu_device_get_battery_level(device) < fu_device_get_battery_threshold(device)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, |
| "battery level is too low: %u%%", |
| fu_device_get_battery_level(device)); |
| return FALSE; |
| } |
| |
| return fu_device_prepare(device, progress, flags, error); |
| } |
| |
| /* same as FuDevice->cleanup, but with the device open */ |
| static gboolean |
| fu_engine_device_cleanup(FuEngine *self, |
| FuDevice *device, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuDeviceLocker) locker = NULL; |
| |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { |
| g_info("skipping device cleanup due to will-disappear flag"); |
| return TRUE; |
| } |
| |
| locker = fu_device_locker_new(device, error); |
| if (locker == NULL) { |
| g_prefix_error_literal(error, "failed to open device for cleanup: "); |
| return FALSE; |
| } |
| return fu_device_cleanup(device, progress, flags, error); |
| } |
| |
| static gboolean |
| fu_engine_device_check_power(FuEngine *self, |
| FuDevice *device, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| if (fu_engine_config_get_ignore_power(self->config)) |
| return TRUE; |
| |
| /* not charging */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| !fu_power_state_is_ac(fu_context_get_power_state(self->ctx))) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_AC_POWER_REQUIRED, |
| "Cannot install update " |
| "when not on AC power unless forced"); |
| return FALSE; |
| } |
| |
| /* not enough just in case */ |
| if (!fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_IGNORE_SYSTEM_POWER) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && |
| fu_context_get_battery_level(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && |
| fu_context_get_battery_threshold(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && |
| fu_context_get_battery_level(self->ctx) < fu_context_get_battery_threshold(self->ctx)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, |
| "Cannot install update when system battery " |
| "is not at least %u%% unless forced", |
| fu_context_get_battery_threshold(self->ctx)); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static FuFirmware * |
| fu_engine_prepare_firmware(FuEngine *self, |
| const gchar *device_id, |
| GInputStream *stream, |
| FuProgress *progress, |
| FuFirmwareParseFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before prepare firmware: "); |
| return NULL; |
| } |
| return fu_device_prepare_firmware(device, stream, progress, flags, error); |
| } |
| |
| static gboolean |
| fu_engine_prepare(FuEngine *self, |
| const gchar *device_id, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update prepare: "); |
| return FALSE; |
| } |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); |
| |
| if (!fu_engine_device_check_power(self, device, flags, error)) { |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); |
| return FALSE; |
| } |
| |
| str = fu_device_to_string(device); |
| g_info("prepare -> %s", str); |
| if (!fu_engine_device_prepare(self, device, progress, flags, error)) |
| return FALSE; |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| if (!fu_plugin_runner_prepare(plugin_tmp, device, progress, flags, error)) |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for prepare replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_cleanup(FuEngine *self, |
| const gchar *device_id, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update cleanup: "); |
| return FALSE; |
| } |
| fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); |
| str = fu_device_to_string(device); |
| g_info("cleanup -> %s", str); |
| if (!fu_engine_device_cleanup(self, device, progress, flags, error)) |
| return FALSE; |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| if (!fu_plugin_runner_cleanup(plugin_tmp, device, progress, flags, error)) |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for cleanup replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_detach(FuEngine *self, |
| const gchar *device_id, |
| FuProgress *progress, |
| FwupdFeatureFlags feature_flags, |
| GError **error) |
| { |
| FuPlugin *plugin; |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(FuDeviceLocker) poll_locker = NULL; |
| g_autoptr(FuDeviceProgress) device_progress = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update detach: "); |
| return FALSE; |
| } |
| device_progress = fu_device_progress_new(device, progress); |
| g_return_val_if_fail(device_progress != NULL, FALSE); |
| |
| /* pause the polling */ |
| poll_locker = fu_device_poll_locker_new(device, error); |
| if (poll_locker == NULL) |
| return FALSE; |
| |
| str = fu_device_to_string(device); |
| g_info("detach -> %s", str); |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| if (!fu_plugin_runner_detach(plugin, device, progress, error)) |
| return FALSE; |
| |
| /* support older clients without the ability to do immediate requests */ |
| if ((feature_flags & FWUPD_FEATURE_FLAG_REQUESTS) == 0 && |
| fu_device_get_request_cnt(device, FWUPD_REQUEST_KIND_IMMEDIATE) > 0) { |
| /* fallback to something sane */ |
| if (fu_device_get_update_message(device) == NULL) { |
| g_autofree gchar *tmp = NULL; |
| tmp = g_strdup_printf("Device %s needs to manually be put in update mode", |
| fu_device_get_name(device)); |
| fu_device_set_update_message(device, tmp); |
| } |
| |
| /* abort and require client to re-submit */ |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NEEDS_USER_ACTION, |
| fu_device_get_update_message(device)); |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for detach replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_attach(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) |
| { |
| FuPlugin *plugin; |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(FuDeviceLocker) poll_locker = NULL; |
| g_autoptr(FuDeviceProgress) device_progress = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update attach: "); |
| return FALSE; |
| } |
| device_progress = fu_device_progress_new(device, progress); |
| g_return_val_if_fail(device_progress != NULL, FALSE); |
| |
| str = fu_device_to_string(device); |
| g_info("attach -> %s", str); |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* pause the polling */ |
| poll_locker = fu_device_poll_locker_new(device, error); |
| if (poll_locker == NULL) |
| return FALSE; |
| |
| if (!fu_plugin_runner_attach(plugin, device, progress, error)) |
| return FALSE; |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for attach replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_set_progress(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before setting progress: "); |
| return FALSE; |
| } |
| fu_device_set_progress(device, progress); |
| return TRUE; |
| } |
| |
| gboolean |
| fu_engine_activate(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) |
| { |
| FuPlugin *plugin; |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check the device exists */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| str = fu_device_to_string(device); |
| g_info("activate -> %s", str); |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| if (!fu_plugin_runner_activate(plugin, device, progress, error)) |
| return FALSE; |
| |
| fu_engine_emit_device_changed_safe(self, device); |
| fu_engine_emit_changed(self); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_reload(FuEngine *self, const gchar *device_id, GError **error) |
| { |
| FuPlugin *plugin; |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update reload: "); |
| return FALSE; |
| } |
| str = fu_device_to_string(device); |
| g_info("reload -> %s", str); |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { |
| g_info("skipping reload due to will-disappear flag"); |
| return TRUE; |
| } |
| |
| if (!fu_plugin_runner_reload(plugin, device, error)) { |
| g_prefix_error_literal(error, "failed to reload device: "); |
| return FALSE; |
| } |
| |
| /* match again any metadata-provided values */ |
| fu_engine_md_refresh_device(self, device); |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| error)) |
| return FALSE; |
| } |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for reload replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_write_firmware(FuEngine *self, |
| const gchar *device_id, |
| FuFirmware *firmware, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| FuPlugin *plugin; |
| g_autofree gchar *str = NULL; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(FuDeviceLocker) poll_locker = NULL; |
| g_autoptr(FuDeviceProgress) device_progress = NULL; |
| g_autoptr(GError) error_write = NULL; |
| |
| /* the device and plugin both may have changed */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device before update: "); |
| return FALSE; |
| } |
| device_progress = fu_device_progress_new(device, progress); |
| g_return_val_if_fail(device_progress != NULL, FALSE); |
| |
| /* pause the polling */ |
| poll_locker = fu_device_poll_locker_new(device, error); |
| if (poll_locker == NULL) |
| return FALSE; |
| |
| str = fu_device_to_string(device); |
| g_info("update -> %s", str); |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin == NULL) |
| return FALSE; |
| if (!fu_plugin_runner_write_firmware(plugin, |
| device, |
| firmware, |
| progress, |
| flags, |
| &error_write)) { |
| g_autofree gchar *str_write = NULL; |
| g_autoptr(GError) error_attach = NULL; |
| g_autoptr(GError) error_cleanup = NULL; |
| |
| if (g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED) || |
| g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) || |
| g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION) || |
| g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM)) { |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); |
| } else { |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); |
| } |
| |
| /* this is really helpful for debugging, as we want to dump the device *before* |
| * we run cleanup */ |
| str_write = fu_device_to_string(device); |
| g_debug("failed write-firmware '%s': %s", error_write->message, str_write); |
| |
| /* attach back into runtime then cleanup */ |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_ATTACH); |
| fu_progress_reset(progress); |
| if (!fu_plugin_runner_attach(plugin, device, progress, &error_attach)) { |
| g_warning("failed to attach device after failed update: %s", |
| error_attach->message); |
| } |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_CLEANUP); |
| fu_progress_reset(progress); |
| if (!fu_engine_cleanup(self, device_id, progress, flags, &error_cleanup)) { |
| g_warning("failed to update-cleanup after failed update: %s", |
| error_cleanup->message); |
| } |
| } |
| |
| /* return error to client */ |
| g_propagate_error(error, g_steal_pointer(&error_write)); |
| return FALSE; |
| } |
| |
| /* save to emulated phase */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| error)) |
| return FALSE; |
| } |
| |
| /* abort loop before waiting for replug */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_LOOP_RESTART)) |
| return TRUE; |
| |
| /* wait for any device to disconnect and reconnect */ |
| if (!fu_device_list_wait_for_replug(self->device_list, error)) { |
| g_prefix_error_literal(error, "failed to wait for write-firmware replug: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| GBytes * |
| fu_engine_firmware_dump(FuEngine *self, |
| FuDevice *device, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuDeviceLocker) locker = NULL; |
| g_autoptr(FuDeviceLocker) poll_locker = NULL; |
| |
| /* pause the polling */ |
| poll_locker = fu_device_poll_locker_new(device, error); |
| if (poll_locker == NULL) |
| return NULL; |
| |
| /* open, read, close */ |
| locker = fu_device_locker_new(device, error); |
| if (locker == NULL) { |
| g_prefix_error_literal(error, "failed to open device for firmware read: "); |
| return NULL; |
| } |
| return fu_device_dump_firmware(device, progress, error); |
| } |
| |
| FuFirmware * |
| fu_engine_firmware_read(FuEngine *self, |
| FuDevice *device, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| GError **error) |
| { |
| g_autoptr(FuDeviceLocker) locker = NULL; |
| g_autoptr(FuDeviceLocker) poll_locker = NULL; |
| |
| /* pause the polling */ |
| poll_locker = fu_device_poll_locker_new(device, error); |
| if (poll_locker == NULL) |
| return NULL; |
| |
| /* open, read, close */ |
| locker = fu_device_locker_new(device, error); |
| if (locker == NULL) { |
| g_prefix_error_literal(error, "failed to open device for firmware read: "); |
| return NULL; |
| } |
| return fu_device_read_firmware(device, progress, FU_FIRMWARE_PARSE_FLAG_NONE, error); |
| } |
| |
| static gboolean |
| fu_engine_install_loop(FuEngine *self, |
| const gchar *device_id, |
| FuRelease *release, |
| FwupdInstallFlags flags, |
| FwupdFeatureFlags feature_flags, |
| gboolean *write_complete, |
| FuProgress *progress, |
| GError **error) |
| { |
| GInputStream *stream_fw; |
| gsize streamsz = 0; |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(FuDevice) device_tmp = NULL; |
| g_autoptr(FuFirmware) firmware = NULL; |
| |
| /* test the firmware is not an empty blob */ |
| stream_fw = fu_release_get_stream(release); |
| if (stream_fw == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "Failed to get firmware stream from release"); |
| return FALSE; |
| } |
| if (!fu_input_stream_size(stream_fw, &streamsz, error)) |
| return FALSE; |
| if (streamsz == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "Firmware is invalid as has zero size"); |
| return FALSE; |
| } |
| |
| /* not ideal; but best we can do as we don't know how many writes are required */ |
| fu_progress_reset(progress); |
| |
| /* progress */ |
| if (!fu_engine_set_progress(self, device_id, progress, error)) |
| return FALSE; |
| if (fu_progress_get_steps(progress) == 0) { |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); |
| fu_progress_add_step(progress, FWUPD_STATUS_DECOMPRESSING, 1, NULL); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, NULL); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, NULL); |
| } else if (fu_progress_get_steps(progress) != 5) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "FuDevice->set_progress did not set " |
| "prepare-firmware,detach,write,attach,reload steps"); |
| return FALSE; |
| } |
| |
| /* some emulations are storing events on the bootloader device */ |
| device = fu_engine_get_device(self, device_id, error); |
| if (device == NULL) { |
| g_prefix_error_literal(error, "failed to get device for flags: "); |
| return FALSE; |
| } |
| |
| /* do not rely on the plugin clearing these */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_LOOP_RESTART)) { |
| g_debug("clearing install-loop-restart"); |
| fu_device_remove_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_LOOP_RESTART); |
| } |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) { |
| g_debug("clearing another-write-required"); |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); |
| } |
| |
| /* detach->parse->install or parse->detach->install */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_DETACH); |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_DETACH_PREPARE_FIRMWARE)) { |
| /* detach to bootloader mode */ |
| if (!fu_engine_detach(self, |
| device_id, |
| fu_progress_get_child(progress), |
| feature_flags, |
| error)) { |
| g_prefix_error_literal(error, "failed to detach: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* parse firmware */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_INSTALL); |
| firmware = fu_engine_prepare_firmware(self, |
| device_id, |
| stream_fw, |
| fu_progress_get_child(progress), |
| FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM, |
| error); |
| if (firmware == NULL) |
| return FALSE; |
| if (fu_firmware_get_version(firmware) == NULL) |
| fu_firmware_set_version(firmware, fu_release_get_version(release)); |
| if (fu_firmware_get_filename(firmware) == NULL) { |
| fu_firmware_set_filename(firmware, |
| fu_release_get_firmware_basename(release)); |
| } |
| fu_progress_step_done(progress); |
| |
| /* install */ |
| if (!fu_engine_write_firmware(self, |
| device_id, |
| firmware, |
| fu_progress_get_child(progress), |
| flags, |
| error)) { |
| g_prefix_error_literal(error, "failed to write-firmware: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| } else { |
| /* parse firmware */ |
| firmware = fu_engine_prepare_firmware(self, |
| device_id, |
| stream_fw, |
| fu_progress_get_child(progress), |
| FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM, |
| error); |
| if (firmware == NULL) |
| return FALSE; |
| if (fu_firmware_get_version(firmware) == NULL) |
| fu_firmware_set_version(firmware, fu_release_get_version(release)); |
| if (fu_firmware_get_filename(firmware) == NULL) { |
| fu_firmware_set_filename(firmware, |
| fu_release_get_firmware_basename(release)); |
| } |
| fu_progress_step_done(progress); |
| |
| /* detach to bootloader mode */ |
| if (!fu_engine_detach(self, |
| device_id, |
| fu_progress_get_child(progress), |
| feature_flags, |
| error)) { |
| g_prefix_error_literal(error, "failed to detach: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* install */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_INSTALL); |
| if (!fu_engine_write_firmware(self, |
| device_id, |
| firmware, |
| fu_progress_get_child(progress), |
| flags, |
| error)) { |
| g_prefix_error_literal(error, "failed to write-firmware: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| } |
| |
| /* abort loop */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_LOOP_RESTART)) { |
| g_debug("restarting install loop, aborting with success"); |
| return TRUE; |
| } |
| |
| /* attach into runtime mode */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_ATTACH); |
| if (!fu_engine_attach(self, device_id, fu_progress_get_child(progress), error)) { |
| g_prefix_error_literal(error, "failed to attach: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* abort loop */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_LOOP_RESTART)) { |
| g_debug("restarting install loop, aborting with success"); |
| return TRUE; |
| } |
| |
| /* get the new version number */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_RELOAD); |
| if (!fu_engine_reload(self, device_id, error)) { |
| g_prefix_error_literal(error, "failed to reload: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* abort loop */ |
| device_tmp = fu_engine_get_device(self, device_id, error); |
| if (device_tmp == NULL) { |
| g_prefix_error_literal(error, "failed to get device after reload: "); |
| return FALSE; |
| } |
| if (fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) { |
| g_debug("another write required, aborting with success"); |
| return TRUE; |
| } |
| |
| /* success */ |
| *write_complete = TRUE; |
| return TRUE; |
| } |
| |
| gboolean |
| fu_engine_install_blob(FuEngine *self, |
| FuDevice *device, |
| FuRelease *release, |
| FuProgress *progress, |
| FwupdInstallFlags flags, |
| FwupdFeatureFlags feature_flags, |
| GError **error) |
| { |
| gboolean write_complete = FALSE; |
| g_autofree gchar *device_id = NULL; |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_autoptr(GTimer) timer = g_timer_new(); |
| g_autoptr(FuDeviceProgress) device_progress = fu_device_progress_new(device, progress); |
| |
| g_return_val_if_fail(device_progress != NULL, FALSE); |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "prepare"); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); |
| fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "cleanup"); |
| |
| /* mark this as modified even if we actually fail to do the update */ |
| fu_device_set_modified_usec(device, g_get_real_time()); |
| |
| /* signal to all the plugins the update is about to happen */ |
| device_id = g_strdup(fu_device_get_id(device)); |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_PREPARE); |
| if (!fu_engine_prepare(self, device_id, fu_progress_get_child(progress), flags, error)) |
| return FALSE; |
| fu_progress_step_done(progress); |
| |
| /* plugins can set FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED to run again, but they |
| * must return TRUE rather than an error */ |
| for (self->emulator_write_cnt = 0; |
| self->emulator_write_cnt < FU_ENGINE_EMULATOR_WRITE_COUNT_MAX && !write_complete; |
| self->emulator_write_cnt++) { |
| if (!fu_engine_install_loop(self, |
| device_id, |
| release, |
| flags, |
| feature_flags, |
| &write_complete, |
| fu_progress_get_child(progress), |
| error)) |
| return FALSE; |
| } |
| if (!write_complete) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "aborting device write loop, limit %u", |
| (guint)FU_ENGINE_EMULATOR_WRITE_COUNT_MAX); |
| return FALSE; |
| } |
| self->emulator_write_cnt = FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT; |
| fu_progress_step_done(progress); |
| |
| /* update history database */ |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); |
| fu_device_set_install_duration(device, g_timer_elapsed(timer, NULL)); |
| if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { |
| if (!fu_history_modify_device(self->history, device, error)) { |
| g_prefix_error_literal(error, "failed to set success: "); |
| return FALSE; |
| } |
| } |
| |
| /* signal to all the plugins the update has happened */ |
| fu_engine_set_emulator_phase(self, FU_ENGINE_EMULATOR_PHASE_CLEANUP); |
| if (!fu_engine_cleanup(self, device_id, fu_progress_get_child(progress), flags, error)) |
| return FALSE; |
| fu_progress_step_done(progress); |
| |
| /* make the UI update */ |
| fu_engine_emit_device_changed(self, device_id); |
| g_info("updating %s took %f seconds", id_display, g_timer_elapsed(timer, NULL)); |
| return TRUE; |
| } |
| |
| static FuDevice * |
| fu_engine_get_item_by_id_fallback_history(FuEngine *self, const gchar *id, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* not a wildcard */ |
| if (g_strcmp0(id, FWUPD_DEVICE_ID_ANY) != 0) { |
| g_autoptr(FuDevice) dev = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| /* get this one device */ |
| dev = fu_history_get_device_by_id(self->history, id, &error_local); |
| if (dev == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Failed to find %s in history database: %s", |
| id, |
| error_local->message); |
| return NULL; |
| } |
| |
| /* only useful */ |
| if (fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS || |
| fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || |
| fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) { |
| return g_steal_pointer(&dev); |
| } |
| |
| /* nothing in database */ |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Device %s has no results to report", |
| fu_device_get_id(dev)); |
| return NULL; |
| } |
| |
| /* allow '*' for any */ |
| devices = fu_history_get_devices(self->history, error); |
| if (devices == NULL) |
| return NULL; |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *dev = g_ptr_array_index(devices, i); |
| if (fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS || |
| fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || |
| fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) |
| return g_object_ref(dev); |
| } |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Failed to find any useful results to report"); |
| return NULL; |
| } |
| |
| static gboolean |
| fu_engine_search_query_append(FuEngine *self, const gchar *xpath, GError **error) |
| { |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(XbQuery) query = NULL; |
| |
| /* prepare tag query with bound GUID parameter */ |
| query = xb_query_new_full(self->silo, xpath, XB_QUERY_FLAG_OPTIMIZE, &error_local); |
| if (query == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || |
| g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { |
| g_debug("ignoring prepared query %s: %s", xpath, error_local->message); |
| return TRUE; |
| } |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| g_ptr_array_add(self->search_queries, g_steal_pointer(&query)); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_search_query_create(FuEngine *self, GError **error) |
| { |
| /* invalidate everything */ |
| g_ptr_array_set_size(self->search_queries, 0); |
| |
| /* we get one for free, add build the others */ |
| g_ptr_array_add(self->search_queries, g_object_ref(self->query_component_by_guid)); |
| if (!fu_engine_search_query_append(self, "components/component/id[text()=?]/..", error)) |
| return FALSE; |
| if (!fu_engine_search_query_append(self, "components/component/name[text()~=?]/..", error)) |
| return FALSE; |
| if (!fu_engine_search_query_append(self, |
| "components/component/developer_name[text()~=?]/..", |
| error)) |
| return FALSE; |
| if (!fu_engine_search_query_append(self, |
| "components/component/releases/release/artifacts/" |
| "artifact/filename[text()=?]/../../../../..", |
| error)) |
| return FALSE; |
| if (!fu_engine_search_query_append(self, |
| "components/component/releases/release/artifacts/" |
| "artifact/checksum[text()=?]/../../../../..", |
| error)) |
| return FALSE; |
| if (!fu_engine_search_query_append( |
| self, |
| "components/component/releases/release/issues/issue[text()=?]/../../../..", |
| error)) |
| return FALSE; |
| if (!fu_engine_search_query_append( |
| self, |
| "components/component/custom/value[@key='LVFS::UpdateProtocol'][text()=?]/../..", |
| error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_create_silo_index(FuEngine *self, GError **error) |
| { |
| g_autoptr(GPtrArray) components = NULL; |
| g_autoptr(GError) error_container_checksum1 = NULL; |
| g_autoptr(GError) error_container_checksum2 = NULL; |
| g_autoptr(GError) error_tag_by_guid_version = NULL; |
| |
| /* print what we've got */ |
| components = xb_silo_query(self->silo, "components/component[@type='firmware']", 0, NULL); |
| if (components == NULL) |
| return TRUE; |
| g_info("%u components now in silo", components->len); |
| |
| /* clear old prepared queries */ |
| g_clear_object(&self->query_component_by_guid); |
| g_clear_object(&self->query_container_checksum1); |
| g_clear_object(&self->query_container_checksum2); |
| g_clear_object(&self->query_tag_by_guid_version); |
| |
| /* build the index */ |
| if (!xb_silo_query_build_index(self->silo, "components/component", "type", error)) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| if (!xb_silo_query_build_index(self->silo, |
| "components/component[@type='firmware']/provides/firmware", |
| "type", |
| error)) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| if (!xb_silo_query_build_index(self->silo, |
| "components/component/provides/firmware", |
| NULL, |
| error)) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| if (!xb_silo_query_build_index(self->silo, |
| "components/component[@type='firmware']/tags/tag", |
| "namespace", |
| error)) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| |
| /* create prepared queries to save time later */ |
| self->query_component_by_guid = |
| xb_query_new_full(self->silo, |
| "components/component/provides/firmware[@type=$'flashed'][text()=?]/" |
| "../..", |
| XB_QUERY_FLAG_OPTIMIZE, |
| error); |
| if (self->query_component_by_guid == NULL) { |
| g_prefix_error_literal(error, "failed to prepare query: "); |
| return FALSE; |
| } |
| |
| /* old-style <checksum target="container"> and new-style <artifact> */ |
| self->query_container_checksum1 = |
| xb_query_new_full(self->silo, |
| "components/component[@type='firmware']/releases/release/" |
| "checksum[@target='container'][text()=?]/..", |
| XB_QUERY_FLAG_OPTIMIZE, |
| &error_container_checksum1); |
| if (self->query_container_checksum1 == NULL) |
| g_debug("ignoring prepared query: %s", error_container_checksum1->message); |
| self->query_container_checksum2 = |
| xb_query_new_full(self->silo, |
| "components/component[@type='firmware']/releases/release/" |
| "artifacts/artifact[@type='binary']/checksum[text()=?]/" |
| "../../..", |
| XB_QUERY_FLAG_OPTIMIZE, |
| &error_container_checksum2); |
| if (self->query_container_checksum2 == NULL) |
| g_debug("ignoring prepared query: %s", error_container_checksum2->message); |
| |
| /* prepare tag query with bound GUID parameter */ |
| self->query_tag_by_guid_version = |
| xb_query_new_full(self->silo, |
| "local/components/component[@merge='append']/provides/" |
| "firmware[text()=?]/../../releases/release[@version=?]/../../" |
| "tags/tag", |
| XB_QUERY_FLAG_OPTIMIZE, |
| &error_tag_by_guid_version); |
| if (self->query_tag_by_guid_version == NULL) |
| g_debug("ignoring prepared query: %s", error_tag_by_guid_version->message); |
| |
| /* build all the search queries */ |
| if (!fu_engine_search_query_create(self, error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| /* for the self tests */ |
| void |
| fu_engine_set_silo(FuEngine *self, XbSilo *silo) |
| { |
| g_autoptr(GError) error_local = NULL; |
| g_return_if_fail(FU_IS_ENGINE(self)); |
| g_return_if_fail(XB_IS_SILO(silo)); |
| g_set_object(&self->silo, silo); |
| if (!fu_engine_create_silo_index(self, &error_local)) |
| g_warning("failed to create indexes: %s", error_local->message); |
| } |
| |
| static gboolean |
| fu_engine_appstream_upgrade_cb(XbBuilderFixup *self, |
| XbBuilderNode *bn, |
| gpointer user_data, |
| GError **error) |
| { |
| if (g_strcmp0(xb_builder_node_get_element(bn), "metadata") == 0) |
| xb_builder_node_set_element(bn, "custom"); |
| return TRUE; |
| } |
| |
| static GInputStream * |
| fu_engine_builder_cabinet_adapter_cb(XbBuilderSource *source, |
| XbBuilderSourceCtx *ctx, |
| gpointer user_data, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| GInputStream *stream = xb_builder_source_ctx_get_stream(ctx); |
| g_autoptr(FuCabinet) cabinet = NULL; |
| g_autoptr(XbSilo) silo = NULL; |
| g_autofree gchar *xml = NULL; |
| |
| /* convert the CAB into metadata XML */ |
| cabinet = fu_engine_build_cabinet_from_stream(self, stream, error); |
| if (cabinet == NULL) |
| return NULL; |
| silo = fu_cabinet_get_silo(cabinet, error); |
| if (silo == NULL) |
| return NULL; |
| xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, error); |
| if (xml == NULL) |
| return NULL; |
| return g_memory_input_stream_new_from_data(g_steal_pointer(&xml), -1, g_free); |
| } |
| |
| static XbBuilderSource * |
| fu_engine_create_metadata_builder_source(FuEngine *self, const gchar *fn, GError **error) |
| { |
| g_autoptr(GFile) file = g_file_new_for_path(fn); |
| g_autoptr(XbBuilderSource) source = xb_builder_source_new(); |
| |
| g_info("using %s as metadata source", fn); |
| xb_builder_source_add_simple_adapter(source, |
| "application/vnd.ms-cab-compressed," |
| "com.microsoft.cab," |
| ".cab," |
| "application/octet-stream", |
| fu_engine_builder_cabinet_adapter_cb, |
| self, |
| NULL); |
| if (!xb_builder_source_load_file(source, |
| file, |
| XB_BUILDER_SOURCE_FLAG_WATCH_FILE | |
| XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY, |
| NULL, |
| error)) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| return g_steal_pointer(&source); |
| } |
| |
| static gboolean |
| fu_engine_create_metadata(FuEngine *self, XbBuilder *builder, FwupdRemote *remote, GError **error) |
| { |
| g_autoptr(GPtrArray) files = NULL; |
| const gchar *path; |
| |
| /* find all files in directory */ |
| path = fwupd_remote_get_filename_cache(remote); |
| if (path == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no filename cache for %s", |
| fwupd_remote_get_id(remote)); |
| return FALSE; |
| } |
| files = fu_path_get_files(path, error); |
| if (files == NULL) |
| return FALSE; |
| |
| /* add each source */ |
| for (guint i = 0; i < files->len; i++) { |
| g_autoptr(XbBuilderNode) custom = NULL; |
| g_autoptr(XbBuilderSource) source = NULL; |
| g_autoptr(GError) error_local = NULL; |
| const gchar *fn = g_ptr_array_index(files, i); |
| g_autofree gchar *fn_lowercase = g_ascii_strdown(fn, -1); |
| |
| /* check is cab file */ |
| if (!g_str_has_suffix(fn_lowercase, ".cab")) { |
| g_info("ignoring: %s", fn); |
| continue; |
| } |
| |
| /* build source for file */ |
| source = fu_engine_create_metadata_builder_source(self, fn, &error_local); |
| if (source == NULL) { |
| g_warning("failed to create builder source: %s", error_local->message); |
| continue; |
| } |
| |
| /* add metadata */ |
| custom = xb_builder_node_new("custom"); |
| xb_builder_node_insert_text(custom, |
| "value", |
| fn, |
| "key", |
| "fwupd::FilenameCache", |
| NULL); |
| xb_builder_node_insert_text(custom, |
| "value", |
| fwupd_remote_get_id(remote), |
| "key", |
| "fwupd::RemoteId", |
| NULL); |
| xb_builder_source_set_info(source, custom); |
| xb_builder_import_source(builder, source); |
| } |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_ensure_device_supported(FuEngine *self, FuDevice *device) |
| { |
| gboolean is_supported = FALSE; |
| gboolean update_pending = FALSE; |
| g_autoptr(GError) error = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| g_autoptr(FuEngineRequest) request = NULL; |
| |
| /* all flags set */ |
| request = fu_engine_request_new(NULL); |
| fu_engine_request_add_flag(request, FU_ENGINE_REQUEST_FLAG_NO_REQUIREMENTS); |
| fu_engine_request_add_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE); |
| fu_engine_request_set_feature_flags(request, ~0); |
| |
| /* get all releases that pass the requirements */ |
| releases = fu_engine_get_releases_for_device(self, request, device, &error); |
| if (releases == NULL) { |
| if (!g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) && |
| !g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_warning("failed to get releases for %s: %s", id_display, error->message); |
| } |
| } else { |
| if (releases->len > 0) |
| is_supported = TRUE; |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *release = FU_RELEASE(g_ptr_array_index(releases, i)); |
| if (fu_release_has_flag(release, FWUPD_RELEASE_FLAG_IS_UPGRADE)) { |
| update_pending = TRUE; |
| break; |
| } |
| } |
| if (update_pending) { |
| fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_UPDATE_PENDING); |
| } else { |
| fu_device_remove_private_flag(device, |
| FU_DEVICE_PRIVATE_FLAG_UPDATE_PENDING); |
| } |
| } |
| |
| /* was supported, now unsupported */ |
| if (!is_supported) { |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) { |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); |
| fu_engine_emit_device_changed_safe(self, device); |
| } |
| return; |
| } |
| |
| /* was unsupported, now supported */ |
| if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) { |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); |
| fu_engine_emit_device_changed_safe(self, device); |
| } |
| } |
| |
| static void |
| fu_engine_md_refresh_device(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(XbNode) component = fu_engine_get_component_by_guids(self, device); |
| |
| /* set or clear the SUPPORTED flag */ |
| fu_engine_ensure_device_supported(self, device); |
| |
| /* fixup the name and format as needed */ |
| if (component != NULL && |
| !fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_ONLY_CHECKSUM)) |
| fu_device_ensure_from_component(device, component); |
| } |
| |
| static void |
| fu_engine_md_refresh_devices(FuEngine *self) |
| { |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| fu_engine_md_refresh_device(self, device); |
| } |
| } |
| |
| static gboolean |
| fu_engine_load_metadata_store_local(FuEngine *self, |
| XbBuilder *builder, |
| FuPathKind path_kind, |
| GError **error) |
| { |
| g_autofree gchar *fn = fu_path_from_kind(path_kind); |
| g_autofree gchar *metadata_path = g_build_filename(fn, "local.d", NULL); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) metadata_fns = NULL; |
| |
| metadata_fns = fu_path_glob(metadata_path, "*.xml", &error_local); |
| if (metadata_fns == NULL) { |
| g_info("ignoring: %s", error_local->message); |
| return TRUE; |
| } |
| for (guint i = 0; i < metadata_fns->len; i++) { |
| const gchar *path = g_ptr_array_index(metadata_fns, i); |
| g_autoptr(XbBuilderSource) source = xb_builder_source_new(); |
| g_autoptr(GFile) file = g_file_new_for_path(path); |
| g_info("loading local metadata: %s", path); |
| if (!xb_builder_source_load_file(source, |
| file, |
| XB_BUILDER_SOURCE_FLAG_NONE, |
| NULL, |
| error)) { |
| fwupd_error_convert(error); |
| return FALSE; |
| } |
| xb_builder_source_set_prefix(source, "local"); |
| xb_builder_import_source(builder, source); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_load_metadata_store(FuEngine *self, FuEngineLoadFlags flags, GError **error) |
| { |
| GPtrArray *remotes; |
| XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID; |
| g_autoptr(GFile) xmlb = NULL; |
| g_autoptr(XbBuilder) builder = xb_builder_new(); |
| |
| /* clear existing silo */ |
| g_clear_object(&self->silo); |
| |
| #ifdef SOURCE_VERSION |
| /* invalidate the cache if the fwupd version changes */ |
| xb_builder_append_guid(builder, SOURCE_VERSION); |
| #endif |
| |
| /* verbose profiling */ |
| if (g_getenv("FWUPD_XMLB_VERBOSE") != NULL) { |
| xb_builder_set_profile_flags(builder, |
| XB_SILO_PROFILE_FLAG_XPATH | |
| XB_SILO_PROFILE_FLAG_DEBUG); |
| } |
| |
| /* load each enabled metadata file */ |
| remotes = fu_remote_list_get_all(self->remote_list); |
| for (guint i = 0; i < remotes->len; i++) { |
| const gchar *path = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GFile) file = NULL; |
| g_autoptr(XbBuilderFixup) fixup = NULL; |
| g_autoptr(XbBuilderNode) custom = NULL; |
| g_autoptr(XbBuilderSource) source = xb_builder_source_new(); |
| |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) |
| continue; |
| path = fwupd_remote_get_filename_cache(remote); |
| if (!g_file_test(path, G_FILE_TEST_EXISTS)) |
| continue; |
| |
| /* generate all metadata on demand */ |
| if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) { |
| g_info("loading metadata for remote '%s'", fwupd_remote_get_id(remote)); |
| if (!fu_engine_create_metadata(self, builder, remote, &error_local)) { |
| g_warning("failed to generate remote %s: %s", |
| fwupd_remote_get_id(remote), |
| error_local->message); |
| } |
| continue; |
| } |
| |
| /* save the remote-id in the custom metadata space */ |
| file = g_file_new_for_path(path); |
| if (!xb_builder_source_load_file(source, |
| file, |
| XB_BUILDER_SOURCE_FLAG_NONE, |
| NULL, |
| &error_local)) { |
| g_warning("failed to load remote %s: %s", |
| fwupd_remote_get_id(remote), |
| error_local->message); |
| continue; |
| } |
| |
| /* fix up any legacy installed files */ |
| fixup = xb_builder_fixup_new("AppStreamUpgrade", |
| fu_engine_appstream_upgrade_cb, |
| self, |
| NULL); |
| xb_builder_fixup_set_max_depth(fixup, 3); |
| xb_builder_source_add_fixup(source, fixup); |
| |
| /* add metadata */ |
| custom = xb_builder_node_new("custom"); |
| xb_builder_node_insert_text(custom, |
| "value", |
| path, |
| "key", |
| "fwupd::FilenameCache", |
| NULL); |
| xb_builder_node_insert_text(custom, |
| "value", |
| fwupd_remote_get_id(remote), |
| "key", |
| "fwupd::RemoteId", |
| NULL); |
| xb_builder_source_set_info(source, custom); |
| |
| /* we need to watch for changes? */ |
| xb_builder_import_source(builder, source); |
| } |
| |
| /* add any client-side data, e.g. BKC tags */ |
| if (!fu_engine_load_metadata_store_local(self, |
| builder, |
| FU_PATH_KIND_LOCALSTATEDIR_PKG, |
| error)) |
| return FALSE; |
| if (!fu_engine_load_metadata_store_local(self, builder, FU_PATH_KIND_DATADIR_PKG, error)) |
| return FALSE; |
| |
| /* on a read-only filesystem don't care about the cache GUID */ |
| if (flags & FU_ENGINE_LOAD_FLAG_READONLY) |
| compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; |
| |
| /* ensure silo is up to date */ |
| if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) { |
| g_autoptr(GFileIOStream) iostr = NULL; |
| xmlb = g_file_new_tmp(NULL, &iostr, error); |
| if (xmlb == NULL) |
| return FALSE; |
| } else { |
| g_autofree gchar *cachedirpkg = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); |
| g_autofree gchar *xmlbfn = g_build_filename(cachedirpkg, "metadata.xmlb", NULL); |
| xmlb = g_file_new_for_path(xmlbfn); |
| } |
| self->silo = xb_builder_ensure(builder, xmlb, compile_flags, NULL, error); |
| if (self->silo == NULL) { |
| g_prefix_error_literal(error, "cannot create metadata.xmlb: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return fu_engine_create_silo_index(self, error); |
| } |
| |
| static void |
| fu_engine_remote_list_ensure_p2p_policy_remote(FuEngine *self, FwupdRemote *remote) |
| { |
| if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { |
| FuP2pPolicy p2p_policy = fu_engine_config_get_p2p_policy(self->config); |
| if (p2p_policy & FU_P2P_POLICY_METADATA) |
| fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA); |
| else |
| fwupd_remote_remove_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA); |
| if (p2p_policy & FU_P2P_POLICY_FIRMWARE) |
| fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE); |
| else |
| fwupd_remote_remove_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE); |
| } |
| } |
| |
| static void |
| fu_engine_config_changed_cb(FuEngineConfig *config, FuEngine *self) |
| { |
| GPtrArray *remotes = fu_remote_list_get_all(self->remote_list); |
| |
| fu_idle_set_timeout(self->idle, fu_engine_config_get_idle_timeout(config)); |
| |
| /* allow changing the hardcoded ESP location */ |
| if (fu_engine_config_get_esp_location(config) != NULL) |
| fu_context_set_esp_location(self->ctx, fu_engine_config_get_esp_location(config)); |
| |
| /* amend P2P policy */ |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| fu_engine_remote_list_ensure_p2p_policy_remote(self, remote); |
| } |
| } |
| |
| static void |
| fu_engine_metadata_changed(FuEngine *self) |
| { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_engine_load_metadata_store(self, FU_ENGINE_LOAD_FLAG_NONE, &error_local)) |
| g_warning("Failed to reload metadata store: %s", error_local->message); |
| |
| /* set device properties from the metadata */ |
| fu_engine_md_refresh_devices(self); |
| |
| /* invalidate host security attributes */ |
| fu_security_attrs_remove_all(self->host_security_attrs); |
| |
| /* make the UI update */ |
| fu_engine_emit_changed(self); |
| } |
| |
| static void |
| fu_engine_remote_list_changed_cb(FuRemoteList *remote_list, FuEngine *self) |
| { |
| fu_engine_metadata_changed(self); |
| } |
| |
| static void |
| fu_engine_remote_list_added_cb(FuRemoteList *remote_list, FwupdRemote *remote, FuEngine *self) |
| { |
| FuReleasePriority priority = fu_engine_config_get_release_priority(self->config); |
| if (priority == FU_RELEASE_PRIORITY_LOCAL && |
| fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) { |
| g_debug("priority local and %s is not download remote, so bumping", |
| fwupd_remote_get_id(remote)); |
| fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote) + 1000); |
| } else if (priority == FU_RELEASE_PRIORITY_REMOTE && |
| fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { |
| g_debug("priority remote and %s is download remote, so bumping", |
| fwupd_remote_get_id(remote)); |
| fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote) + 1000); |
| } |
| |
| /* set the p2p policy */ |
| fu_engine_remote_list_ensure_p2p_policy_remote(self, remote); |
| } |
| |
| static gint |
| fu_engine_sort_jcat_results_timestamp_cb(gconstpointer a, gconstpointer b) |
| { |
| JcatResult *ra = *((JcatResult **)a); |
| JcatResult *rb = *((JcatResult **)b); |
| if (jcat_result_get_timestamp(ra) < jcat_result_get_timestamp(rb)) |
| return 1; |
| if (jcat_result_get_timestamp(ra) > jcat_result_get_timestamp(rb)) |
| return -1; |
| return 0; |
| } |
| |
| static JcatResult * |
| fu_engine_get_newest_signature_jcat_result(GPtrArray *results, GError **error) |
| { |
| /* sort by timestamp, newest first */ |
| g_ptr_array_sort(results, fu_engine_sort_jcat_results_timestamp_cb); |
| |
| /* get the first signature, ignoring the checksums */ |
| for (guint i = 0; i < results->len; i++) { |
| JcatResult *result = g_ptr_array_index(results, i); |
| if (jcat_result_get_method(result) == JCAT_BLOB_METHOD_SIGNATURE) |
| return g_object_ref(result); |
| } |
| |
| /* should never happen due to %JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE */ |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "no signature method in results"); |
| return NULL; |
| } |
| |
| static JcatResult * |
| fu_engine_get_system_jcat_result(FuEngine *self, FwupdRemote *remote, GError **error) |
| { |
| g_autoptr(GBytes) blob = NULL; |
| g_autoptr(GInputStream) istream = NULL; |
| g_autoptr(GPtrArray) results = NULL; |
| g_autoptr(JcatItem) jcat_item = NULL; |
| g_autoptr(JcatFile) jcat_file = jcat_file_new(); |
| JcatVerifyFlags jcat_flags = JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | |
| JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | |
| JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE; |
| |
| blob = fu_bytes_get_contents(fwupd_remote_get_filename_cache(remote), error); |
| if (blob == NULL) |
| return NULL; |
| istream = fu_input_stream_from_path(fwupd_remote_get_filename_cache_sig(remote), error); |
| if (istream == NULL) |
| return NULL; |
| if (!jcat_file_import_stream(jcat_file, istream, JCAT_IMPORT_FLAG_NONE, NULL, error)) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| jcat_item = jcat_file_get_item_default(jcat_file, error); |
| if (jcat_item == NULL) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| |
| /* distrusting RSA? */ |
| if (fu_engine_config_get_only_trust_pq_signatures(self->config)) { |
| #if JCAT_CHECK_VERSION(0, 2, 4) |
| jcat_flags |= JCAT_VERIFY_FLAG_ONLY_PQ; |
| #else |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "only trusting PQ signatures requires libjcat >= 0.2.4"); |
| return NULL; |
| #endif |
| } |
| results = jcat_context_verify_item(self->jcat_context, blob, jcat_item, jcat_flags, error); |
| if (results == NULL) { |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| |
| /* return the newest signature */ |
| return fu_engine_get_newest_signature_jcat_result(results, error); |
| } |
| |
| static gboolean |
| fu_engine_validate_result_timestamp(JcatResult *jcat_result, |
| JcatResult *jcat_result_old, |
| GError **error) |
| { |
| gint64 delta = 0; |
| |
| g_return_val_if_fail(JCAT_IS_RESULT(jcat_result), FALSE); |
| g_return_val_if_fail(JCAT_IS_RESULT(jcat_result_old), FALSE); |
| |
| if (jcat_result_get_timestamp(jcat_result) == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "no signing timestamp"); |
| return FALSE; |
| } |
| if (jcat_result_get_timestamp(jcat_result_old) > 0) { |
| delta = jcat_result_get_timestamp(jcat_result) - |
| jcat_result_get_timestamp(jcat_result_old); |
| } |
| if (delta == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "signing timestamp was not newer"); |
| return FALSE; |
| } |
| if (delta < 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_FILE, |
| "new signing timestamp was %" G_GINT64_FORMAT " seconds older", |
| -delta); |
| return FALSE; |
| } |
| if (delta > 0) |
| g_info("timestamp increased, so no rollback"); |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_update_metadata_bytes: |
| * @self: a #FuEngine |
| * @remote_id: a remote ID, e.g. `lvfs` |
| * @bytes_raw: Blob of metadata |
| * @bytes_sig: Blob of metadata signature, typically Jcat binary format |
| * @error: (nullable): optional return location for an error |
| * |
| * Updates the metadata for a specific remote. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_update_metadata_bytes(FuEngine *self, |
| const gchar *remote_id, |
| GBytes *bytes_raw, |
| GBytes *bytes_sig, |
| GError **error) |
| { |
| FwupdRemote *remote; |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GInputStream) istream = NULL; |
| g_autoptr(GPtrArray) results = NULL; |
| g_autoptr(JcatFile) jcat_file = jcat_file_new(); |
| g_autoptr(JcatItem) jcat_item = NULL; |
| g_autoptr(JcatResult) jcat_result = NULL; |
| g_autoptr(JcatResult) jcat_result_old = NULL; |
| JcatVerifyFlags jcat_flags = |
| JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(remote_id != NULL, FALSE); |
| g_return_val_if_fail(bytes_raw != NULL, FALSE); |
| g_return_val_if_fail(bytes_sig != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* check remote is valid */ |
| remote = fu_remote_list_get_by_id(self->remote_list, remote_id); |
| if (remote == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "remote %s not found", |
| remote_id); |
| return FALSE; |
| } |
| if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "remote %s not enabled", |
| remote_id); |
| return FALSE; |
| } |
| |
| /* verify JCatFile, or create a dummy one from legacy data */ |
| istream = g_memory_input_stream_new_from_bytes(bytes_sig); |
| if (!jcat_file_import_stream(jcat_file, istream, JCAT_IMPORT_FLAG_NONE, NULL, error)) |
| return FALSE; |
| |
| /* distrusting RSA? */ |
| if (fu_engine_config_get_only_trust_pq_signatures(self->config)) { |
| #if JCAT_CHECK_VERSION(0, 2, 4) |
| jcat_flags |= JCAT_VERIFY_FLAG_ONLY_PQ; |
| #else |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "only trusting PQ signatures requires libjcat >= 0.2.4"); |
| return FALSE; |
| #endif |
| } |
| |
| /* this should only be signing one thing */ |
| jcat_item = jcat_file_get_item_default(jcat_file, error); |
| if (jcat_item == NULL) |
| return FALSE; |
| results = |
| jcat_context_verify_item(self->jcat_context, bytes_raw, jcat_item, jcat_flags, error); |
| if (results == NULL) |
| return FALSE; |
| |
| /* return the newest signature */ |
| jcat_result = fu_engine_get_newest_signature_jcat_result(results, error); |
| if (jcat_result == NULL) |
| return FALSE; |
| |
| /* verify the metadata was signed later than the existing |
| * metadata for this remote to mitigate a rollback attack */ |
| jcat_result_old = fu_engine_get_system_jcat_result(self, remote, &error_local); |
| if (jcat_result_old == NULL) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { |
| g_info("no existing valid keyrings: %s", error_local->message); |
| } else { |
| g_warning("could not get existing keyring result: %s", |
| error_local->message); |
| } |
| } else { |
| if (!fu_engine_validate_result_timestamp(jcat_result, jcat_result_old, error)) |
| return FALSE; |
| } |
| |
| /* save XML and signature to remotes.d */ |
| if (!fu_bytes_set_contents(fwupd_remote_get_filename_cache(remote), bytes_raw, error)) |
| return FALSE; |
| if (!fwupd_remote_ensure_mtime(remote, error)) |
| return FALSE; |
| |
| #ifdef HAVE_PASSIM |
| /* lazy load */ |
| fu_engine_ensure_passim_client(self); |
| |
| /* send to passimd, if enabled and running */ |
| if (passim_client_get_version(self->passim_client) != NULL && |
| fwupd_remote_get_username(remote) == NULL && |
| fwupd_remote_get_password(remote) == NULL && |
| fu_engine_config_get_p2p_policy(self->config) & FU_P2P_POLICY_METADATA) { |
| g_autofree gchar *basename = |
| g_path_get_basename(fwupd_remote_get_filename_cache(remote)); |
| g_autoptr(GError) error_passim = NULL; |
| g_autoptr(PassimItem) passim_item = passim_item_new(); |
| passim_item_set_basename(passim_item, basename); |
| passim_item_set_bytes(passim_item, bytes_raw); |
| passim_item_set_max_age(passim_item, fwupd_remote_get_refresh_interval(remote)); |
| passim_item_set_share_limit(passim_item, 50); |
| if (!passim_client_publish(self->passim_client, passim_item, &error_passim)) { |
| if (!g_error_matches(error_passim, G_IO_ERROR, G_IO_ERROR_EXISTS)) { |
| g_warning("failed to publish metadata to Passim: %s", |
| error_passim->message); |
| } |
| } else { |
| g_debug("published %s to Passim", passim_item_get_hash(passim_item)); |
| } |
| } |
| #endif |
| |
| /* save signature to remotes.d */ |
| if (!fu_bytes_set_contents(fwupd_remote_get_filename_cache_sig(remote), bytes_sig, error)) |
| return FALSE; |
| if (!fu_engine_load_metadata_store(self, FU_ENGINE_LOAD_FLAG_NONE, error)) |
| return FALSE; |
| |
| /* refresh SUPPORTED flag on devices */ |
| fu_engine_md_refresh_devices(self); |
| |
| /* invalidate host security attributes */ |
| fu_security_attrs_remove_all(self->host_security_attrs); |
| |
| /* make the UI update */ |
| fu_engine_emit_changed(self); |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_update_metadata: |
| * @self: a #FuEngine |
| * @remote_id: a remote ID, e.g. `lvfs` |
| * @fd: file descriptor of the metadata |
| * @fd_sig: file descriptor of the metadata signature |
| * @error: (nullable): optional return location for an error |
| * |
| * Updates the metadata for a specific remote. |
| * |
| * Note: this will close the fds when done |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_update_metadata(FuEngine *self, |
| const gchar *remote_id, |
| gint fd, |
| gint fd_sig, |
| GError **error) |
| { |
| #ifdef HAVE_GIO_UNIX |
| g_autoptr(GBytes) bytes_raw = NULL; |
| g_autoptr(GBytes) bytes_sig = NULL; |
| g_autoptr(GInputStream) stream_fd = NULL; |
| g_autoptr(GInputStream) stream_sig = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(remote_id != NULL, FALSE); |
| g_return_val_if_fail(fd > 0, FALSE); |
| g_return_val_if_fail(fd_sig > 0, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* ensures the fd's are closed on error */ |
| stream_fd = fu_unix_seekable_input_stream_new(fd, TRUE); |
| stream_sig = fu_unix_seekable_input_stream_new(fd_sig, TRUE); |
| |
| /* read the entire file into memory */ |
| bytes_raw = |
| fu_input_stream_read_bytes(stream_fd, 0, FU_ENGINE_MAX_METADATA_SIZE, NULL, error); |
| if (bytes_raw == NULL) |
| return FALSE; |
| |
| /* read signature */ |
| bytes_sig = |
| fu_input_stream_read_bytes(stream_sig, 0, FU_ENGINE_MAX_SIGNATURE_SIZE, NULL, error); |
| if (bytes_sig == NULL) |
| return FALSE; |
| |
| /* update with blobs */ |
| return fu_engine_update_metadata_bytes(self, remote_id, bytes_raw, bytes_sig, error); |
| #else |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "Not supported as <glib-unix.h> is unavailable"); |
| return FALSE; |
| #endif |
| } |
| |
| /** |
| * fu_engine_build_cabinet_from_stream: |
| * @self: a #FuEngine |
| * @stream: a #GInputStream |
| * @error: (nullable): optional return location for an error |
| * |
| * Creates a silo from a .cab file blob. |
| * |
| * Returns: (transfer container): a #XbSilo, or %NULL |
| **/ |
| FuCabinet * |
| fu_engine_build_cabinet_from_stream(FuEngine *self, GInputStream *stream, GError **error) |
| { |
| g_autoptr(FuCabinet) cabinet = fu_cabinet_new(); |
| FuFirmwareParseFlags flags = FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* distrusting RSA? */ |
| if (fu_engine_config_get_only_trust_pq_signatures(self->config)) |
| flags |= FU_FIRMWARE_PARSE_FLAG_ONLY_TRUST_PQ_SIGNATURES; |
| |
| /* load file */ |
| fu_engine_set_status(self, FWUPD_STATUS_DECOMPRESSING); |
| fu_firmware_set_size_max(FU_FIRMWARE(cabinet), |
| fu_engine_config_get_archive_size_max(self->config)); |
| fu_cabinet_set_jcat_context(cabinet, self->jcat_context); |
| if (!fu_firmware_parse_stream(FU_FIRMWARE(cabinet), stream, 0x0, flags, error)) |
| return NULL; |
| return g_steal_pointer(&cabinet); |
| } |
| |
| static FuDevice * |
| fu_engine_get_result_from_component(FuEngine *self, |
| FuEngineRequest *request, |
| FuCabinet *cabinet, |
| XbNode *component, |
| GError **error) |
| { |
| g_autoptr(FuDevice) dev = NULL; |
| g_autoptr(FuRelease) release = fu_release_new(); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GError) error_reqs = NULL; |
| g_autoptr(GPtrArray) provides = NULL; |
| g_autoptr(GPtrArray) tags = NULL; |
| g_autoptr(XbNode) rel = NULL; |
| g_autoptr(XbQuery) query = NULL; |
| |
| dev = fu_device_new(self->ctx); |
| provides = xb_node_query(component, "provides/firmware[@type=$'flashed']", 0, &error_local); |
| if (provides == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "failed to get release: %s", |
| error_local->message); |
| return NULL; |
| } |
| for (guint i = 0; i < provides->len; i++) { |
| XbNode *prov = XB_NODE(g_ptr_array_index(provides, i)); |
| const gchar *guid; |
| g_autoptr(FuDevice) device = NULL; |
| |
| /* is a online or offline update appropriate */ |
| guid = xb_node_get_text(prov); |
| if (guid == NULL) |
| continue; |
| device = fu_device_list_get_by_guid(self->device_list, guid, NULL); |
| if (device != NULL) { |
| fu_device_incorporate(dev, device, FU_DEVICE_INCORPORATE_FLAG_ALL); |
| } else { |
| fu_device_inhibit(dev, "not-found", "Device was not found"); |
| } |
| |
| /* add GUID */ |
| fu_device_add_instance_id(dev, guid); |
| } |
| fu_device_convert_instance_ids(dev); |
| if (fu_device_get_guids(dev)->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "component has no GUIDs"); |
| return NULL; |
| } |
| |
| /* add tags */ |
| tags = xb_node_query(component, "tags/tag[@namespace=$'lvfs']", 0, NULL); |
| if (tags != NULL) { |
| for (guint i = 0; i < tags->len; i++) { |
| XbNode *tag = g_ptr_array_index(tags, i); |
| fu_release_add_tag(release, xb_node_get_text(tag)); |
| } |
| } |
| |
| /* add EOL flag */ |
| if (xb_node_get_attr(component, "date_eol") != NULL) |
| fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_END_OF_LIFE); |
| |
| /* check we can install it */ |
| fu_release_set_device(release, dev); |
| fu_release_set_request(release, request); |
| query = xb_query_new_full(xb_node_get_silo(component), |
| "releases/release", |
| XB_QUERY_FLAG_FORCE_NODE_CACHE, |
| error); |
| if (query == NULL) |
| return NULL; |
| rel = xb_node_query_first_full(component, query, &error_local); |
| if (rel == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "failed to get release: %s", |
| error_local->message); |
| return NULL; |
| } |
| if (!fu_engine_load_release( |
| self, |
| release, |
| cabinet, |
| component, |
| rel, |
| FWUPD_INSTALL_FLAG_IGNORE_VID_PID | FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | |
| FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH | FWUPD_INSTALL_FLAG_ALLOW_OLDER, |
| &error_reqs)) { |
| if (!fu_device_has_inhibit(dev, "not-found")) |
| fu_device_inhibit(dev, "failed-reqs", error_reqs->message); |
| /* continue */ |
| } |
| |
| /* success */ |
| fu_device_add_release(dev, FWUPD_RELEASE(release)); |
| return g_steal_pointer(&dev); |
| } |
| |
| static gint |
| fu_engine_get_details_sort_cb(gconstpointer a, gconstpointer b) |
| { |
| FuDevice *device1 = *((FuDevice **)a); |
| FuDevice *device2 = *((FuDevice **)b); |
| if (!fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE) && |
| fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE)) |
| return 1; |
| if (fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE) && |
| !fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE)) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * fu_engine_get_details: |
| * @self: a #FuEngine |
| * @request: a #FuEngineRequest |
| * @stream: a seekable #GInputStream |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the details about a local file. |
| * |
| * Note: this will close the fd when done |
| * |
| * Returns: (transfer container) (element-type FuDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_details(FuEngine *self, |
| FuEngineRequest *request, |
| GInputStream *stream, |
| GError **error) |
| { |
| GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; |
| g_autoptr(GPtrArray) components = NULL; |
| g_autoptr(GPtrArray) details = NULL; |
| g_autoptr(GPtrArray) checksums = g_ptr_array_new_with_free_func(g_free); |
| g_autoptr(FuCabinet) cabinet = NULL; |
| g_autoptr(GPtrArray) rels_by_csum = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| cabinet = fu_engine_build_cabinet_from_stream(self, stream, error); |
| if (cabinet == NULL) { |
| g_prefix_error_literal(error, "failed to load file: "); |
| return NULL; |
| } |
| components = fu_cabinet_get_components(cabinet, error); |
| if (components == NULL) |
| return NULL; |
| |
| /* calculate the checksums of the blob */ |
| for (guint i = 0; checksum_types[i] != 0; i++) { |
| g_autofree gchar *checksum = |
| fu_input_stream_compute_checksum(stream, checksum_types[i], error); |
| if (checksum == NULL) |
| return NULL; |
| g_ptr_array_add(checksums, g_steal_pointer(&checksum)); |
| } |
| |
| /* does this exist in any enabled remote */ |
| for (guint i = 0; i < checksums->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums, i); |
| rels_by_csum = fu_engine_get_releases_for_container_checksum(self, csum); |
| if (rels_by_csum != NULL) |
| break; |
| } |
| |
| /* create results with all the metadata in */ |
| details = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < components->len; i++) { |
| XbNode *component = g_ptr_array_index(components, i); |
| FuDevice *dev; |
| g_autoptr(FuRelease) rel = fu_release_new(); |
| |
| dev = fu_engine_get_result_from_component(self, request, cabinet, component, error); |
| if (dev == NULL) |
| return NULL; |
| fu_device_add_release(dev, FWUPD_RELEASE(rel)); |
| |
| for (guint j = 0; rels_by_csum != NULL && j < rels_by_csum->len; j++) { |
| XbNode *rel_by_csum = g_ptr_array_index(rels_by_csum, j); |
| const gchar *remote_id = |
| xb_node_query_text(rel_by_csum, |
| "../../../custom/value[@key='fwupd::RemoteId']", |
| NULL); |
| if (remote_id != NULL) |
| fu_release_set_remote_id(rel, remote_id); |
| fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED); |
| } |
| |
| /* add the checksum of the container blob */ |
| for (guint j = 0; j < checksums->len; j++) { |
| const gchar *csum = g_ptr_array_index(checksums, j); |
| fu_release_add_checksum(rel, csum); |
| } |
| g_ptr_array_add(details, dev); |
| } |
| |
| /* order multiple devices so that the one that passes the requirement |
| * is listed first */ |
| g_ptr_array_sort(details, fu_engine_get_details_sort_cb); |
| |
| return g_steal_pointer(&details); |
| } |
| |
| static gint |
| fu_engine_sort_devices_by_priority_name(gconstpointer a, gconstpointer b) |
| { |
| FuDevice *dev_a = *((FuDevice **)a); |
| FuDevice *dev_b = *((FuDevice **)b); |
| gint prio_a = fu_device_get_priority(dev_a); |
| gint prio_b = fu_device_get_priority(dev_b); |
| const gchar *name_a = fu_device_get_name(dev_a); |
| const gchar *name_b = fu_device_get_name(dev_b); |
| |
| if (prio_a > prio_b) |
| return -1; |
| if (prio_a < prio_b) |
| return 1; |
| if (g_strcmp0(name_a, name_b) > 0) |
| return 1; |
| if (g_strcmp0(name_a, name_b) < 0) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * fu_engine_get_devices: |
| * @self: a #FuEngine |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the list of devices. |
| * |
| * Returns: (transfer container) (element-type FwupdDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_devices(FuEngine *self, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| devices = fu_device_list_get_active(self->device_list); |
| if (devices->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No detected devices"); |
| return NULL; |
| } |
| g_ptr_array_sort(devices, fu_engine_sort_devices_by_priority_name); |
| return g_steal_pointer(&devices); |
| } |
| |
| /** |
| * fu_engine_get_devices_by_guid: |
| * @self: a #FuEngine |
| * @guid: a GUID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets a specific device. |
| * |
| * Returns: (transfer full): a device, or %NULL if not found |
| **/ |
| GPtrArray * |
| fu_engine_get_devices_by_guid(FuEngine *self, const gchar *guid, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_tmp = NULL; |
| |
| /* find the devices by GUID */ |
| devices_tmp = fu_device_list_get_active(self->device_list); |
| devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < devices_tmp->len; i++) { |
| FuDevice *dev_tmp = g_ptr_array_index(devices_tmp, i); |
| if (fu_device_has_guid(dev_tmp, guid)) |
| g_ptr_array_add(devices, g_object_ref(dev_tmp)); |
| } |
| |
| /* nothing */ |
| if (devices->len == 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "failed to find any device providing %s", |
| guid); |
| return NULL; |
| } |
| |
| /* success */ |
| return g_steal_pointer(&devices); |
| } |
| |
| /** |
| * fu_engine_get_devices_by_composite_id: |
| * @self: a #FuEngine |
| * @composite_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets all active devices that match a specific composite ID. |
| * |
| * Returns: (transfer full) (element-type FuDevice): devices |
| **/ |
| GPtrArray * |
| fu_engine_get_devices_by_composite_id(FuEngine *self, const gchar *composite_id, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| g_autoptr(GPtrArray) devices_tmp = NULL; |
| |
| /* find the devices by composite ID */ |
| devices_tmp = fu_device_list_get_active(self->device_list); |
| devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < devices_tmp->len; i++) { |
| FuDevice *dev_tmp = g_ptr_array_index(devices_tmp, i); |
| if (g_strcmp0(fu_device_get_composite_id(dev_tmp), composite_id) == 0) |
| g_ptr_array_add(devices, g_object_ref(dev_tmp)); |
| } |
| |
| /* nothing */ |
| if (devices->len == 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "failed to find any device with composite ID %s", |
| composite_id); |
| return NULL; |
| } |
| |
| /* success */ |
| return g_steal_pointer(&devices); |
| } |
| |
| #ifdef HAVE_HSI |
| static void |
| fu_engine_get_history_set_hsi_attrs(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(GPtrArray) vals = NULL; |
| g_autofree gchar *host_security_id = fu_engine_get_host_security_id(self, NULL); |
| |
| /* ensure up to date */ |
| fu_engine_ensure_security_attrs(self); |
| |
| /* add attributes */ |
| vals = fu_security_attrs_get_all(self->host_security_attrs, NULL); |
| for (guint i = 0; i < vals->len; i++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(vals, i); |
| const gchar *tmp; |
| tmp = fwupd_security_attr_result_to_string(fwupd_security_attr_get_result(attr)); |
| fu_device_set_metadata(device, fwupd_security_attr_get_appstream_id(attr), tmp); |
| } |
| |
| /* computed value */ |
| fu_device_set_metadata(device, "HSI", host_security_id); |
| } |
| #endif |
| |
| static gboolean |
| fu_engine_fixup_history_device_for_rel(FuEngine *self, |
| FuDevice *device, |
| XbNode *rel, |
| GError **error) |
| { |
| FwupdRelease *release = fu_device_get_release_default(device); |
| const gchar *appstream_id; |
| g_autoptr(XbNode) component = NULL; |
| |
| component = xb_node_query_first(rel, "../..", error); |
| if (component == NULL) { |
| fwupd_error_convert(error); |
| g_prefix_error_literal(error, "failed to load component: "); |
| return FALSE; |
| } |
| |
| /* check appstream ID is the same */ |
| appstream_id = xb_node_query_text(component, "id", NULL); |
| if (appstream_id == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_DATA, |
| "no appstream ID for component"); |
| return FALSE; |
| } |
| if (g_strcmp0(appstream_id, fwupd_release_get_appstream_id(release)) != 0) { |
| g_debug("ignoring %s as expecting %s", |
| appstream_id, |
| fwupd_release_get_appstream_id(release)); |
| return TRUE; |
| } |
| |
| if (!fu_release_load(FU_RELEASE(release), |
| NULL, |
| component, |
| rel, |
| FWUPD_INSTALL_FLAG_NONE, |
| error)) { |
| g_prefix_error_literal(error, "failed to load release: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_fixup_history_device_for_csum(FuEngine *self, |
| FuDevice *device, |
| const gchar *csum, |
| GError **error) |
| { |
| g_autoptr(GPtrArray) rels = NULL; |
| |
| rels = fu_engine_get_releases_for_container_checksum(self, csum); |
| if (rels == NULL) |
| return TRUE; |
| for (guint i = 0; i < rels->len; i++) { |
| XbNode *rel = g_ptr_array_index(rels, i); |
| if (!fu_engine_fixup_history_device_for_rel(self, device, rel, error)) |
| return FALSE; |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) |
| break; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_fixup_history_device(FuEngine *self, FuDevice *device, GError **error) |
| { |
| FwupdRelease *release; |
| GPtrArray *csums; |
| |
| /* get the checksums */ |
| release = fu_device_get_release_default(device); |
| if (release == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_DATA, |
| "no default release for device"); |
| return FALSE; |
| } |
| |
| /* find the checksum that matches */ |
| csums = fwupd_release_get_checksums(release); |
| if (csums->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_DATA, |
| "no checksums from release"); |
| return FALSE; |
| } |
| for (guint i = 0; i < csums->len; i++) { |
| const gchar *csum = g_ptr_array_index(csums, i); |
| g_debug("finding release checksum %s", csum); |
| if (!fu_engine_fixup_history_device_for_csum(self, device, csum, error)) |
| return FALSE; |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) |
| break; |
| } |
| |
| /* no device was matched, which is fine... */ |
| return TRUE; |
| } |
| |
| /** |
| * fu_engine_get_history: |
| * @self: a #FuEngine |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the list of history. |
| * |
| * Returns: (transfer container) (element-type FwupdDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_history(FuEngine *self, GError **error) |
| { |
| g_autoptr(GPtrArray) devices_all = NULL; |
| g_autoptr(GPtrArray) devices = |
| g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| devices_all = fu_history_get_devices(self->history, error); |
| if (devices_all == NULL) |
| return NULL; |
| for (guint i = 0; i < devices_all->len; i++) { |
| FuDevice *dev = g_ptr_array_index(devices_all, i); |
| if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_EMULATED)) |
| continue; |
| g_ptr_array_add(devices, g_object_ref(dev)); |
| } |
| if (devices->len == 0) { |
| g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No history"); |
| return NULL; |
| } |
| |
| #ifdef HAVE_HSI |
| /* if this is the system firmware device, add the HSI attrs */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *dev = g_ptr_array_index(devices, i); |
| if (fu_device_has_private_flag_quark(dev, quarks[QUARK_HOST_FIRMWARE])) |
| fu_engine_get_history_set_hsi_attrs(self, dev); |
| } |
| #endif |
| |
| /* try to set the remote ID for each device */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *dev = g_ptr_array_index(devices, i); |
| if (!fu_engine_fixup_history_device(self, dev, error)) |
| return NULL; |
| } |
| |
| return g_steal_pointer(&devices); |
| } |
| |
| /** |
| * fu_engine_get_remotes: |
| * @self: a #FuEngine |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the list of remotes in use by the engine. |
| * |
| * Returns: (transfer container) (element-type FwupdRemote): results |
| **/ |
| GPtrArray * |
| fu_engine_get_remotes(FuEngine *self, GError **error) |
| { |
| GPtrArray *remotes; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| remotes = fu_remote_list_get_all(self->remote_list); |
| if (remotes->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "No remotes configured"); |
| return NULL; |
| } |
| |
| /* deep copy so the remote list can be kept up to date */ |
| return g_ptr_array_copy(remotes, (GCopyFunc)g_object_ref, NULL); |
| } |
| |
| /** |
| * fu_engine_get_remote_by_id: |
| * @self: a #FuEngine |
| * @remote_id: a string representation of a remote |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the FwupdRemote object. |
| * |
| * Returns: FwupdRemote |
| **/ |
| FwupdRemote * |
| fu_engine_get_remote_by_id(FuEngine *self, const gchar *remote_id, GError **error) |
| { |
| g_autoptr(GPtrArray) remotes = NULL; |
| |
| remotes = fu_engine_get_remotes(self, error); |
| if (remotes == NULL) |
| return NULL; |
| |
| for (guint i = 0; i < remotes->len; i++) { |
| FwupdRemote *remote = g_ptr_array_index(remotes, i); |
| if (g_strcmp0(remote_id, fwupd_remote_get_id(remote)) == 0) |
| return remote; |
| } |
| |
| g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Couldn't find remote %s", remote_id); |
| |
| return NULL; |
| } |
| |
| static gint |
| fu_engine_sort_releases_cb(gconstpointer a, gconstpointer b, gpointer user_data) |
| { |
| FuDevice *device = FU_DEVICE(user_data); |
| FuRelease *rel_a = FU_RELEASE(*((FuRelease **)a)); |
| FuRelease *rel_b = FU_RELEASE(*((FuRelease **)b)); |
| gint rc; |
| |
| /* first by branch */ |
| rc = g_strcmp0(fu_release_get_branch(rel_b), fu_release_get_branch(rel_a)); |
| if (rc != 0) |
| return rc; |
| |
| /* then by version */ |
| rc = fu_version_compare(fu_release_get_version(rel_b), |
| fu_release_get_version(rel_a), |
| fu_device_get_version_format(device)); |
| if (rc != 0) |
| return rc; |
| |
| /* then by priority */ |
| return fu_release_compare(rel_a, rel_b); |
| } |
| |
| static gboolean |
| fu_engine_check_release_is_approved(FuEngine *self, FwupdRelease *rel) |
| { |
| GPtrArray *csums = fwupd_release_get_checksums(rel); |
| if (self->approved_firmware == NULL) |
| return FALSE; |
| for (guint i = 0; i < csums->len; i++) { |
| const gchar *csum = g_ptr_array_index(csums, i); |
| g_info("checking %s against approved list", csum); |
| if (g_hash_table_lookup(self->approved_firmware, csum) != NULL) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| fu_engine_check_release_is_blocked(FuEngine *self, FuRelease *release) |
| { |
| GPtrArray *csums = fu_release_get_checksums(release); |
| if (self->blocked_firmware == NULL) |
| return FALSE; |
| for (guint i = 0; i < csums->len; i++) { |
| const gchar *csum = g_ptr_array_index(csums, i); |
| if (g_hash_table_lookup(self->blocked_firmware, csum) != NULL) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| fu_engine_add_releases_for_device_component(FuEngine *self, |
| FuEngineRequest *request, |
| FuDevice *device, |
| XbNode *component, |
| GPtrArray *releases, |
| GError **error) |
| { |
| FwupdFeatureFlags feature_flags; |
| FwupdVersionFormat fmt = fu_device_get_version_format(device); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) releases_tmp = NULL; |
| FwupdInstallFlags install_flags = |
| FWUPD_INSTALL_FLAG_IGNORE_VID_PID | FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH | |
| FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_OLDER; |
| |
| /* get all releases */ |
| releases_tmp = xb_node_query(component, "releases/release", 0, &error_local); |
| if (releases_tmp == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) |
| return TRUE; |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) |
| return TRUE; |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| feature_flags = fu_engine_request_get_feature_flags(request); |
| for (guint i = 0; i < releases_tmp->len; i++) { |
| XbNode *rel = g_ptr_array_index(releases_tmp, i); |
| const gchar *remote_id; |
| const gchar *update_message; |
| const gchar *update_image; |
| const gchar *update_request_id; |
| gint vercmp; |
| GPtrArray *checksums; |
| GPtrArray *locations; |
| g_autoptr(FuRelease) release = fu_release_new(); |
| g_autoptr(GError) error_loop = NULL; |
| |
| /* create new FwupdRelease for the XbNode */ |
| fu_release_set_request(release, request); |
| fu_release_set_device(release, device); |
| if (!fu_engine_load_release(self, |
| release, |
| NULL, /* cabinet */ |
| component, |
| rel, |
| install_flags, |
| &error_loop)) { |
| g_debug("failed to set release for component: %s", error_loop->message); |
| continue; |
| } |
| |
| /* fall back to quirk-provided value */ |
| if (fwupd_release_get_install_duration(FWUPD_RELEASE(release)) == 0) { |
| fwupd_release_set_install_duration(FWUPD_RELEASE(release), |
| fu_device_get_install_duration(device)); |
| } |
| |
| /* invalid */ |
| locations = fwupd_release_get_locations(FWUPD_RELEASE(release)); |
| if (locations->len == 0) { |
| g_autofree gchar *str = fwupd_codec_to_string(FWUPD_CODEC(release)); |
| g_debug("no locations for %s", str); |
| continue; |
| } |
| checksums = fu_release_get_checksums(release); |
| if (checksums->len == 0) { |
| g_autofree gchar *str = fwupd_codec_to_string(FWUPD_CODEC(release)); |
| g_debug("no locations for %s", str); |
| continue; |
| } |
| |
| /* different branch */ |
| if (g_strcmp0(fu_release_get_branch(release), fu_device_get_branch(device)) != 0) { |
| if ((feature_flags & FWUPD_FEATURE_FLAG_SWITCH_BRANCH) == 0) { |
| g_info("client does not understand branches, skipping %s:%s", |
| fu_release_get_branch(release), |
| fu_release_get_version(release)); |
| continue; |
| } |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH); |
| } |
| |
| /* test for upgrade or downgrade */ |
| vercmp = fu_version_compare(fu_release_get_version(release), |
| fu_device_get_version(device), |
| fmt); |
| if (vercmp > 0) |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_UPGRADE); |
| else if (vercmp < 0) |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_DOWNGRADE); |
| |
| /* lower than allowed to downgrade to */ |
| if (fu_device_get_version_lowest(device) != NULL && |
| fu_version_compare(fu_release_get_version(release), |
| fu_device_get_version_lowest(device), |
| fmt) < 0) { |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_VERSION); |
| } |
| |
| /* manually blocked */ |
| if (fu_engine_check_release_is_blocked(self, release)) |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); |
| |
| /* check if remote is filtering firmware */ |
| remote_id = fwupd_release_get_remote_id(FWUPD_RELEASE(release)); |
| if (remote_id != NULL) { |
| FwupdRemote *remote = fu_engine_get_remote_by_id(self, remote_id, NULL); |
| if (remote != NULL && |
| fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED) && |
| !fu_engine_check_release_is_approved(self, FWUPD_RELEASE(release))) { |
| fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); |
| } |
| } |
| |
| /* add update message if exists but device doesn't already have one */ |
| update_message = fwupd_release_get_update_message(FWUPD_RELEASE(release)); |
| if (fu_device_get_update_message(device) == NULL && update_message != NULL) |
| fu_device_set_update_message(device, update_message); |
| update_image = fwupd_release_get_update_image(FWUPD_RELEASE(release)); |
| if (fu_device_get_update_image(device) == NULL && update_image != NULL) |
| fu_device_set_update_image(device, update_image); |
| update_request_id = fu_release_get_update_request_id(release); |
| if (fu_device_get_update_request_id(device) == NULL && update_request_id != NULL) { |
| fu_device_add_request_flag(device, |
| FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); |
| fu_device_set_update_request_id(device, update_request_id); |
| } |
| |
| /* success */ |
| g_ptr_array_add(releases, g_steal_pointer(&release)); |
| |
| /* if we're only checking for SUPPORTED then *any* release is good enough */ |
| if (fu_engine_request_has_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE) && |
| releases->len > 0) |
| break; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static const gchar * |
| fu_engine_get_branch_fallback(const gchar *nullable_branch) |
| { |
| if (nullable_branch == NULL) |
| return "default"; |
| return nullable_branch; |
| } |
| |
| GPtrArray * |
| fu_engine_get_releases_for_device(FuEngine *self, |
| FuEngineRequest *request, |
| FuDevice *device, |
| GError **error) |
| { |
| GPtrArray *device_guids; |
| g_autoptr(GPtrArray) branches = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| |
| /* no components in silo */ |
| if (self->query_component_by_guid == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no components in silo"); |
| return NULL; |
| } |
| |
| /* get device version */ |
| if (!fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERSION) && |
| !fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_FLAGS)) { |
| const gchar *version = fu_device_get_version(device); |
| if (version == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no version set"); |
| return NULL; |
| } |
| } |
| |
| /* only show devices that can be updated */ |
| if (!fu_engine_request_has_feature_flag(request, FWUPD_FEATURE_FLAG_SHOW_PROBLEMS) && |
| !fu_device_is_updatable(device)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "is not updatable"); |
| return NULL; |
| } |
| |
| /* only show devices that can be updated */ |
| if (!fu_engine_request_has_feature_flag(request, FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC) && |
| fu_device_has_request_flag(device, FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "is not updatable as requires a non-generic request"); |
| return NULL; |
| } |
| |
| /* get all the components that provide any of these GUIDs */ |
| device_guids = fu_device_get_guids(device); |
| releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint j = 0; j < device_guids->len; j++) { |
| const gchar *guid = g_ptr_array_index(device_guids, j); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) components = NULL; |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| |
| xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); |
| components = xb_silo_query_with_context(self->silo, |
| self->query_component_by_guid, |
| &context, |
| &error_local); |
| if (components == NULL) { |
| g_debug("%s was not found: %s", guid, error_local->message); |
| continue; |
| } |
| |
| /* find all the releases that pass all the requirements */ |
| g_debug("%s matched %u components", guid, components->len); |
| for (guint i = 0; i < components->len; i++) { |
| XbNode *component = XB_NODE(g_ptr_array_index(components, i)); |
| g_autoptr(GError) error_tmp = NULL; |
| if (!fu_engine_add_releases_for_device_component(self, |
| request, |
| device, |
| component, |
| releases, |
| &error_tmp)) { |
| g_debug("%s", error_tmp->message); |
| continue; |
| } |
| } |
| g_debug("%s matched %u releases", guid, releases->len); |
| |
| /* if we're only checking for SUPPORTED then *any* release is good enough */ |
| if (fu_engine_request_has_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE) && |
| releases->len > 0) |
| break; |
| } |
| |
| /* are there multiple branches available */ |
| branches = g_ptr_array_new_with_free_func(g_free); |
| g_ptr_array_add(branches, |
| g_strdup(fu_engine_get_branch_fallback(fu_device_get_branch(device)))); |
| for (guint i = 0; i < releases->len; i++) { |
| FwupdRelease *rel_tmp = FWUPD_RELEASE(g_ptr_array_index(releases, i)); |
| const gchar *branch_tmp = |
| fu_engine_get_branch_fallback(fwupd_release_get_branch(rel_tmp)); |
| if (g_ptr_array_find_with_equal_func(branches, branch_tmp, g_str_equal, NULL)) |
| continue; |
| g_ptr_array_add(branches, g_strdup(branch_tmp)); |
| } |
| if (branches->len > 1) |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES); |
| |
| /* return the compound error */ |
| if (releases->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No releases found"); |
| return NULL; |
| } |
| return g_steal_pointer(&releases); |
| } |
| |
| /** |
| * fu_engine_search: |
| * @self: a #FuEngine |
| * @token: a search term |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets all the releases that match a specific token. |
| * |
| * Returns: (transfer container) (element-type FwupdResult): results |
| **/ |
| GPtrArray * |
| fu_engine_search(FuEngine *self, const gchar *token, GError **error) |
| { |
| g_autoptr(GPtrArray) releases = |
| g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(token != NULL, NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* bind search token and then query */ |
| xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, token, NULL); |
| for (guint i = 0; i < self->search_queries->len; i++) { |
| XbQuery *query_tmp = g_ptr_array_index(self->search_queries, i); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(GPtrArray) components = NULL; |
| |
| components = |
| xb_silo_query_with_context(self->silo, query_tmp, &context, &error_local); |
| if (components == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || |
| g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) |
| continue; |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| fwupd_error_convert(error); |
| return NULL; |
| } |
| for (guint j = 0; j < components->len; j++) { |
| g_autoptr(FuRelease) rel = fu_release_new(); |
| XbNode *component = g_ptr_array_index(components, j); |
| if (!fu_release_load(rel, |
| NULL, |
| component, |
| NULL, |
| FWUPD_INSTALL_FLAG_FORCE, |
| error)) |
| return NULL; |
| g_ptr_array_add(releases, g_steal_pointer(&rel)); |
| } |
| } |
| |
| /* success */ |
| return g_steal_pointer(&releases); |
| } |
| |
| /** |
| * fu_engine_get_releases: |
| * @self: a #FuEngine |
| * @request: a #FuEngineRequest |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the releases available for a specific device. |
| * |
| * Returns: (transfer container) (element-type FwupdDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_releases(FuEngine *self, |
| FuEngineRequest *request, |
| const gchar *device_id, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| g_autoptr(GPtrArray) releases_deduped = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(FU_IS_ENGINE_REQUEST(request), NULL); |
| g_return_val_if_fail(device_id != NULL, NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* find the device */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return NULL; |
| |
| /* get all the releases for the device */ |
| releases = fu_engine_get_releases_for_device(self, request, device, error); |
| if (releases == NULL) |
| return NULL; |
| if (releases->len == 0) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "No releases for device"); |
| return NULL; |
| } |
| g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); |
| |
| /* dedupe by container checksum */ |
| if (fu_engine_config_get_release_dedupe(self->config)) { |
| g_autoptr(GHashTable) checksums = |
| g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
| releases_deduped = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < releases->len; i++) { |
| FuRelease *rel = g_ptr_array_index(releases, i); |
| GPtrArray *csums = fu_release_get_checksums(rel); |
| gboolean found = FALSE; |
| |
| /* find existing */ |
| for (guint j = 0; j < csums->len; j++) { |
| const gchar *csum = g_ptr_array_index(csums, j); |
| g_autofree gchar *key = |
| g_strdup_printf("%s:%s", csum, fu_release_get_version(rel)); |
| if (g_hash_table_contains(checksums, key)) { |
| found = TRUE; |
| break; |
| } |
| g_hash_table_add(checksums, g_steal_pointer(&key)); |
| } |
| if (found) { |
| g_debug("found higher priority release for %s, skipping", |
| fu_release_get_version(rel)); |
| continue; |
| } |
| g_ptr_array_add(releases_deduped, g_object_ref(rel)); |
| } |
| } else { |
| releases_deduped = g_ptr_array_ref(releases); |
| } |
| |
| /* success */ |
| return g_steal_pointer(&releases_deduped); |
| } |
| |
| /** |
| * fu_engine_get_downgrades: |
| * @self: a #FuEngine |
| * @request: a #FuEngineRequest |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the downgrades available for a specific device. |
| * |
| * Returns: (transfer container) (element-type FwupdDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_downgrades(FuEngine *self, |
| FuEngineRequest *request, |
| const gchar *device_id, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| g_autoptr(GPtrArray) releases_tmp = NULL; |
| g_autoptr(GString) error_str = g_string_new(NULL); |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(device_id != NULL, NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* find the device */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return NULL; |
| |
| /* get all the releases for the device */ |
| releases_tmp = fu_engine_get_releases_for_device(self, request, device, error); |
| if (releases_tmp == NULL) |
| return NULL; |
| releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < releases_tmp->len; i++) { |
| FwupdRelease *rel_tmp = g_ptr_array_index(releases_tmp, i); |
| |
| /* same as installed */ |
| if (!fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && |
| !fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { |
| g_string_append_printf(error_str, |
| "%s=same, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s as the same as %s", |
| fwupd_release_get_version(rel_tmp), |
| fu_device_get_version(device)); |
| continue; |
| } |
| |
| /* newer than current */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE)) { |
| g_string_append_printf(error_str, |
| "%s=newer, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s as newer than %s", |
| fwupd_release_get_version(rel_tmp), |
| fu_device_get_version(device)); |
| continue; |
| } |
| |
| /* don't show releases we are not allowed to downgrade to */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_VERSION)) { |
| g_string_append_printf(error_str, |
| "%s=lowest, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s as older than lowest %s", |
| fwupd_release_get_version(rel_tmp), |
| fu_device_get_version_lowest(device)); |
| continue; |
| } |
| |
| /* different branch */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH)) { |
| g_info("ignoring release %s as branch %s, and device is %s", |
| fwupd_release_get_version(rel_tmp), |
| fwupd_release_get_branch(rel_tmp), |
| fu_device_get_branch(device)); |
| continue; |
| } |
| |
| g_ptr_array_add(releases, g_object_ref(rel_tmp)); |
| } |
| if (error_str->len > 2) |
| g_string_truncate(error_str, error_str->len - 2); |
| if (releases->len == 0) { |
| if (error_str->len > 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "current version is %s: %s", |
| fu_device_get_version(device), |
| error_str->str); |
| return NULL; |
| } |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "current version is %s", |
| fu_device_get_version(device)); |
| return NULL; |
| } |
| g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); |
| return g_steal_pointer(&releases); |
| } |
| |
| GPtrArray * |
| fu_engine_get_approved_firmware(FuEngine *self) |
| { |
| GPtrArray *checksums = g_ptr_array_new_with_free_func(g_free); |
| if (self->approved_firmware != NULL) { |
| g_autoptr(GList) keys = g_hash_table_get_keys(self->approved_firmware); |
| for (GList *l = keys; l != NULL; l = l->next) { |
| const gchar *csum = l->data; |
| g_ptr_array_add(checksums, g_strdup(csum)); |
| } |
| } |
| return checksums; |
| } |
| |
| void |
| fu_engine_add_approved_firmware(FuEngine *self, const gchar *checksum) |
| { |
| if (self->approved_firmware == NULL) { |
| self->approved_firmware = |
| g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
| } |
| g_hash_table_add(self->approved_firmware, g_strdup(checksum)); |
| } |
| |
| GPtrArray * |
| fu_engine_get_blocked_firmware(FuEngine *self) |
| { |
| GPtrArray *checksums = g_ptr_array_new_with_free_func(g_free); |
| if (self->blocked_firmware != NULL) { |
| g_autoptr(GList) keys = g_hash_table_get_keys(self->blocked_firmware); |
| for (GList *l = keys; l != NULL; l = l->next) { |
| const gchar *csum = l->data; |
| g_ptr_array_add(checksums, g_strdup(csum)); |
| } |
| } |
| return checksums; |
| } |
| |
| static void |
| fu_engine_add_blocked_firmware(FuEngine *self, const gchar *checksum) |
| { |
| if (self->blocked_firmware == NULL) { |
| self->blocked_firmware = |
| g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
| } |
| g_hash_table_add(self->blocked_firmware, g_strdup(checksum)); |
| } |
| |
| gboolean |
| fu_engine_set_blocked_firmware(FuEngine *self, GPtrArray *checksums, GError **error) |
| { |
| /* update in-memory hash */ |
| if (self->blocked_firmware != NULL) { |
| g_hash_table_unref(self->blocked_firmware); |
| self->blocked_firmware = NULL; |
| } |
| for (guint i = 0; i < checksums->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums, i); |
| fu_engine_add_blocked_firmware(self, csum); |
| } |
| |
| /* save database */ |
| if (!fu_history_clear_blocked_firmware(self->history, error)) |
| return FALSE; |
| for (guint i = 0; i < checksums->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums, i); |
| if (!fu_history_add_blocked_firmware(self->history, csum, error)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| gchar * |
| fu_engine_self_sign(FuEngine *self, const gchar *value, JcatSignFlags flags, GError **error) |
| { |
| g_autoptr(JcatBlob) jcat_signature = NULL; |
| g_autoptr(JcatEngine) jcat_engine = NULL; |
| g_autoptr(JcatResult) jcat_result = NULL; |
| g_autoptr(GBytes) payload = NULL; |
| |
| /* create detached signature and verify */ |
| jcat_engine = jcat_context_get_engine(self->jcat_context, JCAT_BLOB_KIND_PKCS7, error); |
| if (jcat_engine == NULL) |
| return NULL; |
| payload = g_bytes_new(value, strlen(value)); |
| jcat_signature = jcat_engine_self_sign(jcat_engine, payload, flags, error); |
| if (jcat_signature == NULL) |
| return NULL; |
| jcat_result = jcat_engine_self_verify(jcat_engine, |
| payload, |
| jcat_blob_get_data(jcat_signature), |
| JCAT_VERIFY_FLAG_NONE, |
| error); |
| if (jcat_result == NULL) |
| return NULL; |
| return jcat_blob_get_data_as_string(jcat_signature); |
| } |
| |
| /** |
| * fu_engine_get_upgrades: |
| * @self: a #FuEngine |
| * @request: a #FuEngineRequest |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the upgrades available for a specific device. |
| * |
| * Returns: (transfer container) (element-type FwupdDevice): results |
| **/ |
| GPtrArray * |
| fu_engine_get_upgrades(FuEngine *self, |
| FuEngineRequest *request, |
| const gchar *device_id, |
| GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| g_autoptr(GPtrArray) releases = NULL; |
| g_autoptr(GPtrArray) releases_tmp = NULL; |
| g_autoptr(GString) error_str = g_string_new(NULL); |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(device_id != NULL, NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* find the device */ |
| device = fu_device_list_get_by_id(self->device_list, device_id, error); |
| if (device == NULL) |
| return NULL; |
| |
| /* there is no point checking each release */ |
| if (!fu_device_is_updatable(device)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Device is not updatable"); |
| return NULL; |
| } |
| |
| /* stay on one firmware version unless the new version is explicitly specified */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "Installing a specific release is explicitly required"); |
| return NULL; |
| } |
| |
| /* don't show upgrades again until we reboot */ |
| if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "A reboot is pending"); |
| return NULL; |
| } |
| |
| /* get all the releases for the device */ |
| releases_tmp = fu_engine_get_releases_for_device(self, request, device, error); |
| if (releases_tmp == NULL) |
| return NULL; |
| releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| for (guint i = 0; i < releases_tmp->len; i++) { |
| FwupdRelease *rel_tmp = g_ptr_array_index(releases_tmp, i); |
| |
| /* same as installed */ |
| if (!fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && |
| !fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { |
| g_string_append_printf(error_str, |
| "%s=same, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s == %s", |
| fwupd_release_get_version(rel_tmp), |
| fu_device_get_version(device)); |
| continue; |
| } |
| |
| /* older than current */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { |
| g_string_append_printf(error_str, |
| "%s=older, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s < %s", |
| fwupd_release_get_version(rel_tmp), |
| fu_device_get_version(device)); |
| continue; |
| } |
| |
| /* not approved */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL)) { |
| g_string_append_printf(error_str, |
| "%s=not-approved, ", |
| fwupd_release_get_version(rel_tmp)); |
| g_debug("ignoring %s as not approved as required by %s", |
| fwupd_release_get_version(rel_tmp), |
| fwupd_release_get_remote_id(rel_tmp)); |
| continue; |
| } |
| |
| /* different branch */ |
| if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH)) { |
| g_info("ignoring release %s as branch %s, and device is %s", |
| fwupd_release_get_version(rel_tmp), |
| fwupd_release_get_branch(rel_tmp), |
| fu_device_get_branch(device)); |
| continue; |
| } |
| |
| g_ptr_array_add(releases, g_object_ref(rel_tmp)); |
| } |
| if (error_str->len > 2) |
| g_string_truncate(error_str, error_str->len - 2); |
| if (releases->len == 0) { |
| if (error_str->len > 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "current version is %s: %s", |
| fu_device_get_version(device), |
| error_str->str); |
| return NULL; |
| } |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "current version is %s", |
| fu_device_get_version(device)); |
| return NULL; |
| } |
| g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); |
| return g_steal_pointer(&releases); |
| } |
| |
| /** |
| * fu_engine_clear_results: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Clear the historical state of a specific device operation. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_clear_results(FuEngine *self, const gchar *device_id, GError **error) |
| { |
| g_autoptr(FuDevice) device = NULL; |
| FuPlugin *plugin; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(device_id != NULL, FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* find the device */ |
| device = fu_engine_get_item_by_id_fallback_history(self, device_id, error); |
| if (device == NULL) |
| return FALSE; |
| |
| /* already set on the database */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "device already has notified flag"); |
| return FALSE; |
| } |
| |
| /* call into the plugin if it still exists */ |
| plugin = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); |
| if (plugin != NULL) { |
| if (!fu_plugin_runner_clear_results(plugin, device, error)) |
| return FALSE; |
| } |
| |
| /* if the update never got run, unstage it */ |
| if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_PENDING) |
| fu_device_set_update_state(device, FWUPD_UPDATE_STATE_UNKNOWN); |
| |
| /* override */ |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED); |
| return fu_history_modify_device(self->history, device, error); |
| } |
| |
| /** |
| * fu_engine_get_results: |
| * @self: a #FuEngine |
| * @device_id: a device ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Gets the historical state of a specific device operation. |
| * |
| * Returns: (transfer container): a device, or %NULL |
| **/ |
| FwupdDevice * |
| fu_engine_get_results(FuEngine *self, const gchar *device_id, GError **error) |
| { |
| FwupdRelease *rel; |
| g_autoptr(FuDevice) device = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| g_return_val_if_fail(device_id != NULL, NULL); |
| g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
| |
| /* find the device */ |
| device = fu_engine_get_item_by_id_fallback_history(self, device_id, error); |
| if (device == NULL) |
| return NULL; |
| |
| /* the notification has already been shown to the user */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED)) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOTHING_TO_DO, |
| "User has already been notified about %s", |
| id_display); |
| return NULL; |
| } |
| |
| /* try to set some release properties for the UI */ |
| if (!fu_engine_fixup_history_device(self, device, error)) |
| return NULL; |
| |
| /* we did not either record or find the AppStream ID */ |
| rel = fu_device_get_release_default(device); |
| if (rel == NULL || fwupd_release_get_appstream_id(rel) == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_FOUND, |
| "device %s appstream id was not found", |
| fu_device_get_id(device)); |
| return NULL; |
| } |
| |
| /* success */ |
| return g_object_ref(FWUPD_DEVICE(device)); |
| } |
| |
| static void |
| fu_engine_plugins_startup(FuEngine *self, FuProgress *progress) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, plugins->len); |
| for (guint i = 0; i < plugins->len; i++) { |
| g_autoptr(GError) error = NULL; |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| if (!fu_plugin_runner_startup(plugin, fu_progress_get_child(progress), &error)) { |
| fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); |
| if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) |
| fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_NO_HARDWARE); |
| g_info("disabling plugin because: %s", error->message); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); |
| } |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| static void |
| fu_engine_plugins_ready(FuEngine *self, FuProgress *progress) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, plugins->len); |
| for (guint i = 0; i < plugins->len; i++) { |
| g_autoptr(GError) error = NULL; |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| if (!fu_plugin_runner_ready(plugin, fu_progress_get_child(progress), &error)) { |
| if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) |
| fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_NO_HARDWARE); |
| g_info("disabling plugin because: %s", error->message); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); |
| } |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| static void |
| fu_engine_plugins_coldplug(FuEngine *self, FuProgress *progress) |
| { |
| GPtrArray *plugins; |
| g_autoptr(GString) str = g_string_new(NULL); |
| |
| /* exec */ |
| plugins = fu_plugin_list_get_all(self->plugin_list); |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, plugins->len); |
| for (guint i = 0; i < plugins->len; i++) { |
| g_autoptr(GError) error = NULL; |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| if (!fu_plugin_runner_coldplug(plugin, fu_progress_get_child(progress), &error)) { |
| fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); |
| g_info("disabling plugin because: %s", error->message); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); |
| } |
| fu_progress_step_done(progress); |
| } |
| |
| /* print what we do have */ |
| for (guint i = 0; i < plugins->len; i++) { |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) |
| continue; |
| g_string_append_printf(str, "%s, ", fu_plugin_get_name(plugin)); |
| } |
| if (str->len > 2) { |
| g_string_truncate(str, str->len - 2); |
| g_info("using plugins: %s", str->str); |
| } |
| } |
| |
| static void |
| fu_engine_plugin_device_register(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *backends = fu_context_get_backends(self->ctx); |
| GPtrArray *plugins; |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED)) { |
| g_warning("already registered %s, ignoring", fu_device_get_id(device)); |
| return; |
| } |
| plugins = fu_plugin_list_get_all(self->plugin_list); |
| for (guint i = 0; i < plugins->len; i++) { |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| fu_plugin_runner_device_register(plugin, device); |
| } |
| for (guint i = 0; i < backends->len; i++) { |
| FuBackend *backend = g_ptr_array_index(backends, i); |
| fu_backend_registered(backend, device); |
| } |
| fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED); |
| } |
| |
| static void |
| fu_engine_plugin_device_register_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| fu_engine_plugin_device_register(self, device); |
| } |
| |
| static void |
| fu_engine_plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| |
| /* plugin has prio and device not already set from quirk */ |
| if (fu_plugin_get_priority(plugin) > 0 && fu_device_get_priority(device) == 0) { |
| g_info("auto-setting %s priority to %u", |
| fu_device_get_id(device), |
| fu_plugin_get_priority(plugin)); |
| fu_device_set_priority(device, fu_plugin_get_priority(plugin)); |
| } |
| |
| fu_engine_add_device(self, device); |
| } |
| |
| static void |
| fu_engine_adopt_children_device(FuEngine *self, FuDevice *device, FuDevice *device_tmp) |
| { |
| if (fu_device_has_private_flag_quark(device, quarks[QUARK_HOST_FIRMWARE_CHILD]) && |
| fu_device_has_private_flag_quark(device_tmp, quarks[QUARK_HOST_FIRMWARE])) { |
| fu_device_set_parent(device, device_tmp); |
| fu_engine_ensure_device_supported(self, device_tmp); |
| return; |
| } |
| if (fu_device_has_private_flag_quark(device, quarks[QUARK_HOST_FIRMWARE]) && |
| fu_device_has_private_flag_quark(device_tmp, quarks[QUARK_HOST_FIRMWARE_CHILD])) { |
| fu_device_set_parent(device_tmp, device); |
| fu_engine_ensure_device_supported(self, device_tmp); |
| return; |
| } |
| if (fu_device_has_private_flag_quark(device, quarks[QUARK_HOST_CPU_CHILD]) && |
| fu_device_has_private_flag_quark(device_tmp, quarks[QUARK_HOST_CPU])) { |
| fu_device_set_parent(device, device_tmp); |
| fu_engine_ensure_device_supported(self, device_tmp); |
| return; |
| } |
| if (fu_device_has_private_flag_quark(device, quarks[QUARK_HOST_CPU]) && |
| fu_device_has_private_flag_quark(device_tmp, quarks[QUARK_HOST_CPU_CHILD])) { |
| fu_device_set_parent(device_tmp, device); |
| fu_engine_ensure_device_supported(self, device_tmp); |
| return; |
| } |
| } |
| |
| static void |
| fu_engine_set_device_parent(FuEngine *self, FuDevice *device, FuDevice *parent) |
| { |
| fu_device_set_parent(device, parent); |
| fu_engine_ensure_device_supported(self, device); |
| fu_engine_ensure_device_supported(self, parent); |
| } |
| |
| static void |
| fu_engine_adopt_children(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *guids; |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| |
| /* find the parent in any existing device */ |
| for (guint i = 0; fu_device_get_parent(device) == NULL && i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| fu_engine_adopt_children_device(self, device, device_tmp); |
| } |
| if (fu_device_get_parent(device) == NULL) { |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (!fu_device_has_private_flag_quark(device_tmp, |
| quarks[QUARK_AUTO_PARENT_CHILDREN])) |
| continue; |
| if (fu_device_get_physical_id(device_tmp) == NULL) |
| continue; |
| if (fu_device_has_parent_physical_id( |
| device, |
| fu_device_get_physical_id(device_tmp))) { |
| fu_engine_set_device_parent(self, device, device_tmp); |
| break; |
| } |
| } |
| } |
| if (fu_device_get_parent(device) == NULL) { |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (!fu_device_has_private_flag_quark(device_tmp, |
| quarks[QUARK_AUTO_PARENT_CHILDREN])) |
| continue; |
| if (fu_device_get_backend_id(device_tmp) == NULL) |
| continue; |
| if (fu_device_has_parent_backend_id(device, |
| fu_device_get_backend_id(device_tmp))) { |
| fu_engine_set_device_parent(self, device, device_tmp); |
| break; |
| } |
| } |
| } |
| if (fu_device_get_parent(device) == NULL) { |
| guids = fu_device_get_parent_guids(device); |
| for (guint j = 0; j < guids->len; j++) { |
| const gchar *guid = g_ptr_array_index(guids, j); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (fu_device_has_guid(device_tmp, guid)) { |
| fu_engine_set_device_parent(self, device, device_tmp); |
| break; |
| } |
| } |
| } |
| } |
| |
| /* the new device is the parent to an existing child */ |
| for (guint j = 0; j < devices->len; j++) { |
| GPtrArray *parent_physical_ids = NULL; |
| FuDevice *device_tmp = g_ptr_array_index(devices, j); |
| if (fu_device_get_parent(device_tmp) != NULL) |
| continue; |
| parent_physical_ids = fu_device_get_parent_physical_ids(device_tmp); |
| if (parent_physical_ids == NULL) |
| continue; |
| for (guint i = 0; i < parent_physical_ids->len; i++) { |
| const gchar *parent_physical_id = g_ptr_array_index(parent_physical_ids, i); |
| if (g_strcmp0(parent_physical_id, fu_device_get_physical_id(device)) == 0) |
| fu_engine_set_device_parent(self, device_tmp, device); |
| } |
| } |
| for (guint j = 0; j < devices->len; j++) { |
| GPtrArray *parent_backend_ids = NULL; |
| FuDevice *device_tmp = g_ptr_array_index(devices, j); |
| if (fu_device_get_parent(device_tmp) != NULL) |
| continue; |
| parent_backend_ids = fu_device_get_parent_backend_ids(device_tmp); |
| if (parent_backend_ids == NULL) |
| continue; |
| for (guint i = 0; i < parent_backend_ids->len; i++) { |
| const gchar *parent_backend_id = g_ptr_array_index(parent_backend_ids, i); |
| if (g_strcmp0(parent_backend_id, fu_device_get_backend_id(device)) == 0) |
| fu_engine_set_device_parent(self, device_tmp, device); |
| } |
| } |
| guids = fu_device_get_guids(device); |
| for (guint j = 0; j < guids->len; j++) { |
| const gchar *guid = g_ptr_array_index(guids, j); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (fu_device_get_parent(device_tmp) != NULL) |
| continue; |
| if (fu_device_has_parent_guid(device_tmp, guid)) |
| fu_engine_set_device_parent(self, device_tmp, device); |
| } |
| } |
| } |
| |
| static void |
| fu_engine_set_proxy_device(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *guids; |
| g_autoptr(FuDevice) proxy = NULL; |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| if (fu_device_get_proxy(device) != NULL) |
| return; |
| if (fu_device_get_proxy_guid(device) == NULL) |
| return; |
| |
| /* find the proxy GUID in any existing device */ |
| proxy = |
| fu_device_list_get_by_guid(self->device_list, fu_device_get_proxy_guid(device), NULL); |
| if (proxy != NULL) { |
| g_info("setting proxy of %s to %s for %s", |
| fu_device_get_id(proxy), |
| fu_device_get_id(device), |
| fu_device_get_proxy_guid(device)); |
| fu_device_set_proxy(device, proxy); |
| return; |
| } |
| |
| /* are we the parent of an existing device */ |
| guids = fu_device_get_guids(device); |
| for (guint j = 0; j < guids->len; j++) { |
| const gchar *guid = g_ptr_array_index(guids, j); |
| devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (g_strcmp0(fu_device_get_proxy_guid(device_tmp), guid) == 0) { |
| g_info("adding proxy of %s to %s for %s", |
| fu_device_get_id(device), |
| fu_device_get_id(device_tmp), |
| guid); |
| fu_device_set_proxy(device_tmp, device); |
| return; |
| } |
| } |
| } |
| |
| /* nothing found */ |
| g_warning("did not find proxy device %s", fu_device_get_proxy_guid(device)); |
| } |
| |
| static void |
| fu_engine_device_inherit_history(FuEngine *self, FuDevice *device) |
| { |
| g_autoptr(FuDevice) device_history = NULL; |
| |
| /* ignore */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) |
| return; |
| |
| /* any success or failed update? */ |
| device_history = fu_history_get_device_by_id(self->history, fu_device_get_id(device), NULL); |
| if (device_history == NULL) |
| return; |
| |
| /* in an offline environment we may have used the .cab file to find the version-format |
| * to use for the device -- so when we reboot use the database as the archive data is no |
| * longer available */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERFMT) && |
| fu_device_get_version_format(device_history) != FWUPD_VERSION_FORMAT_UNKNOWN) { |
| g_debug( |
| "absorbing version format %s into %s from history database", |
| fwupd_version_format_to_string(fu_device_get_version_format(device_history)), |
| fu_device_get_id(device)); |
| fu_device_set_version_format(device, fu_device_get_version_format(device_history)); |
| } |
| |
| /* the device is still running the old firmware version and so if it |
| * required activation before, it still requires it now -- note: |
| * we can't just check for version_new=version to allow for re-installs */ |
| if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INHERIT_ACTIVATION) && |
| fu_device_has_flag(device_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { |
| FwupdRelease *release = fu_device_get_release_default(device_history); |
| if (fu_version_compare(fu_device_get_version(device), |
| fwupd_release_get_version(release), |
| fu_device_get_version_format(device)) != 0) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_info("inheriting needs-activation for %s as version %s != %s", |
| id_display, |
| fu_device_get_version(device), |
| fwupd_release_get_version(release)); |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); |
| } |
| } |
| } |
| |
| static void |
| fu_engine_ensure_device_emulation_tag(FuEngine *self, FuDevice *device) |
| { |
| /* already done */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) |
| return; |
| |
| /* we matched this physical ID */ |
| if (fu_device_get_id(device) == NULL) |
| return; |
| if (!fu_history_has_emulation_tag(self->history, fu_device_get_id(device), NULL)) |
| return; |
| |
| /* success */ |
| g_info("adding emulation-tag to %s", fu_device_get_backend_id(device)); |
| fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG); |
| } |
| |
| void |
| fu_engine_add_device(FuEngine *self, FuDevice *device) |
| { |
| GPtrArray *disabled_devices; |
| GPtrArray *device_guids; |
| g_autoptr(XbNode) component = NULL; |
| |
| /* make tests easier */ |
| device_guids = fu_device_get_guids(device); |
| if (device_guids->len == 0) |
| fu_device_convert_instance_ids(device); |
| |
| /* device still has no GUIDs set! */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && device_guids->len == 0 && |
| fu_device_get_children(device)->len == 0) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_warning("no GUIDs for device %s", id_display); |
| return; |
| } |
| |
| /* is this GUID disabled */ |
| disabled_devices = fu_engine_config_get_disabled_devices(self->config); |
| for (guint i = 0; i < disabled_devices->len; i++) { |
| const gchar *disabled_guid = g_ptr_array_index(disabled_devices, i); |
| for (guint j = 0; j < device_guids->len; j++) { |
| const gchar *device_guid = g_ptr_array_index(device_guids, j); |
| if (g_strcmp0(disabled_guid, device_guid) == 0) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_info("%s is disabled [%s], ignoring from %s", |
| id_display, |
| device_guid, |
| fu_device_get_plugin(device)); |
| return; |
| } |
| } |
| } |
| |
| /* does the device not have an assigned protocol */ |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && |
| fu_device_get_protocols(device)->len == 0) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_warning("device %s does not define an update protocol", id_display); |
| } |
| |
| /* if this device is locked get some metadata from AppStream */ |
| component = fu_engine_get_component_by_guids(self, device); |
| if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) { |
| if (component != NULL) { |
| g_autoptr(XbNode) rel = NULL; |
| rel = xb_node_query_first(component, "releases/release", NULL); |
| if (rel != NULL) { |
| g_autoptr(FuRelease) release = fu_release_new(); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL); |
| |
| fu_engine_request_add_flag(request, |
| FU_ENGINE_REQUEST_FLAG_NO_REQUIREMENTS); |
| fu_release_set_request(release, request); |
| fu_release_set_device(release, device); |
| if (!fu_engine_load_release(self, |
| release, |
| NULL, /* cabinet */ |
| component, |
| rel, |
| FWUPD_INSTALL_FLAG_NONE, |
| &error_local)) { |
| g_warning("failed to set AppStream release: %s", |
| error_local->message); |
| } else { |
| fu_device_add_release(device, FWUPD_RELEASE(release)); |
| } |
| } |
| } |
| } |
| |
| /* check if the device needs emulation-tag */ |
| fu_engine_ensure_device_emulation_tag(self, device); |
| |
| /* set or clear the SUPPORTED flag */ |
| fu_engine_ensure_device_supported(self, device); |
| |
| /* adopt any required children, which may or may not already exist */ |
| fu_engine_adopt_children(self, device); |
| |
| /* set the proxy device if specified by GUID */ |
| fu_engine_set_proxy_device(self, device); |
| |
| /* sometimes inherit flags from recent history */ |
| fu_engine_device_inherit_history(self, device); |
| |
| /* notify all plugins about this new device */ |
| if (!fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED)) |
| fu_engine_plugin_device_register(self, device); |
| |
| if (fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN && |
| fu_version_guess_format(fu_device_get_version(device)) == FWUPD_VERSION_FORMAT_NUMBER) { |
| fu_device_inhibit(device, "version-format", "VersionFormat is ambiguous"); |
| } |
| |
| /* no vendor-id, and so no way to lock it down! */ |
| if (fu_device_is_updatable(device) && fu_device_get_vendor_ids(device)->len == 0) |
| fu_device_inhibit(device, "vendor-id", "No vendor ID set"); |
| |
| /* create new device */ |
| fu_device_list_add(self->device_list, device); |
| |
| #ifndef SUPPORTED_BUILD |
| /* we don't know if this device has a signed or unsigned payload */ |
| if (fu_device_is_updatable(device) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD) && |
| !fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_SIGNED)) { |
| g_autofree gchar *id_display = fu_device_get_id_display(device); |
| g_critical("%s does not declare signed/unsigned payload -- perhaps add " |
| "fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);", |
| id_display); |
| } |
| #endif |
| |
| /* clean up any state only valid for ->probe */ |
| fu_device_probe_complete(device); |
| |
| /* fix order */ |
| fu_device_list_depsolve_order(self->device_list, device); |
| |
| /* save to emulated phase, but avoid overwriting reload */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && |
| self->emulator_phase == FU_ENGINE_EMULATOR_PHASE_SETUP && |
| fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG) && |
| !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_engine_emulator_save_phase(self->emulation, |
| self->emulator_phase, |
| self->emulator_write_cnt, |
| &error_local)) |
| g_warning("failed to save phase: %s", error_local->message); |
| } |
| |
| fu_engine_emit_changed(self); |
| } |
| |
| static void |
| fu_engine_plugin_rules_changed_cb(FuPlugin *plugin, gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| GPtrArray *rules = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_INHIBITS_IDLE); |
| if (rules == NULL) |
| return; |
| for (guint j = 0; j < rules->len; j++) { |
| const gchar *tmp = g_ptr_array_index(rules, j); |
| fu_idle_inhibit(self->idle, FU_IDLE_INHIBIT_TIMEOUT, tmp); |
| } |
| } |
| |
| static void |
| fu_engine_context_security_changed_cb(FuContext *ctx, gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| |
| /* invalidate host security attributes */ |
| fu_security_attrs_remove_all(self->host_security_attrs); |
| |
| /* make UI refresh */ |
| fu_engine_emit_changed(self); |
| } |
| |
| static void |
| fu_engine_plugin_device_removed_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) |
| { |
| FuEngine *self = (FuEngine *)user_data; |
| FuPlugin *plugin_old; |
| g_autoptr(GError) error = NULL; |
| |
| /* get the plugin */ |
| plugin_old = |
| fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), &error); |
| if (plugin_old == NULL) { |
| g_info("failed to find plugin %s: %s", |
| fu_device_get_plugin(device), |
| error->message); |
| return; |
| } |
| |
| /* check this came from the same plugin */ |
| if (g_strcmp0(fu_plugin_get_name(plugin), fu_plugin_get_name(plugin_old)) != 0) { |
| g_info("ignoring duplicate removal from %s", fu_plugin_get_name(plugin)); |
| return; |
| } |
| |
| /* make the UI update */ |
| fu_device_list_remove(self->device_list, device); |
| fu_engine_emit_changed(self); |
| } |
| |
| /* this is called by the self tests as well */ |
| void |
| fu_engine_add_plugin(FuEngine *self, FuPlugin *plugin) |
| { |
| fu_plugin_list_add(self->plugin_list, plugin); |
| } |
| |
| gboolean |
| fu_engine_is_uid_trusted(FuEngine *self, guint64 calling_uid) |
| { |
| GArray *trusted; |
| |
| /* root is always trusted */ |
| if (calling_uid == 0) |
| return TRUE; |
| |
| trusted = fu_engine_config_get_trusted_uids(self->config); |
| for (guint i = 0; i < trusted->len; i++) { |
| if (calling_uid == g_array_index(trusted, guint64, i)) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| gboolean |
| fu_engine_plugin_allows_enumeration(FuEngine *self, FuPlugin *plugin) |
| { |
| if (!fu_engine_config_get_require_immutable_enumeration(self->config)) |
| return TRUE; |
| return !fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MUTABLE_ENUMERATION); |
| } |
| |
| static gboolean |
| fu_engine_is_test_plugin_disabled(FuEngine *self, FuPlugin *plugin) |
| { |
| if (!fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_TEST_ONLY)) |
| return FALSE; |
| if (fu_engine_config_get_test_devices(self->config)) |
| return FALSE; |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_is_plugin_name_disabled(FuEngine *self, const gchar *name) |
| { |
| GPtrArray *disabled = fu_engine_config_get_disabled_plugins(self->config); |
| for (guint i = 0; i < disabled->len; i++) { |
| const gchar *name_tmp = g_ptr_array_index(disabled, i); |
| if (g_strcmp0(name_tmp, name) == 0) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| fu_engine_is_plugin_name_enabled(FuEngine *self, const gchar *name) |
| { |
| if (self->plugin_filter->len == 0) |
| return TRUE; |
| for (guint i = 0; i < self->plugin_filter->len; i++) { |
| const gchar *name_tmp = g_ptr_array_index(self->plugin_filter, i); |
| if (g_pattern_match_simple(name_tmp, name)) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| void |
| fu_engine_add_plugin_filter(FuEngine *self, const gchar *plugin_glob) |
| { |
| GString *str; |
| g_return_if_fail(FU_IS_ENGINE(self)); |
| g_return_if_fail(plugin_glob != NULL); |
| str = g_string_new(plugin_glob); |
| g_string_replace(str, "-", "_", 0); |
| g_ptr_array_add(self->plugin_filter, g_string_free(str, FALSE)); |
| } |
| |
| static gboolean |
| fu_engine_plugin_check_supported_cb(FuPlugin *plugin, const gchar *guid, FuEngine *self) |
| { |
| g_autoptr(XbNode) n = NULL; |
| g_autofree gchar *xpath = NULL; |
| |
| if (fu_engine_config_get_enumerate_all_devices(self->config)) |
| return TRUE; |
| |
| xpath = g_strdup_printf("components/component[@type='firmware']/" |
| "provides/firmware[@type='flashed'][text()='%s']", |
| guid); |
| n = xb_silo_query_first(self->silo, xpath, NULL); |
| return n != NULL; |
| } |
| |
| FuEngineConfig * |
| fu_engine_get_config(FuEngine *self) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| return self->config; |
| } |
| |
| const gchar * |
| fu_engine_get_host_vendor(FuEngine *self) |
| { |
| const gchar *result = NULL; |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| result = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_MANUFACTURER); |
| return result != NULL ? result : "Unknown Vendor"; |
| } |
| |
| const gchar * |
| fu_engine_get_host_product(FuEngine *self) |
| { |
| const gchar *result = NULL; |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| result = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_PRODUCT_NAME); |
| return result != NULL ? result : "Unknown Product"; |
| } |
| |
| const gchar * |
| fu_engine_get_host_machine_id(FuEngine *self) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| return self->host_machine_id; |
| } |
| |
| const gchar * |
| fu_engine_get_host_bkc(FuEngine *self) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| if (fu_engine_config_get_host_bkc(self->config) == NULL) |
| return ""; |
| return fu_engine_config_get_host_bkc(self->config); |
| } |
| |
| #ifdef HAVE_HSI |
| static void |
| fu_engine_ensure_security_attrs_supported_cpu(FuEngine *self) |
| { |
| g_autoptr(FwupdSecurityAttr) attr = |
| fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU); |
| fwupd_security_attr_set_plugin(attr, "core"); |
| |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); |
| fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); |
| fu_security_attrs_append(self->host_security_attrs, attr); |
| } |
| |
| static void |
| fu_engine_ensure_security_attrs_tainted(FuEngine *self) |
| { |
| gboolean disabled_plugins = FALSE; |
| GPtrArray *disabled = fu_engine_config_get_disabled_plugins(self->config); |
| g_autoptr(FwupdSecurityAttr) attr = |
| fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS); |
| fwupd_security_attr_set_plugin(attr, "core"); |
| fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED); |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); |
| |
| fu_security_attrs_append(self->host_security_attrs, attr); |
| for (guint i = 0; i < disabled->len; i++) { |
| const gchar *name_tmp = g_ptr_array_index(disabled, i); |
| if (!g_str_has_prefix(name_tmp, "test")) { |
| disabled_plugins = TRUE; |
| break; |
| } |
| } |
| if (self->plugin_filter->len > 0 || disabled_plugins) { |
| fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_TAINTED); |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); |
| return; |
| } |
| |
| /* success */ |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); |
| } |
| |
| /* private */ |
| gchar * |
| fu_engine_get_host_security_id(FuEngine *self, const gchar *fwupd_version) |
| { |
| FuSmbiosChassisKind chassis_kind; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| |
| fu_engine_ensure_security_attrs(self); |
| |
| /* if emulating, force the chassis type to be valid */ |
| chassis_kind = fu_context_get_chassis_kind(self->ctx); |
| if (self->host_emulation && (chassis_kind == FU_SMBIOS_CHASSIS_KIND_OTHER || |
| chassis_kind == FU_SMBIOS_CHASSIS_KIND_UNKNOWN)) { |
| g_info("forcing chassis kind %s to be valid", |
| fu_smbios_chassis_kind_to_string(chassis_kind)); |
| chassis_kind = FU_SMBIOS_CHASSIS_KIND_DESKTOP; |
| } |
| |
| switch (chassis_kind) { |
| case FU_SMBIOS_CHASSIS_KIND_DESKTOP: |
| case FU_SMBIOS_CHASSIS_KIND_LOW_PROFILE_DESKTOP: |
| case FU_SMBIOS_CHASSIS_KIND_MINI_TOWER: |
| case FU_SMBIOS_CHASSIS_KIND_TOWER: |
| case FU_SMBIOS_CHASSIS_KIND_PORTABLE: |
| case FU_SMBIOS_CHASSIS_KIND_LAPTOP: |
| case FU_SMBIOS_CHASSIS_KIND_NOTEBOOK: |
| case FU_SMBIOS_CHASSIS_KIND_ALL_IN_ONE: |
| case FU_SMBIOS_CHASSIS_KIND_SUB_NOTEBOOK: |
| case FU_SMBIOS_CHASSIS_KIND_LUNCH_BOX: |
| case FU_SMBIOS_CHASSIS_KIND_MAIN_SERVER: |
| case FU_SMBIOS_CHASSIS_KIND_TABLET: |
| case FU_SMBIOS_CHASSIS_KIND_CONVERTIBLE: |
| case FU_SMBIOS_CHASSIS_KIND_DETACHABLE: |
| case FU_SMBIOS_CHASSIS_KIND_IOT_GATEWAY: |
| case FU_SMBIOS_CHASSIS_KIND_EMBEDDED_PC: |
| case FU_SMBIOS_CHASSIS_KIND_MINI_PC: |
| case FU_SMBIOS_CHASSIS_KIND_STICK_PC: |
| return fu_security_attrs_calculate_hsi(self->host_security_attrs, |
| fwupd_version, |
| FU_SECURITY_ATTRS_FLAG_ADD_VERSION); |
| default: |
| break; |
| } |
| |
| return g_strdup_printf("HSI:INVALID:chassis[%s] (v%d.%d.%d)", |
| fu_smbios_chassis_kind_to_string(chassis_kind), |
| FWUPD_MAJOR_VERSION, |
| FWUPD_MINOR_VERSION, |
| FWUPD_MICRO_VERSION); |
| } |
| |
| static gboolean |
| fu_engine_record_security_attrs(FuEngine *self, GError **error) |
| { |
| g_autoptr(GPtrArray) attrs_array = NULL; |
| g_autofree gchar *host_security_id = fu_engine_get_host_security_id(self, NULL); |
| g_autofree gchar *json = NULL; |
| |
| /* convert attrs to json string */ |
| json = fwupd_codec_to_json_string(FWUPD_CODEC(self->host_security_attrs), |
| FWUPD_CODEC_FLAG_NONE, |
| error); |
| if (json == NULL) { |
| g_prefix_error_literal(error, "cannot convert current attrs to string: "); |
| return FALSE; |
| } |
| |
| /* check that we did not store this already last boot */ |
| attrs_array = fu_history_get_security_attrs(self->history, 1, error); |
| if (attrs_array == NULL) { |
| g_prefix_error_literal(error, "failed to get historical attr: "); |
| return FALSE; |
| } |
| if (attrs_array->len > 0) { |
| FuSecurityAttrs *attrs_tmp = g_ptr_array_index(attrs_array, 0); |
| if (fu_security_attrs_equal(attrs_tmp, self->host_security_attrs)) { |
| g_info("skipping writing HSI attrs to database as unchanged"); |
| return TRUE; |
| } |
| } |
| |
| /* write new values */ |
| if (!fu_history_add_security_attribute(self->history, json, host_security_id, error)) { |
| g_prefix_error_literal(error, "failed to write to DB: "); |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_security_attrs_depsolve(FuEngine *self) |
| { |
| g_autoptr(GPtrArray) items = NULL; |
| |
| /* set the obsoletes flag for each attr */ |
| fu_security_attrs_depsolve(self->host_security_attrs); |
| |
| /* set the fallback names for clients without native translations */ |
| items = fu_security_attrs_get_all(self->host_security_attrs, NULL); |
| for (guint i = 0; i < items->len; i++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(items, i); |
| if (fwupd_security_attr_get_name(attr) == NULL) { |
| g_autofree gchar *name_tmp = fu_security_attr_get_name(attr); |
| if (name_tmp == NULL) { |
| g_warning("failed to get fallback for %s", |
| fwupd_security_attr_get_appstream_id(attr)); |
| continue; |
| } |
| fwupd_security_attr_set_name(attr, name_tmp); |
| } |
| if (fwupd_security_attr_get_title(attr) == NULL) |
| fwupd_security_attr_set_title(attr, fu_security_attr_get_title(attr)); |
| if (fwupd_security_attr_get_description(attr) == NULL) { |
| fwupd_security_attr_set_description(attr, |
| fu_security_attr_get_description(attr)); |
| } |
| } |
| } |
| #endif |
| |
| /** |
| * fu_history_get_previous_security_attr: |
| * @self: a #FuHistory |
| * @appstream_id: maximum number of attributes to return, or 0 for no limit |
| * @current_setting: (nullable): current value |
| * @error: return location for a #GError, or %NULL |
| * |
| * Gets the security attributes of the previous BIOS setting for the given |
| * appstream ID and current BIOS config. |
| * |
| * Returns: (element-type #FuSecurityAttr) (transfer full): attr, or %NULL |
| **/ |
| static FwupdSecurityAttr * |
| fu_engine_get_previous_bios_security_attr(FuEngine *self, |
| const gchar *appstream_id, |
| const gchar *current_setting, |
| GError **error) |
| { |
| g_autoptr(GPtrArray) attrs_array = NULL; |
| |
| attrs_array = fu_history_get_security_attrs(self->history, 20, error); |
| if (attrs_array == NULL) |
| return NULL; |
| for (guint i = 0; i < attrs_array->len; i++) { |
| FuSecurityAttrs *attrs = g_ptr_array_index(attrs_array, i); |
| g_autoptr(GPtrArray) attr_items = fu_security_attrs_get_all(attrs, NULL); |
| for (guint j = 0; j < attr_items->len; j++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(attr_items, j); |
| if (g_strcmp0(appstream_id, fwupd_security_attr_get_appstream_id(attr)) == |
| 0 && |
| g_strcmp0(current_setting, |
| fwupd_security_attr_get_bios_setting_current_value(attr)) != |
| 0) { |
| g_debug("found previous BIOS setting for %s: %s", |
| appstream_id, |
| fwupd_security_attr_get_bios_setting_id(attr)); |
| return g_object_ref(attr); |
| } |
| } |
| } |
| |
| /* failed */ |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "cannot find previous BIOS value"); |
| return NULL; |
| } |
| |
| /** |
| * fu_engine_fix_host_security_attr: |
| * @self: a #FuEngine |
| * @appstream_id: the Appstream ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Fix one specific security attribute. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_fix_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error) |
| { |
| FuPlugin *plugin; |
| FwupdBiosSetting *bios_attr; |
| g_autoptr(FwupdSecurityAttr) hsi_attr = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| |
| fu_engine_ensure_security_attrs(self); |
| hsi_attr = |
| fu_security_attrs_get_by_appstream_id(self->host_security_attrs, appstream_id, error); |
| if (hsi_attr == NULL) |
| return FALSE; |
| if (!fwupd_security_attr_has_flag(hsi_attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "cannot auto-fix attribute"); |
| return FALSE; |
| } |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, |
| fwupd_security_attr_get_plugin(hsi_attr), |
| error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* first try the per-plugin vfunc */ |
| if (!fu_plugin_runner_fix_host_security_attr(plugin, hsi_attr, &error_local)) { |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| g_debug("ignoring %s", error_local->message); |
| } else { |
| g_info("fixed %s", fwupd_security_attr_get_appstream_id(hsi_attr)); |
| return TRUE; |
| } |
| |
| /* fall back to setting the BIOS attribute */ |
| if (fwupd_security_attr_get_bios_setting_id(hsi_attr) == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no BIOS setting ID set"); |
| return FALSE; |
| } |
| bios_attr = fu_context_get_bios_setting(self->ctx, |
| fwupd_security_attr_get_bios_setting_id(hsi_attr)); |
| if (bios_attr == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "cannot get BIOS setting %s", |
| fwupd_security_attr_get_bios_setting_id(hsi_attr)); |
| return FALSE; |
| } |
| return fwupd_bios_setting_write_value( |
| bios_attr, |
| fwupd_security_attr_get_bios_setting_target_value(hsi_attr), |
| error); |
| } |
| |
| /** |
| * fu_engine_fix_host_security_attr: |
| * @self: a #FuEngine |
| * @appstream_id: the Appstream ID |
| * @error: (nullable): optional return location for an error |
| * |
| * Revert the fix for one specific security attribute. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_undo_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error) |
| { |
| FuPlugin *plugin; |
| FwupdBiosSetting *bios_attr; |
| g_autoptr(FwupdSecurityAttr) hsi_attr = NULL; |
| g_autoptr(FwupdSecurityAttr) hsi_attr_old = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| |
| fu_engine_ensure_security_attrs(self); |
| hsi_attr = |
| fu_security_attrs_get_by_appstream_id(self->host_security_attrs, appstream_id, error); |
| if (hsi_attr == NULL) |
| return FALSE; |
| if (!fwupd_security_attr_has_flag(hsi_attr, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "cannot auto-undo attribute"); |
| return FALSE; |
| } |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, |
| fwupd_security_attr_get_plugin(hsi_attr), |
| error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* first try the per-plugin vfunc */ |
| if (!fu_plugin_runner_undo_host_security_attr(plugin, hsi_attr, &error_local)) { |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_propagate_error(error, g_steal_pointer(&error_local)); |
| return FALSE; |
| } |
| } |
| |
| /* fall back to setting the BIOS attribute */ |
| if (fwupd_security_attr_get_bios_setting_id(hsi_attr) == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no BIOS setting ID"); |
| return FALSE; |
| } |
| bios_attr = fu_context_get_bios_setting(self->ctx, |
| fwupd_security_attr_get_bios_setting_id(hsi_attr)); |
| if (bios_attr == NULL) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "cannot get BIOS setting %s", |
| fwupd_security_attr_get_bios_setting_id(hsi_attr)); |
| return FALSE; |
| } |
| if (fwupd_security_attr_get_bios_setting_current_value(hsi_attr) == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "no BIOS setting current value"); |
| return FALSE; |
| } |
| hsi_attr_old = fu_engine_get_previous_bios_security_attr( |
| self, |
| appstream_id, |
| fwupd_security_attr_get_bios_setting_current_value(hsi_attr), |
| error); |
| if (hsi_attr_old == NULL) |
| return FALSE; |
| return fwupd_bios_setting_write_value( |
| bios_attr, |
| fwupd_security_attr_get_bios_setting_current_value(hsi_attr_old), |
| error); |
| } |
| |
| static gboolean |
| fu_engine_security_attrs_from_json(FuEngine *self, JsonNode *json_node, GError **error) |
| { |
| JsonObject *obj; |
| |
| /* sanity check */ |
| if (!JSON_NODE_HOLDS_OBJECT(json_node)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_DATA, |
| "not JSON object"); |
| return FALSE; |
| } |
| |
| /* not supplied */ |
| obj = json_node_get_object(json_node); |
| if (!json_object_has_member(obj, "SecurityAttributes")) |
| return TRUE; |
| if (!fwupd_codec_from_json(FWUPD_CODEC(self->host_security_attrs), json_node, error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_devices_from_json(FuEngine *self, JsonNode *json_node, GError **error) |
| { |
| JsonArray *array; |
| JsonObject *obj; |
| |
| /* sanity check */ |
| if (!JSON_NODE_HOLDS_OBJECT(json_node)) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INVALID_DATA, |
| "not JSON object"); |
| return FALSE; |
| } |
| |
| /* not supplied */ |
| obj = json_node_get_object(json_node); |
| if (!json_object_has_member(obj, "Devices")) |
| return TRUE; |
| |
| /* this has to exist */ |
| array = json_object_get_array_member(obj, "Devices"); |
| for (guint i = 0; i < json_array_get_length(array); i++) { |
| JsonNode *node_tmp = json_array_get_element(array, i); |
| g_autoptr(FuDevice) device = fu_device_new(self->ctx); |
| if (!fwupd_codec_from_json(FWUPD_CODEC(device), node_tmp, error)) |
| return FALSE; |
| fu_device_set_plugin(device, "dummy"); |
| fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_IS_EMULATED); |
| if (!fu_device_setup(device, error)) |
| return FALSE; |
| fu_engine_add_device(self, device); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_load_host_emulation(FuEngine *self, const gchar *fn, GError **error) |
| { |
| g_autoptr(JsonParser) parser = json_parser_new(); |
| g_autoptr(GFile) file = g_file_new_for_path(fn); |
| g_autoptr(GInputStream) istream_json = NULL; |
| g_autoptr(GInputStream) istream_raw = NULL; |
| g_autoptr(FwupdSecurityAttr) attr = NULL; |
| g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); |
| |
| /* add an attr so we know this is emulated and do not offer to upload results */ |
| attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_HOST_EMULATION); |
| fwupd_security_attr_set_plugin(attr, "core"); |
| fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); |
| fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); |
| fu_security_attrs_append(self->host_security_attrs, attr); |
| |
| /* add from file */ |
| istream_raw = G_INPUT_STREAM(g_file_read(file, NULL, error)); |
| if (istream_raw == NULL) |
| return FALSE; |
| if (g_str_has_suffix(fn, ".gz")) { |
| g_autoptr(GConverter) conv = |
| G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP)); |
| istream_json = g_converter_input_stream_new(istream_raw, conv); |
| } else { |
| istream_json = g_object_ref(istream_raw); |
| } |
| if (!json_parser_load_from_stream(parser, istream_json, NULL, error)) |
| return FALSE; |
| if (!fu_engine_devices_from_json(self, json_parser_get_root(parser), error)) |
| return FALSE; |
| if (!fu_engine_security_attrs_from_json(self, json_parser_get_root(parser), error)) |
| return FALSE; |
| if (!fwupd_codec_from_json(FWUPD_CODEC(bios_settings), json_parser_get_root(parser), error)) |
| return FALSE; |
| |
| #ifdef HAVE_HSI |
| /* depsolve */ |
| fu_engine_security_attrs_depsolve(self); |
| #endif |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_ensure_security_attrs(FuEngine *self) |
| { |
| #ifdef HAVE_HSI |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| g_autoptr(GPtrArray) vals = NULL; |
| g_autoptr(GError) error = NULL; |
| |
| /* already valid */ |
| if (fu_security_attrs_is_valid(self->host_security_attrs) || self->host_emulation) |
| return; |
| |
| /* built in */ |
| fu_engine_ensure_security_attrs_supported_cpu(self); |
| fu_engine_ensure_security_attrs_tainted(self); |
| |
| /* call into devices */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| fu_device_add_security_attrs(device, self->host_security_attrs); |
| } |
| |
| /* call into plugins */ |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| fu_plugin_runner_add_security_attrs(plugin_tmp, self->host_security_attrs); |
| } |
| |
| /* sanity check */ |
| vals = fu_security_attrs_get_all(self->host_security_attrs, NULL); |
| for (guint i = 0; i < vals->len; i++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(vals, i); |
| if (fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { |
| #ifdef SUPPORTED_BUILD |
| g_debug("HSI attribute %s (from %s) had unknown result", |
| fwupd_security_attr_get_appstream_id(attr), |
| fwupd_security_attr_get_plugin(attr)); |
| #else |
| g_warning("HSI attribute %s (from %s) had unknown result", |
| fwupd_security_attr_get_appstream_id(attr), |
| fwupd_security_attr_get_plugin(attr)); |
| #endif |
| } |
| } |
| |
| /* depsolve */ |
| fu_engine_security_attrs_depsolve(self); |
| |
| /* record into the database (best effort) */ |
| if (!fu_engine_record_security_attrs(self, &error)) |
| g_warning("failed to record HSI attributes: %s", error->message); |
| #endif |
| } |
| |
| FuSecurityAttrs * |
| fu_engine_get_host_security_attrs(FuEngine *self) |
| { |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| fu_engine_ensure_security_attrs(self); |
| return g_object_ref(self->host_security_attrs); |
| } |
| |
| FuSecurityAttrs * |
| fu_engine_get_host_security_events(FuEngine *self, guint limit, GError **error) |
| { |
| g_autoptr(FuSecurityAttrs) events = fu_security_attrs_new(); |
| g_autoptr(GPtrArray) attrs_array = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), NULL); |
| |
| attrs_array = fu_history_get_security_attrs(self->history, limit, error); |
| if (attrs_array == NULL) |
| return NULL; |
| for (guint i = 1; i < attrs_array->len; i++) { |
| FuSecurityAttrs *attrs_new = g_ptr_array_index(attrs_array, i - 1); |
| FuSecurityAttrs *attrs_old = g_ptr_array_index(attrs_array, i - 0); |
| g_autoptr(GPtrArray) diffs = fu_security_attrs_compare(attrs_old, attrs_new); |
| for (guint j = 0; j < diffs->len; j++) { |
| FwupdSecurityAttr *attr = g_ptr_array_index(diffs, j); |
| if (fwupd_security_attr_get_title(attr) == NULL) { |
| fwupd_security_attr_set_title(attr, |
| fu_security_attr_get_title(attr)); |
| } |
| if (fwupd_security_attr_get_description(attr) == NULL) { |
| fwupd_security_attr_set_description( |
| attr, |
| fu_security_attr_get_description(attr)); |
| } |
| fu_security_attrs_append_internal(events, attr); |
| } |
| } |
| |
| /* success */ |
| return g_steal_pointer(&events); |
| } |
| |
| static void |
| fu_engine_load_plugins_filename(FuEngine *self, const gchar *filename, FuProgress *progress) |
| { |
| g_autofree gchar *name = NULL; |
| g_autoptr(FuPlugin) plugin = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_name(progress, filename); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 97, "add"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 3, "open"); |
| |
| /* sanity check */ |
| name = fu_plugin_guess_name_from_fn(filename); |
| if (name == NULL) { |
| fu_progress_finished(progress); |
| return; |
| } |
| |
| /* open module */ |
| plugin = fu_plugin_new(self->ctx); |
| fu_plugin_set_name(plugin, name); |
| fu_engine_add_plugin(self, plugin); |
| fu_progress_step_done(progress); |
| |
| /* open the plugin and call ->load() */ |
| if (!fu_plugin_open(plugin, filename, &error_local)) |
| g_warning("cannot load: %s", error_local->message); |
| fu_progress_step_done(progress); |
| } |
| |
| static void |
| fu_engine_load_plugins_filenames(FuEngine *self, GPtrArray *filenames, FuProgress *progress) |
| { |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, filenames->len); |
| for (guint i = 0; i < filenames->len; i++) { |
| const gchar *filename = g_ptr_array_index(filenames, i); |
| fu_engine_load_plugins_filename(self, filename, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| static void |
| fu_engine_load_plugins_builtins(FuEngine *self, FuProgress *progress) |
| { |
| guint steps = 0; |
| |
| /* count possible steps */ |
| for (guint i = 0; fu_plugin_externals[i] != NULL; i++) |
| steps++; |
| if (steps == 0) |
| return; |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, steps); |
| for (guint i = 0; fu_plugin_externals[i] != NULL; i++) { |
| GType plugin_gtype = fu_plugin_externals[i](); |
| g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(plugin_gtype, self->ctx); |
| fu_progress_set_name(fu_progress_get_child(progress), fu_plugin_get_name(plugin)); |
| fu_engine_add_plugin(self, plugin); |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| static gboolean |
| fu_engine_load_plugins(FuEngine *self, |
| FuEngineLoadFlags flags, |
| FuProgress *progress, |
| GError **error) |
| { |
| g_autofree gchar *plugin_path = NULL; |
| g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func(g_free); |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 13, "search"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 87, "load"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "load-builtins"); |
| |
| /* search */ |
| plugin_path = fu_path_from_kind(FU_PATH_KIND_LIBDIR_PKG); |
| if (flags & FU_ENGINE_LOAD_FLAG_EXTERNAL_PLUGINS) { |
| g_auto(GStrv) plugin_paths = g_strsplit(plugin_path, ",", 0); |
| for (guint i = 0; plugin_paths[i] != NULL; i++) { |
| g_autoptr(GPtrArray) filenames_tmp = NULL; |
| g_autoptr(GError) error_local = NULL; |
| filenames_tmp = fu_path_get_files(plugin_paths[i], &error_local); |
| if (filenames_tmp == NULL) { |
| g_debug("no external plugins found in %s: %s", |
| plugin_paths[i], |
| error_local->message); |
| continue; |
| } |
| for (guint j = 0; j < filenames_tmp->len; j++) { |
| const gchar *filename = g_ptr_array_index(filenames_tmp, j); |
| if (!g_str_has_suffix(filename, ".so")) |
| continue; |
| g_ptr_array_add(filenames, g_strdup(filename)); |
| } |
| } |
| } |
| fu_progress_step_done(progress); |
| |
| /* load */ |
| if (filenames != NULL) |
| fu_engine_load_plugins_filenames(self, filenames, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| |
| /* load builtins */ |
| if (flags & FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS) |
| fu_engine_load_plugins_builtins(self, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_plugins_init(FuEngine *self, FuProgress *progress, GError **error) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| g_autoptr(GPtrArray) plugins_disabled = g_ptr_array_new_with_free_func(g_free); |
| g_autoptr(GPtrArray) plugins_disabled_rt = g_ptr_array_new_with_free_func(g_free); |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, plugins->len); |
| for (guint i = 0; i < plugins->len; i++) { |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| const gchar *name = fu_plugin_get_name(plugin); |
| |
| /* progress */ |
| fu_progress_set_name(fu_progress_get_child(progress), name); |
| |
| /* is disabled */ |
| if (fu_engine_is_plugin_name_disabled(self, name) || |
| fu_engine_is_test_plugin_disabled(self, plugin) || |
| !fu_engine_is_plugin_name_enabled(self, name) || |
| !fu_engine_plugin_allows_enumeration(self, plugin)) { |
| g_ptr_array_add(plugins_disabled, g_strdup(name)); |
| fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); |
| fu_progress_step_done(progress); |
| continue; |
| } |
| |
| /* init plugin, adding device and firmware GTypes */ |
| fu_plugin_runner_init(plugin); |
| |
| /* runtime disabled */ |
| if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) { |
| g_ptr_array_add(plugins_disabled_rt, g_strdup(name)); |
| fu_progress_step_done(progress); |
| continue; |
| } |
| |
| /* watch for changes */ |
| g_signal_connect(FU_PLUGIN(plugin), |
| "device-added", |
| G_CALLBACK(fu_engine_plugin_device_added_cb), |
| self); |
| g_signal_connect(FU_PLUGIN(plugin), |
| "device-removed", |
| G_CALLBACK(fu_engine_plugin_device_removed_cb), |
| self); |
| g_signal_connect(FU_PLUGIN(plugin), |
| "device-register", |
| G_CALLBACK(fu_engine_plugin_device_register_cb), |
| self); |
| g_signal_connect(FU_PLUGIN(plugin), |
| "check-supported", |
| G_CALLBACK(fu_engine_plugin_check_supported_cb), |
| self); |
| g_signal_connect(FU_PLUGIN(plugin), |
| "rules-changed", |
| G_CALLBACK(fu_engine_plugin_rules_changed_cb), |
| self); |
| fu_progress_step_done(progress); |
| } |
| |
| /* show list */ |
| if (plugins_disabled->len > 0) { |
| g_autofree gchar *str = NULL; |
| g_ptr_array_add(plugins_disabled, NULL); |
| str = g_strjoinv(", ", (gchar **)plugins_disabled->pdata); |
| g_info("plugins disabled: %s", str); |
| } |
| if (plugins_disabled_rt->len > 0) { |
| g_autofree gchar *str = NULL; |
| g_ptr_array_add(plugins_disabled_rt, NULL); |
| str = g_strjoinv(", ", (gchar **)plugins_disabled_rt->pdata); |
| g_info("plugins runtime-disabled: %s", str); |
| } |
| |
| /* depsolve into the correct order */ |
| if (!fu_plugin_list_depsolve(self->plugin_list, error)) |
| return FALSE; |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_cleanup_state(GError **error) |
| { |
| const gchar *filenames[] = {"/var/cache/app-info/xmls/fwupd-verify.xml", |
| "/var/cache/app-info/xmls/fwupd.xml", |
| NULL}; |
| for (guint i = 0; filenames[i] != NULL; i++) { |
| g_autoptr(GFile) file = g_file_new_for_path(filenames[i]); |
| if (g_file_query_exists(file, NULL)) { |
| if (!g_file_delete(file, NULL, error)) |
| return FALSE; |
| } |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_apply_default_bios_settings_policy(FuEngine *self, GError **error) |
| { |
| const gchar *tmp; |
| g_autofree gchar *base = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); |
| g_autofree gchar *dirname = g_build_filename(base, "bios-settings.d", NULL); |
| g_autoptr(FuBiosSettings) new_bios_settings = fu_bios_settings_new(); |
| g_autoptr(GHashTable) hashtable = NULL; |
| g_autoptr(GDir) dir = NULL; |
| |
| if (!g_file_test(dirname, G_FILE_TEST_EXISTS)) |
| return TRUE; |
| |
| dir = g_dir_open(dirname, 0, error); |
| if (dir == NULL) |
| return FALSE; |
| while ((tmp = g_dir_read_name(dir)) != NULL) { |
| g_autofree gchar *data = NULL; |
| g_autofree gchar *fn = NULL; |
| if (!g_str_has_suffix(tmp, ".json")) |
| continue; |
| fn = g_build_filename(dirname, tmp, NULL); |
| g_info("loading default BIOS settings policy from %s", fn); |
| if (!g_file_get_contents(fn, &data, NULL, error)) |
| return FALSE; |
| if (!fwupd_codec_from_json_string(FWUPD_CODEC(new_bios_settings), data, error)) |
| return FALSE; |
| } |
| hashtable = fu_bios_settings_to_hash_kv(new_bios_settings); |
| return fu_engine_modify_bios_settings(self, hashtable, TRUE, error); |
| } |
| |
| static void |
| fu_engine_check_firmware_attributes(FuEngine *self, FuDevice *device, gboolean added) |
| { |
| const gchar *subsystem; |
| |
| if (!FU_IS_UDEV_DEVICE(device)) |
| return; |
| if (self->host_emulation) |
| return; |
| subsystem = fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)); |
| if (g_strcmp0(subsystem, "firmware-attributes") == 0) { |
| g_autoptr(GError) error = NULL; |
| if (added) { |
| g_autoptr(FuBiosSettings) settings = |
| fu_context_get_bios_settings(self->ctx); |
| g_autoptr(GPtrArray) items = fu_bios_settings_get_all(settings); |
| |
| if (items->len > 0) { |
| g_debug("ignoring add event for already loaded settings"); |
| return; |
| } |
| } |
| if (!fu_context_reload_bios_settings(self->ctx, &error)) { |
| g_debug("%s", error->message); |
| return; |
| } |
| if (!fu_engine_apply_default_bios_settings_policy(self, &error)) { |
| if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) |
| g_debug("%s", error->message); |
| else |
| g_warning("failed to apply BIOS settings policy: %s", |
| error->message); |
| return; |
| } |
| } |
| } |
| |
| static void |
| fu_engine_backend_device_removed_cb(FuBackend *backend, FuDevice *device, FuEngine *self) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* if this is for firmware attributes, reload that part of the daemon */ |
| fu_engine_check_firmware_attributes(self, device, FALSE); |
| |
| /* debug */ |
| g_debug("%s removed %s", fu_backend_get_name(backend), fu_device_get_backend_id(device)); |
| |
| /* go through each device and remove any that match */ |
| devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (g_strcmp0(fu_device_get_backend_id(device_tmp), |
| fu_device_get_backend_id(device)) == 0) { |
| FuPlugin *plugin; |
| g_autofree gchar *id_display = fu_device_get_id_display(device_tmp); |
| if (fu_device_has_private_flag(device_tmp, |
| FU_DEVICE_PRIVATE_FLAG_NO_AUTO_REMOVE)) { |
| g_info("not auto-removing backend device %s due to flags", |
| id_display); |
| continue; |
| } |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, |
| fu_device_get_plugin(device_tmp), |
| NULL); |
| if (plugin == NULL) |
| continue; |
| g_info("auto-removing backend device %s", id_display); |
| fu_plugin_device_remove(plugin, device_tmp); |
| } |
| } |
| } |
| |
| static gboolean |
| fu_engine_backend_device_added_run_plugin(FuEngine *self, |
| FuDevice *device, |
| const gchar *plugin_name, |
| FuProgress *progress, |
| GError **error) |
| { |
| FuPlugin *plugin; |
| |
| /* find plugin */ |
| fu_progress_set_name(progress, plugin_name); |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, plugin_name, error); |
| if (plugin == NULL) |
| return FALSE; |
| |
| /* run the ->probe() then ->setup() vfuncs */ |
| if (!fu_plugin_runner_backend_device_added(plugin, device, progress, error)) { |
| #ifdef SUPPORTED_BUILD |
| /* sanity check */ |
| if (*error == NULL) { /* nocheck:error */ |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "%s failed but no error set", |
| fu_device_get_backend_id(device)); |
| return FALSE; |
| } |
| #endif |
| return FALSE; |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_backend_device_added_run_plugins(FuEngine *self, FuDevice *device, FuProgress *progress) |
| { |
| g_autoptr(GPtrArray) possible_plugins = fu_device_get_possible_plugins(device); |
| |
| /* useful for fwupdtool get-devices --show-all --force */ |
| if ((self->load_flags & FU_ENGINE_LOAD_FLAG_COLDPLUG_FORCE) > 0 && |
| possible_plugins->len == 0) { |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(FuDeviceLocker) locker = NULL; |
| locker = fu_device_locker_new(device, &error_local); |
| if (locker == NULL) { |
| g_debug("ignoring: %s", error_local->message); |
| return; |
| } |
| if (fu_device_get_instance_ids(device)->len > 0) { |
| fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_CAN_EMULATION_TAG); |
| fu_engine_add_device(self, device); |
| } |
| return; |
| } |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, possible_plugins->len); |
| for (guint i = 0; i < possible_plugins->len; i++) { |
| const gchar *plugin_name = g_ptr_array_index(possible_plugins, i); |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_engine_backend_device_added_run_plugin(self, |
| device, |
| plugin_name, |
| fu_progress_get_child(progress), |
| &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || |
| g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
| g_debug("%s ignoring: %s", plugin_name, error_local->message); |
| } else { |
| g_warning("failed to add device %s: %s", |
| fu_device_get_backend_id(device), |
| error_local->message); |
| } |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); |
| fu_progress_step_done(progress); |
| continue; |
| } |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| static void |
| fu_engine_backend_device_added(FuEngine *self, FuDevice *device, FuProgress *progress) |
| { |
| g_autoptr(GError) error_local = NULL; |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_set_name(progress, fu_device_get_backend_id(device)); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 50, "probe-baseclass"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 50, "query-possible-plugins"); |
| |
| /* super useful for plugin development */ |
| if (g_getenv("FWUPD_VERBOSE") != NULL) { |
| g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device)); |
| g_debug("%s added %s", fu_device_get_backend_id(device), str); |
| } |
| |
| /* add any extra quirks */ |
| fu_device_set_context(device, self->ctx); |
| if (!fu_device_probe(device, &error_local)) { |
| if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) && |
| !g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_TIMED_OUT)) { |
| g_warning("failed to probe device %s: %s", |
| fu_device_get_backend_id(device), |
| error_local->message); |
| } else { |
| g_debug("failed to probe device %s : %s", |
| fu_device_get_backend_id(device), |
| error_local->message); |
| } |
| fu_progress_finished(progress); |
| return; |
| } |
| fu_progress_step_done(progress); |
| |
| /* check if the device needs emulation-tag */ |
| fu_engine_ensure_device_emulation_tag(self, device); |
| |
| /* super useful for plugin development */ |
| if (g_getenv("FWUPD_VERBOSE") != NULL) { |
| g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device)); |
| g_debug("%s added %s", fu_device_get_backend_id(device), str); |
| } |
| |
| /* if this is for firmware attributes, reload that part of the daemon */ |
| fu_engine_check_firmware_attributes(self, device, TRUE); |
| |
| /* can be specified using a quirk */ |
| fu_engine_backend_device_added_run_plugins(self, device, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| } |
| |
| static void |
| fu_engine_backend_device_added_cb(FuBackend *backend, FuDevice *device, FuEngine *self) |
| { |
| g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); |
| g_autoptr(GPtrArray) possible_plugins = NULL; |
| |
| fu_engine_backend_device_added(self, device, progress); |
| |
| /* free data cached during ->probe */ |
| fu_device_probe_complete(device); |
| |
| /* there's no point keeping this in the cache */ |
| possible_plugins = fu_device_get_possible_plugins(device); |
| if (possible_plugins->len == 0) { |
| g_debug("removing %s from backend cache as no possible plugin", |
| fu_device_get_backend_id(device)); |
| fu_backend_device_removed(backend, device); |
| } |
| } |
| |
| static void |
| fu_engine_backend_device_changed_cb(FuBackend *backend, FuDevice *device, FuEngine *self) |
| { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* debug */ |
| g_debug("%s changed %s", fu_backend_get_name(backend), fu_device_get_physical_id(device)); |
| |
| /* emit changed on any that match */ |
| devices = fu_device_list_get_active(self->device_list); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) |
| continue; |
| if (!FU_IS_UDEV_DEVICE(device_tmp) || !FU_IS_UDEV_DEVICE(device)) |
| continue; |
| if (g_strcmp0(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device_tmp)), |
| fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))) == 0) { |
| fu_udev_device_emit_changed(FU_UDEV_DEVICE(device)); |
| } |
| } |
| |
| /* update the device for emulated devices */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device_tmp = g_ptr_array_index(devices, i); |
| if (!fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) |
| continue; |
| if (!fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_CAN_EMULATION_TAG)) |
| continue; |
| if (g_strcmp0(fu_device_get_backend_id(device_tmp), |
| fu_device_get_backend_id(device)) == 0) { |
| g_debug("incorporating new device for %s", fu_device_get_id(device_tmp)); |
| fu_device_incorporate(device_tmp, device, FU_DEVICE_INCORPORATE_FLAG_ALL); |
| } |
| } |
| |
| /* run all plugins */ |
| for (guint j = 0; j < plugins->len; j++) { |
| FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); |
| g_autoptr(GError) error = NULL; |
| if (!fu_plugin_runner_backend_device_changed(plugin_tmp, device, &error)) { |
| #ifdef SUPPORTED_BUILD |
| /* sanity check */ |
| if (error == NULL) { |
| g_critical( |
| "failed to change device %s: exec failed but no error set!", |
| fu_device_get_backend_id(device)); |
| continue; |
| } |
| #endif |
| if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_debug("%s ignoring: %s", |
| fu_plugin_get_name(plugin_tmp), |
| error->message); |
| continue; |
| } |
| g_warning("%s failed to change device %s: %s", |
| fu_plugin_get_name(plugin_tmp), |
| fu_device_get_id(device), |
| error->message); |
| } |
| } |
| } |
| |
| static void |
| fu_engine_load_quirks_for_hwid(FuEngine *self, const gchar *hwid) |
| { |
| FuPlugin *plugin; |
| const gchar *value; |
| g_auto(GStrv) plugins = NULL; |
| |
| /* does prefixed quirk exist */ |
| value = fu_context_lookup_quirk_by_id(self->ctx, hwid, FU_QUIRKS_PLUGIN); |
| if (value == NULL) |
| return; |
| plugins = g_strsplit(value, ",", -1); |
| for (guint i = 0; plugins[i] != NULL; i++) { |
| g_autoptr(GError) error_local = NULL; |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, plugins[i], &error_local); |
| if (plugin == NULL) { |
| g_info("no %s plugin for HwId %s: %s", |
| plugins[i], |
| hwid, |
| error_local->message); |
| continue; |
| } |
| g_info("enabling %s due to HwId %s", plugins[i], hwid); |
| fu_plugin_remove_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); |
| } |
| } |
| |
| static gboolean |
| fu_engine_update_history_device(FuEngine *self, FuDevice *dev_history, GError **error) |
| { |
| FuPlugin *plugin; |
| FuRelease *rel_history; |
| g_autofree gchar *btime = NULL; |
| g_autoptr(FuDevice) dev = NULL; |
| g_autoptr(GHashTable) metadata_device = NULL; |
| |
| /* is in the device list */ |
| dev = fu_device_list_get_by_id(self->device_list, fu_device_get_id(dev_history), error); |
| if (dev == NULL) |
| return FALSE; |
| |
| /* does the installed version match what we tried to install |
| * before fwupd was restarted */ |
| rel_history = FU_RELEASE(fu_device_get_release_default(dev_history)); |
| if (rel_history == NULL) { |
| g_set_error_literal(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "no release for history FuDevice"); |
| return FALSE; |
| } |
| |
| /* is this the same boot time as when we scheduled the update, |
| * i.e. has fwupd been restarted before we rebooted */ |
| btime = fu_engine_get_boot_time(); |
| if (g_strcmp0(fu_release_get_metadata_item(rel_history, "BootTime"), btime) == 0) { |
| g_info("service restarted, but no reboot has taken place"); |
| |
| /* if it needed reboot then, it also needs it now... */ |
| if (fu_device_get_update_state(dev_history) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { |
| g_autofree gchar *id_display = fu_device_get_id_display(dev); |
| if (g_strcmp0(fu_device_get_plugin(dev_history), "test") == 0) { |
| g_debug("ignoring needs-reboot for %s", id_display); |
| } else { |
| g_info("inheriting needs-reboot for %s", id_display); |
| fu_device_set_update_state(dev, FWUPD_UPDATE_STATE_NEEDS_REBOOT); |
| } |
| } |
| return TRUE; |
| } |
| |
| /* save any additional report metadata */ |
| metadata_device = fu_device_report_metadata_post(dev); |
| if (metadata_device != NULL && g_hash_table_size(metadata_device) > 0) { |
| fu_release_add_metadata(rel_history, metadata_device); |
| if (!fu_history_modify_device_release(self->history, |
| dev_history, |
| rel_history, |
| error)) { |
| g_prefix_error_literal(error, "failed to set metadata: "); |
| return FALSE; |
| } |
| } |
| |
| /* measure the "new" system state */ |
| plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(dev), error); |
| if (plugin == NULL) |
| return FALSE; |
| if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) |
| fu_engine_update_release_integrity(self, rel_history, "SystemIntegrityNew"); |
| |
| /* do any late-cleanup actions */ |
| if (!fu_plugin_runner_reboot_cleanup(plugin, dev, error)) { |
| g_prefix_error_literal(error, "failed to do post-reboot cleanup: "); |
| return FALSE; |
| } |
| |
| /* the system is running with the new firmware version */ |
| if (fu_version_compare(fu_device_get_version(dev), |
| fu_release_get_version(rel_history), |
| fu_device_get_version_format(dev)) == 0) { |
| GPtrArray *checksums; |
| g_info("installed version %s matching history %s", |
| fu_device_get_version(dev), |
| fu_release_get_version(rel_history)); |
| |
| /* copy over runtime checksums if set from probe() */ |
| checksums = fu_device_get_checksums(dev); |
| for (guint i = 0; i < checksums->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums, i); |
| fu_device_add_checksum(dev_history, csum); |
| } |
| fu_device_set_version_format(dev_history, fu_device_get_version_format(dev)); |
| fu_device_set_version(dev_history, fu_device_get_version(dev)); |
| fu_device_remove_flag(dev_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); |
| fu_device_set_update_state(dev_history, FWUPD_UPDATE_STATE_SUCCESS); |
| return fu_history_modify_device_release(self->history, |
| dev_history, |
| rel_history, |
| error); |
| } |
| |
| /* does the plugin know the update failure */ |
| if (!fu_plugin_runner_get_results(plugin, dev, error)) |
| return FALSE; |
| |
| /* the plugin either can't tell us the error, or doesn't know itself */ |
| if (fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED && |
| fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { |
| g_autoptr(GString) str = g_string_new("failed to run update on reboot: "); |
| g_info("falling back to generic failure"); |
| fu_device_set_update_state(dev_history, FWUPD_UPDATE_STATE_FAILED); |
| g_string_append_printf(str, |
| "expected %s and got %s", |
| fu_release_get_version(rel_history), |
| fu_device_get_version(dev)); |
| fu_device_set_update_error(dev_history, str->str); |
| } else { |
| fu_device_set_update_state(dev_history, fu_device_get_update_state(dev)); |
| fu_device_set_update_error(dev_history, fu_device_get_update_error(dev)); |
| } |
| |
| /* update the state in the database */ |
| return fu_history_modify_device_release(self->history, dev_history, rel_history, error); |
| } |
| |
| static gboolean |
| fu_engine_update_history_database(FuEngine *self, GError **error) |
| { |
| g_autoptr(GPtrArray) devices = NULL; |
| |
| /* get any devices */ |
| devices = fu_history_get_devices(self->history, error); |
| if (devices == NULL) |
| return FALSE; |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *dev = g_ptr_array_index(devices, i); |
| g_autoptr(GError) error_local = NULL; |
| |
| /* not in the required state */ |
| if (fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_NEEDS_REBOOT && |
| fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_PENDING) |
| continue; |
| |
| /* try to save the new update-state, but ignoring any error */ |
| if (!fu_engine_update_history_device(self, dev, &error_local)) { |
| if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
| g_debug("failed to update history database: %s", |
| error_local->message); |
| continue; |
| } |
| g_warning("failed to update history database: %s", error_local->message); |
| } |
| } |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_ensure_client_certificate(FuEngine *self) |
| { |
| g_autoptr(GBytes) blob = g_bytes_new_static(NULL, 0); |
| g_autoptr(GError) error_local = NULL; |
| g_autoptr(JcatBlob) jcat_sig = NULL; |
| g_autoptr(JcatEngine) jcat_engine = NULL; |
| |
| /* create keyring and sign dummy data to ensure certificate exists */ |
| jcat_engine = |
| jcat_context_get_engine(self->jcat_context, JCAT_BLOB_KIND_PKCS7, &error_local); |
| if (jcat_engine == NULL) { |
| g_message("failed to create keyring: %s", error_local->message); |
| return; |
| } |
| jcat_sig = jcat_engine_self_sign(jcat_engine, blob, JCAT_SIGN_FLAG_NONE, &error_local); |
| if (jcat_sig == NULL) { |
| if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { |
| g_info("client certificate now exists: %s", error_local->message); |
| return; |
| } |
| g_message("failed to sign using keyring: %s", error_local->message); |
| return; |
| } |
| g_info("client certificate exists and working"); |
| } |
| |
| static void |
| fu_engine_context_set_battery_threshold(FuContext *ctx) |
| { |
| guint64 minimum_battery; |
| g_autofree gchar *battery_str = NULL; |
| g_autofree gchar *vendor_guid = NULL; |
| g_autofree gchar *vendor = NULL; |
| |
| vendor = fu_context_get_hwid_replace_value(ctx, FU_HWIDS_KEY_MANUFACTURER, NULL); |
| vendor_guid = fwupd_guid_hash_string(vendor); |
| if (vendor_guid != NULL) { |
| battery_str = g_strdup( |
| fu_context_lookup_quirk_by_id(ctx, vendor_guid, FU_QUIRKS_BATTERY_THRESHOLD)); |
| } |
| if (battery_str == NULL) { |
| minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; |
| } else { |
| g_autoptr(GError) error_local = NULL; |
| if (!fu_strtoull(battery_str, |
| &minimum_battery, |
| 0, |
| 100, |
| FU_INTEGER_BASE_AUTO, |
| &error_local)) { |
| g_warning("invalid minimum battery level specified: %s", |
| error_local->message); |
| minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; |
| } |
| } |
| fu_context_set_battery_threshold(ctx, minimum_battery); |
| } |
| |
| static gboolean |
| fu_engine_ensure_paths_exist(GError **error) |
| { |
| FuPathKind path_kinds[] = {FU_PATH_KIND_LOCALSTATEDIR_QUIRKS, |
| FU_PATH_KIND_LOCALSTATEDIR_METADATA, |
| FU_PATH_KIND_LOCALSTATEDIR_REMOTES, |
| FU_PATH_KIND_CACHEDIR_PKG, |
| FU_PATH_KIND_LAST}; |
| for (guint i = 0; path_kinds[i] != FU_PATH_KIND_LAST; i++) { |
| g_autofree gchar *fn = fu_path_from_kind(path_kinds[i]); |
| if (!fu_path_mkdir(fn, error)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_local_metadata_changed_cb(GFileMonitor *monitor, |
| GFile *file, |
| GFile *other_file, |
| GFileMonitorEvent event_type, |
| gpointer user_data) |
| { |
| FuEngine *self = FU_ENGINE(user_data); |
| fu_engine_metadata_changed(self); |
| } |
| |
| static gboolean |
| fu_engine_load_local_metadata_watches(FuEngine *self, GError **error) |
| { |
| const FuPathKind path_kinds[] = {FU_PATH_KIND_DATADIR_PKG, FU_PATH_KIND_LOCALSTATEDIR_PKG}; |
| |
| /* add the watches even if the directory does not exist */ |
| for (guint i = 0; i < G_N_ELEMENTS(path_kinds); i++) { |
| GFileMonitor *monitor; |
| g_autoptr(GFile) file = NULL; |
| g_autoptr(GError) error_local = NULL; |
| g_autofree gchar *base = fu_path_from_kind(path_kinds[i]); |
| g_autofree gchar *fn = g_build_filename(base, "local.d", NULL); |
| |
| file = g_file_new_for_path(fn); |
| monitor = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, &error_local); |
| if (monitor == NULL) { |
| g_warning("failed to watch %s: %s", fn, error_local->message); |
| continue; |
| } |
| g_signal_connect(monitor, |
| "changed", |
| G_CALLBACK(fu_engine_local_metadata_changed_cb), |
| self); |
| g_ptr_array_add(self->local_monitors, g_steal_pointer(&monitor)); |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| #ifdef _WIN32 |
| static gchar * |
| fu_engine_win32_registry_get_string(HKEY hkey, |
| const gchar *subkey, |
| const gchar *value, |
| GError **error) |
| { |
| gchar buf[255] = {'\0'}; |
| DWORD bufsz = sizeof(buf); |
| LSTATUS rc; |
| |
| rc = RegGetValue(hkey, subkey, value, RRF_RT_REG_SZ, NULL, (PVOID)&buf, &bufsz); |
| if (rc != ERROR_SUCCESS) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED, |
| "failed to get registry string %s [0x%lX]", |
| subkey, |
| (unsigned long)rc); |
| return NULL; |
| } |
| return g_strndup(buf, bufsz); |
| } |
| #endif |
| |
| static gboolean |
| fu_engine_backends_coldplug_backend_add_devices(FuEngine *self, |
| FuBackend *backend, |
| FuProgress *progress, |
| GError **error) |
| { |
| g_autoptr(GPtrArray) devices = fu_backend_get_devices(backend); |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, devices->len); |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| g_autoptr(GPtrArray) possible_plugins = NULL; |
| |
| fu_engine_backend_device_added(self, device, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| |
| /* free data cached during ->probe */ |
| fu_device_probe_complete(device); |
| |
| /* there's no point keeping this in the cache */ |
| possible_plugins = fu_device_get_possible_plugins(device); |
| if (possible_plugins->len == 0) { |
| g_debug("removing %s from backend cache as no possible plugin", |
| fu_device_get_backend_id(device)); |
| fu_backend_device_removed(backend, device); |
| } |
| } |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static gboolean |
| fu_engine_backends_coldplug_backend(FuEngine *self, |
| FuBackend *backend, |
| FuProgress *progress, |
| GError **error) |
| { |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_set_name(progress, fu_backend_get_name(backend)); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "coldplug"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "add-devices"); |
| |
| /* coldplug */ |
| if (!fu_backend_coldplug(backend, fu_progress_get_child(progress), error)) |
| return FALSE; |
| fu_progress_step_done(progress); |
| |
| /* add */ |
| fu_engine_backends_coldplug_backend_add_devices(self, |
| backend, |
| fu_progress_get_child(progress), |
| error); |
| fu_progress_step_done(progress); |
| |
| /* success */ |
| g_signal_connect(FU_BACKEND(backend), |
| "device-added", |
| G_CALLBACK(fu_engine_backend_device_added_cb), |
| self); |
| g_signal_connect(FU_BACKEND(backend), |
| "device-removed", |
| G_CALLBACK(fu_engine_backend_device_removed_cb), |
| self); |
| g_signal_connect(FU_BACKEND(backend), |
| "device-changed", |
| G_CALLBACK(fu_engine_backend_device_changed_cb), |
| self); |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_backends_coldplug(FuEngine *self, FuProgress *progress) |
| { |
| GPtrArray *backends = fu_context_get_backends(self->ctx); |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_set_steps(progress, backends->len); |
| for (guint i = 0; i < backends->len; i++) { |
| FuBackend *backend = g_ptr_array_index(backends, i); |
| g_autoptr(GError) error_backend = NULL; |
| |
| if (!fu_backend_get_enabled(backend)) { |
| fu_progress_step_done(progress); |
| continue; |
| } |
| if (!fu_engine_backends_coldplug_backend(self, |
| backend, |
| fu_progress_get_child(progress), |
| &error_backend)) { |
| if (g_error_matches(error_backend, |
| FWUPD_ERROR, |
| FWUPD_ERROR_NOT_SUPPORTED)) { |
| g_debug("ignoring coldplug failure %s: %s", |
| fu_backend_get_name(backend), |
| error_backend->message); |
| } else { |
| g_warning("failed to coldplug backend %s: %s", |
| fu_backend_get_name(backend), |
| error_backend->message); |
| } |
| fu_progress_finished(fu_progress_get_child(progress)); |
| } |
| fu_progress_step_done(progress); |
| } |
| } |
| |
| /** |
| * fu_engine_load: |
| * @self: a #FuEngine |
| * @flags: engine load flags, e.g. %FU_ENGINE_LOAD_FLAG_READONLY |
| * @progress: a #FuProgress |
| * @error: (nullable): optional return location for an error |
| * |
| * Load the firmware update engine so it is ready for use. |
| * |
| * Returns: %TRUE for success |
| **/ |
| gboolean |
| fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress, GError **error) |
| { |
| FuPlugin *plugin_uefi; |
| FuQuirksLoadFlags quirks_flags = FU_QUIRKS_LOAD_FLAG_NONE; |
| GPtrArray *backends = fu_context_get_backends(self->ctx); |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| const gchar *host_emulate = g_getenv("FWUPD_HOST_EMULATE"); |
| g_autoptr(GPtrArray) checksums_approved = NULL; |
| g_autoptr(GPtrArray) checksums_blocked = NULL; |
| g_autoptr(GError) error_quirks = NULL; |
| g_autoptr(GError) error_json_devices = NULL; |
| g_autoptr(GError) error_local = NULL; |
| |
| g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); |
| g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); |
| g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
| |
| /* avoid re-loading a second time if fu-tool or fu-util request to */ |
| if (self->load_flags & FU_ENGINE_LOAD_FLAG_READY) |
| return TRUE; |
| |
| /* progress */ |
| fu_progress_set_id(progress, G_STRLOC); |
| fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "read-config"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "read-remotes"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "ensure-client-cert"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "write-db"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-plugins"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-quirks"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-hwinfo"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-appstream"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "backend-setup"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-init"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "hwid-quirks"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-setup"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 3, "plugins-coldplug"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 90, "backend-coldplug"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-ready"); |
| fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "update-history-db"); |
| |
| /* sanity check libraries are in sync with daemon */ |
| if (g_strcmp0(fwupd_version_string(), VERSION) != 0) { |
| g_set_error(error, |
| FWUPD_ERROR, |
| FWUPD_ERROR_INTERNAL, |
| "libfwupd version %s does not match daemon %s", |
| fwupd_version_string(), |
| VERSION); |
| return FALSE; |
| } |
| |
| /* cache machine ID so we can use it from a sandboxed app */ |
| #ifdef _WIN32 |
| self->host_machine_id = |
| fu_engine_win32_registry_get_string(HKEY_LOCAL_MACHINE, |
| "SOFTWARE\\Microsoft\\Cryptography", |
| "MachineGuid", |
| &error_local); |
| #else |
| self->host_machine_id = fu_engine_build_machine_id("fwupd", &error_local); |
| #endif |
| if (self->host_machine_id == NULL) |
| g_info("failed to build machine-id: %s", error_local->message); |
| |
| /* ensure these exist before starting */ |
| if (!fu_engine_ensure_paths_exist(error)) |
| return FALSE; |
| |
| /* read config file */ |
| if (!fu_config_load(FU_CONFIG(self->config), |
| FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS | FU_CONFIG_LOAD_FLAG_WATCH_FILES | |
| FU_CONFIG_LOAD_FLAG_MIGRATE_FILES, |
| error)) { |
| g_prefix_error_literal(error, "failed to load config: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* set the hardcoded ESP */ |
| if (fu_engine_config_get_esp_location(self->config) != NULL) { |
| fu_context_set_esp_location(self->ctx, |
| fu_engine_config_get_esp_location(self->config)); |
| } |
| |
| /* read remotes */ |
| if (flags & FU_ENGINE_LOAD_FLAG_REMOTES) { |
| FuRemoteListLoadFlags remote_list_flags = FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI; |
| if (fu_engine_config_get_test_devices(self->config)) |
| remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_TEST_REMOTE; |
| if (flags & FU_ENGINE_LOAD_FLAG_READONLY) |
| remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS; |
| if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) |
| remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_NO_CACHE; |
| fu_remote_list_set_lvfs_metadata_format(self->remote_list, FU_LVFS_METADATA_FORMAT); |
| if (!fu_remote_list_load(self->remote_list, remote_list_flags, error)) { |
| g_prefix_error_literal(error, "failed to load remotes: "); |
| return FALSE; |
| } |
| } |
| fu_progress_step_done(progress); |
| |
| /* create client certificate */ |
| if (flags & FU_ENGINE_LOAD_FLAG_ENSURE_CLIENT_CERT) |
| fu_engine_ensure_client_certificate(self); |
| fu_progress_step_done(progress); |
| |
| /* get hardcoded approved and blocked firmware */ |
| checksums_approved = fu_engine_config_get_approved_firmware(self->config); |
| for (guint i = 0; i < checksums_approved->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums_approved, i); |
| fu_engine_add_approved_firmware(self, csum); |
| } |
| checksums_blocked = fu_engine_config_get_blocked_firmware(self->config); |
| for (guint i = 0; i < checksums_blocked->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums_blocked, i); |
| fu_engine_add_blocked_firmware(self, csum); |
| } |
| |
| /* get extra firmware saved to the database */ |
| checksums_approved = fu_history_get_approved_firmware(self->history, error); |
| if (checksums_approved == NULL) |
| return FALSE; |
| for (guint i = 0; i < checksums_approved->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums_approved, i); |
| fu_engine_add_approved_firmware(self, csum); |
| } |
| checksums_blocked = fu_history_get_blocked_firmware(self->history, error); |
| if (checksums_blocked == NULL) |
| return FALSE; |
| for (guint i = 0; i < checksums_blocked->len; i++) { |
| const gchar *csum = g_ptr_array_index(checksums_blocked, i); |
| fu_engine_add_blocked_firmware(self, csum); |
| } |
| fu_progress_step_done(progress); |
| |
| /* load plugins early, as we have to call ->load() *before* building quirk silo */ |
| if (!fu_engine_load_plugins(self, flags, fu_progress_get_child(progress), error)) { |
| g_prefix_error_literal(error, "failed to load plugins: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* migrate per-plugin settings into fwupd.conf */ |
| plugin_uefi = fu_plugin_list_find_by_name(self->plugin_list, "uefi_capsule", NULL); |
| if (plugin_uefi != NULL) { |
| const gchar *tmp = fu_plugin_get_config_value(plugin_uefi, "OverrideESPMountPoint"); |
| if (tmp != NULL && |
| g_strcmp0(tmp, fu_engine_config_get_esp_location(self->config)) != 0) { |
| g_info("migrating OverrideESPMountPoint=%s to EspLocation", tmp); |
| if (!fu_config_set_value(FU_CONFIG(self->config), |
| "fwupd", |
| "EspLocation", |
| tmp, |
| error)) |
| return FALSE; |
| } |
| } |
| |
| /* set up idle exit */ |
| if (!fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_NO_IDLE_SOURCES)) |
| fu_idle_set_timeout(self->idle, fu_engine_config_get_idle_timeout(self->config)); |
| |
| /* on a read-only filesystem don't care about the cache GUID */ |
| if (flags & FU_ENGINE_LOAD_FLAG_READONLY) |
| quirks_flags |= FU_QUIRKS_LOAD_FLAG_READONLY_FS; |
| if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) |
| quirks_flags |= FU_QUIRKS_LOAD_FLAG_NO_CACHE; |
| if (!fu_context_load_quirks(self->ctx, quirks_flags, &error_quirks)) |
| g_warning("Failed to load quirks: %s", error_quirks->message); |
| fu_progress_step_done(progress); |
| |
| /* do not mount disks if only loading readonly */ |
| if (flags & FU_ENGINE_LOAD_FLAG_READONLY) |
| fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_INHIBIT_VOLUME_MOUNT); |
| |
| /* required on Linux kernel < 6.4, or when `RT->QueryVariableInfo` is not supported */ |
| if (fu_engine_config_get_ignore_efivars_free_space(self->config)) |
| fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_IGNORE_EFIVARS_FREE_SPACE); |
| |
| /* load SMBIOS and the hwids */ |
| if (flags & FU_ENGINE_LOAD_FLAG_HWINFO) { |
| if (!fu_context_load_hwinfo(self->ctx, |
| fu_progress_get_child(progress), |
| FU_CONTEXT_HWID_FLAG_LOAD_ALL | |
| FU_CONTEXT_HWID_FLAG_FIX_PERMISSIONS | |
| FU_CONTEXT_HWID_FLAG_WATCH_FILES, |
| error)) |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* load AppStream metadata */ |
| if (!fu_engine_load_metadata_store(self, flags, error)) { |
| g_prefix_error_literal(error, "failed to load AppStream data: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* watch the local.d directories for changes */ |
| if (!fu_engine_load_local_metadata_watches(self, error)) |
| return FALSE; |
| |
| /* add the "built-in" firmware types */ |
| fu_engine_add_firmware_gtypes(self); |
| |
| /* we are emulating a different host */ |
| if (host_emulate != NULL) { |
| g_autofree gchar *fn = NULL; |
| |
| /* did the user specify an absolute path */ |
| if (g_file_test(host_emulate, G_FILE_TEST_EXISTS)) { |
| fn = g_strdup(host_emulate); |
| } else { |
| g_autofree gchar *datadir = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); |
| fn = g_build_filename(datadir, "host-emulate.d", host_emulate, NULL); |
| } |
| if (!fu_engine_load_host_emulation(self, fn, error)) { |
| g_prefix_error_literal(error, "failed to load emulated host: "); |
| return FALSE; |
| } |
| |
| /* do not load actual hardware */ |
| flags &= ~FU_ENGINE_LOAD_FLAG_COLDPLUG; |
| self->host_emulation = TRUE; |
| } |
| |
| /* set up backends */ |
| self->load_flags = flags; |
| if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { |
| FuBackendSetupFlags backend_flags = FU_BACKEND_SETUP_FLAG_NONE; |
| if (flags & FU_ENGINE_LOAD_FLAG_DEVICE_HOTPLUG) |
| backend_flags |= FU_BACKEND_SETUP_FLAG_USE_HOTPLUG; |
| for (guint i = 0; i < backends->len; i++) { |
| FuBackend *backend = g_ptr_array_index(backends, i); |
| g_autoptr(GError) error_backend = NULL; |
| if (!fu_backend_setup(backend, |
| backend_flags, |
| fu_progress_get_child(progress), |
| &error_backend)) { |
| g_info("failed to setup backend %s: %s", |
| fu_backend_get_name(backend), |
| error_backend->message); |
| continue; |
| } |
| } |
| } |
| fu_progress_step_done(progress); |
| |
| /* delete old data files */ |
| if (!fu_engine_cleanup_state(error)) { |
| g_prefix_error_literal(error, "failed to clean up: "); |
| return FALSE; |
| } |
| |
| /* init plugins, adding device and firmware GTypes */ |
| if (!fu_engine_plugins_init(self, fu_progress_get_child(progress), error)) { |
| g_prefix_error_literal(error, "failed to init plugins: "); |
| return FALSE; |
| } |
| fu_progress_step_done(progress); |
| |
| /* set quirks for each hwid */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) { |
| GPtrArray *guids = fu_context_get_hwid_guids(self->ctx); |
| for (guint i = 0; i < guids->len; i++) { |
| const gchar *hwid = g_ptr_array_index(guids, i); |
| fu_engine_load_quirks_for_hwid(self, hwid); |
| } |
| } |
| fu_progress_step_done(progress); |
| |
| /* set up battery threshold */ |
| if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) |
| fu_engine_context_set_battery_threshold(self->ctx); |
| |
| /* watch the device list for updates and proxy */ |
| g_signal_connect(FU_DEVICE_LIST(self->device_list), |
| "added", |
| G_CALLBACK(fu_engine_device_added_cb), |
| self); |
| g_signal_connect(FU_DEVICE_LIST(self->device_list), |
| "removed", |
| G_CALLBACK(fu_engine_device_removed_cb), |
| self); |
| g_signal_connect(FU_DEVICE_LIST(self->device_list), |
| "changed", |
| G_CALLBACK(fu_engine_device_changed_cb), |
| self); |
| fu_engine_set_status(self, FWUPD_STATUS_LOADING); |
| |
| /* add devices */ |
| if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { |
| fu_engine_ensure_context_flag_save_events(self); |
| fu_engine_plugins_startup(self, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| fu_engine_plugins_coldplug(self, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| } else { |
| fu_progress_step_done(progress); |
| fu_progress_step_done(progress); |
| } |
| |
| /* coldplug backends */ |
| if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) |
| fu_engine_backends_coldplug(self, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| |
| /* coldplug done, so plugin is ready */ |
| if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { |
| fu_engine_plugins_ready(self, fu_progress_get_child(progress)); |
| fu_progress_step_done(progress); |
| } else { |
| fu_progress_step_done(progress); |
| } |
| |
| /* dump plugin information to the console */ |
| if (g_getenv("FWUPD_VERBOSE") != NULL) { |
| g_autoptr(GString) str = g_string_new(NULL); |
| for (guint i = 0; i < backends->len; i++) { |
| FuBackend *backend = g_ptr_array_index(backends, i); |
| fu_backend_add_string(backend, 0, str); |
| } |
| for (guint i = 0; i < plugins->len; i++) { |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) |
| continue; |
| fu_plugin_add_string(plugin, 0, str); |
| } |
| g_info("%s", str->str); |
| } |
| |
| /* update the db for devices that were updated during the reboot */ |
| if (!fu_engine_update_history_database(self, error)) |
| return FALSE; |
| fu_progress_step_done(progress); |
| |
| /* update the devices JSON file */ |
| if (!fu_engine_update_devices_file(self, &error_json_devices)) |
| g_info("failed to update list of devices: %s", error_json_devices->message); |
| |
| fu_engine_set_status(self, FWUPD_STATUS_IDLE); |
| self->load_flags |= FU_ENGINE_LOAD_FLAG_READY; |
| |
| /* let clients know engine finished starting up */ |
| fu_engine_emit_changed(self); |
| |
| /* success */ |
| return TRUE; |
| } |
| |
| static void |
| fu_engine_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
| { |
| FuEngine *self = FU_ENGINE(object); |
| switch (prop_id) { |
| case PROP_CONTEXT: |
| g_value_set_object(value, self->ctx); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| fu_engine_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
| { |
| FuEngine *self = FU_ENGINE(object); |
| switch (prop_id) { |
| case PROP_CONTEXT: |
| g_set_object(&self->ctx, g_value_get_object(value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| fu_engine_dispose(GObject *obj) |
| { |
| FuEngine *self = FU_ENGINE(obj); |
| |
| if (self->plugin_list != NULL) { |
| GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); |
| for (guint i = 0; i < plugins->len; i++) { |
| FuPlugin *plugin = g_ptr_array_index(plugins, i); |
| g_signal_handlers_disconnect_by_data(plugin, self); |
| } |
| fu_plugin_list_remove_all(self->plugin_list); |
| } |
| if (self->device_list != NULL) |
| fu_device_list_remove_all(self->device_list); |
| if (self->config != NULL) |
| g_signal_handlers_disconnect_by_data(self->config, self); |
| |
| if (self->ctx != NULL) { |
| GPtrArray *backends = fu_context_get_backends(self->ctx); |
| for (guint i = 0; i < backends->len; i++) { |
| FuBackend *backend = g_ptr_array_index(backends, i); |
| g_signal_handlers_disconnect_by_data(backend, self); |
| } |
| g_ptr_array_set_size(backends, 0); |
| g_signal_handlers_disconnect_by_data(self->ctx, self); |
| } |
| g_clear_object(&self->ctx); |
| |
| G_OBJECT_CLASS(fu_engine_parent_class)->dispose(obj); |
| } |
| |
| static void |
| fu_engine_class_init(FuEngineClass *klass) |
| { |
| GParamSpec *pspec; |
| GObjectClass *object_class = G_OBJECT_CLASS(klass); |
| const gchar *quark_flags[] = { |
| FU_DEVICE_PRIVATE_FLAG_AUTO_PARENT_CHILDREN, |
| FU_DEVICE_PRIVATE_FLAG_HOST_FIRMWARE, |
| FU_DEVICE_PRIVATE_FLAG_HOST_FIRMWARE_CHILD, |
| FU_DEVICE_PRIVATE_FLAG_HOST_CPU, |
| FU_DEVICE_PRIVATE_FLAG_HOST_CPU_CHILD, |
| }; |
| |
| object_class->dispose = fu_engine_dispose; |
| object_class->finalize = fu_engine_finalize; |
| object_class->get_property = fu_engine_get_property; |
| object_class->set_property = fu_engine_set_property; |
| object_class->constructed = fu_engine_constructed; |
| |
| /* used as device flags, order is important! */ |
| for (guint i = 0; i < G_N_ELEMENTS(quark_flags); i++) |
| quarks[i] = g_quark_from_static_string(quark_flags[i]); |
| |
| pspec = g_param_spec_object("context", |
| NULL, |
| NULL, |
| FU_TYPE_CONTEXT, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); |
| g_object_class_install_property(object_class, PROP_CONTEXT, pspec); |
| |
| /** |
| * FuEngine::changed: |
| * @self: the #FuEngine instance that emitted the signal |
| * |
| * The ::changed signal is emitted when the engine has changed, for instance when a device |
| * state has been modified. |
| **/ |
| signals[SIGNAL_CHANGED] = g_signal_new("changed", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__VOID, |
| G_TYPE_NONE, |
| 0); |
| /** |
| * FuEngine::device-added: |
| * @self: the #FuEngine instance that emitted the signal |
| * @device: the #FuDevice |
| * |
| * The ::device-added signal is emitted when a device has been added. |
| **/ |
| signals[SIGNAL_DEVICE_ADDED] = g_signal_new("device-added", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__OBJECT, |
| G_TYPE_NONE, |
| 1, |
| FU_TYPE_DEVICE); |
| /** |
| * FuEngine::device-removed: |
| * @self: the #FuEngine instance that emitted the signal |
| * @device: the #FuDevice |
| * |
| * The ::device-removed signal is emitted when a device has been removed. |
| **/ |
| signals[SIGNAL_DEVICE_REMOVED] = g_signal_new("device-removed", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__OBJECT, |
| G_TYPE_NONE, |
| 1, |
| FU_TYPE_DEVICE); |
| /** |
| * FuEngine::device-changed: |
| * @self: the #FuEngine instance that emitted the signal |
| * @device: the #FuDevice |
| * |
| * The ::device-changed signal is emitted when a device has been changed. |
| **/ |
| signals[SIGNAL_DEVICE_CHANGED] = g_signal_new("device-changed", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__OBJECT, |
| G_TYPE_NONE, |
| 1, |
| FU_TYPE_DEVICE); |
| /** |
| * FuEngine::device-request: |
| * @self: the #FuEngine instance that emitted the signal |
| * @request: the #FwupdRequest |
| * |
| * The ::device-request signal is emitted when the engine has asked the front end for an |
| * interactive request. |
| **/ |
| signals[SIGNAL_DEVICE_REQUEST] = g_signal_new("device-request", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__OBJECT, |
| G_TYPE_NONE, |
| 1, |
| FWUPD_TYPE_REQUEST); |
| /** |
| * FuEngine::status-changed: |
| * @self: the #FuEngine instance that emitted the signal |
| * @status: the #FwupdStatus |
| * |
| * The ::status-changed signal is emitted when the daemon global status has changed. |
| **/ |
| signals[SIGNAL_STATUS_CHANGED] = g_signal_new("status-changed", |
| G_TYPE_FROM_CLASS(object_class), |
| G_SIGNAL_RUN_LAST, |
| 0, |
| NULL, |
| NULL, |
| g_cclosure_marshal_VOID__UINT, |
| G_TYPE_NONE, |
| 1, |
| G_TYPE_UINT); |
| } |
| |
| void |
| fu_engine_add_runtime_version(FuEngine *self, const gchar *component_id, const gchar *version) |
| { |
| fu_context_add_runtime_version(self->ctx, component_id, version); |
| } |
| |
| static void |
| fu_engine_context_power_changed(FuEngine *self) |
| { |
| g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); |
| |
| /* apply policy on any existing devices */ |
| for (guint i = 0; i < devices->len; i++) { |
| FuDevice *device = g_ptr_array_index(devices, i); |
| fu_engine_ensure_device_power_inhibit(self, device); |
| fu_engine_ensure_device_lid_inhibit(self, device); |
| fu_engine_ensure_device_display_required_inhibit(self, device); |
| fu_engine_ensure_device_system_inhibit(self, device); |
| } |
| } |
| |
| static void |
| fu_engine_context_power_changed_cb(FuContext *ctx, GParamSpec *pspec, FuEngine *self) |
| { |
| if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS)) { |
| g_debug("suppressing ::power-changed as transaction is in progress"); |
| return; |
| } |
| fu_engine_context_power_changed(self); |
| } |
| |
| static void |
| fu_engine_idle_timeout_cb(FuIdle *idle, FuEngine *self) |
| { |
| fu_engine_set_status(self, FWUPD_STATUS_SHUTDOWN); |
| } |
| |
| static void |
| fu_engine_idle_inhibit_changed_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self) |
| { |
| if (!fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS) && |
| g_hash_table_size(self->device_changed_allowlist) > 0) { |
| g_debug("clearing device-changed allowlist as transaction done"); |
| g_hash_table_remove_all(self->device_changed_allowlist); |
| |
| /* we might have suppressed this during the transaction, so ensure all the device |
| * inhibits are being set up correctly */ |
| fu_engine_context_power_changed(self); |
| } |
| } |
| |
| static void |
| fu_engine_constructed(GObject *obj) |
| { |
| FuEngine *self = FU_ENGINE(obj); |
| #ifdef HAVE_UTSNAME_H |
| struct utsname uname_tmp = {0}; |
| #endif |
| g_autofree gchar *keyring_path = NULL; |
| g_autofree gchar *pkidir_fw = NULL; |
| g_autofree gchar *pkidir_md = NULL; |
| g_autofree gchar *sysconfdir = NULL; |
| |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "security-changed", |
| G_CALLBACK(fu_engine_context_security_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::power-state", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::lid-state", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::display-state", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::battery-level", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::battery-threshold", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| g_signal_connect(FU_CONTEXT(self->ctx), |
| "notify::flags", |
| G_CALLBACK(fu_engine_context_power_changed_cb), |
| self); |
| |
| g_signal_connect(FU_CONFIG(self->config), |
| "changed", |
| G_CALLBACK(fu_engine_config_changed_cb), |
| self); |
| g_signal_connect(FU_REMOTE_LIST(self->remote_list), |
| "changed", |
| G_CALLBACK(fu_engine_remote_list_changed_cb), |
| self); |
| g_signal_connect(FU_REMOTE_LIST(self->remote_list), |
| "added", |
| G_CALLBACK(fu_engine_remote_list_added_cb), |
| self); |
| |
| g_signal_connect(FU_IDLE(self->idle), |
| "inhibit-changed", |
| G_CALLBACK(fu_engine_idle_inhibit_changed_cb), |
| self); |
| g_signal_connect(FU_IDLE(self->idle), |
| "timeout", |
| G_CALLBACK(fu_engine_idle_timeout_cb), |
| self); |
| |
| /* backends */ |
| { |
| g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx); |
| fu_context_add_backend(self->ctx, backend); |
| } |
| { |
| g_autoptr(FuBackend) backend = fu_uefi_backend_new(self->ctx); |
| fu_context_add_backend(self->ctx, backend); |
| } |
| #ifdef HAVE_UDEV |
| { |
| g_autoptr(FuBackend) backend = fu_udev_backend_new(self->ctx); |
| fu_context_add_backend(self->ctx, backend); |
| } |
| #endif |
| #ifdef HAVE_BLUEZ |
| { |
| g_autoptr(FuBackend) backend = fu_bluez_backend_new(self->ctx); |
| fu_context_add_backend(self->ctx, backend); |
| } |
| #endif |
| |
| self->history = fu_history_new(self->ctx); |
| self->emulation = fu_engine_emulator_new(self); |
| |
| /* setup Jcat context */ |
| self->jcat_context = jcat_context_new(); |
| jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA256); |
| jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA512); |
| jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_PKCS7); |
| jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_GPG); |
| keyring_path = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); |
| jcat_context_set_keyring_path(self->jcat_context, keyring_path); |
| sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR); |
| pkidir_fw = g_build_filename(sysconfdir, "pki", "fwupd", NULL); |
| jcat_context_add_public_keys(self->jcat_context, pkidir_fw); |
| pkidir_md = g_build_filename(sysconfdir, "pki", "fwupd-metadata", NULL); |
| jcat_context_add_public_keys(self->jcat_context, pkidir_md); |
| |
| /* add some runtime versions of things the daemon depends on */ |
| fu_engine_add_runtime_version(self, "org.freedesktop.fwupd", VERSION); |
| fu_engine_add_runtime_version(self, "com.hughsie.libjcat", jcat_version_string()); |
| fu_engine_add_runtime_version(self, "com.hughsie.libxmlb", xb_version_string()); |
| |
| /* optional kernel version */ |
| #ifdef HAVE_UTSNAME_H |
| if (uname(&uname_tmp) >= 0) |
| fu_engine_add_runtime_version(self, "org.kernel", uname_tmp.release); |
| #endif |
| |
| fu_context_add_compile_version(self->ctx, "org.freedesktop.fwupd", VERSION); |
| #ifdef SOURCE_VERSION |
| if (g_strcmp0(SOURCE_VERSION, VERSION) != 0) |
| fu_context_add_compile_version(self->ctx, |
| "org.freedesktop.fwupd.source", |
| SOURCE_VERSION); |
| #endif |
| fu_context_add_compile_version(self->ctx, "info.libusb", LIBUSB_VERSION); |
| #ifdef HAVE_PASSIM |
| { |
| g_autofree gchar *version = g_strdup_printf("%i.%i.%i", |
| PASSIM_MAJOR_VERSION, |
| PASSIM_MINOR_VERSION, |
| PASSIM_MICRO_VERSION); |
| fu_context_add_compile_version(self->ctx, "org.freedesktop.Passim", version); |
| } |
| #endif |
| { |
| g_autofree gchar *version = g_strdup_printf("%i.%i.%i", |
| JCAT_MAJOR_VERSION, |
| JCAT_MINOR_VERSION, |
| JCAT_MICRO_VERSION); |
| fu_context_add_compile_version(self->ctx, "com.hughsie.libjcat", version); |
| } |
| { |
| g_autofree gchar *version = g_strdup_printf("%i.%i.%i", |
| XMLB_MAJOR_VERSION, |
| XMLB_MINOR_VERSION, |
| XMLB_MICRO_VERSION); |
| fu_context_add_compile_version(self->ctx, "com.hughsie.libxmlb", version); |
| } |
| |
| /* add optional snap version */ |
| if (g_getenv("SNAP_REVISION") != NULL) { |
| fu_context_add_compile_version(self->ctx, |
| "io.snapcraft.fwupd", |
| g_getenv("SNAP_REVISION")); |
| } |
| } |
| |
| static void |
| fu_engine_init(FuEngine *self) |
| { |
| self->percentage = 0; |
| self->config = fu_engine_config_new(); |
| self->remote_list = fu_remote_list_new(); |
| self->device_list = fu_device_list_new(); |
| self->idle = fu_idle_new(); |
| self->plugin_list = fu_plugin_list_new(); |
| self->plugin_filter = g_ptr_array_new_with_free_func(g_free); |
| self->host_security_attrs = fu_security_attrs_new(); |
| self->local_monitors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| self->search_queries = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
| self->acquiesce_loop = g_main_loop_new(NULL, FALSE); |
| self->device_changed_allowlist = |
| g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
| #ifdef HAVE_PASSIM |
| self->passim_client = passim_client_new(); |
| #endif |
| |
| /* register /org/freedesktop/fwupd globally */ |
| g_resources_register(fu_get_resource()); |
| } |
| |
| static void |
| fu_engine_finalize(GObject *obj) |
| { |
| FuEngine *self = FU_ENGINE(obj); |
| |
| for (guint i = 0; i < self->local_monitors->len; i++) { |
| GFileMonitor *monitor = g_ptr_array_index(self->local_monitors, i); |
| g_file_monitor_cancel(monitor); |
| } |
| |
| if (self->silo != NULL) |
| g_object_unref(self->silo); |
| if (self->query_component_by_guid != NULL) |
| g_object_unref(self->query_component_by_guid); |
| if (self->query_container_checksum1 != NULL) |
| g_object_unref(self->query_container_checksum1); |
| if (self->query_container_checksum2 != NULL) |
| g_object_unref(self->query_container_checksum2); |
| if (self->query_tag_by_guid_version != NULL) |
| g_object_unref(self->query_tag_by_guid_version); |
| if (self->approved_firmware != NULL) |
| g_hash_table_unref(self->approved_firmware); |
| if (self->blocked_firmware != NULL) |
| g_hash_table_unref(self->blocked_firmware); |
| if (self->acquiesce_id != 0) |
| g_source_remove(self->acquiesce_id); |
| if (self->update_motd_id != 0) |
| g_source_remove(self->update_motd_id); |
| if (self->emulation != NULL) |
| g_object_unref(self->emulation); |
| #ifdef HAVE_PASSIM |
| if (self->passim_client != NULL) |
| g_object_unref(self->passim_client); |
| #endif |
| g_main_loop_unref(self->acquiesce_loop); |
| |
| g_free(self->host_machine_id); |
| g_object_unref(self->host_security_attrs); |
| g_object_unref(self->idle); |
| g_object_unref(self->config); |
| g_object_unref(self->remote_list); |
| g_object_unref(self->history); |
| g_object_unref(self->device_list); |
| g_object_unref(self->jcat_context); |
| g_ptr_array_unref(self->plugin_filter); |
| g_ptr_array_unref(self->local_monitors); |
| g_ptr_array_unref(self->search_queries); |
| g_hash_table_unref(self->device_changed_allowlist); |
| g_object_unref(self->plugin_list); |
| |
| G_OBJECT_CLASS(fu_engine_parent_class)->finalize(obj); |
| } |
| |
| FuEngine * |
| fu_engine_new(FuContext *ctx) |
| { |
| return FU_ENGINE(g_object_new(FU_TYPE_ENGINE, "context", ctx, NULL)); |
| } |