summaryrefslogtreecommitdiffstats
path: root/lib/gs-fedora-third-party.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gs-fedora-third-party.c')
-rw-r--r--lib/gs-fedora-third-party.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/lib/gs-fedora-third-party.c b/lib/gs-fedora-third-party.c
new file mode 100644
index 0000000..6afebc0
--- /dev/null
+++ b/lib/gs-fedora-third-party.c
@@ -0,0 +1,497 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Red Hat <www.redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "gs-fedora-third-party.h"
+
+struct _GsFedoraThirdParty
+{
+ GObject parent_instance;
+ GMutex lock;
+ gchar *executable;
+ GHashTable *repos; /* gchar *name ~> gchar *packaging format */
+ gint64 last_update;
+};
+
+G_DEFINE_TYPE (GsFedoraThirdParty, gs_fedora_third_party, G_TYPE_OBJECT)
+
+static GObject *
+gs_fedora_third_party_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ static GWeakRef singleton;
+ GObject *result;
+
+ result = g_weak_ref_get (&singleton);
+ if (result == NULL) {
+ result = G_OBJECT_CLASS (gs_fedora_third_party_parent_class)->constructor (type, n_construct_properties, construct_properties);
+
+ if (result)
+ g_weak_ref_set (&singleton, result);
+ }
+
+ return result;
+}
+
+static void
+gs_fedora_third_party_finalize (GObject *object)
+{
+ GsFedoraThirdParty *self = GS_FEDORA_THIRD_PARTY (object);
+
+ g_clear_pointer (&self->executable, g_free);
+ g_clear_pointer (&self->repos, g_hash_table_unref);
+ g_mutex_clear (&self->lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (gs_fedora_third_party_parent_class)->finalize (object);
+}
+
+static void
+gs_fedora_third_party_class_init (GsFedoraThirdPartyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->constructor = gs_fedora_third_party_constructor;
+ object_class->finalize = gs_fedora_third_party_finalize;
+}
+
+static void
+gs_fedora_third_party_init (GsFedoraThirdParty *self)
+{
+ g_mutex_init (&self->lock);
+}
+
+GsFedoraThirdParty *
+gs_fedora_third_party_new (void)
+{
+ return g_object_new (GS_TYPE_FEDORA_THIRD_PARTY, NULL);
+}
+
+static gboolean
+gs_fedora_third_party_ensure_executable_locked (GsFedoraThirdParty *self,
+ GError **error)
+{
+ if (self->executable == NULL)
+ self->executable = g_find_program_in_path ("fedora-third-party");
+
+ if (self->executable == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File 'fedora-third-party' not found");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_fedora_third_party_is_available (GsFedoraThirdParty *self)
+{
+ gboolean res;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ g_mutex_lock (&self->lock);
+ res = gs_fedora_third_party_ensure_executable_locked (self, NULL);
+ g_mutex_unlock (&self->lock);
+
+ return res;
+}
+
+void
+gs_fedora_third_party_invalidate (GsFedoraThirdParty *self)
+{
+ g_return_if_fail (GS_IS_FEDORA_THIRD_PARTY (self));
+
+ g_mutex_lock (&self->lock);
+ g_clear_pointer (&self->executable, g_free);
+ g_clear_pointer (&self->repos, g_hash_table_unref);
+ self->last_update = 0;
+ g_mutex_unlock (&self->lock);
+}
+
+typedef struct _AsyncData
+{
+ gboolean enable;
+ gboolean config_only;
+} AsyncData;
+
+static AsyncData *
+async_data_new (gboolean enable,
+ gboolean config_only)
+{
+ AsyncData *async_data = g_slice_new0 (AsyncData);
+ async_data->enable = enable;
+ async_data->config_only = config_only;
+ return async_data;
+}
+
+static void
+async_data_free (gpointer ptr)
+{
+ AsyncData *async_data = ptr;
+ if (async_data != NULL)
+ g_slice_free (AsyncData, async_data);
+}
+
+static void
+gs_fedora_third_party_query_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GError) error = NULL;
+ GsFedoraThirdPartyState state;
+ if (gs_fedora_third_party_query_sync (GS_FEDORA_THIRD_PARTY (source_object), &state, cancellable, &error))
+ g_task_return_int (task, state);
+ else
+ g_task_return_error (task, g_steal_pointer (&error));
+}
+
+void
+gs_fedora_third_party_query (GsFedoraThirdParty *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (GS_IS_FEDORA_THIRD_PARTY (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_fedora_third_party_query);
+ g_task_run_in_thread (task, gs_fedora_third_party_query_thread);
+}
+
+gboolean
+gs_fedora_third_party_query_finish (GsFedoraThirdParty *self,
+ GAsyncResult *result,
+ GsFedoraThirdPartyState *out_state,
+ GError **error)
+{
+ GError *local_error = NULL;
+ GsFedoraThirdPartyState state = GS_FEDORA_THIRD_PARTY_STATE_UNKNOWN;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ state = g_task_propagate_int (G_TASK (result), &local_error);
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ if (out_state)
+ *out_state = state;
+
+ return TRUE;
+}
+
+gboolean
+gs_fedora_third_party_query_sync (GsFedoraThirdParty *self,
+ GsFedoraThirdPartyState *out_state,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *args[] = {
+ "", /* executable */
+ "query",
+ "--quiet",
+ NULL
+ };
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ g_mutex_lock (&self->lock);
+ if (gs_fedora_third_party_ensure_executable_locked (self, error)) {
+ gint wait_status = -1;
+ args[0] = self->executable;
+ success = g_spawn_sync (NULL, (gchar **) args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &wait_status, error);
+ if (success) {
+ GsFedoraThirdPartyState state = GS_FEDORA_THIRD_PARTY_STATE_UNKNOWN;
+ /* See https://pagure.io/fedora-third-party/blob/main/f/doc/fedora-third-party.1.md */
+ switch (WEXITSTATUS (wait_status)) {
+ case 0:
+ state = GS_FEDORA_THIRD_PARTY_STATE_ENABLED;
+ break;
+ case 1:
+ state = GS_FEDORA_THIRD_PARTY_STATE_DISABLED;
+ break;
+ case 2:
+ state = GS_FEDORA_THIRD_PARTY_STATE_ASK;
+ break;
+ default:
+ break;
+ }
+ if (out_state)
+ *out_state = state;
+ }
+ }
+ g_mutex_unlock (&self->lock);
+
+ return success;
+}
+
+static void
+gs_fedora_third_party_switch_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GError) error = NULL;
+ AsyncData *async_data = task_data;
+ if (gs_fedora_third_party_switch_sync (GS_FEDORA_THIRD_PARTY (source_object), async_data->enable, async_data->config_only, cancellable, &error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, g_steal_pointer (&error));
+}
+
+void
+gs_fedora_third_party_switch (GsFedoraThirdParty *self,
+ gboolean enable,
+ gboolean config_only,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (GS_IS_FEDORA_THIRD_PARTY (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_fedora_third_party_switch);
+ g_task_set_task_data (task, async_data_new (enable, config_only), async_data_free);
+ g_task_run_in_thread (task, gs_fedora_third_party_switch_thread);
+}
+
+gboolean
+gs_fedora_third_party_switch_finish (GsFedoraThirdParty *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+gs_fedora_third_party_switch_sync (GsFedoraThirdParty *self,
+ gboolean enable,
+ gboolean config_only,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *args[] = {
+ "pkexec",
+ "", /* executable */
+ "", /* command */
+ "", /* config-only */
+ NULL
+ };
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ g_mutex_lock (&self->lock);
+ if (gs_fedora_third_party_ensure_executable_locked (self, error)) {
+ gint wait_status = -1;
+ args[1] = self->executable;
+ args[2] = enable ? "enable" : "disable";
+ args[3] = config_only ? "--config-only" : NULL;
+ success = g_spawn_sync (NULL, (gchar **) args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &wait_status, error) &&
+ g_spawn_check_wait_status (wait_status, error);
+ }
+ g_mutex_unlock (&self->lock);
+
+ return success;
+}
+
+static void
+gs_fedora_third_party_opt_out_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GError) error = NULL;
+ if (gs_fedora_third_party_opt_out_sync (GS_FEDORA_THIRD_PARTY (source_object), cancellable, &error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, g_steal_pointer (&error));
+}
+
+void
+gs_fedora_third_party_opt_out (GsFedoraThirdParty *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (GS_IS_FEDORA_THIRD_PARTY (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_fedora_third_party_opt_out);
+ g_task_run_in_thread (task, gs_fedora_third_party_opt_out_thread);
+}
+
+gboolean
+gs_fedora_third_party_opt_out_finish (GsFedoraThirdParty *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+gs_fedora_third_party_opt_out_sync (GsFedoraThirdParty *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* fedora-third-party-opt-out is a single-purpose script that changes
+ * the third-party status from unset => disabled. It exists to allow
+ * a different pkexec configuration for opting-out and thus avoid
+ * admin users needing to authenticate to opt-out.
+ */
+ const gchar *args[] = {
+ "pkexec",
+ "/usr/lib/fedora-third-party/fedora-third-party-opt-out",
+ NULL
+ };
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ g_mutex_lock (&self->lock);
+ if (gs_fedora_third_party_ensure_executable_locked (self, error)) {
+ gint wait_status = -1;
+ success = g_spawn_sync (NULL, (gchar **) args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &wait_status, error) &&
+ g_spawn_check_wait_status (wait_status, error);
+ }
+ g_mutex_unlock (&self->lock);
+
+ return success;
+}
+
+static void
+gs_fedora_third_party_list_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GHashTable) repos = NULL;
+ if (gs_fedora_third_party_list_sync (GS_FEDORA_THIRD_PARTY (source_object), &repos, cancellable, &error))
+ g_task_return_pointer (task, g_steal_pointer (&repos), (GDestroyNotify) g_hash_table_unref);
+ else
+ g_task_return_error (task, g_steal_pointer (&error));
+}
+
+void
+gs_fedora_third_party_list (GsFedoraThirdParty *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (GS_IS_FEDORA_THIRD_PARTY (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_fedora_third_party_list);
+ g_task_run_in_thread (task, gs_fedora_third_party_list_thread);
+}
+
+gboolean
+gs_fedora_third_party_list_finish (GsFedoraThirdParty *self,
+ GAsyncResult *result,
+ GHashTable **out_repos, /* gchar *name ~> gchar *management_plugin */
+ GError **error)
+{
+ g_autoptr(GHashTable) repos = NULL;
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+ repos = g_task_propagate_pointer (G_TASK (result), error);
+ if (repos == NULL)
+ return FALSE;
+ if (out_repos)
+ *out_repos = g_steal_pointer (&repos);
+ return TRUE;
+}
+
+gboolean
+gs_fedora_third_party_list_sync (GsFedoraThirdParty *self,
+ GHashTable **out_repos, /* gchar *name ~> gchar *management_plugin */
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *args[] = {
+ "", /* executable */
+ "list",
+ "--csv",
+ "--columns=type,name",
+ NULL
+ };
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GS_IS_FEDORA_THIRD_PARTY (self), FALSE);
+
+ g_mutex_lock (&self->lock);
+ /* Auto-recheck only twice a day */
+ if (self->repos == NULL || (g_get_real_time () / G_USEC_PER_SEC) - self->last_update > 12 * 60 * 60) {
+ g_clear_pointer (&self->repos, g_hash_table_unref);
+ if (gs_fedora_third_party_ensure_executable_locked (self, error)) {
+ gint wait_status = -1;
+ g_autofree gchar *stdoutput = NULL;
+ args[0] = self->executable;
+ if (g_spawn_sync (NULL, (gchar **) args, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdoutput, NULL, &wait_status, error) &&
+ g_spawn_check_wait_status (wait_status, error)) {
+ GHashTable *repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ g_auto(GStrv) lines = NULL;
+
+ lines = g_strsplit (stdoutput != NULL ? stdoutput : "", "\n", -1);
+
+ for (gsize ii = 0; lines != NULL && lines[ii]; ii++) {
+ g_auto(GStrv) tokens = g_strsplit (lines[ii], ",", 2);
+ if (tokens != NULL && tokens[0] != NULL && tokens[1] != NULL) {
+ const gchar *repo_type = tokens[0];
+ /* The 'dnf' means 'packagekit' here */
+ if (g_str_equal (repo_type, "dnf"))
+ repo_type = "packagekit";
+ /* Hash them by name, which cannot clash between types */
+ g_hash_table_insert (repos, g_strdup (tokens[1]), g_strdup (repo_type));
+ }
+ }
+
+ self->repos = repos;
+ }
+ }
+ self->last_update = g_get_real_time () / G_USEC_PER_SEC;
+ }
+ success = self->repos != NULL;
+ if (success && out_repos)
+ *out_repos = g_hash_table_ref (self->repos);
+ g_mutex_unlock (&self->lock);
+
+ return success;
+}
+
+gboolean
+gs_fedora_third_party_util_is_third_party_repo (GHashTable *third_party_repos,
+ const gchar *origin,
+ const gchar *management_plugin)
+{
+ const gchar *expected_management_plugin;
+
+ if (third_party_repos == NULL || origin == NULL)
+ return FALSE;
+
+ expected_management_plugin = g_hash_table_lookup (third_party_repos, origin);
+ if (expected_management_plugin == NULL)
+ return FALSE;
+
+ return g_strcmp0 (management_plugin, expected_management_plugin) == 0;
+}