summaryrefslogtreecommitdiffstats
path: root/plugins/rpm-ostree
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:57:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:57:27 +0000
commit6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18 (patch)
treed423850ae901365e582137bdf2b5cbdffd7ca266 /plugins/rpm-ostree
parentInitial commit. (diff)
downloadgnome-software-6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18.tar.xz
gnome-software-6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18.zip
Adding upstream version 43.5.upstream/43.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--plugins/rpm-ostree/gs-plugin-rpm-ostree.c2878
-rw-r--r--plugins/rpm-ostree/gs-plugin-rpm-ostree.h22
-rw-r--r--plugins/rpm-ostree/meson.build23
-rw-r--r--plugins/rpm-ostree/org.projectatomic.rpmostree1.xml458
4 files changed, 3381 insertions, 0 deletions
diff --git a/plugins/rpm-ostree/gs-plugin-rpm-ostree.c b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
new file mode 100644
index 0000000..b983c24
--- /dev/null
+++ b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
@@ -0,0 +1,2878 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2017-2020 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+
+#include <gnome-software.h>
+
+#include <fcntl.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <libdnf/libdnf.h>
+#include <ostree.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlib.h>
+#include <rpm/rpmts.h>
+#include <rpmostree.h>
+
+#include "gs-plugin-private.h"
+#include "gs-plugin-rpm-ostree.h"
+#include "gs-rpmostree-generated.h"
+
+/*
+ * SECTION:
+ * Exposes rpm-ostree system updates and overlays.
+ *
+ * The plugin has a worker thread which all operations are delegated to, as
+ * while the rpm-ostreed API is asynchronous over D-Bus, the plugin also needs
+ * to use lower level libostree and libdnf APIs which are entirely synchronous.
+ * Message passing to the worker thread is by gs_worker_thread_queue().
+ */
+
+/* This shows up in the `rpm-ostree status` as the software that
+ * initiated the update.
+ */
+#define GS_RPMOSTREE_CLIENT_ID PACKAGE_NAME
+
+/* How long to wait between two consecutive requests, before considering
+ * the connection to the rpm-ostree daemon inactive and disconnect from it.
+ */
+#define INACTIVE_TIMEOUT_SECONDS 60
+
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(Header, headerFree, NULL)
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(rpmts, rpmtsFree, NULL);
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(rpmdbMatchIterator, rpmdbFreeIterator, NULL);
+
+struct _GsPluginRpmOstree {
+ GsPlugin parent;
+
+ GsWorkerThread *worker; /* (owned) */
+
+ GMutex mutex;
+ GsRPMOSTreeOS *os_proxy;
+ GsRPMOSTreeSysroot *sysroot_proxy;
+ OstreeRepo *ot_repo;
+ OstreeSysroot *ot_sysroot;
+ DnfContext *dnf_context;
+ gboolean update_triggered;
+ guint inactive_timeout_id;
+};
+
+G_DEFINE_TYPE (GsPluginRpmOstree, gs_plugin_rpm_ostree, GS_TYPE_PLUGIN)
+
+#define assert_in_worker(self) \
+ g_assert (gs_worker_thread_is_in_worker_context (self->worker))
+
+static void
+gs_plugin_rpm_ostree_dispose (GObject *object)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (object);
+
+ g_clear_handle_id (&self->inactive_timeout_id, g_source_remove);
+ g_clear_object (&self->os_proxy);
+ g_clear_object (&self->sysroot_proxy);
+ g_clear_object (&self->ot_sysroot);
+ g_clear_object (&self->ot_repo);
+ g_clear_object (&self->dnf_context);
+ g_clear_object (&self->worker);
+
+ G_OBJECT_CLASS (gs_plugin_rpm_ostree_parent_class)->dispose (object);
+}
+
+static void
+gs_plugin_rpm_ostree_finalize (GObject *object)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (object);
+
+ g_mutex_clear (&self->mutex);
+
+ G_OBJECT_CLASS (gs_plugin_rpm_ostree_parent_class)->finalize (object);
+}
+
+static void
+gs_plugin_rpm_ostree_init (GsPluginRpmOstree *self)
+{
+ /* only works on OSTree */
+ if (!g_file_test ("/run/ostree-booted", G_FILE_TEST_EXISTS)) {
+ gs_plugin_set_enabled (GS_PLUGIN (self), FALSE);
+ return;
+ }
+
+ g_mutex_init (&self->mutex);
+
+ /* open transaction */
+ rpmReadConfigFiles (NULL, NULL);
+
+ /* rpm-ostree is already a daemon with a DBus API; hence it makes
+ * more sense to use a custom plugin instead of using PackageKit.
+ */
+ gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_CONFLICTS, "packagekit");
+
+ /* need pkgname */
+ gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+}
+
+static void
+gs_rpmostree_error_convert (GError **perror)
+{
+ GError *error = perror != NULL ? *perror : NULL;
+
+ /* not set */
+ if (error == NULL)
+ return;
+
+ /* parse remote RPM_OSTREED_ERROR */
+ if (g_dbus_error_is_remote_error (error)) {
+ g_autofree gchar *remote_error = g_dbus_error_get_remote_error (error);
+
+ g_dbus_error_strip_remote_error (error);
+
+ if (g_strcmp0 (remote_error, "org.projectatomic.rpmostreed.Error.NotAuthorized") == 0) {
+ error->code = GS_PLUGIN_ERROR_NO_SECURITY;
+ } else if (g_str_has_prefix (remote_error, "org.projectatomic.rpmostreed.Error")) {
+ error->code = GS_PLUGIN_ERROR_FAILED;
+ } else {
+ g_warning ("can't reliably fixup remote error %s", remote_error);
+ error->code = GS_PLUGIN_ERROR_FAILED;
+ }
+ error->domain = GS_PLUGIN_ERROR;
+ return;
+ }
+
+ /* this are allowed for low-level errors */
+ if (gs_utils_error_convert_gio (perror))
+ return;
+
+ /* this are allowed for low-level errors */
+ if (gs_utils_error_convert_gdbus (perror))
+ return;
+}
+
+static void
+gs_rpmostree_unregister_client_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!gs_rpmostree_sysroot_call_unregister_client_finish (GS_RPMOSTREE_SYSROOT (source_object), result, &error))
+ g_debug ("Failed to unregister client: %s", error->message);
+ else
+ g_debug ("Unregistered client from the rpm-ostreed");
+}
+
+static gboolean
+gs_rpmostree_inactive_timeout_cb (gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (user_data);
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (g_source_is_destroyed (g_main_current_source ()))
+ return G_SOURCE_REMOVE;
+
+ locker = g_mutex_locker_new (&self->mutex);
+
+ /* In case it gets destroyed before the lock is acquired */
+ if (!g_source_is_destroyed (g_main_current_source ()) &&
+ self->inactive_timeout_id == g_source_get_id (g_main_current_source ())) {
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+
+ if (self->sysroot_proxy)
+ sysroot_proxy = g_steal_pointer (&self->sysroot_proxy);
+
+ g_clear_object (&self->os_proxy);
+ g_clear_object (&self->sysroot_proxy);
+ g_clear_object (&self->ot_sysroot);
+ g_clear_object (&self->ot_repo);
+ g_clear_object (&self->dnf_context);
+ self->inactive_timeout_id = 0;
+
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ if (sysroot_proxy) {
+ g_autoptr(GVariantBuilder) options_builder = NULL;
+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (options_builder, "{sv}", "id",
+ g_variant_new_string (GS_RPMOSTREE_CLIENT_ID));
+ gs_rpmostree_sysroot_call_unregister_client (sysroot_proxy,
+ g_variant_builder_end (options_builder),
+ NULL,
+ gs_rpmostree_unregister_client_done_cb,
+ NULL);
+ }
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+/* Hold the plugin mutex when called */
+static gboolean
+gs_rpmostree_ref_proxies_locked (GsPluginRpmOstree *self,
+ GsRPMOSTreeOS **out_os_proxy,
+ GsRPMOSTreeSysroot **out_sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (self->inactive_timeout_id) {
+ g_source_remove (self->inactive_timeout_id);
+ self->inactive_timeout_id = 0;
+ }
+
+ /* Create a proxy for sysroot */
+ if (self->sysroot_proxy == NULL) {
+ g_autoptr(GVariantBuilder) options_builder = NULL;
+
+ self->sysroot_proxy = gs_rpmostree_sysroot_proxy_new_sync (gs_plugin_get_system_bus_connection (GS_PLUGIN (self)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.projectatomic.rpmostree1",
+ "/org/projectatomic/rpmostree1/Sysroot",
+ cancellable,
+ error);
+ if (self->sysroot_proxy == NULL) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (options_builder, "{sv}", "id",
+ g_variant_new_string (GS_RPMOSTREE_CLIENT_ID));
+ /* Register as a client so that the rpm-ostree daemon doesn't exit */
+ if (!gs_rpmostree_sysroot_call_register_client_sync (self->sysroot_proxy,
+ g_variant_builder_end (options_builder),
+ cancellable,
+ error)) {
+ g_clear_object (&self->sysroot_proxy);
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ g_debug ("Registered client on the rpm-ostreed");
+ }
+
+ /* Create a proxy for currently booted OS */
+ if (self->os_proxy == NULL) {
+ g_autofree gchar *os_object_path = NULL;
+
+ os_object_path = gs_rpmostree_sysroot_dup_booted (self->sysroot_proxy);
+ if (os_object_path == NULL &&
+ !gs_rpmostree_sysroot_call_get_os_sync (self->sysroot_proxy,
+ "",
+ &os_object_path,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ g_clear_object (&self->sysroot_proxy);
+ return FALSE;
+ }
+
+ self->os_proxy = gs_rpmostree_os_proxy_new_sync (gs_plugin_get_system_bus_connection (GS_PLUGIN (self)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.projectatomic.rpmostree1",
+ os_object_path,
+ cancellable,
+ error);
+ if (self->os_proxy == NULL) {
+ gs_rpmostree_error_convert (error);
+ g_clear_object (&self->sysroot_proxy);
+ return FALSE;
+ }
+ }
+
+ /* Load ostree sysroot and repo */
+ if (self->ot_sysroot == NULL) {
+ g_autofree gchar *sysroot_path = NULL;
+ g_autoptr(GFile) sysroot_file = NULL;
+
+ sysroot_path = gs_rpmostree_sysroot_dup_path (self->sysroot_proxy);
+ sysroot_file = g_file_new_for_path (sysroot_path);
+
+ self->ot_sysroot = ostree_sysroot_new (sysroot_file);
+ if (!ostree_sysroot_load (self->ot_sysroot, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ g_clear_object (&self->sysroot_proxy);
+ g_clear_object (&self->os_proxy);
+ g_clear_object (&self->ot_sysroot);
+ return FALSE;
+ }
+
+ if (!ostree_sysroot_get_repo (self->ot_sysroot, &self->ot_repo, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ g_clear_object (&self->sysroot_proxy);
+ g_clear_object (&self->os_proxy);
+ g_clear_object (&self->ot_sysroot);
+ return FALSE;
+ }
+ }
+
+ self->inactive_timeout_id = g_timeout_add_seconds (INACTIVE_TIMEOUT_SECONDS,
+ gs_rpmostree_inactive_timeout_cb, self);
+
+ if (out_os_proxy)
+ *out_os_proxy = g_object_ref (self->os_proxy);
+
+ if (out_sysroot_proxy)
+ *out_sysroot_proxy = g_object_ref (self->sysroot_proxy);
+
+ return TRUE;
+}
+
+static gboolean
+gs_rpmostree_ref_proxies (GsPluginRpmOstree *self,
+ GsRPMOSTreeOS **out_os_proxy,
+ GsRPMOSTreeSysroot **out_sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ locker = g_mutex_locker_new (&self->mutex);
+
+ return gs_rpmostree_ref_proxies_locked (self, out_os_proxy, out_sysroot_proxy, cancellable, error);
+}
+
+static gint
+get_priority_for_interactivity (gboolean interactive)
+{
+ return interactive ? G_PRIORITY_DEFAULT : G_PRIORITY_LOW;
+}
+
+static void setup_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_setup_async (GsPlugin *plugin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+
+ g_debug ("rpm-ostree version: %s", RPM_OSTREE_VERSION_S);
+
+ task = g_task_new (plugin, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_setup_async);
+
+ /* Start up a worker thread to process all the plugin’s function calls. */
+ self->worker = gs_worker_thread_new ("gs-plugin-rpm-ostree");
+
+ /* Queue a job to set up the D-Bus proxies. While these could be set
+ * up from the main thread asynchronously, setting them up in the worker
+ * thread means their signal emissions will correctly be in the worker
+ * thread, and locking is simpler. */
+ gs_worker_thread_queue (self->worker, G_PRIORITY_DEFAULT,
+ setup_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker. */
+static void
+setup_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (source_object);
+ g_autoptr(GError) local_error = NULL;
+
+ assert_in_worker (self);
+
+ if (!gs_rpmostree_ref_proxies (self, NULL, NULL, cancellable, &local_error))
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_rpm_ostree_setup_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void shutdown_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+static void
+gs_plugin_rpm_ostree_shutdown_async (GsPlugin *plugin,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_shutdown_async);
+
+ /* Stop checking for inactivity. */
+ g_clear_handle_id (&self->inactive_timeout_id, g_source_remove);
+
+ /* Stop the worker thread. */
+ gs_worker_thread_shutdown_async (self->worker, cancellable, shutdown_cb, g_steal_pointer (&task));
+}
+
+static void
+shutdown_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = G_TASK (user_data);
+ GsPluginRpmOstree *self = g_task_get_source_object (task);
+ g_autoptr(GsWorkerThread) worker = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ worker = g_steal_pointer (&self->worker);
+
+ if (!gs_worker_thread_shutdown_finish (worker, result, &local_error))
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_rpm_ostree_shutdown_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+app_set_rpm_ostree_packaging_format (GsApp *app)
+{
+ gs_app_set_metadata (app, "GnomeSoftware::PackagingFormat", "RPM");
+ gs_app_set_metadata (app, "GnomeSoftware::PackagingBaseCssColor", "error_color");
+}
+
+void
+gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+{
+ if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE &&
+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM) {
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ }
+
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_OPERATING_SYSTEM) {
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ }
+}
+
+typedef struct {
+ GsPlugin *plugin;
+ GError *error;
+ GMainContext *context;
+ GsApp *app;
+ gboolean complete;
+ gboolean owner_changed;
+} TransactionProgress;
+
+static TransactionProgress *
+transaction_progress_new (void)
+{
+ TransactionProgress *self;
+
+ self = g_slice_new0 (TransactionProgress);
+ self->context = g_main_context_ref_thread_default ();
+
+ return self;
+}
+
+static void
+transaction_progress_free (TransactionProgress *self)
+{
+ g_clear_object (&self->plugin);
+ g_clear_error (&self->error);
+ g_main_context_unref (self->context);
+ g_clear_object (&self->app);
+ g_slice_free (TransactionProgress, self);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(TransactionProgress, transaction_progress_free);
+
+static void
+transaction_progress_end (TransactionProgress *self)
+{
+ self->complete = TRUE;
+ g_main_context_wakeup (self->context);
+}
+
+static void
+on_transaction_progress (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ TransactionProgress *tp = user_data;
+
+ if (g_strcmp0 (signal_name, "PercentProgress") == 0) {
+ const gchar *message = NULL;
+ guint32 percentage;
+
+ g_variant_get_child (parameters, 0, "&s", &message);
+ g_variant_get_child (parameters, 1, "u", &percentage);
+ g_debug ("PercentProgress: %u, %s\n", percentage, message);
+
+ if (tp->app != NULL)
+ gs_app_set_progress (tp->app, (guint) percentage);
+
+ if (tp->app != NULL && tp->plugin != NULL) {
+ GsPluginStatus plugin_status;
+
+ switch (gs_app_get_state (tp->app)) {
+ case GS_APP_STATE_INSTALLING:
+ plugin_status = GS_PLUGIN_STATUS_INSTALLING;
+ break;
+ case GS_APP_STATE_REMOVING:
+ plugin_status = GS_PLUGIN_STATUS_REMOVING;
+ break;
+ default:
+ plugin_status = GS_PLUGIN_STATUS_DOWNLOADING;
+ break;
+ }
+ gs_plugin_status_update (tp->plugin, tp->app, plugin_status);
+ }
+ } else if (g_strcmp0 (signal_name, "Finished") == 0) {
+ if (tp->error == NULL) {
+ g_autofree gchar *error_message = NULL;
+ gboolean success = FALSE;
+
+ g_variant_get (parameters, "(bs)", &success, &error_message);
+
+ if (!success) {
+ tp->error = g_dbus_error_new_for_dbus_error ("org.projectatomic.rpmostreed.Error.Failed",
+ error_message);
+ }
+ }
+
+ transaction_progress_end (tp);
+ }
+}
+
+static void
+on_owner_notify (GObject *obj,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ TransactionProgress *tp = user_data;
+
+ tp->owner_changed = TRUE;
+
+ /* Wake up the context so it can notice the server has disappeared. */
+ g_main_context_wakeup (tp->context);
+}
+
+static void
+cancelled_handler (GCancellable *cancellable,
+ gpointer user_data)
+{
+ GsRPMOSTreeTransaction *transaction = user_data;
+ gs_rpmostree_transaction_call_cancel_sync (transaction, NULL, NULL);
+}
+
+static gboolean
+gs_rpmostree_transaction_get_response_sync (GsRPMOSTreeSysroot *sysroot_proxy,
+ const gchar *transaction_address,
+ TransactionProgress *tp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsRPMOSTreeTransaction *transaction = NULL;
+ g_autoptr(GDBusConnection) peer_connection = NULL;
+ gint cancel_handler = 0;
+ gulong signal_handler = 0;
+ gulong notify_handler = 0;
+ gboolean success = FALSE;
+ gboolean just_started = FALSE;
+ gboolean saw_name_owner = FALSE;
+ g_autofree gchar *name_owner = NULL;
+
+ peer_connection = g_dbus_connection_new_for_address_sync (transaction_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL,
+ cancellable,
+ error);
+
+ if (peer_connection == NULL)
+ goto out;
+
+ transaction = gs_rpmostree_transaction_proxy_new_sync (peer_connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "/",
+ cancellable,
+ error);
+ if (transaction == NULL)
+ goto out;
+
+ if (cancellable) {
+ /* setup cancel handler */
+ cancel_handler = g_cancellable_connect (cancellable,
+ G_CALLBACK (cancelled_handler),
+ transaction, NULL);
+ }
+
+ signal_handler = g_signal_connect (transaction, "g-signal",
+ G_CALLBACK (on_transaction_progress),
+ tp);
+
+ notify_handler = g_signal_connect (transaction, "notify::g-name-owner",
+ G_CALLBACK (on_owner_notify),
+ tp);
+
+ /* Tell the server we're ready to receive signals. */
+ if (!gs_rpmostree_transaction_call_start_sync (transaction,
+ &just_started,
+ cancellable,
+ error))
+ goto out;
+
+ /* Process all the signals until we receive the Finished signal or the
+ * daemon disappears (which can happen if it crashes).
+ *
+ * The property can be NULL right after connecting to it, before the D-Bus
+ * transfers the property value to the client. */
+ while (!tp->complete &&
+ !g_cancellable_is_cancelled (cancellable) &&
+ ((name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (transaction))) != NULL ||
+ (!saw_name_owner && !tp->owner_changed))) {
+ saw_name_owner = saw_name_owner || name_owner != NULL;
+ g_clear_pointer (&name_owner, g_free);
+ g_main_context_iteration (tp->context, TRUE);
+ }
+
+ if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ if (tp->error) {
+ g_propagate_error (error, g_steal_pointer (&tp->error));
+ } else if (!tp->complete && name_owner == NULL) {
+ g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY,
+ "Daemon disappeared");
+ } else {
+ success = TRUE;
+ }
+ }
+
+out:
+ if (cancel_handler)
+ g_cancellable_disconnect (cancellable, cancel_handler);
+ if (notify_handler != 0)
+ g_signal_handler_disconnect (transaction, notify_handler);
+ if (signal_handler)
+ g_signal_handler_disconnect (transaction, signal_handler);
+ if (transaction != NULL)
+ g_object_unref (transaction);
+
+ return success;
+}
+
+/* FIXME: Refactor this once rpmostree returns a specific error code
+ * for ‘transaction in progress’, to avoid the slight race here where
+ * gnome-software could return from this function just as another client
+ * starts a new transaction.
+ * https://github.com/coreos/rpm-ostree/issues/3070 */
+static gboolean
+gs_rpmostree_wait_for_ongoing_transaction_end (GsRPMOSTreeSysroot *sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *current_path = NULL;
+ g_autoptr(GMainContext) main_context = NULL;
+ gulong notify_handler, cancelled_handler = 0;
+
+ current_path = gs_rpmostree_sysroot_dup_active_transaction_path (sysroot_proxy);
+ if (current_path == NULL || *current_path == '\0')
+ return TRUE;
+
+ main_context = g_main_context_ref_thread_default ();
+
+ notify_handler = g_signal_connect_swapped (sysroot_proxy, "notify::active-transaction-path",
+ G_CALLBACK (g_main_context_wakeup), main_context);
+ if (cancellable) {
+ /* Not using g_cancellable_connect() here for simplicity and because checking the state below anyway. */
+ cancelled_handler = g_signal_connect_swapped (cancellable, "cancelled",
+ G_CALLBACK (g_main_context_wakeup), main_context);
+ }
+
+ while (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ g_clear_pointer (&current_path, g_free);
+ current_path = gs_rpmostree_sysroot_dup_active_transaction_path (sysroot_proxy);
+ if (current_path == NULL || *current_path == '\0') {
+ g_clear_signal_handler (&notify_handler, sysroot_proxy);
+ g_clear_signal_handler (&cancelled_handler, cancellable);
+ return TRUE;
+ }
+ g_main_context_iteration (main_context, TRUE);
+ }
+
+ g_clear_signal_handler (&notify_handler, sysroot_proxy);
+ g_clear_signal_handler (&cancelled_handler, cancellable);
+
+ gs_rpmostree_error_convert (error);
+
+ return FALSE;
+}
+
+static GsApp *
+app_from_modified_pkg_variant (GsPlugin *plugin, GVariant *variant)
+{
+ g_autoptr(GsApp) app = NULL;
+ const char *name;
+ const char *old_evr, *old_arch;
+ const char *new_evr, *new_arch;
+ g_autofree char *old_nevra = NULL;
+ g_autofree char *new_nevra = NULL;
+
+ g_variant_get (variant, "(us(ss)(ss))", NULL /* type*/, &name, &old_evr, &old_arch, &new_evr, &new_arch);
+ old_nevra = g_strdup_printf ("%s-%s-%s", name, old_evr, old_arch);
+ new_nevra = g_strdup_printf ("%s-%s-%s", name, new_evr, new_arch);
+
+ app = gs_plugin_cache_lookup (plugin, old_nevra);
+ if (app != NULL)
+ return g_steal_pointer (&app);
+
+ /* create new app */
+ app = gs_app_new (NULL);
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM);
+
+ /* update or downgrade */
+ gs_app_add_source (app, name);
+ gs_app_set_version (app, old_evr);
+ gs_app_set_update_version (app, new_evr);
+ gs_app_set_state (app, GS_APP_STATE_UPDATABLE);
+
+ g_debug ("!%s\n", old_nevra);
+ g_debug ("=%s\n", new_nevra);
+
+ gs_plugin_cache_add (plugin, old_nevra, app);
+ return g_steal_pointer (&app);
+}
+
+static GsApp *
+app_from_single_pkg_variant (GsPlugin *plugin, GVariant *variant, gboolean addition)
+{
+ g_autoptr(GsApp) app = NULL;
+ const char *name;
+ const char *evr;
+ const char *arch;
+ g_autofree char *nevra = NULL;
+
+ g_variant_get (variant, "(usss)", NULL /* type*/, &name, &evr, &arch);
+ nevra = g_strdup_printf ("%s-%s-%s", name, evr, arch);
+
+ app = gs_plugin_cache_lookup (plugin, nevra);
+ if (app != NULL)
+ return g_steal_pointer (&app);
+
+ /* create new app */
+ app = gs_app_new (NULL);
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM);
+
+ if (addition) {
+ /* addition */
+ gs_app_add_source (app, name);
+ gs_app_set_version (app, evr);
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
+ g_debug ("+%s\n", nevra);
+ } else {
+ /* removal */
+ gs_app_add_source (app, name);
+ gs_app_set_version (app, evr);
+ gs_app_set_state (app, GS_APP_STATE_UNAVAILABLE);
+
+ g_debug ("-%s\n", nevra);
+ }
+
+ gs_plugin_cache_add (plugin, nevra, app);
+ return g_steal_pointer (&app);
+}
+
+static GVariant *
+make_rpmostree_options_variant (gboolean reboot,
+ gboolean allow_downgrade,
+ gboolean cache_only,
+ gboolean download_only,
+ gboolean skip_purge,
+ gboolean no_pull_base,
+ gboolean dry_run,
+ gboolean no_overrides)
+{
+ GVariantDict dict;
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert (&dict, "reboot", "b", reboot);
+ g_variant_dict_insert (&dict, "allow-downgrade", "b", allow_downgrade);
+ g_variant_dict_insert (&dict, "cache-only", "b", cache_only);
+ g_variant_dict_insert (&dict, "download-only", "b", download_only);
+ g_variant_dict_insert (&dict, "skip-purge", "b", skip_purge);
+ g_variant_dict_insert (&dict, "no-pull-base", "b", no_pull_base);
+ g_variant_dict_insert (&dict, "dry-run", "b", dry_run);
+ g_variant_dict_insert (&dict, "no-overrides", "b", no_overrides);
+ return g_variant_ref_sink (g_variant_dict_end (&dict));
+}
+
+static GVariant *
+make_refresh_md_options_variant (gboolean force)
+{
+ GVariantDict dict;
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert (&dict, "force", "b", force);
+ return g_variant_ref_sink (g_variant_dict_end (&dict));
+}
+
+static gboolean
+make_rpmostree_modifiers_variant (const char *install_package,
+ const char *uninstall_package,
+ const char *install_local_package,
+ GVariant **out_modifiers,
+ GUnixFDList **out_fd_list,
+ GError **error)
+{
+ GVariantDict dict;
+ g_autoptr(GUnixFDList) fd_list = g_unix_fd_list_new ();
+
+ g_variant_dict_init (&dict, NULL);
+
+ if (install_package != NULL) {
+ g_autoptr(GPtrArray) repo_pkgs = g_ptr_array_new ();
+
+ g_ptr_array_add (repo_pkgs, (gpointer) install_package);
+
+ g_variant_dict_insert_value (&dict, "install-packages",
+ g_variant_new_strv ((const char *const*)repo_pkgs->pdata,
+ repo_pkgs->len));
+
+ }
+
+ if (uninstall_package != NULL) {
+ g_autoptr(GPtrArray) repo_pkgs = g_ptr_array_new ();
+
+ g_ptr_array_add (repo_pkgs, (gpointer) uninstall_package);
+
+ g_variant_dict_insert_value (&dict, "uninstall-packages",
+ g_variant_new_strv ((const char *const*)repo_pkgs->pdata,
+ repo_pkgs->len));
+
+ }
+
+ if (install_local_package != NULL) {
+ g_auto(GVariantBuilder) builder;
+ int fd;
+ int idx;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ah"));
+
+ fd = openat (AT_FDCWD, install_local_package, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ if (fd == -1) {
+ g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+ "Failed to open %s", install_local_package);
+ return FALSE;
+ }
+
+ idx = g_unix_fd_list_append (fd_list, fd, error);
+ if (idx < 0) {
+ close (fd);
+ return FALSE;
+ }
+
+ g_variant_builder_add (&builder, "h", idx);
+ g_variant_dict_insert_value (&dict, "install-local-packages",
+ g_variant_new ("ah", &builder));
+ close (fd);
+ }
+
+ *out_fd_list = g_steal_pointer (&fd_list);
+ *out_modifiers = g_variant_ref_sink (g_variant_dict_end (&dict));
+ return TRUE;
+}
+
+static gboolean
+rpmostree_update_deployment (GsRPMOSTreeOS *os_proxy,
+ const char *install_package,
+ const char *uninstall_package,
+ const char *install_local_package,
+ GVariant *options,
+ char **out_transaction_address,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GUnixFDList) fd_list = NULL;
+ g_autoptr(GVariant) modifiers = NULL;
+
+ if (!make_rpmostree_modifiers_variant (install_package,
+ uninstall_package,
+ install_local_package,
+ &modifiers, &fd_list, error))
+ return FALSE;
+
+ return gs_rpmostree_os_call_update_deployment_sync (os_proxy,
+ modifiers,
+ options,
+ fd_list,
+ out_transaction_address,
+ NULL,
+ cancellable,
+ error);
+}
+
+#define RPMOSTREE_CORE_CACHEDIR "/var/cache/rpm-ostree/"
+#define RPMOSTREE_DIR_CACHE_REPOMD "repomd"
+#define RPMOSTREE_DIR_CACHE_SOLV "solv"
+
+static DnfContext *
+gs_rpmostree_create_bare_dnf_context (GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(DnfContext) context = dnf_context_new ();
+
+ dnf_context_set_repo_dir (context, "/etc/yum.repos.d");
+ dnf_context_set_cache_dir (context, RPMOSTREE_CORE_CACHEDIR RPMOSTREE_DIR_CACHE_REPOMD);
+ dnf_context_set_solv_dir (context, RPMOSTREE_CORE_CACHEDIR RPMOSTREE_DIR_CACHE_SOLV);
+ dnf_context_set_cache_age (context, G_MAXUINT);
+ dnf_context_set_enable_filelists (context, FALSE);
+
+ if (!dnf_context_setup (context, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return NULL;
+ }
+
+ return g_steal_pointer (&context);
+}
+
+static gboolean
+gs_rpmostree_ref_dnf_context_locked (GsPluginRpmOstree *self,
+ GsRPMOSTreeOS **out_os_proxy,
+ GsRPMOSTreeSysroot **out_sysroot_proxy,
+ DnfContext **out_dnf_context,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(DnfContext) context = NULL;
+ g_autoptr(DnfState) state = NULL;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+
+ if (!gs_rpmostree_ref_proxies_locked (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ if (self->dnf_context != NULL) {
+ if (out_os_proxy)
+ *out_os_proxy = g_steal_pointer (&os_proxy);
+ if (out_sysroot_proxy)
+ *out_sysroot_proxy = g_steal_pointer (&sysroot_proxy);
+ if (out_dnf_context)
+ *out_dnf_context = g_object_ref (self->dnf_context);
+ return TRUE;
+ }
+
+ context = gs_rpmostree_create_bare_dnf_context (cancellable, error);
+ if (!context)
+ return FALSE;
+
+ state = dnf_state_new ();
+
+ if (!dnf_context_setup_sack_with_flags (context, state, DNF_CONTEXT_SETUP_SACK_FLAG_SKIP_RPMDB, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ g_set_object (&self->dnf_context, context);
+
+ if (out_os_proxy)
+ *out_os_proxy = g_steal_pointer (&os_proxy);
+ if (out_sysroot_proxy)
+ *out_sysroot_proxy = g_steal_pointer (&sysroot_proxy);
+ if (out_dnf_context)
+ *out_dnf_context = g_object_ref (self->dnf_context);
+
+ return TRUE;
+}
+
+static void refresh_metadata_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_refresh_metadata_async (GsPlugin *plugin,
+ guint64 cache_age_secs,
+ GsPluginRefreshMetadataFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+ gboolean interactive = (flags & GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE);
+
+ task = g_task_new (plugin, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_refresh_metadata_async);
+ g_task_set_task_data (task, gs_plugin_refresh_metadata_data_new (cache_age_secs, flags), (GDestroyNotify) gs_plugin_refresh_metadata_data_free);
+
+ gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive),
+ refresh_metadata_thread_cb, g_steal_pointer (&task));
+}
+
+static gboolean
+gs_plugin_rpm_ostree_refresh_metadata_in_worker (GsPluginRpmOstree *self,
+ GsPluginRefreshMetadataData *data,
+ GsRPMOSTreeOS *os_proxy,
+ GsRPMOSTreeSysroot *sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPlugin *plugin = GS_PLUGIN (self);
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ assert_in_worker (self);
+
+ {
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GsApp) progress_app = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = NULL;
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ progress_app = gs_app_new (gs_plugin_get_name (plugin));
+ tp = transaction_progress_new ();
+ tp->app = g_object_ref (progress_app);
+ tp->plugin = g_object_ref (plugin);
+
+ options = make_refresh_md_options_variant (FALSE /* force */);
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_refresh_md_sync (os_proxy,
+ options,
+ &transaction_address,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+ done = FALSE;
+ continue;
+ }
+
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (data->cache_age_secs == G_MAXUINT64)
+ return TRUE;
+
+ {
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GsApp) progress_app = gs_app_new (gs_plugin_get_name (plugin));
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ tp->app = g_object_ref (progress_app);
+ tp->plugin = g_object_ref (plugin);
+
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ FALSE, /* allow-downgrade */
+ FALSE, /* cache-only */
+ TRUE, /* download-only */
+ FALSE, /* skip-purge */
+ FALSE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_upgrade_sync (os_proxy,
+ options,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+ done = FALSE;
+ continue;
+ }
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ {
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GsApp) progress_app = gs_app_new (gs_plugin_get_name (plugin));
+ g_autoptr(GVariant) options = NULL;
+ GVariantDict dict;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ tp->app = g_object_ref (progress_app);
+ tp->plugin = g_object_ref (plugin);
+
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert (&dict, "mode", "s", "check");
+ options = g_variant_ref_sink (g_variant_dict_end (&dict));
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_automatic_update_trigger_sync (os_proxy,
+ options,
+ NULL,
+ &transaction_address,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+ done = FALSE;
+ continue;
+ }
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ /* update UI */
+ gs_plugin_updates_changed (plugin);
+
+ return TRUE;
+}
+
+static void
+refresh_metadata_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPlugin *plugin = GS_PLUGIN (source_object);
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ GsPluginRefreshMetadataData *data = task_data;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ assert_in_worker (self);
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, &local_error)) {
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ return;
+ }
+
+ if (gs_plugin_rpm_ostree_refresh_metadata_in_worker (self, data, os_proxy, sysroot_proxy, cancellable, &local_error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, g_steal_pointer (&local_error));
+}
+
+static gboolean
+gs_plugin_rpm_ostree_refresh_metadata_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GVariant) cached_update = NULL;
+ g_autoptr(GVariant) rpm_diff = NULL;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ const gchar *checksum = NULL;
+ const gchar *version = NULL;
+ g_auto(GVariantDict) cached_update_dict;
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, &local_error)) {
+ g_debug ("Failed to ref proxies to get updates: %s", local_error->message);
+ return TRUE;
+ }
+
+ /* ensure D-Bus properties are updated before reading them */
+ if (!gs_rpmostree_sysroot_call_reload_sync (sysroot_proxy, cancellable, &local_error)) {
+ g_debug ("Failed to call reload to get updates: %s", local_error->message);
+ return TRUE;
+ }
+
+ cached_update = gs_rpmostree_os_dup_cached_update (os_proxy);
+ g_variant_dict_init (&cached_update_dict, cached_update);
+
+ if (!g_variant_dict_lookup (&cached_update_dict, "checksum", "&s", &checksum))
+ return TRUE;
+ if (!g_variant_dict_lookup (&cached_update_dict, "version", "&s", &version))
+ return TRUE;
+
+ g_debug ("got CachedUpdate version '%s', checksum '%s'", version, checksum);
+
+ rpm_diff = g_variant_dict_lookup_value (&cached_update_dict, "rpm-diff", G_VARIANT_TYPE ("a{sv}"));
+ if (rpm_diff != NULL) {
+ GVariantIter iter;
+ GVariant *child;
+ g_autoptr(GVariant) upgraded = NULL;
+ g_autoptr(GVariant) downgraded = NULL;
+ g_autoptr(GVariant) removed = NULL;
+ g_autoptr(GVariant) added = NULL;
+ g_auto(GVariantDict) rpm_diff_dict;
+ g_variant_dict_init (&rpm_diff_dict, rpm_diff);
+
+ upgraded = g_variant_dict_lookup_value (&rpm_diff_dict, "upgraded", G_VARIANT_TYPE ("a(us(ss)(ss))"));
+ if (upgraded == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no 'upgraded' in rpm-diff dict");
+ return FALSE;
+ }
+ downgraded = g_variant_dict_lookup_value (&rpm_diff_dict, "downgraded", G_VARIANT_TYPE ("a(us(ss)(ss))"));
+ if (downgraded == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no 'downgraded' in rpm-diff dict");
+ return FALSE;
+ }
+ removed = g_variant_dict_lookup_value (&rpm_diff_dict, "removed", G_VARIANT_TYPE ("a(usss)"));
+ if (removed == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no 'removed' in rpm-diff dict");
+ return FALSE;
+ }
+ added = g_variant_dict_lookup_value (&rpm_diff_dict, "added", G_VARIANT_TYPE ("a(usss)"));
+ if (added == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no 'added' in rpm-diff dict");
+ return FALSE;
+ }
+
+ /* iterate over all upgraded packages and add them */
+ g_variant_iter_init (&iter, upgraded);
+ while ((child = g_variant_iter_next_value (&iter)) != NULL) {
+ g_autoptr(GsApp) app = app_from_modified_pkg_variant (plugin, child);
+ if (app != NULL)
+ gs_app_list_add (list, app);
+ g_variant_unref (child);
+ }
+
+ /* iterate over all downgraded packages and add them */
+ g_variant_iter_init (&iter, downgraded);
+ while ((child = g_variant_iter_next_value (&iter)) != NULL) {
+ g_autoptr(GsApp) app = app_from_modified_pkg_variant (plugin, child);
+ if (app != NULL)
+ gs_app_list_add (list, app);
+ g_variant_unref (child);
+ }
+
+ /* iterate over all removed packages and add them */
+ g_variant_iter_init (&iter, removed);
+ while ((child = g_variant_iter_next_value (&iter)) != NULL) {
+ g_autoptr(GsApp) app = app_from_single_pkg_variant (plugin, child, FALSE);
+ if (app != NULL)
+ gs_app_list_add (list, app);
+ g_variant_unref (child);
+ }
+
+ /* iterate over all added packages and add them */
+ g_variant_iter_init (&iter, added);
+ while ((child = g_variant_iter_next_value (&iter)) != NULL) {
+ g_autoptr(GsApp) app = app_from_single_pkg_variant (plugin, child, TRUE);
+ if (app != NULL)
+ gs_app_list_add (list, app);
+ g_variant_unref (child);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+trigger_rpmostree_update (GsPluginRpmOstree *self,
+ GsApp *app,
+ GsRPMOSTreeOS *os_proxy,
+ GsRPMOSTreeSysroot *sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ /* if we can process this online do not require a trigger */
+ if (gs_app_get_state (app) != GS_APP_STATE_UPDATABLE)
+ return TRUE;
+
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, GS_PLUGIN (self)))
+ return TRUE;
+
+ /* already in correct state */
+ if (self->update_triggered)
+ return TRUE;
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ /* trigger the update */
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ FALSE, /* allow-downgrade */
+ TRUE, /* cache-only */
+ FALSE, /* download-only */
+ FALSE, /* skip-purge */
+ FALSE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_upgrade_sync (os_proxy,
+ options,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ self->update_triggered = TRUE;
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ GsAppList *related = gs_app_get_related (app);
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ /* we don't currently don't put all updates in the OsUpdate proxy app */
+ if (!gs_app_has_quirk (app, GS_APP_QUIRK_IS_PROXY))
+ return trigger_rpmostree_update (self, app, os_proxy, sysroot_proxy, cancellable, error);
+
+ /* try to trigger each related app */
+ for (guint i = 0; i < gs_app_list_length (related); i++) {
+ GsApp *app_tmp = gs_app_list_index (related, i);
+ if (!trigger_rpmostree_update (self, app_tmp, os_proxy, sysroot_proxy, cancellable, error))
+ return FALSE;
+ }
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_upgrade_trigger (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ const char *packages[] = { NULL };
+ g_autofree gchar *new_refspec = NULL;
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, plugin))
+ return TRUE;
+
+ /* check is distro-upgrade */
+ if (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM)
+ return TRUE;
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ /* construct new refspec based on the distro version we're upgrading to */
+ new_refspec = g_strdup_printf ("ostree://fedora/%s/x86_64/silverblue",
+ gs_app_get_version (app));
+
+ /* trigger the upgrade */
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ TRUE, /* allow-downgrade */
+ TRUE, /* cache-only */
+ FALSE, /* download-only */
+ FALSE, /* skip-purge */
+ FALSE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_rebase_sync (os_proxy,
+ options,
+ new_refspec,
+ packages,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+
+ if (g_strrstr ((*error)->message, "Old and new refs are equal")) {
+ /* don't error out if the correct tree is already deployed */
+ g_debug ("ignoring rpm-ostree error: %s", (*error)->message);
+ g_clear_error (error);
+ } else {
+ return FALSE;
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+gs_rpmostree_repo_enable (GsPlugin *plugin,
+ GsApp *app,
+ gboolean enable,
+ GsRPMOSTreeOS *os_proxy,
+ GsRPMOSTreeSysroot *sysroot_proxy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariantBuilder) options_builder = NULL;
+ g_autoptr(TransactionProgress) tp = NULL;
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ if (enable)
+ gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+ else
+ gs_app_set_state (app, GS_APP_STATE_REMOVING);
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ g_clear_pointer (&options_builder, g_variant_builder_unref);
+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (options_builder, "{ss}", "enabled", enable ? "1" : "0");
+ if (!gs_rpmostree_os_call_modify_yum_repo_sync (os_proxy,
+ gs_app_get_id (app),
+ g_variant_builder_end (options_builder),
+ &transaction_address,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error)) {
+ gs_app_set_state_recover (app);
+ gs_utils_error_add_origin_id (error, app);
+ return FALSE;
+ }
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ gs_utils_error_add_origin_id (error, app);
+ return FALSE;
+ }
+ }
+
+ tp = transaction_progress_new ();
+ tp->app = g_object_ref (app);
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ gs_utils_error_add_origin_id (error, app);
+ return FALSE;
+ }
+
+
+ /* state is known */
+ if (enable)
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+ else
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
+ gs_plugin_repository_changed (plugin, app);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_install (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ const gchar *install_package = NULL;
+ g_autofree gchar *local_filename = NULL;
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, plugin))
+ return TRUE;
+
+ /* enable repo, handled by dedicated function */
+ g_return_val_if_fail (gs_app_get_kind (app) != AS_COMPONENT_KIND_REPOSITORY, FALSE);
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ switch (gs_app_get_state (app)) {
+ case GS_APP_STATE_AVAILABLE:
+ case GS_APP_STATE_QUEUED_FOR_INSTALL:
+ if (gs_app_get_source_default (app) == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "no source set");
+ return FALSE;
+ }
+
+ install_package = gs_app_get_source_default (app);
+ break;
+ case GS_APP_STATE_AVAILABLE_LOCAL:
+ if (gs_app_get_local_file (app) == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "local package, but no filename");
+ return FALSE;
+ }
+
+ local_filename = g_file_get_path (gs_app_get_local_file (app));
+ break;
+ default:
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "do not know how to install app in state %s",
+ gs_app_state_to_string (gs_app_get_state (app)));
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+ tp->app = g_object_ref (app);
+
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ FALSE, /* allow-downgrade */
+ FALSE, /* cache-only */
+ FALSE, /* download-only */
+ FALSE, /* skip-purge */
+ TRUE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!rpmostree_update_deployment (os_proxy,
+ install_package,
+ NULL /* remove package */,
+ local_filename,
+ options,
+ &transaction_address,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error)) {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ /* state is known */
+ gs_app_set_state (app, GS_APP_STATE_PENDING_INSTALL);
+
+ /* get the new icon from the package */
+ gs_app_set_local_file (app, NULL);
+ gs_app_remove_all_icons (app);
+
+ /* no longer valid */
+ gs_app_clear_source_ids (app);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, plugin))
+ return TRUE;
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ /* disable repo, handled by dedicated function */
+ g_return_val_if_fail (gs_app_get_kind (app) != AS_COMPONENT_KIND_REPOSITORY, FALSE);
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ gs_app_set_state (app, GS_APP_STATE_REMOVING);
+ tp->app = g_object_ref (app);
+
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ FALSE, /* allow-downgrade */
+ TRUE, /* cache-only */
+ FALSE, /* download-only */
+ FALSE, /* skip-purge */
+ TRUE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!rpmostree_update_deployment (os_proxy,
+ NULL /* install package */,
+ gs_app_get_source_default (app),
+ NULL /* install local package */,
+ options,
+ &transaction_address,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error)) {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ if (gs_app_has_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT)) {
+ gs_app_set_state (app, GS_APP_STATE_PENDING_REMOVE);
+ } else {
+ /* state is not known: we don't know if we can re-install this app */
+ gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
+ }
+
+ return TRUE;
+}
+
+static DnfPackage *
+find_package_by_name (DnfSack *sack,
+ const char *pkgname)
+{
+ g_autoptr(GPtrArray) pkgs = NULL;
+ hy_autoquery HyQuery query = hy_query_create (sack);
+
+ hy_query_filter (query, HY_PKG_NAME, HY_EQ, pkgname);
+ hy_query_filter_latest_per_arch (query, TRUE);
+
+ pkgs = hy_query_run (query);
+ if (pkgs->len == 0)
+ return NULL;
+
+ return g_object_ref (pkgs->pdata[pkgs->len-1]);
+}
+
+static GPtrArray *
+find_packages_by_provides (DnfSack *sack,
+ gchar **search)
+{
+ g_autoptr(GPtrArray) pkgs = NULL;
+ hy_autoquery HyQuery query = hy_query_create (sack);
+
+ hy_query_filter_provides_in (query, search);
+ hy_query_filter_latest_per_arch (query, TRUE);
+
+ pkgs = hy_query_run (query);
+
+ return g_steal_pointer (&pkgs);
+}
+
+static gboolean
+gs_rpm_ostree_has_launchable (GsApp *app)
+{
+ const gchar *desktop_id;
+ GDesktopAppInfo *desktop_appinfo;
+
+ if (gs_app_has_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE) ||
+ gs_app_has_quirk (app, GS_APP_QUIRK_PARENTAL_NOT_LAUNCHABLE))
+ return FALSE;
+
+ desktop_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID);
+ if (!desktop_id)
+ desktop_id = gs_app_get_id (app);
+ if (!desktop_id)
+ return FALSE;
+
+ desktop_appinfo = gs_utils_get_desktop_app_info (desktop_id);
+ if (!desktop_appinfo)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+resolve_installed_packages_app (GsPlugin *plugin,
+ GHashTable *packages,
+ GHashTable *layered_packages,
+ GHashTable *layered_local_packages,
+ GsApp *app)
+{
+ RpmOstreePackage *pkg;
+
+ if (!gs_app_get_source_default (app))
+ return FALSE;
+
+ pkg = g_hash_table_lookup (packages, gs_app_get_source_default (app));
+
+ if (pkg) {
+ gs_app_set_version (app, rpm_ostree_package_get_evr (pkg));
+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) {
+ /* Kind of hack, pending installs do not have available the desktop file */
+ if (gs_app_get_kind (app) != AS_COMPONENT_KIND_DESKTOP_APP || gs_rpm_ostree_has_launchable (app))
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+ else
+ gs_app_set_state (app, GS_APP_STATE_PENDING_INSTALL);
+ }
+ if ((rpm_ostree_package_get_name (pkg) &&
+ g_hash_table_contains (layered_packages, rpm_ostree_package_get_name (pkg))) ||
+ (rpm_ostree_package_get_nevra (pkg) &&
+ g_hash_table_contains (layered_local_packages, rpm_ostree_package_get_nevra (pkg)))) {
+ /* layered packages can always be removed */
+ gs_app_remove_quirk (app, GS_APP_QUIRK_COMPULSORY);
+ } else {
+ /* can't remove packages that are part of the base system */
+ gs_app_add_quirk (app, GS_APP_QUIRK_COMPULSORY);
+ }
+ if (gs_app_get_origin (app) == NULL)
+ gs_app_set_origin (app, "rpm-ostree");
+ if (gs_app_get_name (app) == NULL)
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, rpm_ostree_package_get_name (pkg));
+ return TRUE /* found */;
+ }
+
+ return FALSE /* not found */;
+}
+
+static gboolean
+resolve_available_packages_app (GsPlugin *plugin,
+ DnfSack *sack,
+ GsApp *app)
+{
+ g_autoptr(DnfPackage) pkg = NULL;
+
+ pkg = find_package_by_name (sack, gs_app_get_source_default (app));
+ if (pkg != NULL) {
+ gs_app_set_version (app, dnf_package_get_evr (pkg));
+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
+ /* anything not part of the base system can be removed */
+ gs_app_remove_quirk (app, GS_APP_QUIRK_COMPULSORY);
+
+ /* set origin */
+ if (gs_app_get_origin (app) == NULL) {
+ const gchar *reponame = dnf_package_get_reponame (pkg);
+ gs_app_set_origin (app, reponame);
+ }
+
+ /* set more metadata for packages that don't have appstream data */
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, dnf_package_get_name (pkg));
+ gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, dnf_package_get_summary (pkg));
+
+ /* set hide-from-search quirk for available apps we don't want to show; results for non-installed desktop apps
+ * are intentionally hidden (as recommended by Matthias Clasen) by a special quirk because app layering
+ * should be intended for power users and not a common practice on Fedora Silverblue */
+ if (!gs_app_is_installed (app)) {
+ switch (gs_app_get_kind (app)) {
+ case AS_COMPONENT_KIND_DESKTOP_APP:
+ case AS_COMPONENT_KIND_WEB_APP:
+ case AS_COMPONENT_KIND_CONSOLE_APP:
+ gs_app_add_quirk (app, GS_APP_QUIRK_HIDE_FROM_SEARCH);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return TRUE /* found */;
+ }
+
+ return FALSE /* not found */;
+}
+
+static gboolean
+resolve_appstream_source_file_to_package_name (GsPlugin *plugin,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ Header h;
+ const gchar *fn;
+ gint rc;
+ g_auto(rpmdbMatchIterator) mi = NULL;
+ g_auto(rpmts) ts = NULL;
+
+ /* open db readonly */
+ ts = rpmtsCreate();
+ rpmtsSetRootDir (ts, NULL);
+ rc = rpmtsOpenDB (ts, O_RDONLY);
+ if (rc != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "Failed to open rpmdb: %i", rc);
+ return FALSE;
+ }
+
+ /* look for a specific file */
+ fn = gs_app_get_metadata_item (app, "appstream::source-file");
+ if (fn == NULL)
+ return TRUE;
+
+ mi = rpmtsInitIterator (ts, RPMDBI_INSTFILENAMES, fn, 0);
+ if (mi == NULL) {
+ g_debug ("rpm: no search results for %s", fn);
+ return TRUE;
+ }
+
+ /* process any results */
+ g_debug ("rpm: querying for %s with %s", gs_app_get_id (app), fn);
+ while ((h = rpmdbNextIterator (mi)) != NULL) {
+ const gchar *name;
+
+ /* add default source */
+ name = headerGetString (h, RPMTAG_NAME);
+ if (gs_app_get_source_default (app) == NULL) {
+ g_debug ("rpm: setting source to %s", name);
+ gs_app_add_source (app, name);
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gs_rpm_ostree_refine_apps (GsPlugin *plugin,
+ GsAppList *list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GHashTable) packages = NULL;
+ g_autoptr(GHashTable) layered_packages = NULL;
+ g_autoptr(GHashTable) layered_local_packages = NULL;
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autoptr(GPtrArray) pkglist = NULL;
+ g_autoptr(GVariant) default_deployment = NULL;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(DnfContext) dnf_context = NULL;
+ g_autoptr(OstreeRepo) ot_repo = NULL;
+ g_auto(GStrv) layered_packages_strv = NULL;
+ g_auto(GStrv) layered_local_packages_strv = NULL;
+ g_autofree gchar *checksum = NULL;
+
+ locker = g_mutex_locker_new (&self->mutex);
+
+ if (!gs_rpmostree_ref_dnf_context_locked (self, &os_proxy, &sysroot_proxy, &dnf_context, cancellable, error))
+ return FALSE;
+
+ ot_repo = g_object_ref (self->ot_repo);
+
+ if (!dnf_context)
+ return FALSE;
+
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ /* ensure D-Bus properties are updated before reading them */
+ if (!gs_rpmostree_sysroot_call_reload_sync (sysroot_proxy, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ default_deployment = gs_rpmostree_os_dup_default_deployment (os_proxy);
+ g_assert (g_variant_lookup (default_deployment,
+ "packages", "^as",
+ &layered_packages_strv));
+ g_assert (g_variant_lookup (default_deployment,
+ "requested-local-packages", "^as",
+ &layered_local_packages_strv));
+ g_assert (g_variant_lookup (default_deployment,
+ "checksum", "s",
+ &checksum));
+
+ pkglist = rpm_ostree_db_query_all (ot_repo, checksum, cancellable, error);
+ if (pkglist == NULL) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ packages = g_hash_table_new (g_str_hash, g_str_equal);
+ layered_packages = g_hash_table_new (g_str_hash, g_str_equal);
+ layered_local_packages = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (guint ii = 0; ii < pkglist->len; ii++) {
+ RpmOstreePackage *pkg = g_ptr_array_index (pkglist, ii);
+ if (rpm_ostree_package_get_name (pkg))
+ g_hash_table_insert (packages, (gpointer) rpm_ostree_package_get_name (pkg), pkg);
+ }
+
+ for (guint ii = 0; layered_packages_strv && layered_packages_strv[ii]; ii++) {
+ g_hash_table_add (layered_packages, layered_packages_strv[ii]);
+ }
+
+ for (guint ii = 0; layered_local_packages_strv && layered_local_packages_strv[ii]; ii++) {
+ g_hash_table_add (layered_local_packages, layered_local_packages_strv[ii]);
+ }
+
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+ gboolean found;
+
+ if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD))
+ continue;
+ /* set management plugin for apps where appstream just added the source package name in refine() */
+ if (gs_app_has_management_plugin (app, NULL) &&
+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE &&
+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM &&
+ gs_app_get_source_default (app) != NULL) {
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ }
+ /* resolve the source package name based on installed appdata/desktop file name */
+ if (gs_app_has_management_plugin (app, NULL) &&
+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_UNKNOWN &&
+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM &&
+ gs_app_get_source_default (app) == NULL) {
+ if (!resolve_appstream_source_file_to_package_name (plugin, app, flags, cancellable, error))
+ return FALSE;
+ }
+ if (!gs_app_has_management_plugin (app, plugin))
+ continue;
+ if (gs_app_get_source_default (app) == NULL)
+ continue;
+
+ /* first try to resolve from installed packages */
+ found = resolve_installed_packages_app (plugin, packages, layered_packages, layered_local_packages, app);
+
+ /* if we didn't find anything, try resolving from available packages */
+ if (!found && dnf_context != NULL)
+ found = resolve_available_packages_app (plugin, dnf_context_get_sack (dnf_context), app);
+
+ /* if we still didn't find anything then it's likely a package
+ * that is still in appstream data, but removed from the repos */
+ if (!found)
+ g_debug ("failed to resolve %s", gs_app_get_unique_id (app));
+ }
+
+ return TRUE;
+}
+
+static void refine_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_refine_async (GsPlugin *plugin,
+ GsAppList *list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+ gboolean interactive = gs_plugin_has_flags (GS_PLUGIN (self), GS_PLUGIN_FLAGS_INTERACTIVE);
+
+ task = gs_plugin_refine_data_new_task (plugin, list, flags, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_refine_async);
+
+ gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive),
+ refine_thread_cb, g_steal_pointer (&task));
+}
+
+static void
+refine_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPlugin *plugin = GS_PLUGIN (source_object);
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ GsPluginRefineData *data = task_data;
+ GsAppList *list = data->list;
+ GsPluginRefineFlags flags = data->flags;
+ g_autoptr(GError) local_error = NULL;
+
+ assert_in_worker (self);
+
+ if (!gs_rpm_ostree_refine_apps (plugin, list, flags, cancellable, &local_error))
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_rpm_ostree_refine_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+gs_plugin_app_upgrade_download (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ const char *packages[] = { NULL };
+ g_autofree gchar *new_refspec = NULL;
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ gboolean done;
+
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, plugin))
+ return TRUE;
+
+ /* check is distro-upgrade */
+ if (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM)
+ return TRUE;
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error))
+ return FALSE;
+
+ /* construct new refspec based on the distro version we're upgrading to */
+ new_refspec = g_strdup_printf ("ostree://fedora/%s/x86_64/silverblue",
+ gs_app_get_version (app));
+
+ options = make_rpmostree_options_variant (FALSE, /* reboot */
+ TRUE, /* allow-downgrade */
+ FALSE, /* cache-only */
+ TRUE, /* download-only */
+ FALSE, /* skip-purge */
+ FALSE, /* no-pull-base */
+ FALSE, /* dry-run */
+ FALSE); /* no-overrides */
+
+ gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+ tp->app = g_object_ref (app);
+
+ done = FALSE;
+ while (!done) {
+ done = TRUE;
+ if (!gs_rpmostree_os_call_rebase_sync (os_proxy,
+ options,
+ new_refspec,
+ packages,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ &local_error)) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) {
+ g_clear_error (&local_error);
+ if (!gs_rpmostree_wait_for_ongoing_transaction_end (sysroot_proxy, cancellable, error)) {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ done = FALSE;
+ continue;
+ }
+ if (local_error)
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+
+ if (g_strrstr ((*error)->message, "Old and new refs are equal")) {
+ /* don't error out if the correct tree is already deployed */
+ g_debug ("ignoring rpm-ostree error: %s", (*error)->message);
+ g_clear_error (error);
+ } else {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ }
+
+ /* state is known */
+ gs_app_set_state (app, GS_APP_STATE_UPDATABLE);
+ return TRUE;
+}
+
+
+static gboolean
+plugin_rpmostree_pick_rpm_desktop_file_cb (GsPlugin *plugin,
+ GsApp *app,
+ const gchar *filename,
+ GKeyFile *key_file)
+{
+ return strstr (filename, "/snapd/") == NULL &&
+ strstr (filename, "/snap/") == NULL &&
+ strstr (filename, "/flatpak/") == NULL &&
+ g_key_file_has_group (key_file, "Desktop Entry") &&
+ !g_key_file_has_key (key_file, "Desktop Entry", "X-Flatpak", NULL) &&
+ !g_key_file_has_key (key_file, "Desktop Entry", "X-SnapInstanceName", NULL);
+}
+
+gboolean
+gs_plugin_launch (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* only process this app if was created by this plugin */
+ if (!gs_app_has_management_plugin (app, plugin))
+ return TRUE;
+
+ return gs_plugin_app_launch_filtered (plugin, app, plugin_rpmostree_pick_rpm_desktop_file_cb, NULL, error);
+}
+
+static void
+add_quirks_from_package_name (GsApp *app, const gchar *package_name)
+{
+ /* these packages don't have a .repo file in their file lists, but
+ * instead install one through rpm scripts / cron job */
+ const gchar *packages_with_repos[] = {
+ "google-chrome-stable",
+ "google-earth-pro-stable",
+ "google-talkplugin",
+ NULL };
+
+ if (g_strv_contains (packages_with_repos, package_name))
+ gs_app_add_quirk (app, GS_APP_QUIRK_HAS_SOURCE);
+}
+
+gboolean
+gs_plugin_file_to_app (GsPlugin *plugin,
+ GsAppList *list,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ FD_t rpmfd = NULL;
+ guint64 epoch;
+ guint64 size;
+ const gchar *name;
+ const gchar *version;
+ const gchar *release;
+ const gchar *license;
+ g_auto(Header) h = NULL;
+ g_auto(rpmts) ts = NULL;
+ g_autofree gchar *evr = NULL;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GsAppList) tmp_list = NULL;
+
+ filename = g_file_get_path (file);
+ if (!g_str_has_suffix (filename, ".rpm")) {
+ ret = TRUE;
+ goto out;
+ }
+
+ ts = rpmtsCreate ();
+ rpmtsSetVSFlags (ts, _RPMVSF_NOSIGNATURES);
+
+ /* librpm needs Fopenfd */
+ rpmfd = Fopen (filename, "r.fdio");
+ if (rpmfd == NULL) {
+ g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+ "Opening %s failed", filename);
+ goto out;
+ }
+ if (Ferror (rpmfd)) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Opening %s failed: %s",
+ filename,
+ Fstrerror (rpmfd));
+ goto out;
+ }
+
+ if (rpmReadPackageFile (ts, rpmfd, filename, &h) != RPMRC_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Verification of %s failed",
+ filename);
+ goto out;
+ }
+
+ app = gs_app_new (NULL);
+ gs_app_set_metadata (app, "GnomeSoftware::Creator", gs_plugin_get_name (plugin));
+ gs_app_set_management_plugin (app, plugin);
+ if (h) {
+ const gchar *str;
+
+ str = headerGetString (h, RPMTAG_NAME);
+ if (str && *str)
+ gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, str);
+
+ str = headerGetString (h, RPMTAG_SUMMARY);
+ if (str && *str)
+ gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, str);
+
+ str = headerGetString (h, RPMTAG_DESCRIPTION);
+ if (str && *str)
+ gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, str);
+ }
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM);
+
+ /* add default source */
+ name = headerGetString (h, RPMTAG_NAME);
+ g_debug ("rpm: setting source to %s", name);
+ gs_app_add_source (app, name);
+
+ /* add version */
+ epoch = headerGetNumber (h, RPMTAG_EPOCH);
+ version = headerGetString (h, RPMTAG_VERSION);
+ release = headerGetString (h, RPMTAG_RELEASE);
+ if (epoch > 0) {
+ evr = g_strdup_printf ("%" G_GUINT64_FORMAT ":%s-%s",
+ epoch, version, release);
+ } else {
+ evr = g_strdup_printf ("%s-%s",
+ version, release);
+ }
+ g_debug ("rpm: setting version to %s", evr);
+ gs_app_set_version (app, evr);
+
+ /* set size */
+ size = headerGetNumber (h, RPMTAG_SIZE);
+ gs_app_set_size_installed (app, GS_SIZE_TYPE_VALID, size);
+
+ /* set license */
+ license = headerGetString (h, RPMTAG_LICENSE);
+ if (license != NULL) {
+ g_autofree gchar *license_spdx = NULL;
+ license_spdx = as_license_to_spdx_id (license);
+ gs_app_set_license (app, GS_APP_QUALITY_NORMAL, license_spdx);
+ g_debug ("rpm: setting license to %s", license_spdx);
+ }
+
+ add_quirks_from_package_name (app, name);
+
+ tmp_list = gs_app_list_new ();
+ gs_app_list_add (tmp_list, app);
+
+ if (gs_rpm_ostree_refine_apps (plugin, tmp_list, 0, cancellable, error)) {
+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE_LOCAL);
+
+ gs_app_list_add (list, app);
+ ret = TRUE;
+ }
+
+out:
+ if (rpmfd != NULL)
+ (void) Fclose (rpmfd);
+ return ret;
+}
+
+static gchar **
+what_provides_decompose (GsAppQueryProvidesType provides_type,
+ const gchar *provides_tag)
+{
+ g_autoptr(GPtrArray) array = g_ptr_array_new ();
+
+ /* The provides_tag possibly already contains the prefix, thus use it as is */
+ if (provides_type != GS_APP_QUERY_PROVIDES_UNKNOWN &&
+ g_str_has_suffix (provides_tag, ")") &&
+ strchr (provides_tag, '(') != NULL)
+ provides_type = GS_APP_QUERY_PROVIDES_PACKAGE_NAME;
+
+ /* Wrap the @provides_tag with the appropriate Fedora prefix */
+ switch (provides_type) {
+ case GS_APP_QUERY_PROVIDES_PACKAGE_NAME:
+ g_ptr_array_add (array, g_strdup (provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_GSTREAMER:
+ g_ptr_array_add (array, g_strdup_printf ("gstreamer0.10(%s)", provides_tag));
+ g_ptr_array_add (array, g_strdup_printf ("gstreamer1(%s)", provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_FONT:
+ g_ptr_array_add (array, g_strdup_printf ("font(%s)", provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_MIME_HANDLER:
+ g_ptr_array_add (array, g_strdup_printf ("mimehandler(%s)", provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_PS_DRIVER:
+ g_ptr_array_add (array, g_strdup_printf ("postscriptdriver(%s)", provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_PLASMA:
+ g_ptr_array_add (array, g_strdup_printf ("plasma4(%s)", provides_tag));
+ g_ptr_array_add (array, g_strdup_printf ("plasma5(%s)", provides_tag));
+ break;
+ case GS_APP_QUERY_PROVIDES_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_ptr_array_add (array, NULL);
+
+ return (gchar **) g_ptr_array_free (g_steal_pointer (&array), FALSE);
+}
+
+static void list_apps_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_list_apps_async (GsPlugin *plugin,
+ GsAppQuery *query,
+ GsPluginListAppsFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+ gboolean interactive = (flags & GS_PLUGIN_LIST_APPS_FLAGS_INTERACTIVE);
+
+ task = gs_plugin_list_apps_data_new_task (plugin, query, flags,
+ cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_list_apps_async);
+
+ /* Queue a job to get the apps. */
+ gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive),
+ list_apps_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker. */
+static void
+list_apps_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (source_object);
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+ GsPluginListAppsData *data = task_data;
+ const gchar *provides_tag = NULL;
+ GsAppQueryProvidesType provides_type = GS_APP_QUERY_PROVIDES_UNKNOWN;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autoptr(GPtrArray) pkglist = NULL;
+ g_autoptr(DnfContext) dnf_context = NULL;
+ g_auto(GStrv) provides = NULL;
+
+ assert_in_worker (self);
+
+ if (data->query != NULL) {
+ provides_type = gs_app_query_get_provides (data->query, &provides_tag);
+ }
+
+ /* Currently only support a subset of query properties, and only one set at once. */
+ if (provides_tag == NULL ||
+ gs_app_query_get_n_properties_set (data->query) != 1) {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unsupported query");
+ return;
+ }
+
+ /* Prepare a dnf context */
+ locker = g_mutex_locker_new (&self->mutex);
+
+ if (!gs_rpmostree_ref_dnf_context_locked (self, NULL, NULL, &dnf_context, cancellable, &local_error)) {
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ return;
+ }
+
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ provides = what_provides_decompose (provides_type, provides_tag);
+ pkglist = find_packages_by_provides (dnf_context_get_sack (dnf_context), provides);
+ for (guint i = 0; i < pkglist->len; i++) {
+ DnfPackage *pkg = g_ptr_array_index (pkglist, i);
+ g_autoptr(GsApp) app = NULL;
+
+ app = gs_plugin_cache_lookup (GS_PLUGIN (self), dnf_package_get_nevra (pkg));
+ if (app != NULL) {
+ gs_app_list_add (list, app);
+ continue;
+ }
+
+ /* create new app */
+ app = gs_app_new (NULL);
+ gs_app_set_metadata (app, "GnomeSoftware::Creator", gs_plugin_get_name (GS_PLUGIN (self)));
+ gs_app_set_management_plugin (app, GS_PLUGIN (self));
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM);
+ gs_app_add_source (app, dnf_package_get_name (pkg));
+
+ gs_plugin_cache_add (GS_PLUGIN (self), dnf_package_get_nevra (pkg), app);
+ gs_app_list_add (list, app);
+ }
+
+ g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref);
+}
+
+static GsAppList *
+gs_plugin_rpm_ostree_list_apps_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+gboolean
+gs_plugin_add_sources (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(DnfContext) dnf_context = NULL;
+ GPtrArray *repos;
+
+ dnf_context = gs_rpmostree_create_bare_dnf_context (cancellable, error);
+ if (!dnf_context)
+ return FALSE;
+
+ repos = dnf_context_get_repos (dnf_context);
+ if (repos == NULL)
+ return TRUE;
+
+ for (guint i = 0; i < repos->len; i++) {
+ DnfRepo *repo = g_ptr_array_index (repos, i);
+ g_autofree gchar *description = NULL;
+ g_autoptr(GsApp) app = NULL;
+ gboolean enabled;
+
+ /* hide these from the user */
+ if (dnf_repo_is_devel (repo) || dnf_repo_is_source (repo))
+ continue;
+
+ app = gs_app_new (dnf_repo_get_id (repo));
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_REPOSITORY);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+
+ enabled = (dnf_repo_get_enabled (repo) & DNF_REPO_ENABLED_PACKAGES) > 0;
+ gs_app_set_state (app, enabled ? GS_APP_STATE_INSTALLED : GS_APP_STATE_AVAILABLE);
+
+ description = dnf_repo_get_description (repo);
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, description);
+ gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, description);
+
+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "200");
+ gs_app_set_origin_ui (app, _("Operating System (OSTree)"));
+
+ gs_app_list_add (list, app);
+ }
+
+ return TRUE;
+}
+
+static void enable_repository_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_enable_repository_async (GsPlugin *plugin,
+ GsApp *repository,
+ GsPluginManageRepositoryFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+ gboolean interactive = (flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE);
+
+ task = gs_plugin_manage_repository_data_new_task (plugin, repository, flags, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_enable_repository_async);
+
+ /* only process this app if it was created by this plugin */
+ if (!gs_app_has_management_plugin (repository, plugin)) {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ g_assert (gs_app_get_kind (repository) == AS_COMPONENT_KIND_REPOSITORY);
+
+ gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive),
+ enable_repository_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker. */
+static void
+enable_repository_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (source_object);
+ GsPluginManageRepositoryData *data = task_data;
+ GsPluginRefreshMetadataData refresh_data = { 0 };
+ gboolean interactive = (data->flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE);
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ assert_in_worker (self);
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, &local_error)) {
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ return;
+ }
+
+ if (!gs_rpmostree_repo_enable (GS_PLUGIN (self), data->repository, TRUE, os_proxy, sysroot_proxy, cancellable, &local_error)) {
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ return;
+ }
+
+ refresh_data.flags = interactive ? GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE : GS_PLUGIN_REFRESH_METADATA_FLAGS_NONE;
+ refresh_data.cache_age_secs = 1;
+
+ if (!gs_plugin_rpm_ostree_refresh_metadata_in_worker (self, &refresh_data, os_proxy, sysroot_proxy, cancellable, &local_error))
+ g_debug ("Failed to refresh after repository enable: %s", local_error->message);
+
+ /* This can fail silently, it's only to update necessary caches, to provide
+ * up-to-date information after the successful repository enable/install.
+ */
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_rpm_ostree_enable_repository_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void disable_repository_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+
+static void
+gs_plugin_rpm_ostree_disable_repository_async (GsPlugin *plugin,
+ GsApp *repository,
+ GsPluginManageRepositoryFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (plugin);
+ g_autoptr(GTask) task = NULL;
+ gboolean interactive = (flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE);
+
+ task = gs_plugin_manage_repository_data_new_task (plugin, repository, flags, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_rpm_ostree_disable_repository_async);
+
+ /* only process this app if it was created by this plugin */
+ if (!gs_app_has_management_plugin (repository, plugin)) {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ g_assert (gs_app_get_kind (repository) == AS_COMPONENT_KIND_REPOSITORY);
+
+ gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive),
+ disable_repository_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker. */
+static void
+disable_repository_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GsPluginRpmOstree *self = GS_PLUGIN_RPM_OSTREE (source_object);
+ GsPluginManageRepositoryData *data = task_data;
+ g_autoptr(GsRPMOSTreeOS) os_proxy = NULL;
+ g_autoptr(GsRPMOSTreeSysroot) sysroot_proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ assert_in_worker (self);
+
+ if (!gs_rpmostree_ref_proxies (self, &os_proxy, &sysroot_proxy, cancellable, &local_error)) {
+ g_task_return_error (task, g_steal_pointer (&local_error));
+ return;
+ }
+
+ if (gs_rpmostree_repo_enable (GS_PLUGIN (self), data->repository, FALSE, os_proxy, sysroot_proxy, cancellable, &local_error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, g_steal_pointer (&local_error));
+}
+
+static gboolean
+gs_plugin_rpm_ostree_disable_repository_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gs_plugin_rpm_ostree_class_init (GsPluginRpmOstreeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass);
+
+ object_class->dispose = gs_plugin_rpm_ostree_dispose;
+ object_class->finalize = gs_plugin_rpm_ostree_finalize;
+
+ plugin_class->setup_async = gs_plugin_rpm_ostree_setup_async;
+ plugin_class->setup_finish = gs_plugin_rpm_ostree_setup_finish;
+ plugin_class->shutdown_async = gs_plugin_rpm_ostree_shutdown_async;
+ plugin_class->shutdown_finish = gs_plugin_rpm_ostree_shutdown_finish;
+ plugin_class->refine_async = gs_plugin_rpm_ostree_refine_async;
+ plugin_class->refine_finish = gs_plugin_rpm_ostree_refine_finish;
+ plugin_class->refresh_metadata_async = gs_plugin_rpm_ostree_refresh_metadata_async;
+ plugin_class->refresh_metadata_finish = gs_plugin_rpm_ostree_refresh_metadata_finish;
+ plugin_class->enable_repository_async = gs_plugin_rpm_ostree_enable_repository_async;
+ plugin_class->enable_repository_finish = gs_plugin_rpm_ostree_enable_repository_finish;
+ plugin_class->disable_repository_async = gs_plugin_rpm_ostree_disable_repository_async;
+ plugin_class->disable_repository_finish = gs_plugin_rpm_ostree_disable_repository_finish;
+ plugin_class->list_apps_async = gs_plugin_rpm_ostree_list_apps_async;
+ plugin_class->list_apps_finish = gs_plugin_rpm_ostree_list_apps_finish;
+}
+
+GType
+gs_plugin_query_type (void)
+{
+ return GS_TYPE_PLUGIN_RPM_OSTREE;
+}
diff --git a/plugins/rpm-ostree/gs-plugin-rpm-ostree.h b/plugins/rpm-ostree/gs-plugin-rpm-ostree.h
new file mode 100644
index 0000000..ab322b7
--- /dev/null
+++ b/plugins/rpm-ostree/gs-plugin-rpm-ostree.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation LLC
+ *
+ * Author: Philip Withnall <pwithnall@endlessos.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PLUGIN_RPM_OSTREE (gs_plugin_rpm_ostree_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPluginRpmOstree, gs_plugin_rpm_ostree, GS, PLUGIN_RPM_OSTREE, GsPlugin)
+
+G_END_DECLS
diff --git a/plugins/rpm-ostree/meson.build b/plugins/rpm-ostree/meson.build
new file mode 100644
index 0000000..724556e
--- /dev/null
+++ b/plugins/rpm-ostree/meson.build
@@ -0,0 +1,23 @@
+cargs = ['-DG_LOG_DOMAIN="GsPluginRpmOstree"']
+
+rpmostree_generated = gnome.gdbus_codegen(
+ 'gs-rpmostree-generated',
+ 'org.projectatomic.rpmostree1.xml',
+ interface_prefix : 'org.projectatomic.rpmostree1',
+ namespace : 'GsRPMOSTree'
+)
+
+shared_module(
+ 'gs_plugin_rpm-ostree',
+ rpmostree_generated,
+ sources : 'gs-plugin-rpm-ostree.c',
+ include_directories : [
+ include_directories('../..'),
+ include_directories('../../lib'),
+ ],
+ install : true,
+ install_dir: plugin_dir,
+ install_rpath: join_paths(rpm_ostree.get_variable('libdir'), 'rpm-ostree'),
+ c_args : cargs,
+ dependencies : [ plugin_libs, libdnf, ostree, rpm, rpm_ostree ],
+)
diff --git a/plugins/rpm-ostree/org.projectatomic.rpmostree1.xml b/plugins/rpm-ostree/org.projectatomic.rpmostree1.xml
new file mode 100644
index 0000000..6ae04a4
--- /dev/null
+++ b/plugins/rpm-ostree/org.projectatomic.rpmostree1.xml
@@ -0,0 +1,458 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+
+ <!-- Deployment dictionary keys:
+ 'id' (type 's')
+ 'osname' (type 's')
+ 'serial' (type 'i')
+ 'checksum' (type 's')
+ 'version' (type 's')
+ 'timestamp' (type 't')
+ 'origin' (type 's')
+ 'signatures' (type 'av')
+ 'packages' (type 'as')
+ 'booted' (type 'b')
+ -->
+
+ <interface name="org.projectatomic.rpmostree1.Sysroot">
+ <!-- The booted OSName -->
+ <property name="Booted" type="o" access="read"/>
+
+ <!-- The system root path -->
+ <property name="Path" type="s" access="read"/>
+
+ <!-- The values are (method-name, sender-name, object path) -->
+ <property name="ActiveTransaction" type="(sss)" access="read"/>
+ <!-- A DBus address - connect to it to access its methods -->
+ <property name="ActiveTransactionPath" type="s" access="read"/>
+
+ <!-- (Currently) optional method to denote the client plans
+ to either invoke methods on the daemon, or monitor status.
+ If no clients are registered, the daemon may exit.
+
+ 'id (type 's') - Package/component name (e.g. `cockpit`, `gnome-software`)
+ -->
+ <method name="RegisterClient">
+ <arg type="a{sv}" name="options" direction="in"/>
+ </method>
+
+ <!-- You must call this if your process is no longer interested in talking to
+ rpm-ostree, but will remain connected to the bus.
+
+ No options are currently defined.
+ -->
+ <method name="UnregisterClient">
+ <arg type="a{sv}" name="options" direction="in"/>
+ </method>
+
+ <!-- Reload sysroot if changed. This can also be used as a way to sync with the daemon
+ to ensure e.g. D-Bus properties are updated before reading them. -->
+ <method name="Reload">
+ </method>
+
+ <!-- Like Reload, but also reload configuration files. -->
+ <method name="ReloadConfig">
+ </method>
+
+ <!-- none, check, stage -->
+ <property name="AutomaticUpdatePolicy" type="s" access="read"/>
+
+ <method name="CreateOSName">
+ <arg type="s" name="name"/>
+ <arg type="o" name="result" direction="out"/>
+ </method>
+
+ <method name="GetOS">
+ <arg name="name" type="s"/>
+ <arg name="object_path" type="o" direction="out"/>
+ </method>
+
+ <!-- Array of all deployments in boot order -->
+ <property name="Deployments" type="aa{sv}" access="read"/>
+ </interface>
+
+ <interface name="org.projectatomic.rpmostree1.OS">
+ <property name="BootedDeployment" type="a{sv}" access="read"/>
+ <property name="DefaultDeployment" type="a{sv}" access="read"/>
+ <property name="RollbackDeployment" type="a{sv}" access="read"/>
+
+ <!-- CachedUpdate dictionary keys:
+ 'osname' (type 's')
+ 'checksum' (type 's')
+ 'version' (type 's')
+ 'timestamp' (type 't')
+ 'origin' (type 's')
+ 'signatures' (type 'av')
+ 'gpg-enabled' (type 'b')
+ 'ref-has-new-commit' (type 'b')
+ TRUE if 'checksum' refers to a new base commit we're not booted in.
+ 'rpm-diff' (type 'a{sv}')
+ 'upgraded' (type 'a(us(ss)(ss))')
+ 'downgraded' (type 'a(us(ss)(ss))')
+ 'removed' (type 'a(usss)')
+ 'added' (type 'a(usss)')
+ 'advisories' (type 'a(suuasa{sv})')
+ -->
+ <property name="CachedUpdate" type="a{sv}" access="read"/>
+ <property name="HasCachedUpdateRpmDiff" type="b" access="read"/>
+
+ <!-- Available options:
+ "mode" (type 's')
+ One of auto, none, check. Defaults to auto, which follows configured
+ policy (available in AutomaticUpdatePolicy property).
+ "output-to-self" (type 'b')
+ Whether output should go to the daemon itself rather than the
+ transaction. Defaults to TRUE.
+
+ If automatic updates are not enabled, @enabled will be FALSE and
+ @transaction_address will be the empty string.
+ -->
+ <method name="AutomaticUpdateTrigger">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="b" name="enabled" direction="out"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <property name="Name" type="s" access="read"/>
+
+ <method name="GetDeploymentsRpmDiff">
+ <arg type="s" name="deployid0"/>
+ <arg type="s" name="deployid1"/>
+ <arg type="a(sua{sv})" name="result" direction="out"/>
+ </method>
+
+ <!-- Revision may be a full checksum or version string.
+
+ Available options:
+ "reboot" (type 'b')
+ -->
+ <method name="Deploy">
+ <arg type="s" name="revision" direction="in"/>
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ </method>
+
+ <!-- details dictionary keys:
+ 'osname' (type 's')
+ 'checksum' (type 's')
+ 'version' (type 's')
+ 'timestamp' (type 't')
+ 'origin' (type 's')
+ 'signatures' (type 'av')
+ -->
+ <method name="GetCachedDeployRpmDiff">
+ <arg type="s" name="revision"/>
+ <arg type="as" name="packages"/>
+ <arg type="a(sua{sv})" name="result" direction="out"/>
+ <arg type="a{sv}" name="details" direction="out"/>
+ </method>
+
+ <method name="DownloadDeployRpmDiff">
+ <arg type="s" name="revision"/>
+ <arg type="as" name="packages"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "allow-downgrade" (type 'b')
+ "reboot" (type 'b')
+ -->
+ <method name="Upgrade">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ </method>
+
+ <!-- details dictionary keys:
+ 'osname' (type 's')
+ 'checksum' (type 's')
+ 'version' (type 's')
+ 'timestamp' (type 't')
+ 'origin' (type 's')
+ 'signatures' (type 'av')
+ -->
+ <method name="GetCachedUpdateRpmDiff">
+ <arg type="s" name="deployid"/>
+ <arg type="a(sua{sv})" name="result" direction="out"/>
+ <arg type="a{sv}" name="details" direction="out"/>
+ </method>
+
+ <method name="DownloadUpdateRpmDiff">
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "reboot" (type 'b')
+ -->
+ <method name="Rollback">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "reboot" (type 'b')
+ -->
+ <method name="ClearRollbackTarget">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "skip-purge" (type 'b')
+ "reboot" (type 'b')
+ "revision" (type 's')
+ -->
+ <method name="Rebase">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="refspec"/>
+ <arg type="as" name="packages"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ </method>
+
+ <!-- details dictionary keys:
+ 'osname' (type 's')
+ 'checksum' (type 's')
+ 'version' (type 's')
+ 'timestamp' (type 't')
+ 'origin' (type 's')
+ 'signatures' (type 'av')
+ -->
+ <method name="GetCachedRebaseRpmDiff">
+ <arg type="s" name="refspec"/>
+ <arg type="as" name="packages"/>
+ <arg type="a(sua{sv})" name="result" direction="out"/>
+ <arg type="a{sv}" name="details" direction="out"/>
+ </method>
+
+ <method name="DownloadRebaseRpmDiff">
+ <arg type="s" name="refspec"/>
+ <arg type="as" name="packages"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "reboot" (type 'b')
+ "dry-run" (type 'b')
+ -->
+ <method name="PkgChange">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="as" name="packages_added"/>
+ <arg type="as" name="packages_removed"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ </method>
+
+ <method name="SetInitramfsState">
+ <arg type="b" name="regenerate" direction="in"/>
+ <arg type="as" name="args" direction="in"/>
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available options:
+ "reboot" (type 'b')
+ -->
+ <method name="KernelArgs">
+ <arg type="s" name="existing_kernel_arg_string"/>
+ <arg type="as" name="kernel_args_added" direction="in"/>
+ <arg type="as" name="kernel_args_replaced" direction="in"/>
+ <arg type="as" name="kernel_args_removed" direction="in"/>
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <method name="GetDeploymentBootConfig">
+ <arg type="s" name="deployid" />
+ <arg type="b" name="is_pending" direction="in"/>
+ <arg type="a{sv}" name="bootconfig" direction="out"/>
+ </method>
+
+ <method name="Cleanup">
+ <arg type="as" name="elements" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <method name="RefreshMd">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Set options in yum .repo files -->
+ <method name="ModifyYumRepo">
+ <arg type="s" name="repo_id" direction="in"/>
+ <arg type="a{ss}" name="settings" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ <!-- Available modifiers:
+ "set-refspec" (type 's')
+ "set-revision" (type 's')
+ "install-packages" (type 'as')
+ "uninstall-packages" (type 'as')
+ "install-local-packages" (type 'ah')
+ "override-remove-packages" (type 'as')
+ "override-reset-packages" (type 'as')
+ "override-replace-packages" (type 'as')
+ "override-replace-local-packages" (type 'ah')
+ "custom-origin" (type '(ss)')
+
+ Available options:
+ "reboot" (type 'b')
+ Initiate a reboot after transaction.
+ "allow-downgrade" (type 'b')
+ Allow newly pulled bases to have older timestamps than the
+ current base. Defaults to TRUE if either "set-refspec" or
+ "set-revision" is specified.
+ "skip-purge" (type 'b')
+ Do not purge the old refspec. Only valid if "set-refspec" is
+ specified.
+ "no-pull-base" (type 'b')
+ Do not pull a base layer from the remote. Not valid if
+ either "set-refspec" or "set-revision" is specified.
+ "dry-run" (type 'b')
+ Stop short of deploying the new tree. If layering packages,
+ the pkg diff is printed but packages are not downloaded or
+ imported.
+ "no-layering" (type 'b')
+ Remove all package requests. Requests in "install-packages"
+ are still subsequently processed if specified.
+ "no-overrides" (type 'b')
+ Remove all active overrides. Not valid if any override
+ modifiers are specified.
+ "no-initramfs" (type 'b')
+ Disable any initramfs regeneration.
+ "cache-only" (type 'b')
+ Do not update rpmmd repo metadata cache or ostree refspec.
+ Not valid if "download-only" is specified.
+ "download-only" (type 'b')
+ Update rpmmd repo metadata cache and ostree refspec. Do not
+ perform any deployments. This is like "dry-run" except that
+ the latter does not download and import packages. Not valid
+ if "cache-only" or "dry-run" is specified.
+ "allow-inactive-requests" (type 'b')
+ When installing packages, allow package requests which would
+ not immediately be active.
+ "idempotent-layering" (type 'b')
+ Don't error out on requests in install-* or uninstall-*
+ modifiers that are already satisfied.
+ -->
+ <method name="UpdateDeployment">
+ <arg type="a{sv}" name="modifiers" direction="in"/>
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ </method>
+
+ </interface>
+
+ <interface name="org.projectatomic.rpmostree1.OSExperimental">
+
+ <!-- Just a test method -->
+ <method name="Moo">
+ <arg type="b" name="utf8" direction="in"/>
+ <arg type="s" name="result" direction="out"/>
+ </method>
+
+ <method name="LiveFs">
+ <arg type="a{sv}" name="options" direction="in"/>
+ <arg type="s" name="transaction_address" direction="out"/>
+ </method>
+
+ </interface>
+
+ <interface name="org.projectatomic.rpmostree1.Transaction">
+
+ <!-- A single-line human-readable string -->
+ <property name="Title" type="s" access="read"/>
+
+ <!-- Yes, we can. -->
+ <method name="Cancel"/>
+
+ <!-- For a client to call when ready to receive signals.
+ The return boolean indicates whether the transaction was
+ started by this method call (true) or was already started
+ by another client (false). -->
+ <method name="Start">
+ <arg type="b" name="started" direction="out"/>
+ </method>
+
+ <signal name="Finished">
+ <arg name="success" type="b" direction="out"/>
+ <arg name="error_message" type="s" direction="out"/>
+ </signal>
+
+ <!-- For miscellaneous messages; line-buffered. -->
+ <signal name="Message">
+ <arg name="text" type="s" direction="out"/>
+ </signal>
+
+ <!-- Tasks are notifications that work is being done. -->
+ <signal name="TaskBegin">
+ <arg name="text" type="s" direction="out"/>
+ </signal>
+
+ <signal name="TaskEnd">
+ <arg name="text" type="s" direction="out"/>
+ </signal>
+
+ <!-- Generic percentage progress. -->
+ <signal name="PercentProgress">
+ <arg name="text" type="s" direction="out"/>
+ <arg name="percentage" type="u" direction="out"/>
+ </signal>
+
+ <signal name="DownloadProgress">
+ <!-- time data, format is:
+ start time, elapsed seconds
+ -->
+ <arg name="time" type="(tt)" direction="out"/>
+
+ <!--
+ outstanding data counts, format is:
+ (outstanding fetches, outstanding writes)
+ -->
+ <arg name="outstanding" type="(uu)" direction="out"/>
+
+ <!--
+ metadata counts, format is:
+ (scanned, fetched, outstanding)
+ -->
+ <arg name="metadata" type="(uuu)" direction="out"/>
+
+ <!--
+ delta data, format is:
+ (total parts, fetched parts, total super blocks, total size)
+ -->
+ <arg name="delta" type="(uuut)" direction="out"/>
+
+ <!--
+ content data, format is:
+ (fetched, requested)
+ -->
+ <arg name="content" type="(uu)" direction="out"/>
+
+ <!--
+ transfer data, format is:
+ (bytes transfered, bytes/s)
+ -->
+ <arg name="transfer" type="(tt)" direction="out"/>
+ </signal>
+
+ <signal name="SignatureProgress">
+ <!-- An ostree GVariant containing signature data
+ see ostree_gpg_verify_result_get_all.
+ -->
+ <arg name="signature" type="av" direction="out"/>
+ <!-- The signed commit -->
+ <arg name="commit" type="s" direction="out"/>
+ </signal>
+
+ <!-- Indicates progress signals are done and subsequent
+ Message signals should be output on separate lines. -->
+ <signal name="ProgressEnd"/>
+ </interface>
+</node>