diff options
Diffstat (limited to 'modules/dbus/util')
-rw-r--r-- | modules/dbus/util/Makefile.am | 5 | ||||
-rw-r--r-- | modules/dbus/util/Makefile.in | 453 | ||||
-rw-r--r-- | modules/dbus/util/dbus-private.h | 55 | ||||
-rw-r--r-- | modules/dbus/util/dbus-proxy.c | 666 | ||||
-rw-r--r-- | modules/dbus/util/dbus-proxy.h | 74 | ||||
-rw-r--r-- | modules/dbus/util/dbus-signals.c | 1318 | ||||
-rw-r--r-- | modules/dbus/util/dbus.c | 3023 | ||||
-rw-r--r-- | modules/dbus/util/dbus.h | 216 | ||||
-rw-r--r-- | modules/dbus/util/log.h | 2 |
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, ¤t_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,...) |