/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2.0.21
src/fu-engine-emulator.c
396 строк
12 KB
Richard Hughes
Record the system state for each composite emulation
17 ноя 2025, 19:59
17 ноя 2025, 19:59
1f2293c
Код
Авторство
О чём код?
/* * Copyright 2024 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include "fu-archive.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-engine-emulator.h" struct _FuEngineEmulator { GObject parent_instance; FuEngine *engine; GHashTable *phase_blobs; /* (element-type utf-8 GBytes) */ }; G_DEFINE_TYPE(FuEngineEmulator, fu_engine_emulator, G_TYPE_OBJECT) enum { PROP_0, PROP_ENGINE, PROP_LAST }; /* [composite_cnt:]{phase}[-write_cnt].json */ static gchar * fu_engine_emulator_phase_to_filename(guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt) { g_autoptr(GString) fn = g_string_new(NULL); if (composite_cnt != 0) g_string_append_printf(fn, "%u:", composite_cnt); g_string_append(fn, fu_engine_emulator_phase_to_string(phase)); if (write_cnt != FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT) g_string_append_printf(fn, "-%u", write_cnt); g_string_append(fn, ".json"); return g_string_free(g_steal_pointer(&fn), FALSE); } gboolean fu_engine_emulator_save(FuEngineEmulator *self, GOutputStream *stream, GError **error) { GHashTableIter iter; gboolean got_json = FALSE; gpointer key; gpointer value; g_autoptr(GByteArray) buf = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(FuArchive) archive = fu_archive_new(NULL, FU_ARCHIVE_FLAG_NONE, NULL); g_return_val_if_fail(FU_IS_ENGINE_EMULATOR(self), FALSE); g_return_val_if_fail(G_IS_OUTPUT_STREAM(stream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ g_hash_table_iter_init(&iter, self->phase_blobs); while (g_hash_table_iter_next(&iter, &key, &value)) { fu_archive_add_entry(archive, (const gchar *)key, (GBytes *)value); got_json = TRUE; } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data, perhaps no devices have been added?"); return FALSE; } /* write */ buf = fu_archive_write(archive, FU_ARCHIVE_FORMAT_ZIP, FU_ARCHIVE_COMPRESSION_GZIP, error); if (buf == NULL) return FALSE; blob = g_byte_array_free_to_bytes(g_steal_pointer(&buf)); /* nocheck:blocked */ if (!fu_output_stream_write_bytes(stream, blob, NULL, error)) return FALSE; if (!g_output_stream_flush(stream, NULL, error)) { fwupd_error_convert(error); return FALSE; } /* success */ g_hash_table_remove_all(self->phase_blobs); return TRUE; } static gboolean fu_engine_emulator_load_json_blob(FuEngineEmulator *self, GBytes *json_blob, GError **error) { GPtrArray *backends = fu_context_get_backends(fu_engine_get_context(self->engine)); JsonNode *root; g_autoptr(JsonParser) parser = json_parser_new(); /* parse */ if (!json_parser_load_from_data(parser, g_bytes_get_data(json_blob, NULL), g_bytes_get_size(json_blob), error)) return FALSE; /* load into all backends */ root = json_parser_get_root(parser); for (guint i = 0; i < backends->len; i++) { FuBackend *backend = g_ptr_array_index(backends, i); if (!fwupd_codec_from_json(FWUPD_CODEC(backend), root, error)) return FALSE; } /* success */ return TRUE; } gboolean fu_engine_emulator_load_phase(FuEngineEmulator *self, guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt, GError **error) { GBytes *json_blob; g_autofree gchar *fn = NULL; fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); json_blob = g_hash_table_lookup(self->phase_blobs, fn); if (json_blob == NULL) { g_debug("emulator not loading %s, as not found", fn); return TRUE; } g_debug("emulator loading %s", fn); return fu_engine_emulator_load_json_blob(self, json_blob, error); } static void fu_engine_emulator_to_json(FuEngineEmulator *self, GPtrArray *devices, JsonBuilder *json_builder) { /* not always correct, but we want to remain compatible with all the old emulation files */ json_builder_begin_object(json_builder); fwupd_codec_json_append(json_builder, "FwupdVersion", PACKAGE_VERSION); json_builder_set_member_name(json_builder, "UsbDevices"); json_builder_begin_array(json_builder); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); /* interesting? */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) continue; json_builder_begin_object(json_builder); fu_device_add_json(device, json_builder, FWUPD_CODEC_FLAG_NONE); json_builder_end_object(json_builder); } json_builder_end_array(json_builder); json_builder_end_object(json_builder); /* we've recorded these, now drop them */ 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_EMULATION_TAG)) continue; fu_device_clear_events(device); } } gboolean fu_engine_emulator_save_phase(FuEngineEmulator *self, guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt, GError **error) { GBytes *blob_old; g_autofree gchar *blob_new_safe = NULL; g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob_new = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GOutputStream) ostream = g_memory_output_stream_new_resizable(); g_autoptr(JsonBuilder) json_builder = json_builder_new(); g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* all devices in all backends */ devices = fu_engine_get_devices(self->engine, error); if (devices == NULL) return FALSE; fu_engine_emulator_to_json(self, devices, json_builder); json_root = json_builder_get_root(json_builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); g_debug("saving %s", fn); blob_old = g_hash_table_lookup(self->phase_blobs, fn); if (!json_generator_to_stream(json_generator, ostream, NULL, error)) return FALSE; if (!g_output_stream_close(ostream, NULL, error)) return FALSE; blob_new = g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(ostream)); if (g_bytes_get_size(blob_new) == 0) { g_info("no data for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); return TRUE; } if (blob_old != NULL && g_bytes_compare(blob_old, blob_new) == 0) { g_info("JSON unchanged for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); return TRUE; } blob_new_safe = fu_strsafe_bytes(blob_new, 8000); g_info("JSON %s for phase %s [%u]: %s…", blob_old == NULL ? "added" : "changed", fu_engine_emulator_phase_to_string(phase), write_cnt, blob_new_safe); g_hash_table_insert(self->phase_blobs, g_steal_pointer(&fn), g_steal_pointer(&blob_new)); /* success */ return TRUE; } static gboolean fu_engine_emulator_load_phases(FuEngineEmulator *self, FuArchive *archive, guint composite_cnt, guint write_cnt, gboolean *got_json, GError **error) { for (FuEngineEmulatorPhase phase = FU_ENGINE_EMULATOR_PHASE_SETUP; phase < FU_ENGINE_EMULATOR_PHASE_LAST; phase++) { g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob = NULL; /* not found */ fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); blob = fu_archive_lookup_by_fn(archive, fn, NULL); if (blob == NULL || g_bytes_get_size(blob) == 0) continue; *got_json = TRUE; g_info("emulation for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); if (composite_cnt == 0 && write_cnt == FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT && phase == FU_ENGINE_EMULATOR_PHASE_SETUP) { if (!fu_engine_emulator_load_json_blob(self, blob, error)) return FALSE; } else { g_hash_table_insert(self->phase_blobs, g_steal_pointer(&fn), g_steal_pointer(&blob)); } } /* success */ return TRUE; } gboolean fu_engine_emulator_load(FuEngineEmulator *self, GInputStream *stream, GError **error) { gboolean got_json = FALSE; const gchar *json_empty = "{\"UsbDevices\":[]}"; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) json_blob = g_bytes_new_static(json_empty, strlen(json_empty)); g_autoptr(GError) error_archive = NULL; g_return_val_if_fail(FU_IS_ENGINE_EMULATOR(self), FALSE); g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* unload any existing devices */ if (!fu_engine_emulator_load_json_blob(self, json_blob, error)) return FALSE; g_hash_table_remove_all(self->phase_blobs); /* load archive */ archive = fu_archive_new_stream(stream, FU_ARCHIVE_FLAG_NONE, &error_archive); if (archive == NULL) { g_autoptr(GBytes) blob = NULL; g_debug("no archive found, using JSON as phase setup: %s", error_archive->message); blob = fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error); if (blob == NULL) return FALSE; return fu_engine_emulator_load_json_blob(self, blob, error); } /* load JSON files from archive */ for (guint composite_cnt = 0; composite_cnt < FU_ENGINE_EMULATOR_COMPOSITE_MAX; composite_cnt++) { for (guint write_cnt = 0; write_cnt < FU_ENGINE_EMULATOR_WRITE_COUNT_MAX; write_cnt++) { if (!fu_engine_emulator_load_phases(self, archive, composite_cnt, write_cnt, &got_json, error)) return FALSE; } } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data found in archive"); return FALSE; } /* success */ return TRUE; } static void fu_engine_emulator_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(object); switch (prop_id) { case PROP_ENGINE: g_value_set_object(value, self->engine); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_emulator_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(object); switch (prop_id) { case PROP_ENGINE: self->engine = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_emulator_init(FuEngineEmulator *self) { self->phase_blobs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref); } static void fu_engine_emulator_finalize(GObject *obj) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(obj); g_hash_table_unref(self->phase_blobs); G_OBJECT_CLASS(fu_engine_emulator_parent_class)->finalize(obj); } static void fu_engine_emulator_dispose(GObject *obj) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(obj); g_clear_object(&self->engine); G_OBJECT_CLASS(fu_engine_emulator_parent_class)->dispose(obj); } static void fu_engine_emulator_class_init(FuEngineEmulatorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_engine_emulator_finalize; object_class->dispose = fu_engine_emulator_dispose; object_class->get_property = fu_engine_emulator_get_property; object_class->set_property = fu_engine_emulator_set_property; pspec = g_param_spec_object("engine", NULL, NULL, FU_TYPE_ENGINE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ENGINE, pspec); } FuEngineEmulator * fu_engine_emulator_new(FuEngine *engine) { return FU_ENGINE_EMULATOR(g_object_new(FU_TYPE_ENGINE_EMULATOR, "engine", engine, NULL)); }