summaryrefslogtreecommitdiff
path: root/modules/dbus/util/dbus.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/dbus/util/dbus.c')
-rw-r--r--modules/dbus/util/dbus.c3023
1 files changed, 3023 insertions, 0 deletions
diff --git a/modules/dbus/util/dbus.c b/modules/dbus/util/dbus.c
new file mode 100644
index 0000000..89adf27
--- /dev/null
+++ b/modules/dbus/util/dbus.c
@@ -0,0 +1,3023 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus.h"
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "dbus-private.h"
+#include "dbus-proxy.h"
+#include "log.h"
+#include "glib.h"
+
+typedef struct {
+ const BigDBusConnectFuncs *funcs;
+ void *data;
+ unsigned int opened : 1;
+} ConnectFuncs;
+
+typedef enum {
+ NAME_NOT_REQUESTED,
+ NAME_PRIMARY_OWNER,
+ NAME_IN_QUEUE,
+ NAME_NOT_OWNED
+} NameOwnershipState;
+
+typedef struct {
+ char *name;
+ const BigDBusJsonMethod *methods;
+ int n_methods;
+} BigJsonIface;
+
+typedef struct {
+ DBusBusType bus_type;
+ /* If prev_state != state then we may need to notify */
+ NameOwnershipState prev_state;
+ NameOwnershipState state;
+ const BigDBusNameOwnerFuncs *funcs;
+ void *data;
+ unsigned int id;
+} BigNameOwnershipMonitor;
+
+typedef struct {
+ char *name;
+ char *current_owner;
+ GSList *watchers;
+} BigNameWatch;
+
+typedef struct {
+ BigDBusWatchNameFlags flags;
+ const BigDBusWatchNameFuncs *funcs;
+ void *data;
+ DBusBusType bus_type;
+ BigNameWatch *watch;
+ guint notify_idle;
+ int refcount;
+ guint destroyed : 1;
+} BigNameWatcher;
+
+typedef struct {
+ DBusBusType bus_type;
+ char *name;
+ BigNameWatcher *watcher;
+} BigPendingNameWatcher;
+
+static DBusConnection *session_bus_weak_ref = NULL;
+static GSList *session_bus_weak_refs = NULL;
+static DBusConnection *system_bus_weak_ref = NULL;
+static GSList *system_bus_weak_refs = NULL;
+static guint session_connect_idle_id = 0;
+static guint system_connect_idle_id = 0;
+static GSList *all_connect_funcs = NULL;
+
+static GSList *pending_name_ownership_monitors = NULL;
+static GSList *pending_name_watchers = NULL;
+
+#define BIG_DBUS_NAME_OWNER_MONITOR_INVALID_ID 0
+
+static unsigned int global_monitor_id = 0;
+
+static DBusHandlerResult disconnect_filter_message (DBusConnection *connection,
+ DBusMessage *message,
+ void *data);
+static DBusHandlerResult name_ownership_monitor_filter_message (DBusConnection *connection,
+ DBusMessage *message,
+ void *data);
+static void process_name_ownership_monitors (DBusConnection *connection,
+ BigDBusInfo *info);
+static void name_watch_remove_watcher (BigNameWatch *watch,
+ BigNameWatcher *watcher);
+static DBusHandlerResult name_watch_filter_message (DBusConnection *connection,
+ DBusMessage *message,
+ void *data);
+static void process_pending_name_watchers (DBusConnection *connection,
+ BigDBusInfo *info);
+static void json_iface_free (BigJsonIface *iface);
+static void info_free (BigDBusInfo *info);
+static gboolean notify_watcher_name_appeared (gpointer data);
+
+static dbus_int32_t info_slot = -1;
+BigDBusInfo*
+_big_dbus_ensure_info(DBusConnection *connection)
+{
+ BigDBusInfo *info;
+
+ dbus_connection_allocate_data_slot(&info_slot);
+
+ info = dbus_connection_get_data(connection, info_slot);
+
+ if (info == NULL) {
+ info = g_slice_new0(BigDBusInfo);
+
+ info->where_connection_was = connection;
+
+ if (connection == session_bus_weak_ref)
+ info->bus_type = DBUS_BUS_SESSION;
+ else if (connection == system_bus_weak_ref)
+ info->bus_type = DBUS_BUS_SYSTEM;
+ else
+ g_error("Unknown bus type opened in %s", __FILE__);
+
+ info->json_ifaces = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, (GFreeFunc) json_iface_free);
+ info->name_watches = g_hash_table_new(g_str_hash, g_str_equal);
+ dbus_connection_set_data(connection, info_slot, info, (DBusFreeFunction) info_free);
+
+ dbus_connection_add_filter(connection, name_ownership_monitor_filter_message,
+ NULL, NULL);
+ dbus_connection_add_filter(connection, name_watch_filter_message,
+ NULL, NULL);
+ dbus_connection_add_filter(connection, _big_dbus_signal_watch_filter_message,
+ NULL, NULL);
+
+ /* Important: disconnect_filter_message() must be LAST so
+ * it runs last when the disconnect message arrives.
+ */
+ dbus_connection_add_filter(connection, disconnect_filter_message,
+ NULL, NULL);
+
+ /* caution, this could get circular if proxy_new() goes back around
+ * and tries to use dbus.c - but we'll fix it when it happens.
+ * Also, this refs the connection ...
+ */
+ info->driver_proxy =
+ big_dbus_proxy_new(connection,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+ }
+
+ return info;
+}
+
+void
+_big_dbus_dispose_info(DBusConnection *connection)
+{
+ BigDBusInfo *info;
+
+ if (info_slot < 0)
+ return;
+
+ info = dbus_connection_get_data(connection, info_slot);
+
+ if (info != NULL) {
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Disposing info on connection %p",
+ connection);
+
+ /* the driver proxy refs the connection, we want
+ * to break that cycle.
+ */
+ g_object_unref(info->driver_proxy);
+ info->driver_proxy = NULL;
+
+ dbus_connection_set_data(connection, info_slot, NULL, NULL);
+
+ dbus_connection_free_data_slot(&info_slot);
+ }
+}
+
+DBusConnection*
+_big_dbus_get_weak_ref(DBusBusType which_bus)
+{
+ if (which_bus == DBUS_BUS_SESSION) {
+ return session_bus_weak_ref;
+ } else if (which_bus == DBUS_BUS_SYSTEM) {
+ return system_bus_weak_ref;
+ }
+
+ g_assert_not_reached();
+ return NULL;
+}
+
+static DBusHandlerResult
+disconnect_filter_message(DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ /* We should be running after all other filters */
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Disconnected in %s", G_STRFUNC);
+
+ _big_dbus_dispose_info(connection);
+
+ if (session_bus_weak_ref == connection)
+ session_bus_weak_ref = NULL;
+
+ if (system_bus_weak_ref == connection)
+ system_bus_weak_ref = NULL;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusConnection*
+try_connecting(DBusBusType which_bus)
+{
+
+ DBusGConnection *gconnection;
+ DBusConnection *connection;
+ GError *error;
+
+ connection = _big_dbus_get_weak_ref(which_bus);
+ if (connection != NULL)
+ return connection;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "trying to connect to message bus");
+
+ error = NULL;
+ gconnection = dbus_g_bus_get(which_bus,
+ &error);
+ if (gconnection == NULL) {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "bus connection failed: %s",
+ error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ connection = dbus_g_connection_get_connection(gconnection);
+
+ /* Disable this because all our apps will be well-behaved! */
+ dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+ if (which_bus == DBUS_BUS_SESSION &&
+ session_bus_weak_ref == NULL) {
+ GSList *l;
+ session_bus_weak_ref = connection;
+ for (l = session_bus_weak_refs; l != NULL; l = l->next) {
+ DBusConnection **connection_p = l->data;
+ *connection_p = session_bus_weak_ref;
+ }
+ } else if (which_bus == DBUS_BUS_SYSTEM &&
+ system_bus_weak_ref == NULL) {
+ GSList *l;
+ system_bus_weak_ref = connection;
+ for (l = system_bus_weak_refs; l != NULL; l = l->next) {
+ DBusConnection **connection_p = l->data;
+ *connection_p = system_bus_weak_ref;
+ }
+ }
+
+ dbus_g_connection_unref(gconnection); /* rely on libdbus holding a ref */
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Successfully connected");
+
+ return connection;
+}
+
+static gboolean
+connect_idle(void *data)
+{
+ GSList *l;
+ DBusConnection *connection;
+ BigDBusInfo *info;
+ DBusBusType bus_type;
+
+ bus_type = GPOINTER_TO_INT(data);
+
+ if (bus_type == DBUS_BUS_SESSION)
+ session_connect_idle_id = 0;
+ else if (bus_type == DBUS_BUS_SYSTEM)
+ system_connect_idle_id = 0;
+ else
+ g_assert_not_reached();
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "connection idle with %d connect listeners to traverse", g_slist_length(all_connect_funcs));
+
+ connection = try_connecting(bus_type);
+ if (connection == NULL) {
+ if (bus_type == DBUS_BUS_SESSION) {
+ g_printerr("Lost connection to session bus, exiting\n");
+ exit(1);
+ } else {
+ /* Here it would theoretically make sense to reinstall the
+ * idle as a timeout or something, but we don't for now,
+ * just wait for something to trigger a reconnect. It is
+ * not a situation that should happen in reality (we won't
+ * restart the system bus without rebooting).
+ */
+ }
+ return FALSE;
+ }
+
+ info = _big_dbus_ensure_info(connection);
+
+ /* We first need to call AddMatch on all signal watchers.
+ * This is so if on connect, the app calls methods to get
+ * the state the signal notifies the app of changes in,
+ * the match rule is added before the "get current state"
+ * methods are called. Otherwise there's a race where
+ * a signal can be missed between a "get current state" method
+ * call reply and the AddMatch.
+ */
+ _big_dbus_process_pending_signal_watchers(connection, info);
+
+ /* We want the app to see notification of connection opening,
+ * THEN other notifications, so notify it's open first.
+ */
+
+ for (l = all_connect_funcs; l != NULL; l = l->next) {
+ ConnectFuncs *f;
+ f = l->data;
+
+ if (!f->opened && f->funcs->which_bus == bus_type) {
+ f->opened = TRUE;
+ (* f->funcs->opened) (connection, f->data);
+ }
+ }
+
+ /* These two invoke application callbacks, unlike
+ * _big_dbus_process_pending_signal_watchers(), so should come after
+ * the above calls to the "connection opened" callbacks.
+ */
+
+ process_name_ownership_monitors(connection, info);
+
+ process_pending_name_watchers(connection, info);
+
+ return FALSE;
+}
+
+void
+_big_dbus_ensure_connect_idle(DBusBusType bus_type)
+{
+ if (bus_type == DBUS_BUS_SESSION) {
+ if (session_connect_idle_id == 0) {
+ session_connect_idle_id = g_idle_add(connect_idle, GINT_TO_POINTER(bus_type));
+ }
+ } else if (bus_type == DBUS_BUS_SYSTEM) {
+ if (system_connect_idle_id == 0) {
+ system_connect_idle_id = g_idle_add(connect_idle, GINT_TO_POINTER(bus_type));
+ }
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+static void
+internal_add_connect_funcs(const BigDBusConnectFuncs *funcs,
+ void *data,
+ gboolean sync_notify)
+{
+ ConnectFuncs *f;
+
+ f = g_slice_new0(ConnectFuncs);
+ f->funcs = funcs;
+ f->data = data;
+ f->opened = FALSE;
+
+ all_connect_funcs = g_slist_prepend(all_connect_funcs, f);
+
+ _big_dbus_ensure_connect_idle(f->funcs->which_bus);
+
+ if (sync_notify) {
+ /* sync_notify means IF we are already connected
+ * (we have a weak ref != NULL) then notify
+ * right away before we return.
+ */
+ DBusConnection *connection;
+
+ connection = _big_dbus_get_weak_ref(f->funcs->which_bus);
+
+ if (connection != NULL && !f->opened) {
+ f->opened = TRUE;
+ (* f->funcs->opened) (connection, f->data);
+ }
+ }
+}
+
+/* this should guarantee that the funcs are only called async, which is why
+ * it does not try_connecting right away; the idea is to defer to inside the
+ * main loop.
+ */
+void
+big_dbus_add_connect_funcs(const BigDBusConnectFuncs *funcs,
+ void *data)
+{
+ internal_add_connect_funcs(funcs, data, FALSE);
+}
+
+/* The sync_notify flavor calls the open notification right away if
+ * we are already connected.
+ */
+void
+big_dbus_add_connect_funcs_sync_notify(const BigDBusConnectFuncs *funcs,
+ void *data)
+{
+ internal_add_connect_funcs(funcs, data, TRUE);
+}
+
+void
+big_dbus_remove_connect_funcs(const BigDBusConnectFuncs *funcs,
+ void *data)
+{
+ ConnectFuncs *f;
+ GSList *l;
+
+ f = NULL;
+ for (l = all_connect_funcs; l != NULL; l = l->next) {
+ f = l->data;
+
+ if (f->funcs == funcs &&
+ f->data == data)
+ break;
+ }
+
+ if (l == NULL) {
+ g_warning("Could not find functions matching %p %p", funcs, data);
+ return;
+ }
+ g_assert(l->data == f);
+
+ all_connect_funcs = g_slist_delete_link(all_connect_funcs, l);
+ g_slice_free(ConnectFuncs, f);
+}
+
+void
+big_dbus_add_bus_weakref(DBusBusType which_bus,
+ DBusConnection **connection_p)
+{
+ if (which_bus == DBUS_BUS_SESSION) {
+ *connection_p = session_bus_weak_ref;
+ session_bus_weak_refs = g_slist_prepend(session_bus_weak_refs, connection_p);
+ } else if (which_bus == DBUS_BUS_SYSTEM) {
+ *connection_p = system_bus_weak_ref;
+ system_bus_weak_refs = g_slist_prepend(system_bus_weak_refs, connection_p);
+ } else {
+ g_assert_not_reached();
+ }
+
+ _big_dbus_ensure_connect_idle(which_bus);
+}
+
+void
+big_dbus_remove_bus_weakref(DBusBusType which_bus,
+ DBusConnection **connection_p)
+{
+ if (which_bus == DBUS_BUS_SESSION) {
+ *connection_p = NULL;
+ session_bus_weak_refs = g_slist_remove(session_bus_weak_refs, connection_p);
+ } else if (which_bus == DBUS_BUS_SYSTEM) {
+ *connection_p = NULL;
+ system_bus_weak_refs = g_slist_remove(system_bus_weak_refs, connection_p);
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+void
+big_dbus_try_connecting_now(DBusBusType which_bus)
+{
+ try_connecting(which_bus);
+}
+
+static BigJsonIface*
+json_iface_new(const char *name,
+ const BigDBusJsonMethod *methods,
+ int n_methods)
+{
+ BigJsonIface *iface;
+
+ iface = g_slice_new0(BigJsonIface);
+ iface->name = g_strdup(name);
+ iface->methods = methods;
+ iface->n_methods = n_methods;
+
+ return iface;
+}
+
+static void
+json_iface_free(BigJsonIface *iface)
+{
+ g_free(iface->name);
+ g_slice_free(BigJsonIface, iface);
+}
+
+static BigNameOwnershipMonitor*
+name_ownership_monitor_new(DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data)
+{
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = g_slice_new0(BigNameOwnershipMonitor);
+ monitor->bus_type = bus_type;
+ monitor->prev_state = NAME_NOT_REQUESTED;
+ monitor->state = NAME_NOT_REQUESTED;
+ monitor->funcs = funcs;
+ monitor->data = data;
+ monitor->id = ++global_monitor_id;
+
+ return monitor;
+}
+
+static void
+name_ownership_monitor_free(BigNameOwnershipMonitor *monitor)
+{
+
+ g_slice_free(BigNameOwnershipMonitor, monitor);
+}
+
+static BigNameWatch*
+name_watch_new(const char *name)
+{
+ BigNameWatch *watch;
+
+ watch = g_slice_new0(BigNameWatch);
+ watch->name = g_strdup(name);
+
+ /* For unique names, we assume the owner is itself,
+ * so we default to "exists" and maybe emit "vanished",
+ * while with well-known names we do the opposite.
+ */
+ if (*watch->name == ':') {
+ watch->current_owner = g_strdup(watch->name);
+ }
+
+ return watch;
+}
+
+static void
+name_watch_free(BigNameWatch *watch)
+{
+ g_assert(watch->watchers == NULL);
+
+ g_free(watch->name);
+ g_free(watch->current_owner);
+ g_slice_free(BigNameWatch, watch);
+}
+
+static BigNameWatcher*
+name_watcher_new(BigDBusWatchNameFlags flags,
+ const BigDBusWatchNameFuncs *funcs,
+ void *data,
+ DBusBusType bus_type)
+{
+ BigNameWatcher *watcher;
+
+ watcher = g_slice_new0(BigNameWatcher);
+ watcher->flags = flags;
+ watcher->funcs = funcs;
+ watcher->data = data;
+ watcher->bus_type = bus_type;
+ watcher->watch = NULL;
+ watcher->refcount = 1;
+
+ return watcher;
+}
+
+static void
+name_watcher_ref(BigNameWatcher *watcher)
+{
+ watcher->refcount += 1;
+}
+
+static void
+name_watcher_unref(BigNameWatcher *watcher)
+{
+ watcher->refcount -= 1;
+
+ if (watcher->refcount == 0)
+ g_slice_free(BigNameWatcher, watcher);
+}
+
+static void
+info_free(BigDBusInfo *info)
+{
+ void *key;
+ void *value;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Destroy notify invoked on bus connection info for %p",
+ info->where_connection_was);
+
+ if (info->where_connection_was == session_bus_weak_ref)
+ session_bus_weak_ref = NULL;
+
+ if (info->where_connection_was == system_bus_weak_ref)
+ system_bus_weak_ref = NULL;
+
+ /* This could create some strange re-entrancy so do it first.
+ * If we processed a disconnect message, this should have been done
+ * already at that time, but if we were finalized without that,
+ * it may not have been.
+ */
+ if (info->driver_proxy != NULL) {
+ g_object_unref(info->driver_proxy);
+ info->driver_proxy = NULL;
+ }
+
+ while (info->name_ownership_monitors != NULL) {
+ name_ownership_monitor_free(info->name_ownership_monitors->data);
+ info->name_ownership_monitors = g_slist_remove(info->name_ownership_monitors,
+ info->name_ownership_monitors->data);
+ }
+
+ while ((value = g_hash_table_lookup(info->name_watches, &key)))
+ {
+ BigNameWatch *watch = value;
+
+ while (watch->watchers) {
+ name_watch_remove_watcher(watch, watch->watchers->data);
+ }
+
+ name_watch_free(watch);
+ g_hash_table_steal (info->name_watches, &key);
+ }
+
+ if (info->signal_watchers_by_unique_sender) {
+ g_hash_table_destroy(info->signal_watchers_by_unique_sender);
+ }
+
+ if (info->signal_watchers_by_path) {
+ g_hash_table_destroy(info->signal_watchers_by_path);
+ }
+
+ if (info->signal_watchers_by_iface) {
+ g_hash_table_destroy(info->signal_watchers_by_iface);
+ }
+
+ if (info->signal_watchers_by_signal) {
+ g_hash_table_destroy(info->signal_watchers_by_signal);
+ }
+
+ g_hash_table_destroy(info->name_watches);
+ g_hash_table_destroy(info->json_ifaces);
+ g_slice_free(BigDBusInfo, info);
+}
+
+static DBusHandlerResult
+name_ownership_monitor_filter_message(DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ BigDBusInfo *info;
+ gboolean states_changed;
+
+ info = _big_dbus_ensure_info(connection);
+
+ states_changed = FALSE;
+
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameLost") &&
+ dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+ const char *name = NULL;
+ if (dbus_message_get_args(message, NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ GSList *l;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Lost name %s", name);
+
+ for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = l->data;
+
+ if (monitor->state == NAME_PRIMARY_OWNER &&
+ strcmp(name, monitor->funcs->name) == 0) {
+ monitor->prev_state = monitor->state;
+ monitor->state = NAME_NOT_OWNED;
+ states_changed = TRUE;
+ /* keep going, don't break, there may be more matches */
+ }
+ }
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "NameLost has wrong arguments???");
+ }
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameAcquired") &&
+ dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+ const char *name = NULL;
+ if (dbus_message_get_args(message, NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ GSList *l;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Acquired name %s", name);
+
+ for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = l->data;
+
+ if (monitor->state != NAME_PRIMARY_OWNER &&
+ strcmp(name, monitor->funcs->name) == 0) {
+ monitor->prev_state = monitor->state;
+ monitor->state = NAME_PRIMARY_OWNER;
+ states_changed = TRUE;
+ /* keep going, don't break, there may be more matches */
+ }
+ }
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "NameAcquired has wrong arguments???");
+ }
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+ GSList *l;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Disconnected in %s", G_STRFUNC);
+
+ for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = l->data;
+
+ if (monitor->state != NAME_NOT_REQUESTED) {
+ /* Set things up to re-request the name */
+ monitor->prev_state = monitor->state;
+ monitor->state = NAME_NOT_REQUESTED;
+ states_changed = TRUE;
+ }
+ }
+
+ /* FIXME move the monitors back to the pending list so they'll be found on reconnect */
+ }
+
+ if (states_changed)
+ process_name_ownership_monitors(connection, info);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void
+process_name_ownership_monitors(DBusConnection *connection,
+ BigDBusInfo *info)
+{
+ GSList *l;
+ gboolean connected;
+ GSList *still_pending;
+
+ /* First pull anything out of pending queue */
+
+ still_pending = NULL;
+ while (pending_name_ownership_monitors != NULL) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = pending_name_ownership_monitors->data;
+ pending_name_ownership_monitors =
+ g_slist_remove(pending_name_ownership_monitors,
+ pending_name_ownership_monitors->data);
+
+ if (monitor->bus_type == info->bus_type) {
+ info->name_ownership_monitors =
+ g_slist_prepend(info->name_ownership_monitors,
+ monitor);
+ } else {
+ still_pending = g_slist_prepend(still_pending, monitor);
+ }
+ }
+ g_assert(pending_name_ownership_monitors == NULL);
+ pending_name_ownership_monitors = still_pending;
+
+ /* Now send notifications to the app */
+
+ connected = dbus_connection_get_is_connected(connection);
+
+ if (connected) {
+ for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = l->data;
+
+ if (monitor->state == NAME_NOT_REQUESTED) {
+ int result;
+ unsigned int flags;
+ DBusError derror;
+
+ flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
+ if (monitor->funcs->type == BIG_DBUS_NAME_SINGLE_INSTANCE)
+ flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE;
+
+ dbus_error_init(&derror);
+ result = dbus_bus_request_name(connection,
+ monitor->funcs->name,
+ flags,
+ &derror);
+
+ /* log 'error' word only when one occurred */
+ if (derror.message != NULL) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Requested name %s result %d error %s",
+ monitor->funcs->name, result, derror.message);
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Requested name %s result %d",
+ monitor->funcs->name, result);
+ }
+
+ dbus_error_free(&derror);
+
+ /* An important feature of this code is that we always
+ * transition from NOT_REQUESTED to something else when
+ * a name monitor is first added, so we always notify
+ * the app either "acquired" or "lost" and don't
+ * leave the app in limbo.
+ *
+ * This means the app can "get going" when it gets the name
+ * and exit when it loses it, and that will just work
+ * since one or the other will always happen on startup.
+ */
+
+ monitor->prev_state = monitor->state;
+
+ if (result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
+ result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
+ monitor->state = NAME_PRIMARY_OWNER;
+ } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
+ monitor->state = NAME_IN_QUEUE;
+ } else if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) {
+ monitor->state = NAME_NOT_OWNED;
+ } else {
+ /* reply code we don't understand? */
+ monitor->state = NAME_NOT_OWNED;
+ }
+ }
+ }
+ }
+
+ /* Do notifications with a list copy for extra safety
+ * (for true safety we also need to refcount each monitor
+ * and have a "destroyed" flag)
+ */
+ l = g_slist_copy(info->name_ownership_monitors);
+ while (l != NULL) {
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = l->data;
+ l = g_slist_remove(l, l->data);
+
+ if (monitor->prev_state != monitor->state) {
+ monitor->prev_state = monitor->state;
+
+ if (monitor->state == NAME_PRIMARY_OWNER) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Notifying acquired %s",
+ monitor->funcs->name);
+ (* monitor->funcs->acquired) (connection, monitor->funcs->name, monitor->data);
+ } else if (monitor->state != NAME_PRIMARY_OWNER) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Notifying lost %s",
+ monitor->funcs->name);
+ (* monitor->funcs->lost) (connection, monitor->funcs->name, monitor->data);
+ }
+ }
+ }
+}
+
+unsigned int
+big_dbus_acquire_name (DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data)
+{
+ BigNameOwnershipMonitor *monitor;
+
+ monitor = name_ownership_monitor_new(bus_type, funcs, data);
+ pending_name_ownership_monitors = g_slist_prepend(pending_name_ownership_monitors, monitor);
+
+ _big_dbus_ensure_connect_idle(bus_type);
+
+ return monitor->id;
+}
+
+static void
+release_name_internal (DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data,
+ unsigned int id)
+{
+ BigDBusInfo *info;
+ GSList *l;
+ BigNameOwnershipMonitor *monitor;
+ DBusConnection *connection;
+
+ connection = _big_dbus_get_weak_ref(bus_type);
+ if (!connection)
+ return;
+
+ info = _big_dbus_ensure_info(connection);
+
+ /* Check first pending list */
+ for (l = pending_name_ownership_monitors; l; l = l->next) {
+ monitor = l->data;
+ /* If the id is valid an matches, we are done */
+ if (monitor->state == NAME_PRIMARY_OWNER &&
+ ((id != BIG_DBUS_NAME_OWNER_MONITOR_INVALID_ID && monitor->id == id) ||
+ (monitor->funcs == funcs &&
+ monitor->data == data))) {
+ dbus_bus_release_name(connection, monitor->funcs->name, NULL);
+ pending_name_ownership_monitors =
+ g_slist_remove(pending_name_ownership_monitors,
+ monitor);
+ name_ownership_monitor_free(monitor);
+ /* If the monitor was in the pending list it
+ * can't be in the processed list
+ */
+ return;
+ }
+ }
+
+ for (l = info->name_ownership_monitors; l; l = l->next) {
+ monitor = l->data;
+ /* If the id is valid an matches, we are done */
+ if (monitor->state == NAME_PRIMARY_OWNER &&
+ ((id != BIG_DBUS_NAME_OWNER_MONITOR_INVALID_ID && monitor->id == id) ||
+ (monitor->funcs == funcs &&
+ monitor->data == data))) {
+ dbus_bus_release_name(connection, monitor->funcs->name, NULL);
+ info->name_ownership_monitors = g_slist_remove(info->name_ownership_monitors,
+ monitor);
+ name_ownership_monitor_free(monitor);
+ break;
+ }
+ }
+}
+
+void
+big_dbus_release_name_by_id (DBusBusType bus_type,
+ unsigned int id)
+{
+ release_name_internal(bus_type, NULL, NULL, id);
+}
+
+void
+big_dbus_release_name (DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data)
+{
+ release_name_internal(bus_type, funcs, data,
+ BIG_DBUS_NAME_OWNER_MONITOR_INVALID_ID);
+}
+
+static void
+notify_name_owner_changed(DBusConnection *connection,
+ const char *name,
+ const char *new_owner)
+{
+ BigDBusInfo *info;
+ BigNameWatch *watch;
+ GSList *l, *watchers;
+ gchar *old_owner;
+
+ info = _big_dbus_ensure_info(connection);
+
+ if (*new_owner == '\0')
+ new_owner = NULL;
+
+ watch = g_hash_table_lookup(info->name_watches, name);
+
+ if (watch == NULL)
+ return;
+
+ if ((watch->current_owner == new_owner) ||
+ (watch->current_owner && new_owner &&
+ strcmp(watch->current_owner, new_owner) == 0)) {
+ /* No change */
+ return;
+ }
+
+ /* we copy the list before iterating, because the
+ * callbacks may modify it */
+ watchers = g_slist_copy(watch->watchers);
+ g_slist_foreach(watchers, (GFunc)name_watcher_ref, NULL);
+
+ /* copy the old owner in case the watch is removed in
+ * the callbacks */
+ old_owner = g_strdup(watch->current_owner);
+
+ /* vanish the old owner */
+ if (old_owner != NULL) {
+ for (l = watchers;
+ l != NULL;
+ l = l->next) {
+ BigNameWatcher *watcher = l->data;
+
+ if (watcher->notify_idle != 0) {
+ /* Name owner changed before we notified
+ * the watcher of the initial name. We will notify
+ * him now of the old name, then that this name
+ * vanished.
+ *
+ * This is better than not sending calling any
+ * callback, it might for instance trigger destroying
+ * signal watchers on the unique name.
+ */
+ g_source_remove(watcher->notify_idle);
+ notify_watcher_name_appeared(watcher);
+ }
+
+ if (!watcher->destroyed) {
+ (* watcher->funcs->vanished) (connection,
+ name,
+ old_owner,
+ watcher->data);
+ }
+ }
+ }
+
+ /* lookup for the watch again, since it might have vanished
+ * if all watchers were removed in the watcher->vanished
+ * callbacks */
+ watch = g_hash_table_lookup(info->name_watches, name);
+
+ if (watch) {
+ g_free(watch->current_owner);
+ watch->current_owner = g_strdup(new_owner);
+ }
+
+ /* appear the new owner */
+ if (new_owner != NULL) {
+ for (l = watchers;
+ l != NULL;
+ l = l->next) {
+ BigNameWatcher *watcher = l->data;
+
+ if (!watcher->destroyed) {
+ (* watcher->funcs->appeared) (connection,
+ name,
+ new_owner,
+ watcher->data);
+ }
+ }
+ }
+
+ /* now destroy our copy */
+ g_slist_foreach(watchers, (GFunc)name_watcher_unref, NULL);
+ g_slist_free(watchers);
+
+ g_free(old_owner);
+}
+
+static DBusHandlerResult
+name_watch_filter_message(DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ BigDBusInfo *info;
+
+ info = _big_dbus_ensure_info(connection);
+
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged") &&
+ dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+ const char *name = NULL;
+ const char *old_owner = NULL;
+ const char *new_owner = NULL;
+ if (dbus_message_get_args(message, NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID)) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "NameOwnerChanged %s: %s -> %s",
+ name, old_owner, new_owner);
+
+ notify_name_owner_changed(connection, name, new_owner);
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "NameOwnerChanged has wrong arguments???");
+ }
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Disconnected in %s", G_STRFUNC);
+
+ /* FIXME set all current owners to NULL, and move watches back to the pending
+ * list so they are found on reconnect.
+ */
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+void
+_big_dbus_set_matching_name_owner_changed(DBusConnection *connection,
+ const char *bus_name,
+ gboolean matched)
+{
+ char *s;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "%s NameOwnerChanged on name '%s'",
+ matched ? "Matching" : "No longer matching",
+ bus_name);
+
+ s = g_strdup_printf("type='signal',sender='"
+ DBUS_SERVICE_DBUS
+ "',interface='"
+ DBUS_INTERFACE_DBUS
+ "',member='"
+ "NameOwnerChanged"
+ "',arg0='%s'",
+ bus_name);
+
+ if (matched)
+ dbus_bus_add_match(connection,
+ s, NULL); /* asking for error would make this block */
+ else
+ dbus_bus_remove_match(connection, s, NULL);
+
+ g_free(s);
+}
+
+static void
+on_start_service_reply(BigDBusProxy *proxy,
+ DBusMessage *message,
+ void *data)
+{
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Got successful reply to service start");
+}
+
+static void
+on_start_service_error(BigDBusProxy *proxy,
+ const char *error_name,
+ const char *error_message,
+ void *data)
+{
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Got error starting service: %s: %s",
+ error_name, error_message);
+}
+
+void
+big_dbus_start_service(DBusConnection *connection,
+ const char *name)
+{
+ DBusMessage *message;
+ dbus_uint32_t flags;
+ BigDBusInfo *info;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Starting service '%s'",
+ name);
+
+ info = _big_dbus_ensure_info(connection);
+
+ message = big_dbus_proxy_new_method_call(info->driver_proxy,
+ "StartServiceByName");
+
+ flags = 0;
+ if (dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_UINT32, &flags,
+ DBUS_TYPE_INVALID)) {
+ big_dbus_proxy_send(info->driver_proxy,
+ message,
+ on_start_service_reply,
+ on_start_service_error,
+ NULL);
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "No memory appending args to StartServiceByName");
+ }
+
+ dbus_message_unref(message);
+}
+
+typedef struct {
+ DBusConnection *connection;
+ char *name;
+ BigDBusWatchNameFlags flags;
+} GetOwnerRequest;
+
+static GetOwnerRequest*
+get_owner_request_new(DBusConnection *connection,
+ const char *name,
+ BigDBusWatchNameFlags flags)
+{
+ GetOwnerRequest *gor;
+
+ gor = g_slice_new0(GetOwnerRequest);
+ gor->connection = connection;
+ gor->name = g_strdup(name);
+ gor->flags = flags;
+ dbus_connection_ref(connection);
+
+ return gor;
+}
+
+static void
+get_owner_request_free(GetOwnerRequest *gor)
+{
+ dbus_connection_unref(gor->connection);
+ g_free(gor->name);
+ g_slice_free(GetOwnerRequest, gor);
+}
+
+static void
+on_get_owner_reply(DBusPendingCall *pending,
+ void *user_data)
+{
+ DBusMessage *reply;
+ GetOwnerRequest *gor;
+
+ gor = user_data;
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (reply == NULL) {
+ g_warning("NULL reply in on_get_owner_reply?");
+ return;
+ }
+
+ if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ const char *current_owner = NULL;
+
+ if (!dbus_message_get_args(reply, NULL,
+ DBUS_TYPE_STRING, &current_owner,
+ DBUS_TYPE_INVALID)) {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "GetNameOwner has wrong args '%s'",
+ dbus_message_get_signature(reply));
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Got owner '%s' for name '%s'",
+ current_owner, gor->name);
+ if (current_owner != NULL) {
+ notify_name_owner_changed(gor->connection,
+ gor->name,
+ current_owner);
+ }
+ }
+ } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+ if (g_str_equal(dbus_message_get_error_name(reply),
+ DBUS_ERROR_NAME_HAS_NO_OWNER)) {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "'%s' was not running",
+ gor->name);
+ if (gor->flags & BIG_DBUS_NAME_START_IF_NOT_FOUND) {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ " (starting it up)");
+ big_dbus_start_service(gor->connection, gor->name);
+ } else {
+ /* no owner for now, notify app */
+ notify_name_owner_changed(gor->connection,
+ gor->name,
+ "");
+ }
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Error getting owner of name '%s': %s",
+ gor->name,
+ dbus_message_get_error_name(reply));
+
+ /* Notify no owner for now, ensuring the app
+ * gets advised "appeared" or "vanished",
+ * one or the other.
+ */
+ notify_name_owner_changed(gor->connection,
+ gor->name,
+ "");
+ }
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Nonsensical reply type to GetNameOwner");
+ }
+
+ dbus_message_unref(reply);
+}
+
+static void
+request_name_owner(DBusConnection *connection,
+ BigDBusInfo *info,
+ BigNameWatch *watch)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetNameOwner");
+ if (message == NULL)
+ g_error("no memory");
+
+ if (!dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &watch->name,
+ DBUS_TYPE_INVALID))
+ g_error("no memory");
+
+ call = NULL;
+ dbus_connection_send_with_reply(connection, message, &call, -1);
+ if (call != NULL) {
+ GetOwnerRequest *gor;
+ BigDBusWatchNameFlags flags;
+ GSList *l;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Sent GetNameOwner for '%s'",
+ watch->name);
+
+ flags = 0;
+ for (l = watch->watchers;
+ l != NULL;
+ l = l->next) {
+ BigNameWatcher *watcher = l->data;
+
+ if (watcher->flags & BIG_DBUS_NAME_START_IF_NOT_FOUND)
+ flags |= BIG_DBUS_NAME_START_IF_NOT_FOUND;
+ }
+
+ gor = get_owner_request_new(connection, watch->name, flags);
+
+ if (!dbus_pending_call_set_notify(call, on_get_owner_reply,
+ gor,
+ (DBusFreeFunction) get_owner_request_free))
+ g_error("no memory");
+
+ /* the connection will hold a ref to the pending call */
+ dbus_pending_call_unref(call);
+ } else {
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "GetNameOwner for '%s' not sent, connection disconnected",
+ watch->name);
+ }
+}
+
+static gboolean
+notify_watcher_name_appeared(gpointer data)
+{
+ BigNameWatcher *watcher;
+ DBusConnection *connection;
+
+ watcher = data;
+ watcher->notify_idle = 0;
+
+ connection = _big_dbus_get_weak_ref(watcher->bus_type);
+
+ if (!connection)
+ return FALSE;
+
+ (* watcher->funcs->appeared) (connection,
+ watcher->watch->name,
+ watcher->watch->current_owner,
+ watcher->data);
+ return FALSE;
+}
+
+static void
+create_watch_for_watcher(DBusConnection *connection,
+ BigDBusInfo *info,
+ const char *name,
+ BigNameWatcher *watcher)
+{
+ BigNameWatch *watch;
+
+ watch = g_hash_table_lookup(info->name_watches, name);
+ if (watch == NULL) {
+ watch = name_watch_new(name);
+
+ g_hash_table_replace(info->name_watches, watch->name, watch);
+
+ watch->watchers = g_slist_prepend(watch->watchers, watcher);
+
+ _big_dbus_set_matching_name_owner_changed(connection, watch->name, TRUE);
+
+ request_name_owner(connection, info, watch);
+ } else {
+ watch->watchers = g_slist_prepend(watch->watchers, watcher);
+ }
+ name_watcher_ref(watcher);
+
+ watcher->watch = watch;
+
+}
+
+static void
+process_pending_name_watchers(DBusConnection *connection,
+ BigDBusInfo *info)
+{
+ GSList *still_pending;
+
+ still_pending = NULL;
+ while (pending_name_watchers != NULL) {
+ BigPendingNameWatcher *pending;
+ BigNameWatch *watch;
+
+ pending = pending_name_watchers->data;
+ pending_name_watchers = g_slist_remove(pending_name_watchers,
+ pending_name_watchers->data);
+
+ if (pending->bus_type != info->bus_type) {
+ still_pending = g_slist_prepend(still_pending, pending);
+ continue;
+ }
+
+ create_watch_for_watcher(connection,
+ info,
+ pending->name,
+ pending->watcher);
+
+ watch = pending->watcher->watch;
+
+ /* If we already know the owner, let the new watcher know */
+ if (watch->current_owner != NULL) {
+ (* pending->watcher->funcs->appeared) (connection,
+ watch->name,
+ watch->current_owner,
+ pending->watcher->data);
+ }
+
+ g_free(pending->name);
+ name_watcher_unref(pending->watcher);
+ g_slice_free(BigPendingNameWatcher, pending);
+ }
+
+ g_assert(pending_name_watchers == NULL);
+ pending_name_watchers = still_pending;
+}
+
+static void
+name_watch_remove_watcher(BigNameWatch *watch,
+ BigNameWatcher *watcher)
+{
+ watch->watchers = g_slist_remove(watch->watchers,
+ watcher);
+
+ if (watcher->notify_idle) {
+ g_source_remove(watcher->notify_idle);
+ watcher->notify_idle = 0;
+ }
+
+ watcher->destroyed = TRUE;
+ name_watcher_unref(watcher);
+}
+
+void
+big_dbus_watch_name(DBusBusType bus_type,
+ const char *name,
+ BigDBusWatchNameFlags flags,
+ const BigDBusWatchNameFuncs *funcs,
+ void *data)
+{
+ BigNameWatcher *watcher;
+ DBusConnection *connection;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Adding watch on name '%s'",
+ name);
+
+ watcher = name_watcher_new(flags, funcs, data, bus_type);
+
+ connection = _big_dbus_get_weak_ref(bus_type);
+
+ if (connection) {
+ BigDBusInfo *info;
+
+ info = _big_dbus_ensure_info(connection);
+
+ create_watch_for_watcher(connection,
+ info,
+ name,
+ watcher);
+ /* The initial reference is now transferred to the watch */
+ name_watcher_unref(watcher);
+
+ /* If we already know the owner, notify the user in an idle */
+ if (watcher->watch->current_owner) {
+ watcher->notify_idle =
+ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
+ notify_watcher_name_appeared,
+ watcher,
+ (GDestroyNotify)name_watcher_unref);
+ name_watcher_ref(watcher);
+ }
+
+ } else {
+ BigPendingNameWatcher *pending;
+
+ pending = g_slice_new0(BigPendingNameWatcher);
+
+ pending->bus_type = bus_type;
+ pending->name = g_strdup(name);
+ pending->watcher = watcher;
+
+ pending_name_watchers = g_slist_prepend(pending_name_watchers, pending);
+
+ _big_dbus_ensure_connect_idle(pending->bus_type);
+ }
+}
+
+void
+big_dbus_unwatch_name(DBusBusType bus_type,
+ const char *name,
+ const BigDBusWatchNameFuncs *funcs,
+ void *data)
+{
+ DBusConnection *connection;
+ BigDBusInfo *info;
+ BigNameWatch *watch;
+ GSList *l;
+ BigNameWatcher *watcher;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Removing watch on name '%s'",
+ name);
+
+ connection = _big_dbus_get_weak_ref(bus_type);
+ if (connection == NULL) {
+ /* right now our state is entirely hosed if we disconnect
+ * (we don't move the watchers out of the connection data),
+ * so can't do much here without larger changes to the file
+ */
+ g_warning("Have not implemented disconnect handling");
+ return;
+ }
+
+ info = _big_dbus_ensure_info(connection);
+
+ /* could still be pending */
+ process_pending_name_watchers(connection, info);
+
+ watch = g_hash_table_lookup(info->name_watches, name);
+
+ if (watch == NULL) {
+ g_warning("attempt to unwatch name %s but nobody is watching that",
+ name);
+ return;
+ }
+
+ watcher = NULL;
+ for (l = watch->watchers; l != NULL; l = l->next) {
+ watcher = l->data;
+
+ if (watcher->funcs == funcs &&
+ watcher->data == data)
+ break;
+ }
+
+ if (l == NULL) {
+ g_warning("Could not find a watch on %s matching %p %p",
+ name, funcs, data);
+ return;
+ }
+ g_assert(l->data == watcher);
+
+ name_watch_remove_watcher(watch, watcher);
+
+ /* Clear out the watch if it's gone */
+ if (watch->watchers == NULL) {
+ g_hash_table_remove(info->name_watches, watch->name);
+
+ _big_dbus_set_matching_name_owner_changed(connection, watch->name, FALSE);
+
+ name_watch_free(watch);
+ }
+}
+
+const char*
+big_dbus_get_watched_name_owner(DBusBusType bus_type,
+ const char *name)
+{
+ DBusConnection *connection;
+ BigNameWatch *watch;
+ BigDBusInfo *info;
+
+ connection = _big_dbus_get_weak_ref(bus_type);
+ if (connection == NULL) {
+ return NULL;
+ }
+
+ info = _big_dbus_ensure_info(connection);
+
+ /* could still be pending */
+ process_pending_name_watchers(connection, info);
+
+ watch = g_hash_table_lookup(info->name_watches, name);
+ if (watch == NULL) {
+ g_warning("Tried to get owner of '%s' but there is no watch on it",
+ name);
+ return NULL;
+ }
+
+ return watch->current_owner;
+}
+
+void
+big_dbus_register_json(DBusConnection *connection,
+ const char *iface_name,
+ const BigDBusJsonMethod *methods,
+ int n_methods)
+{
+ BigDBusInfo *info;
+ BigJsonIface *iface;
+
+ info = _big_dbus_ensure_info(connection);
+
+ iface = json_iface_new(iface_name, methods, n_methods);
+
+ g_hash_table_replace(info->json_ifaces, iface->name, iface);
+}
+
+void
+big_dbus_unregister_json(DBusConnection *connection,
+ const char *iface_name)
+{
+ BigDBusInfo *info;
+
+ info = _big_dbus_ensure_info(connection);
+
+ g_hash_table_remove(info->json_ifaces, iface_name);
+}
+
+typedef struct {
+ DBusConnection *connection;
+ GObject *gobj;
+ char *iface_name;
+} BigDBusGObject;
+
+static void
+gobj_path_unregistered(DBusConnection *connection,
+ void *user_data)
+{
+ BigDBusGObject *g;
+
+ g = user_data;
+
+ if (g->gobj) {
+ g_object_remove_weak_pointer(g->gobj, (void**) &g->gobj);
+ g->gobj = NULL;
+ }
+
+ g_free(g->iface_name);
+ g_slice_free(BigDBusGObject, g);
+}
+
+static DBusHandlerResult
+gobj_path_message(DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ BigDBusGObject *g;
+ BigDBusInfo *info;
+ BigJsonIface *iface;
+ const char *message_iface;
+ const char *message_method;
+ DBusError derror;
+ int i;
+ const BigDBusJsonMethod *method;
+ DBusMessageIter arg_iter, dict_iter;
+
+ info = _big_dbus_ensure_info(connection);
+ g = user_data;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Received message to iface %s gobj %p",
+ g->iface_name, g->gobj);
+
+ if (g->gobj == NULL) {
+ /* GObject was destroyed */
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ dbus_error_init(&derror);
+
+ message_iface = dbus_message_get_interface(message);
+
+ /* FIXME implement Introspectable() just to enable dbus debugger */
+
+ if (message_iface != NULL &&
+ strcmp(message_iface, g->iface_name) != 0) {
+
+ dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+ "Interface '%s' not implemented by this object, did you mean '%s'?",
+ message_iface, g->iface_name);
+
+ goto out;
+ }
+
+ iface = g_hash_table_lookup(info->json_ifaces,
+ g->iface_name);
+ if (iface == NULL) {
+ g_warning("Object registered with iface %s but that iface is not registered",
+ g->iface_name);
+ dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+ "Bug - '%s' is not registered",
+ g->iface_name);
+ goto out;
+ }
+
+ method = NULL;
+ message_method = dbus_message_get_member(message);
+ for (i = 0; i < iface->n_methods; ++i) {
+ if (strcmp(message_method, iface->methods[i].name) == 0) {
+ method = &iface->methods[i];
+ break;
+ }
+ }
+
+ if (method == NULL) {
+ dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+ "Interface '%s' has no method '%s'",
+ g->iface_name, message_method);
+ goto out;
+ }
+
+ if (!dbus_message_has_signature(message, "a{sv}")) {
+ dbus_set_error(&derror, DBUS_ERROR_INVALID_ARGS,
+ "Method %s.%s should have 1 argument which is a dictionary",
+ g->iface_name, message_method);
+ goto out;
+ }
+
+ dbus_message_iter_init(message, &arg_iter);
+ dbus_message_iter_recurse(&arg_iter, &dict_iter);
+
+ if (method->sync_func != NULL) {
+ DBusMessage *reply;
+ DBusMessageIter out_arg_iter, out_dict_iter;
+
+ reply = dbus_message_new_method_return(message);
+ if (reply == NULL) {
+ dbus_set_error(&derror, DBUS_ERROR_NO_MEMORY,
+ "No memory");
+ goto out;
+ }
+
+ dbus_message_iter_init_append(reply, &out_arg_iter);
+ dbus_message_iter_open_container(&out_arg_iter,
+ DBUS_TYPE_ARRAY, "{sv}",
+ &out_dict_iter);
+
+ g_object_ref(g->gobj);
+ (* method->sync_func) (connection, message,
+ &dict_iter, &out_dict_iter,
+ g->gobj,
+ &derror);
+ g_object_unref(g->gobj);
+
+ dbus_message_iter_close_container(&out_arg_iter, &out_dict_iter);
+
+ if (!dbus_error_is_set(&derror)) {
+ dbus_connection_send(connection, reply, NULL);
+ }
+ dbus_message_unref(reply);
+
+ } else if (method->async_func != NULL) {
+ g_object_ref(g->gobj);
+ (* method->async_func) (connection, message,
+ &dict_iter,
+ g->gobj);
+ g_object_unref(g->gobj);
+ } else {
+ g_warning("Method %s does not have any implementation", method->name);
+ }
+
+ out:
+ if (dbus_error_is_set(&derror)) {
+ DBusMessage *reply;
+
+ reply = dbus_message_new_error(message,
+ derror.name,
+ derror.message);
+ dbus_error_free(&derror);
+
+ if (reply != NULL) {
+ dbus_connection_send(connection, reply, NULL);
+
+ dbus_message_unref(reply);
+ } else {
+ /* use g_printerr not g_warning since this is NOT a "can
+ * never happen" just a "probably will never happen"
+ */
+ g_printerr("Could not send OOM error\n");
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusObjectPathVTable gobj_vtable = {
+ gobj_path_unregistered,
+ gobj_path_message,
+ NULL,
+};
+
+/* Note that because of how this works, each object can be registered
+ * at multiple paths but only once per path. Which is sort of bizarre,
+ * but we'll fix it when we need it.
+ */
+void
+big_dbus_register_g_object(DBusConnection *connection,
+ const char *path,
+ GObject *gobj,
+ const char *iface_name)
+{
+ BigDBusGObject *g;
+
+ g = g_slice_new0(BigDBusGObject);
+ g->iface_name = g_strdup(iface_name);
+ g->gobj = gobj;
+
+ if (!dbus_connection_register_object_path(connection, path,
+ &gobj_vtable, g)) {
+ g_warning("Failed to register object path %s", path);
+ }
+
+ g_object_add_weak_pointer(g->gobj, (void**) &g->gobj);
+}
+
+void
+big_dbus_unregister_g_object (DBusConnection *connection,
+ const char *path)
+{
+ dbus_connection_unregister_object_path(connection, path);
+}
+
+static void
+open_json_entry(DBusMessageIter *dict_iter,
+ const char *key,
+ const char *signature,
+ DBusMessageIter *entry_iter,
+ DBusMessageIter *variant_iter)
+{
+ dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, entry_iter);
+
+ dbus_message_iter_append_basic(entry_iter, DBUS_TYPE_STRING, &key);
+
+ dbus_message_iter_open_container(entry_iter, DBUS_TYPE_VARIANT, signature, variant_iter);
+}
+
+static void
+close_json_entry(DBusMessageIter *dict_iter,
+ DBusMessageIter *entry_iter,
+ DBusMessageIter *variant_iter)
+{
+ dbus_message_iter_close_container(entry_iter, variant_iter);
+
+ dbus_message_iter_close_container(dict_iter, entry_iter);
+}
+
+static void
+open_json_entry_array(DBusMessageIter *dict_iter,
+ const char *key,
+ int array_element_type,
+ DBusMessageIter *entry_iter,
+ DBusMessageIter *variant_iter,
+ DBusMessageIter *array_iter)
+{
+ char buf[3];
+ buf[0] = 'a';
+ buf[1] = array_element_type;
+ buf[2] = '\0';
+
+ open_json_entry(dict_iter, key, buf, entry_iter, variant_iter);
+
+ dbus_message_iter_open_container(variant_iter, DBUS_TYPE_ARRAY, &buf[1], array_iter);
+}
+
+static void
+close_json_entry_array(DBusMessageIter *dict_iter,
+ DBusMessageIter *entry_iter,
+ DBusMessageIter *variant_iter,
+ DBusMessageIter *array_iter)
+{
+ dbus_message_iter_close_container(variant_iter, array_iter);
+
+ close_json_entry(dict_iter, entry_iter, variant_iter);
+}
+
+void
+big_dbus_append_json_entry (DBusMessageIter *dict_iter,
+ const char *key,
+ int dbus_type,
+ void *basic_value_p)
+{
+ DBusMessageIter entry_iter, variant_iter;
+ char buf[2];
+
+ buf[0] = dbus_type;
+ buf[1] = '\0';
+
+ open_json_entry(dict_iter, key, buf, &entry_iter, &variant_iter);
+
+ dbus_message_iter_append_basic(&variant_iter, dbus_type, basic_value_p);
+
+ close_json_entry(dict_iter, &entry_iter, &variant_iter);
+}
+
+void
+big_dbus_append_json_entry_STRING (DBusMessageIter *dict_iter,
+ const char *key,
+ const char *value)
+{
+ big_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_STRING, &value);
+}
+
+void
+big_dbus_append_json_entry_INT32 (DBusMessageIter *dict_iter,
+ const char *key,
+ dbus_int32_t value)
+{
+ big_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_INT32, &value);
+}
+
+void
+big_dbus_append_json_entry_DOUBLE (DBusMessageIter *dict_iter,
+ const char *key,
+ double value)
+{
+ big_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_DOUBLE, &value);
+}
+
+void
+big_dbus_append_json_entry_BOOLEAN (DBusMessageIter *dict_iter,
+ const char *key,
+ dbus_bool_t value)
+{
+ big_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_BOOLEAN, &value);
+}
+
+/* when coming from a dynamic language, we don't know what type of array '[]' is supposed to be */
+void
+big_dbus_append_json_entry_EMPTY_ARRAY (DBusMessageIter *dict_iter,
+ const char *key)
+{
+ DBusMessageIter entry_iter, variant_iter, array_iter;
+
+ /* so just say VARIANT even though there won't be any elements in the array */
+ open_json_entry_array(dict_iter, key, DBUS_TYPE_VARIANT, &entry_iter, &variant_iter, &array_iter);
+
+ close_json_entry_array(dict_iter, &entry_iter, &variant_iter, &array_iter);
+}
+
+void
+big_dbus_append_json_entry_STRING_ARRAY (DBusMessageIter *dict_iter,
+ const char *key,
+ const char **value)
+{
+ DBusMessageIter entry_iter, variant_iter, array_iter;
+ int i;
+
+ open_json_entry_array(dict_iter, key, DBUS_TYPE_STRING, &entry_iter, &variant_iter, &array_iter);
+
+ for (i = 0; value[i] != NULL; ++i) {
+ dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &value[i]);
+ }
+
+ close_json_entry_array(dict_iter, &entry_iter, &variant_iter, &array_iter);
+}
+
+gboolean
+big_dbus_message_iter_get_gsize(DBusMessageIter *iter,
+ gsize *value_p)
+{
+ switch (dbus_message_iter_get_arg_type(iter)) {
+ case DBUS_TYPE_INT32:
+ {
+ dbus_int32_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v < 0)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_UINT32:
+ {
+ dbus_uint32_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_INT64:
+ {
+ dbus_int64_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v < 0)
+ return FALSE;
+ if (((guint64)v) > G_MAXSIZE)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_UINT64:
+ {
+ dbus_uint64_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v > G_MAXSIZE)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+big_dbus_message_iter_get_gssize(DBusMessageIter *iter,
+ gssize *value_p)
+{
+ switch (dbus_message_iter_get_arg_type(iter)) {
+ case DBUS_TYPE_INT32:
+ {
+ dbus_int32_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_UINT32:
+ {
+ dbus_uint32_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v > (guint32) G_MAXSSIZE)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_INT64:
+ {
+ dbus_int64_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v > (gint64) G_MAXSSIZE)
+ return FALSE;
+ if (v < (gint64) G_MINSSIZE)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ case DBUS_TYPE_UINT64:
+ {
+ dbus_uint64_t v;
+ dbus_message_iter_get_basic(iter, &v);
+ if (v > (guint64) G_MAXSSIZE)
+ return FALSE;
+ *value_p = v;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#if BIG_BUILD_TESTS
+
+#include "dbus-proxy.h"
+#include "dbus-input-stream.h"
+#include "dbus-output-stream.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+static pid_t test_service_pid = 0;
+static BigDBusProxy *test_service_proxy = NULL;
+
+static pid_t test_io_pid = 0;
+static BigDBusProxy *test_io_proxy = NULL;
+
+static GMainLoop *client_loop = NULL;
+
+static int n_running_children = 0;
+
+static BigDBusInputStream *input_from_io_service;
+static BigDBusOutputStream *output_to_io_service;
+
+static const char stream_data_to_io_service[] = "This is sent from the main test process to the IO service.";
+static const char stream_data_from_io_service[] = "This is sent from the IO service to the main test process. The quick brown fox, etc.";
+
+static void do_test_service_child (void);
+static void do_test_io_child (void);
+
+/* quit when all children are gone */
+static void
+another_child_down(void)
+{
+ g_assert(n_running_children > 0);
+ n_running_children -= 1;
+
+ if (n_running_children == 0) {
+ g_main_loop_quit(client_loop);
+ }
+}
+
+static const char*
+extract_string_arg(DBusMessageIter *in_iter,
+ const char *prop_name,
+ DBusError *error)
+{
+ const char *s;
+
+ s = NULL;
+ while (dbus_message_iter_get_arg_type(in_iter) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry_iter, variant_iter;
+ const char *key;
+
+ dbus_message_iter_recurse(in_iter, &entry_iter);
+
+ dbus_message_iter_get_basic(&entry_iter, &key);
+
+ if (strcmp(key, prop_name) == 0) {
+ dbus_message_iter_next(&entry_iter);
+
+ dbus_message_iter_recurse(&entry_iter, &variant_iter);
+ if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_STRING) {
+ dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+ "Value of '%s' prop should be a string",
+ prop_name);
+ return NULL;
+ }
+
+ dbus_message_iter_get_basic(&variant_iter, &s);
+
+ return s;
+ }
+ }
+
+ dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+ "No '%s' prop provided", prop_name);
+ return NULL;
+}
+
+static void
+fork_child_test_service(void)
+{
+ pid_t child_pid;
+
+ /* it would break to fork after we already connected */
+ g_assert(session_bus_weak_ref == NULL);
+ g_assert(system_bus_weak_ref == NULL);
+ g_assert(test_service_pid == 0);
+
+ child_pid = fork();
+
+ if (child_pid == -1) {
+ g_error("Failed to fork dbus service");
+ } else if (child_pid > 0) {
+ /* We are the parent */
+ test_service_pid = child_pid;
+ n_running_children += 1;
+
+ return;
+ }
+
+ /* we are the child, set up a service for main test process to talk to */
+
+ do_test_service_child();
+}
+
+/* This test function doesn't really test anything, just sets up
+ * for the following one
+ */
+static void
+fork_child_test_io(void)
+{
+ pid_t child_pid;
+
+ /* it would break to fork after we already connected */
+ g_assert(session_bus_weak_ref == NULL);
+ g_assert(system_bus_weak_ref == NULL);
+ g_assert(test_io_pid == 0);
+
+ child_pid = fork();
+
+ if (child_pid == -1) {
+ g_error("Failed to fork dbus service");
+ } else if (child_pid > 0) {
+ /* We are the parent */
+ test_io_pid = child_pid;
+ n_running_children += 1;
+
+ return;
+ }
+
+ /* we are the child, set up a service for main test process to talk to */
+
+ do_test_io_child();
+}
+
+static void
+on_expected_fnf_error_reply_kill_child(BigDBusProxy *proxy,
+ const char *error_name,
+ const char *error_message,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "got expected error reply to alwaysErrorSync, killing child");
+
+ /* We were expecting an error, good. */
+ if (strcmp(error_name, DBUS_ERROR_FILE_NOT_FOUND) != 0) {
+ g_error("Got error we did not expect %s: %s",
+ error_name, error_message);
+ }
+
+ if (kill(test_service_pid, SIGTERM) < 0) {
+ g_error("Test service was no longer around... it must have failed somehow (%s)",
+ strerror(errno));
+ }
+
+ /* We will quit main loop when we see the child go away */
+}
+
+static void
+on_unexpected_error_reply(BigDBusProxy *proxy,
+ const char *error_name,
+ const char *error_message,
+ void *data)
+{
+ const char *context_text = data;
+
+ g_error("Got error %s: '%s' context was: %s",
+ error_name, error_message, context_text);
+}
+
+static void
+on_get_always_error_reply(BigDBusProxy *proxy,
+ DBusMessage *message,
+ DBusMessageIter *return_value_iter,
+ void *data)
+{
+ g_error("alwaysError json method supposed to return an error always, not a valid reply");
+}
+
+static void
+on_get_some_stuff_reply(BigDBusProxy *proxy,
+ DBusMessage *message,
+ DBusMessageIter *return_value_iter,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "reply received to getSomeStuffSync");
+
+ /* FIXME look at the return value to see if it's what
+ * the test service sends
+ */
+
+ big_dbus_proxy_call_json_async(test_service_proxy,
+ "alwaysErrorSync",
+ on_get_always_error_reply,
+ on_expected_fnf_error_reply_kill_child,
+ NULL,
+ NULL);
+}
+
+static void
+on_test_service_appeared(DBusConnection *connection,
+ const char *name,
+ const char *new_owner_unique_name,
+ void *data)
+{
+ dbus_int32_t v_INT32;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "%s appeared",
+ name);
+
+ test_service_proxy =
+ big_dbus_proxy_new(connection, new_owner_unique_name,
+ "/com/litl/test/object42",
+ "com.litl.TestIface");
+ v_INT32 = 42;
+ big_dbus_proxy_call_json_async(test_service_proxy,
+ "getSomeStuffSync",
+ on_get_some_stuff_reply,
+ on_unexpected_error_reply,
+ "getSomeStuffSync call from on_test_service_appeared",
+ "yourNameIs", DBUS_TYPE_STRING, &name,
+ "yourUniqueNameIs", DBUS_TYPE_STRING, &new_owner_unique_name,
+ "anIntegerIs", DBUS_TYPE_INT32, &v_INT32,
+ NULL);
+}
+
+static void
+on_test_service_vanished(DBusConnection *connection,
+ const char *name,
+ const char *old_owner_unique_name,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "%s vanished", name);
+
+ another_child_down();
+}
+
+static BigDBusWatchNameFuncs watch_test_service_funcs = {
+ on_test_service_appeared,
+ on_test_service_vanished
+};
+
+static void
+on_confirm_streams_reply(BigDBusProxy *proxy,
+ DBusMessage *message,
+ DBusMessageIter *return_value_iter,
+ void *data)
+{
+ const char *received;
+
+ received = extract_string_arg(return_value_iter,
+ "received",
+ NULL);
+ g_assert(received != NULL);
+
+ if (strcmp(received, stream_data_to_io_service) != 0) {
+ g_error("We sent the child process '%s' but it says it got '%s'",
+ stream_data_to_io_service,
+ received);
+ }
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestIO says it got: '%s'", received);
+
+ /* We've exchanged all our streams - time to kill the TestIO
+ * child process
+ */
+ big_debug(BIG_DEBUG_IN_TESTS, "Sending TERM to TestIO child");
+ if (kill(test_io_pid, SIGTERM) < 0) {
+ g_error("Test IO service was no longer around... it must have failed somehow (%s)",
+ strerror(errno));
+ }
+}
+
+static void
+on_setup_streams_reply(BigDBusProxy *proxy,
+ DBusMessage *message,
+ DBusMessageIter *return_value_iter,
+ void *data)
+{
+ const char *stream_path;
+ gsize total;
+ gssize result;
+ gsize read_size;
+ GError *error;
+ GString *str;
+ char buf[10];
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "Got reply to setupStreams");
+
+ stream_path = extract_string_arg(return_value_iter,
+ "stream",
+ NULL);
+ g_assert(stream_path != NULL);
+
+ output_to_io_service =
+ big_dbus_output_stream_new(big_dbus_proxy_get_connection(proxy),
+ dbus_message_get_sender(message),
+ stream_path);
+
+ g_assert(input_from_io_service && output_to_io_service);
+
+ /* Write to the output stream */
+
+ total = strlen(stream_data_to_io_service);
+
+ error = NULL;
+ result = g_output_stream_write(G_OUTPUT_STREAM(output_to_io_service),
+ stream_data_to_io_service,
+ 10,
+ NULL,
+ &error);
+ if (result < 0) {
+ g_error("Error writing to output stream: %s", error->message);
+ g_error_free(error);
+ }
+
+ if (result != 10) {
+ g_error("Wrote %d instead of 10 bytes", (int) result);
+ }
+
+ if (!g_output_stream_write_all(G_OUTPUT_STREAM(output_to_io_service),
+ stream_data_to_io_service + 10,
+ total - 10,
+ NULL, NULL, &error)) {
+ g_error("Error writing all to output stream: %s", error->message);
+ g_error_free(error);
+ }
+
+ /* flush should do nothing here, and is not needed, but
+ * just calling it to test it
+ */
+ if (!g_output_stream_flush(G_OUTPUT_STREAM(output_to_io_service), NULL, &error)) {
+ g_error("Error flushing output stream: %s", error->message);
+ g_error_free(error);
+ }
+
+ if (!g_output_stream_close(G_OUTPUT_STREAM(output_to_io_service), NULL, &error)) {
+ g_error("Error closing output stream: %s", error->message);
+ g_error_free(error);
+ }
+ g_object_unref(output_to_io_service);
+ output_to_io_service = NULL;
+
+ /* Now read from the input stream - in an inefficient way to be sure
+ * we test multiple, partial reads
+ */
+
+ read_size = 1;
+ str = g_string_new(NULL);
+
+ while (TRUE) {
+ /* test get_received() */
+ g_assert(big_dbus_input_stream_get_received(input_from_io_service) <= strlen(stream_data_from_io_service));
+
+ /* This is a blocking read... in production code, you would
+ * want to use the ready-to-read signal instead to avoid
+ * blocking when there is nothing to read.
+ */
+ result = g_input_stream_read(G_INPUT_STREAM(input_from_io_service),
+ buf,
+ read_size,
+ NULL, &error);
+ if (result < 0) {
+ g_error("Error reading %d bytes from input stream: %s",
+ (int) read_size, error->message);
+ g_error_free(error);
+ }
+
+ if (result == 0) {
+ /* EOF */
+ break;
+ }
+
+ g_string_append_len(str, buf, result);
+
+ if (read_size < sizeof(buf))
+ read_size += 1;
+ }
+
+ if (!g_input_stream_close(G_INPUT_STREAM(input_from_io_service), NULL, &error)) {
+ g_error("Error closing input stream: %s", error->message);
+ g_error_free(error);
+ }
+ g_object_unref(input_from_io_service);
+ input_from_io_service = NULL;
+
+ /* Now make the confirmStreams call
+ */
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "Confirming to com.litl.TestIO we got: '%s'", str->str);
+
+ big_dbus_proxy_call_json_async(test_io_proxy,
+ "confirmStreamsData",
+ on_confirm_streams_reply,
+ on_unexpected_error_reply,
+ "confirmStreamsData call from on_setup_streams_reply",
+ "received", DBUS_TYPE_STRING, &str->str,
+ NULL);
+
+ g_string_free(str, TRUE);
+}
+
+static void
+on_test_io_appeared(DBusConnection *connection,
+ const char *name,
+ const char *new_owner_unique_name,
+ void *data)
+{
+ const char *stream_path;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "%s appeared",
+ name);
+
+ test_io_proxy =
+ big_dbus_proxy_new(connection, new_owner_unique_name,
+ "/com/litl/test/object47",
+ "com.litl.TestIO");
+
+ input_from_io_service =
+ g_object_new(BIG_TYPE_DBUS_INPUT_STREAM, NULL);
+ big_dbus_input_stream_attach(input_from_io_service, connection);
+
+ stream_path = big_dbus_input_stream_get_path(input_from_io_service);
+
+ big_dbus_proxy_call_json_async(test_io_proxy,
+ "setupStreams",
+ on_setup_streams_reply,
+ on_unexpected_error_reply,
+ "setupStreams call from on_test_io_appeared",
+ "stream", DBUS_TYPE_STRING, &stream_path,
+ NULL);
+}
+
+static void
+on_test_io_vanished(DBusConnection *connection,
+ const char *name,
+ const char *old_owner_unique_name,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "%s vanished", name);
+
+ another_child_down();
+}
+
+static BigDBusWatchNameFuncs watch_test_io_funcs = {
+ on_test_io_appeared,
+ on_test_io_vanished
+};
+
+void
+bigtest_test_func_util_dbus_client(void)
+{
+ pid_t result;
+ int status;
+
+ /* We have to fork() to avoid creating the DBusConnection*
+ * and thus preventing other dbus-using tests from forking
+ * children. This dbus bug, when the fix makes it into Ubuntu,
+ * should solve the problem:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=15570
+ *
+ * The symptom of that bug is failure to connect to the bus in
+ * dbus-signals.c tests. The symptom of opening a connection
+ * before forking children is the connection FD shared among
+ * multiple processes, i.e. huge badness.
+ */
+ if (!g_test_trap_fork(0, 0)) {
+ /* We are the parent */
+ g_test_trap_assert_passed();
+ return;
+ }
+
+ /* All this stuff runs in the forked child only */
+
+ fork_child_test_service();
+ fork_child_test_io();
+
+ g_type_init();
+
+ g_assert(test_service_pid != 0);
+ g_assert(test_io_pid != 0);
+
+ big_dbus_watch_name(DBUS_BUS_SESSION,
+ "com.litl.TestService",
+ 0,
+ &watch_test_service_funcs,
+ NULL);
+
+ big_dbus_watch_name(DBUS_BUS_SESSION,
+ "com.litl.TestIO",
+ 0,
+ &watch_test_io_funcs,
+ NULL);
+
+ client_loop = g_main_loop_new(NULL, FALSE);
+
+ g_main_loop_run(client_loop);
+
+ if (test_service_proxy != NULL)
+ g_object_unref(test_service_proxy);
+
+ if (test_io_proxy != NULL)
+ g_object_unref(test_io_proxy);
+
+ /* child was killed already, or should have been */
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "waitpid() for first child");
+
+ result = waitpid(test_service_pid, &status, 0);
+ if (result < 0) {
+ g_error("Failed to waitpid() for forked child: %s", strerror(errno));
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+ g_error("Forked dbus service child exited with error code %d", WEXITSTATUS(status));
+ }
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
+ g_error("Forked dbus service child exited on wrong signal number %d", WTERMSIG(status));
+ }
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "waitpid() for second child");
+
+ result = waitpid(test_io_pid, &status, 0);
+ if (result < 0) {
+ g_error("Failed to waitpid() for forked child: %s", strerror(errno));
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+ g_error("Forked dbus service child exited with error code %d", WEXITSTATUS(status));
+ }
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
+ g_error("Forked dbus service child exited on wrong signal number %d", WTERMSIG(status));
+ }
+
+ big_debug(BIG_DEBUG_IN_TESTS, "dbus client test completed");
+
+ /* We want to kill dbus so the weak refs are NULL to start the
+ * next dbus-related test, which allows those tests
+ * to fork new child processes.
+ */
+ _big_dbus_dispose_info(_big_dbus_get_weak_ref(DBUS_BUS_SESSION));
+ dbus_shutdown();
+
+ big_debug(BIG_DEBUG_IN_TESTS, "dbus shut down");
+
+ /* FIXME this is here only while we need g_test_trap_fork(),
+ * see comment above.
+ */
+ exit(0);
+}
+
+/*
+ * First child service we forked, tests general dbus API
+ */
+
+static gboolean currently_have_test_service = FALSE;
+static GObject *test_service_object = NULL;
+
+static void
+test_service_get_some_stuff_sync(DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestService got getSomeStuffSync");
+
+ g_assert(G_IS_OBJECT(data));
+
+ big_dbus_append_json_entry_BOOLEAN(out_iter,
+ "haveTestService",
+ currently_have_test_service);
+}
+
+static void
+test_service_always_error_sync(DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestService got alwaysErrorSync");
+
+ g_assert(G_IS_OBJECT(data));
+
+ dbus_set_error(error, DBUS_ERROR_FILE_NOT_FOUND,
+ "Did not find some kind of file! Help!");
+}
+
+static BigDBusJsonMethod test_service_methods[] = {
+ { "getSomeStuffSync", test_service_get_some_stuff_sync, NULL },
+ { "alwaysErrorSync", test_service_always_error_sync, NULL }
+};
+
+static void
+on_test_service_acquired(DBusConnection *connection,
+ const char *name,
+ void *data)
+{
+ g_assert(!currently_have_test_service);
+ currently_have_test_service = TRUE;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestService acquired by child");
+
+ big_dbus_register_json(connection,
+ "com.litl.TestIface",
+ test_service_methods,
+ G_N_ELEMENTS(test_service_methods));
+
+ test_service_object = g_object_new(G_TYPE_OBJECT, NULL);
+
+ big_dbus_register_g_object(connection,
+ "/com/litl/test/object42",
+ test_service_object,
+ "com.litl.TestIface");
+}
+
+static void
+on_test_service_lost(DBusConnection *connection,
+ const char *name,
+ void *data)
+{
+ g_assert(currently_have_test_service);
+ currently_have_test_service = FALSE;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestService lost by child");
+
+ big_dbus_unregister_g_object(connection,
+ "/com/litl/test/object42");
+
+ big_dbus_unregister_json(connection,
+ "com.litl.TestIface");
+}
+
+static BigDBusNameOwnerFuncs test_service_funcs = {
+ "com.litl.TestService",
+ BIG_DBUS_NAME_SINGLE_INSTANCE,
+ on_test_service_acquired,
+ on_test_service_lost
+};
+
+static void
+do_test_service_child(void)
+{
+ GMainLoop *loop;
+
+ g_type_init();
+
+ loop = g_main_loop_new(NULL, FALSE);
+
+ big_dbus_acquire_name(DBUS_BUS_SESSION,
+ &test_service_funcs,
+ NULL);
+
+ g_main_loop_run(loop);
+
+ /* Don't return to the test program main() */
+ exit(0);
+}
+
+/*
+ * Second child service we forked, tests IO streams
+ */
+
+static gboolean currently_have_test_io = FALSE;
+static GObject *test_io_object = NULL;
+
+static BigDBusInputStream *io_input_stream = NULL;
+static BigDBusOutputStream *io_output_stream = NULL;
+
+static GString *input_buffer = NULL;
+
+static void
+test_io_confirm_streams_data(DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error)
+{
+ const char *received;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestIO got confirmStreamsData");
+
+ g_assert(G_IS_OBJECT(data));
+
+ received = extract_string_arg(in_iter, "received", error);
+ if (received == NULL) {
+ g_assert(error == NULL || dbus_error_is_set(error));
+ return;
+ }
+
+ if (strcmp(received, stream_data_from_io_service) != 0) {
+ g_error("We sent the main process '%s' but it says it got '%s'",
+ stream_data_from_io_service,
+ received);
+ return;
+ }
+
+ /* We were reading from the main process in the main loop.
+ * As a hack, we'll block in the main loop here to test.
+ * In a real app, never block in the main loop; you would
+ * just plain block, e.g. in g_input_stream_read(), if
+ * you wanted to block. But don't block.
+ */
+ while (io_input_stream != NULL) {
+ g_main_context_iteration(NULL, TRUE);
+ }
+
+ big_dbus_append_json_entry_STRING(out_iter,
+ "received",
+ input_buffer->str);
+
+ g_string_free(input_buffer, TRUE);
+ input_buffer = NULL;
+}
+
+static void
+on_input_ready(BigDBusInputStream *dbus_stream,
+ void *data)
+{
+ GInputStream *stream;
+ char buf[3];
+ gssize result;
+ GError *error;
+
+ stream = G_INPUT_STREAM(dbus_stream);
+
+ g_assert(dbus_stream == io_input_stream);
+
+ /* test get_received() */
+ g_assert(big_dbus_input_stream_get_received(dbus_stream) <= strlen(stream_data_to_io_service));
+
+ /* Should not block, since we got the ready-to-read signal */
+ error = NULL;
+ result = g_input_stream_read(G_INPUT_STREAM(io_input_stream),
+ buf,
+ sizeof(buf),
+ NULL,
+ &error);
+ if (result < 0) {
+ g_error("Error reading bytes from input stream: %s",
+ error->message);
+ g_error_free(error);
+ }
+
+ if (result == 0) {
+ /* EOF */
+ if (!g_input_stream_close(G_INPUT_STREAM(io_input_stream), NULL, &error)) {
+ g_error("Error closing input stream in child: %s", error->message);
+ g_error_free(error);
+ }
+ g_object_unref(io_input_stream);
+ io_input_stream = NULL;
+
+ return;
+ }
+
+ g_string_append_len(input_buffer, buf, result);
+
+ /* We should automatically get another callback if there's more data or EOF
+ * was not yet reached.
+ */
+}
+
+static void
+test_io_setup_streams(DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error)
+{
+ const char *stream_path;
+ gsize total;
+ gsize remaining;
+ gssize result;
+ GError *gerror;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestIO got setupStreams");
+
+ g_assert(G_IS_OBJECT(data));
+
+ stream_path = extract_string_arg(in_iter, "stream", error);
+
+ if (stream_path == NULL) {
+ g_assert(error == NULL || dbus_error_is_set(error));
+ return;
+ }
+
+ /* Create output stream to write to caller's path */
+ io_output_stream =
+ big_dbus_output_stream_new(connection,
+ dbus_message_get_sender(message),
+ stream_path);
+
+ /* Create input stream and return its path to caller */
+ io_input_stream =
+ g_object_new(BIG_TYPE_DBUS_INPUT_STREAM,
+ NULL);
+ big_dbus_input_stream_attach(io_input_stream,
+ connection);
+ stream_path = big_dbus_input_stream_get_path(io_input_stream);
+
+ big_dbus_append_json_entry_STRING(out_iter,
+ "stream",
+ stream_path);
+
+ /* Set up callbacks to read input stream in an async way */
+ input_buffer = g_string_new(NULL);
+
+ g_signal_connect(io_input_stream,
+ "ready-to-read",
+ G_CALLBACK(on_input_ready),
+ NULL);
+
+ /* Write to output stream */
+ gerror = NULL;
+ total = strlen(stream_data_from_io_service);
+ remaining = total;
+ while (remaining > 0) {
+ /* One byte at a time, fun torture test, totally silly in real
+ * code of course
+ */
+ result = g_output_stream_write(G_OUTPUT_STREAM(io_output_stream),
+ stream_data_from_io_service + (total - remaining),
+ 1,
+ NULL,
+ &gerror);
+ if (result < 0) {
+ g_assert(gerror != NULL);
+ g_error("Error writing to output stream: %s", gerror->message);
+ g_error_free(gerror);
+ }
+
+ if (result != 1) {
+ g_error("Wrote %d instead of 1 bytes", (int) result);
+ }
+
+ remaining -= 1;
+ }
+
+ /* flush should do nothing here, and is not needed, but
+ * just calling it to test it
+ */
+ if (!g_output_stream_flush(G_OUTPUT_STREAM(io_output_stream), NULL, &gerror)) {
+ g_assert(gerror != NULL);
+ g_error("Error flushing output stream: %s", gerror->message);
+ g_error_free(gerror);
+ }
+
+ if (!g_output_stream_close(G_OUTPUT_STREAM(io_output_stream), NULL, &gerror)) {
+ g_assert(gerror != NULL);
+ g_error("Error closing output stream: %s", gerror->message);
+ g_error_free(gerror);
+ }
+ g_object_unref(io_output_stream);
+ io_output_stream = NULL;
+
+
+ /* Now return, and wait for our input stream data to come in from
+ * the main process
+ */
+}
+
+static BigDBusJsonMethod test_io_methods[] = {
+ { "setupStreams", test_io_setup_streams, NULL },
+ { "confirmStreamsData", test_io_confirm_streams_data, NULL }
+};
+
+static void
+on_test_io_acquired(DBusConnection *connection,
+ const char *name,
+ void *data)
+{
+ g_assert(!currently_have_test_io);
+ currently_have_test_io = TRUE;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestIO acquired by child");
+
+ big_dbus_register_json(connection,
+ "com.litl.TestIO",
+ test_io_methods,
+ G_N_ELEMENTS(test_io_methods));
+
+ test_io_object = g_object_new(G_TYPE_OBJECT, NULL);
+
+ big_dbus_register_g_object(connection,
+ "/com/litl/test/object47",
+ test_io_object,
+ "com.litl.TestIO");
+}
+
+static void
+on_test_io_lost(DBusConnection *connection,
+ const char *name,
+ void *data)
+{
+ g_assert(currently_have_test_io);
+ currently_have_test_io = FALSE;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "com.litl.TestIO lost by child");
+
+ big_dbus_unregister_g_object(connection,
+ "/com/litl/test/object47");
+
+ big_dbus_unregister_json(connection,
+ "com.litl.TestIO");
+}
+
+static BigDBusNameOwnerFuncs test_io_funcs = {
+ "com.litl.TestIO",
+ BIG_DBUS_NAME_SINGLE_INSTANCE,
+ on_test_io_acquired,
+ on_test_io_lost
+};
+
+static void
+do_test_io_child(void)
+{
+ GMainLoop *loop;
+
+ g_type_init();
+
+ loop = g_main_loop_new(NULL, FALSE);
+
+ big_dbus_acquire_name(DBUS_BUS_SESSION,
+ &test_io_funcs,
+ NULL);
+
+ g_main_loop_run(loop);
+
+ /* Don't return to the test program main() */
+ exit(0);
+}
+
+#endif /* BIG_BUILD_TESTS */