/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright (C) 2015-2016 Richard Hughes * Copyright (C) 2018 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include "gs-plugin-provenance.h" /* * SECTION: * Sets the package provenance to TRUE if installed by an official * software source. Also sets compulsory quirk when a required repository. * * This plugin executes entirely in the main thread. */ struct _GsPluginProvenance { GsPlugin parent; GSettings *settings; GHashTable *repos; /* gchar *name ~> guint flags */ GPtrArray *provenance_wildcards; /* non-NULL, when have names with wildcards */ GPtrArray *compulsory_wildcards; /* non-NULL, when have names with wildcards */ }; G_DEFINE_TYPE (GsPluginProvenance, gs_plugin_provenance, GS_TYPE_PLUGIN) static GHashTable * gs_plugin_provenance_remove_by_flag (GHashTable *old_repos, GsAppQuirk quirk) { GHashTable *new_repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, old_repos); while (g_hash_table_iter_next (&iter, &key, &value)) { guint flags = GPOINTER_TO_UINT (value); flags = flags & (~quirk); if (flags != 0) g_hash_table_insert (new_repos, g_strdup (key), GUINT_TO_POINTER (flags)); } return new_repos; } static void gs_plugin_provenance_add_quirks (GsApp *app, guint quirks) { if ((quirks & GS_APP_QUIRK_PROVENANCE) != 0) gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); if ((quirks & GS_APP_QUIRK_COMPULSORY) != 0 && gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) gs_app_add_quirk (app, GS_APP_QUIRK_COMPULSORY); } static gchar ** gs_plugin_provenance_get_sources (GsPluginProvenance *self, const gchar *key) { const gchar *tmp; tmp = g_getenv ("GS_SELF_TEST_PROVENANCE_SOURCES"); if (tmp != NULL) { if (g_strcmp0 (key, "required-repos") == 0) return NULL; g_debug ("using custom provenance sources of %s", tmp); return g_strsplit (tmp, ",", -1); } return g_settings_get_strv (self->settings, key); } static void gs_plugin_provenance_settings_changed_cb (GSettings *settings, const gchar *key, gpointer user_data) { GsPluginProvenance *self = GS_PLUGIN_PROVENANCE (user_data); GsAppQuirk quirk = GS_APP_QUIRK_NONE; GPtrArray **pwildcards = NULL; if (g_strcmp0 (key, "official-repos") == 0) { quirk = GS_APP_QUIRK_PROVENANCE; pwildcards = &self->provenance_wildcards; } else if (g_strcmp0 (key, "required-repos") == 0) { quirk = GS_APP_QUIRK_COMPULSORY; pwildcards = &self->compulsory_wildcards; } if (quirk != GS_APP_QUIRK_NONE) { /* The keys are stolen by the hash table, thus free only the array */ g_autofree gchar **repos = NULL; g_autoptr(GHashTable) old_repos = self->repos; g_autoptr(GPtrArray) old_wildcards = *pwildcards; GHashTable *new_repos = gs_plugin_provenance_remove_by_flag (old_repos, quirk); GPtrArray *new_wildcards = NULL; repos = gs_plugin_provenance_get_sources (self, key); for (guint ii = 0; repos && repos[ii]; ii++) { gchar *repo = g_steal_pointer (&(repos[ii])); if (strchr (repo, '*') || strchr (repo, '?') || strchr (repo, '[')) { if (new_wildcards == NULL) new_wildcards = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (new_wildcards, repo); } else { g_hash_table_insert (new_repos, repo, GUINT_TO_POINTER (quirk | GPOINTER_TO_UINT (g_hash_table_lookup (new_repos, repo)))); } } if (new_wildcards != NULL) g_ptr_array_add (new_wildcards, NULL); self->repos = new_repos; *pwildcards = new_wildcards; } } static void gs_plugin_provenance_init (GsPluginProvenance *self) { self->settings = g_settings_new ("org.gnome.software"); self->repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_signal_connect (self->settings, "changed", G_CALLBACK (gs_plugin_provenance_settings_changed_cb), self); gs_plugin_provenance_settings_changed_cb (self->settings, "official-repos", self); gs_plugin_provenance_settings_changed_cb (self->settings, "required-repos", self); /* after the package source is set */ gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "dummy"); gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "packagekit"); gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "rpm-ostree"); } static void gs_plugin_provenance_dispose (GObject *object) { GsPluginProvenance *self = GS_PLUGIN_PROVENANCE (object); g_clear_pointer (&self->repos, g_hash_table_unref); g_clear_pointer (&self->provenance_wildcards, g_ptr_array_unref); g_clear_pointer (&self->compulsory_wildcards, g_ptr_array_unref); g_clear_object (&self->settings); G_OBJECT_CLASS (gs_plugin_provenance_parent_class)->dispose (object); } static gboolean gs_plugin_provenance_find_repo_flags (GHashTable *repos, GPtrArray *provenance_wildcards, GPtrArray *compulsory_wildcards, const gchar *repo, guint *out_flags) { if (repo == NULL || *repo == '\0') return FALSE; *out_flags = GPOINTER_TO_UINT (g_hash_table_lookup (repos, repo)); if (provenance_wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) provenance_wildcards->pdata, repo)) *out_flags |= GS_APP_QUIRK_PROVENANCE; if (compulsory_wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) compulsory_wildcards->pdata, repo)) *out_flags |= GS_APP_QUIRK_COMPULSORY; return *out_flags != 0; } static gboolean refine_app (GsPlugin *plugin, GsApp *app, GsPluginRefineFlags flags, GHashTable *repos, GPtrArray *provenance_wildcards, GPtrArray *compulsory_wildcards, GCancellable *cancellable, GError **error) { const gchar *origin; guint quirks; /* not required */ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) return TRUE; if (gs_app_has_quirk (app, GS_APP_QUIRK_PROVENANCE)) return TRUE; /* Software sources/repositories are represented as #GsApps too. Add the * provenance quirk to the system-configured repositories (but not * user-configured ones). */ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) { if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, gs_app_get_id (app), &quirks) && gs_app_get_scope (app) != AS_COMPONENT_SCOPE_USER) gs_plugin_provenance_add_quirks (app, quirks); return TRUE; } /* simple case */ origin = gs_app_get_origin (app); if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin, &quirks)) { gs_plugin_provenance_add_quirks (app, quirks); return TRUE; } /* this only works for packages */ origin = gs_app_get_source_id_default (app); if (origin == NULL) return TRUE; origin = g_strrstr (origin, ";"); if (origin == NULL) return TRUE; if (g_str_has_prefix (origin + 1, "installed:")) origin += 10; if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin + 1, &quirks)) gs_plugin_provenance_add_quirks (app, quirks); return TRUE; } static void gs_plugin_provenance_refine_async (GsPlugin *plugin, GsAppList *list, GsPluginRefineFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GsPluginProvenance *self = GS_PLUGIN_PROVENANCE (plugin); g_autoptr(GTask) task = NULL; g_autoptr(GError) local_error = NULL; g_autoptr(GHashTable) repos = NULL; g_autoptr(GPtrArray) provenance_wildcards = NULL; g_autoptr(GPtrArray) compulsory_wildcards = NULL; task = g_task_new (plugin, cancellable, callback, user_data); g_task_set_source_tag (task, gs_plugin_provenance_refine_async); /* nothing to do here */ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) { g_task_return_boolean (task, TRUE); return; } repos = g_hash_table_ref (self->repos); provenance_wildcards = self->provenance_wildcards != NULL ? g_ptr_array_ref (self->provenance_wildcards) : NULL; compulsory_wildcards = self->compulsory_wildcards != NULL ? g_ptr_array_ref (self->compulsory_wildcards) : NULL; /* nothing to search */ if (g_hash_table_size (repos) == 0 && provenance_wildcards == NULL && compulsory_wildcards == NULL) { g_task_return_boolean (task, TRUE); return; } for (guint i = 0; i < gs_app_list_length (list); i++) { GsApp *app = gs_app_list_index (list, i); if (!refine_app (plugin, app, flags, repos, provenance_wildcards, compulsory_wildcards, cancellable, &local_error)) { g_task_return_error (task, g_steal_pointer (&local_error)); return; } } g_task_return_boolean (task, TRUE); } static gboolean gs_plugin_provenance_refine_finish (GsPlugin *plugin, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void gs_plugin_provenance_class_init (GsPluginProvenanceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass); object_class->dispose = gs_plugin_provenance_dispose; plugin_class->refine_async = gs_plugin_provenance_refine_async; plugin_class->refine_finish = gs_plugin_provenance_refine_finish; } GType gs_plugin_query_type (void) { return GS_TYPE_PLUGIN_PROVENANCE; }