diff options
Diffstat (limited to 'lib/gs-fedora-third-party.c')
-rw-r--r-- | lib/gs-fedora-third-party.c | 497 |
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; +} |