summaryrefslogtreecommitdiffstats
path: root/plugins/rpm-ostree
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/rpm-ostree')
-rw-r--r--plugins/rpm-ostree/gs-plugin-rpm-ostree.c1829
-rw-r--r--plugins/rpm-ostree/meson.build26
-rw-r--r--plugins/rpm-ostree/org.projectatomic.rpmostree1.xml458
3 files changed, 2313 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..e4977bb
--- /dev/null
+++ b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
@@ -0,0 +1,1829 @@
+/* -*- 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 <libdnf/libdnf.h>
+#include <ostree.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlib.h>
+#include <rpm/rpmts.h>
+#include <rpmostree.h>
+
+#include "gs-rpmostree-generated.h"
+
+/* This shows up in the `rpm-ostree status` as the software that
+ * initiated the update.
+ */
+#define GS_RPMOSTREE_CLIENT_ID PACKAGE_NAME
+
+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 GsPluginData {
+ GMutex mutex;
+ GsRPMOSTreeOS *os_proxy;
+ GsRPMOSTreeSysroot *sysroot_proxy;
+ OstreeRepo *ot_repo;
+ OstreeSysroot *ot_sysroot;
+ DnfContext *dnf_context;
+ gboolean update_triggered;
+};
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+
+ /* only works on OSTree */
+ if (!g_file_test ("/run/ostree-booted", G_FILE_TEST_EXISTS)) {
+ gs_plugin_set_enabled (plugin, FALSE);
+ return;
+ }
+
+ g_mutex_init (&priv->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 (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-history");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-local");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-offline");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-proxy");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-refine");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-refine-repos");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-refresh");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-upgrade");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-url-to-app");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "systemd-updates");
+
+ /* need pkgname */
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ if (priv->os_proxy != NULL)
+ g_object_unref (priv->os_proxy);
+ if (priv->sysroot_proxy != NULL)
+ g_object_unref (priv->sysroot_proxy);
+ if (priv->ot_sysroot != NULL)
+ g_object_unref (priv->ot_sysroot);
+ if (priv->ot_repo != NULL)
+ g_object_unref (priv->ot_repo);
+ if (priv->dnf_context != NULL)
+ g_object_unref (priv->dnf_context);
+ g_mutex_clear (&priv->mutex);
+}
+
+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;
+}
+
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GVariantBuilder) options_builder = NULL;
+
+ /* Create a proxy for sysroot */
+ if (priv->sysroot_proxy == NULL) {
+ priv->sysroot_proxy = gs_rpmostree_sysroot_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.projectatomic.rpmostree1",
+ "/org/projectatomic/rpmostree1/Sysroot",
+ cancellable,
+ error);
+ if (priv->sysroot_proxy == NULL) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ /* Create a proxy for currently booted OS */
+ if (priv->os_proxy == NULL) {
+ g_autofree gchar *os_object_path = NULL;
+
+ os_object_path = gs_rpmostree_sysroot_dup_booted (priv->sysroot_proxy);
+ if (os_object_path == NULL &&
+ !gs_rpmostree_sysroot_call_get_os_sync (priv->sysroot_proxy,
+ "",
+ &os_object_path,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ priv->os_proxy = gs_rpmostree_os_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.projectatomic.rpmostree1",
+ os_object_path,
+ cancellable,
+ error);
+ if (priv->os_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 (priv->sysroot_proxy,
+ g_variant_builder_end (options_builder),
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ /* Load ostree sysroot and repo */
+ if (priv->ot_sysroot == NULL) {
+ g_autofree gchar *sysroot_path = NULL;
+ g_autoptr(GFile) sysroot_file = NULL;
+
+ sysroot_path = gs_rpmostree_sysroot_dup_path (priv->sysroot_proxy);
+ sysroot_file = g_file_new_for_path (sysroot_path);
+
+ priv->ot_sysroot = ostree_sysroot_new (sysroot_file);
+ if (!ostree_sysroot_load (priv->ot_sysroot, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!ostree_sysroot_get_repo (priv->ot_sysroot, &priv->ot_repo, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+app_set_rpm_ostree_packaging_format (GsApp *app)
+{
+ gs_app_set_metadata (app, "GnomeSoftware::PackagingFormat", "RPM");
+}
+
+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_APP_SCOPE_SYSTEM) {
+ gs_app_set_management_plugin (app, gs_plugin_get_name (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_APP_KIND_OS_UPGRADE) {
+ gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ }
+}
+
+typedef struct {
+ GsPlugin *plugin;
+ GError *error;
+ GMainLoop *loop;
+ GsApp *app;
+ gboolean complete;
+} TransactionProgress;
+
+static TransactionProgress *
+transaction_progress_new (void)
+{
+ TransactionProgress *self;
+
+ self = g_slice_new0 (TransactionProgress);
+ self->loop = g_main_loop_new (NULL, FALSE);
+
+ return self;
+}
+
+static void
+transaction_progress_free (TransactionProgress *self)
+{
+ g_clear_object (&self->plugin);
+ g_clear_error (&self->error);
+ g_main_loop_unref (self->loop);
+ 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)
+{
+ g_main_loop_quit (self->loop);
+}
+
+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 AS_APP_STATE_INSTALLING:
+ plugin_status = GS_PLUGIN_STATUS_INSTALLING;
+ break;
+ case AS_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
+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;
+ gulong signal_handler = 0;
+ gboolean success = FALSE;
+ gboolean just_started = FALSE;
+
+ 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;
+
+ /* 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);
+
+ /* Tell the server we're ready to receive signals. */
+ if (!gs_rpmostree_transaction_call_start_sync (transaction,
+ &just_started,
+ cancellable,
+ error))
+ goto out;
+
+ g_main_loop_run (tp->loop);
+
+ g_cancellable_disconnect (cancellable, cancel_handler);
+
+ if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ if (tp->error) {
+ g_propagate_error (error, g_steal_pointer (&tp->error));
+ } else {
+ success = TRUE;
+ }
+ }
+
+out:
+ if (signal_handler)
+ g_signal_handler_disconnect (transaction, signal_handler);
+ if (transaction != NULL)
+ g_object_unref (transaction);
+
+ return success;
+}
+
+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, "rpm-ostree");
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_size_download (app, 0);
+ gs_app_set_kind (app, AS_APP_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_APP_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, AS_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, "rpm-ostree");
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_size_download (app, 0);
+ gs_app_set_kind (app, AS_APP_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+
+ if (addition) {
+ /* addition */
+ gs_app_add_source (app, name);
+ gs_app_set_version (app, evr);
+ gs_app_set_state (app, AS_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, AS_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, 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, 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 gboolean
+ensure_rpmostree_dnf_context (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GsApp) progress_app = gs_app_new (gs_plugin_get_name (plugin));
+ g_autoptr(DnfContext) context = dnf_context_new ();
+ g_autoptr(DnfState) state = dnf_state_new ();
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+
+ if (priv->dnf_context != NULL)
+ return TRUE;
+
+ tp->app = g_object_ref (progress_app);
+ tp->plugin = g_object_ref (plugin);
+
+ 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);
+
+ options = make_refresh_md_options_variant (FALSE /* force */);
+ if (!gs_rpmostree_os_call_refresh_md_sync (priv->os_proxy,
+ options,
+ &transaction_address,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!dnf_context_setup (context, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ 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 (&priv->dnf_context, context);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+ guint cache_age,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ if (!ensure_rpmostree_dnf_context (plugin, cancellable, error))
+ return FALSE;
+
+ if (cache_age == G_MAXUINT)
+ 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 ();
+
+ 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 */
+ if (!gs_rpmostree_os_call_upgrade_sync (priv->os_proxy,
+ options,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->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 ();
+
+ 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));
+
+ if (!gs_rpmostree_os_call_automatic_update_trigger_sync (priv->os_proxy,
+ options,
+ NULL,
+ &transaction_address,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ /* update UI */
+ gs_plugin_updates_changed (plugin);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GVariant) cached_update = NULL;
+ g_autoptr(GVariant) rpm_diff = NULL;
+ const gchar *checksum = NULL;
+ const gchar *version = NULL;
+ g_auto(GVariantDict) cached_update_dict;
+
+ /* ensure D-Bus properties are updated before reading them */
+ if (!gs_rpmostree_sysroot_call_reload_sync (priv->sysroot_proxy, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ cached_update = gs_rpmostree_os_dup_cached_update (priv->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 (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+
+ /* if we can process this online do not require a trigger */
+ if (gs_app_get_state (app) != AS_APP_STATE_UPDATABLE)
+ return TRUE;
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* already in correct state */
+ if (priv->update_triggered)
+ return TRUE;
+
+ /* 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 */
+ if (!gs_rpmostree_os_call_upgrade_sync (priv->os_proxy,
+ options,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ priv->update_triggered = TRUE;
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsAppList *related = gs_app_get_related (app);
+
+ /* 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 (plugin, app, 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 (plugin, app_tmp, cancellable, error))
+ return FALSE;
+ }
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_upgrade_trigger (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (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 ();
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* check is distro-upgrade */
+ if (gs_app_get_kind (app) != AS_APP_KIND_OS_UPGRADE)
+ return TRUE;
+
+ /* 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 */
+
+ if (!gs_rpmostree_os_call_rebase_sync (priv->os_proxy,
+ options,
+ new_refspec,
+ packages,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->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_plugin_repo_enable (GsPlugin *plugin,
+ GsApp *app,
+ gboolean enable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariantBuilder) options_builder = NULL;
+ g_autoptr(TransactionProgress) tp = NULL;
+
+ if (enable)
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ else
+ gs_app_set_state (app, AS_APP_STATE_REMOVING);
+
+ 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 (priv->os_proxy,
+ gs_app_get_id (app),
+ g_variant_builder_end (options_builder),
+ &transaction_address,
+ cancellable,
+ 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 (priv->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, AS_APP_STATE_INSTALLED);
+ else
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_install (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (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 ();
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* enable repo */
+ if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+ return gs_plugin_repo_enable (plugin, app, TRUE, cancellable, error);
+
+ switch (gs_app_get_state (app)) {
+ case AS_APP_STATE_AVAILABLE:
+ 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 AS_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",
+ as_app_state_to_string (gs_app_get_state (app)));
+ return FALSE;
+ }
+
+ gs_app_set_state (app, AS_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 */
+
+ if (!rpmostree_update_deployment (priv->os_proxy,
+ install_package,
+ NULL /* remove package */,
+ local_filename,
+ options,
+ &transaction_address,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->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, AS_APP_STATE_INSTALLED);
+
+ /* get the new icon from the package */
+ gs_app_set_local_file (app, NULL);
+ gs_app_add_icon (app, NULL);
+ gs_app_set_pixbuf (app, NULL);
+
+ /* no longer valid */
+ gs_app_clear_source_ids (app);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autofree gchar *transaction_address = NULL;
+ g_autoptr(GVariant) options = NULL;
+ g_autoptr(TransactionProgress) tp = transaction_progress_new ();
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* disable repo */
+ if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+ return gs_plugin_repo_enable (plugin, app, FALSE, cancellable, error);
+
+ gs_app_set_state (app, AS_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 */
+
+ if (!rpmostree_update_deployment (priv->os_proxy,
+ NULL /* install package */,
+ gs_app_get_source_default (app),
+ NULL /* install local package */,
+ options,
+ &transaction_address,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->sysroot_proxy,
+ transaction_address,
+ tp,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ /* state is not known: we don't know if we can re-install this app */
+ gs_app_set_state (app, AS_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
+resolve_installed_packages_app (GsPlugin *plugin,
+ GPtrArray *pkglist,
+ gchar **layered_packages,
+ gchar **layered_local_packages,
+ GsApp *app)
+{
+ for (guint i = 0; i < pkglist->len; i++) {
+ RpmOstreePackage *pkg = g_ptr_array_index (pkglist, i);
+ if (g_strcmp0 (rpm_ostree_package_get_name (pkg), gs_app_get_source_default (app)) == 0) {
+ gs_app_set_version (app, rpm_ostree_package_get_evr (pkg));
+ if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ if (g_strv_contains ((const gchar * const *) layered_packages,
+ rpm_ostree_package_get_name (pkg)) ||
+ g_strv_contains ((const gchar * const *) 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");
+ 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) == AS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, AS_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 */
+ if (!gs_app_is_installed (app)) {
+ switch (gs_app_get_kind (app)) {
+ case AS_APP_KIND_DESKTOP:
+ case AS_APP_KIND_WEB_APP:
+ case AS_APP_KIND_CONSOLE:
+ 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, gs_plugin_get_name (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;
+}
+
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+ GsAppList *list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autoptr(GPtrArray) pkglist = NULL;
+ g_autoptr(GVariant) default_deployment = NULL;
+ g_auto(GStrv) layered_packages = NULL;
+ g_auto(GStrv) layered_local_packages = NULL;
+ g_autofree gchar *checksum = NULL;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ /* ensure D-Bus properties are updated before reading them */
+ if (!gs_rpmostree_sysroot_call_reload_sync (priv->sysroot_proxy, cancellable, error)) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ default_deployment = gs_rpmostree_os_dup_default_deployment (priv->os_proxy);
+ g_assert (g_variant_lookup (default_deployment,
+ "packages", "^as",
+ &layered_packages));
+ g_assert (g_variant_lookup (default_deployment,
+ "requested-local-packages", "^as",
+ &layered_local_packages));
+ g_assert (g_variant_lookup (default_deployment,
+ "checksum", "s",
+ &checksum));
+
+ pkglist = rpm_ostree_db_query_all (priv->ot_repo, checksum, cancellable, error);
+ if (pkglist == NULL) {
+ gs_rpmostree_error_convert (error);
+ return FALSE;
+ }
+
+ 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_get_management_plugin (app) == NULL &&
+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE &&
+ gs_app_get_scope (app) == AS_APP_SCOPE_SYSTEM &&
+ gs_app_get_source_default (app) != NULL) {
+ gs_app_set_management_plugin (app, gs_plugin_get_name (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_get_management_plugin (app) == NULL &&
+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_UNKNOWN &&
+ gs_app_get_scope (app) == AS_APP_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 (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ continue;
+ if (gs_app_get_source_default (app) == NULL)
+ continue;
+
+ /* first try to resolve from installed packages */
+ found = resolve_installed_packages_app (plugin, pkglist, layered_packages, layered_local_packages, app);
+
+ /* if we didn't find anything, try resolving from available packages */
+ if (!found && priv->dnf_context != NULL)
+ found = resolve_available_packages_app (plugin, dnf_context_get_sack (priv->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;
+}
+
+gboolean
+gs_plugin_app_upgrade_download (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (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 ();
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* check is distro-upgrade */
+ if (gs_app_get_kind (app) != AS_APP_KIND_OS_UPGRADE)
+ return TRUE;
+
+ /* 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, AS_APP_STATE_INSTALLING);
+ tp->app = g_object_ref (app);
+
+ if (!gs_rpmostree_os_call_rebase_sync (priv->os_proxy,
+ options,
+ new_refspec,
+ packages,
+ NULL /* fd list */,
+ &transaction_address,
+ NULL /* fd list out */,
+ cancellable,
+ error)) {
+ gs_rpmostree_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ if (!gs_rpmostree_transaction_get_response_sync (priv->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, AS_APP_STATE_UPDATABLE);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_launch (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app),
+ gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* these are handled by the shell extensions plugin */
+ if (gs_app_get_kind (app) == AS_APP_KIND_SHELL_EXTENSION)
+ return TRUE;
+
+ return gs_plugin_app_launch (plugin, app, 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;
+ int r;
+ 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;
+
+ 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 ((r = 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, gs_plugin_get_name (plugin));
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_kind (app, AS_APP_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
+
+ /* 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, size);
+
+ /* set license */
+ license = headerGetString (h, RPMTAG_LICENSE);
+ if (license != NULL) {
+ g_autofree gchar *license_spdx = NULL;
+ license_spdx = as_utils_license_to_spdx (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);
+
+ gs_app_list_add (list, app);
+ ret = TRUE;
+
+out:
+ if (rpmfd != NULL)
+ (void) Fclose (rpmfd);
+ return ret;
+}
+
+static gchar **
+what_provides_decompose (gchar **values)
+{
+ GPtrArray *array = g_ptr_array_new ();
+
+ /* iter on each provide string, and wrap it with the Fedora prefix */
+ for (guint i = 0; values[i] != NULL; i++) {
+ g_ptr_array_add (array, g_strdup (values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("gstreamer0.10(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("gstreamer1(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("font(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("mimehandler(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("postscriptdriver(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("plasma4(%s)", values[i]));
+ g_ptr_array_add (array, g_strdup_printf ("plasma5(%s)", values[i]));
+ }
+ g_ptr_array_add (array, NULL);
+ return (gchar **) g_ptr_array_free (array, FALSE);
+}
+
+gboolean
+gs_plugin_add_search_what_provides (GsPlugin *plugin,
+ gchar **search,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autoptr(GPtrArray) pkglist = NULL;
+ g_auto(GStrv) provides = NULL;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ if (priv->dnf_context == NULL)
+ return TRUE;
+
+ provides = what_provides_decompose (search);
+ pkglist = find_packages_by_provides (dnf_context_get_sack (priv->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 (plugin, 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 (plugin));
+ gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT);
+ app_set_rpm_ostree_packaging_format (app);
+ gs_app_set_kind (app, AS_APP_KIND_GENERIC);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+ gs_app_add_source (app, dnf_package_get_name (pkg));
+
+ gs_plugin_cache_add (plugin, dnf_package_get_nevra (pkg), app);
+ gs_app_list_add (list, app);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_sources (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GMutexLocker) locker = NULL;
+ GPtrArray *repos;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ if (priv->dnf_context == NULL)
+ return TRUE;
+
+ repos = dnf_context_get_repos (priv->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, gs_plugin_get_name (plugin));
+ gs_app_set_kind (app, AS_APP_KIND_SOURCE);
+ 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 ? AS_APP_STATE_INSTALLED : AS_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_list_add (list, app);
+ }
+
+ return TRUE;
+}
diff --git a/plugins/rpm-ostree/meson.build b/plugins/rpm-ostree/meson.build
new file mode 100644
index 0000000..840f9e7
--- /dev/null
+++ b/plugins/rpm-ostree/meson.build
@@ -0,0 +1,26 @@
+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_pkgconfig_variable('libdir'), 'rpm-ostree'),
+ c_args : cargs,
+ dependencies : [ plugin_libs, libdnf, ostree, rpm, rpm_ostree ],
+ link_with : [
+ libgnomesoftware
+ ]
+)
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>