rpm-ostree/src/daemon/rpmostreed-transaction.c

808 lines
27 KiB
C
Raw Normal View History

/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "ostree.h"
#include <libglnx.h>
#include "rpmostreed-transaction.h"
#include "rpmostreed-errors.h"
#include "rpmostreed-sysroot.h"
struct _RpmostreedTransactionPrivate {
GDBusMethodInvocation *invocation;
GCancellable *cancellable;
/* For the duration of the transaction, we hold a ref to a new
* OstreeSysroot instance (to avoid any threading issues), and we
* also lock it.
*/
char *sysroot_path;
OstreeSysroot *sysroot;
GDBusServer *server;
GHashTable *peer_connections;
/* For emitting Finished signals to late connections. */
GVariant *finished_params;
guint watch_id;
};
enum {
PROP_0,
PROP_ACTIVE,
PROP_INVOCATION,
PROP_SYSROOT_PATH
};
enum {
CLOSED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
static void rpmostreed_transaction_initable_iface_init (GInitableIface *iface);
static void rpmostreed_transaction_dbus_iface_init (RPMOSTreeTransactionIface *iface);
/* XXX I tried using G_ADD_PRIVATE here, but was getting memory corruption
* on the 2nd instance and valgrind was going crazy with invalid reads
* and writes. So I'm falling back to the allegedly deprecated method
* and deferring further investigation. */
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (RpmostreedTransaction,
rpmostreed_transaction,
RPMOSTREE_TYPE_TRANSACTION_SKELETON,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
rpmostreed_transaction_initable_iface_init)
G_IMPLEMENT_INTERFACE (RPMOSTREE_TYPE_TRANSACTION,
rpmostreed_transaction_dbus_iface_init))
/* XXX This is lame but it's meant to keep it simple to
* transition to transaction_get_instance_private(). */
static RpmostreedTransactionPrivate *
rpmostreed_transaction_get_private (RpmostreedTransaction *self)
{
return self->priv;
}
static void
transaction_maybe_emit_closed (RpmostreedTransaction *self)
{
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
if (rpmostreed_transaction_get_active (self))
return;
if (g_hash_table_size (priv->peer_connections) > 0)
return;
g_signal_emit (self, signals[CLOSED], 0);
}
static void
transaction_connection_closed_cb (GDBusConnection *connection,
gboolean remote_peer_vanished,
GError *error,
RpmostreedTransaction *self)
{
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
g_debug ("%s (%p): Client disconnected",
G_OBJECT_TYPE_NAME (self), self);
g_hash_table_remove (priv->peer_connections, connection);
transaction_maybe_emit_closed (self);
}
static gboolean
transaction_new_connection_cb (GDBusServer *server,
GDBusConnection *connection,
RpmostreedTransaction *self)
{
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
GError *local_error = NULL;
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
connection, "/", &local_error);
if (local_error != NULL)
{
g_critical ("Failed to export interface: %s", local_error->message);
g_clear_error (&local_error);
return FALSE;
}
g_signal_connect_object (connection,
"closed",
G_CALLBACK (transaction_connection_closed_cb),
self, 0);
g_hash_table_add (priv->peer_connections, g_object_ref (connection));
g_debug ("%s (%p): Client connected",
G_OBJECT_TYPE_NAME (self), self);
return TRUE;
}
static void
transaction_owner_vanished_cb (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (user_data);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
if (priv->watch_id > 0)
{
g_bus_unwatch_name (priv->watch_id);
priv->watch_id = 0;
/* Emit the signal AFTER unwatching the bus name, since this
* may finalize the transaction and invalidate the watch_id. */
g_signal_emit (self, signals[CLOSED], 0);
}
}
static void
transaction_progress_changed_cb (OstreeAsyncProgress *progress,
RPMOSTreeTransaction *transaction)
{
guint64 start_time = ostree_async_progress_get_uint64 (progress, "start-time");
guint64 elapsed_secs = 0;
guint outstanding_fetches = ostree_async_progress_get_uint (progress, "outstanding-fetches");
guint outstanding_writes = ostree_async_progress_get_uint (progress, "outstanding-writes");
guint n_scanned_metadata = ostree_async_progress_get_uint (progress, "scanned-metadata");
guint metadata_fetched = ostree_async_progress_get_uint (progress, "metadata-fetched");
guint outstanding_metadata_fetches = ostree_async_progress_get_uint (progress, "outstanding-metadata-fetches");
guint total_delta_parts = ostree_async_progress_get_uint (progress, "total-delta-parts");
guint fetched_delta_parts = ostree_async_progress_get_uint (progress, "fetched-delta-parts");
guint total_delta_superblocks = ostree_async_progress_get_uint (progress, "total-delta-superblocks");
guint64 total_delta_part_size = ostree_async_progress_get_uint64 (progress, "total-delta-part-size");
guint fetched = ostree_async_progress_get_uint (progress, "fetched");
guint requested = ostree_async_progress_get_uint (progress, "requested");
guint64 bytes_sec = 0;
guint64 bytes_transferred = ostree_async_progress_get_uint64 (progress, "bytes-transferred");
GVariant *arg_time;
GVariant *arg_outstanding;
GVariant *arg_metadata;
GVariant *arg_delta;
GVariant *arg_content;
GVariant *arg_transfer;
g_autofree gchar *status = NULL;
/* If there is a status that is all we output */
status = ostree_async_progress_get_status (progress);
if (status) {
rpmostree_transaction_emit_message (transaction, g_strdup (status));
return;
}
if (start_time)
{
guint64 elapsed_secs = (g_get_monotonic_time () - start_time) / G_USEC_PER_SEC;
if (elapsed_secs)
bytes_sec = bytes_transferred / elapsed_secs;
}
arg_time = g_variant_new ("(tt)",
start_time,
elapsed_secs);
arg_outstanding = g_variant_new ("(uu)",
outstanding_fetches,
outstanding_writes);
arg_metadata = g_variant_new ("(uuu)",
n_scanned_metadata,
metadata_fetched,
outstanding_metadata_fetches);
arg_delta = g_variant_new ("(uuut)",
total_delta_parts,
fetched_delta_parts,
total_delta_superblocks,
total_delta_part_size);
arg_content = g_variant_new ("(uu)",
fetched,
requested);
arg_transfer = g_variant_new ("(tt)",
bytes_transferred,
bytes_sec);
/* This sinks the floating GVariant refs (I think...). */
rpmostree_transaction_emit_download_progress (transaction,
arg_time,
arg_outstanding,
arg_metadata,
arg_delta,
arg_content,
arg_transfer);
}
static void
transaction_gpg_verify_result_cb (OstreeRepo *repo,
const char *checksum,
OstreeGpgVerifyResult *result,
RPMOSTreeTransaction *transaction)
{
guint n, i;
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
n = ostree_gpg_verify_result_count_all (result);
for (i = 0; i < n; i++)
{
g_variant_builder_add (&builder, "v",
ostree_gpg_verify_result_get_all (result, i));
}
rpmostree_transaction_emit_signature_progress (transaction,
g_variant_builder_end (&builder),
g_strdup (checksum));
}
static void
transaction_execute_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (source_object);
RpmostreedTransactionClass *class = RPMOSTREED_TRANSACTION_GET_CLASS (self);
gboolean success = TRUE;
GError *local_error = NULL;
g_autoptr(GMainContext) mctx = g_main_context_new ();
/* libostree iterates and calls quit on main loop
* so we need to run in our own context. Having a different
* main context for worker threads should be standard practice
* anyways.
*/
g_main_context_push_thread_default (mctx);
if (class->execute != NULL)
success = class->execute (self, cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, local_error);
else
g_task_return_boolean (task, success);
/* Clean up context */
g_main_context_pop_thread_default (mctx);
}
static void
transaction_execute_done_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (source_object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
const char *error_message = NULL;
gboolean success;
GError *local_error = NULL;
success = g_task_propagate_boolean (G_TASK (result), &local_error);
if (success)
{
if (!rpmostreed_sysroot_reload (rpmostreed_sysroot_get (), &local_error))
success = FALSE;
}
/* Sanity check */
g_warn_if_fail ((success && local_error == NULL) ||
(!success && local_error != NULL));
if (local_error != NULL)
error_message = local_error->message;
if (error_message == NULL)
error_message = "";
g_debug ("%s (%p): Finished%s%s%s",
G_OBJECT_TYPE_NAME (self), self,
success ? "" : " (error: ",
success ? "" : error_message,
success ? "" : ")");
rpmostree_transaction_emit_finished (RPMOSTREE_TRANSACTION (self),
success, error_message);
/* Stash the Finished signal parameters in case we need
* to emit the signal again on subsequent new connections. */
priv->finished_params = g_variant_new ("(bs)", success, error_message);
g_variant_ref_sink (priv->finished_params);
g_object_notify (G_OBJECT (self), "active");
transaction_maybe_emit_closed (self);
}
static void
transaction_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
switch (property_id)
{
case PROP_INVOCATION:
priv->invocation = g_value_dup_object (value);
break;
case PROP_SYSROOT_PATH:
priv->sysroot_path = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
transaction_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
switch (property_id)
{
case PROP_INVOCATION:
g_value_set_object (value, priv->invocation);
break;
case PROP_SYSROOT_PATH:
g_value_set_string (value, priv->sysroot_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
transaction_dispose (GObject *object)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
if (priv->sysroot != NULL)
ostree_sysroot_unlock (priv->sysroot);
g_hash_table_remove_all (priv->peer_connections);
g_clear_object (&priv->invocation);
g_clear_object (&priv->cancellable);
g_clear_object (&priv->sysroot);
g_clear_object (&priv->server);
g_clear_pointer (&priv->sysroot_path, g_free);
g_clear_pointer (&priv->finished_params, (GDestroyNotify) g_variant_unref);
G_OBJECT_CLASS (rpmostreed_transaction_parent_class)->dispose (object);
}
static void
transaction_finalize (GObject *object)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
g_debug ("%s (%p): Finalized", G_OBJECT_TYPE_NAME (self), self);
if (priv->watch_id > 0)
g_bus_unwatch_name (priv->watch_id);
g_hash_table_destroy (priv->peer_connections);
G_OBJECT_CLASS (rpmostreed_transaction_parent_class)->finalize (object);
}
static void
transaction_constructed (GObject *object)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (object);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
G_OBJECT_CLASS (rpmostreed_transaction_parent_class)->constructed (object);
if (priv->invocation != NULL)
{
GDBusConnection *connection;
const char *sender;
connection = g_dbus_method_invocation_get_connection (priv->invocation);
sender = g_dbus_method_invocation_get_sender (priv->invocation);
/* Watch the sender's bus name until the transaction is started.
* This guards against a process initiating a transaction but then
* terminating before calling Start(). If the bus name vanishes
* during this time, we abort the transaction. */
priv->watch_id = g_bus_watch_name_on_connection (connection,
sender,
G_BUS_NAME_WATCHER_FLAGS_NONE,
NULL,
transaction_owner_vanished_cb,
self,
NULL);
}
}
static gboolean
transaction_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (initable);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
g_autofree char *guid = NULL;
gboolean ret = FALSE;
if (G_IS_CANCELLABLE (cancellable))
priv->cancellable = g_object_ref (cancellable);
/* Set up a private D-Bus server over which to emit
* progress and informational messages to the caller. */
guid = g_dbus_generate_guid ();
priv->server = g_dbus_server_new_sync ("unix:tmpdir=/tmp/rpm-ostree",
G_DBUS_SERVER_FLAGS_NONE,
guid,
NULL,
cancellable,
error);
if (priv->server == NULL)
goto out;
g_signal_connect_object (priv->server,
"new-connection",
G_CALLBACK (transaction_new_connection_cb),
self, 0);
if (priv->sysroot_path != NULL)
{
g_autoptr(GFile) tmp_path = g_file_new_for_path (priv->sysroot_path);
gboolean lock_acquired = FALSE;
/* We create a *new* sysroot to avoid threading issues like data
* races - OstreeSysroot has no internal locking. Efficiency
* could be improved with a "clone" operation to avoid reloading
* everything from disk.
*/
priv->sysroot = ostree_sysroot_new (tmp_path);
if (!ostree_sysroot_load (priv->sysroot, cancellable, error))
goto out;
if (!ostree_sysroot_try_lock (priv->sysroot, &lock_acquired, error))
goto out;
if (!lock_acquired)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_BUSY,
"System transaction in progress");
goto out;
}
}
g_dbus_server_start (priv->server);
g_debug ("%s (%p): Initialized, listening on %s",
G_OBJECT_TYPE_NAME (self), self,
rpmostreed_transaction_get_client_address (self));
ret = TRUE;
out:
return ret;
}
static gboolean
transaction_handle_cancel (RPMOSTreeTransaction *transaction,
GDBusMethodInvocation *invocation)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (transaction);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
g_debug ("%s (%p): Cancelled", G_OBJECT_TYPE_NAME (self), self);
g_cancellable_cancel (priv->cancellable);
rpmostree_transaction_complete_cancel (transaction, invocation);
return TRUE;
}
static gboolean
transaction_handle_start (RPMOSTreeTransaction *transaction,
GDBusMethodInvocation *invocation)
{
RpmostreedTransaction *self = RPMOSTREED_TRANSACTION (transaction);
RpmostreedTransactionPrivate *priv = rpmostreed_transaction_get_private (self);
gboolean started = FALSE;
/* The bus name watch ID doubles as a "not-yet-started" flag.
* Once started the transaction proceeds independently of the
* initiating process whose bus name we were watching. */
if (priv->watch_id > 0)
{
GTask *task;
started = TRUE;
g_debug ("%s (%p): Started", G_OBJECT_TYPE_NAME (self), self);
g_bus_unwatch_name (priv->watch_id);
priv->watch_id = 0;
task = g_task_new (transaction,
priv->cancellable,
transaction_execute_done_cb,
NULL);
g_task_run_in_thread (task, transaction_execute_thread);
g_object_unref (task);
}
rpmostree_transaction_complete_start (transaction, invocation, started);
/* If the transaction is already finished, emit the
* Finished signal again but only on this connection. */
if (priv->finished_params != NULL)
{
GDBusConnection *connection;
const char *object_path;
const char *interface_name;
GError *local_error = NULL;
connection = g_dbus_method_invocation_get_connection (invocation);
object_path = g_dbus_method_invocation_get_object_path (invocation);
interface_name = g_dbus_method_invocation_get_interface_name (invocation);
g_dbus_connection_emit_signal (connection,
NULL,
object_path,
interface_name,
"Finished",
priv->finished_params,
&local_error);
if (local_error != NULL)
{
g_critical ("%s", local_error->message);
g_clear_error (&local_error);
}
}
return TRUE;
}
static void
rpmostreed_transaction_class_init (RpmostreedTransactionClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (RpmostreedTransactionPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = transaction_set_property;
object_class->get_property = transaction_get_property;
object_class->dispose = transaction_dispose;
object_class->finalize = transaction_finalize;
object_class->constructed = transaction_constructed;
g_object_class_install_property (object_class,
PROP_ACTIVE,
g_param_spec_boolean ("active",
"Active",
"Whether the transaction is active (unfinished)",
TRUE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_INVOCATION,
g_param_spec_object ("invocation",
"Invocation",
"D-Bus method invocation",
G_TYPE_DBUS_METHOD_INVOCATION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_SYSROOT_PATH,
g_param_spec_string ("sysroot-path",
"Sysroot path",
"An OstreeSysroot path",
"",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
signals[CLOSED] = g_signal_new ("closed",
RPMOSTREED_TYPE_TRANSACTION,
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
rpmostreed_transaction_initable_iface_init (GInitableIface *iface)
{
iface->init = transaction_initable_init;
}
static void
rpmostreed_transaction_dbus_iface_init (RPMOSTreeTransactionIface *iface)
{
iface->handle_cancel = transaction_handle_cancel;
iface->handle_start = transaction_handle_start;
}
static void
rpmostreed_transaction_init (RpmostreedTransaction *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
RPMOSTREED_TYPE_TRANSACTION,
RpmostreedTransactionPrivate);
self->priv->peer_connections = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
g_object_unref,
NULL);
}
gboolean
rpmostreed_transaction_get_active (RpmostreedTransaction *transaction)
{
RpmostreedTransactionPrivate *priv;
g_return_val_if_fail (RPMOSTREED_IS_TRANSACTION (transaction), FALSE);
priv = rpmostreed_transaction_get_private (transaction);
return (priv->finished_params == NULL);
}
OstreeSysroot *
rpmostreed_transaction_get_sysroot (RpmostreedTransaction *transaction)
{
RpmostreedTransactionPrivate *priv;
g_return_val_if_fail (RPMOSTREED_IS_TRANSACTION (transaction), NULL);
priv = rpmostreed_transaction_get_private (transaction);
return priv->sysroot;
}
GDBusMethodInvocation *
rpmostreed_transaction_get_invocation (RpmostreedTransaction *transaction)
{
RpmostreedTransactionPrivate *priv;
g_return_val_if_fail (RPMOSTREED_IS_TRANSACTION (transaction), NULL);
priv = rpmostreed_transaction_get_private (transaction);
return priv->invocation;
}
const char *
rpmostreed_transaction_get_client_address (RpmostreedTransaction *transaction)
{
RpmostreedTransactionPrivate *priv;
g_return_val_if_fail (RPMOSTREED_IS_TRANSACTION (transaction), NULL);
priv = rpmostreed_transaction_get_private (transaction);
return g_dbus_server_get_client_address (priv->server);
}
void
rpmostreed_transaction_emit_message_printf (RpmostreedTransaction *transaction,
const char *format,
...)
{
g_autofree char *formatted_message = NULL;
va_list args;
g_return_if_fail (RPMOSTREED_IS_TRANSACTION (transaction));
g_return_if_fail (format != NULL);
va_start (args, format);
formatted_message = g_strdup_vprintf (format, args);
va_end (args);
rpmostree_transaction_emit_message (RPMOSTREE_TRANSACTION (transaction),
formatted_message);
}
gboolean
rpmostreed_transaction_is_compatible (RpmostreedTransaction *transaction,
GDBusMethodInvocation *invocation)
{
RpmostreedTransactionPrivate *priv;
const char *method_name_a;
const char *method_name_b;
GVariant *parameters_a;
GVariant *parameters_b;
g_return_val_if_fail (RPMOSTREED_IS_TRANSACTION (transaction), FALSE);
g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
priv = rpmostreed_transaction_get_private (transaction);
method_name_a = g_dbus_method_invocation_get_method_name (priv->invocation);
method_name_b = g_dbus_method_invocation_get_method_name (invocation);
parameters_a = g_dbus_method_invocation_get_parameters (priv->invocation);
parameters_b = g_dbus_method_invocation_get_parameters (invocation);
return g_str_equal (method_name_a, method_name_b) &&
g_variant_equal (parameters_a, parameters_b);
}
void
rpmostreed_transaction_connect_download_progress (RpmostreedTransaction *transaction,
OstreeAsyncProgress *progress)
{
g_return_if_fail (RPMOSTREED_IS_TRANSACTION (transaction));
g_return_if_fail (OSTREE_IS_ASYNC_PROGRESS (progress));
g_signal_connect_object (progress,
"changed",
G_CALLBACK (transaction_progress_changed_cb),
transaction, 0);
}
void
rpmostreed_transaction_connect_signature_progress (RpmostreedTransaction *transaction,
OstreeRepo *repo)
{
g_return_if_fail (RPMOSTREED_IS_TRANSACTION (transaction));
g_return_if_fail (OSTREE_REPO (repo));
g_signal_connect_object (repo, "gpg-verify-result",
G_CALLBACK (transaction_gpg_verify_result_cb),
transaction, 0);
}