3e833659b7
Just like `rpm-ostree ex`, for things like `ex livefs` that have DBus interfaces, we should segregate these off so that people know they're unstable. And conversely that they can test for the presence of the method on the main interface for stability. I initially tried having the same `RpmostreeOS` object implement both but couldn't work out how to do that; see https://mail.gnome.org/archives/gtk-app-devel-list/2017-March/msg00161.html Closes: #701 Approved by: jlebon
519 lines
16 KiB
C
519 lines
16 KiB
C
/*
|
|
* 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 "rpmostreed-daemon.h"
|
|
#include "rpmostreed-sysroot.h"
|
|
#include "rpmostreed-types.h"
|
|
#include "rpmostreed-utils.h"
|
|
|
|
#include <libglnx.h>
|
|
#include <systemd/sd-journal.h>
|
|
#include <systemd/sd-daemon.h>
|
|
#include <stdio.h>
|
|
|
|
#define RPMOSTREE_MESSAGE_TRANSACTION_STARTED SD_ID128_MAKE(d5,be,a3,7a,8f,c8,4f,f5,9d,bc,fd,79,17,7b,7d,f8)
|
|
|
|
/**
|
|
* SECTION: daemon
|
|
* @title: RpmostreedDaemon
|
|
* @short_description: Main daemon object
|
|
*
|
|
* Object holding all global state.
|
|
*/
|
|
|
|
typedef struct _RpmostreedDaemonClass RpmostreedDaemonClass;
|
|
|
|
/**
|
|
* RpmostreedDaemon:
|
|
*
|
|
* The #RpmostreedDaemon structure contains only private data and should
|
|
* only be accessed using the provided API.
|
|
*/
|
|
struct _RpmostreedDaemon {
|
|
GObject parent_instance;
|
|
|
|
GHashTable *bus_clients;
|
|
|
|
gboolean running;
|
|
RpmostreedSysroot *sysroot;
|
|
gchar *sysroot_path;
|
|
|
|
GDBusConnection *connection;
|
|
GDBusObjectManagerServer *object_manager;
|
|
};
|
|
|
|
struct _RpmostreedDaemonClass {
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
static RpmostreedDaemon *_daemon_instance;
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONNECTION,
|
|
PROP_OBJECT_MANAGER,
|
|
PROP_SYSROOT_PATH
|
|
};
|
|
|
|
static void rpmostreed_daemon_initable_iface_init (GInitableIface *iface);
|
|
static void render_systemd_status (RpmostreedDaemon *self);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (RpmostreedDaemon, rpmostreed_daemon, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
|
|
rpmostreed_daemon_initable_iface_init))
|
|
|
|
typedef struct {
|
|
GAsyncReadyCallback callback;
|
|
gpointer callback_data;
|
|
gpointer proxy_source;
|
|
} DaemonTaskData;
|
|
|
|
|
|
static void
|
|
daemon_finalize (GObject *object)
|
|
{
|
|
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
|
|
|
|
g_clear_object (&self->object_manager);
|
|
self->object_manager = NULL;
|
|
|
|
g_clear_object (&self->sysroot);
|
|
|
|
g_object_unref (self->connection);
|
|
g_hash_table_unref (self->bus_clients);
|
|
|
|
g_free (self->sysroot_path);
|
|
G_OBJECT_CLASS (rpmostreed_daemon_parent_class)->finalize (object);
|
|
|
|
_daemon_instance = NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
daemon_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_OBJECT_MANAGER:
|
|
g_value_set_object (value, self->object_manager);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
daemon_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CONNECTION:
|
|
g_assert (self->connection == NULL);
|
|
self->connection = g_value_dup_object (value);
|
|
break;
|
|
case PROP_SYSROOT_PATH:
|
|
g_assert (self->sysroot_path == NULL);
|
|
self->sysroot_path = g_value_dup_string (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
rpmostreed_daemon_init (RpmostreedDaemon *self)
|
|
{
|
|
g_assert (_daemon_instance == NULL);
|
|
_daemon_instance = self;
|
|
|
|
self->sysroot_path = NULL;
|
|
self->sysroot = NULL;
|
|
self->bus_clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
}
|
|
|
|
static char *
|
|
render_txn (const char *method,
|
|
const char *sender,
|
|
const char *path)
|
|
{
|
|
return g_strdup_printf ("%s %s %s", method, sender, path);
|
|
}
|
|
|
|
static void
|
|
on_active_txn_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
RpmostreedDaemon *self = user_data;
|
|
g_autoptr(GVariant) active_txn = NULL;
|
|
const char *method, *sender, *path;
|
|
|
|
g_object_get (rpmostreed_sysroot_get (), "active-transaction", &active_txn, NULL);
|
|
|
|
if (active_txn)
|
|
{
|
|
g_variant_get (active_txn, "(&s&s&s)", &method, &sender, &path);
|
|
if (*method)
|
|
{
|
|
g_autofree char *txn_str = render_txn (method, sender, path);
|
|
sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_TRANSACTION_STARTED),
|
|
"MESSAGE=Initiated txn: %s", txn_str,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
render_systemd_status (self);
|
|
}
|
|
|
|
static gboolean
|
|
rpmostreed_daemon_initable_init (GInitable *initable,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
RpmostreedDaemon *self = RPMOSTREED_DAEMON (initable);
|
|
g_autofree gchar *path = NULL;
|
|
gboolean ret = FALSE;
|
|
|
|
self->object_manager = g_dbus_object_manager_server_new (BASE_DBUS_PATH);
|
|
/* Export the ObjectManager */
|
|
g_dbus_object_manager_server_set_connection (self->object_manager, self->connection);
|
|
g_debug ("exported object manager");
|
|
|
|
path = rpmostreed_generate_object_path (BASE_DBUS_PATH, "Sysroot", NULL);
|
|
self->sysroot = g_object_new (RPMOSTREED_TYPE_SYSROOT,
|
|
"path", self->sysroot_path,
|
|
NULL);
|
|
|
|
if (!rpmostreed_sysroot_populate (rpmostreed_sysroot_get (), cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Error setting up sysroot: ");
|
|
goto out;
|
|
}
|
|
|
|
g_signal_connect (rpmostreed_sysroot_get (), "notify::active-transaction",
|
|
G_CALLBACK (on_active_txn_changed), self);
|
|
|
|
rpmostreed_daemon_publish (self, path, FALSE, self->sysroot);
|
|
g_dbus_connection_start_message_processing (self->connection);
|
|
|
|
g_debug ("daemon constructed");
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
rpmostreed_daemon_class_init (RpmostreedDaemonClass *klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->finalize = daemon_finalize;
|
|
gobject_class->set_property = daemon_set_property;
|
|
gobject_class->get_property = daemon_get_property;
|
|
|
|
/**
|
|
* Daemon:connection:
|
|
*
|
|
* The #GDBusConnection the daemon is for.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CONNECTION,
|
|
g_param_spec_object ("connection",
|
|
"Connection",
|
|
"The D-Bus connection the daemon is for",
|
|
G_TYPE_DBUS_CONNECTION,
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* Daemon:object-manager:
|
|
*
|
|
* The #GDBusObjectManager used by the daemon
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_OBJECT_MANAGER,
|
|
g_param_spec_object ("object-manager",
|
|
"Object Manager",
|
|
"The D-Bus Object Manager server used by the daemon",
|
|
G_TYPE_DBUS_OBJECT_MANAGER_SERVER,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_SYSROOT_PATH,
|
|
g_param_spec_string ("sysroot-path",
|
|
"Sysroot Path",
|
|
"Sysroot location on the filesystem",
|
|
FALSE,
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
rpmostreed_daemon_initable_iface_init (GInitableIface *iface)
|
|
{
|
|
iface->init = rpmostreed_daemon_initable_init;
|
|
}
|
|
|
|
/**
|
|
* rpmostreed_daemon_get:
|
|
*
|
|
* Returns: (transfer none): The singleton #RpmostreedDaemon instance
|
|
*/
|
|
RpmostreedDaemon *
|
|
rpmostreed_daemon_get (void)
|
|
{
|
|
g_assert (_daemon_instance);
|
|
return _daemon_instance;
|
|
}
|
|
|
|
static void
|
|
on_name_owner_changed (GDBusConnection *connection,
|
|
const gchar *sender_name,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *signal_name,
|
|
GVariant *parameters,
|
|
gpointer user_data)
|
|
{
|
|
RpmostreedDaemon *self = user_data;
|
|
const char *name;
|
|
const char *old_owner;
|
|
gboolean has_old_owner;
|
|
const char *new_owner;
|
|
gboolean has_new_owner;
|
|
|
|
if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
|
|
g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
|
|
g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
|
|
return;
|
|
|
|
g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
|
|
|
|
has_old_owner = old_owner && *old_owner;
|
|
has_new_owner = new_owner && *new_owner;
|
|
if (has_old_owner && !has_new_owner)
|
|
rpmostreed_daemon_remove_client (self, name);
|
|
}
|
|
|
|
static void
|
|
render_systemd_status (RpmostreedDaemon *self)
|
|
{
|
|
g_autoptr(GVariant) active_txn = NULL;
|
|
gboolean have_active_txn = FALSE;
|
|
const char *method, *sender, *path;
|
|
|
|
g_object_get (rpmostreed_sysroot_get (), "active-transaction", &active_txn, NULL);
|
|
|
|
if (active_txn)
|
|
{
|
|
g_variant_get (active_txn, "(&s&s&s)", &method, &sender, &path);
|
|
if (*method)
|
|
have_active_txn = TRUE;
|
|
}
|
|
|
|
if (have_active_txn)
|
|
{
|
|
g_autofree char *txn_str = render_txn (method, sender, path);
|
|
sd_notifyf (0, "STATUS=clients=%u; txn=%s", g_hash_table_size (self->bus_clients), txn_str);
|
|
}
|
|
else
|
|
sd_notifyf (0, "STATUS=clients=%u; idle", g_hash_table_size (self->bus_clients));
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_add_client (RpmostreedDaemon *self,
|
|
const char *client)
|
|
{
|
|
if (g_hash_table_lookup (self->bus_clients, client))
|
|
return;
|
|
g_dbus_connection_signal_subscribe (self->connection,
|
|
"org.freedesktop.DBus",
|
|
"org.freedesktop.DBus",
|
|
"NameOwnerChanged",
|
|
"/org/freedesktop/DBus",
|
|
client,
|
|
G_DBUS_SIGNAL_FLAGS_NONE,
|
|
on_name_owner_changed,
|
|
g_object_ref (self),
|
|
g_object_unref);
|
|
|
|
g_hash_table_add (self->bus_clients, g_strdup (client));
|
|
sd_journal_print (LOG_INFO, "Client %s added; new total=%u", client, g_hash_table_size (self->bus_clients));
|
|
render_systemd_status (self);
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_remove_client (RpmostreedDaemon *self,
|
|
const char *client)
|
|
{
|
|
if (!g_hash_table_lookup (self->bus_clients, client))
|
|
return;
|
|
g_hash_table_remove (self->bus_clients, client);
|
|
sd_journal_print (LOG_INFO, "Client %s vanished; remaining=%u", client, g_hash_table_size (self->bus_clients));
|
|
render_systemd_status (self);
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_exit_now (RpmostreedDaemon *self)
|
|
{
|
|
self->running = FALSE;
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_run_until_idle_exit (RpmostreedDaemon *self)
|
|
{
|
|
self->running = TRUE;
|
|
render_systemd_status (self);
|
|
while (self->running)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_publish (RpmostreedDaemon *self,
|
|
const gchar *path,
|
|
gboolean uniquely,
|
|
gpointer thing)
|
|
{
|
|
GDBusInterface *prev = NULL;
|
|
GDBusInterfaceInfo *info = NULL;
|
|
GDBusObjectSkeleton *object = NULL;
|
|
g_autoptr(GDBusObjectSkeleton) owned_object = NULL;
|
|
|
|
g_return_if_fail (RPMOSTREED_IS_DAEMON (self));
|
|
g_return_if_fail (path != NULL);
|
|
|
|
if (G_IS_DBUS_INTERFACE (thing))
|
|
{
|
|
g_debug ("%spublishing iface: %s %s", uniquely ? "uniquely " : "", path,
|
|
g_dbus_interface_get_info (thing)->name);
|
|
|
|
object = G_DBUS_OBJECT_SKELETON (g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), path));
|
|
if (object != NULL)
|
|
{
|
|
if (uniquely)
|
|
{
|
|
info = g_dbus_interface_get_info (thing);
|
|
prev = g_dbus_object_get_interface (G_DBUS_OBJECT (object), info->name);
|
|
if (prev)
|
|
{
|
|
g_object_unref (prev);
|
|
g_object_unref (object);
|
|
object = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (object == NULL)
|
|
object = owned_object = g_dbus_object_skeleton_new (path);
|
|
|
|
g_dbus_object_skeleton_add_interface (object, thing);
|
|
}
|
|
else
|
|
{
|
|
g_critical ("Unsupported type to publish: %s", G_OBJECT_TYPE_NAME (thing));
|
|
return;
|
|
}
|
|
|
|
if (uniquely)
|
|
g_dbus_object_manager_server_export_uniquely (self->object_manager, object);
|
|
else
|
|
g_dbus_object_manager_server_export (self->object_manager, object);
|
|
}
|
|
|
|
void
|
|
rpmostreed_daemon_unpublish (RpmostreedDaemon *self,
|
|
const gchar *path,
|
|
gpointer thing)
|
|
{
|
|
GDBusObject *object;
|
|
gboolean unexport = FALSE;
|
|
GList *interfaces, *l;
|
|
|
|
g_return_if_fail (RPMOSTREED_IS_DAEMON (self));
|
|
g_return_if_fail (path != NULL);
|
|
|
|
if (self->object_manager == NULL)
|
|
return;
|
|
|
|
object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), path);
|
|
if (object == NULL)
|
|
return;
|
|
|
|
path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
|
|
if (G_IS_DBUS_INTERFACE (thing))
|
|
{
|
|
g_debug ("unpublishing interface: %s %s", path,
|
|
g_dbus_interface_get_info (thing)->name);
|
|
|
|
unexport = TRUE;
|
|
|
|
interfaces = g_dbus_object_get_interfaces (object);
|
|
for (l = interfaces; l != NULL; l = g_list_next (l))
|
|
{
|
|
if (G_DBUS_INTERFACE (l->data) != G_DBUS_INTERFACE (thing))
|
|
unexport = FALSE;
|
|
}
|
|
g_list_free_full (interfaces, g_object_unref);
|
|
|
|
/*
|
|
* HACK: GDBusObjectManagerServer is broken ... and sends InterfaceRemoved
|
|
* too many times, if you remove all interfaces manually, and then unexport
|
|
* a GDBusObject. So only do it here if we're not unexporting the object.
|
|
*/
|
|
if (!unexport)
|
|
g_dbus_object_skeleton_remove_interface (G_DBUS_OBJECT_SKELETON (object), thing);
|
|
else
|
|
g_debug ("(unpublishing object, too)");
|
|
}
|
|
else if (thing == NULL)
|
|
{
|
|
unexport = TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_critical ("Unsupported type to unpublish: %s", G_OBJECT_TYPE_NAME (thing));
|
|
}
|
|
|
|
if (unexport)
|
|
g_dbus_object_manager_server_unexport (self->object_manager, path);
|
|
|
|
g_object_unref (object);
|
|
}
|