diff --git a/0001-dnf5-plugin.patch b/0001-dnf5-plugin.patch index 3e9a13f..3b4b355 100644 --- a/0001-dnf5-plugin.patch +++ b/0001-dnf5-plugin.patch @@ -1,5 +1,5 @@ -at commit 8a1f0ca6564157dd688c84ffd25af9747c1e038e -Date: Mon Aug 11 17:07:06 2025 +0200 +at commit 786bab5f1928920251ee9e93d51953ff9dd9faa4 +Date: Mon Sep 1 18:23:09 2025 +0200 diff --git a/meson_options.txt b/meson_options.txt index cd49cf529..27fa15e8b 100644 @@ -610,10 +610,10 @@ index 000000000..5d522468c +G_END_DECLS diff --git a/plugins/dnf5/gs-plugin-dnf5.c b/plugins/dnf5/gs-plugin-dnf5.c new file mode 100644 -index 000000000..88d7e25d8 +index 000000000..82f3e19a8 --- /dev/null +++ b/plugins/dnf5/gs-plugin-dnf5.c -@@ -0,0 +1,4275 @@ +@@ -0,0 +1,4589 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * @@ -1671,9 +1671,13 @@ index 000000000..88d7e25d8 + gchar *details; + gchar *accept_label; + gchar *key_id; ++ gchar *key_filename; ++ gchar *session_path; ++ GCancellable *cancellable; /* (owned) */ + GsPluginDnf5 *self; /* (owned) */ + GWeakRef repo_proxy_weakref; /* GsDnf5RpmRepo */ + gboolean interactive; ++ gboolean confirmed_by_repo; +} QuestionData; + +static void @@ -1685,6 +1689,9 @@ index 000000000..88d7e25d8 + g_free (data->details); + g_free (data->accept_label); + g_free (data->key_id); ++ g_free (data->key_filename); ++ g_free (data->session_path); ++ g_clear_object (&data->cancellable); + g_clear_object (&data->self); + g_weak_ref_clear (&data->repo_proxy_weakref); + g_free (data); @@ -1702,7 +1709,10 @@ index 000000000..88d7e25d8 + g_autoptr(GVariantBuilder) options_builder = NULL; + gboolean confirmed; + -+ confirmed = gs_plugin_ask_untrusted (GS_PLUGIN (data->self), data->title, data->msg, data->details, data->accept_label); ++ /* auto-accept new keys, just as PacakgeKit plugin used to do */ ++ confirmed = data->confirmed_by_repo; ++ if (!confirmed) ++ confirmed = gs_plugin_ask_untrusted (GS_PLUGIN (data->self), data->title, data->msg, data->details, data->accept_label); + + options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (options_builder, "{sv}", "interactive", @@ -1712,7 +1722,7 @@ index 000000000..88d7e25d8 + data->key_id, + confirmed, + g_variant_builder_end (options_builder), -+ NULL, ++ data->cancellable, + gs_dnf5_confirm_key_response_cb, + NULL); + } @@ -1720,10 +1730,184 @@ index 000000000..88d7e25d8 + return G_SOURCE_REMOVE; +} + ++static gboolean ++gs_dnf5_read_package_repo_ids_cb (GsPluginDnf5 *self, ++ GVariant *package_array, ++ gpointer user_data, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ GPtrArray **out_repo_ids = user_data; ++ g_autoptr(GVariantDict) dict = NULL; ++ g_autoptr(GVariant) value = NULL; ++ ++ dict = g_variant_dict_new (package_array); ++ ++ value = g_variant_dict_lookup_value (dict, "from_repo_id", G_VARIANT_TYPE_STRING); ++ if (value != NULL) { ++ const gchar *repo_id_const = g_variant_get_string (value, NULL); ++ if (repo_id_const != NULL && *repo_id_const != '\0') { ++ if (*out_repo_ids == NULL) ++ *out_repo_ids = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (*out_repo_ids, g_strdup (repo_id_const)); ++ } ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++gs_dnf5_read_repo_gpg_info_cb (GsPluginDnf5 *self, ++ GVariant *package_array, ++ gpointer user_data, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ guint *out_n_good = user_data; ++ g_autoptr(GVariantDict) dict = NULL; ++ g_autoptr(GVariant) id_value = NULL; ++ g_autoptr(GVariant) gpgcheck_value = NULL; ++ ++ dict = g_variant_dict_new (package_array); ++ ++ id_value = g_variant_dict_lookup_value (dict, "id", G_VARIANT_TYPE_STRING); ++ gpgcheck_value = g_variant_dict_lookup_value (dict, "gpgcheck", G_VARIANT_TYPE_BOOLEAN); ++ ++ if (id_value != NULL && gpgcheck_value != NULL) { ++ gboolean gpgcheck = g_variant_get_boolean (gpgcheck_value); ++ ++ if (gpgcheck) ++ *out_n_good = (*out_n_good) + 1; ++ } ++ ++ return TRUE; ++} ++ ++static gpointer ++gs_dnf5_check_key_source_thread (gpointer user_data) ++{ ++ QuestionData *data = user_data; ++ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; ++ g_autoptr(GPtrArray) repo_ids = NULL; ++ g_autoptr(GError) local_error = NULL; ++ gboolean success; ++ ++ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (data->self->connection, ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, ++ GS_DNF5_INTERFACE_RPM_DNF, ++ data->session_path, ++ data->cancellable, ++ &local_error); ++ success = rpm_proxy != NULL; ++ if (!success) { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); ++ } ++ ++ /* try to get whether the key comes from any package */ ++ if (success) { ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ g_autoptr(GVariantBuilder) package_attrs_builder = NULL; ++ g_autoptr(GVariantBuilder) whatprovides_builder = NULL; ++ g_autoptr(GVariant) result = NULL; ++ ++ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); ++ ++ package_attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ g_variant_builder_add (package_attrs_builder, "s", "from_repo_id"); ++ ++ whatprovides_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ g_variant_builder_add (whatprovides_builder, "s", data->key_filename); ++ ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "package_attrs", ++ g_variant_builder_end (package_attrs_builder)); ++ g_variant_builder_add (options_builder, "{sv}", "whatprovides", ++ g_variant_builder_end (whatprovides_builder)); ++ g_variant_builder_add (options_builder, "{sv}", "scope", ++ g_variant_new_string ("installed")); ++ ++ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, ++ g_variant_builder_end (options_builder), ++ &result, ++ data->cancellable, ++ &local_error); ++ ++ if (success) { ++ success = gs_dnf5_foreach_item (data->self, result, gs_dnf5_read_package_repo_ids_cb, &repo_ids, data->cancellable, &local_error); ++ } else { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to call rpm::list: "); ++ } ++ } ++ ++ if (success && repo_ids != NULL) { ++ g_autoptr(GVariantBuilder) patterns_builder = NULL; ++ guint n_verify = 0; ++ ++ for (guint i = 0; i < repo_ids->len; i++) { ++ const gchar *repo_id = g_ptr_array_index (repo_ids, i); ++ /* those are special repos, like `@commandline` */ ++ if (*repo_id != '@') { ++ if (patterns_builder == NULL) ++ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ g_variant_builder_add (patterns_builder, "s", repo_id); ++ n_verify++; ++ } ++ } ++ ++ if (patterns_builder != NULL) { ++ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; ++ ++ repo_proxy = g_weak_ref_get (&data->repo_proxy_weakref); ++ if (repo_proxy != NULL) { ++ g_autoptr(GVariantBuilder) repo_attrs_builder = NULL; ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ g_autoptr(GVariant) result = NULL; ++ ++ repo_attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ g_variant_builder_add (repo_attrs_builder, "s", "id"); ++ g_variant_builder_add (repo_attrs_builder, "s", "gpgcheck"); ++ ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "patterns", ++ g_variant_builder_end (patterns_builder)); ++ g_variant_builder_add (options_builder, "{sv}", "repo_attrs", ++ g_variant_builder_end (repo_attrs_builder)); ++ ++ success = gs_dnf5_rpm_repo_call_list_sync (repo_proxy, ++ g_variant_builder_end (options_builder), ++ &result, ++ data->cancellable, ++ &local_error); ++ ++ if (success) { ++ guint n_good = 0; ++ ++ success = gs_dnf5_foreach_item (data->self, result, gs_dnf5_read_repo_gpg_info_cb, &n_good, data->cancellable, &local_error); ++ ++ data->confirmed_by_repo = success && n_verify == n_good; ++ } else { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to call repo::list: "); ++ } ++ } else { ++ success = FALSE; ++ g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, "Repo proxy vanished"); ++ } ++ } ++ } ++ ++ g_idle_add_full (G_PRIORITY_HIGH_IDLE, gs_dnf5_ask_question_idle_cb, data, (GDestroyNotify) question_data_free); ++ ++ return NULL; ++} ++ +typedef struct { + GsPluginDnf5 *self; /* (not owned) */ + const gchar *session_path; /* (not owned) */ + GsDnf5RpmRepo *repo_proxy; /* (not owned) */ ++ GCancellable *cancellable; /* (not owned) */ + gboolean interactive; +} RepoKeyImportData; + @@ -1798,11 +1982,18 @@ index 000000000..88d7e25d8 + data->details = g_string_free (details, FALSE); + data->accept_label = g_strdup (_("_Import Key")); + data->key_id = g_strdup (arg_key_id); ++ data->key_filename = g_steal_pointer (&key_filename); ++ data->session_path = g_strdup (repo_key_import_data->session_path); + data->self = g_object_ref (repo_key_import_data->self); + data->interactive = repo_key_import_data->interactive; ++ data->confirmed_by_repo = FALSE; ++ g_set_object (&data->cancellable, repo_key_import_data->cancellable); + g_weak_ref_init (&data->repo_proxy_weakref, repo_key_import_data->repo_proxy); + -+ g_idle_add_full (G_PRIORITY_HIGH_IDLE, gs_dnf5_ask_question_idle_cb, data, (GDestroyNotify) question_data_free); ++ if (data->key_filename != NULL) ++ g_thread_unref (g_thread_new ("gs-dnf5-check-key-source_thread", gs_dnf5_check_key_source_thread, data)); ++ else ++ g_idle_add_full (G_PRIORITY_HIGH_IDLE, gs_dnf5_ask_question_idle_cb, data, (GDestroyNotify) question_data_free); +} + +static void @@ -1884,7 +2075,8 @@ index 000000000..88d7e25d8 + GS_DNF5_TRANSACTION_FLAG_OFFLINE = 1 << 0, + GS_DNF5_TRANSACTION_FLAG_RESOLVE_ONLY = 1 << 1, + GS_DNF5_TRANSACTION_FLAG_DISALLOW_ERASING = 1 << 2, /* negative flag, to not have it everywhere */ -+ GS_DNF5_TRANSACTION_FLAG_INTERACTIVE = 1 << 3 ++ GS_DNF5_TRANSACTION_FLAG_INTERACTIVE = 1 << 3, ++ GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY = 1 << 4 +} GsDnf5TransactionFlags; + +static gboolean @@ -1963,6 +2155,7 @@ index 000000000..88d7e25d8 + repo_key_import_data.session_path = session_path; + repo_key_import_data.repo_proxy = repo_proxy; + repo_key_import_data.interactive = interactive; ++ repo_key_import_data.cancellable = cancellable; + repo_key_import_id = g_signal_connect (base_proxy, "repo_key_import_request", + G_CALLBACK (gs_dnf5_repo_key_import_request_cb), &repo_key_import_data); + @@ -2015,6 +2208,10 @@ index 000000000..88d7e25d8 + g_variant_builder_add (options_builder, "{sv}", "offline", + g_variant_new_boolean (TRUE)); + } ++ if ((flags & GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY) != 0) { ++ g_variant_builder_add (options_builder, "{sv}", "downloadonly", ++ g_variant_new_boolean (TRUE)); ++ } + g_variant_builder_add (options_builder, "{sv}", "interactive", + g_variant_new_boolean (interactive)); + @@ -2570,54 +2767,57 @@ index 000000000..88d7e25d8 + g_prefix_error_literal (&local_error, "Failed to call Rpm::list: "); + } + } else if (is_historical_update == GS_APP_QUERY_TRISTATE_TRUE) { -+ g_autoptr(GsDnf5History) history_proxy = NULL; ++ g_autoptr(GSettings) settings = NULL; ++ gint64 timestamp; + -+ history_proxy = gs_dnf5_history_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = history_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create History proxy: "); -+ } ++ /* use the option the GUI part uses */ ++ settings = g_settings_new ("org.gnome.software"); ++ timestamp = (gint64) g_settings_get_uint64 (settings, "packagekit-historical-updates-timestamp"); + -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ g_autoptr(GSettings) settings; -+ gint64 timestamp; ++ if (timestamp > 0) { ++ g_autoptr(GsDnf5History) history_proxy = NULL; + -+ /* use the option the GUI part uses */ -+ settings = g_settings_new ("org.gnome.software"); -+ timestamp = g_settings_get_int64 (settings, "install-timestamp"); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "since", -+ g_variant_new_int64 (timestamp)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ -+ success = gs_dnf5_history_call_recent_changes_sync (history_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); ++ history_proxy = gs_dnf5_history_proxy_new_sync (self->connection, ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, ++ GS_DNF5_INTERFACE_RPM_DNF, ++ session_path, ++ cancellable, ++ &local_error); ++ success = history_proxy != NULL; ++ if (!success) { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to create History proxy: "); ++ } + + if (success) { -+ ReadPackageData rpd = { 0, }; ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ g_autoptr(GVariant) result = NULL; + -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_UNKNOWN; -+ rpd.nevra_to_app = NULL; -+ rpd.timestamp = timestamp; ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "since", ++ g_variant_new_int64 (timestamp)); ++ g_variant_builder_add (options_builder, "{sv}", "package_attrs", ++ gs_dnf5_dup_package_attrs ()); + -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_history_items_cb, &rpd, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call recent_changes: "); ++ success = gs_dnf5_history_call_recent_changes_sync (history_proxy, ++ g_variant_builder_end (options_builder), ++ &result, ++ cancellable, ++ &local_error); ++ ++ if (success) { ++ ReadPackageData rpd = { 0, }; ++ ++ rpd.list = list; ++ rpd.set_state = GS_APP_STATE_UNKNOWN; ++ rpd.nevra_to_app = NULL; ++ rpd.timestamp = timestamp; ++ ++ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_history_items_cb, &rpd, cancellable, &local_error); ++ } else { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to call recent_changes: "); ++ } + } + } + } else if (gs_component_kind_array_contains (component_kinds, AS_COMPONENT_KIND_REPOSITORY)) { @@ -3574,6 +3774,7 @@ index 000000000..88d7e25d8 + GsAppList *list, + gboolean is_install, + gboolean interactive, ++ gboolean can_apply, + GsPluginEventCallback event_callback, + void *event_user_data, + GCancellable *cancellable, @@ -3698,7 +3899,8 @@ index 000000000..88d7e25d8 + success = rpm_proxy != NULL; + if (success) { + g_autoptr(GVariantBuilder) options_builder = NULL; -+ GsDnf5TransactionFlags flags = interactive ? GS_DNF5_TRANSACTION_FLAG_INTERACTIVE : GS_DNF5_TRANSACTION_FLAG_NONE; ++ GsDnf5TransactionFlags flags = (interactive ? GS_DNF5_TRANSACTION_FLAG_INTERACTIVE : GS_DNF5_TRANSACTION_FLAG_NONE) | ++ (!can_apply ? GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY : GS_DNF5_TRANSACTION_FLAG_NONE); + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); + @@ -3730,7 +3932,7 @@ index 000000000..88d7e25d8 + success = success && gs_dnf5_run_transaction (self, session_path, rpm_proxy, progress_app, progress_list, + flags | (is_install ? 0 : GS_DNF5_TRANSACTION_FLAG_OFFLINE), + NULL, cancellable, error); -+ if (success && !is_install) { ++ if (success && !is_install && can_apply) { + /* ensure the update is finished with the 'reboot' action */ + g_autoptr(GsDnf5Offline) offline_proxy = NULL; + @@ -3767,6 +3969,10 @@ index 000000000..88d7e25d8 + } else if (!success) { + gs_dnf5_convert_error (error); + g_prefix_error_literal (error, "Failed to set finish action: "); ++ } else { ++ g_autoptr(GSettings) settings = g_settings_new ("org.gnome.software"); ++ /* to check anything installed/updated after today */ ++ g_settings_set (settings, "packagekit-historical-updates-timestamp", "t", g_get_real_time () / G_USEC_PER_SEC); + } + } else { + gs_dnf5_convert_error (error); @@ -3850,6 +4056,7 @@ index 000000000..88d7e25d8 + + success = gs_dnf5_install_update (plugin, data->apps, TRUE, + (data->flags & GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) != 0, ++ (data->flags & GS_PLUGIN_INSTALL_APPS_FLAGS_NO_APPLY) == 0, + data->event_callback, data->event_user_data, + cancellable, &local_error); + @@ -4093,6 +4300,7 @@ index 000000000..88d7e25d8 + + success = gs_dnf5_install_update (plugin, data->apps, FALSE, + (data->flags & GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE) != 0, ++ (data->flags & GS_PLUGIN_UPDATE_APPS_FLAGS_NO_APPLY) == 0, + data->event_callback, data->event_user_data, + cancellable, &local_error); + @@ -4778,6 +4986,110 @@ index 000000000..88d7e25d8 + return g_task_propagate_boolean (G_TASK (result), error); +} + ++/* Run in @worker. */ ++static void ++gs_dnf5_cancel_offline_update_thread_cb (GTask *task, ++ gpointer source_object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); ++ GsPluginCancelOfflineUpdateData *data = task_data; ++ g_autofree gchar *session_path = NULL; ++ g_autoptr(GsAppList) list = NULL; ++ g_autoptr(GsDnf5SessionManager) session_manager = NULL; ++ g_autoptr(GsDnf5Offline) offline_proxy = NULL; ++ gboolean success; ++ g_autoptr(GError) local_error = NULL; ++ ++ assert_in_worker (self); ++ ++ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); ++ if (session_path == NULL) { ++ gs_dnf5_convert_error (&local_error); ++ g_task_return_error (task, g_steal_pointer (&local_error)); ++ return; ++ } ++ ++ offline_proxy = gs_dnf5_offline_proxy_new_sync (self->connection, ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, ++ GS_DNF5_INTERFACE_RPM_DNF, ++ session_path, ++ cancellable, ++ &local_error); ++ success = offline_proxy != NULL; ++ if (!success) { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to create Offline proxy: "); ++ } ++ ++ if (success) { ++ gboolean pending = FALSE; ++ g_autoptr(GVariant) transaction_status = NULL; ++ ++ if (gs_dnf5_offline_call_get_status_sync (offline_proxy, &pending, &transaction_status, cancellable, &local_error) && ++ pending && transaction_status != NULL) { ++ g_autoptr(GVariantBuilder) options_builder = NULL; ++ g_autofree gchar *op_error_msg = NULL; ++ gboolean op_success = FALSE; ++ ++ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (options_builder, "{sv}", "interactive", ++ g_variant_new_boolean ((data->flags & GS_PLUGIN_CANCEL_OFFLINE_UPDATE_FLAGS_INTERACTIVE) != 0)); ++ ++ success = gs_dnf5_offline_call_cancel_with_options_sync (offline_proxy, g_variant_builder_end (options_builder), ++ &op_success, &op_error_msg, cancellable, &local_error); ++ if (!success) { ++ gs_dnf5_convert_error (&local_error); ++ g_prefix_error_literal (&local_error, "Failed to call OfflineProxy::cancel: "); ++ } else if (!op_success) { ++ success = FALSE; ++ if (op_error_msg != NULL) { ++ g_set_error_literal (&local_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, op_error_msg); ++ g_prefix_error_literal (&local_error, "Failed to call OfflineProxy::cancel op: "); ++ } else { ++ g_set_error_literal (&local_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Failed to call OfflineProxy::cancel op"); ++ } ++ } ++ } ++ } ++ ++ gs_dnf5_close_session (self, session_manager, session_path); ++ ++ if (success) { ++ g_task_return_boolean (task, TRUE); ++ } else { ++ gs_dnf5_convert_error (&local_error); ++ g_task_return_error (task, g_steal_pointer (&local_error)); ++ } ++} ++ ++static void ++gs_plugin_dnf5_cancel_offline_update_async (GsPlugin *plugin, ++ GsPluginCancelOfflineUpdateFlags flags, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); ++ g_autoptr(GTask) task = NULL; ++ gboolean interactive = (flags & GS_PLUGIN_CANCEL_OFFLINE_UPDATE_FLAGS_INTERACTIVE) != 0; ++ ++ task = gs_plugin_cancel_offline_update_data_new_task (plugin, flags, cancellable, callback, user_data); ++ g_task_set_source_tag (task, gs_plugin_dnf5_cancel_offline_update_async); ++ ++ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), ++ gs_dnf5_cancel_offline_update_thread_cb, g_steal_pointer (&task)); ++} ++ ++static gboolean ++gs_plugin_dnf5_cancel_offline_update_finish (GsPlugin *plugin, ++ GAsyncResult *result, ++ GError **error) ++{ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ +static void +gs_plugin_dnf5_dispose (GObject *object) +{ @@ -4863,6 +5175,8 @@ index 000000000..88d7e25d8 + plugin_class->download_upgrade_finish = gs_plugin_dnf5_download_upgrade_finish; + plugin_class->trigger_upgrade_async = gs_plugin_dnf5_trigger_upgrade_async; + plugin_class->trigger_upgrade_finish = gs_plugin_dnf5_trigger_upgrade_finish; ++ plugin_class->cancel_offline_update_async = gs_plugin_dnf5_cancel_offline_update_async; ++ plugin_class->cancel_offline_update_finish = gs_plugin_dnf5_cancel_offline_update_finish; +} + +static void diff --git a/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch b/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch new file mode 100644 index 0000000..33140df --- /dev/null +++ b/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch @@ -0,0 +1,686 @@ +From 0c79d9fb08a348a45a18f15427c945a63b44982e Mon Sep 17 00:00:00 2001 +Date: Mon, 24 Nov 2025 16:06:51 +0100 +Subject: [PATCH 1/3] flatpak: Fix XbSilo lifetime in + gs_flatpak_refine_wildcard() + +The function uses pre-cached data in the hash tables, consisting of XbNode-s +from an XbSilo. The nodes are referenced, but the silos the node belongs to +is not referenced (by the XbNode). + +This change keeps the XbSilo alive as long as it's needed for the nodes +in the pre-cached data. It also does not call gs_flatpak_ref_silo() multiple +times for the same GsFlatpak instance, which could invalidate the XbNode-s +in the pre-cached data when a change in the silo or in the installation +had been received while the function was still processing the apps. + +Related downstream bug https://bugzilla.redhat.com/show_bug.cgi?id=2416542 +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/2900 +--- + plugins/flatpak/gs-flatpak.c | 18 ++++++++--- + plugins/flatpak/gs-flatpak.h | 4 +++ + plugins/flatpak/gs-plugin-flatpak.c | 49 ++++++++++++++++++++--------- + 3 files changed, 51 insertions(+), 20 deletions(-) + +diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c +index b409475c7..a40ab54fb 100644 +--- a/plugins/flatpak/gs-flatpak.c ++++ b/plugins/flatpak/gs-flatpak.c +@@ -3748,16 +3748,19 @@ gboolean + gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, + GsAppList *list, GsPluginRefineRequireFlags require_flags, + gboolean interactive, ++ XbSilo **inout_silo, ++ gchar **inout_silo_filename, ++ GHashTable **inout_installed_by_desktopid, + GHashTable **inout_components_by_id, + GHashTable **inout_components_by_bundle, + GCancellable *cancellable, GError **error) + { + const gchar *id; + GPtrArray* components = NULL; ++ XbSilo *silo; ++ GHashTable *silo_installed_by_desktopid; ++ const gchar *silo_filename; + g_autoptr(GError) error_local = NULL; +- g_autoptr(XbSilo) silo = NULL; +- g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; +- g_autofree gchar *silo_filename = NULL; + + GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); + +@@ -3766,10 +3769,15 @@ gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, + if (id == NULL) + return TRUE; + +- silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); +- if (silo == NULL) ++ if (*inout_silo == NULL) ++ *inout_silo = gs_flatpak_ref_silo (self, interactive, inout_silo_filename, inout_installed_by_desktopid, cancellable, error); ++ if (*inout_silo == NULL) + return FALSE; + ++ silo = *inout_silo; ++ silo_filename = *inout_silo_filename; ++ silo_installed_by_desktopid = *inout_installed_by_desktopid; ++ + GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardQuerySilo, "Flatpak (query silo)", NULL); + + if (*inout_components_by_id != NULL) { +diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h +index 66ff59967..34e1416b5 100644 +--- a/plugins/flatpak/gs-flatpak.h ++++ b/plugins/flatpak/gs-flatpak.h +@@ -11,6 +11,7 @@ + + #include + #include ++#include + + G_BEGIN_DECLS + +@@ -108,6 +109,9 @@ gboolean gs_flatpak_refine_wildcard (GsFlatpak *self, + GsAppList *list, + GsPluginRefineRequireFlags require_flags, + gboolean interactive, ++ XbSilo **inout_silo, ++ gchar **inout_silo_filename, ++ GHashTable **inout_installed_by_desktopid, + GHashTable **inout_components_by_id, + GHashTable **inout_components_by_bundle, + GCancellable *cancellable, +diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c +index 3d0503d46..f5d4a1d67 100644 +--- a/plugins/flatpak/gs-plugin-flatpak.c ++++ b/plugins/flatpak/gs-plugin-flatpak.c +@@ -667,12 +667,29 @@ gs_plugin_flatpak_refine_app (GsPluginFlatpak *self, + return gs_flatpak_refine_app (flatpak, app, require_flags, interactive, FALSE, event_callback, event_user_data, cancellable, error); + } + ++typedef struct { ++ XbSilo *silo; ++ gchar *silo_filename; ++ GHashTable *installed_by_desktopid; ++ GHashTable *components_by_id; ++ GHashTable *components_by_bundle; ++} RefineInstallationData; ++ + static void +-unref_nonnull_hash_table (gpointer ptr) ++refine_installation_data_free (gpointer ptr) + { +- GHashTable *hash_table = ptr; +- if (hash_table != NULL) +- g_hash_table_unref (hash_table); ++ RefineInstallationData *data = ptr; ++ ++ if (data == NULL) ++ return; ++ ++ g_clear_pointer (&data->silo_filename, g_free); ++ g_clear_pointer (&data->installed_by_desktopid, g_hash_table_unref); ++ g_clear_pointer (&data->components_by_id, g_hash_table_unref); ++ g_clear_pointer (&data->components_by_bundle, g_hash_table_unref); ++ /* free the silo as the last, just in case, because the data from it is in the above hash tables */ ++ g_clear_object (&data->silo); ++ g_free (data); + } + + static gboolean +@@ -761,8 +778,7 @@ refine_thread_cb (GTask *task, + gboolean interactive = (data->job_flags & GS_PLUGIN_REFINE_FLAGS_INTERACTIVE) != 0; + GsPluginEventCallback event_callback = data->event_callback; + void *event_user_data = data->event_user_data; +- g_autoptr(GPtrArray) array_components_by_id = NULL; /* (element-type GHashTable) */ +- g_autoptr(GPtrArray) array_components_by_bundle = NULL; /* (element-type GHashTable) */ ++ g_autoptr(GPtrArray) installation_data = NULL; /* (element-type RefineInstallationData) */ + g_autoptr(GsAppList) app_list = NULL; + g_autoptr(GError) local_error = NULL; + +@@ -783,10 +799,9 @@ refine_thread_cb (GTask *task, + * (e.g. inserting an app in the list on every call results in + * an infinite loop) */ + app_list = gs_app_list_copy (list); +- array_components_by_id = g_ptr_array_new_full (self->installations->len, unref_nonnull_hash_table); +- g_ptr_array_set_size (array_components_by_id, self->installations->len); +- array_components_by_bundle = g_ptr_array_new_full (self->installations->len, unref_nonnull_hash_table); +- g_ptr_array_set_size (array_components_by_bundle, self->installations->len); ++ ++ installation_data = g_ptr_array_new_full (self->installations->len, refine_installation_data_free); ++ g_ptr_array_set_size (installation_data, self->installations->len); + + for (guint j = 0; j < gs_app_list_length (app_list); j++) { + GsApp *app = gs_app_list_index (app_list, j); +@@ -796,16 +811,20 @@ refine_thread_cb (GTask *task, + + for (guint i = 0; i < self->installations->len; i++) { + GsFlatpak *flatpak = g_ptr_array_index (self->installations, i); +- GHashTable *components_by_id = array_components_by_id->pdata[i]; +- GHashTable *components_by_bundle = array_components_by_bundle->pdata[i]; ++ RefineInstallationData *inst_data = g_ptr_array_index (installation_data, i); ++ ++ if (inst_data == NULL) { ++ inst_data = g_new0 (RefineInstallationData, 1); ++ installation_data->pdata[i] = inst_data; ++ } + +- if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &components_by_id, &components_by_bundle, ++ if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &inst_data->silo, ++ &inst_data->silo_filename, &inst_data->installed_by_desktopid, ++ &inst_data->components_by_id, &inst_data->components_by_bundle, + cancellable, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } +- array_components_by_id->pdata[i] = components_by_id; +- array_components_by_bundle->pdata[i] = components_by_bundle; + } + } + +-- +GitLab + + +From 546c6ee17b5959d2a696a6cb346a1f8b94aba303 Mon Sep 17 00:00:00 2001 +Date: Wed, 7 Jan 2026 18:37:52 +0100 +Subject: [PATCH 2/3] flatpak: Simplify wildcard app lookup function + +By flipping the order of traversing app and installation the code +can be simplified to not have too many in/out arguments. + +Suggested by Philip Withnall +--- + plugins/flatpak/gs-flatpak.c | 251 ++++++++++++++-------------- + plugins/flatpak/gs-flatpak.h | 9 +- + plugins/flatpak/gs-plugin-flatpak.c | 61 +------ + 3 files changed, 130 insertions(+), 191 deletions(-) + +diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c +index a40ab54fb..782b0bb48 100644 +--- a/plugins/flatpak/gs-flatpak.c ++++ b/plugins/flatpak/gs-flatpak.c +@@ -3745,167 +3745,158 @@ gs_flatpak_refine_app (GsFlatpak *self, + } + + gboolean +-gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, +- GsAppList *list, GsPluginRefineRequireFlags require_flags, +- gboolean interactive, +- XbSilo **inout_silo, +- gchar **inout_silo_filename, +- GHashTable **inout_installed_by_desktopid, +- GHashTable **inout_components_by_id, +- GHashTable **inout_components_by_bundle, +- GCancellable *cancellable, GError **error) ++gs_flatpak_refine_wildcards (GsFlatpak *self, ++ GPtrArray *wildcard_apps, ++ GsAppList *list, ++ GsPluginRefineRequireFlags require_flags, ++ gboolean interactive, ++ GCancellable *cancellable, ++ GError **error) + { +- const gchar *id; +- GPtrArray* components = NULL; +- XbSilo *silo; +- GHashTable *silo_installed_by_desktopid; +- const gchar *silo_filename; + g_autoptr(GError) error_local = NULL; ++ g_autoptr(XbSilo) silo = NULL; ++ g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; ++ g_autoptr(GHashTable) components_by_id = NULL; ++ g_autoptr(GHashTable) components_by_bundle = NULL; ++ g_autoptr(GPtrArray) components_with_id = NULL; ++ g_autoptr(GPtrArray) bundles = NULL; ++ g_autofree gchar *silo_filename = NULL; + + GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); + +- /* not enough info to find */ +- id = gs_app_get_id (app); +- if (id == NULL) +- return TRUE; +- +- if (*inout_silo == NULL) +- *inout_silo = gs_flatpak_ref_silo (self, interactive, inout_silo_filename, inout_installed_by_desktopid, cancellable, error); +- if (*inout_silo == NULL) ++ silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); ++ if (silo == NULL) + return FALSE; + +- silo = *inout_silo; +- silo_filename = *inout_silo_filename; +- silo_installed_by_desktopid = *inout_installed_by_desktopid; +- + GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardQuerySilo, "Flatpak (query silo)", NULL); + +- if (*inout_components_by_id != NULL) { +- components = g_hash_table_lookup (*inout_components_by_id, gs_app_get_id (app)); +- } else { +- g_autoptr(GPtrArray) components_with_id = NULL; +- *inout_components_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); +- components_with_id = xb_silo_query (silo, "components/component/id", 0, &error_local); +- if (components_with_id == NULL) { +- if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) +- return TRUE; +- g_propagate_error (error, g_steal_pointer (&error_local)); +- return FALSE; ++ components_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); ++ components_with_id = xb_silo_query (silo, "components/component/id", 0, &error_local); ++ if (components_with_id == NULL) { ++ if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ return TRUE; ++ g_propagate_error (error, g_steal_pointer (&error_local)); ++ return FALSE; ++ } ++ ++ for (guint i = 0; i < components_with_id->len; i++) { ++ XbNode *node = g_ptr_array_index (components_with_id, i); ++ XbNode *comp_node = xb_node_get_parent (node); ++ const gchar *comp_id = xb_node_get_text (node); ++ GPtrArray *comps = g_hash_table_lookup (components_by_id, comp_id); ++ if (comps == NULL) { ++ comps = g_ptr_array_new_with_free_func (g_object_unref); ++ g_hash_table_insert (components_by_id, g_strdup (comp_id), comps); + } +- for (guint i = 0; i < components_with_id->len; i++) { +- XbNode *node = g_ptr_array_index (components_with_id, i); +- XbNode *comp_node = xb_node_get_parent (node); +- const gchar *comp_id = xb_node_get_text (node); +- GPtrArray *comps = g_hash_table_lookup (*inout_components_by_id, comp_id); +- if (comps == NULL) { +- comps = g_ptr_array_new_with_free_func (g_object_unref); +- g_hash_table_insert (*inout_components_by_id, g_strdup (comp_id), comps); ++ g_ptr_array_add (comps, comp_node); ++ } ++ ++ components_by_bundle = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); ++ bundles = xb_silo_query (silo, "/components/component/bundle[@type='flatpak']", 0, NULL); ++ for (guint b = 0; bundles != NULL && b < bundles->len; b++) { ++ XbNode *bundle_node = g_ptr_array_index (bundles, b); ++ g_autoptr(XbNode) component_node = xb_node_get_parent (bundle_node); ++ g_autoptr(XbNode) components_node = xb_node_get_parent (component_node); ++ const gchar *origin = xb_node_get_attr (components_node, "origin"); ++ if (origin != NULL) { ++ const gchar *bundle = xb_node_get_text (bundle_node); ++ if (bundle != NULL) { ++ g_autofree gchar *key = g_strconcat (origin, "\n", bundle, NULL); ++ g_hash_table_insert (components_by_bundle, g_steal_pointer (&key), g_steal_pointer (&component_node)); + } +- g_ptr_array_add (comps, comp_node); +- if (components == NULL && g_strcmp0 (id, comp_id) == 0) +- components = comps; + } + } + + GS_PROFILER_END_SCOPED (FlatpakRefineWildcardQuerySilo); + +- if (components == NULL) +- return TRUE; +- + gs_flatpak_ensure_remote_title (self, interactive, cancellable); + +- if (*inout_components_by_bundle == NULL) { +- g_autoptr(GPtrArray) bundles = NULL; +- +- *inout_components_by_bundle = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); +- bundles = xb_silo_query (silo, "/components/component/bundle[@type='flatpak']", 0, NULL); +- for (guint b = 0; bundles != NULL && b < bundles->len; b++) { +- XbNode *bundle_node = g_ptr_array_index (bundles, b); +- g_autoptr(XbNode) component_node = xb_node_get_parent (bundle_node); +- g_autoptr(XbNode) components_node = xb_node_get_parent (component_node); +- const gchar *origin = xb_node_get_attr (components_node, "origin"); +- if (origin != NULL) { +- const gchar *bundle = xb_node_get_text (bundle_node); +- if (bundle != NULL) { +- g_autofree gchar *key = g_strconcat (origin, "\n", bundle, NULL); +- g_hash_table_insert (*inout_components_by_bundle, g_steal_pointer (&key), g_steal_pointer (&component_node)); +- } +- } +- } +- } ++ for (guint j = 0; j < wildcard_apps->len; j++) { ++ GsApp *app = g_ptr_array_index (wildcard_apps, j); ++ GPtrArray *components = NULL; ++ const gchar *id; + ++ /* not enough info to find */ ++ id = gs_app_get_id (app); ++ if (id == NULL) ++ continue; + +- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardGenerateApps, "Flatpak (create app)", NULL); +- for (guint i = 0; i < components->len; i++) { +- XbNode *component = g_ptr_array_index (components, i); +- g_autoptr(GsApp) new = NULL; ++ components = g_hash_table_lookup (components_by_id, id); ++ if (components == NULL) ++ continue; + +- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardCreateAppstreamApp, "Flatpak (create Appstream app)", NULL); +- new = gs_appstream_create_app (self->plugin, silo, component, silo_filename ? silo_filename : "", +- self->scope, error); +- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardCreateAppstreamApp); ++ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardGenerateApps, "Flatpak (create app)", NULL); ++ for (guint i = 0; i < components->len; i++) { ++ XbNode *component = g_ptr_array_index (components, i); ++ g_autoptr(GsApp) new = NULL; + +- if (new == NULL) +- return FALSE; ++ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardCreateAppstreamApp, "Flatpak (create Appstream app)", NULL); ++ new = gs_appstream_create_app (self->plugin, silo, component, silo_filename ? silo_filename : "", ++ self->scope, error); ++ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardCreateAppstreamApp); + +- gs_flatpak_claim_app (self, new); +- +- /* The appstream plugin did not find the component in the plugin's cache, +- thus read the required info from the 'bundle' element. */ +- if (gs_flatpak_app_get_ref_name (new) == NULL || +- gs_flatpak_app_get_ref_arch (new) == NULL) { +- const gchar *xref_str = NULL; +- g_autoptr(XbNode) child = NULL; +- g_autoptr(XbNode) next = NULL; +- for (child = xb_node_get_child (component); child != NULL && xref_str == NULL; +- g_object_unref (child), child = g_steal_pointer (&next)) { +- next = xb_node_get_next (child); +- if (g_strcmp0 (xb_node_get_element (child), "bundle") == 0 && +- g_strcmp0 (xb_node_get_attr (child, "type"), "flatpak") == 0) { +- xref_str = xb_node_get_text (child); +- break; ++ if (new == NULL) ++ return FALSE; ++ ++ gs_flatpak_claim_app (self, new); ++ ++ /* The appstream plugin did not find the component in the plugin's cache, ++ thus read the required info from the 'bundle' element. */ ++ if (gs_flatpak_app_get_ref_name (new) == NULL || ++ gs_flatpak_app_get_ref_arch (new) == NULL) { ++ const gchar *xref_str = NULL; ++ g_autoptr(XbNode) child = NULL; ++ g_autoptr(XbNode) next = NULL; ++ for (child = xb_node_get_child (component); child != NULL && xref_str == NULL; ++ g_object_unref (child), child = g_steal_pointer (&next)) { ++ next = xb_node_get_next (child); ++ if (g_strcmp0 (xb_node_get_element (child), "bundle") == 0 && ++ g_strcmp0 (xb_node_get_attr (child, "type"), "flatpak") == 0) { ++ xref_str = xb_node_get_text (child); ++ break; ++ } + } +- } +- if (xref_str != NULL) { +- g_auto(GStrv) split = NULL; +- +- /* get the kind/name/arch/branch */ +- split = g_strsplit (xref_str, "/", -1); +- if (g_strv_length (split) == 4) { +- const gchar *comp_type = xb_node_get_attr (component, "type"); +- AsComponentKind kind = as_component_kind_from_string (comp_type); +- if (kind != AS_COMPONENT_KIND_UNKNOWN) +- gs_app_set_kind (new, kind); +- else if (g_ascii_strcasecmp (split[0], "app") == 0) +- gs_app_set_kind (new, AS_COMPONENT_KIND_DESKTOP_APP); +- else if (g_ascii_strcasecmp (split[0], "runtime") == 0) +- gs_flatpak_set_runtime_kind_from_id (new); +- gs_flatpak_app_set_ref_name (new, split[1]); +- gs_flatpak_app_set_ref_arch (new, split[2]); +- gs_app_set_branch (new, split[3]); +- gs_app_set_metadata (new, "GnomeSoftware::packagename-value", xref_str); ++ if (xref_str != NULL) { ++ g_auto(GStrv) split = NULL; ++ ++ /* get the kind/name/arch/branch */ ++ split = g_strsplit (xref_str, "/", -1); ++ if (g_strv_length (split) == 4) { ++ const gchar *comp_type = xb_node_get_attr (component, "type"); ++ AsComponentKind kind = as_component_kind_from_string (comp_type); ++ if (kind != AS_COMPONENT_KIND_UNKNOWN) ++ gs_app_set_kind (new, kind); ++ else if (g_ascii_strcasecmp (split[0], "app") == 0) ++ gs_app_set_kind (new, AS_COMPONENT_KIND_DESKTOP_APP); ++ else if (g_ascii_strcasecmp (split[0], "runtime") == 0) ++ gs_flatpak_set_runtime_kind_from_id (new); ++ gs_flatpak_app_set_ref_name (new, split[1]); ++ gs_flatpak_app_set_ref_arch (new, split[2]); ++ gs_app_set_branch (new, split[3]); ++ gs_app_set_metadata (new, "GnomeSoftware::packagename-value", xref_str); ++ } + } + } +- } + +- if (gs_flatpak_app_get_ref_name (new) == NULL || +- gs_flatpak_app_get_ref_arch (new) == NULL) { +- g_debug ("Failed to get ref info for '%s' from wildcard '%s', skipping it...", gs_app_get_id (new), id); +- } else { +- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardRefineNewApp, "Flatpak (refine new app)", NULL); +- if (!gs_flatpak_refine_app_internal (self, new, require_flags, interactive, FALSE, *inout_components_by_bundle, +- silo, silo_filename, silo_installed_by_desktopid, cancellable, error)) +- return FALSE; +- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardRefineNewApp); ++ if (gs_flatpak_app_get_ref_name (new) == NULL || ++ gs_flatpak_app_get_ref_arch (new) == NULL) { ++ g_debug ("Failed to get ref info for '%s' from wildcard '%s', skipping it...", gs_app_get_id (new), id); ++ } else { ++ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardRefineNewApp, "Flatpak (refine new app)", NULL); ++ if (!gs_flatpak_refine_app_internal (self, new, require_flags, interactive, FALSE, components_by_bundle, ++ silo, silo_filename, silo_installed_by_desktopid, cancellable, error)) ++ return FALSE; ++ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardRefineNewApp); + +- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardSubsumeMetadata, "Flatpak (subsume metadata)", NULL); +- gs_app_subsume_metadata (new, app); +- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardSubsumeMetadata); ++ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardSubsumeMetadata, "Flatpak (subsume metadata)", NULL); ++ gs_app_subsume_metadata (new, app); ++ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardSubsumeMetadata); + +- gs_app_list_add (list, new); ++ gs_app_list_add (list, new); ++ } + } ++ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardGenerateApps); + } +- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardGenerateApps); + + GS_PROFILER_END_SCOPED (FlatpakRefineWildcard); + +diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h +index 34e1416b5..1f7d35a8f 100644 +--- a/plugins/flatpak/gs-flatpak.h ++++ b/plugins/flatpak/gs-flatpak.h +@@ -104,16 +104,11 @@ gboolean gs_flatpak_refine_app_state (GsFlatpak *self, + void *event_user_data, + GCancellable *cancellable, + GError **error); +-gboolean gs_flatpak_refine_wildcard (GsFlatpak *self, +- GsApp *app, ++gboolean gs_flatpak_refine_wildcards (GsFlatpak *self, ++ GPtrArray *wildcard_apps, + GsAppList *list, + GsPluginRefineRequireFlags require_flags, + gboolean interactive, +- XbSilo **inout_silo, +- gchar **inout_silo_filename, +- GHashTable **inout_installed_by_desktopid, +- GHashTable **inout_components_by_id, +- GHashTable **inout_components_by_bundle, + GCancellable *cancellable, + GError **error); + gboolean gs_flatpak_launch (GsFlatpak *self, +diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c +index f5d4a1d67..c06b1fb5f 100644 +--- a/plugins/flatpak/gs-plugin-flatpak.c ++++ b/plugins/flatpak/gs-plugin-flatpak.c +@@ -667,31 +667,6 @@ gs_plugin_flatpak_refine_app (GsPluginFlatpak *self, + return gs_flatpak_refine_app (flatpak, app, require_flags, interactive, FALSE, event_callback, event_user_data, cancellable, error); + } + +-typedef struct { +- XbSilo *silo; +- gchar *silo_filename; +- GHashTable *installed_by_desktopid; +- GHashTable *components_by_id; +- GHashTable *components_by_bundle; +-} RefineInstallationData; +- +-static void +-refine_installation_data_free (gpointer ptr) +-{ +- RefineInstallationData *data = ptr; +- +- if (data == NULL) +- return; +- +- g_clear_pointer (&data->silo_filename, g_free); +- g_clear_pointer (&data->installed_by_desktopid, g_hash_table_unref); +- g_clear_pointer (&data->components_by_id, g_hash_table_unref); +- g_clear_pointer (&data->components_by_bundle, g_hash_table_unref); +- /* free the silo as the last, just in case, because the data from it is in the above hash tables */ +- g_clear_object (&data->silo); +- g_free (data); +-} +- + static gboolean + refine_app (GsPluginFlatpak *self, + GsApp *app, +@@ -778,8 +753,7 @@ refine_thread_cb (GTask *task, + gboolean interactive = (data->job_flags & GS_PLUGIN_REFINE_FLAGS_INTERACTIVE) != 0; + GsPluginEventCallback event_callback = data->event_callback; + void *event_user_data = data->event_user_data; +- g_autoptr(GPtrArray) installation_data = NULL; /* (element-type RefineInstallationData) */ +- g_autoptr(GsAppList) app_list = NULL; ++ g_autoptr(GPtrArray) wildcard_apps = g_ptr_array_new_with_free_func (g_object_unref); /* (element-type GsApp) (owned) */ + g_autoptr(GError) local_error = NULL; + + assert_in_worker (self); +@@ -790,38 +764,17 @@ refine_thread_cb (GTask *task, + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } +- } +- +- /* Refine wildcards. +- * +- * Use a copy of the list for the loop because a function called +- * on the plugin may affect the list which can lead to problems +- * (e.g. inserting an app in the list on every call results in +- * an infinite loop) */ +- app_list = gs_app_list_copy (list); +- +- installation_data = g_ptr_array_new_full (self->installations->len, refine_installation_data_free); +- g_ptr_array_set_size (installation_data, self->installations->len); +- +- for (guint j = 0; j < gs_app_list_length (app_list); j++) { +- GsApp *app = gs_app_list_index (app_list, j); + +- if (!gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) +- continue; ++ if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD) && gs_app_get_id (app) != NULL) ++ g_ptr_array_add (wildcard_apps, g_object_ref (app)); ++ } + ++ /* Refine wildcards. */ ++ if (wildcard_apps->len > 0) { + for (guint i = 0; i < self->installations->len; i++) { + GsFlatpak *flatpak = g_ptr_array_index (self->installations, i); +- RefineInstallationData *inst_data = g_ptr_array_index (installation_data, i); +- +- if (inst_data == NULL) { +- inst_data = g_new0 (RefineInstallationData, 1); +- installation_data->pdata[i] = inst_data; +- } + +- if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &inst_data->silo, +- &inst_data->silo_filename, &inst_data->installed_by_desktopid, +- &inst_data->components_by_id, &inst_data->components_by_bundle, +- cancellable, &local_error)) { ++ if (!gs_flatpak_refine_wildcards (flatpak, wildcard_apps, list, require_flags, interactive, cancellable, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } +-- +GitLab + + +From 8472d717356a801e6c0171156738ea48480fa695 Mon Sep 17 00:00:00 2001 +Date: Wed, 7 Jan 2026 18:45:47 +0100 +Subject: [PATCH 3/3] flatpak: Hold silo lock when refining wildcard apps + +The refine can take a long time, thus hold the silo lock to not have +it invalidated from another thread. Object reference is not enough +for the XbSilo, it breaks as soon as the underlying file changes. +--- + plugins/flatpak/gs-flatpak.c | 16 +++++++++++----- + 1 file changed, 11 insertions(+), 5 deletions(-) + +diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c +index 782b0bb48..c5437125d 100644 +--- a/plugins/flatpak/gs-flatpak.c ++++ b/plugins/flatpak/gs-flatpak.c +@@ -59,7 +59,7 @@ struct _GsFlatpak { + AsComponentScope scope; + GsPlugin *plugin; + XbSilo *silo; +- GMutex silo_lock; ++ GRecMutex silo_lock; + gchar *silo_filename; + GHashTable *silo_installed_by_desktopid; + gint silo_change_stamp; +@@ -1187,11 +1187,11 @@ gs_flatpak_ref_silo (GsFlatpak *self, + g_autoptr(GFile) file = NULL; + g_autoptr(GPtrArray) xremotes = NULL; + g_autoptr(GPtrArray) desktop_paths = NULL; +- g_autoptr(GMutexLocker) locker = NULL; ++ g_autoptr(GRecMutexLocker) locker = NULL; + g_autoptr(XbBuilder) builder = NULL; + g_autoptr(GMainContext) old_thread_default = NULL; + +- locker = g_mutex_locker_new (&self->silo_lock); ++ locker = g_rec_mutex_locker_new (&self->silo_lock); + /* everything is okay */ + if (self->silo != NULL && xb_silo_is_valid (self->silo) && + g_atomic_int_get (&self->silo_change_stamp_current) == g_atomic_int_get (&self->silo_change_stamp)) { +@@ -3754,6 +3754,7 @@ gs_flatpak_refine_wildcards (GsFlatpak *self, + GError **error) + { + g_autoptr(GError) error_local = NULL; ++ g_autoptr(GRecMutexLocker) silo_locker = NULL; + g_autoptr(XbSilo) silo = NULL; + g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; + g_autoptr(GHashTable) components_by_id = NULL; +@@ -3764,6 +3765,11 @@ gs_flatpak_refine_wildcards (GsFlatpak *self, + + GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); + ++ /* the refine can take a long time, thus hold the silo lock to not have ++ it invalidated from another thread; object reference is not enough ++ for the XbSilo, it breaks as soon as the underlying file changes */ ++ silo_locker = g_rec_mutex_locker_new (&self->silo_lock); ++ + silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); + if (silo == NULL) + return FALSE; +@@ -4810,7 +4816,7 @@ gs_flatpak_finalize (GObject *object) + g_object_unref (self->plugin); + g_hash_table_unref (self->broken_remotes); + g_mutex_clear (&self->broken_remotes_mutex); +- g_mutex_clear (&self->silo_lock); ++ g_rec_mutex_clear (&self->silo_lock); + g_hash_table_unref (self->app_silos); + g_mutex_clear (&self->app_silos_mutex); + g_clear_pointer (&self->remote_title, g_hash_table_unref); +@@ -4831,7 +4837,7 @@ gs_flatpak_init (GsFlatpak *self) + { + /* XbSilo needs external locking as we destroy the silo and build a new + * one when something changes */ +- g_mutex_init (&self->silo_lock); ++ g_rec_mutex_init (&self->silo_lock); + + g_mutex_init (&self->installed_refs_mutex); + self->installed_refs = NULL; +-- +GitLab + diff --git a/gnome-software.spec b/gnome-software.spec index 10c8d74..c080ede 100644 --- a/gnome-software.spec +++ b/gnome-software.spec @@ -29,17 +29,22 @@ %global __provides_exclude_from ^%{_libdir}/%{name}/plugins-%{gs_plugin_version}/.*\\.so.*$ Name: gnome-software -Version: 49~beta -Release: 3%{?dist} +Version: 49.3 +Release: 1%{?dist} Summary: A software center for GNOME License: GPL-2.0-or-later URL: https://apps.gnome.org/Software Source0: https://download.gnome.org/sources/gnome-software/49/%{name}-%{tarball_version}.tar.xz +%if %{with dnf5} # to update the patch enter the ./dnf5-plugin/ directory and run from # it the ./update-patch.sh script Patch: 0001-dnf5-plugin.patch +%endif + +# https://bugzilla.redhat.com/show_bug.cgi?id=2416542 +Patch: 0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch # ostree and flatpak not on i686 for Fedora and RHEL 10 # https://github.com/containers/composefs/pull/229#issuecomment-1838735764 @@ -158,7 +163,7 @@ This package includes the rpm-ostree backend. %endif %prep -%autosetup -p1 -S git -n %{name}-%{tarball_version} +%autosetup -p1 -S gendiff -n %{name}-%{tarball_version} %build %meson \ @@ -177,8 +182,6 @@ This package includes the rpm-ostree backend. %endif %if %{with dnf5} -Ddnf5=true \ -%else - -Ddnf5=false \ %endif -Dexternal_appstream=false \ %if %{with rpmostree} @@ -232,7 +235,7 @@ FOE desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %files -f %{name}.lang -%doc AUTHORS README.md +%doc AUTHORS NEWS README.md %license COPYING %{_bindir}/gnome-software %{_datadir}/applications/gnome-software-local-file-flatpak.desktop diff --git a/sources b/sources index f212fbd..c24e704 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (gnome-software-49.beta.tar.xz) = 9cb6d67216193840b2661871ef43da5e726a284d1164781f13423ee8dd86359334f199efc29f4b3a0b3eddfa8a3811d7dd38cd412fa53aaebb2e1c35ddc1f9df +SHA512 (gnome-software-49.3.tar.xz) = 0414ea55ad3b83bcd50514985e3d2026207801c040934805bb26d162e88f0619a9d70aa6ba324a29acfab02573490b928c83594b54f57ec2a8ad00e6c127f657