From d01031ef661c3abfb9e331bcbe8b9e786dd5f5a3 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 26 Oct 2012 14:11:28 +0200 Subject: [PATCH] Add ability to run in 'install' mode * For use when no dbus daemon is running, or need to run in a specific file root prefix. * We spawn the daemon directly and connect to it over peer to peer dbus connection on a socketpair() file descriptor. * We don't run service restarts or other service bits when in install mode. * We don't flush caches when in install mode. * No checks for polkit permissions when in install mode. * Don't timeout and quit when in install mode. * Had to copy GDBusObjectManagerClient and GDBusObjectProxy into our project temporarily so we can get a fix for using the object manager over a peer to peer connection. glib bug #686920 https://bugs.freedesktop.org/show_bug.cgi?id=56424 --- service/realm-daemon.c | 180 +++- service/realm-daemon.h | 4 + service/realm-kerberos.c | 12 +- service/realm-service.c | 51 +- tools/Makefile.am | 5 + tools/eggdbusobjectmanagerclient.c | 1740 ++++++++++++++++++++++++++++++++++++ tools/eggdbusobjectmanagerclient.h | 148 +++ tools/eggdbusobjectproxy.c | 365 ++++++++ tools/eggdbusobjectproxy.h | 92 ++ tools/realm-client.c | 172 +++- tools/realm-client.h | 3 +- tools/realm.c | 21 +- 12 files changed, 2708 insertions(+), 85 deletions(-) create mode 100644 tools/eggdbusobjectmanagerclient.c create mode 100644 tools/eggdbusobjectmanagerclient.h create mode 100644 tools/eggdbusobjectproxy.c create mode 100644 tools/eggdbusobjectproxy.h diff --git a/service/realm-daemon.c b/service/realm-daemon.c index d52a387..6548dda 100644 --- a/service/realm-daemon.c +++ b/service/realm-daemon.c @@ -31,6 +31,7 @@ #include #include +#include #define TIMEOUT 60 /* seconds */ #define HOLD_INTERNAL (GUINT_TO_POINTER (~0)) @@ -46,6 +47,8 @@ static guint service_bus_name_owner_id = 0; static gboolean service_bus_name_claimed = FALSE; static GDBusObjectManagerServer *object_server = NULL; static gboolean service_debug = FALSE; +static gchar *service_install = NULL; +static gint service_dbus_fd = -1; typedef struct { guint watch; @@ -105,6 +108,18 @@ realm_daemon_unlock_for_action (GDBusMethodInvocation *invocation) } gboolean +realm_daemon_is_dbus_peer (void) +{ + return service_dbus_fd != -1; +} + +gboolean +realm_daemon_is_install_mode (void) +{ + return service_install != NULL; +} + +gboolean realm_daemon_has_debug_flag (void) { return service_debug; @@ -118,7 +133,7 @@ realm_daemon_set_locale_until_loop (GDBusMethodInvocation *invocation) gboolean realm_daemon_check_dbus_action (const gchar *sender, - const gchar *action_id) + const gchar *action_id) { PolkitAuthorizationResult *result; PolkitAuthority *authority; @@ -126,6 +141,10 @@ realm_daemon_check_dbus_action (const gchar *sender, GError *error = NULL; gboolean ret; + /* If we're a dbus peer, just allow all calls */ + if (realm_daemon_is_dbus_peer ()) + return TRUE; + g_return_val_if_fail (sender != NULL, FALSE); g_return_val_if_fail (action_id != NULL, FALSE); @@ -367,7 +386,7 @@ on_service_set_locale (RealmDbusService *object, static void on_name_acquired (GDBusConnection *connection, const gchar *name, - gpointer user_data) + gpointer unused) { service_bus_name_claimed = TRUE; g_debug ("claimed name on bus: %s", name); @@ -377,7 +396,7 @@ on_name_acquired (GDBusConnection *connection, static void on_name_lost (GDBusConnection *connection, const gchar *name, - gpointer user_data) + gpointer unused) { if (!service_bus_name_claimed) g_message ("couldn't claim service name on DBus bus: %s", name); @@ -394,6 +413,43 @@ realm_daemon_export_object (GDBusObjectSkeleton *object) } static void +initialize_service (GDBusConnection *connection) +{ + RealmProvider *all_provider; + RealmProvider *provider; + + realm_diagnostics_initialize (connection); + + object_server = g_dbus_object_manager_server_new (REALM_DBUS_SERVICE_PATH); + + all_provider = realm_all_provider_new_and_export (connection); + + provider = realm_sssd_provider_new (); + g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); + realm_all_provider_register (all_provider, provider); + g_object_unref (provider); + + provider = realm_samba_provider_new (); + g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); + realm_all_provider_register (all_provider, provider); + g_object_unref (provider); + + provider = realm_kerberos_provider_new (); + g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); + realm_all_provider_register (all_provider, provider); + g_object_unref (provider); + + g_dbus_object_manager_server_set_connection (object_server, connection); + + /* Use this to control the life time of the providers */ + g_object_set_data_full (G_OBJECT (object_server), "the-provider", + all_provider, g_object_unref); + + /* Matches the hold() in main() */ + realm_daemon_release ("main"); +} + +static void on_bus_get_connection (GObject *source, GAsyncResult *result, gpointer unused) @@ -401,8 +457,6 @@ on_bus_get_connection (GObject *source, GError *error = NULL; GDBusConnection *connection; const gchar *self_name; - RealmProvider *all_provider; - RealmProvider *provider; guint owner_id; connection = g_bus_get_finish (result, &error); @@ -419,40 +473,73 @@ on_bus_get_connection (GObject *source, g_dbus_connection_add_filter (connection, on_connection_filter, (gchar *)self_name, NULL); - realm_diagnostics_initialize (connection); - - object_server = g_dbus_object_manager_server_new (REALM_DBUS_SERVICE_PATH); - - all_provider = realm_all_provider_new_and_export (connection); - - provider = realm_sssd_provider_new (); - g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); - realm_all_provider_register (all_provider, provider); - g_object_unref (provider); - - provider = realm_samba_provider_new (); - g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); - realm_all_provider_register (all_provider, provider); - g_object_unref (provider); - - provider = realm_kerberos_provider_new (); - g_dbus_object_manager_server_export (object_server, G_DBUS_OBJECT_SKELETON (provider)); - realm_all_provider_register (all_provider, provider); - g_object_unref (provider); - - g_dbus_object_manager_server_set_connection (object_server, connection); + initialize_service (connection); owner_id = g_bus_own_name_on_connection (connection, REALM_DBUS_BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, on_name_acquired, on_name_lost, - all_provider, g_object_unref); + NULL, NULL); + service_bus_name_owner_id = owner_id; g_object_unref (connection); } +} - /* Matches the hold() in main() */ - realm_daemon_release ("main"); +static void +on_peer_connection_new (GObject *source, + GAsyncResult *result, + gpointer unused) +{ + GDBusConnection *connection; + GError *error = NULL; + + connection = g_dbus_connection_new_finish (result, &error); + if (error != NULL) { + g_warning ("Couldn't connect to peer: %s", error->message); + g_main_loop_quit (main_loop); + g_error_free (error); + + } else { + g_debug ("connected to peer"); + initialize_service (connection); + g_object_unref (connection); + } +} + +static gboolean +connect_to_bus_or_peer (void) +{ + GSocketConnection *stream; + GSocket *socket; + GError *error = NULL; + gchar *guid; + + if (service_dbus_fd == -1) { + g_bus_get (G_BUS_TYPE_SYSTEM, NULL, on_bus_get_connection, NULL); + return TRUE; + } + + socket = g_socket_new_from_fd (service_dbus_fd, &error); + if (error != NULL) { + g_warning ("Couldn't create socket: %s", error->message); + g_error_free (error); + return FALSE; + } + + stream = g_socket_connection_factory_create_connection (socket); + g_return_val_if_fail (stream != NULL, FALSE); + g_object_unref (socket); + + guid = g_dbus_generate_guid (); + g_dbus_connection_new (G_IO_STREAM (stream), guid, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, + NULL, NULL, on_peer_connection_new, NULL); + + g_free (guid); + g_object_unref (stream); + return TRUE; } static void @@ -495,6 +582,10 @@ main (int argc, GOptionEntry option_entries[] = { { "debug", 'd', 0, G_OPTION_ARG_NONE, &service_debug, "Turn on debug output, prevent timeout exit", NULL }, + { "install", 0, 0, G_OPTION_ARG_STRING, &service_install, + "Turn on installer mode, install to this prefix", NULL }, + { "dbus-peer", 0, 0, G_OPTION_ARG_INT, &service_dbus_fd, + "Use a peer to peer dbus connection on this fd", NULL }, { NULL } }; @@ -518,15 +609,33 @@ main (int argc, context = g_option_context_new ("realmd"); g_option_context_add_main_entries (context, option_entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_printerr ("%s", error->message); + g_message ("%s", error->message); g_option_context_free (context); g_error_free (error); return 2; } + g_option_context_free (context); + + /* Load the platform specific data */ + realm_settings_init (); + + if (service_install) { + if (chdir (service_install) < 0) { + g_message ("Couldn't use install prefix: %s: %s", + service_install, g_strerror (errno)); + return 1; + } + if (chroot (service_install) < 0) { + g_message ("Couldn't chroot into install prefix: %s: %s", + service_install, g_strerror (errno)); + return 1; + } + } + if (g_getenv ("REALM_DEBUG")) service_debug = TRUE; - if (g_getenv ("REALM_PERSIST") || service_debug) + if (g_getenv ("REALM_PERSIST") || service_debug || service_install) service_persist = TRUE; if (service_debug) { @@ -539,16 +648,13 @@ main (int argc, g_free, realm_client_unwatch_and_free); realm_daemon_hold ("main"); - /* Load the platform specific data */ - realm_settings_init (); - service = realm_dbus_service_skeleton_new (); g_signal_connect (service, "handle-release", G_CALLBACK (on_service_release), NULL); g_signal_connect (service, "handle-set-locale", G_CALLBACK (on_service_set_locale), NULL); g_signal_connect (service, "handle-cancel", G_CALLBACK (on_service_cancel), NULL); g_debug ("starting service"); - g_bus_get (G_BUS_TYPE_SYSTEM, NULL, on_bus_get_connection, &object_server); + connect_to_bus_or_peer (); main_loop = g_main_loop_new (NULL, FALSE); @@ -568,9 +674,9 @@ main (int argc, g_debug ("stopping service"); realm_settings_uninit (); g_main_loop_unref (main_loop); - g_option_context_free (context); g_object_unref (service); g_hash_table_unref (service_clients); + g_free (service_install); return 0; } diff --git a/service/realm-daemon.h b/service/realm-daemon.h index b0d63da..a3cbfce 100644 --- a/service/realm-daemon.h +++ b/service/realm-daemon.h @@ -35,6 +35,10 @@ void realm_daemon_set_locale (const gchar *sende const gchar *locale, const gchar *operation_id); +gboolean realm_daemon_is_dbus_peer (void); + +gboolean realm_daemon_is_install_mode (void); + gboolean realm_daemon_has_debug_flag (void); void realm_daemon_poke (void); diff --git a/service/realm-kerberos.c b/service/realm-kerberos.c index f7ed63a..43e9de5 100644 --- a/service/realm-kerberos.c +++ b/service/realm-kerberos.c @@ -152,14 +152,18 @@ on_enroll_complete (GObject *source, (iface->enroll_finish) (REALM_KERBEROS_MEMBERSHIP (closure->self), result, &error); - if (error == NULL) { + if (error != NULL) { + enroll_method_reply (closure->invocation, error); + method_closure_free (closure); + g_clear_error (&error); + + /* Only flush the name caches if not in install mode */ + } else if (!realm_daemon_is_install_mode ()) { realm_command_run_known_async ("name-caches-flush", NULL, closure->invocation, NULL, on_name_caches_flush, closure); } else { - enroll_method_reply (closure->invocation, error); - method_closure_free (closure); - g_clear_error (&error); + enroll_method_reply (closure->invocation, NULL); } } diff --git a/service/realm-service.c b/service/realm-service.c index c076b59..18539ad 100644 --- a/service/realm-service.c +++ b/service/realm-service.c @@ -15,11 +15,46 @@ #include "config.h" #include "realm-command.h" +#include "realm-daemon.h" #include "realm-service.h" #include "realm-settings.h" #include +static void +begin_service_command (const gchar *command, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + + /* If install mode, never do any service stuff */ + if (realm_daemon_is_install_mode ()) { + g_debug ("skipping %s command in install mode", command); + async = g_simple_async_result_new (NULL, callback, user_data, + begin_service_command); + g_simple_async_result_complete_in_idle (async); + g_object_unref (async); + return; + } + + realm_command_run_known_async (command, NULL, invocation, NULL, callback, user_data); +} + +static gboolean +finish_service_command (GAsyncResult *result, + GError **error) +{ + if (g_simple_async_result_is_valid (result, NULL, begin_service_command)) { + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) + return FALSE; + return TRUE; + } + + return realm_command_run_finish (result, NULL, error) != -1; +} + void realm_service_enable (const gchar *service_name, GDBusMethodInvocation *invocation, @@ -29,7 +64,7 @@ realm_service_enable (const gchar *service_name, gchar *command; command = g_strdup_printf ("%s-enable-service", service_name); - realm_command_run_known_async (command, NULL, invocation, NULL, callback, user_data); + begin_service_command (command, invocation, callback, user_data); g_free (command); } @@ -37,7 +72,7 @@ gboolean realm_service_enable_finish (GAsyncResult *result, GError **error) { - return realm_command_run_finish (result, NULL, error) != -1; + return finish_service_command (result, error); } void @@ -49,7 +84,7 @@ realm_service_disable (const gchar *service_name, gchar *command; command = g_strdup_printf ("%s-disable-service", service_name); - realm_command_run_known_async (command, NULL, invocation, NULL, callback, user_data); + begin_service_command (command, invocation, callback, user_data); g_free (command); } @@ -57,7 +92,7 @@ gboolean realm_service_disable_finish (GAsyncResult *result, GError **error) { - return realm_command_run_finish (result, NULL, error) != -1; + return finish_service_command (result, error); } void @@ -69,7 +104,7 @@ realm_service_restart (const gchar *service_name, gchar *command; command = g_strdup_printf ("%s-restart-service", service_name); - realm_command_run_known_async (command, NULL, invocation, NULL, callback, user_data); + begin_service_command (command, invocation, callback, user_data); g_free (command); } @@ -77,7 +112,7 @@ gboolean realm_service_restart_finish (GAsyncResult *result, GError **error) { - return realm_command_run_finish (result, NULL, error) != -1; + return finish_service_command (result, error); } void @@ -89,7 +124,7 @@ realm_service_stop (const gchar *service_name, gchar *command; command = g_strdup_printf ("%s-stop-service", service_name); - realm_command_run_known_async (command, NULL, invocation, NULL, callback, user_data); + begin_service_command (command, invocation, callback, user_data); g_free (command); } @@ -97,7 +132,7 @@ gboolean realm_service_stop_finish (GAsyncResult *result, GError **error) { - return realm_command_run_finish (result, NULL, error) != -1; + return finish_service_command (result, error); } typedef struct { diff --git a/tools/Makefile.am b/tools/Makefile.am index b9bd9aa..4335d98 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -11,12 +11,17 @@ realm_SOURCES = \ realm-join.c \ realm-leave.c \ realm-logins.c \ + eggdbusobjectmanagerclient.c \ + eggdbusobjectmanagerclient.h \ + eggdbusobjectproxy.c \ + eggdbusobjectproxy.h \ $(NULL) AM_CFLAGS = \ -I$(top_srcdir)/dbus \ -I$(top_builddir)/dbus \ -DLOCALEDIR=\""$(datadir)/locale"\" \ + -DREALMD_EXECUTABLE=\""$(privatedir)/realmd"\" \ $(GLIB_CFLAGS) \ $(KRB5_CFLAGS) \ $(NULL) diff --git a/tools/eggdbusobjectmanagerclient.c b/tools/eggdbusobjectmanagerclient.c new file mode 100644 index 0000000..f8c5ac8 --- /dev/null +++ b/tools/eggdbusobjectmanagerclient.c @@ -0,0 +1,1740 @@ +/* + * This file is copied from glib in order to fix: + * https://bugzilla.gnome.org/show_bug.cgi?id=686920 + */ + +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * This library 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 License, 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. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "eggdbusobjectproxy.h" +#include "eggdbusobjectmanagerclient.h" + +#if GLIB_CHECK_VERSION(2,36,0) +#warning Switch back to glib implementation +#endif + +/** + * SECTION:gdbusobjectmanagerclient + * @short_description: Client-side object manager + * @include: gio/gio.h + * + * #EggDBusObjectManagerClient is used to create, monitor and delete object + * proxies for remote objects exported by a #GDBusObjectManagerServer (or any + * code implementing the org.freedesktop.DBus.ObjectManager + * interface). + * + * Once an instance of this type has been created, you can connect to + * the #GDBusObjectManager::object-added and + * #GDBusObjectManager::object-removed signals and inspect the + * #GDBusObjectProxy objects returned by + * g_dbus_object_manager_get_objects(). + * + * If the name for a #EggDBusObjectManagerClient is not owned by anyone at + * object construction time, the default behavior is to request the + * message bus to launch an owner for the name. This behavior can be + * disabled using the %EGG_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START + * flag. It's also worth noting that this only works if the name of + * interest is activatable in the first place. E.g. in some cases it + * is not possible to launch an owner for the requested name. In this + * case, #EggDBusObjectManagerClient object construction still succeeds but + * there will be no object proxies + * (e.g. g_dbus_object_manager_get_objects() returns the empty list) and + * the #EggDBusObjectManagerClient:name-owner property is %NULL. + * + * The owner of the requested name can come and go (for example + * consider a system service being restarted) – #EggDBusObjectManagerClient + * handles this case too; simply connect to the #GObject::notify + * signal to watch for changes on the #EggDBusObjectManagerClient:name-owner + * property. When the name owner vanishes, the behavior is that + * #EggDBusObjectManagerClient:name-owner is set to %NULL (this includes + * emission of the #GObject::notify signal) and then + * #GDBusObjectManager::object-removed signals are synthesized + * for all currently existing object proxies. Since + * #EggDBusObjectManagerClient:name-owner is %NULL when this happens, you can + * use this information to disambiguate a synthesized signal from a + * genuine signal caused by object removal on the remote + * #GDBusObjectManager. Similarly, when a new name owner appears, + * #GDBusObjectManager::object-added signals are synthesized + * while #EggDBusObjectManagerClient:name-owner is still %NULL. Only when all + * object proxies have been added, the #EggDBusObjectManagerClient:name-owner + * is set to the new name owner (this includes emission of the + * #GObject::notify signal). Furthermore, you are guaranteed that + * #EggDBusObjectManagerClient:name-owner will alternate between a name owner + * (e.g. :1.42) and %NULL even in the case where + * the name of interest is atomically replaced + * + * Ultimately, #EggDBusObjectManagerClient is used to obtain #GDBusProxy + * instances. All signals (including the + * org.freedesktop.DBus.Properties::PropertiesChanged + * signal) delivered to #GDBusProxy instances are guaranteed to + * originate from the name owner. This guarantee along with the + * behavior described above, means that certain race conditions + * including the half the proxy is from the old owner + * and the other half is from the new owner problem + * cannot happen. + * + * To avoid having the application connect to signals on the returned + * #GDBusObjectProxy and #GDBusProxy objects, the + * #GDBusObject::interface-added, + * #GDBusObject::interface-removed, + * #GDBusProxy::g-properties-changed and + * #GDBusProxy::g-signal signals + * are also emitted on the #EggDBusObjectManagerClient instance managing these + * objects. The signals emitted are + * #GDBusObjectManager::interface-added, + * #GDBusObjectManager::interface-removed, + * #EggDBusObjectManagerClient::interface-proxy-properties-changed and + * #EggDBusObjectManagerClient::interface-proxy-signal. + * + * Note that all callbacks and signals are emitted in the + * thread-default main loop + * that the #EggDBusObjectManagerClient object was constructed + * in. Additionally, the #GDBusObjectProxy and #GDBusProxy objects + * originating from the #EggDBusObjectManagerClient object will be created in + * the same context and, consequently, will deliver signals in the + * same main loop. + */ + +struct _EggDBusObjectManagerClientPrivate +{ + GMutex lock; + + GBusType bus_type; + GDBusConnection *connection; + gchar *object_path; + gchar *name; + gchar *name_owner; + GDBusObjectManagerClientFlags flags; + + GDBusProxy *control_proxy; + + GHashTable *map_object_path_to_object_proxy; + + guint signal_subscription_id; + gchar *match_rule; + + EggDBusProxyTypeFunc get_proxy_type_func; + gpointer get_proxy_type_user_data; + GDestroyNotify get_proxy_type_destroy_notify; +}; + +enum +{ + PROP_0, + PROP_BUS_TYPE, + PROP_CONNECTION, + PROP_FLAGS, + PROP_OBJECT_PATH, + PROP_NAME, + PROP_NAME_OWNER, + PROP_GET_PROXY_TYPE_FUNC, + PROP_GET_PROXY_TYPE_USER_DATA, + PROP_GET_PROXY_TYPE_DESTROY_NOTIFY +}; + +enum +{ + INTERFACE_PROXY_SIGNAL_SIGNAL, + INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void initable_iface_init (GInitableIface *initable_iface); +static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); +static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (EggDBusObjectManagerClient, egg_dbus_object_manager_client, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init)); + +static void maybe_unsubscribe_signals (EggDBusObjectManagerClient *manager); + +static void on_control_proxy_g_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +static void process_get_all_result (EggDBusObjectManagerClient *manager, + GVariant *value, + const gchar *name_owner); + +static void +egg_dbus_object_manager_client_finalize (GObject *object) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (object); + + maybe_unsubscribe_signals (manager); + + g_hash_table_unref (manager->priv->map_object_path_to_object_proxy); + + if (manager->priv->control_proxy != NULL) + { + g_signal_handlers_disconnect_by_func (manager->priv->control_proxy, + on_control_proxy_g_signal, + manager); + g_object_unref (manager->priv->control_proxy); + } + g_object_unref (manager->priv->connection); + g_free (manager->priv->object_path); + g_free (manager->priv->name); + g_free (manager->priv->name_owner); + + if (manager->priv->get_proxy_type_destroy_notify != NULL) + manager->priv->get_proxy_type_destroy_notify (manager->priv->get_proxy_type_user_data); + + g_mutex_clear (&manager->priv->lock); + + if (G_OBJECT_CLASS (egg_dbus_object_manager_client_parent_class)->finalize != NULL) + G_OBJECT_CLASS (egg_dbus_object_manager_client_parent_class)->finalize (object); +} + +static void +egg_dbus_object_manager_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (object); + + switch (prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, egg_dbus_object_manager_client_get_connection (manager)); + break; + + case PROP_OBJECT_PATH: + g_value_set_string (value, g_dbus_object_manager_get_object_path (G_DBUS_OBJECT_MANAGER (manager))); + break; + + case PROP_NAME: + g_value_set_string (value, egg_dbus_object_manager_client_get_name (manager)); + break; + + case PROP_FLAGS: + g_value_set_flags (value, egg_dbus_object_manager_client_get_flags (manager)); + break; + + case PROP_NAME_OWNER: + g_value_take_string (value, egg_dbus_object_manager_client_get_name_owner (manager)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_dbus_object_manager_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (object); + const gchar *name; + + switch (prop_id) + { + case PROP_BUS_TYPE: + manager->priv->bus_type = g_value_get_enum (value); + break; + + case PROP_CONNECTION: + if (g_value_get_object (value) != NULL) + { + g_return_if_fail (manager->priv->connection == NULL); + g_return_if_fail (G_IS_DBUS_CONNECTION (g_value_get_object (value))); + manager->priv->connection = g_value_dup_object (value); + } + break; + + case PROP_OBJECT_PATH: + g_return_if_fail (manager->priv->object_path == NULL); + g_return_if_fail (g_variant_is_object_path (g_value_get_string (value))); + manager->priv->object_path = g_value_dup_string (value); + break; + + case PROP_NAME: + g_return_if_fail (manager->priv->name == NULL); + name = g_value_get_string (value); + g_return_if_fail (name == NULL || g_dbus_is_name (name)); + manager->priv->name = g_strdup (name); + break; + + case PROP_FLAGS: + manager->priv->flags = g_value_get_flags (value); + break; + + case PROP_GET_PROXY_TYPE_FUNC: + manager->priv->get_proxy_type_func = g_value_get_pointer (value); + break; + + case PROP_GET_PROXY_TYPE_USER_DATA: + manager->priv->get_proxy_type_user_data = g_value_get_pointer (value); + break; + + case PROP_GET_PROXY_TYPE_DESTROY_NOTIFY: + manager->priv->get_proxy_type_destroy_notify = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_dbus_object_manager_client_class_init (EggDBusObjectManagerClientClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = egg_dbus_object_manager_client_finalize; + gobject_class->set_property = egg_dbus_object_manager_client_set_property; + gobject_class->get_property = egg_dbus_object_manager_client_get_property; + + /** + * EggDBusObjectManagerClient:connection: + * + * The #GDBusConnection to use. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "Connection", + "The connection to use", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:bus-type: + * + * If this property is not %G_BUS_TYPE_NONE, then + * #EggDBusObjectManagerClient:connection must be %NULL and will be set to the + * #GDBusConnection obtained by calling g_bus_get() with the value + * of this property. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_BUS_TYPE, + g_param_spec_enum ("bus-type", + "Bus Type", + "The bus to connect to, if any", + G_TYPE_BUS_TYPE, + G_BUS_TYPE_NONE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * EggDBusObjectManagerClient:flags: + * + * Flags from the #GDBusObjectManagerClientFlags enumeration. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_FLAGS, + g_param_spec_flags ("flags", + "Flags", + "Flags for the proxy manager", + G_TYPE_DBUS_OBJECT_MANAGER_CLIENT_FLAGS, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * EggDBusObjectManagerClient:object-path: + * + * The object path the manager is for. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "Object Path", + "The object path of the control object", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:name: + * + * The well-known name or unique name that the manager is for. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name that the manager is for", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:name-owner: + * + * The unique name that owns #EggDBusObjectManagerClient:name or %NULL if + * no-one is currently owning the name. Connect to the + * #GObject::notify signal to track changes to this property. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_NAME_OWNER, + g_param_spec_string ("name-owner", + "Name Owner", + "The owner of the name we are watching", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:get-proxy-type-func: + * + * The #GDBusProxyTypeFunc to use when determining what #GType to + * use for interface proxies or %NULL. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_GET_PROXY_TYPE_FUNC, + g_param_spec_pointer ("get-proxy-type-func", + "GDBusProxyTypeFunc Function Pointer", + "The GDBusProxyTypeFunc pointer to use", + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:get-proxy-type-user-data: + * + * The #gpointer user_data to pass to #EggDBusObjectManagerClient:get-proxy-type-func. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_GET_PROXY_TYPE_USER_DATA, + g_param_spec_pointer ("get-proxy-type-user-data", + "GDBusProxyTypeFunc User Data", + "The GDBusProxyTypeFunc user_data", + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient:get-proxy-type-destroy-notify: + * + * A #GDestroyNotify for the #gpointer user_data in #EggDBusObjectManagerClient:get-proxy-type-user-data. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_GET_PROXY_TYPE_DESTROY_NOTIFY, + g_param_spec_pointer ("get-proxy-type-destroy-notify", + "GDBusProxyTypeFunc user data free function", + "The GDBusProxyTypeFunc user data free function", + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectManagerClient::interface-proxy-signal: + * @manager: The #EggDBusObjectManagerClient emitting the signal. + * @object_proxy: The #GDBusObjectProxy on which an interface is emitting a D-Bus signal. + * @interface_proxy: The #GDBusProxy that is emitting a D-Bus signal. + * @sender_name: The sender of the signal or NULL if the connection is not a bus connection. + * @signal_name: The signal name. + * @parameters: A #GVariant tuple with parameters for the signal. + * + * Emitted when a D-Bus signal is received on @interface_proxy. + * + * This signal exists purely as a convenience to avoid having to + * connect signals to all interface proxies managed by @manager. + * + * This signal is emitted in the + * thread-default main loop + * that @manager was constructed in. + * + * Since: 2.30 + */ + signals[INTERFACE_PROXY_SIGNAL_SIGNAL] = + g_signal_new ("interface-proxy-signal", + EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggDBusObjectManagerClientClass, interface_proxy_signal), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 5, + EGG_TYPE_DBUS_OBJECT_PROXY, + G_TYPE_DBUS_PROXY, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_VARIANT); + + /** + * EggDBusObjectManagerClient::interface-proxy-properties-changed: + * @manager: The #EggDBusObjectManagerClient emitting the signal. + * @object_proxy: The #GDBusObjectProxy on which an interface has properties that are changing. + * @interface_proxy: The #GDBusProxy that has properties that are changing. + * @changed_properties: A #GVariant containing the properties that changed. + * @invalidated_properties: A %NULL terminated array of properties that was invalidated. + * + * Emitted when one or more D-Bus properties on proxy changes. The + * local cache has already been updated when this signal fires. Note + * that both @changed_properties and @invalidated_properties are + * guaranteed to never be %NULL (either may be empty though). + * + * This signal exists purely as a convenience to avoid having to + * connect signals to all interface proxies managed by @manager. + * + * This signal is emitted in the + * thread-default main loop + * that @manager was constructed in. + * + * Since: 2.30 + */ + signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL] = + g_signal_new ("interface-proxy-properties-changed", + EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggDBusObjectManagerClientClass, interface_proxy_properties_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 4, + EGG_TYPE_DBUS_OBJECT_PROXY, + G_TYPE_DBUS_PROXY, + G_TYPE_VARIANT, + G_TYPE_STRV); + + g_type_class_add_private (klass, sizeof (EggDBusObjectManagerClientPrivate)); +} + +static void +egg_dbus_object_manager_client_init (EggDBusObjectManagerClient *manager) +{ + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, + EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + EggDBusObjectManagerClientPrivate); + g_mutex_init (&manager->priv->lock); + manager->priv->map_object_path_to_object_proxy = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_object_unref); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * egg_dbus_object_manager_client_new_sync: + * @connection: A #GDBusConnection. + * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. + * @name: (allow-none): The owner of the control object (unique or well-known name), or %NULL when not using a message bus connection. + * @object_path: The object path of the control object. + * @get_proxy_type_func: (allow-none): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. + * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. + * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL + * @error: Return location for error or %NULL. + * + * Creates a new #EggDBusObjectManagerClient object. + * + * This is a synchronous failable constructor - the calling thread is + * blocked until a reply is received. See egg_dbus_object_manager_client_new() + * for the asynchronous version. + * + * Returns: (transfer full) (type EggDBusObjectManagerClient): A + * #EggDBusObjectManagerClient object or %NULL if @error is set. Free + * with g_object_unref(). + * + * Since: 2.30 + */ +GDBusObjectManager * +egg_dbus_object_manager_client_new_sync (GDBusConnection *connection, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GError **error) +{ + GInitable *initable; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || + g_dbus_is_name (name), NULL); + g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + initable = g_initable_new (EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + cancellable, + error, + "connection", connection, + "flags", flags, + "name", name, + "object-path", object_path, + "get-proxy-type-func", get_proxy_type_func, + "get-proxy-type-user-data", get_proxy_type_user_data, + "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, + NULL); + if (initable != NULL) + return G_DBUS_OBJECT_MANAGER (initable); + else + return NULL; +} + +/** + * egg_dbus_object_manager_client_new: + * @connection: A #GDBusConnection. + * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. + * @name: The owner of the control object (unique or well-known name). + * @object_path: The object path of the control object. + * @get_proxy_type_func: (allow-none): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. + * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. + * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: The data to pass to @callback. + * + * Asynchronously creates a new #EggDBusObjectManagerClient object. + * + * This is an asynchronous failable constructor. When the result is + * ready, @callback will be invoked in the + * thread-default main loop + * of the thread you are calling this method from. You can + * then call egg_dbus_object_manager_client_new_finish() to get the result. See + * egg_dbus_object_manager_client_new_sync() for the synchronous version. + * + * Since: 2.30 + */ +void +egg_dbus_object_manager_client_new (GDBusConnection *connection, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + g_return_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || + g_dbus_is_name (name)); + g_return_if_fail (g_variant_is_object_path (object_path)); + + g_async_initable_new_async (EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "connection", connection, + "flags", flags, + "name", name, + "object-path", object_path, + "get-proxy-type-func", get_proxy_type_func, + "get-proxy-type-user-data", get_proxy_type_user_data, + "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, + NULL); +} + +/** + * egg_dbus_object_manager_client_new_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to egg_dbus_object_manager_client_new(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with egg_dbus_object_manager_client_new(). + * + * Returns: (transfer full) (type EggDBusObjectManagerClient): A + * #EggDBusObjectManagerClient object or %NULL if @error is set. Free + * with g_object_unref(). + * + * Since: 2.30 + */ +GDBusObjectManager * +egg_dbus_object_manager_client_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *object; + GObject *source_object; + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + error); + g_object_unref (source_object); + + if (object != NULL) + return G_DBUS_OBJECT_MANAGER (object); + else + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * egg_dbus_object_manager_client_new_for_bus_sync: + * @bus_type: A #GBusType. + * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. + * @name: The owner of the control object (unique or well-known name). + * @object_path: The object path of the control object. + * @get_proxy_type_func: (allow-none): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. + * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. + * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL + * @error: Return location for error or %NULL. + * + * Like egg_dbus_object_manager_client_new_sync() but takes a #GBusType instead + * of a #GDBusConnection. + * + * This is a synchronous failable constructor - the calling thread is + * blocked until a reply is received. See egg_dbus_object_manager_client_new_for_bus() + * for the asynchronous version. + * + * Returns: (transfer full) (type EggDBusObjectManagerClient): A + * #EggDBusObjectManagerClient object or %NULL if @error is set. Free + * with g_object_unref(). + * + * Since: 2.30 + */ +GDBusObjectManager * +egg_dbus_object_manager_client_new_for_bus_sync (GBusType bus_type, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GError **error) +{ + GInitable *initable; + + g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, NULL); + g_return_val_if_fail (g_dbus_is_name (name), NULL); + g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + initable = g_initable_new (EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + cancellable, + error, + "bus-type", bus_type, + "flags", flags, + "name", name, + "object-path", object_path, + "get-proxy-type-func", get_proxy_type_func, + "get-proxy-type-user-data", get_proxy_type_user_data, + "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, + NULL); + if (initable != NULL) + return G_DBUS_OBJECT_MANAGER (initable); + else + return NULL; +} + +/** + * egg_dbus_object_manager_client_new_for_bus: + * @bus_type: A #GBusType. + * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. + * @name: The owner of the control object (unique or well-known name). + * @object_path: The object path of the control object. + * @get_proxy_type_func: (allow-none): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. + * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. + * @get_proxy_type_destroy_notify: (allow-none): Free function for @get_proxy_type_user_data or %NULL. + * @cancellable: (allow-none): A #GCancellable or %NULL + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: The data to pass to @callback. + * + * Like egg_dbus_object_manager_client_new() but takes a #GBusType instead of a + * #GDBusConnection. + * + * This is an asynchronous failable constructor. When the result is + * ready, @callback will be invoked in the + * thread-default main loop + * of the thread you are calling this method from. You can + * then call egg_dbus_object_manager_client_new_for_bus_finish() to get the result. See + * egg_dbus_object_manager_client_new_for_bus_sync() for the synchronous version. + * + * Since: 2.30 + */ +void +egg_dbus_object_manager_client_new_for_bus (GBusType bus_type, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (bus_type != G_BUS_TYPE_NONE); + g_return_if_fail (g_dbus_is_name (name)); + g_return_if_fail (g_variant_is_object_path (object_path)); + + g_async_initable_new_async (EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "bus-type", bus_type, + "flags", flags, + "name", name, + "object-path", object_path, + "get-proxy-type-func", get_proxy_type_func, + "get-proxy-type-user-data", get_proxy_type_user_data, + "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, + NULL); +} + +/** + * egg_dbus_object_manager_client_new_for_bus_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to egg_dbus_object_manager_client_new_for_bus(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with egg_dbus_object_manager_client_new_for_bus(). + * + * Returns: (transfer full) (type EggDBusObjectManagerClient): A + * #EggDBusObjectManagerClient object or %NULL if @error is set. Free + * with g_object_unref(). + * + * Since: 2.30 + */ +GDBusObjectManager * +egg_dbus_object_manager_client_new_for_bus_finish (GAsyncResult *res, + GError **error) +{ + GObject *object; + GObject *source_object; + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + error); + g_object_unref (source_object); + + if (object != NULL) + return G_DBUS_OBJECT_MANAGER (object); + else + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * egg_dbus_object_manager_client_get_connection: + * @manager: A #EggDBusObjectManagerClient + * + * Gets the #GDBusConnection used by @manager. + * + * Returns: (transfer none): A #GDBusConnection object. Do not free, + * the object belongs to @manager. + * + * Since: 2.30 + */ +GDBusConnection * +egg_dbus_object_manager_client_get_connection (EggDBusObjectManagerClient *manager) +{ + GDBusConnection *ret; + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); + g_mutex_lock (&manager->priv->lock); + ret = manager->priv->connection; + g_mutex_unlock (&manager->priv->lock); + return ret; +} + +/** + * egg_dbus_object_manager_client_get_name: + * @manager: A #EggDBusObjectManagerClient + * + * Gets the name that @manager is for, or %NULL if not a message bus + * connection. + * + * Returns: A unique or well-known name. Do not free, the string + * belongs to @manager. + * + * Since: 2.30 + */ +const gchar * +egg_dbus_object_manager_client_get_name (EggDBusObjectManagerClient *manager) +{ + const gchar *ret; + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); + g_mutex_lock (&manager->priv->lock); + ret = manager->priv->name; + g_mutex_unlock (&manager->priv->lock); + return ret; +} + +/** + * egg_dbus_object_manager_client_get_flags: + * @manager: A #EggDBusObjectManagerClient + * + * Gets the flags that @manager was constructed with. + * + * Returns: Zero of more flags from the #GDBusObjectManagerClientFlags + * enumeration. + * + * Since: 2.30 + */ +GDBusObjectManagerClientFlags +egg_dbus_object_manager_client_get_flags (EggDBusObjectManagerClient *manager) +{ + GDBusObjectManagerClientFlags ret; + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE); + g_mutex_lock (&manager->priv->lock); + ret = manager->priv->flags; + g_mutex_unlock (&manager->priv->lock); + return ret; +} + +/** + * egg_dbus_object_manager_client_get_name_owner: + * @manager: A #EggDBusObjectManagerClient. + * + * The unique name that owns the name that @manager is for or %NULL if + * no-one currently owns that name. You can connect to the + * #GObject::notify signal to track changes to the + * #EggDBusObjectManagerClient:name-owner property. + * + * Returns: The name owner or %NULL if no name owner exists. Free with + * g_free(). + * + * Since: 2.30 + */ +gchar * +egg_dbus_object_manager_client_get_name_owner (EggDBusObjectManagerClient *manager) +{ + gchar *ret; + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); + g_mutex_lock (&manager->priv->lock); + ret = g_strdup (manager->priv->name_owner); + g_mutex_unlock (&manager->priv->lock); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* signal handler for all objects we manage - we dispatch signals + * from here to the objects + */ +static void +signal_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (user_data); + EggDBusObjectProxy *object_proxy; + GDBusInterface *interface; + + g_mutex_lock (&manager->priv->lock); + object_proxy = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); + if (object_proxy == NULL) + { + g_mutex_unlock (&manager->priv->lock); + goto out; + } + g_object_ref (object_proxy); + g_mutex_unlock (&manager->priv->lock); + + //g_debug ("yay, signal_cb %s %s: %s\n", signal_name, object_path, g_variant_print (parameters, TRUE)); + + if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0) + { + if (g_strcmp0 (signal_name, "PropertiesChanged") == 0) + { + const gchar *interface_name; + GVariant *changed_properties; + const gchar **invalidated_properties; + + g_variant_get (parameters, + "(&s@a{sv}^a&s)", + &interface_name, + &changed_properties, + &invalidated_properties); + + interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name); + if (interface != NULL) + { + GVariantIter property_iter; + const gchar *property_name; + GVariant *property_value; + guint n; + + /* update caches... */ + g_variant_iter_init (&property_iter, changed_properties); + while (g_variant_iter_next (&property_iter, + "{&sv}", + &property_name, + &property_value)) + { + g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface), + property_name, + property_value); + g_variant_unref (property_value); + } + + for (n = 0; invalidated_properties[n] != NULL; n++) + { + g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface), + invalidated_properties[n], + NULL); + } + /* ... and then synthesize the signal */ + g_signal_emit_by_name (interface, + "g-properties-changed", + changed_properties, + invalidated_properties); + g_signal_emit (manager, + signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL], + 0, + object_proxy, + interface, + changed_properties, + invalidated_properties); + g_object_unref (interface); + } + g_variant_unref (changed_properties); + g_free (invalidated_properties); + } + } + else + { + /* regular signal - just dispatch it */ + interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name); + if (interface != NULL) + { + g_signal_emit_by_name (interface, + "g-signal", + sender_name, + signal_name, + parameters); + g_signal_emit (manager, + signals[INTERFACE_PROXY_SIGNAL_SIGNAL], + 0, + object_proxy, + interface, + sender_name, + signal_name, + parameters); + g_object_unref (interface); + } + } + + out: + g_clear_object (&object_proxy); +} + +static void +subscribe_signals (EggDBusObjectManagerClient *manager, + const gchar *name_owner) +{ + GError *error = NULL; + GVariant *ret; + + g_return_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager)); + g_return_if_fail (manager->priv->signal_subscription_id == 0); + g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); + + if (name_owner) + { + /* the bus daemon may not implement path_prefix so gracefully + * handle this by using a fallback + */ + manager->priv->match_rule = g_strdup_printf ("type='signal',sender='%s',path_namespace='%s'", + name_owner, manager->priv->object_path); + + ret = g_dbus_connection_call_sync (manager->priv->connection, + "org.freedesktop.DBus", + "/org/freedeskop/DBus", + "org.freedesktop.DBus", + "AddMatch", + g_variant_new ("(s)", + manager->priv->match_rule), + NULL, /* reply_type */ + G_DBUS_CALL_FLAGS_NONE, + -1, /* default timeout */ + NULL, /* TODO: Cancellable */ + &error); + + /* yay, bus daemon supports path_namespace */ + if (ret != NULL) + g_variant_unref (ret); + } + + if (error == NULL) + { + /* still need to ask GDBusConnection for the callbacks */ + manager->priv->signal_subscription_id = + g_dbus_connection_signal_subscribe (manager->priv->connection, + name_owner, + NULL, /* interface */ + NULL, /* member */ + NULL, /* path - TODO: really want wilcard support here */ + NULL, /* arg0 */ + G_DBUS_SIGNAL_FLAGS_NONE | + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + signal_cb, + manager, + NULL); /* user_data_free_func */ + + } + else + { + /* TODO: we could report this to the user + g_warning ("Message bus daemon does not support path_namespace: %s (%s %d)", + error->message, + g_quark_to_string (error->domain), + error->code); + */ + + g_error_free (error); + + /* no need to call RemoveMatch when done since it didn't work */ + g_free (manager->priv->match_rule); + manager->priv->match_rule = NULL; + + /* Fallback is to subscribe to *all* signals from the name owner which + * is rather wasteful. It's probably not a big practical problem because + * users typically want all objects that the name owner supplies. + */ + manager->priv->signal_subscription_id = + g_dbus_connection_signal_subscribe (manager->priv->connection, + name_owner, + NULL, /* interface */ + NULL, /* member */ + NULL, /* path - TODO: really want wilcard support here */ + NULL, /* arg0 */ + G_DBUS_SIGNAL_FLAGS_NONE, + signal_cb, + manager, + NULL); /* user_data_free_func */ + } +} + +static void +maybe_unsubscribe_signals (EggDBusObjectManagerClient *manager) +{ + g_return_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager)); + + if (manager->priv->signal_subscription_id > 0) + { + g_dbus_connection_signal_unsubscribe (manager->priv->connection, + manager->priv->signal_subscription_id); + manager->priv->signal_subscription_id = 0; + } + + if (manager->priv->match_rule != NULL) + { + /* Since the AddMatch call succeeded this is guaranteed to not + * fail - therefore, don't bother checking the return value + */ + g_dbus_connection_call (manager->priv->connection, + "org.freedesktop.DBus", + "/org/freedeskop/DBus", + "org.freedesktop.DBus", + "RemoveMatch", + g_variant_new ("(s)", + manager->priv->match_rule), + NULL, /* reply_type */ + G_DBUS_CALL_FLAGS_NONE, + -1, /* default timeout */ + NULL, /* GCancellable */ + NULL, /* GAsyncReadyCallback */ + NULL); /* user data */ + g_free (manager->priv->match_rule); + manager->priv->match_rule = NULL; + } + +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_notify_g_name_owner (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (user_data); + gchar *old_name_owner; + gchar *new_name_owner; + + g_mutex_lock (&manager->priv->lock); + old_name_owner = manager->priv->name_owner; + new_name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); + manager->priv->name_owner = NULL; + + if (g_strcmp0 (old_name_owner, new_name_owner) != 0) + { + GList *l; + GList *proxies; + + /* remote manager changed; nuke all local proxies */ + proxies = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy); + g_list_foreach (proxies, (GFunc) g_object_ref, NULL); + g_hash_table_remove_all (manager->priv->map_object_path_to_object_proxy); + + g_mutex_unlock (&manager->priv->lock); + + /* do the :name-owner notify with a NULL name - this way the user knows + * the ::object-proxy-removed following is because the name owner went + * away + */ + g_object_notify (G_OBJECT (manager), "name-owner"); + + for (l = proxies; l != NULL; l = l->next) + { + EggDBusObjectProxy *object_proxy = EGG_DBUS_OBJECT_PROXY (l->data); + g_signal_emit_by_name (manager, "object-removed", object_proxy); + } + g_list_free_full (proxies, g_object_unref); + + /* nuke local filter */ + maybe_unsubscribe_signals (manager); + } + else + { + g_mutex_unlock (&manager->priv->lock); + } + + if (new_name_owner != NULL) + { + GError *error; + GVariant *value; + + //g_debug ("repopulating for %s", new_name_owner); + + /* TODO: do this async! */ + subscribe_signals (manager, + new_name_owner); + error = NULL; + value = g_dbus_proxy_call_sync (manager->priv->control_proxy, + "GetManagedObjects", + NULL, /* parameters */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (value == NULL) + { + maybe_unsubscribe_signals (manager); + g_warning ("Error calling GetManagedObjects() when name owner %s for name %s came back: %s", + new_name_owner, + manager->priv->name, + error->message); + g_error_free (error); + } + else + { + process_get_all_result (manager, value, new_name_owner); + g_variant_unref (value); + } + + /* do the :name-owner notify *AFTER* emitting ::object-proxy-added signals - this + * way the user knows that the signals were emitted because the name owner came back + */ + g_mutex_lock (&manager->priv->lock); + manager->priv->name_owner = new_name_owner; + g_mutex_unlock (&manager->priv->lock); + g_object_notify (G_OBJECT (manager), "name-owner"); + + } + g_free (old_name_owner); +} + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (initable); + gboolean ret; + GVariant *value; + GDBusProxyFlags proxy_flags; + + ret = FALSE; + + if (manager->priv->bus_type != G_BUS_TYPE_NONE) + { + g_assert (manager->priv->connection == NULL); + manager->priv->connection = g_bus_get_sync (manager->priv->bus_type, cancellable, error); + if (manager->priv->connection == NULL) + goto out; + } + + proxy_flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES; + if (manager->priv->flags & G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START) + proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START; + + manager->priv->control_proxy = g_dbus_proxy_new_sync (manager->priv->connection, + proxy_flags, + NULL, /* GDBusInterfaceInfo* */ + manager->priv->name, + manager->priv->object_path, + "org.freedesktop.DBus.ObjectManager", + cancellable, + error); + if (manager->priv->control_proxy == NULL) + goto out; + + g_signal_connect (G_OBJECT (manager->priv->control_proxy), + "notify::g-name-owner", + G_CALLBACK (on_notify_g_name_owner), + manager); + + manager->priv->name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); + if (manager->priv->name_owner == NULL && manager->priv->name != NULL) + { + /* it's perfectly fine if there's no name owner.. we're just going to + * wait until one is ready + */ + } + else + { + /* yay, we can get the objects */ + g_signal_connect (manager->priv->control_proxy, + "g-signal", + G_CALLBACK (on_control_proxy_g_signal), + manager); + subscribe_signals (manager, + manager->priv->name_owner); + value = g_dbus_proxy_call_sync (manager->priv->control_proxy, + "GetManagedObjects", + NULL, /* parameters */ + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (value == NULL) + { + maybe_unsubscribe_signals (manager); + g_warn_if_fail (g_signal_handlers_disconnect_by_func (manager->priv->control_proxy, + on_control_proxy_g_signal, + manager) == 1); + g_object_unref (manager->priv->control_proxy); + manager->priv->control_proxy = NULL; + goto out; + } + + process_get_all_result (manager, value, manager->priv->name_owner); + g_variant_unref (value); + } + + ret = TRUE; + + out: + return ret; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = initable_init; +} + +static void +async_initable_iface_init (GAsyncInitableIface *async_initable_iface) +{ + /* for now, just use default: run GInitable code in thread */ +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +add_interfaces (EggDBusObjectManagerClient *manager, + const gchar *object_path, + GVariant *ifaces_and_properties, + const gchar *name_owner) +{ + EggDBusObjectProxy *op; + gboolean added; + GVariantIter iter; + const gchar *interface_name; + GVariant *properties; + GList *interface_added_signals, *l; + GDBusProxy *interface_proxy; + + g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); + + g_mutex_lock (&manager->priv->lock); + + interface_added_signals = NULL; + added = FALSE; + + op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); + if (op == NULL) + { + GType object_proxy_type; + if (manager->priv->get_proxy_type_func != NULL) + { + object_proxy_type = manager->priv->get_proxy_type_func (manager, + object_path, + NULL, + manager->priv->get_proxy_type_user_data); + g_warn_if_fail (g_type_is_a (object_proxy_type, EGG_TYPE_DBUS_OBJECT_PROXY)); + } + else + { + object_proxy_type = EGG_TYPE_DBUS_OBJECT_PROXY; + } + op = g_object_new (object_proxy_type, + "g-connection", manager->priv->connection, + "g-object-path", object_path, + NULL); + added = TRUE; + } + g_object_ref (op); + + g_variant_iter_init (&iter, ifaces_and_properties); + while (g_variant_iter_next (&iter, + "{&s@a{sv}}", + &interface_name, + &properties)) + { + GError *error; + GType interface_proxy_type; + + if (manager->priv->get_proxy_type_func != NULL) + { + interface_proxy_type = manager->priv->get_proxy_type_func (manager, + object_path, + interface_name, + manager->priv->get_proxy_type_user_data); + g_warn_if_fail (g_type_is_a (interface_proxy_type, G_TYPE_DBUS_PROXY)); + } + else + { + interface_proxy_type = G_TYPE_DBUS_PROXY; + } + + /* this is fine - there is no blocking IO because we pass DO_NOT_LOAD_PROPERTIES and + * DO_NOT_CONNECT_SIGNALS and use a unique name + */ + error = NULL; + interface_proxy = g_initable_new (interface_proxy_type, + NULL, /* GCancellable */ + &error, + "g-connection", manager->priv->connection, + "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "g-name", name_owner, + "g-object-path", object_path, + "g-interface-name", interface_name, + NULL); + if (interface_proxy == NULL) + { + g_warning ("%s: Error constructing proxy for path %s and interface %s: %s", + G_STRLOC, + object_path, + interface_name, + error->message); + g_error_free (error); + } + else + { + GVariantIter property_iter; + const gchar *property_name; + GVariant *property_value; + + /* associate the interface proxy with the object */ + g_dbus_interface_set_object (G_DBUS_INTERFACE (interface_proxy), + G_DBUS_OBJECT (op)); + + g_variant_iter_init (&property_iter, properties); + while (g_variant_iter_next (&property_iter, + "{&sv}", + &property_name, + &property_value)) + { + g_dbus_proxy_set_cached_property (interface_proxy, + property_name, + property_value); + g_variant_unref (property_value); + } + + egg_dbus_object_proxy_add_interface (op, interface_proxy); + if (!added) + interface_added_signals = g_list_append (interface_added_signals, g_object_ref (interface_proxy)); + g_object_unref (interface_proxy); + } + g_variant_unref (properties); + } + + g_mutex_unlock (&manager->priv->lock); + + /* now that we don't hold the lock any more, emit signals */ + for (l = interface_added_signals; l != NULL; l = l->next) + { + interface_proxy = G_DBUS_PROXY (l->data); + g_signal_emit_by_name (manager, "interface-added", op, interface_proxy); + g_object_unref (interface_proxy); + } + g_list_free (interface_added_signals); + + if (added) + { + g_hash_table_insert (manager->priv->map_object_path_to_object_proxy, + g_strdup (object_path), + op); + g_signal_emit_by_name (manager, "object-added", op); + } + g_object_unref (op); + +} + +static void +remove_interfaces (EggDBusObjectManagerClient *manager, + const gchar *object_path, + const gchar *const *interface_names) +{ + EggDBusObjectProxy *op; + GList *interfaces; + guint n; + guint num_interfaces; + guint num_interfaces_to_remove; + + g_mutex_lock (&manager->priv->lock); + + op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); + if (op == NULL) + { + g_warning ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists", + G_STRLOC, + object_path); + g_mutex_unlock (&manager->priv->lock); + goto out; + } + + interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (op)); + num_interfaces = g_list_length (interfaces); + g_list_free_full (interfaces, g_object_unref); + + num_interfaces_to_remove = g_strv_length ((gchar **) interface_names); + + /* see if we are going to completety remove the object */ + if (num_interfaces_to_remove == num_interfaces) + { + g_object_ref (op); + g_warn_if_fail (g_hash_table_remove (manager->priv->map_object_path_to_object_proxy, object_path)); + g_mutex_unlock (&manager->priv->lock); + g_signal_emit_by_name (manager, "object-removed", op); + g_object_unref (op); + } + else + { + g_object_ref (op); + g_mutex_unlock (&manager->priv->lock); + for (n = 0; interface_names != NULL && interface_names[n] != NULL; n++) + { + GDBusInterface *interface; + interface = g_dbus_object_get_interface (G_DBUS_OBJECT (op), interface_names[n]); + egg_dbus_object_proxy_remove_interface (op, interface_names[n]); + if (interface != NULL) + { + g_signal_emit_by_name (manager, "interface-removed", op, interface); + g_object_unref (interface); + } + } + g_object_unref (op); + } + out: + ; +} + +static void +process_get_all_result (EggDBusObjectManagerClient *manager, + GVariant *value, + const gchar *name_owner) +{ + GVariant *arg0; + const gchar *object_path; + GVariant *ifaces_and_properties; + GVariantIter iter; + + g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); + + arg0 = g_variant_get_child_value (value, 0); + g_variant_iter_init (&iter, arg0); + while (g_variant_iter_next (&iter, + "{&o@a{sa{sv}}}", + &object_path, + &ifaces_and_properties)) + { + add_interfaces (manager, object_path, ifaces_and_properties, name_owner); + g_variant_unref (ifaces_and_properties); + } + g_variant_unref (arg0); +} + +static void +on_control_proxy_g_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (user_data); + const gchar *object_path; + + //g_debug ("yay, g_signal %s: %s\n", signal_name, g_variant_print (parameters, TRUE)); + + if (g_strcmp0 (signal_name, "InterfacesAdded") == 0) + { + GVariant *ifaces_and_properties; + g_variant_get (parameters, + "(&o@a{sa{sv}})", + &object_path, + &ifaces_and_properties); + add_interfaces (manager, object_path, ifaces_and_properties, manager->priv->name_owner); + g_variant_unref (ifaces_and_properties); + } + else if (g_strcmp0 (signal_name, "InterfacesRemoved") == 0) + { + const gchar **ifaces; + g_variant_get (parameters, + "(&o^a&s)", + &object_path, + &ifaces); + remove_interfaces (manager, object_path, ifaces); + g_free (ifaces); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +egg_dbus_object_manager_client_get_object_path (GDBusObjectManager *_manager) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (_manager); + return manager->priv->object_path; +} + +static GDBusObject * +egg_dbus_object_manager_client_get_object (GDBusObjectManager *_manager, + const gchar *object_path) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (_manager); + GDBusObject *ret; + + g_mutex_lock (&manager->priv->lock); + ret = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); + if (ret != NULL) + g_object_ref (ret); + g_mutex_unlock (&manager->priv->lock); + return ret; +} + +static GDBusInterface * +egg_dbus_object_manager_client_get_interface (GDBusObjectManager *_manager, + const gchar *object_path, + const gchar *interface_name) +{ + GDBusInterface *ret; + GDBusObject *object; + + ret = NULL; + + object = g_dbus_object_manager_get_object (_manager, object_path); + if (object == NULL) + goto out; + + ret = g_dbus_object_get_interface (object, interface_name); + g_object_unref (object); + + out: + return ret; +} + +static GList * +egg_dbus_object_manager_client_get_objects (GDBusObjectManager *_manager) +{ + EggDBusObjectManagerClient *manager = EGG_DBUS_OBJECT_MANAGER_CLIENT (_manager); + GList *ret; + + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); + + g_mutex_lock (&manager->priv->lock); + ret = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy); + g_list_foreach (ret, (GFunc) g_object_ref, NULL); + g_mutex_unlock (&manager->priv->lock); + + return ret; +} + + +static void +dbus_object_manager_interface_init (GDBusObjectManagerIface *iface) +{ + iface->get_object_path = egg_dbus_object_manager_client_get_object_path; + iface->get_objects = egg_dbus_object_manager_client_get_objects; + iface->get_object = egg_dbus_object_manager_client_get_object; + iface->get_interface = egg_dbus_object_manager_client_get_interface; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/tools/eggdbusobjectmanagerclient.h b/tools/eggdbusobjectmanagerclient.h new file mode 100644 index 0000000..6b60e66 --- /dev/null +++ b/tools/eggdbusobjectmanagerclient.h @@ -0,0 +1,148 @@ +/* + * This file is copied from glib in order to fix: + * https://bugzilla.gnome.org/show_bug.cgi?id=686920 + */ + +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * This library 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 License, 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. + * + * Author: David Zeuthen + */ + +#ifndef __EGG_DBUS_OBJECT_MANAGER_CLIENT_H__ +#define __EGG_DBUS_OBJECT_MANAGER_CLIENT_H__ + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT (egg_dbus_object_manager_client_get_type ()) +#define EGG_DBUS_OBJECT_MANAGER_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, EggDBusObjectManagerClient)) +#define EGG_DBUS_OBJECT_MANAGER_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, EggDBusObjectManagerClientClass)) +#define EGG_DBUS_OBJECT_MANAGER_CLIENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT, EggDBusObjectManagerClientClass)) +#define EGG_IS_DBUS_OBJECT_MANAGER_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT)) +#define EGG_IS_DBUS_OBJECT_MANAGER_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT)) + +typedef struct _EggDBusObjectManagerClient EggDBusObjectManagerClient; +typedef struct _EggDBusObjectManagerClientClass EggDBusObjectManagerClientClass; +typedef struct _EggDBusObjectManagerClientPrivate EggDBusObjectManagerClientPrivate; + +/** + * EggDBusObjectManagerClient: + * + * The #EggDBusObjectManagerClient structure contains private data and should + * only be accessed using the provided API. + * + * Since: 2.30 + */ +struct _EggDBusObjectManagerClient +{ + /*< private >*/ + GObject parent_instance; + EggDBusObjectManagerClientPrivate *priv; +}; + +/** + * EggDBusObjectManagerClientClass: + * @parent_class: The parent class. + * @interface_proxy_signal: Signal class handler for the #EggDBusObjectManagerClient::interface-proxy-signal signal. + * @interface_proxy_properties_changed: Signal class handler for the #EggDBusObjectManagerClient::interface-proxy-properties-changed signal. + * + * Class structure for #EggDBusObjectManagerClient. + * + * Since: 2.30 + */ +struct _EggDBusObjectManagerClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*interface_proxy_signal) (EggDBusObjectManagerClient *manager, + GDBusObjectProxy *object_proxy, + GDBusProxy *interface_proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters); + + void (*interface_proxy_properties_changed) (EggDBusObjectManagerClient *manager, + GDBusObjectProxy *object_proxy, + GDBusProxy *interface_proxy, + GVariant *changed_properties, + const gchar* const *invalidated_properties); + + /*< private >*/ + gpointer padding[8]; +}; + +typedef GType (*EggDBusProxyTypeFunc) (EggDBusObjectManagerClient *manager, + const gchar *object_path, + const gchar *interface_name, + gpointer user_data); + +GType egg_dbus_object_manager_client_get_type (void) G_GNUC_CONST; +void egg_dbus_object_manager_client_new (GDBusConnection *connection, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusObjectManager *egg_dbus_object_manager_client_new_finish (GAsyncResult *res, + GError **error); +GDBusObjectManager *egg_dbus_object_manager_client_new_sync (GDBusConnection *connection, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GError **error); +void egg_dbus_object_manager_client_new_for_bus (GBusType bus_type, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusObjectManager *egg_dbus_object_manager_client_new_for_bus_finish (GAsyncResult *res, + GError **error); +GDBusObjectManager *egg_dbus_object_manager_client_new_for_bus_sync (GBusType bus_type, + GDBusObjectManagerClientFlags flags, + const gchar *name, + const gchar *object_path, + EggDBusProxyTypeFunc get_proxy_type_func, + gpointer get_proxy_type_user_data, + GDestroyNotify get_proxy_type_destroy_notify, + GCancellable *cancellable, + GError **error); +GDBusConnection *egg_dbus_object_manager_client_get_connection (EggDBusObjectManagerClient *manager); +GDBusObjectManagerClientFlags egg_dbus_object_manager_client_get_flags (EggDBusObjectManagerClient *manager); +const gchar *egg_dbus_object_manager_client_get_name (EggDBusObjectManagerClient *manager); +gchar *egg_dbus_object_manager_client_get_name_owner (EggDBusObjectManagerClient *manager); + +G_END_DECLS + +#endif /* __EGG_DBUS_OBJECT_MANAGER_CLIENT_H */ diff --git a/tools/eggdbusobjectproxy.c b/tools/eggdbusobjectproxy.c new file mode 100644 index 0000000..dbd293f --- /dev/null +++ b/tools/eggdbusobjectproxy.c @@ -0,0 +1,365 @@ +/* + * This file is copied from glib in order to fix: + * https://bugzilla.gnome.org/show_bug.cgi?id=686920 + */ + +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * This library 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 License, 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. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "eggdbusobjectproxy.h" + +#if GLIB_CHECK_VERSION(2,36,0) +#warning Switch back to glib implementation +#endif + +/** + * SECTION:gdbusobjectproxy + * @short_description: Client-side D-Bus object + * @include: gio/gio.h + * + * A #EggDBusObjectProxy is an object used to represent a remote object + * with one or more D-Bus interfaces. Normally, you don't instantiate + * a #EggDBusObjectProxy yourself - typically #GDBusObjectManagerClient + * is used to obtain it. + * + * Since: 2.30 + */ + +struct _EggDBusObjectProxyPrivate +{ + GMutex lock; + GHashTable *map_name_to_iface; + gchar *object_path; + GDBusConnection *connection; +}; + +enum +{ + PROP_0, + PROP_G_OBJECT_PATH, + PROP_G_CONNECTION +}; + +static void dbus_object_interface_init (GDBusObjectIface *iface); + +G_DEFINE_TYPE_WITH_CODE (EggDBusObjectProxy, egg_dbus_object_proxy, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT, dbus_object_interface_init)); + +static void +egg_dbus_object_proxy_finalize (GObject *object) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + + g_hash_table_unref (proxy->priv->map_name_to_iface); + + g_clear_object (&proxy->priv->connection); + + g_free (proxy->priv->object_path); + + g_mutex_clear (&proxy->priv->lock); + + if (G_OBJECT_CLASS (egg_dbus_object_proxy_parent_class)->finalize != NULL) + G_OBJECT_CLASS (egg_dbus_object_proxy_parent_class)->finalize (object); +} + +static void +egg_dbus_object_proxy_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + + switch (prop_id) + { + case PROP_G_OBJECT_PATH: + g_mutex_lock (&proxy->priv->lock); + g_value_set_string (value, proxy->priv->object_path); + g_mutex_unlock (&proxy->priv->lock); + break; + + case PROP_G_CONNECTION: + g_value_set_object (value, egg_dbus_object_proxy_get_connection (proxy)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_dbus_object_proxy_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + + switch (prop_id) + { + case PROP_G_OBJECT_PATH: + g_mutex_lock (&proxy->priv->lock); + proxy->priv->object_path = g_value_dup_string (value); + g_mutex_unlock (&proxy->priv->lock); + break; + + case PROP_G_CONNECTION: + g_mutex_lock (&proxy->priv->lock); + proxy->priv->connection = g_value_dup_object (value); + g_mutex_unlock (&proxy->priv->lock); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_dbus_object_proxy_class_init (EggDBusObjectProxyClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = egg_dbus_object_proxy_finalize; + gobject_class->set_property = egg_dbus_object_proxy_set_property; + gobject_class->get_property = egg_dbus_object_proxy_get_property; + + /** + * EggDBusObjectProxy:g-object-path: + * + * The object path of the proxy. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_G_OBJECT_PATH, + g_param_spec_string ("g-object-path", + "Object Path", + "The object path of the proxy", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EggDBusObjectProxy:g-connection: + * + * The connection of the proxy. + * + * Since: 2.30 + */ + g_object_class_install_property (gobject_class, + PROP_G_CONNECTION, + g_param_spec_object ("g-connection", + "Connection", + "The connection of the proxy", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (klass, sizeof (EggDBusObjectProxyPrivate)); +} + +static void +egg_dbus_object_proxy_init (EggDBusObjectProxy *proxy) +{ + proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, + EGG_TYPE_DBUS_OBJECT_PROXY, + EggDBusObjectProxyPrivate); + g_mutex_init (&proxy->priv->lock); + proxy->priv->map_name_to_iface = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_object_unref); +} + +static const gchar * +egg_dbus_object_proxy_get_object_path (GDBusObject *object) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + const gchar *ret; + g_mutex_lock (&proxy->priv->lock); + ret = proxy->priv->object_path; + g_mutex_unlock (&proxy->priv->lock); + return ret; +} + +/** + * egg_dbus_object_proxy_get_connection: + * @proxy: a #EggDBusObjectProxy + * + * Gets the connection that @proxy is for. + * + * Returns: (transfer none): A #GDBusConnection. Do not free, the + * object is owned by @proxy. + * + * Since: 2.30 + */ +GDBusConnection * +egg_dbus_object_proxy_get_connection (EggDBusObjectProxy *proxy) +{ + GDBusConnection *ret; + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_PROXY (proxy), NULL); + g_mutex_lock (&proxy->priv->lock); + ret = proxy->priv->connection; + g_mutex_unlock (&proxy->priv->lock); + return ret; +} + +static GDBusInterface * +egg_dbus_object_proxy_get_interface (GDBusObject *object, + const gchar *interface_name) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + GDBusProxy *ret; + + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_PROXY (proxy), NULL); + g_return_val_if_fail (g_dbus_is_interface_name (interface_name), NULL); + + g_mutex_lock (&proxy->priv->lock); + ret = g_hash_table_lookup (proxy->priv->map_name_to_iface, interface_name); + if (ret != NULL) + g_object_ref (ret); + g_mutex_unlock (&proxy->priv->lock); + + return (GDBusInterface *) ret; /* TODO: proper cast */ +} + +static GList * +egg_dbus_object_proxy_get_interfaces (GDBusObject *object) +{ + EggDBusObjectProxy *proxy = EGG_DBUS_OBJECT_PROXY (object); + GList *ret; + + g_return_val_if_fail (EGG_IS_DBUS_OBJECT_PROXY (proxy), NULL); + + ret = NULL; + + g_mutex_lock (&proxy->priv->lock); + ret = g_hash_table_get_values (proxy->priv->map_name_to_iface); + g_list_foreach (ret, (GFunc) g_object_ref, NULL); + g_mutex_unlock (&proxy->priv->lock); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * egg_dbus_object_proxy_new: + * @connection: a #GDBusConnection + * @object_path: the object path + * + * Creates a new #EggDBusObjectProxy for the given connection and + * object path. + * + * Returns: a new #EggDBusObjectProxy + * + * Since: 2.30 + */ +EggDBusObjectProxy * +egg_dbus_object_proxy_new (GDBusConnection *connection, + const gchar *object_path) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); + return EGG_DBUS_OBJECT_PROXY (g_object_new (EGG_TYPE_DBUS_OBJECT_PROXY, + "g-object-path", object_path, + "g-connection", connection, + NULL)); +} + +void +egg_dbus_object_proxy_add_interface (EggDBusObjectProxy *proxy, + GDBusProxy *interface_proxy) +{ + const gchar *interface_name; + GDBusProxy *interface_proxy_to_remove; + + g_return_if_fail (EGG_IS_DBUS_OBJECT_PROXY (proxy)); + g_return_if_fail (G_IS_DBUS_PROXY (interface_proxy)); + + g_mutex_lock (&proxy->priv->lock); + + interface_name = g_dbus_proxy_get_interface_name (interface_proxy); + interface_proxy_to_remove = g_hash_table_lookup (proxy->priv->map_name_to_iface, interface_name); + if (interface_proxy_to_remove != NULL) + { + g_object_ref (interface_proxy_to_remove); + g_warn_if_fail (g_hash_table_remove (proxy->priv->map_name_to_iface, interface_name)); + } + g_hash_table_insert (proxy->priv->map_name_to_iface, + g_strdup (interface_name), + g_object_ref (interface_proxy)); + g_object_ref (interface_proxy); + + g_mutex_unlock (&proxy->priv->lock); + + if (interface_proxy_to_remove != NULL) + { + g_signal_emit_by_name (proxy, "interface-removed", interface_proxy_to_remove); + g_object_unref (interface_proxy_to_remove); + } + + g_signal_emit_by_name (proxy, "interface-added", interface_proxy); + g_object_unref (interface_proxy); +} + +void +egg_dbus_object_proxy_remove_interface (EggDBusObjectProxy *proxy, + const gchar *interface_name) +{ + GDBusProxy *interface_proxy; + + g_return_if_fail (EGG_IS_DBUS_OBJECT_PROXY (proxy)); + g_return_if_fail (g_dbus_is_interface_name (interface_name)); + + g_mutex_lock (&proxy->priv->lock); + + interface_proxy = g_hash_table_lookup (proxy->priv->map_name_to_iface, interface_name); + if (interface_proxy != NULL) + { + g_object_ref (interface_proxy); + g_warn_if_fail (g_hash_table_remove (proxy->priv->map_name_to_iface, interface_name)); + g_mutex_unlock (&proxy->priv->lock); + g_signal_emit_by_name (proxy, "interface-removed", interface_proxy); + g_object_unref (interface_proxy); + } + else + { + g_mutex_unlock (&proxy->priv->lock); + } +} + +static void +dbus_object_interface_init (GDBusObjectIface *iface) +{ + iface->get_object_path = egg_dbus_object_proxy_get_object_path; + iface->get_interfaces = egg_dbus_object_proxy_get_interfaces; + iface->get_interface = egg_dbus_object_proxy_get_interface; +} diff --git a/tools/eggdbusobjectproxy.h b/tools/eggdbusobjectproxy.h new file mode 100644 index 0000000..ac6e724 --- /dev/null +++ b/tools/eggdbusobjectproxy.h @@ -0,0 +1,92 @@ +/* + * This file is copied from glib in order to fix: + * https://bugzilla.gnome.org/show_bug.cgi?id=686920 + */ + +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * This library 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 License, 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. + * + * Author: David Zeuthen + */ + +#ifndef __EGG_DBUS_OBJECT_PROXY_H__ +#define __EGG_DBUS_OBJECT_PROXY_H__ + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_DBUS_OBJECT_PROXY (egg_dbus_object_proxy_get_type ()) +#define EGG_DBUS_OBJECT_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EGG_TYPE_DBUS_OBJECT_PROXY, EggDBusObjectProxy)) +#define EGG_DBUS_OBJECT_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EGG_TYPE_DBUS_OBJECT_PROXY, EggDBusObjectProxyClass)) +#define EGG_DBUS_OBJECT_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EGG_TYPE_DBUS_OBJECT_PROXY, EggDBusObjectProxyClass)) +#define EGG_IS_DBUS_OBJECT_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EGG_TYPE_DBUS_OBJECT_PROXY)) +#define EGG_IS_DBUS_OBJECT_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EGG_TYPE_DBUS_OBJECT_PROXY)) + +typedef struct _EggDBusObjectProxy EggDBusObjectProxy; +typedef struct _EggDBusObjectProxyClass EggDBusObjectProxyClass; +typedef struct _EggDBusObjectProxyPrivate EggDBusObjectProxyPrivate; + +/** + * EggDBusObjectProxy: + * + * The #EggDBusObjectProxy structure contains private data and should + * only be accessed using the provided API. + * + * Since: 2.30 + */ +struct _EggDBusObjectProxy +{ + /*< private >*/ + GObject parent_instance; + EggDBusObjectProxyPrivate *priv; +}; + +/** + * EggDBusObjectProxyClass: + * @parent_class: The parent class. + * + * Class structure for #EggDBusObjectProxy. + * + * Since: 2.30 + */ +struct _EggDBusObjectProxyClass +{ + GObjectClass parent_class; + + /*< private >*/ + gpointer padding[8]; +}; + +GType egg_dbus_object_proxy_get_type (void) G_GNUC_CONST; +EggDBusObjectProxy *egg_dbus_object_proxy_new (GDBusConnection *connection, + const gchar *object_path); +GDBusConnection *egg_dbus_object_proxy_get_connection (EggDBusObjectProxy *proxy); + +void +egg_dbus_object_proxy_add_interface (EggDBusObjectProxy *proxy, + GDBusProxy *interface_proxy); + +void +egg_dbus_object_proxy_remove_interface (EggDBusObjectProxy *proxy, + const gchar *interface_name); + +G_END_DECLS + +#endif /* __EGG_DBUS_OBJECT_PROXY_H */ diff --git a/tools/realm-client.c b/tools/realm-client.c index 4f1b075..688f38d 100644 --- a/tools/realm-client.c +++ b/tools/realm-client.c @@ -18,21 +18,27 @@ #include "realm-client.h" #include "realm-dbus-constants.h" +#include "eggdbusobjectproxy.h" +#include "eggdbusobjectmanagerclient.h" + #include +#include + #include #include struct _RealmClient { - GDBusObjectManagerClient parent; + EggDBusObjectManagerClient parent; RealmDbusProvider *provider; + GPid peer_pid; }; struct _RealmClientClass { - GDBusObjectManagerClientClass parent; + EggDBusObjectManagerClientClass parent; }; -G_DEFINE_TYPE (RealmClient, realm_client, G_TYPE_DBUS_OBJECT_MANAGER_CLIENT); +G_DEFINE_TYPE (RealmClient, realm_client, EGG_TYPE_DBUS_OBJECT_MANAGER_CLIENT); typedef struct { GAsyncResult *result; @@ -60,6 +66,11 @@ realm_client_finalize (GObject *obj) { RealmClient *self = REALM_CLIENT (obj); + if (self->peer_pid) { + kill (self->peer_pid, SIGTERM); + g_spawn_close_pid (self->peer_pid); + } + if (self->provider) g_object_unref (self->provider); @@ -75,7 +86,7 @@ realm_client_class_init (RealmClientClass *klass) } static GType -realm_object_client_get_proxy_type (GDBusObjectManagerClient *manager, +realm_object_client_get_proxy_type (EggDBusObjectManagerClient *manager, const gchar *object_path, const gchar *interface_name, gpointer user_data) @@ -85,7 +96,7 @@ realm_object_client_get_proxy_type (GDBusObjectManagerClient *manager, GType ret; if (interface_name == NULL) - return G_TYPE_DBUS_OBJECT_PROXY; + return EGG_TYPE_DBUS_OBJECT_PROXY; if (g_once_init_enter (&once_init_value)) { lookup_hash = g_hash_table_new (g_str_hash, g_str_equal); @@ -99,7 +110,7 @@ realm_object_client_get_proxy_type (GDBusObjectManagerClient *manager, ret = GPOINTER_TO_SIZE (g_hash_table_lookup (lookup_hash, interface_name)); if (ret == 0) - ret = G_TYPE_DBUS_OBJECT_PROXY; + ret = EGG_TYPE_DBUS_OBJECT_PROXY; return ret; } @@ -119,45 +130,43 @@ on_diagnostics_signal (GDBusConnection *connection, g_printerr ("%s", data); } -RealmClient * -realm_client_new (gboolean verbose) +static RealmClient * +realm_client_new_on_connection (GDBusConnection *connection, + gboolean verbose, + const gchar *bus_name) { - GDBusConnection *connection; RealmDbusProvider *provider; GError *error = NULL; - RealmClient *client = NULL; GInitable *ret; - - connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); - if (error == NULL) { - if (verbose) { - g_dbus_connection_signal_subscribe (connection, REALM_DBUS_BUS_NAME, - REALM_DBUS_SERVICE_INTERFACE, - REALM_DBUS_DIAGNOSTICS_SIGNAL, - REALM_DBUS_SERVICE_PATH, - NULL, G_DBUS_SIGNAL_FLAGS_NONE, - on_diagnostics_signal, NULL, NULL); - } - - } else { - realm_handle_error (error, _("Couldn't connect to system bus")); - return NULL; + RealmClient *client; + GDBusSignalFlags flags; + + flags = G_DBUS_SIGNAL_FLAGS_NONE; + if (bus_name == NULL) + flags |= G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE; + + if (verbose) { + g_dbus_connection_signal_subscribe (connection, bus_name, + REALM_DBUS_SERVICE_INTERFACE, + REALM_DBUS_DIAGNOSTICS_SIGNAL, + REALM_DBUS_SERVICE_PATH, + NULL, flags, + on_diagnostics_signal, NULL, NULL); } provider = realm_dbus_provider_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, - REALM_DBUS_BUS_NAME, + bus_name, REALM_DBUS_SERVICE_PATH, NULL, &error); if (error != NULL) { realm_handle_error (error, _("Couldn't connect to realm service")); - g_object_unref (connection); return NULL; } ret = g_initable_new (REALM_TYPE_CLIENT, NULL, &error, "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, - "name", REALM_DBUS_BUS_NAME, + "name", bus_name, "connection", connection, "object-path", REALM_DBUS_SERVICE_PATH, "get-proxy-type-func", realm_object_client_get_proxy_type, @@ -170,16 +179,121 @@ realm_client_new (gboolean verbose) } g_object_unref (provider); - g_object_unref (connection); if (error != NULL) { realm_handle_error (error, _("Couldn't load the realm service")); + g_object_unref (client); return NULL; } + return client; } +static RealmClient * +realm_client_new_system (gboolean verbose) +{ + GDBusConnection *connection; + GError *error = NULL; + RealmClient *client = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (error != NULL) { + realm_handle_error (error, _("Couldn't connect to system bus")); + return NULL; + } + + client = realm_client_new_on_connection (connection, verbose, REALM_DBUS_BUS_NAME); + g_object_unref (connection); + return client; +} + +static RealmClient * +realm_client_new_installer (gboolean verbose, + const gchar *prefix) +{ + GDBusConnection *connection; + GSocketConnection *stream; + RealmClient *client; + GSocket *socket; + GError *error = NULL; + gchar buffer[16]; + GPid pid = 0; + int pair[2]; + + const gchar *args[] = { + REALMD_EXECUTABLE, + "--install", prefix, + "--dbus-peer", buffer, + NULL + }; + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, pair) < 0) { + realm_handle_error (NULL, _("Couldn't create socket pair: %s"), g_strerror (errno)); + return NULL; + } + + g_snprintf (buffer, sizeof (buffer), "%d", pair[1]); + + socket = g_socket_new_from_fd (pair[0], &error); + if (error != NULL) { + realm_handle_error (error, _("Couldn't create socket")); + close(pair[0]); + close(pair[1]); + return NULL; + } + + g_spawn_async (prefix ? prefix : "/", (gchar **)args, NULL, + G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &pid, &error); + + close(pair[1]); + + if (error != NULL) { + realm_handle_error (error, _("Couldn't run realmd")); + close(pair[0]); + return NULL; + } + + stream = g_socket_connection_factory_create_connection (socket); + g_return_val_if_fail (stream != NULL, NULL); + g_object_unref (socket); + + connection = g_dbus_connection_new_sync (G_IO_STREAM (stream), NULL, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, + NULL, NULL, &error); + g_object_unref (stream); + + if (error == NULL) { + client = realm_client_new_on_connection (connection, verbose, NULL); + g_object_unref (connection); + } else { + realm_handle_error (error, _("Couldn't create socket")); + client = NULL; + } + + /* Make sure the process is owned */ + if (client) { + client->peer_pid = pid; + } else { + kill (pid, SIGTERM); + g_spawn_close_pid (pid); + } + + return client; +} + +RealmClient * +realm_client_new (gboolean verbose, + const gchar *prefix) +{ + if (prefix) + return realm_client_new_installer (verbose, prefix); + else + return realm_client_new_system (verbose); +} + RealmDbusProvider * realm_client_get_provider (RealmClient *self) { diff --git a/tools/realm-client.h b/tools/realm-client.h index b704ca5..0658f7f 100644 --- a/tools/realm-client.h +++ b/tools/realm-client.h @@ -33,7 +33,8 @@ typedef struct _RealmClientClass RealmClientClass; GType realm_client_get_type (void) G_GNUC_CONST; -RealmClient * realm_client_new (gboolean verbose); +RealmClient * realm_client_new (gboolean verbose, + const gchar *prefix); RealmDbusProvider * realm_client_get_provider (RealmClient *self); diff --git a/tools/realm.c b/tools/realm.c index f597eba..9557546 100644 --- a/tools/realm.c +++ b/tools/realm.c @@ -283,9 +283,11 @@ usage (int code) return code; } +static gchar *arg_prefix = NULL; gboolean realm_verbose = FALSE; GOptionEntry realm_global_options[] = { + { "install", 'i', 0, G_OPTION_ARG_STRING, &arg_prefix, N_("Install mode to a specific prefix"), NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &realm_verbose, N_("Verbose output"), NULL }, { NULL, } }; @@ -340,18 +342,25 @@ main (int argc, g_option_context_free (context); - + ret = 2; for (i = 0; i < G_N_ELEMENTS (realm_commands); i++) { if (g_str_equal (realm_commands[i].name, command)) { - client = realm_client_new (realm_verbose); - if (!client) - return 1; + client = realm_client_new (realm_verbose, arg_prefix); + if (!client) { + ret = 1; + break; + } ret = (realm_commands[i].function) (client, argc, argv); g_object_unref (client); - return ret; + + g_free (arg_prefix); + break; } } - return usage(2); + if (ret == 2) + usage(2); + return ret; + } -- 1.7.12.1