/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/fu-engine-helper.c
640 строк
19 KB
Richard Hughes
trivial: Fix clang-tidy 'readability-isolate-declaration'
10 июн 2026, 15:03
10 июн 2026, 15:03
e7d238c
Код
Авторство
О чём код?
/* * Copyright 2020 Mario Limonciello <mario.limonciello@dell.com> * Copyright 2022 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include <fwupdplugin.h> #include <glib/gi18n.h> #include "fu-cabinet.h" #include "fu-context-private.h" #include "fu-engine-helper.h" #include "fu-engine.h" #include "fu-usb-device-fw-ds20.h" #include "fu-usb-device-ms-ds20.h" #define FU_ENGINE_BKC_MAX_TAGS 100 #define FU_ENGINE_BKC_MAX_RELEASES 1000 void fu_engine_add_firmware_gtypes(FuEngine *self) { FuContext *ctx = fu_engine_get_context(self); fu_context_add_firmware_gtypes(ctx); fu_context_add_firmware_gtype(ctx, FU_TYPE_CABINET); } static FwupdRelease * fu_engine_get_release_with_tag(FuEngine *self, FuEngineRequest *request, FwupdDevice *dev, const gchar *host_bkc, GError **error) { g_autoptr(GPtrArray) rels = NULL; g_auto(GStrv) host_bkcs = NULL; /* find the newest release that matches */ rels = fu_engine_get_releases(self, request, fwupd_device_get_id(dev), error); if (rels == NULL) return NULL; /* sanity check: reject unreasonable release counts */ if (rels->len > FU_ENGINE_BKC_MAX_RELEASES) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "device has unreasonable release count: %u (max %u)", rels->len, (guint)FU_ENGINE_BKC_MAX_RELEASES); return NULL; } host_bkcs = g_strsplit(host_bkc, ",", FU_ENGINE_BKC_MAX_TAGS); if (g_strv_length(host_bkcs) == FU_ENGINE_BKC_MAX_TAGS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "host has unreasonable BKC count: %u", (guint)FU_ENGINE_BKC_MAX_TAGS); return NULL; } for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); for (guint j = 0; host_bkcs[j] != NULL; j++) { if (fwupd_release_has_tag(rel, host_bkcs[j])) return g_object_ref(rel); } } /* no match */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no matching releases for device"); return NULL; } gboolean fu_engine_update_motd(FuEngine *self, GError **error) { FuContext *ctx = fu_engine_get_context(self); guint upgrade_count = 0; guint sync_count = 0; guint reboot_count = 0; g_autoptr(FuEngineRequest) request = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) str = g_string_new(NULL); g_autofree gchar *host_bkc = fu_context_get_config_str(ctx, "HostBkc"); g_autofree gchar *target = NULL; /* a subset of what fwupdmgr can do */ request = fu_engine_request_new(NULL); fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION | FWUPD_FEATURE_FLAG_UPDATE_ACTION); /* get devices from daemon, we even want to know if it's nothing */ devices = fu_engine_get_devices(self, NULL); if (devices != NULL) { for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) rels = NULL; /* check if device needs reboot to complete update */ if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { reboot_count++; continue; } /* skip devices with failed updates */ if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED || fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { continue; } /* get the releases for this device */ if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; rels = fu_engine_get_upgrades(self, request, fwupd_device_get_id(dev), NULL); if (rels == NULL) continue; upgrade_count++; } if (host_bkc != NULL) { for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(FwupdRelease) rel = NULL; /* skip devices with failed updates */ if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED || fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { continue; } if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; rel = fu_engine_get_release_with_tag(self, request, dev, host_bkc, NULL); if (rel == NULL) continue; if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) != 0) sync_count++; } } } /* if running under systemd unit, use the directory as a base */ if (g_getenv("RUNTIME_DIRECTORY") != NULL) { target = g_build_filename(g_getenv("RUNTIME_DIRECTORY"), MOTD_FILE, NULL); /* otherwise use the cache directory */ } else { target = fu_context_build_filename(ctx, error, FU_PATH_KIND_CACHEDIR_PKG, MOTD_DIR, MOTD_FILE, NULL); if (target == NULL) return FALSE; } /* create the directory and file, even if zero devices; we want an empty file then */ if (!fu_path_mkdir_parent(target, error)) return FALSE; /* nag about reboot first, then syncing or updating, but never both */ if (reboot_count > 0) { g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ ngettext("%u device has been updated and needs a reboot.", "%u devices have been updated and need a reboot.", reboot_count), reboot_count); g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ _("Reboot to complete the update.")); g_string_append(str, "\n\n"); } else if (sync_count > 0) { g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ ngettext("%u device is not the best known configuration.", "%u devices are not the best known configuration.", sync_count), sync_count); g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD -- %1 is the * command name, e.g. `fwupdmgr sync` */ _("Run `%s` to complete this action."), "fwupdmgr sync"); g_string_append(str, "\n\n"); } else if (upgrade_count > 0) { g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ ngettext("%u device has a firmware upgrade available.", "%u devices have a firmware upgrade available.", upgrade_count), upgrade_count); g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD -- %1 is the * command name, e.g. `fwupdmgr get-upgrades` */ _("Run `%s` for more information."), "fwupdmgr get-upgrades"); g_string_append(str, "\n\n"); } /* success, with an empty file if nothing to say */ g_debug("writing motd target %s", target); return g_file_set_contents(target, str->str, str->len, error); } gboolean fu_engine_update_devices_file(FuEngine *self, GError **error) { FuContext *ctx = fu_engine_get_context(self); FwupdCodecFlags flags = FWUPD_CODEC_FLAG_NONE; g_autoptr(FwupdJsonObject) json_obj = fwupd_json_object_new(); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) data = NULL; g_autofree gchar *target = NULL; target = fu_context_build_filename(ctx, &error_local, FU_PATH_KIND_CACHEDIR_PKG, "devices.json", NULL); if (target == NULL) { g_debug("ignoring update devices file: %s", error_local->message); return TRUE; } if (fu_context_get_config_bool(ctx, "ShowDevicePrivate")) flags |= FWUPD_CODEC_FLAG_TRUSTED; devices = fu_engine_get_devices(self, NULL); if (devices != NULL) fwupd_codec_array_to_json(devices, "Devices", json_obj, flags); data = fwupd_json_object_to_string(json_obj, FWUPD_JSON_EXPORT_FLAG_INDENT); return g_file_set_contents(target, data->str, -1, error); } static void fu_engine_integrity_add_measurement(GHashTable *self, const gchar *id, GBytes *blob) { g_autofree gchar *csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, blob); g_hash_table_insert(self, g_strdup(id), g_steal_pointer(&csum)); } static void fu_engine_integrity_measure_acpi(FuContext *ctx, GHashTable *self) { const gchar *tables[] = { "SLIC", "MSDM", "TPM2", }; for (guint i = 0; i < G_N_ELEMENTS(tables); i++) { g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob = NULL; fn = fu_context_build_filename(ctx, NULL, FU_PATH_KIND_ACPI_TABLES, tables[i], NULL); if (fn == NULL) continue; blob = fu_bytes_get_contents(fn, NULL); if (blob != NULL && g_bytes_get_size(blob) > 0) { g_autofree gchar *id = g_strdup_printf("ACPI:%s", tables[i]); fu_engine_integrity_add_measurement(self, id, blob); } } } static void fu_engine_integrity_measure_uefi(FuContext *ctx, GHashTable *self) { FuEfivars *efivars = fu_context_get_efivars(ctx); struct { const gchar *guid; const gchar *name; } keys[] = { {FU_EFIVARS_GUID_EFI_GLOBAL, "BootCurrent"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "KEK"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "KEKDefault"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "OsIndications"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "OsIndicationsSupported"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "PK"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "PKDefault"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "SecureBoot"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "SetupMode"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "SignatureSupport"}, {FU_EFIVARS_GUID_EFI_GLOBAL, "VendorKeys"}, {FU_EFIVARS_GUID_SECURITY_DATABASE, "db"}, {FU_EFIVARS_GUID_SECURITY_DATABASE, "dbDefault"}, {FU_EFIVARS_GUID_SECURITY_DATABASE, "dbx"}, {FU_EFIVARS_GUID_SECURITY_DATABASE, "dbxDefault"}, }; /* important keys */ for (guint i = 0; i < G_N_ELEMENTS(keys); i++) { g_autoptr(GBytes) blob = fu_efivars_get_data_bytes(efivars, keys[i].guid, keys[i].name, NULL, NULL); if (blob != NULL) { g_autofree gchar *id = g_strdup_printf("UEFI:%s", keys[i].name); fu_engine_integrity_add_measurement(self, id, blob); } } /* UEFI Boot#### */ for (guint i = 0; i < 0xFF; i++) { g_autoptr(GBytes) blob = fu_efivars_get_boot_data(efivars, i, NULL); if (blob != NULL && g_bytes_get_size(blob) > 0) { const guint8 needle[] = "f\0w\0u\0p\0d"; g_autofree gchar *id = g_strdup_printf("UEFI:Boot%04X", i); if (fu_memmem_safe(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), needle, sizeof(needle), NULL, NULL)) { g_debug("skipping %s as fwupd found", id); continue; } fu_engine_integrity_add_measurement(self, id, blob); } } } GHashTable * fu_engine_integrity_new(FuContext *ctx, GError **error) { g_autoptr(GHashTable) self = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fu_engine_integrity_measure_uefi(ctx, self); fu_engine_integrity_measure_acpi(ctx, self); /* nothing of use */ if (g_hash_table_size(self) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no measurements"); return NULL; } /* success */ return g_steal_pointer(&self); } gchar * fu_engine_integrity_to_string(GHashTable *self) { GHashTableIter iter; gpointer key; gpointer value; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(self != NULL, NULL); /* sanity check */ if (g_hash_table_size(self) == 0) return NULL; /* build into KV array */ g_hash_table_iter_init(&iter, self); while (g_hash_table_iter_next(&iter, &key, &value)) { g_ptr_array_add(array, g_strdup_printf("%s=%s", (const gchar *)key, (const gchar *)value)); } return fu_strjoin("\n", array); } static const GError * fu_engine_error_array_find(GPtrArray *errors, FwupdError error_code) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); if (g_error_matches(error, FWUPD_ERROR, error_code)) return error; } return NULL; } static guint fu_engine_error_array_count(GPtrArray *errors, FwupdError error_code) { guint cnt = 0; for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); if (g_error_matches(error, FWUPD_ERROR, error_code)) cnt++; } return cnt; } static gboolean fu_engine_error_array_matches_any(GPtrArray *errors, FwupdError *error_codes) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); gboolean matches_any = FALSE; for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) { if (g_error_matches(error, FWUPD_ERROR, error_codes[i])) { matches_any = TRUE; break; } } if (!matches_any) return FALSE; } return TRUE; } /** * fu_engine_error_array_get_best: * @errors: (element-type GError): array of errors * * Finds the 'best' error to show the user from a array of errors, creating a * completely bespoke error where required. * * Returns: (transfer full): a #GError, never %NULL **/ GError * fu_engine_error_array_get_best(GPtrArray *errors) { FwupdError err_prio[] = {FWUPD_ERROR_INVALID_FILE, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_INTERNAL, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_LAST}; FwupdError err_all_uptodate[] = {FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST}; FwupdError err_all_newer[] = {FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST}; /* are all the errors either GUID-not-matched or version-same? */ if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_SAME) > 1 && fu_engine_error_array_matches_any(errors, err_all_uptodate)) { return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable firmware is already installed"); } /* are all the errors either GUID-not-matched or version same or newer? */ if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_NEWER) > 1 && fu_engine_error_array_matches_any(errors, err_all_newer)) { return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable devices already have newer versions"); } /* get the most important single error */ for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) { const GError *error_tmp = fu_engine_error_array_find(errors, err_prio[i]); if (error_tmp != NULL) return g_error_copy(error_tmp); } /* fall back to something */ return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found"); } /** * fu_engine_build_machine_id: * @salt: (nullable): optional salt * @error: (nullable): optional return location for an error * * Gets a salted hash of the /etc/machine-id contents. This can be used to * identify a specific machine. It is not possible to recover the original * machine-id from the machine-hash. * * Returns: the SHA256 machine hash, or %NULL if the ID is not present **/ gchar * fu_engine_build_machine_id(const gchar *salt, GError **error) { const gchar *machine_id; gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(GChecksum) csum = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* in test mode */ machine_id = g_getenv("FWUPD_MACHINE_ID"); if (machine_id != NULL) { buf = g_strdup(machine_id); bufsz = strlen(buf); } else { const gchar *fn = NULL; g_autoptr(GPtrArray) fns = g_ptr_array_new_with_free_func(g_free); /* one of these has to exist */ g_ptr_array_add(fns, g_build_filename(FWUPD_SYSCONFDIR, "machine-id", NULL)); g_ptr_array_add( fns, g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "dbus", "machine-id", NULL)); g_ptr_array_add(fns, g_strdup("/etc/machine-id")); g_ptr_array_add(fns, g_strdup("/var/lib/dbus/machine-id")); g_ptr_array_add(fns, g_strdup("/var/db/dbus/machine-id")); /* this is the hardcoded path for homebrew, e.g. `sudo dbus-uuidgen --ensure` */ g_ptr_array_add(fns, g_strdup("/usr/local/var/lib/dbus/machine-id")); for (guint i = 0; i < fns->len; i++) { const gchar *fn_tmp = g_ptr_array_index(fns, i); if (g_file_test(fn_tmp, G_FILE_TEST_EXISTS)) { fn = fn_tmp; break; } } if (fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is not present"); return NULL; } if (!g_file_get_contents(fn, &buf, &bufsz, error)) return NULL; if (bufsz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is present but unset"); return NULL; } } csum = g_checksum_new(G_CHECKSUM_SHA256); if (salt != NULL) g_checksum_update(csum, (const guchar *)salt, (gssize)strlen(salt)); g_checksum_update(csum, (const guchar *)buf, (gssize)bufsz); return g_strdup(g_checksum_get_string(csum)); } static gboolean fu_engine_report_from_flags(FwupdReport *report, const gchar *flags_str, GError **error) { g_auto(GStrv) flags_strv = g_strsplit(flags_str, ",", -1); for (guint i = 0; flags_strv[i] != NULL; i++) { FwupdReportFlags flag = fwupd_report_flag_from_string(flags_strv[i]); if (flag == FWUPD_REPORT_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "report flag '%s' unknown", flags_strv[i]); return FALSE; } fwupd_report_add_flag(report, flag); } return TRUE; } FwupdReport * fu_engine_report_from_spec(const gchar *report_spec, GError **error) { g_auto(GStrv) parts = g_strsplit(report_spec, "&", -1); g_autoptr(FwupdReport) report = fwupd_report_new(); for (guint i = 0; parts[i] != NULL; i++) { g_autofree gchar *value = NULL; g_auto(GStrv) kv = g_strsplit(parts[i], "=", 2); if (g_strv_length(kv) != 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "failed to parse report specifier key=value %s", parts[i]); return NULL; } if (g_str_has_prefix(kv[1], "$")) value = g_get_os_info(kv[1] + 1); if (value == NULL) value = g_strdup(kv[1]); if (g_strcmp0(kv[0], "VendorId") == 0) { guint64 tmp = 0; if (g_strcmp0(value, "$OEM") == 0) { fwupd_report_add_flag(report, FWUPD_REPORT_FLAG_FROM_OEM); } else { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, FU_INTEGER_BASE_AUTO, error)) { g_prefix_error(error, "failed to parse '%s': ", value); return NULL; } fwupd_report_set_vendor_id(report, tmp); } } else if (g_strcmp0(kv[0], "DistroId") == 0) { fwupd_report_set_distro_id(report, value); } else if (g_strcmp0(kv[0], "DistroVariant") == 0) { fwupd_report_set_distro_variant(report, value); } else if (g_strcmp0(kv[0], "DistroVersion") == 0) { fwupd_report_set_distro_version(report, value); } else if (g_strcmp0(kv[0], "RemoteId") == 0) { fwupd_report_set_remote_id(report, value); } else if (g_strcmp0(kv[0], "Flags") == 0) { if (!fu_engine_report_from_flags(report, value, error)) return NULL; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "failed to parse report specifier key %s", kv[0]); return NULL; } } /* success */ return g_steal_pointer(&report); }