summaryrefslogtreecommitdiff
path: root/modules/dbus/util
diff options
context:
space:
mode:
Diffstat (limited to 'modules/dbus/util')
-rw-r--r--modules/dbus/util/Makefile.am5
-rw-r--r--modules/dbus/util/Makefile.in453
-rw-r--r--modules/dbus/util/dbus-private.h55
-rw-r--r--modules/dbus/util/dbus-proxy.c666
-rw-r--r--modules/dbus/util/dbus-proxy.h74
-rw-r--r--modules/dbus/util/dbus-signals.c1318
-rw-r--r--modules/dbus/util/dbus.c3023
-rw-r--r--modules/dbus/util/dbus.h216
-rw-r--r--modules/dbus/util/log.h2
9 files changed, 5812 insertions, 0 deletions
diff --git a/modules/dbus/util/Makefile.am b/modules/dbus/util/Makefile.am
new file mode 100644
index 0000000..2b8beed
--- /dev/null
+++ b/modules/dbus/util/Makefile.am
@@ -0,0 +1,5 @@
+EXTRA_DIST = \
+ dbus.h \
+ dbus-private.h \
+ dbus-proxy.h \
+ log.h
diff --git a/modules/dbus/util/Makefile.in b/modules/dbus/util/Makefile.in
new file mode 100644
index 0000000..03b770a
--- /dev/null
+++ b/modules/dbus/util/Makefile.in
@@ -0,0 +1,453 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = modules/dbus/util
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_CAIRO_MODULE = @BUILD_CAIRO_MODULE@
+BUILD_CANVAS_MODULE = @BUILD_CANVAS_MODULE@
+BUILD_DBUS_MODULE = @BUILD_DBUS_MODULE@
+BUILD_DYNAMICOBJECT_MODULE = @BUILD_DYNAMICOBJECT_MODULE@
+BUILD_EXAMPLE_MODULE = @BUILD_EXAMPLE_MODULE@
+BUILD_FFI_MODULE = @BUILD_FFI_MODULE@
+BUILD_GETTEXT_MODULE = @BUILD_GETTEXT_MODULE@
+BUILD_GTKBUILDER_MODULE = @BUILD_GTKBUILDER_MODULE@
+BUILD_LIBXML_MODULE = @BUILD_LIBXML_MODULE@
+BUILD_MPFR_MODULE = @BUILD_MPFR_MODULE@
+BUILD_MULTIPROCESSING_MODULE = @BUILD_MULTIPROCESSING_MODULE@
+BUILD_OS_MODULE = @BUILD_OS_MODULE@
+BUILD_READLINE_MODULE = @BUILD_READLINE_MODULE@
+BUILD_SQLITE_MODULE = @BUILD_SQLITE_MODULE@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LDFLAGS = @CAIRO_LDFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DBUSGLIB_CFLAGS = @DBUSGLIB_CFLAGS@
+DBUSGLIB_LDFLAGS = @DBUSGLIB_LDFLAGS@
+DBUSGLIB_LIBS = @DBUSGLIB_LIBS@
+DBUS_CFLAGS = @DBUS_CFLAGS@
+DBUS_LDFLAGS = @DBUS_LDFLAGS@
+DBUS_LIBS = @DBUS_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FFI_CFLAGS = @FFI_CFLAGS@
+FFI_LDFLAGS = @FFI_LDFLAGS@
+FFI_LIBS = @FFI_LIBS@
+FGREP = @FGREP@
+GDK_CFLAGS = @GDK_CFLAGS@
+GDK_LDFLAGS = @GDK_LDFLAGS@
+GDK_LIBS = @GDK_LIBS@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GNOME_JS_CFLAGS = @GNOME_JS_CFLAGS@
+GNOME_JS_DIR = @GNOME_JS_DIR@
+GNOME_JS_LIBS = @GNOME_JS_LIBS@
+GOBJECT_INTROSPECTION_CFLAGS = @GOBJECT_INTROSPECTION_CFLAGS@
+GOBJECT_INTROSPECTION_LDFLAGS = @GOBJECT_INTROSPECTION_LDFLAGS@
+GOBJECT_INTROSPECTION_LIBS = @GOBJECT_INTROSPECTION_LIBS@
+GOBJECT_INTROSPECTION_VERSION = @GOBJECT_INTROSPECTION_VERSION@
+GREP = @GREP@
+GTHREAD_CFLAGS = @GTHREAD_CFLAGS@
+GTHREAD_LIBS = @GTHREAD_LIBS@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
+GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
+GTKDOC_MKPDF = @GTKDOC_MKPDF@
+GTKDOC_REBASE = @GTKDOC_REBASE@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LDFLAGS = @GTK_LDFLAGS@
+GTK_LIBS = @GTK_LIBS@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBXML_CFLAGS = @LIBXML_CFLAGS@
+LIBXML_LDFLAGS = @LIBXML_LDFLAGS@
+LIBXML_LIBS = @LIBXML_LIBS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+RANLIB = @RANLIB@
+SED = @SED@
+SEED_DEBUG_CFLAGS = @SEED_DEBUG_CFLAGS@
+SEED_GTK_VERSION = @SEED_GTK_VERSION@
+SEED_PROFILE_CFLAGS = @SEED_PROFILE_CFLAGS@
+SEED_PROFILE_LIBS = @SEED_PROFILE_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LDFLAGS = @SQLITE_LDFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
+WEBKIT_LDFLAGS = @WEBKIT_LDFLAGS@
+WEBKIT_LIBS = @WEBKIT_LIBS@
+WEBKIT_PC = @WEBKIT_PC@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = \
+ dbus.h \
+ dbus-private.h \
+ dbus-proxy.h \
+ log.h
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu modules/dbus/util/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu modules/dbus/util/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ distclean distclean-generic distclean-libtool distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/modules/dbus/util/dbus-private.h b/modules/dbus/util/dbus-private.h
new file mode 100644
index 0000000..9a31321
--- /dev/null
+++ b/modules/dbus/util/dbus-private.h
@@ -0,0 +1,55 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#ifndef __BIG_UTIL_DBUS_PRIVATE_H__
+#define __BIG_UTIL_DBUS_PRIVATE_H__
+
+#include <glib.h>
+#include <util/dbus.h>
+#include <util/dbus-proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ DBusBusType bus_type;
+ void *where_connection_was;
+ BigDBusProxy *driver_proxy;
+ GHashTable *json_ifaces;
+ GSList *name_ownership_monitors;
+ GHashTable *name_watches;
+
+ GSList *all_signal_watchers;
+
+ /* These signal watcher tables are maps from a
+ * string to a GSList of BigSignalWatcher,
+ * and they are lazily created if a signal watcher
+ * needs to be looked up by the given key.
+ */
+ GHashTable *signal_watchers_by_unique_sender;
+ GHashTable *signal_watchers_by_path;
+ GHashTable *signal_watchers_by_iface;
+ GHashTable *signal_watchers_by_signal;
+ /* These are matching on well-known name only,
+ * or watching all signals
+ */
+ GSList *signal_watchers_in_no_table;
+
+} BigDBusInfo;
+
+BigDBusInfo* _big_dbus_ensure_info (DBusConnection *connection);
+void _big_dbus_dispose_info (DBusConnection *connection);
+void _big_dbus_process_pending_signal_watchers (DBusConnection *connection,
+ BigDBusInfo *info);
+DBusHandlerResult _big_dbus_signal_watch_filter_message (DBusConnection *connection,
+ DBusMessage *message,
+ void *data);
+void _big_dbus_set_matching_name_owner_changed (DBusConnection *connection,
+ const char *bus_name,
+ gboolean matched);
+void _big_dbus_ensure_connect_idle (DBusBusType bus_type);
+DBusConnection* _big_dbus_get_weak_ref (DBusBusType which_bus);
+
+
+G_END_DECLS
+
+#endif /* __BIG_UTIL_DBUS_PRIVATE_H__ */
diff --git a/modules/dbus/util/dbus-proxy.c b/modules/dbus/util/dbus-proxy.c
new file mode 100644
index 0000000..a9a413c
--- /dev/null
+++ b/modules/dbus/util/dbus-proxy.c
@@ -0,0 +1,666 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus-proxy.h"
+#include "dbus.h"
+#include "log.h"
+#include <dbus/dbus-glib-lowlevel.h>
+#include <stdarg.h>
+
+typedef enum {
+ REPLY_CLOSURE_PLAIN,
+ REPLY_CLOSURE_JSON
+} ReplyClosureType;
+
+typedef struct {
+ BigDBusProxy *proxy;
+ ReplyClosureType type;
+ union {
+ BigDBusProxyReplyFunc plain;
+ BigDBusProxyJsonReplyFunc json;
+ } func;
+ BigDBusProxyErrorReplyFunc error_func;
+ void *data;
+ /* this is a debug thing; we want to guarantee
+ * we call exactly 1 time either the reply or error
+ * callback.
+ */
+ guint reply_invoked : 1;
+ guint error_invoked : 1;
+} ReplyClosure;
+
+static void big_dbus_proxy_dispose (GObject *object);
+static void big_dbus_proxy_finalize (GObject *object);
+static GObject* big_dbus_proxy_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params);
+static void big_dbus_proxy_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void big_dbus_proxy_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+struct _BigDBusProxy {
+ GObject parent;
+
+ DBusConnection *connection;
+ char *bus_name;
+ char *object_path;
+ char *iface;
+};
+
+struct _BigDBusProxyClass {
+ GObjectClass parent;
+};
+
+G_DEFINE_TYPE(BigDBusProxy, big_dbus_proxy, G_TYPE_OBJECT);
+
+#if 0
+enum {
+ LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+enum {
+ PROP_0,
+ PROP_CONNECTION,
+ PROP_BUS_NAME,
+ PROP_OBJECT_PATH,
+ PROP_INTERFACE
+};
+
+static void
+big_dbus_proxy_init(BigDBusProxy *proxy)
+{
+
+}
+
+static void
+big_dbus_proxy_class_init(BigDBusProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = big_dbus_proxy_dispose;
+ object_class->finalize = big_dbus_proxy_finalize;
+
+ object_class->constructor = big_dbus_proxy_constructor;
+ object_class->get_property = big_dbus_proxy_get_property;
+ object_class->set_property = big_dbus_proxy_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_CONNECTION,
+ g_param_spec_boxed("connection",
+ "DBusConnection",
+ "Our connection to the bus",
+ DBUS_TYPE_CONNECTION,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_BUS_NAME,
+ g_param_spec_string("bus-name",
+ "Bus Name",
+ "Name of app on the bus",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_OBJECT_PATH,
+ g_param_spec_string("object-path",
+ "Object Path",
+ "Object's dbus path",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class,
+ PROP_INTERFACE,
+ g_param_spec_string("interface",
+ "Interface",
+ "Interface to invoke methods on",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+big_dbus_proxy_dispose(GObject *object)
+{
+ BigDBusProxy *proxy;
+
+ proxy = BIG_DBUS_PROXY(object);
+
+ if (proxy->connection) {
+ dbus_connection_unref(proxy->connection);
+ proxy->connection = NULL;
+ }
+
+ if (proxy->bus_name) {
+ g_free(proxy->bus_name);
+ proxy->bus_name = NULL;
+ }
+
+ if (proxy->object_path) {
+ g_free(proxy->object_path);
+ proxy->object_path = NULL;
+ }
+
+ if (proxy->iface) {
+ g_free(proxy->iface);
+ proxy->iface = NULL;
+ }
+
+ G_OBJECT_CLASS(big_dbus_proxy_parent_class)->dispose(object);
+}
+
+static void
+big_dbus_proxy_finalize(GObject *object)
+{
+
+ G_OBJECT_CLASS(big_dbus_proxy_parent_class)->finalize(object);
+}
+
+static GObject*
+big_dbus_proxy_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ BigDBusProxy *proxy;
+
+ object = (* G_OBJECT_CLASS (big_dbus_proxy_parent_class)->constructor) (type,
+ n_construct_properties,
+ construct_params);
+
+ proxy = BIG_DBUS_PROXY(object);
+
+ return object;
+}
+
+static void
+big_dbus_proxy_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ BigDBusProxy *proxy;
+
+ proxy = BIG_DBUS_PROXY (object);
+
+ switch (prop_id) {
+ case PROP_CONNECTION:
+ g_value_set_boxed(value, proxy->connection);
+ break;
+ case PROP_BUS_NAME:
+ g_value_set_string(value, proxy->bus_name);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string(value, proxy->object_path);
+ break;
+ case PROP_INTERFACE:
+ g_value_set_string(value, proxy->iface);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+big_dbus_proxy_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ BigDBusProxy *proxy;
+
+ proxy = BIG_DBUS_PROXY (object);
+
+ switch (prop_id) {
+ case PROP_CONNECTION:
+ if (proxy->connection != NULL) {
+ g_warning("Cannot change BigDBusProxy::connection after it's set");
+ return;
+ }
+ proxy->connection = dbus_connection_ref(g_value_get_boxed(value));
+ break;
+ case PROP_BUS_NAME:
+ if (proxy->bus_name != NULL) {
+ g_warning("Cannot change BigDBusProxy::bus-name after it's set");
+ return;
+ }
+ proxy->bus_name = g_value_dup_string(value);
+ break;
+ case PROP_OBJECT_PATH:
+ if (proxy->object_path != NULL) {
+ g_warning("Cannot change BigDBusProxy::object-path after it's set");
+ return;
+ }
+ proxy->object_path = g_value_dup_string(value);
+ break;
+ case PROP_INTERFACE:
+ if (proxy->iface != NULL) {
+ g_warning("Cannot change BigDBusProxy::interface after it's set");
+ return;
+ }
+ proxy->iface = g_value_dup_string(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* bus_name can be NULL if not going through a bus, and
+ * iface is allowed to be NULL but likely should not be.
+ */
+BigDBusProxy*
+big_dbus_proxy_new(DBusConnection *connection,
+ const char *bus_name,
+ const char *object_path,
+ const char *iface)
+{
+ BigDBusProxy *proxy;
+
+ g_return_val_if_fail(connection != NULL, NULL);
+ g_return_val_if_fail(object_path != NULL, NULL);
+
+ proxy = g_object_new(BIG_TYPE_DBUS_PROXY,
+ "connection", connection,
+ "bus-name", bus_name,
+ "object-path", object_path,
+ "interface", iface,
+ NULL);
+
+ return proxy;
+}
+
+DBusConnection*
+big_dbus_proxy_get_connection(BigDBusProxy *proxy)
+{
+ return proxy->connection;
+}
+
+const char*
+big_dbus_proxy_get_bus_name(BigDBusProxy *proxy)
+{
+ return proxy->bus_name;
+}
+
+DBusMessage*
+big_dbus_proxy_new_method_call(BigDBusProxy *proxy,
+ const char *method_name)
+{
+ DBusMessage *message;
+
+ message = dbus_message_new_method_call(proxy->bus_name,
+ proxy->object_path,
+ proxy->iface,
+ method_name);
+ if (message == NULL)
+ g_error("no memory");
+
+ /* We don't want methods to auto-start services... if a service
+ * needs starting or restarting, we want to do so explicitly so we
+ * can do it in an orderly and predictable way.
+ */
+ dbus_message_set_auto_start(message, FALSE);
+
+ return message;
+}
+
+DBusMessage*
+big_dbus_proxy_new_json_call(BigDBusProxy *proxy,
+ const char *method_name,
+ DBusMessageIter *arg_iter,
+ DBusMessageIter *dict_iter)
+{
+ DBusMessage *message;
+
+ message = big_dbus_proxy_new_method_call(proxy, method_name);
+
+ dbus_message_iter_init_append(message, arg_iter);
+ dbus_message_iter_open_container(arg_iter, DBUS_TYPE_ARRAY, "{sv}", dict_iter);
+
+ return message;
+}
+
+static ReplyClosure*
+reply_closure_new(BigDBusProxy *proxy,
+ BigDBusProxyReplyFunc plain_func,
+ BigDBusProxyJsonReplyFunc json_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data)
+{
+ ReplyClosure *c;
+
+ c = g_slice_new0(ReplyClosure);
+
+ c->proxy = g_object_ref(proxy);
+
+ g_assert(!(plain_func && json_func));
+
+ if (plain_func != NULL) {
+ c->type = REPLY_CLOSURE_PLAIN;
+ c->func.plain = plain_func;
+ } else {
+ c->type = REPLY_CLOSURE_JSON;
+ c->func.json = json_func;
+ }
+
+ c->error_func = error_func;
+ c->data = data;
+
+ return c;
+}
+
+static void
+reply_closure_free(ReplyClosure *c)
+{
+ /* call exactly one of these */
+ g_assert(!(c->error_invoked &&
+ c->reply_invoked));
+
+ if (!(c->error_invoked ||
+ c->reply_invoked)) {
+ c->error_invoked = TRUE;
+ if (c->error_func) {
+ (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+ "Pending call was freed (due to dbus_shutdown() probably) before it was ever notified",
+ c->data);
+ }
+ }
+
+ g_object_unref(c->proxy);
+ g_slice_free(ReplyClosure, c);
+}
+
+static void
+reply_closure_invoke_error(ReplyClosure *c,
+ DBusMessage *reply)
+{
+ g_assert(dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR);
+
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ DBusError derror;
+
+ dbus_error_init(&derror);
+
+ dbus_set_error_from_message(&derror, reply);
+
+ (* c->error_func) (c->proxy, derror.name,
+ derror.message,
+ c->data);
+
+ dbus_error_free(&derror);
+ }
+}
+
+static void
+reply_closure_invoke(ReplyClosure *c,
+ DBusMessage *reply)
+{
+ if (c->type == REPLY_CLOSURE_PLAIN) {
+ if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->reply_invoked = TRUE;
+
+ if (c->func.plain != NULL) {
+ (* c->func.plain) (c->proxy,
+ reply,
+ c->data);
+ }
+ } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+ reply_closure_invoke_error(c, reply);
+ } else {
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+ "Got weird message type back as a reply",
+ c->data);
+ }
+ }
+ } else if (c->type == REPLY_CLOSURE_JSON) {
+ if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ if (dbus_message_has_signature(reply, "a{sv}")) {
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->reply_invoked = TRUE;
+
+ if (c->func.json) {
+ DBusMessageIter arg_iter;
+ DBusMessageIter dict_iter;
+
+ dbus_message_iter_init(reply, &arg_iter);
+ dbus_message_iter_recurse(&arg_iter, &dict_iter);
+
+ (* c->func.json) (c->proxy,
+ reply,
+ &dict_iter,
+ c->data);
+ }
+ } else {
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ (* c->error_func) (c->proxy,
+ DBUS_ERROR_FAILED,
+ "Message we got back did not have the right signature",
+ c->data);
+ }
+ }
+ } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+ reply_closure_invoke_error(c, reply);
+ } else {
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+ "Got weird message type back as a reply",
+ c->data);
+ }
+ }
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+
+static gboolean
+failed_to_send_idle(void *data)
+{
+ ReplyClosure *c;
+
+ c = data;
+
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ (* c->error_func) (c->proxy,
+ DBUS_ERROR_NO_MEMORY,
+ "Unable to send method call",
+ c->data);
+ }
+
+ reply_closure_free(c);
+
+ return FALSE;
+}
+
+
+static void
+pending_call_notify(DBusPendingCall *pending,
+ void *user_data)
+{
+ DBusMessage *reply;
+ ReplyClosure *c;
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "BigDBusProxy received reply to pending call");
+
+ c = user_data;
+
+ /* reply may be NULL if none received? I think it may never be if
+ * we've already been notified, but be safe here.
+ */
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (reply) {
+ reply_closure_invoke(c, reply);
+
+ dbus_message_unref(reply);
+ } else {
+ /* I think libdbus won't let this happen, but to be safe... */
+ g_assert(!c->reply_invoked);
+ g_assert(!c->error_invoked);
+
+ c->error_invoked = TRUE;
+
+ if (c->error_func) {
+ (* c->error_func) (c->proxy,
+ DBUS_ERROR_TIMED_OUT,
+ "Did not receive a reply or error",
+ c->data);
+ }
+ }
+
+ /* The closure should be freed along with the pending call */
+}
+
+static void
+pending_call_free_data(void *data)
+{
+ ReplyClosure *c = data;
+ reply_closure_free(c);
+}
+
+static void
+big_dbus_proxy_send_internal(BigDBusProxy *proxy,
+ DBusMessage *message,
+ BigDBusProxyReplyFunc plain_func,
+ BigDBusProxyJsonReplyFunc json_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data)
+{
+ ReplyClosure *c;
+ DBusPendingCall *pending;
+
+ if (!(plain_func || json_func || error_func)) {
+ /* Fire and forget! */
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Firing and forgetting dbus proxy call");
+
+ dbus_connection_send(proxy->connection, message, NULL);
+ return;
+ }
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Sending dbus proxy call %s",
+ dbus_message_get_member(message));
+
+ c = reply_closure_new(proxy, plain_func, json_func, error_func, data);
+ pending = NULL;
+ if (!dbus_connection_send_with_reply(proxy->connection, message, &pending, -1) ||
+ pending == NULL) {
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Failed to send call, will report error in idle handler");
+
+ /* Send an error on return to main loop */
+ g_idle_add(failed_to_send_idle, c);
+ return;
+ }
+
+ dbus_pending_call_set_notify(pending, pending_call_notify, c,
+ pending_call_free_data);
+
+ dbus_pending_call_unref(pending); /* DBusConnection should still hold a ref until it's completed */
+}
+
+void
+big_dbus_proxy_send(BigDBusProxy *proxy,
+ DBusMessage *message,
+ BigDBusProxyReplyFunc reply_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data)
+{
+ big_dbus_proxy_send_internal(proxy, message, reply_func, NULL, error_func, data);
+}
+
+static void
+append_entries_from_valist(DBusMessageIter *dict_iter,
+ const char *first_key,
+ va_list args)
+{
+ const char *key;
+ int dbus_type;
+ void *value_p;
+
+ key = first_key;
+ dbus_type = va_arg(args, int);
+ value_p = va_arg(args, void*);
+
+ big_dbus_append_json_entry(dict_iter, key, dbus_type, value_p);
+
+ key = va_arg(args, const char*);
+ while (key != NULL) {
+ dbus_type = va_arg(args, int);
+ value_p = va_arg(args, void*);
+
+ big_dbus_append_json_entry(dict_iter, key, dbus_type, value_p);
+
+ key = va_arg(args, const char*);
+ }
+}
+
+void
+big_dbus_proxy_call_json_async (BigDBusProxy *proxy,
+ const char *method_name,
+ BigDBusProxyJsonReplyFunc reply_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data,
+ const char *first_key,
+ ...)
+{
+ DBusMessageIter arg_iter, dict_iter;
+ DBusMessage *message;
+ va_list args;
+
+ message = big_dbus_proxy_new_json_call(proxy, method_name, &arg_iter, &dict_iter);
+
+ if (first_key != NULL) {
+ va_start(args, first_key);
+ append_entries_from_valist(&dict_iter, first_key, args);
+ va_end(args);
+ }
+
+ dbus_message_iter_close_container(&arg_iter, &dict_iter);
+
+ big_dbus_proxy_send_internal(proxy, message, NULL, reply_func, error_func, data);
+
+ dbus_message_unref(message);
+}
diff --git a/modules/dbus/util/dbus-proxy.h b/modules/dbus/util/dbus-proxy.h
new file mode 100644
index 0000000..6b793e6
--- /dev/null
+++ b/modules/dbus/util/dbus-proxy.h
@@ -0,0 +1,74 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#ifndef __BIG_UTIL_DBUS_PROXY_H__
+#define __BIG_UTIL_DBUS_PROXY_H__
+
+#include <gio/gio.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+
+typedef struct _BigDBusProxy BigDBusProxy;
+typedef struct _BigDBusProxyClass BigDBusProxyClass;
+
+typedef void (* BigDBusProxyReplyFunc) (BigDBusProxy *proxy,
+ DBusMessage *message,
+ void *data);
+typedef void (* BigDBusProxyJsonReplyFunc) (BigDBusProxy *proxy,
+ DBusMessage *message,
+ DBusMessageIter *return_value_iter,
+ void *data);
+typedef void (* BigDBusProxyErrorReplyFunc) (BigDBusProxy *proxy,
+ const char *error_name,
+ const char *error_message,
+ void *data);
+
+#define BIG_TYPE_DBUS_PROXY (big_dbus_proxy_get_type ())
+#define BIG_DBUS_PROXY(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), BIG_TYPE_DBUS_PROXY, BigDBusProxy))
+#define BIG_DBUS_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BIG_TYPE_DBUS_PROXY, BigDBusProxyClass))
+#define BIG_IS_DBUS_PROXY(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), BIG_TYPE_DBUS_PROXY))
+#define BIG_IS_DBUS_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BIG_TYPE_DBUS_PROXY))
+#define BIG_DBUS_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BIG_TYPE_DBUS_PROXY, BigDBusProxyClass))
+
+GType big_dbus_proxy_get_type (void) G_GNUC_CONST;
+
+
+BigDBusProxy* big_dbus_proxy_new (DBusConnection *connection,
+ const char *bus_name,
+ const char *object_path,
+ const char *iface);
+DBusConnection* big_dbus_proxy_get_connection (BigDBusProxy *proxy);
+const char* big_dbus_proxy_get_bus_name (BigDBusProxy *proxy);
+DBusMessage* big_dbus_proxy_new_method_call (BigDBusProxy *proxy,
+ const char *method_name);
+DBusMessage* big_dbus_proxy_new_json_call (BigDBusProxy *proxy,
+ const char *method_name,
+ DBusMessageIter *arg_iter,
+ DBusMessageIter *dict_iter);
+void big_dbus_proxy_send (BigDBusProxy *proxy,
+ DBusMessage *message,
+ BigDBusProxyReplyFunc reply_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data);
+
+/* varargs are like:
+ *
+ * key1, dbus_type_1, &value_1,
+ * key2, dbus_type_2, &value_2,
+ * NULL
+ *
+ * Basic types only (no arrays)
+ */
+void big_dbus_proxy_call_json_async (BigDBusProxy *proxy,
+ const char *method_name,
+ BigDBusProxyJsonReplyFunc reply_func,
+ BigDBusProxyErrorReplyFunc error_func,
+ void *data,
+ const char *first_key,
+ ...);
+
+G_END_DECLS
+
+#endif /* __BIG_UTIL_DBUS_PROXY_H__ */
diff --git a/modules/dbus/util/dbus-signals.c b/modules/dbus/util/dbus-signals.c
new file mode 100644
index 0000000..f2e5ba2
--- /dev/null
+++ b/modules/dbus/util/dbus-signals.c
@@ -0,0 +1,1318 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus-private.h"
+#include "log.h"
+
+#include <string.h>
+
+#define INVALID_SIGNAL_ID (-1)
+
+typedef struct {
+ DBusBusType bus_type;
+ int refcount;
+ char *sender;
+ char *path;
+ char *iface;
+ char *name;
+ BigDBusSignalHandler handler;
+ void *data;
+ GDestroyNotify data_dnotify;
+ int id;
+ unsigned int matching : 1;
+ unsigned int destroyed : 1;
+} BigSignalWatcher;
+
+static GSList *pending_signal_watchers = NULL;
+
+static void signal_watcher_remove (DBusConnection *connection,
+ BigDBusInfo *info,
+ BigSignalWatcher *watcher);
+
+
+static int global_handler_id = 0;
+
+static BigSignalWatcher*
+signal_watcher_new(DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ BigDBusSignalHandler handler,
+ void *data,
+ GDestroyNotify data_dnotify)
+{
+ BigSignalWatcher *watcher;
+
+ watcher = g_slice_new0(BigSignalWatcher);
+
+ watcher->refcount = 1;
+
+ watcher->bus_type = bus_type;
+ watcher->sender = g_strdup(sender);
+ watcher->path = g_strdup(path);
+ watcher->iface = g_strdup(iface);
+ watcher->name = g_strdup(name);
+ watcher->handler = handler;
+ watcher->id = global_handler_id++;
+ watcher->data = data;
+ watcher->data_dnotify = data_dnotify;
+
+ return watcher;
+}
+
+static void
+signal_watcher_dnotify(BigSignalWatcher *watcher)
+{
+ if (watcher->data_dnotify != NULL) {
+ (* watcher->data_dnotify) (watcher->data);
+ watcher->data_dnotify = NULL;
+ }
+ watcher->destroyed = TRUE;
+}
+
+static void
+signal_watcher_ref(BigSignalWatcher *watcher)
+{
+ watcher->refcount += 1;
+}
+
+static void
+signal_watcher_unref(BigSignalWatcher *watcher)
+{
+ watcher->refcount -= 1;
+
+ if (watcher->refcount == 0) {
+ signal_watcher_dnotify(watcher);
+
+ g_free(watcher->sender);
+ g_free(watcher->path);
+ g_free(watcher->iface);
+ g_free(watcher->name);
+
+ g_slice_free(BigSignalWatcher, watcher);
+ }
+}
+
+static char*
+signal_watcher_build_match_rule(BigSignalWatcher *watcher)
+{
+ GString *s;
+
+ s = g_string_new("type='signal'");
+
+ if (watcher->sender) {
+ g_string_append_printf(s, ",sender='%s'", watcher->sender);
+ }
+
+ if (watcher->path) {
+ g_string_append_printf(s, ",path='%s'", watcher->path);
+ }
+
+ if (watcher->iface) {
+ g_string_append_printf(s, ",interface='%s'", watcher->iface);
+ }
+
+ if (watcher->name) {
+ g_string_append_printf(s, ",member='%s'", watcher->name);
+ }
+
+ return g_string_free(s, FALSE);
+}
+
+
+static GSList*
+signal_watcher_table_lookup(GHashTable *table,
+ const char *key)
+{
+ if (table == NULL) {
+ return NULL;
+ }
+
+ return g_hash_table_lookup(table, key);
+}
+
+static void
+signal_watcher_list_free(void *data)
+{
+ GSList *l = data;
+ while (l != NULL) {
+ GSList *next = l->next;
+ signal_watcher_unref(l->data);
+ g_slist_free_1(l);
+ l = next;
+ }
+}
+
+static void
+signal_watcher_table_add(GHashTable **table_p,
+ const char *key,
+ BigSignalWatcher *watcher)
+{
+ GSList *list;
+ char *original_key;
+
+ if (*table_p == NULL) {
+ list = NULL;
+ original_key = g_strdup(key);
+ *table_p = g_hash_table_new_full(g_str_hash,
+ g_str_equal,
+ g_free,
+ signal_watcher_list_free);
+ } else {
+ if (!g_hash_table_lookup_extended(*table_p,
+ key,
+ (gpointer*)&original_key,
+ (gpointer*)&list)) {
+ original_key = g_strdup(key);
+ list = NULL;
+ }
+ }
+
+ list = g_slist_prepend(list, watcher);
+ signal_watcher_ref(watcher);
+
+ g_hash_table_steal(*table_p, key);
+ g_hash_table_insert(*table_p, original_key, list);
+}
+
+static void
+signal_watcher_table_remove(GHashTable *table,
+ const char *key,
+ BigSignalWatcher *watcher)
+{
+ GSList *list;
+ GSList *l;
+ char *original_key;
+
+ if (table == NULL)
+ return; /* Never lazily-created the table, nothing ever added */
+
+ if (!g_hash_table_lookup_extended(table,
+ key,
+ (gpointer*)&original_key,
+ (gpointer*)&list)) {
+ return;
+ }
+
+ l = g_slist_find(list, watcher);
+ if (!l)
+ return; /* we don't want to unref if we weren't in this table */
+
+ list = g_slist_delete_link(list, l);
+
+ g_hash_table_steal(table, key);
+ if (list != NULL) {
+ g_hash_table_insert(table, original_key, list);
+ } else {
+ g_free(original_key);
+ }
+
+ signal_watcher_unref(watcher);
+}
+
+static void
+signal_emitter_name_appeared(DBusConnection *connection,
+ const char *name,
+ const char *new_owner_unique_name,
+ void *data)
+{
+ /* We don't need to do anything here, we installed a name watch so
+ * we could call big_dbus_get_watched_name_owner() to dispatch
+ * signals, and to get destroy notification on unique names.
+ */
+}
+
+static void
+signal_emitter_name_vanished(DBusConnection *connection,
+ const char *name,
+ const char *old_owner_unique_name,
+ void *data)
+{
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Signal emitter '%s' is now gone",
+ name);
+
+ /* If a watcher is matching on a unique name sender, once the unique
+ * name goes away, the watcher can never see anything so nuke it.
+ */
+ if (*name == ':') {
+ GSList *list;
+ BigDBusInfo *info;
+
+ info = _big_dbus_ensure_info(connection);
+
+ list = signal_watcher_table_lookup(info->signal_watchers_by_unique_sender,
+ name);
+
+ if (list == NULL)
+ return;
+
+ /* copy the list since we're about to remove stuff from it
+ * in signal_watcher_remove
+ */
+ list = g_slist_copy(list);
+ while (list != NULL) {
+ signal_watcher_remove(connection, info, list->data);
+ list = g_slist_delete_link(list, list);
+ }
+ }
+}
+
+static BigDBusWatchNameFuncs signal_emitter_name_funcs = {
+ signal_emitter_name_appeared,
+ signal_emitter_name_vanished
+};
+
+static void
+signal_watcher_set_matching(DBusConnection *connection,
+ BigSignalWatcher *watcher,
+ gboolean matching)
+{
+ char *rule;
+
+ if (watcher->matching == (matching != FALSE)) {
+ return;
+ }
+
+ /* Never add match on a destroyed signal watcher */
+ if (watcher->destroyed && matching)
+ return;
+
+ /* We can't affect match rules if not connected */
+ if (!dbus_connection_get_is_connected(connection)) {
+ return;
+ }
+
+ watcher->matching = matching != FALSE;
+
+ rule = signal_watcher_build_match_rule(watcher);
+
+ if (matching)
+ dbus_bus_add_match(connection,
+ rule, NULL); /* asking for error would make this block */
+ else
+ dbus_bus_remove_match(connection, rule, NULL);
+
+ g_free(rule);
+
+ if (watcher->sender) {
+ /* If the signal is from a well-known name, we have to add
+ * a name watch to know who owns that name.
+ *
+ * If the signal is from a unique name, we want to destroy
+ * the watcher if the unique name goes away
+ */
+ if (matching) {
+ big_dbus_watch_name(watcher->bus_type,
+ watcher->sender,
+ 0,
+ &signal_emitter_name_funcs,
+ NULL);
+ } else {
+ big_dbus_unwatch_name(watcher->bus_type,
+ watcher->sender,
+ &signal_emitter_name_funcs,
+ NULL);
+ }
+ }
+}
+
+static void
+signal_watcher_add(DBusConnection *connection,
+ BigDBusInfo *info,
+ BigSignalWatcher *watcher)
+{
+ gboolean in_some_table;
+
+ signal_watcher_set_matching(connection, watcher, TRUE);
+
+ info->all_signal_watchers = g_slist_prepend(info->all_signal_watchers, watcher);
+ signal_watcher_ref(watcher);
+
+ in_some_table = FALSE;
+
+ if (watcher->sender && *(watcher->sender) == ':') {
+ signal_watcher_table_add(&info->signal_watchers_by_unique_sender,
+ watcher->sender,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->path) {
+ signal_watcher_table_add(&info->signal_watchers_by_path,
+ watcher->path,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->iface) {
+ signal_watcher_table_add(&info->signal_watchers_by_iface,
+ watcher->iface,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->name) {
+ signal_watcher_table_add(&info->signal_watchers_by_signal,
+ watcher->name,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (!in_some_table) {
+ info->signal_watchers_in_no_table =
+ g_slist_prepend(info->signal_watchers_in_no_table,
+ watcher);
+ signal_watcher_ref(watcher);
+ }
+}
+
+static void
+signal_watcher_remove(DBusConnection *connection,
+ BigDBusInfo *info,
+ BigSignalWatcher *watcher)
+{
+ gboolean in_some_table;
+
+ signal_watcher_set_matching(connection, watcher, FALSE);
+
+ info->all_signal_watchers = g_slist_remove(info->all_signal_watchers, watcher);
+
+ in_some_table = FALSE;
+
+ if (watcher->sender && *(watcher->sender) == ':') {
+ signal_watcher_table_remove(info->signal_watchers_by_unique_sender,
+ watcher->sender,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->path) {
+ signal_watcher_table_remove(info->signal_watchers_by_path,
+ watcher->path,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->iface) {
+ signal_watcher_table_remove(info->signal_watchers_by_iface,
+ watcher->iface,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (watcher->name) {
+ signal_watcher_table_remove(info->signal_watchers_by_signal,
+ watcher->name,
+ watcher);
+ in_some_table = TRUE;
+ }
+
+ if (!in_some_table) {
+ info->signal_watchers_in_no_table =
+ g_slist_remove(info->signal_watchers_in_no_table,
+ watcher);
+ signal_watcher_unref(watcher);
+ }
+
+ /* Destroy-notify before dropping last ref for a little more safety
+ * (avoids "resurrection" issues), and to ensure we call the destroy
+ * notifier even if we don't finish finalizing just yet.
+ */
+ signal_watcher_dnotify(watcher);
+
+ signal_watcher_unref(watcher);
+}
+
+/* This is called before we notify the app that the connection is open,
+ * to add match rules. It must add the match rules, but MUST NOT
+ * invoke application callbacks since the "connection opened"
+ * callback needs to be first.
+ */
+void
+_big_dbus_process_pending_signal_watchers(DBusConnection *connection,
+ BigDBusInfo *info)
+{
+ GSList *remaining;
+
+ remaining = NULL;
+ while (pending_signal_watchers) {
+ BigSignalWatcher *watcher = pending_signal_watchers->data;
+ pending_signal_watchers = g_slist_delete_link(pending_signal_watchers,
+ pending_signal_watchers);
+
+ if (watcher->bus_type == info->bus_type) {
+ /* Transfer to the non-pending BigDBusInfo */
+ signal_watcher_add(connection, info, watcher);
+ signal_watcher_unref(watcher);
+ } else {
+ remaining = g_slist_prepend(remaining, watcher);
+ }
+ }
+
+ /* keep the order deterministic by reversing, though I don't know
+ * of a reason it matters.
+ */
+ pending_signal_watchers = g_slist_reverse(remaining);
+}
+
+static void
+signal_watchers_disconnected(DBusConnection *connection,
+ BigDBusInfo *info)
+{
+ /* None should be pending on this bus, because at start of
+ * _big_dbus_signal_watch_filter_message() we process all the pending ones.
+ * However there could be stuff in pending_signal_watchers for
+ * another bus. Anyway bottom line we can ignore pending_signal_watchers
+ * in here.
+ */
+ GSList *list;
+ GSList *destroyed;
+
+ /* Build a separate list to destroy to avoid re-entrancy as we are
+ * walking the list
+ */
+ destroyed = NULL;
+ for (list = info->all_signal_watchers;
+ list != NULL;
+ list = list->next) {
+ BigSignalWatcher *watcher = list->data;
+ if (watcher->sender && *(watcher->sender) == ':') {
+ destroyed = g_slist_prepend(destroyed,
+ watcher);
+ signal_watcher_ref(watcher);
+ }
+ }
+
+ while (destroyed != NULL) {
+ BigSignalWatcher *watcher = destroyed->data;
+ destroyed = g_slist_delete_link(destroyed, destroyed);
+
+ signal_watcher_remove(connection, info, watcher);
+ signal_watcher_unref(watcher);
+ }
+}
+
+static void
+concat_candidates(GSList **candidates_p,
+ GHashTable *table,
+ const char *key)
+{
+ GSList *list;
+
+ list = signal_watcher_table_lookup(table, key);
+ if (list == NULL)
+ return;
+
+ *candidates_p = g_slist_concat(*candidates_p,
+ g_slist_copy(list));
+}
+
+static int
+direct_cmp(gconstpointer a,
+ gconstpointer b)
+{
+ /* gcc dislikes pointer math on void* so cast */
+ return ((const char*)a) - ((const char*)b);
+}
+
+static gboolean
+signal_watcher_watches(BigDBusInfo *info,
+ BigSignalWatcher *watcher,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name)
+{
+ if (watcher->path &&
+ strcmp(watcher->path, path) != 0)
+ return FALSE;
+
+ if (watcher->iface &&
+ strcmp(watcher->iface, iface) != 0)
+ return FALSE;
+
+ if (watcher->name &&
+ strcmp(watcher->name, name) != 0)
+ return FALSE;
+
+ /* "sender" from message is always the unique name, but
+ * watcher may or may not be.
+ */
+
+ if (watcher->sender == NULL)
+ return TRUE;
+
+
+ if (* (watcher->sender) == ':') {
+ return strcmp(watcher->sender, sender) == 0;
+ } else {
+ const char *owner;
+
+ owner = big_dbus_get_watched_name_owner(info->bus_type,
+ watcher->sender);
+
+ if (owner != NULL &&
+ strcmp(sender, owner) == 0)
+ return TRUE;
+ else
+ return FALSE;
+ }
+}
+
+DBusHandlerResult
+_big_dbus_signal_watch_filter_message(DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ /* Two things we're looking for
+ * 1) signals
+ * 2) if the sender of a signal watcher is a unique name,
+ * we want to destroy notify when it vanishes or
+ * when the bus disconnects.
+ */
+ BigDBusInfo *info;
+ const char *sender;
+ const char *path;
+ const char *iface;
+ const char *name;
+ GSList *candidates;
+ BigSignalWatcher *previous;
+
+ info = _big_dbus_ensure_info(connection);
+
+ /* Be sure they are all in the lookup tables */
+ _big_dbus_process_pending_signal_watchers(connection, info);
+
+ if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ sender = dbus_message_get_sender(message);
+ path = dbus_message_get_path(message);
+ iface = dbus_message_get_interface(message);
+ name = dbus_message_get_member(message);
+
+ /* libdbus requires path, iface, name. The bus daemon
+ * will always set a sender but some locally-generated
+ * messages (e.g. disconnected) may not have one.
+ */
+ g_assert(path != NULL);
+ g_assert(iface != NULL);
+ g_assert(name != NULL);
+
+ big_debug(BIG_DEBUG_UTIL_DBUS,
+ "Signal from %s %s.%s sender %s",
+ path, iface, name, sender ? sender : "(none)");
+
+ candidates = NULL;
+
+ if (sender != NULL) {
+ concat_candidates(&candidates,
+ info->signal_watchers_by_unique_sender,
+ sender);
+ }
+ concat_candidates(&candidates,
+ info->signal_watchers_by_path,
+ path);
+ concat_candidates(&candidates,
+ info->signal_watchers_by_iface,
+ iface);
+ concat_candidates(&candidates,
+ info->signal_watchers_by_signal,
+ name);
+ candidates = g_slist_concat(candidates,
+ g_slist_copy(info->signal_watchers_in_no_table));
+
+ /* Sort so we can find dups */
+ candidates = g_slist_sort(candidates, direct_cmp);
+
+ previous = NULL;
+ while (candidates != NULL) {
+ BigSignalWatcher *watcher;
+
+ watcher = candidates->data;
+ candidates = g_slist_delete_link(candidates, candidates);
+
+ if (previous == watcher)
+ continue; /* watcher was in more than one table */
+
+ previous = watcher;
+
+ if (!signal_watcher_watches(info,
+ watcher,
+ sender, path, iface, name))
+ continue;
+
+ /* destroyed would happen if e.g. removed while we are going
+ * through here.
+ */
+ if (watcher->destroyed)
+ continue;
+
+ /* Invoke the watcher */
+
+ signal_watcher_ref(watcher);
+
+ (* watcher->handler) (connection,
+ message,
+ watcher->data);
+
+ signal_watcher_unref(watcher);
+ }
+
+ /* Note that signal watchers can also listen to the disconnected
+ * signal, so we do our special handling of it last
+ */
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+ big_debug(BIG_DEBUG_UTIL_DBUS, "Disconnected in %s", G_STRFUNC);
+
+ signal_watchers_disconnected(connection, info);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int
+big_dbus_watch_signal(DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ BigDBusSignalHandler handler,
+ void *data,
+ GDestroyNotify data_dnotify)
+{
+ BigSignalWatcher *watcher;
+ DBusConnection *weak;
+
+ watcher = signal_watcher_new(bus_type, sender, path,
+ iface, name, handler,
+ data, data_dnotify);
+
+ /* If we're already connected, it's essential to get the
+ * match rule added right away. Otherwise the race-free pattern
+ * is not possible:
+ * 1. Add match rule to monitor state of remote object
+ * 2. Get current state of remote object
+ *
+ * Using the pending_signal_watchers codepath, there's no
+ * notification when the match rule is added so you can't
+ * be sure you get current state *after* that.
+ *
+ * Since we add our match rule here immediately if connected,
+ * then apps can rely on first watching the signal, then
+ * getting current state.
+ *
+ * In the connect idle, we process pending signal watchers
+ * before calling any other app callbacks, so if someone
+ * gets current state on connect, that will be after
+ * all their match rules are added.
+ */
+ weak = _big_dbus_get_weak_ref(bus_type);
+ if (weak != NULL) {
+ signal_watcher_add(weak, _big_dbus_ensure_info(weak), watcher);
+ signal_watcher_unref(watcher);
+ } else {
+ pending_signal_watchers = g_slist_prepend(pending_signal_watchers, watcher);
+ _big_dbus_ensure_connect_idle(bus_type);
+ }
+
+ return watcher->id;
+}
+
+/* Does the watcher match a removal request? */
+static gboolean
+signal_watcher_matches(BigSignalWatcher *watcher,
+ DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ int id,
+ BigDBusSignalHandler handler,
+ void *data)
+{
+ /* If we have an ID, check that first. If it matches, we are
+ * done
+ */
+ if (id != INVALID_SIGNAL_ID && watcher->id == id)
+ return TRUE;
+
+ /* Start with data, most likely thing to not match */
+ if (watcher->data != data)
+ return FALSE;
+
+ /* Second most likely non-match */
+ if (watcher->handler != handler)
+ return FALSE;
+
+ /* Then third, do the more expensive checks */
+
+ if (watcher->bus_type != bus_type)
+ return FALSE;
+
+ if (g_strcmp0(watcher->sender, sender) != 0)
+ return FALSE;
+
+ if (g_strcmp0(watcher->path, path) != 0)
+ return FALSE;
+
+ if (g_strcmp0(watcher->iface, iface) != 0)
+ return FALSE;
+
+ if (g_strcmp0(watcher->name, name) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+unwatch_signal(DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ int id,
+ BigDBusSignalHandler handler,
+ void *data)
+{
+ GSList *list;
+ DBusConnection *weak;
+ BigDBusInfo *info;
+
+ /* Always remove only ONE watcher (the first one we find) */
+
+ weak = _big_dbus_get_weak_ref(bus_type);
+
+ /* First see if it's still pending */
+ for (list = pending_signal_watchers;
+ list != NULL;
+ list = list->next) {
+ if (signal_watcher_matches(list->data,
+ bus_type,
+ sender,
+ path,
+ iface,
+ name,
+ id,
+ handler,
+ data)) {
+ BigSignalWatcher *watcher = list->data;
+ pending_signal_watchers = g_slist_remove_link(pending_signal_watchers,
+ list);
+
+ if (weak != NULL)
+ signal_watcher_set_matching(weak, watcher, FALSE);
+
+ signal_watcher_dnotify(watcher); /* destroy even if we don't finalize */
+ signal_watcher_unref(watcher);
+ return;
+ }
+ }
+
+ /* If not pending, and no bus connection, it can't exist */
+ if (weak == NULL) {
+ /* don't warn on nonexistent, since a vanishing bus name could
+ * have nuked it outside the app's control.
+ */
+ return;
+ }
+
+ info = _big_dbus_ensure_info(weak);
+
+ for (list = info->all_signal_watchers;
+ list != NULL;
+ list = list->next) {
+ if (signal_watcher_matches(list->data,
+ bus_type,
+ sender,
+ path,
+ iface,
+ name,
+ id,
+ handler,
+ data)) {
+ signal_watcher_remove(weak, info, list->data);
+ /* note that "list" node is now invalid */
+ return;
+ }
+ }
+
+ /* don't warn on nonexistent, since a vanishing bus name could
+ * have nuked it outside the app's control. Just do nothing.
+ */
+}
+
+void
+big_dbus_unwatch_signal(DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ BigDBusSignalHandler handler,
+ void *data)
+{
+ unwatch_signal(bus_type,
+ sender,
+ path,
+ iface,
+ name,
+ INVALID_SIGNAL_ID,
+ handler,
+ data);
+}
+
+void
+big_dbus_unwatch_signal_by_id(DBusBusType bus_type,
+ int id)
+{
+ unwatch_signal(bus_type,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ id,
+ (BigDBusSignalHandler)NULL,
+ NULL);
+}
+
+#if BIG_BUILD_TESTS
+
+#include "dbus-proxy.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 GMainLoop *outer_loop = NULL;
+static GMainLoop *inner_loop = NULL;
+
+static int n_running_children = 0;
+
+typedef struct {
+ const char *sender;
+ const char *path;
+ const char *iface;
+ const char *member;
+} SignalWatchTest;
+
+static SignalWatchTest watch_tests[] = {
+ { NULL, NULL, NULL, NULL },
+ { "com.litl.TestService", NULL, NULL, NULL },
+ { NULL, "/com/litl/test/object42", NULL, NULL },
+ { NULL, NULL, "com.litl.TestIface", NULL },
+ { NULL, NULL, NULL, "TheSignal" }
+};
+
+static void do_test_service_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(outer_loop);
+ }
+}
+
+/* This test function doesn't really test anything, just sets up
+ * for the following one
+ */
+static void
+fork_test_signal_service(void)
+{
+ pid_t child_pid;
+
+ /* it would break to fork after we already connected */
+ g_assert(_big_dbus_get_weak_ref(DBUS_BUS_SESSION) == NULL);
+ g_assert(_big_dbus_get_weak_ref(DBUS_BUS_SYSTEM) == 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();
+}
+
+static void
+kill_child(void)
+{
+ 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 int signal_received_count = 0;
+static int destroy_notify_count = 0;
+
+static void
+the_destroy_notifier(void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "got destroy notification on signal watch");
+ destroy_notify_count += 1;
+}
+
+static void
+the_destroy_notifier_that_quits(void *data)
+{
+ the_destroy_notifier(data);
+ g_main_loop_quit(inner_loop);
+}
+
+static void
+expect_receive_signal_handler(DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "dbus signal watch handler called");
+
+ g_assert(dbus_message_is_signal(message,
+ "com.litl.TestIface",
+ "TheSignal"));
+
+ signal_received_count += 1;
+
+ g_main_loop_quit(inner_loop);
+}
+
+static void
+test_match_combo(const char *sender,
+ const char *path,
+ const char *iface,
+ const char *member)
+{
+ signal_received_count = 0;
+ destroy_notify_count = 0;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "Watching %s %s %s %s",
+ sender,
+ path,
+ iface,
+ member);
+
+ big_dbus_watch_signal(DBUS_BUS_SESSION,
+ sender,
+ path,
+ iface,
+ member,
+ expect_receive_signal_handler,
+ GINT_TO_POINTER(1),
+ the_destroy_notifier);
+
+ big_dbus_proxy_call_json_async(test_service_proxy,
+ "emitTheSignal",
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ g_main_loop_run(inner_loop);
+
+ g_assert(signal_received_count == 1);
+ g_assert(destroy_notify_count == 0);
+
+ big_dbus_unwatch_signal(DBUS_BUS_SESSION,
+ sender,
+ path,
+ iface,
+ member,
+ expect_receive_signal_handler,
+ GINT_TO_POINTER(1));
+
+ g_assert(destroy_notify_count == 1);
+}
+
+static gboolean
+run_signal_tests_idle(void *data)
+{
+ int i;
+ const char *unique_name;
+
+ for (i = 0; i < (int) G_N_ELEMENTS(watch_tests); ++i) {
+ SignalWatchTest *test = &watch_tests[i];
+
+ test_match_combo(test->sender,
+ test->path,
+ test->iface,
+ test->member);
+ }
+
+ /* Now try on the unique bus name */
+
+ unique_name = big_dbus_proxy_get_bus_name(test_service_proxy);
+
+ test_match_combo(unique_name,
+ NULL, NULL, NULL);
+
+ /* Now test we get destroy notify when the unique name disappears
+ * on killing the child.
+ */
+ signal_received_count = 0;
+ destroy_notify_count = 0;
+
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "Watching unique name %s",
+ unique_name);
+
+ big_dbus_watch_signal(DBUS_BUS_SESSION,
+ unique_name,
+ NULL, NULL, NULL,
+ expect_receive_signal_handler,
+ GINT_TO_POINTER(1),
+ the_destroy_notifier_that_quits);
+
+ /* kill owner of unique_name */
+ kill_child();
+
+ /* wait for destroy notify */
+ g_main_loop_run(inner_loop);
+
+ g_assert(signal_received_count == 0);
+ /* roundabout way to write == 1 that gives more info on fail */
+ g_assert(destroy_notify_count > 0);
+ g_assert(destroy_notify_count < 2);
+
+ big_dbus_unwatch_signal(DBUS_BUS_SESSION,
+ unique_name,
+ NULL, NULL, NULL,
+ expect_receive_signal_handler,
+ GINT_TO_POINTER(1));
+
+ g_assert(signal_received_count == 0);
+ g_assert(destroy_notify_count == 1);
+
+ /* remove idle */
+ return FALSE;
+}
+
+static void
+on_test_service_appeared(DBusConnection *connection,
+ const char *name,
+ const char *new_owner_unique_name,
+ void *data)
+{
+ big_debug(BIG_DEBUG_IN_TESTS,
+ "%s appeared",
+ name);
+
+ inner_loop = g_main_loop_new(NULL, FALSE);
+
+ test_service_proxy =
+ big_dbus_proxy_new(connection, new_owner_unique_name,
+ "/com/litl/test/object42",
+ "com.litl.TestIface");
+
+ g_idle_add(run_signal_tests_idle, 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
+};
+
+void
+bigtest_test_func_util_dbus_signals_client(void)
+{
+ pid_t result;
+ int status;
+
+ /* See comment in dbus.c above the g_test_trap_fork()
+ * there on why we have to do this.
+ */
+ if (!g_test_trap_fork(0, 0)) {
+ /* We are the parent */
+ g_test_trap_assert_passed();
+ return;
+ }
+
+ /* All this code runs in a child process */
+
+ fork_test_signal_service();
+
+ g_type_init();
+
+ /* We rely on the child-forking test functions being called first */
+ g_assert(test_service_pid != 0);
+
+ big_dbus_watch_name(DBUS_BUS_SESSION,
+ "com.litl.TestService",
+ 0,
+ &watch_test_service_funcs,
+ NULL);
+
+ outer_loop = g_main_loop_new(NULL, FALSE);
+
+ g_main_loop_run(outer_loop);
+
+ if (test_service_proxy != NULL)
+ g_object_unref(test_service_proxy);
+
+ 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, "dbus signals 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 only here because we've forked */
+ exit(0);
+}
+
+/*
+ * Child service that emits signals
+ */
+
+static gboolean currently_have_test_service = FALSE;
+static GObject *test_service_object = NULL;
+
+static void
+test_service_emit_the_signal(DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error)
+{
+ DBusMessage *signal;
+
+ signal = dbus_message_new_signal("/com/litl/test/object42",
+ "com.litl.TestIface",
+ "TheSignal");
+ dbus_connection_send(connection, signal, NULL);
+ dbus_message_unref(signal);
+}
+
+static BigDBusJsonMethod test_service_methods[] = {
+ { "emitTheSignal", test_service_emit_the_signal, 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",
+ DBUS_BUS_SESSION,
+ 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);
+}
+
+#endif /* BIG_BUILD_TESTS */
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 */
diff --git a/modules/dbus/util/dbus.h b/modules/dbus/util/dbus.h
new file mode 100644
index 0000000..5b31c39
--- /dev/null
+++ b/modules/dbus/util/dbus.h
@@ -0,0 +1,216 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#ifndef __BIG_UTIL_DBUS_H__
+#define __BIG_UTIL_DBUS_H__
+
+#include <glib-object.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+/* Convenience macro */
+
+#define BIG_DBUS_NAME_FROM_TYPE(type) ((type) == DBUS_BUS_SESSION ? "session" : "system")
+
+/* Error names */
+#define BIG_DBUS_ERROR_STREAM_RECEIVER_CLOSED "com.litl.Error.Stream.ReceiverClosed"
+
+/*
+ * Monitor whether we are connected / not-connected to the bus
+ */
+
+typedef void (* BigDBusConnectionOpenedFunc) (DBusConnection *connection,
+ void *data);
+typedef void (* BigDBusConnectionClosedFunc) (DBusConnection *connection,
+ void *data);
+
+typedef struct {
+ DBusBusType which_bus;
+ BigDBusConnectionOpenedFunc opened;
+ BigDBusConnectionClosedFunc closed;
+} BigDBusConnectFuncs;
+
+void big_dbus_add_connect_funcs (const BigDBusConnectFuncs *funcs,
+ void *data);
+void big_dbus_remove_connect_funcs (const BigDBusConnectFuncs *funcs,
+ void *data);
+void big_dbus_add_connect_funcs_sync_notify (const BigDBusConnectFuncs *funcs,
+ void *data);
+
+
+void big_dbus_add_bus_weakref (DBusBusType bus_type,
+ DBusConnection **connection_p);
+void big_dbus_remove_bus_weakref (DBusBusType bus_type,
+ DBusConnection **connection_p);
+
+void big_dbus_try_connecting_now (DBusBusType which_bus);
+
+/*
+ * Own a bus name
+ *
+ */
+
+typedef enum {
+ BIG_DBUS_NAME_SINGLE_INSTANCE,
+ BIG_DBUS_NAME_MANY_INSTANCES
+} BigDBusNameType;
+
+typedef void (* BigDBusNameAcquiredFunc) (DBusConnection *connection,
+ const char *name,
+ void *data);
+typedef void (* BigDBusNameLostFunc) (DBusConnection *connection,
+ const char *name,
+ void *data);
+
+typedef struct {
+ const char *name;
+ BigDBusNameType type;
+ BigDBusNameAcquiredFunc acquired;
+ BigDBusNameLostFunc lost;
+} BigDBusNameOwnerFuncs;
+
+guint big_dbus_acquire_name (DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data);
+void big_dbus_release_name (DBusBusType bus_type,
+ const BigDBusNameOwnerFuncs *funcs,
+ void *data);
+void big_dbus_release_name_by_id (DBusBusType bus_type,
+ guint id);
+
+/*
+ * Keep track of someone else's bus name
+ *
+ */
+
+typedef enum {
+ BIG_DBUS_NAME_START_IF_NOT_FOUND = 0x1
+} BigDBusWatchNameFlags;
+
+typedef void (* BigDBusNameAppearedFunc) (DBusConnection *connection,
+ const char *name,
+ const char *new_owner_unique_name,
+ void *data);
+typedef void (* BigDBusNameVanishedFunc) (DBusConnection *connection,
+ const char *name,
+ const char *old_owner_unique_name,
+ void *data);
+
+typedef struct {
+ BigDBusNameAppearedFunc appeared;
+ BigDBusNameVanishedFunc vanished;
+} BigDBusWatchNameFuncs;
+
+void big_dbus_watch_name (DBusBusType bus_type,
+ const char *name,
+ BigDBusWatchNameFlags flags,
+ const BigDBusWatchNameFuncs *funcs,
+ void *data);
+void big_dbus_unwatch_name (DBusBusType bus_type,
+ const char *name,
+ const BigDBusWatchNameFuncs *funcs,
+ void *data);
+const char* big_dbus_get_watched_name_owner (DBusBusType bus_type,
+ const char *name);
+
+
+typedef void (* BigDBusSignalHandler) (DBusConnection *connection,
+ DBusMessage *message,
+ void *data);
+int big_dbus_watch_signal (DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ BigDBusSignalHandler handler,
+ void *data,
+ GDestroyNotify data_dnotify);
+void big_dbus_unwatch_signal (DBusBusType bus_type,
+ const char *sender,
+ const char *path,
+ const char *iface,
+ const char *name,
+ BigDBusSignalHandler handler,
+ void *data);
+void big_dbus_unwatch_signal_by_id (DBusBusType bus_type,
+ int id);
+
+/* A "json method" is a D-Bus method with signature
+ * DICT jsonMethodName(DICT)
+ * with the idea that it both takes and returns
+ * a JavaScript-style dictionary. This makes
+ * our JavaScript-to-dbus bindings really simple,
+ * and avoids a lot of futzing with dbus IDL.
+ *
+ * Of course it's completely annoying for someone
+ * using D-Bus in a "normal" way but the idea is just
+ * to use this to communicate within our own app
+ * that happens to consist of multiple processes
+ * and have bits written in JS.
+ */
+typedef void (* BigDBusJsonSyncMethodFunc) (DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ DBusMessageIter *out_iter,
+ void *data,
+ DBusError *error);
+
+typedef void (* BigDBusJsonAsyncMethodFunc) (DBusConnection *connection,
+ DBusMessage *message,
+ DBusMessageIter *in_iter,
+ void *data);
+
+typedef struct {
+ const char *name;
+ /* one of these two but not both should be non-NULL */
+ BigDBusJsonSyncMethodFunc sync_func;
+ BigDBusJsonAsyncMethodFunc async_func;
+} BigDBusJsonMethod;
+
+void big_dbus_register_json (DBusConnection *connection,
+ const char *iface_name,
+ const BigDBusJsonMethod *methods,
+ int n_methods);
+void big_dbus_unregister_json (DBusConnection *connection,
+ const char *iface_name);
+void big_dbus_register_g_object (DBusConnection *connection,
+ const char *path,
+ GObject *gobj,
+ const char *iface_name);
+void big_dbus_unregister_g_object (DBusConnection *connection,
+ const char *path);
+
+void big_dbus_append_json_entry (DBusMessageIter *dict_iter,
+ const char *key,
+ int dbus_type,
+ void *basic_value_p);
+void big_dbus_append_json_entry_STRING (DBusMessageIter *dict_iter,
+ const char *key,
+ const char *value);
+void big_dbus_append_json_entry_INT32 (DBusMessageIter *dict_iter,
+ const char *key,
+ dbus_int32_t value);
+void big_dbus_append_json_entry_DOUBLE (DBusMessageIter *dict_iter,
+ const char *key,
+ double value);
+void big_dbus_append_json_entry_BOOLEAN (DBusMessageIter *dict_iter,
+ const char *key,
+ dbus_bool_t value);
+void big_dbus_append_json_entry_EMPTY_ARRAY (DBusMessageIter *dict_iter,
+ const char *key);
+void big_dbus_append_json_entry_STRING_ARRAY (DBusMessageIter *dict_iter,
+ const char *key,
+ const char **value);
+
+gboolean big_dbus_message_iter_get_gsize (DBusMessageIter *iter,
+ gsize *value_p);
+gboolean big_dbus_message_iter_get_gssize (DBusMessageIter *iter,
+ gssize *value_p);
+
+void big_dbus_start_service(DBusConnection *connection,
+ const char *name);
+
+G_END_DECLS
+
+#endif /* __BIG_UTIL_DBUS_H__ */
diff --git a/modules/dbus/util/log.h b/modules/dbus/util/log.h
new file mode 100644
index 0000000..39299dd
--- /dev/null
+++ b/modules/dbus/util/log.h
@@ -0,0 +1,2 @@
+#define BIG_DEBUG_UTIL_DBUS 1
+#define big_debug(type,...)