de7b750728
Takes a GVariant returned by the daemon's various "PkgDiff" methods.
699 lines
22 KiB
C
699 lines
22 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
|
*
|
|
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the licence or (at
|
|
* your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "rpmostree-dbus-helpers.h"
|
|
#include "libgsystem.h"
|
|
#include "libglnx.h"
|
|
#include <sys/socket.h>
|
|
#include "glib-unix.h"
|
|
#include <signal.h>
|
|
|
|
static GPid peer_pid = 0;
|
|
|
|
void
|
|
rpmostree_cleanup_peer ()
|
|
{
|
|
if (peer_pid > 0)
|
|
kill (peer_pid, SIGTERM);
|
|
}
|
|
|
|
static gboolean
|
|
get_connection_for_path (gchar *sysroot,
|
|
gboolean force_peer,
|
|
GCancellable *cancellable,
|
|
GDBusConnection **out_connection,
|
|
GError **error)
|
|
{
|
|
glnx_unref_object GDBusConnection *connection = NULL;
|
|
glnx_unref_object GDBusObjectManager *om = NULL;
|
|
glnx_unref_object GSocketConnection *stream = NULL;
|
|
glnx_unref_object GSocket *socket = NULL;
|
|
|
|
gchar buffer[16];
|
|
|
|
int pair[2];
|
|
gboolean ret = FALSE;
|
|
|
|
const gchar *args[] = {
|
|
"rpm-ostreed",
|
|
"--sysroot", sysroot,
|
|
"--dbus-peer", buffer,
|
|
NULL
|
|
};
|
|
|
|
/* This is only intended for use by installed tests.
|
|
* Note that it disregards the 'sysroot' and 'force_peer' options
|
|
* and assumes the service activation command has been configured
|
|
* to use the desired system root path. */
|
|
if (g_getenv ("RPMOSTREE_USE_SESSION_BUS") != NULL)
|
|
{
|
|
if (sysroot != NULL)
|
|
g_warning ("RPMOSTREE_USE_SESSION_BUS set, ignoring --sysroot=%s", sysroot);
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, error);
|
|
goto out;
|
|
}
|
|
|
|
if (sysroot == NULL)
|
|
sysroot = "/";
|
|
|
|
if (g_strcmp0 ("/", sysroot) == 0 && force_peer == FALSE)
|
|
{
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
|
|
goto out;
|
|
}
|
|
|
|
g_print ("Running in single user mode. Be sure no other users are modifying the system\n");
|
|
if (socketpair (AF_UNIX, SOCK_STREAM, 0, pair) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Couldn't create socket pair: %s",
|
|
g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
g_snprintf (buffer, sizeof (buffer), "%d", pair[1]);
|
|
|
|
socket = g_socket_new_from_fd (pair[0], error);
|
|
if (socket == NULL)
|
|
{
|
|
close (pair[0]);
|
|
close (pair[1]);
|
|
goto out;
|
|
}
|
|
|
|
if (!g_spawn_async (NULL, (gchar **)args, NULL,
|
|
G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD,
|
|
NULL, NULL, &peer_pid, error))
|
|
{
|
|
close (pair[1]);
|
|
goto out;
|
|
}
|
|
|
|
stream = g_socket_connection_factory_create_connection (socket);
|
|
connection = g_dbus_connection_new_sync (G_IO_STREAM (stream), NULL,
|
|
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
|
|
NULL, cancellable, error);
|
|
|
|
out:
|
|
if (connection)
|
|
{
|
|
ret = TRUE;
|
|
*out_connection = g_steal_pointer (&connection);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* rpmostree_load_sysroot
|
|
* @sysroot: sysroot path
|
|
* @force_peer: Force a peer connection
|
|
* @cancellable: A GCancellable
|
|
* @out_sysroot: (out) Return location for sysroot
|
|
* @error: A pointer to a GError pointer.
|
|
*
|
|
* Returns: True on success
|
|
**/
|
|
gboolean
|
|
rpmostree_load_sysroot (gchar *sysroot,
|
|
gboolean force_peer,
|
|
GCancellable *cancellable,
|
|
RPMOSTreeSysroot **out_sysroot_proxy,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
const char *bus_name = NULL;
|
|
glnx_unref_object GDBusConnection *connection = NULL;
|
|
glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL;
|
|
|
|
if (!get_connection_for_path (sysroot,
|
|
force_peer,
|
|
cancellable,
|
|
&connection,
|
|
error))
|
|
goto out;
|
|
|
|
if (g_dbus_connection_get_unique_name (connection) != NULL)
|
|
bus_name = BUS_NAME;
|
|
|
|
sysroot_proxy = rpmostree_sysroot_proxy_new_sync (connection,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
bus_name,
|
|
"/org/projectatomic/rpmostree1/Sysroot",
|
|
NULL,
|
|
error);
|
|
if (sysroot_proxy == NULL)
|
|
goto out;
|
|
|
|
*out_sysroot_proxy = g_steal_pointer (&sysroot_proxy);
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
rpmostree_load_os_proxy (RPMOSTreeSysroot *sysroot_proxy,
|
|
gchar *opt_osname,
|
|
GCancellable *cancellable,
|
|
RPMOSTreeOS **out_os_proxy,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
const char *bus_name;
|
|
g_autofree char *os_object_path = NULL;
|
|
glnx_unref_object RPMOSTreeOS *os_proxy = NULL;
|
|
|
|
GDBusConnection *connection = NULL; /* owned by sysroot_proxy */
|
|
|
|
if (opt_osname == NULL)
|
|
{
|
|
os_object_path = rpmostree_sysroot_dup_booted (sysroot_proxy);
|
|
}
|
|
|
|
if (os_object_path == NULL)
|
|
{
|
|
/* Usually if opt_osname is null and the property isn't
|
|
populated that means the daemon isn't listen on the bus
|
|
make the call anyways to get the standard error.
|
|
*/
|
|
if (!opt_osname)
|
|
opt_osname = "";
|
|
|
|
if (!rpmostree_sysroot_call_get_os_sync (sysroot_proxy,
|
|
opt_osname,
|
|
&os_object_path,
|
|
cancellable,
|
|
error))
|
|
goto out;
|
|
}
|
|
|
|
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy));
|
|
|
|
if (g_dbus_connection_get_unique_name (connection) != NULL)
|
|
bus_name = BUS_NAME;
|
|
|
|
os_proxy = rpmostree_os_proxy_new_sync (connection,
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
bus_name,
|
|
os_object_path,
|
|
cancellable,
|
|
error);
|
|
|
|
if (os_proxy == NULL)
|
|
goto out;
|
|
|
|
*out_os_proxy = g_steal_pointer (&os_proxy);
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* transaction_console_get_progress_line
|
|
*
|
|
* Similar to ostree_repo_pull_default_console_progress_changed
|
|
*
|
|
* Displays outstanding fetch progress in bytes/sec,
|
|
* or else outstanding content or metadata writes to the repository in
|
|
* number of objects.
|
|
**/
|
|
static gchar *
|
|
transaction_get_progress_line (guint64 start_time,
|
|
guint64 elapsed_secs,
|
|
guint outstanding_fetches,
|
|
guint outstanding_writes,
|
|
guint n_scanned_metadata,
|
|
guint metadata_fetched,
|
|
guint outstanding_metadata_fetches,
|
|
guint total_delta_parts,
|
|
guint fetched_delta_parts,
|
|
guint total_delta_superblocks,
|
|
guint64 total_delta_part_size,
|
|
guint fetched,
|
|
guint requested,
|
|
guint64 bytes_transferred,
|
|
guint64 bytes_sec)
|
|
{
|
|
GString *buf;
|
|
|
|
buf = g_string_new ("");
|
|
|
|
if (outstanding_fetches)
|
|
{
|
|
g_autofree gchar *formatted_bytes_transferred = g_format_size_full (bytes_transferred, 0);
|
|
g_autofree gchar *formatted_bytes_sec = NULL;
|
|
|
|
if (!bytes_sec)
|
|
formatted_bytes_sec = g_strdup ("-");
|
|
else
|
|
formatted_bytes_sec = g_format_size (bytes_sec);
|
|
|
|
if (total_delta_parts > 0)
|
|
{
|
|
g_autofree gchar *formatted_total = g_format_size (total_delta_part_size);
|
|
g_string_append_printf (buf, "Receiving delta parts: %u/%u %s/s %s/%s",
|
|
fetched_delta_parts, total_delta_parts,
|
|
formatted_bytes_sec, formatted_bytes_transferred,
|
|
formatted_total);
|
|
}
|
|
else if (outstanding_metadata_fetches)
|
|
{
|
|
g_string_append_printf (buf, "Receiving metadata objects: %u/(estimating) %s/s %s",
|
|
metadata_fetched, formatted_bytes_sec, formatted_bytes_transferred);
|
|
}
|
|
else
|
|
{
|
|
g_string_append_printf (buf, "Receiving objects: %u%% (%u/%u) %s/s %s",
|
|
(guint)((((double)fetched) / requested) * 100),
|
|
fetched, requested, formatted_bytes_sec, formatted_bytes_transferred);
|
|
}
|
|
}
|
|
else if (outstanding_writes)
|
|
{
|
|
g_string_append_printf (buf, "Writing objects: %u", outstanding_writes);
|
|
}
|
|
else
|
|
{
|
|
g_string_append_printf (buf, "Scanning metadata: %u", n_scanned_metadata);
|
|
}
|
|
|
|
return g_string_free (buf, FALSE);
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GSConsole *console;
|
|
gboolean in_status_line;
|
|
GError *error;
|
|
GMainLoop *loop;
|
|
gboolean complete;
|
|
} TransactionProgress;
|
|
|
|
|
|
static TransactionProgress *
|
|
transaction_progress_new (void)
|
|
{
|
|
TransactionProgress *self;
|
|
|
|
self = g_slice_new0 (TransactionProgress);
|
|
self->console = gs_console_get ();
|
|
self->loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
static void
|
|
transaction_progress_free (TransactionProgress *self)
|
|
{
|
|
g_main_loop_unref (self->loop);
|
|
g_slice_free (TransactionProgress, self);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
end_status_line (TransactionProgress *self)
|
|
{
|
|
gboolean ret = TRUE;
|
|
|
|
if (self->in_status_line)
|
|
{
|
|
ret = gs_console_end_status_line (self->console, NULL, NULL);
|
|
self->in_status_line = FALSE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
add_status_line (TransactionProgress *self,
|
|
const char *line)
|
|
{
|
|
self->in_status_line = TRUE;
|
|
return gs_console_begin_status_line (self->console, line, NULL, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
transaction_progress_end (TransactionProgress *self)
|
|
{
|
|
end_status_line (self);
|
|
self->console = NULL;
|
|
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, "SignatureProgress") == 0)
|
|
{
|
|
g_autoptr(GVariant) sig = NULL;
|
|
sig = g_variant_get_child_value (parameters, 0);
|
|
rpmostree_print_signatures (g_variant_ref (sig), " ");
|
|
add_status_line (tp, "\n");
|
|
}
|
|
else if (g_strcmp0 (signal_name, "Message") == 0)
|
|
{
|
|
g_autofree gchar *message = NULL;
|
|
|
|
g_variant_get_child (parameters, 0, "s", &message);
|
|
if (tp->in_status_line)
|
|
add_status_line (tp, message);
|
|
else
|
|
g_print ("%s\n", message);
|
|
}
|
|
else if (g_strcmp0 (signal_name, "DownloadProgress") == 0)
|
|
{
|
|
g_autofree gchar *line = NULL;
|
|
|
|
guint64 start_time;
|
|
guint64 elapsed_secs;
|
|
guint outstanding_fetches;
|
|
guint outstanding_writes;
|
|
guint n_scanned_metadata;
|
|
guint metadata_fetched;
|
|
guint outstanding_metadata_fetches;
|
|
guint total_delta_parts;
|
|
guint fetched_delta_parts;
|
|
guint total_delta_superblocks;
|
|
guint64 total_delta_part_size;
|
|
guint fetched;
|
|
guint requested;
|
|
guint64 bytes_transferred;
|
|
guint64 bytes_sec;
|
|
g_variant_get (parameters, "((tt)(uu)(uuu)(uuut)(uu)(tt))",
|
|
&start_time, &elapsed_secs,
|
|
&outstanding_fetches, &outstanding_writes,
|
|
&n_scanned_metadata, &metadata_fetched,
|
|
&outstanding_metadata_fetches,
|
|
&total_delta_parts, &fetched_delta_parts,
|
|
&total_delta_superblocks, &total_delta_part_size,
|
|
&fetched, &requested, &bytes_transferred, &bytes_sec);
|
|
|
|
line = transaction_get_progress_line (start_time, elapsed_secs,
|
|
outstanding_fetches,
|
|
outstanding_writes,
|
|
n_scanned_metadata,
|
|
metadata_fetched,
|
|
outstanding_metadata_fetches,
|
|
total_delta_parts,
|
|
fetched_delta_parts,
|
|
total_delta_superblocks,
|
|
total_delta_part_size,
|
|
fetched,
|
|
requested,
|
|
bytes_transferred,
|
|
bytes_sec);
|
|
add_status_line (tp, line);
|
|
}
|
|
else if (g_strcmp0 (signal_name, "ProgressEnd") == 0)
|
|
{
|
|
end_status_line (tp);
|
|
}
|
|
else if (g_strcmp0 (signal_name, "Finished") == 0)
|
|
{
|
|
if (tp->error == NULL)
|
|
{
|
|
g_autofree char *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_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
/* Owner shouldn't change durning a transaction
|
|
* that messes with notifications, abort, abort.
|
|
*/
|
|
TransactionProgress *tp = user_data;
|
|
tp->error = g_dbus_error_new_for_dbus_error ("org.projectatomic.rpmostreed.Error.Failed",
|
|
"Bus owner changed, aborting.");
|
|
transaction_progress_end (tp);
|
|
}
|
|
|
|
static void
|
|
cancelled_handler (GCancellable *cancellable,
|
|
gpointer user_data)
|
|
{
|
|
RPMOSTreeTransaction *transaction = user_data;
|
|
rpmostree_transaction_call_cancel_sync (transaction, NULL, NULL);
|
|
}
|
|
|
|
|
|
gboolean
|
|
rpmostree_transaction_get_response_sync (RPMOSTreeSysroot *sysroot_proxy,
|
|
const char *transaction_address,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GDBusConnection *connection;
|
|
glnx_unref_object GDBusObjectManager *object_manager = NULL;
|
|
glnx_unref_object RPMOSTreeTransaction *transaction = NULL;
|
|
g_autoptr(GDBusConnection) peer_connection = NULL;
|
|
|
|
TransactionProgress *tp = transaction_progress_new ();
|
|
|
|
const char *bus_name;
|
|
gint cancel_handler;
|
|
gulong signal_handler = 0;
|
|
gboolean success = FALSE;
|
|
gboolean just_started = FALSE;
|
|
|
|
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy));
|
|
|
|
if (g_dbus_connection_get_unique_name (connection) != NULL)
|
|
bus_name = BUS_NAME;
|
|
|
|
/* If we are on the message bus, setup object manager connection
|
|
* to notify if the owner changes. */
|
|
if (bus_name != NULL)
|
|
{
|
|
object_manager = rpmostree_object_manager_client_new_sync (connection,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
bus_name,
|
|
"/org/projectatomic/rpmostree1",
|
|
cancellable,
|
|
error);
|
|
|
|
if (object_manager == NULL)
|
|
goto out;
|
|
|
|
g_signal_connect (object_manager,
|
|
"notify::name-owner",
|
|
G_CALLBACK (on_owner_changed),
|
|
tp);
|
|
}
|
|
|
|
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 = rpmostree_transaction_proxy_new_sync (peer_connection,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_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 (!rpmostree_transaction_call_start_sync (transaction,
|
|
&just_started,
|
|
cancellable,
|
|
error))
|
|
goto out;
|
|
|
|
/* FIXME Use the 'just_started' flag to determine whether to print
|
|
* a message about reattaching to an in-progress transaction,
|
|
* like:
|
|
*
|
|
* Existing upgrade in progress, reattaching. Control-C to cancel.
|
|
*
|
|
* But that requires having a printable description of the
|
|
* operation. Maybe just add a string arg to this function?
|
|
*/
|
|
|
|
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, tp->error);
|
|
}
|
|
else
|
|
{
|
|
success = TRUE;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (signal_handler)
|
|
g_signal_handler_disconnect (transaction, signal_handler);
|
|
|
|
transaction_progress_free (tp);
|
|
return success;
|
|
}
|
|
|
|
|
|
void
|
|
rpmostree_print_signatures (GVariant *variant,
|
|
const gchar *sep)
|
|
{
|
|
GString *sigs_buffer;
|
|
guint i;
|
|
guint n_sigs = g_variant_n_children (variant);
|
|
sigs_buffer = g_string_sized_new (256);
|
|
|
|
for (i = 0; i < n_sigs; i++)
|
|
{
|
|
g_autoptr(GVariant) v = NULL;
|
|
g_string_append_c (sigs_buffer, '\n');
|
|
g_variant_get_child (variant, i, "v", &v);
|
|
ostree_gpg_verify_result_describe_variant (v, sigs_buffer, sep,
|
|
OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT);
|
|
}
|
|
|
|
g_print ("%s", sigs_buffer->str);
|
|
g_string_free (sigs_buffer, TRUE);
|
|
}
|
|
|
|
static gint
|
|
pkg_diff_variant_compare (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer unused)
|
|
{
|
|
const char *pkg_name_a = NULL;
|
|
const char *pkg_name_b = NULL;
|
|
|
|
g_variant_get_child ((GVariant *) a, 0, "&s", &pkg_name_a);
|
|
g_variant_get_child ((GVariant *) b, 0, "&s", &pkg_name_b);
|
|
|
|
/* XXX Names should be unique since we're comparing packages
|
|
* from two different trees... right? */
|
|
|
|
return g_strcmp0 (pkg_name_a, pkg_name_b);
|
|
}
|
|
|
|
static void
|
|
pkg_diff_variant_print (GVariant *variant)
|
|
{
|
|
g_autoptr(GVariant) details = NULL;
|
|
const char *old_name, *old_evr, *old_arch;
|
|
const char *new_name, *new_evr, *new_arch;
|
|
gboolean have_old = FALSE;
|
|
gboolean have_new = FALSE;
|
|
|
|
details = g_variant_get_child_value (variant, 2);
|
|
g_return_if_fail (details != NULL);
|
|
|
|
have_old = g_variant_lookup (details,
|
|
"PreviousPackage", "(&s&s&s)",
|
|
&old_name, &old_evr, &old_arch);
|
|
|
|
have_new = g_variant_lookup (details,
|
|
"NewPackage", "(&s&s&s)",
|
|
&new_name, &new_evr, &new_arch);
|
|
|
|
if (have_old && have_new)
|
|
{
|
|
g_print ("!%s-%s-%s\n", old_name, old_evr, old_arch);
|
|
g_print ("=%s-%s-%s\n", new_name, new_evr, new_arch);
|
|
}
|
|
else if (have_old)
|
|
{
|
|
g_print ("-%s-%s-%s\n", old_name, old_evr, old_arch);
|
|
}
|
|
else if (have_new)
|
|
{
|
|
g_print ("+%s-%s-%s\n", new_name, new_evr, new_arch);
|
|
}
|
|
}
|
|
|
|
void
|
|
rpmostree_print_package_diffs (GVariant *variant)
|
|
{
|
|
GQueue queue = G_QUEUE_INIT;
|
|
GVariantIter iter;
|
|
GVariant *child;
|
|
|
|
/* GVariant format should be a(sua{sv}) */
|
|
|
|
g_return_if_fail (variant != NULL);
|
|
|
|
g_variant_iter_init (&iter, variant);
|
|
|
|
/* Queue takes ownership of the child variant. */
|
|
while ((child = g_variant_iter_next_value (&iter)) != NULL)
|
|
g_queue_insert_sorted (&queue, child, pkg_diff_variant_compare, NULL);
|
|
|
|
while (!g_queue_is_empty (&queue))
|
|
{
|
|
child = g_queue_pop_head (&queue);
|
|
pkg_diff_variant_print (child);
|
|
g_variant_unref (child);
|
|
}
|
|
}
|