summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2010-05-06 14:13:59 -0400
committerDavid Zeuthen <davidz@redhat.com>2010-05-06 14:21:32 -0400
commitd0a14469d09d5fe23de219ba293fd4a266b02ced (patch)
tree0a2e2f1d566ff0f0ceb1d3ebe9f9cf6cd5263ab9
parent81e98c399e11d7621c91ff2911ef4f92c7a382e5 (diff)
downloadglib-d0a14469d09d5fe23de219ba293fd4a266b02ced.tar.gz
Initial GDBus code-drop from GDBus-standalone repo
Things compile and the test-suite passes. Still need to hook up gio.symbols and docs. There are still a bunch of TODOs left in the sources that needs to be addressed. Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r--configure.in10
-rw-r--r--gio/Makefile.am58
-rw-r--r--gio/gcredentials.c427
-rw-r--r--gio/gcredentials.h104
-rw-r--r--gio/gdbus-bash-completion.sh33
-rw-r--r--gio/gdbus-tool.c1491
-rw-r--r--gio/gdbusaddress.c1004
-rw-r--r--gio/gdbusaddress.h54
-rw-r--r--gio/gdbusauth.c1538
-rw-r--r--gio/gdbusauth.h86
-rw-r--r--gio/gdbusauthmechanism.c342
-rw-r--r--gio/gdbusauthmechanism.h174
-rw-r--r--gio/gdbusauthmechanismanon.c327
-rw-r--r--gio/gdbusauthmechanismanon.h82
-rw-r--r--gio/gdbusauthmechanismexternal.c416
-rw-r--r--gio/gdbusauthmechanismexternal.h82
-rw-r--r--gio/gdbusauthmechanismsha1.c1216
-rw-r--r--gio/gdbusauthmechanismsha1.h82
-rw-r--r--gio/gdbusauthobserver.c218
-rw-r--r--gio/gdbusauthobserver.h100
-rw-r--r--gio/gdbusconnection.c5280
-rw-r--r--gio/gdbusconnection.h467
-rw-r--r--gio/gdbuserror.c847
-rw-r--r--gio/gdbuserror.h92
-rw-r--r--gio/gdbusintrospection.c2009
-rw-r--r--gio/gdbusintrospection.h255
-rw-r--r--gio/gdbusmessage.c2421
-rw-r--r--gio/gdbusmessage.h172
-rw-r--r--gio/gdbusmethodinvocation.c795
-rw-r--r--gio/gdbusmethodinvocation.h119
-rw-r--r--gio/gdbusnameowning.c713
-rw-r--r--gio/gdbusnameowning.h88
-rw-r--r--gio/gdbusnamewatching.c620
-rw-r--r--gio/gdbusnamewatching.h68
-rw-r--r--gio/gdbusprivate.c1040
-rw-r--r--gio/gdbusprivate.h83
-rw-r--r--gio/gdbusproxy.c1542
-rw-r--r--gio/gdbusproxy.h146
-rw-r--r--gio/gdbusproxywatching.c397
-rw-r--r--gio/gdbusproxywatching.h77
-rw-r--r--gio/gdbusserver.c1043
-rw-r--r--gio/gdbusserver.h97
-rw-r--r--gio/gdbusutils.c364
-rw-r--r--gio/gdbusutils.h42
-rw-r--r--gio/gio-marshal.list2
-rw-r--r--gio/gio.h16
-rw-r--r--gio/gioenums.h372
-rw-r--r--gio/giotypes.h19
-rw-r--r--gio/gunixcredentialsmessage.c341
-rw-r--r--gio/gunixcredentialsmessage.h68
-rw-r--r--gio/tests/Makefile.am98
-rw-r--r--gio/tests/gdbus-addresses.c77
-rw-r--r--gio/tests/gdbus-connection.c653
-rw-r--r--gio/tests/gdbus-error.c198
-rw-r--r--gio/tests/gdbus-example-own-name.c99
-rw-r--r--gio/tests/gdbus-example-peer.c318
-rw-r--r--gio/tests/gdbus-example-server.c388
-rw-r--r--gio/tests/gdbus-example-subtree.c410
-rw-r--r--gio/tests/gdbus-example-unix-fd-client.c145
-rw-r--r--gio/tests/gdbus-example-watch-name.c101
-rw-r--r--gio/tests/gdbus-example-watch-proxy.c205
-rw-r--r--gio/tests/gdbus-exit-on-close.c82
-rw-r--r--gio/tests/gdbus-export.c1410
-rw-r--r--gio/tests/gdbus-introspection.c169
-rw-r--r--gio/tests/gdbus-names.c749
-rw-r--r--gio/tests/gdbus-peer.c746
-rw-r--r--gio/tests/gdbus-proxy.c455
-rw-r--r--gio/tests/gdbus-serialization.c650
-rw-r--r--gio/tests/gdbus-sessionbus.c342
-rw-r--r--gio/tests/gdbus-sessionbus.h38
-rw-r--r--gio/tests/gdbus-tests.c218
-rw-r--r--gio/tests/gdbus-tests.h146
-rwxr-xr-xgio/tests/gdbus-testserver.py270
-rw-r--r--gio/tests/gdbus-threading.c532
74 files changed, 35927 insertions, 11 deletions
diff --git a/configure.in b/configure.in
index 05bfad7a4..882bbc0ed 100644
--- a/configure.in
+++ b/configure.in
@@ -3496,6 +3496,16 @@ if test x$glib_win32_static_compilation = xyes; then
fi
])
+# Check for libdbus1 - Optional - is only used in the GDBus test cases
+#
+PKG_CHECK_MODULES(DBUS1,
+ dbus-1,
+ [AC_DEFINE(HAVE_DBUS1, 1, [Define if dbus-1 is available]) have_dbus1=yes],
+ have_dbus1=no)
+AC_SUBST(DBUS1_CFLAGS)
+AC_SUBST(DBUS1_LIBS)
+AM_CONDITIONAL(HAVE_DBUS1, [test "x$have_dbus1" = "xyes"])
+
AC_CONFIG_FILES([
glib-2.0.pc
glib-2.0-uninstalled.pc
diff --git a/gio/Makefile.am b/gio/Makefile.am
index bdc897d01..147974923 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -79,6 +79,48 @@ gio-marshal.c: gio-marshal.h gio-marshal.list
$(glib_genmarshal) --prefix=_gio_marshal $(srcdir)/gio-marshal.list --body --internal) > $@.tmp && \
mv $@.tmp $@
+gdbus_headers = \
+ gdbusauthobserver.h \
+ gcredentials.h \
+ gunixcredentialsmessage.h \
+ gdbusutils.h \
+ gdbuserror.h \
+ gdbusaddress.h \
+ gdbusconnection.h \
+ gdbusmessage.h \
+ gdbusnameowning.h \
+ gdbusnamewatching.h \
+ gdbusproxywatching.h \
+ gdbusproxy.h \
+ gdbusintrospection.h \
+ gdbusmethodinvocation.h \
+ gdbusserver.h \
+ $(NULL)
+
+gdbus_sources = \
+ gdbusutils.h gdbusutils.c \
+ gcredentials.h gcredentials.c \
+ gunixcredentialsmessage.h gunixcredentialsmessage.c \
+ gdbusaddress.h gdbusaddress.c \
+ gdbusauthobserver.h gdbusauthobserver.c \
+ gdbusauth.h gdbusauth.c \
+ gdbusauthmechanism.h gdbusauthmechanism.c \
+ gdbusauthmechanismanon.h gdbusauthmechanismanon.c \
+ gdbusauthmechanismexternal.h gdbusauthmechanismexternal.c \
+ gdbusauthmechanismsha1.h gdbusauthmechanismsha1.c \
+ gdbuserror.h gdbuserror.c \
+ gdbusconnection.h gdbusconnection.c \
+ gdbusmessage.h gdbusmessage.c \
+ gdbusnameowning.h gdbusnameowning.c \
+ gdbusnamewatching.h gdbusnamewatching.c \
+ gdbusproxywatching.h gdbusproxywatching.c \
+ gdbusproxy.h gdbusproxy.c \
+ gdbusprivate.h gdbusprivate.c \
+ gdbusintrospection.h gdbusintrospection.c \
+ gdbusmethodinvocation.h gdbusmethodinvocation.c \
+ gdbusserver.h gdbusserver.c \
+ $(NULL)
+
settings_headers = \
gsettingsbackend.h \
gsettings.h
@@ -327,6 +369,7 @@ libgio_2_0_la_SOURCES = \
$(unix_sources) \
$(win32_sources) \
$(settings_sources) \
+ $(gdbus_sources) \
$(local_sources) \
$(marshal_sources) \
$(NULL)
@@ -454,6 +497,7 @@ gio_headers = \
gzlibcompressor.h \
gzlibdecompressor.h \
$(settings_headers) \
+ $(gdbus_headers) \
$(NULL)
gioincludedir=$(includedir)/glib-2.0/gio/
@@ -527,10 +571,22 @@ gsettings_LDADD = \
libgio-2.0.la
gsettings_SOURCES = gsettings-tool.c
-
schemadir = $(datadir)/glib-2.0/schemas
dist_schema_DATA = gschema.dtd
+# ------------------------------------------------------------------------
+# gdbus(1) tool
+
+bin_PROGRAMS += gdbus
+gdbus_SOURCES = gdbus-tool.c
+gdbus_LDADD = libgio-2.0.la
+
+completiondir = $(sysconfdir)/bash_completion.d
+completion_SCRIPTS = gdbus-bash-completion.sh
+EXTRA_DIST += $(completion_SCRIPTS)
+
+# ------------------------------------------------------------------------
+
dist-hook: $(BUILT_EXTRA_DIST) ../build/win32/vs9/gio.vcproj
files='$(BUILT_EXTRA_DIST)'; \
for f in $$files; do \
diff --git a/gio/gcredentials.c b/gio/gcredentials.c
new file mode 100644
index 000000000..e1712fe61
--- /dev/null
+++ b/gio/gcredentials.c
@@ -0,0 +1,427 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "gcredentials.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+/**
+ * SECTION:gcredentials
+ * @short_description: Credentials
+ * @include: gdbus/gdbus.h
+ *
+ * The #GCredentials type is used for storing information that can be
+ * used for identifying, authenticating and authorizing processes.
+ *
+ * Most UNIX and UNIX-like operating systems support a secure exchange
+ * of credentials over a Unix Domain Socket, see
+ * #GUnixCredentialsMessage, g_unix_connection_send_credentials() and
+ * g_unix_connection_receive_credentials() for details.
+ */
+
+struct _GCredentialsPrivate
+{
+ gint64 unix_user;
+ gint64 unix_group;
+ gint64 unix_process;
+ gchar *windows_user;
+};
+
+G_DEFINE_TYPE (GCredentials, g_credentials, G_TYPE_OBJECT);
+
+static void
+g_credentials_finalize (GObject *object)
+{
+ GCredentials *credentials = G_CREDENTIALS (object);
+
+ g_free (credentials->priv->windows_user);
+
+ if (G_OBJECT_CLASS (g_credentials_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_credentials_parent_class)->finalize (object);
+}
+
+
+static void
+g_credentials_class_init (GCredentialsClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (klass, sizeof (GCredentialsPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = g_credentials_finalize;
+}
+
+static void
+g_credentials_init (GCredentials *credentials)
+{
+ credentials->priv = G_TYPE_INSTANCE_GET_PRIVATE (credentials, G_TYPE_CREDENTIALS, GCredentialsPrivate);
+
+ credentials->priv->unix_user = -1;
+ credentials->priv->unix_group = -1;
+ credentials->priv->unix_process = -1;
+ credentials->priv->windows_user = NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new:
+ *
+ * Creates a new empty credentials object.
+ *
+ * Returns: A #GCredentials. Free with g_object_unref().
+ */
+GCredentials *
+g_credentials_new (void)
+{
+ return g_object_new (G_TYPE_CREDENTIALS, NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+static GCredentials *
+g_credentials_new_for_unix_process (void)
+{
+ GCredentials *credentials;
+ credentials = g_credentials_new ();
+ credentials->priv->unix_user = getuid ();
+ credentials->priv->unix_group = getgid ();
+ credentials->priv->unix_process = getpid ();
+ return credentials;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new_for_process:
+ *
+ * Gets the credentials for the current process. Note that the exact
+ * set of credentials in the returned object vary according to
+ * platform.
+ *
+ * Returns: A #GCredentials. Free with g_object_unref().
+ */
+GCredentials *
+g_credentials_new_for_process (void)
+{
+#ifdef G_OS_UNIX
+ return g_credentials_new_for_unix_process ();
+#elif G_OS_WIN32
+ return g_credentials_new_for_win32_process ();
+#else
+#warning Please implement g_credentials_new_for_process() for your OS. For now g_credentials_new_for_process() will return empty credentials.
+ return g_credentials_new ();
+#endif
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new_for_string:
+ * @str: A string returned from g_credentials_to_string().
+ * @error: Return location for error.
+ *
+ * Constructs a #GCredentials instance from @str.
+ *
+ * Returns: A #GCredentials or %NULL if @error is set. The return
+ * object must be freed with g_object_unref().
+ */
+GCredentials *
+g_credentials_new_for_string (const gchar *str,
+ GError **error)
+{
+ GCredentials *credentials;
+ gchar **tokens;
+ guint n;
+
+ g_return_val_if_fail (str != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ tokens = NULL;
+ credentials = g_credentials_new ();
+
+ if (!g_str_has_prefix (str, "GCredentials:"))
+ goto fail;
+
+ tokens = g_strsplit (str + sizeof "GCredentials:" - 1, ",", 0);
+ for (n = 0; tokens[n] != NULL; n++)
+ {
+ const gchar *token = tokens[n];
+ if (g_str_has_prefix (token, "unix-user:"))
+ g_credentials_set_unix_user (credentials, atoi (token + sizeof ("unix-user:") - 1));
+ else if (g_str_has_prefix (token, "unix-group:"))
+ g_credentials_set_unix_group (credentials, atoi (token + sizeof ("unix-group:") - 1));
+ else if (g_str_has_prefix (token, "unix-process:"))
+ g_credentials_set_unix_process (credentials, atoi (token + sizeof ("unix-process:") - 1));
+ else if (g_str_has_prefix (token, "windows-user:"))
+ g_credentials_set_windows_user (credentials, token + sizeof ("windows-user:"));
+ else
+ goto fail;
+ }
+ g_strfreev (tokens);
+ return credentials;
+
+ fail:
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("The string `%s' is not a valid credentials string"),
+ str);
+ g_object_unref (credentials);
+ g_strfreev (tokens);
+ return NULL;
+}
+
+/**
+ * g_credentials_to_string:
+ * @credentials: A #GCredentials object.
+ *
+ * Serializes @credentials to a string that can be used with
+ * g_credentials_new_for_string().
+ *
+ * Returns: A string that should be freed with g_free().
+ */
+gchar *
+g_credentials_to_string (GCredentials *credentials)
+{
+ GString *ret;
+
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+
+ ret = g_string_new ("GCredentials:");
+ if (credentials->priv->unix_user != -1)
+ g_string_append_printf (ret, "unix-user=%" G_GINT64_FORMAT ",", credentials->priv->unix_user);
+ if (credentials->priv->unix_group != -1)
+ g_string_append_printf (ret, "unix-group=%" G_GINT64_FORMAT ",", credentials->priv->unix_group);
+ if (credentials->priv->unix_process != -1)
+ g_string_append_printf (ret, "unix-process=%" G_GINT64_FORMAT ",", credentials->priv->unix_process);
+ if (credentials->priv->windows_user != NULL)
+ g_string_append_printf (ret, "windows-user=%s,", credentials->priv->windows_user);
+ if (ret->str[ret->len - 1] == ',')
+ ret->str[ret->len - 1] = '\0';
+
+ return g_string_free (ret, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_user:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX user credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_user (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+ return credentials->priv->unix_user != -1;
+}
+
+/**
+ * g_credentials_get_unix_user:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX user identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_user (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+ return credentials->priv->unix_user;
+}
+
+/**
+ * g_credentials_set_unix_user:
+ * @credentials: A #GCredentials.
+ * @user_id: A UNIX user identifier (typically type #uid_t) or -1 to unset it.
+ *
+ * Sets the UNIX user identifier.
+ */
+void
+g_credentials_set_unix_user (GCredentials *credentials,
+ gint64 user_id)
+{
+ g_return_if_fail (G_IS_CREDENTIALS (credentials));
+ credentials->priv->unix_user = user_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_group:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX group credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_group (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+ return credentials->priv->unix_group != -1;
+}
+
+/**
+ * g_credentials_get_unix_group:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX group identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_group (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+ return credentials->priv->unix_group;
+}
+
+/**
+ * g_credentials_set_unix_group:
+ * @credentials: A #GCredentials.
+ * @group_id: A UNIX group identifier (typically type #gid_t) or -1 to unset.
+ *
+ * Sets the UNIX group identifier.
+ */
+void
+g_credentials_set_unix_group (GCredentials *credentials,
+ gint64 group_id)
+{
+ g_return_if_fail (G_IS_CREDENTIALS (credentials));
+ credentials->priv->unix_group = group_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_process:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX process credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_process (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+ return credentials->priv->unix_process != -1;
+}
+
+/**
+ * g_credentials_get_unix_process:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX process identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_process (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+ return credentials->priv->unix_process;
+}
+
+/**
+ * g_credentials_set_unix_process:
+ * @credentials: A #GCredentials.
+ * @process_id: A UNIX process identifier (typically type #pid_t/#GPid) or -1 to unset.
+ *
+ * Sets the UNIX process identifier.
+ */
+void
+g_credentials_set_unix_process (GCredentials *credentials,
+ gint64 process_id)
+{
+ g_return_if_fail (G_IS_CREDENTIALS (credentials));
+ credentials->priv->unix_process = process_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_windows_user:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a Windows user SID (Security Identifier).
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_windows_user (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+ return credentials->priv->windows_user != NULL;
+}
+
+/**
+ * g_credentials_get_windows_user:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the Windows User SID from @credentials.
+ *
+ * Returns: A string or %NULL if unset. Do not free, the string is owned by @credentials.
+ */
+const gchar *
+g_credentials_get_windows_user (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+ return credentials->priv->windows_user;
+}
+
+/**
+ * g_credentials_set_windows_user:
+ * @credentials: A #GCredentials.
+ * @user_sid: The Windows User SID or %NULL to unset.
+ *
+ * Sets the Windows User SID.
+ */
+void
+g_credentials_set_windows_user (GCredentials *credentials,
+ const gchar *user_sid)
+{
+ g_return_if_fail (G_IS_CREDENTIALS (credentials));
+ g_free (credentials->priv->windows_user);
+ credentials->priv->windows_user = g_strdup (user_sid);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gcredentials.h b/gio/gcredentials.h
new file mode 100644
index 000000000..a5bab7b7a
--- /dev/null
+++ b/gio/gcredentials.h
@@ -0,0 +1,104 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_CREDENTIALS_H__
+#define __G_CREDENTIALS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_CREDENTIALS (g_credentials_get_type ())
+#define G_CREDENTIALS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_CREDENTIALS, GCredentials))
+#define G_CREDENTIALS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_CREDENTIALS, GCredentialsClass))
+#define G_CREDENTIALS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_CREDENTIALS, GCredentialsClass))
+#define G_IS_CREDENTIALS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_CREDENTIALS))
+#define G_IS_CREDENTIALS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_CREDENTIALS))
+
+typedef struct _GCredentialsClass GCredentialsClass;
+typedef struct _GCredentialsPrivate GCredentialsPrivate;
+
+/**
+ * GCredentials:
+ *
+ * The #GCredentials structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GCredentials
+{
+ /*< private >*/
+ GObject parent_instance;
+ GCredentialsPrivate *priv;
+};
+
+/**
+ * GCredentialsClass:
+ *
+ * Class structure for #GCredentials.
+ */
+struct _GCredentialsClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+GType g_credentials_get_type (void) G_GNUC_CONST;
+
+GCredentials *g_credentials_new (void);
+GCredentials *g_credentials_new_for_process (void);
+GCredentials *g_credentials_new_for_string (const gchar *str,
+ GError **error);
+gchar *g_credentials_to_string (GCredentials *credentials);
+
+gboolean g_credentials_has_unix_user (GCredentials *credentials);
+gint64 g_credentials_get_unix_user (GCredentials *credentials);
+void g_credentials_set_unix_user (GCredentials *credentials,
+ gint64 user_id);
+
+gboolean g_credentials_has_unix_group (GCredentials *credentials);
+gint64 g_credentials_get_unix_group (GCredentials *credentials);
+void g_credentials_set_unix_group (GCredentials *credentials,
+ gint64 group_id);
+
+gboolean g_credentials_has_unix_process (GCredentials *credentials);
+gint64 g_credentials_get_unix_process (GCredentials *credentials);
+void g_credentials_set_unix_process (GCredentials *credentials,
+ gint64 process_id);
+
+gboolean g_credentials_has_windows_user (GCredentials *credentials);
+const gchar *g_credentials_get_windows_user (GCredentials *credentials);
+void g_credentials_set_windows_user (GCredentials *credentials,
+ const gchar *user_sid);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_H__ */
diff --git a/gio/gdbus-bash-completion.sh b/gio/gdbus-bash-completion.sh
new file mode 100644
index 000000000..79f4cb4b4
--- /dev/null
+++ b/gio/gdbus-bash-completion.sh
@@ -0,0 +1,33 @@
+
+# Check for bash
+[ -z "$BASH_VERSION" ] && return
+
+####################################################################################################
+
+
+__gdbus() {
+ local IFS=$'\n'
+ local cur=`_get_cword :`
+
+ local suggestions=$(gdbus complete "${COMP_LINE}" ${COMP_POINT})
+ COMPREPLY=($(compgen -W "$suggestions" -- "$cur"))
+
+ # Remove colon-word prefix from COMPREPLY items
+ case "$cur" in
+ *:*)
+ case "$COMP_WORDBREAKS" in
+ *:*)
+ local colon_word=${cur%${cur##*:}}
+ local i=${#COMPREPLY[*]}
+ while [ $((--i)) -ge 0 ]; do
+ COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
+ done
+ ;;
+ esac
+ ;;
+ esac
+}
+
+####################################################################################################
+
+complete -o nospace -F __gdbus gdbus
diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c
new file mode 100644
index 000000000..b3233cf45
--- /dev/null
+++ b/gio/gdbus-tool.c
@@ -0,0 +1,1491 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <gio/gio.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_GNUC_UNUSED static void completion_debug (const gchar *format, ...);
+
+/* Uncomment to get debug traces in /tmp/gdbus-completion-debug.txt (nice
+ * to not have it interfere with stdout/stderr)
+ */
+#if 0
+G_GNUC_UNUSED static void
+completion_debug (const gchar *format, ...)
+{
+ va_list var_args;
+ gchar *s;
+ static FILE *f = NULL;
+
+ va_start (var_args, format);
+ s = g_strdup_vprintf (format, var_args);
+ if (f == NULL)
+ {
+ f = fopen ("/tmp/gdbus-completion-debug.txt", "a+");
+ }
+ fprintf (f, "%s\n", s);
+ g_free (s);
+}
+#else
+static void
+completion_debug (const gchar *format, ...)
+{
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static void
+remove_arg (gint num, gint *argc, gchar **argv[])
+{
+ gint n;
+
+ g_assert (num <= (*argc));
+
+ for (n = num; (*argv)[n] != NULL; n++)
+ (*argv)[n] = (*argv)[n+1];
+ (*argv)[n] = NULL;
+ (*argc) = (*argc) - 1;
+}
+
+static void
+usage (gint *argc, gchar **argv[], gboolean use_stdout)
+{
+ GOptionContext *o;
+ gchar *s;
+ gchar *program_name;
+
+ o = g_option_context_new (_("COMMAND"));
+ g_option_context_set_help_enabled (o, FALSE);
+ /* Ignore parsing result */
+ g_option_context_parse (o, argc, argv, NULL);
+ program_name = g_path_get_basename ((*argv)[0]);
+ s = g_strdup_printf (_("Commands:\n"
+ " help Shows this information\n"
+ " introspect Introspect a remote object\n"
+ " call Invoke a method on a remote object\n"
+ "\n"
+ "Use \"%s COMMAND --help\" to get help on each command.\n"),
+ program_name);
+ g_free (program_name);
+ g_option_context_set_description (o, s);
+ g_free (s);
+ s = g_option_context_get_help (o, FALSE, NULL);
+ if (use_stdout)
+ g_print ("%s", s);
+ else
+ g_printerr ("%s", s);
+ g_free (s);
+ g_option_context_free (o);
+}
+
+static void
+modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command)
+{
+ gchar *s;
+ gchar *program_name;
+
+ /* TODO:
+ * 1. get a g_set_prgname() ?; or
+ * 2. save old argv[0] and restore later
+ */
+
+ g_assert (g_strcmp0 ((*argv)[1], command) == 0);
+ remove_arg (1, argc, argv);
+
+ program_name = g_path_get_basename ((*argv)[0]);
+ s = g_strdup_printf ("%s %s", (*argv)[0], command);
+ (*argv)[0] = s;
+ g_free (program_name);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+print_methods (GDBusConnection *c,
+ const gchar *name,
+ const gchar *path)
+{
+ GVariant *result;
+ GError *error;
+ const gchar *xml_data;
+ GDBusNodeInfo *node;
+ guint n;
+ guint m;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_sync (c,
+ name,
+ path,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 secs */
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+ {
+ g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+ g_variant_get_type_string (result));
+ g_variant_unref (result);
+ goto out;
+ }
+ g_variant_get (result, "(s)", &xml_data);
+
+ error = NULL;
+ node = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_variant_unref (result);
+ if (node == NULL)
+ {
+ g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ for (n = 0; node->interfaces != NULL && node->interfaces[n] != NULL; n++)
+ {
+ const GDBusInterfaceInfo *iface = node->interfaces[n];
+ for (m = 0; iface->methods != NULL && iface->methods[m] != NULL; m++)
+ {
+ const GDBusMethodInfo *method = iface->methods[m];
+ g_print ("%s.%s \n", iface->name, method->name);
+ }
+ }
+ g_dbus_node_info_unref (node);
+
+ out:
+ ;
+}
+
+static void
+print_paths (GDBusConnection *c,
+ const gchar *name,
+ const gchar *path)
+{
+ GVariant *result;
+ GError *error;
+ const gchar *xml_data;
+ GDBusNodeInfo *node;
+ guint n;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_sync (c,
+ name,
+ path,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 secs */
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+ {
+ g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+ g_variant_get_type_string (result));
+ g_variant_unref (result);
+ goto out;
+ }
+ g_variant_get (result, "(s)", &xml_data);
+
+ error = NULL;
+ node = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_variant_unref (result);
+ if (node == NULL)
+ {
+ g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ //g_printerr ("xml=`%s'", xml_data);
+
+ //g_printerr ("bar `%s'\n", path);
+
+ if (node->interfaces != NULL)
+ g_print ("%s \n", path);
+
+ for (n = 0; node->nodes != NULL && node->nodes[n] != NULL; n++)
+ {
+ gchar *s;
+
+ //g_printerr ("foo `%s'\n", node->nodes[n].path);
+
+ if (g_strcmp0 (path, "/") == 0)
+ s = g_strdup_printf ("/%s", node->nodes[n]->path);
+ else
+ s = g_strdup_printf ("%s/%s", path, node->nodes[n]->path);
+
+ print_paths (c, name, s);
+
+ g_free (s);
+ }
+ g_dbus_node_info_unref (node);
+
+ out:
+ ;
+}
+
+static void
+print_names (GDBusConnection *c,
+ gboolean include_unique_names)
+{
+ GVariant *result;
+ GError *error;
+ GVariantIter *iter;
+ gchar *str;
+ GHashTable *name_set;
+ GList *keys;
+ GList *l;
+
+ name_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_sync (c,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ListNames",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 secs */
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)")))
+ {
+ g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result));
+ g_variant_unref (result);
+ goto out;
+ }
+ g_variant_get (result, "(as)", &iter);
+ while (g_variant_iter_loop (iter, "s", &str))
+ g_hash_table_insert (name_set, g_strdup (str), NULL);
+ g_variant_iter_free (iter);
+ g_variant_unref (result);
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_sync (c,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ListActivatableNames",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 secs */
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)")))
+ {
+ g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result));
+ g_variant_unref (result);
+ goto out;
+ }
+ g_variant_get (result, "(as)", &iter);
+ while (g_variant_iter_loop (iter, "s", &str))
+ g_hash_table_insert (name_set, g_strdup (str), NULL);
+ g_variant_iter_free (iter);
+ g_variant_unref (result);
+
+ keys = g_hash_table_get_keys (name_set);
+ keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *name = l->data;
+ if (!include_unique_names && g_str_has_prefix (name, ":"))
+ continue;
+
+ g_print ("%s \n", name);
+ }
+ g_list_free (keys);
+
+ out:
+ g_hash_table_unref (name_set);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean opt_connection_system = FALSE;
+static gboolean opt_connection_session = FALSE;
+static gchar *opt_connection_address = NULL;
+
+static const GOptionEntry connection_entries[] =
+{
+ { "system", 'y', 0, G_OPTION_ARG_NONE, &opt_connection_system, N_("Connect to the system bus"), NULL},
+ { "session", 'e', 0, G_OPTION_ARG_NONE, &opt_connection_session, N_("Connect to the session bus"), NULL},
+ { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_connection_address, N_("Connect to given D-Bus address"), NULL},
+ { NULL }
+};
+
+static GOptionGroup *
+connection_get_group (void)
+{
+ static GOptionGroup *g;
+
+ g = g_option_group_new ("connection",
+ N_("Connection Endpoint Options:"),
+ N_("Options specifying the connection endpoint"),
+ NULL,
+ NULL);
+ g_option_group_add_entries (g, connection_entries);
+ return g;
+}
+
+static GDBusConnection *
+connection_get_dbus_connection (GError **error)
+{
+ GDBusConnection *c;
+
+ c = NULL;
+
+ /* First, ensure we have exactly one connect */
+ if (!opt_connection_system && !opt_connection_session && opt_connection_address == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("No connection endpoint specified"));
+ goto out;
+ }
+ else if ((opt_connection_system && (opt_connection_session || opt_connection_address != NULL)) ||
+ (opt_connection_session && (opt_connection_system || opt_connection_address != NULL)) ||
+ (opt_connection_address != NULL && (opt_connection_system || opt_connection_session)))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Multiple connection endpoints specified"));
+ goto out;
+ }
+
+ if (opt_connection_system)
+ {
+ c = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
+ }
+ else if (opt_connection_session)
+ {
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+ }
+ else if (opt_connection_address != NULL)
+ {
+ c = g_dbus_connection_new_for_address_sync (opt_connection_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* GCancellable */
+ error);
+ }
+
+ out:
+ return c;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+call_helper_get_method_in_signature (GDBusConnection *c,
+ const gchar *dest,
+ const gchar *path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GError **error)
+{
+ GPtrArray *ret;
+ GVariant *result;
+ GDBusNodeInfo *node_info;
+ const gchar *xml_data;
+ const GDBusInterfaceInfo *interface_info;
+ const GDBusMethodInfo *method_info;
+ guint n;
+
+ ret = NULL;
+ result = NULL;
+ node_info = NULL;
+
+ result = g_dbus_connection_invoke_method_sync (c,
+ dest,
+ path,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 secs */
+ NULL,
+ error);
+ if (result == NULL)
+ goto out;
+
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Error: Result is type `%s', expected `(s)'\n"),
+ g_variant_get_type_string (result));
+ goto out;
+ }
+
+ g_variant_get (result, "(s)", &xml_data);
+ node_info = g_dbus_node_info_new_for_xml (xml_data, error);
+ if (node_info == NULL)
+ goto out;
+
+ interface_info = g_dbus_node_info_lookup_interface (node_info, interface_name);
+ if (interface_info == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Warning: According to introspection data, interface `%s' does not exist\n"),
+ interface_name);
+ goto out;
+ }
+
+ method_info = g_dbus_interface_info_lookup_method (interface_info, method_name);
+ if (method_info == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Warning: According to introspection data, method `%s' does not exist on interface `%s'\n"),
+ method_name,
+ interface_name);
+ goto out;
+ }
+
+ ret = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_type_free);
+ for (n = 0; method_info->in_args != NULL && method_info->in_args[n] != NULL; n++)
+ {
+ g_ptr_array_add (ret, g_variant_type_new (method_info->in_args[n]->signature));
+ }
+
+ out:
+ if (node_info != NULL)
+ g_dbus_node_info_unref (node_info);
+ if (result != NULL)
+ g_variant_unref (result);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVariant *
+_g_variant_parse_me_harder (GVariantType *type,
+ const gchar *given_str,
+ GError **error)
+{
+ GVariant *value;
+ gchar *s;
+ guint n;
+ GString *str;
+
+ str = g_string_new ("\"");
+ for (n = 0; given_str[n] != '\0'; n++)
+ {
+ if (G_UNLIKELY (given_str[n] == '\"'))
+ g_string_append (str, "\\\"");
+ else
+ g_string_append_c (str, given_str[n]);
+ }
+ g_string_append_c (str, '"');
+ s = g_string_free (str, FALSE);
+
+ value = g_variant_parse (type,
+ s,
+ NULL,
+ NULL,
+ error);
+ g_free (s);
+
+ return value;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *opt_call_dest = NULL;
+static gchar *opt_call_object_path = NULL;
+static gchar *opt_call_method = NULL;
+
+static const GOptionEntry call_entries[] =
+{
+ { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_call_dest, N_("Destination name to invoke method on"), NULL},
+ { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_call_object_path, N_("Object path to invoke method on"), NULL},
+ { "method", 'm', 0, G_OPTION_ARG_STRING, &opt_call_method, N_("Method and interface name"), NULL},
+ { NULL }
+};
+
+static gboolean
+handle_call (gint *argc,
+ gchar **argv[],
+ gboolean request_completion,
+ const gchar *completion_cur,
+ const gchar *completion_prev)
+{
+ gint ret;
+ GOptionContext *o;
+ gchar *s;
+ GError *error;
+ GDBusConnection *c;
+ GVariant *parameters;
+ gchar *interface_name;
+ gchar *method_name;
+ GVariant *result;
+ GPtrArray *in_signature_types;
+ gboolean complete_names;
+ gboolean complete_paths;
+ gboolean complete_methods;
+ GVariantBuilder builder;
+ guint n;
+
+ ret = FALSE;
+ c = NULL;
+ parameters = NULL;
+ interface_name = NULL;
+ method_name = NULL;
+ result = NULL;
+ in_signature_types = NULL;
+
+ modify_argv0_for_command (argc, argv, "call");
+
+ o = g_option_context_new (NULL);
+ g_option_context_set_help_enabled (o, FALSE);
+ g_option_context_set_summary (o, _("Invoke a method on a remote object."));
+ g_option_context_add_main_entries (o, call_entries, NULL /* GETTEXT_PACKAGE*/);
+ g_option_context_add_group (o, connection_get_group ());
+
+ complete_names = FALSE;
+ if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
+ {
+ complete_names = TRUE;
+ remove_arg ((*argc) - 1, argc, argv);
+ }
+
+ complete_paths = FALSE;
+ if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
+ {
+ complete_paths = TRUE;
+ remove_arg ((*argc) - 1, argc, argv);
+ }
+
+ complete_methods = FALSE;
+ if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--method") == 0)
+ {
+ complete_methods = TRUE;
+ remove_arg ((*argc) - 1, argc, argv);
+ }
+
+ if (!g_option_context_parse (o, argc, argv, NULL))
+ {
+ if (!request_completion)
+ {
+ s = g_option_context_get_help (o, FALSE, NULL);
+ g_printerr ("%s", s);
+ g_free (s);
+ goto out;
+ }
+ }
+
+ error = NULL;
+ c = connection_get_dbus_connection (&error);
+ if (c == NULL)
+ {
+ if (request_completion)
+ {
+ if (g_strcmp0 (completion_prev, "--address") == 0)
+ {
+ g_print ("unix:\n"
+ "tcp:\n"
+ "nonce-tcp:\n");
+ }
+ else
+ {
+ g_print ("--system \n--session \n--address \n");
+ }
+ }
+ else
+ {
+ g_printerr (_("Error connecting: %s\n"), error->message);
+ g_error_free (error);
+ }
+ goto out;
+ }
+
+ /* validate and complete destination (bus name) */
+ if (g_dbus_connection_get_unique_name (c) != NULL)
+ {
+ /* this only makes sense on message bus connections */
+ if (complete_names)
+ {
+ print_names (c, FALSE);
+ goto out;
+ }
+ if (opt_call_dest == NULL)
+ {
+ if (request_completion)
+ g_print ("--dest \n");
+ else
+ g_printerr (_("Error: Destination is not specified\n"));
+ goto out;
+ }
+ if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
+ {
+ print_names (c, g_str_has_prefix (opt_call_dest, ":"));
+ goto out;
+ }
+ }
+
+ /* validate and complete object path */
+ if (complete_paths)
+ {
+ print_paths (c, opt_call_dest, "/");
+ goto out;
+ }
+ if (opt_call_object_path == NULL)
+ {
+ if (request_completion)
+ g_print ("--object-path \n");
+ else
+ g_printerr (_("Error: Object path is not specified\n"));
+ goto out;
+ }
+ if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
+ {
+ gchar *p;
+ s = g_strdup (opt_call_object_path);
+ p = strrchr (s, '/');
+ if (p != NULL)
+ {
+ if (p == s)
+ p++;
+ *p = '\0';
+ }
+ print_paths (c, opt_call_dest, s);
+ g_free (s);
+ goto out;
+ }
+ if (!request_completion && !g_variant_is_object_path (opt_call_object_path))
+ {
+ g_printerr (_("Error: %s is not a valid object path\n"), opt_call_object_path);
+ goto out;
+ }
+
+ /* validate and complete method (interface + method name) */
+ if (complete_methods)
+ {
+ print_methods (c, opt_call_dest, opt_call_object_path);
+ goto out;
+ }
+ if (opt_call_method == NULL)
+ {
+ if (request_completion)
+ g_print ("--method \n");
+ else
+ g_printerr (_("Error: Method name is not specified\n"));
+ goto out;
+ }
+ if (request_completion && g_strcmp0 ("--method", completion_prev) == 0)
+ {
+ print_methods (c, opt_call_dest, opt_call_object_path);
+ goto out;
+ }
+ s = strrchr (opt_call_method, '.');
+ if (!request_completion && s == NULL)
+ {
+ g_printerr (_("Error: Method name `%s' is invalid\n"), opt_call_method);
+ goto out;
+ }
+ method_name = g_strdup (s + 1);
+ interface_name = g_strndup (opt_call_method, s - opt_call_method);
+
+ /* All done with completion now */
+ if (request_completion)
+ goto out;
+
+ /* Introspect, for easy conversion - it's not fatal if we can't do this */
+ in_signature_types = call_helper_get_method_in_signature (c,
+ opt_call_dest,
+ opt_call_object_path,
+ interface_name,
+ method_name,
+ &error);
+ if (in_signature_types == NULL)
+ {
+ //g_printerr ("Error getting introspection data: %s\n", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ /* Read parameters */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+ for (n = 1; n < (guint) *argc; n++)
+ {
+ GVariant *value;
+ GVariantType *type;
+
+ type = NULL;
+ if (in_signature_types != NULL)
+ {
+ if (n - 1 >= in_signature_types->len)
+ {
+ /* Only warn for the first param */
+ if (n - 1 == in_signature_types->len)
+ {
+ g_printerr ("Warning: Introspection data indicates %d parameters but more was passed\n",
+ in_signature_types->len);
+ }
+ }
+ else
+ {
+ type = in_signature_types->pdata[n - 1];
+ }
+ }
+
+ error = NULL;
+ value = g_variant_parse (type,
+ (*argv)[n],
+ NULL,
+ NULL,
+ &error);
+ if (value == NULL)
+ {
+ g_error_free (error);
+ error = NULL;
+ value = _g_variant_parse_me_harder (type, (*argv)[n], &error);
+ if (value == NULL)
+ {
+ if (type != NULL)
+ {
+ s = g_variant_type_dup_string (type);
+ g_printerr (_("Error parsing parameter %d of type `%s': %s\n"),
+ n,
+ s,
+ error->message);
+ g_free (s);
+ }
+ else
+ {
+ g_printerr (_("Error parsing parameter %d: %s\n"),
+ n,
+ error->message);
+ }
+ g_error_free (error);
+ g_variant_builder_clear (&builder);
+ goto out;
+ }
+ }
+ g_variant_builder_add_value (&builder, value);
+ }
+ parameters = g_variant_builder_end (&builder);
+
+ if (parameters != NULL)
+ parameters = g_variant_ref_sink (parameters);
+ result = g_dbus_connection_invoke_method_sync (c,
+ opt_call_dest,
+ opt_call_object_path,
+ interface_name,
+ method_name,
+ parameters,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ if (in_signature_types != NULL)
+ {
+ GString *s;
+ s = g_string_new (NULL);
+ for (n = 0; n < in_signature_types->len; n++)
+ {
+ GVariantType *type = in_signature_types->pdata[n];
+ g_string_append_len (s,
+ g_variant_type_peek_string (type),
+ g_variant_type_get_string_length (type));
+ }
+ g_printerr ("(According to introspection data, you need to pass `%s')\n", s->str);
+ g_string_free (s, TRUE);
+ }
+ goto out;
+ }
+
+ s = g_variant_print (result, TRUE);
+ g_print ("%s\n", s);
+ g_free (s);
+
+ ret = TRUE;
+
+ out:
+ if (in_signature_types != NULL)
+ g_ptr_array_unref (in_signature_types);
+ if (result != NULL)
+ g_variant_unref (result);
+ if (c != NULL)
+ g_object_unref (c);
+ if (parameters != NULL)
+ g_variant_unref (parameters);
+ g_free (interface_name);
+ g_free (method_name);
+ g_option_context_free (o);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: dump annotations */
+
+static void
+dump_arg (const GDBusArgInfo *o,
+ guint indent,
+ const gchar *direction,
+ gboolean ignore_indent,
+ gboolean include_newline)
+{
+ g_print ("%*s%s%s %s%s",
+ ignore_indent ? 0 : indent, "",
+ direction,
+ o->signature,
+ o->name,
+ include_newline ? ",\n" : "");
+}
+
+static guint
+count_args (GDBusArgInfo **args)
+{
+ guint n;
+ n = 0;
+ if (args == NULL)
+ goto out;
+ while (args[n] != NULL)
+ n++;
+ out:
+ return n;
+}
+
+static void
+dump_method (const GDBusMethodInfo *o,
+ guint indent)
+{
+ guint n;
+ guint m;
+ guint name_len;
+ guint total_num_args;
+ g_print ("%*s%s(", indent, "", o->name);
+ name_len = strlen (o->name);
+ total_num_args = count_args (o->in_args) + count_args (o->out_args);
+ for (n = 0, m = 0; o->in_args != NULL && o->in_args[n] != NULL; n++, m++)
+ {
+ gboolean ignore_indent = (m == 0);
+ gboolean include_newline = (m != total_num_args - 1);
+ dump_arg (o->in_args[n],
+ indent + name_len + 1,
+ "in ",
+ ignore_indent,
+ include_newline);
+ }
+ for (n = 0; o->out_args != NULL && o->out_args[n] != NULL; n++, m++)
+ {
+ gboolean ignore_indent = (m == 0);
+ gboolean include_newline = (m != total_num_args - 1);
+ dump_arg (o->out_args[n],
+ indent + name_len + 1,
+ "out ",
+ ignore_indent,
+ include_newline);
+ }
+ g_print (");\n");
+}
+
+static void
+dump_signal (const GDBusSignalInfo *o,
+ guint indent)
+{
+ guint n;
+ guint name_len;
+ guint total_num_args;
+ g_print ("%*s%s(", indent, "", o->name);
+ name_len = strlen (o->name);
+ total_num_args = count_args (o->args);
+ for (n = 0; o->args != NULL && o->args[n] != NULL; n++)
+ {
+ gboolean ignore_indent = (n == 0);
+ gboolean include_newline = (n != total_num_args - 1);
+ dump_arg (o->args[n],
+ indent + name_len + 1,
+ "",
+ ignore_indent,
+ include_newline);
+ }
+ g_print (");\n");
+}
+
+static void
+dump_property (const GDBusPropertyInfo *o,
+ guint indent,
+ GVariant *value)
+{
+ const gchar *access;
+ if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+ access = "readonly";
+ else if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
+ access = "writeonly";
+ else if (o->flags == (G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+ access = "readwrite";
+ else
+ g_assert_not_reached ();
+ if (value != NULL)
+ {
+ gchar *s = g_variant_print (value, FALSE);
+ g_print ("%*s%s %s %s = %s;\n", indent, "", access, o->signature, o->name, s);
+ g_free (s);
+ }
+ else
+ {
+ g_print ("%*s%s %s %s;\n", indent, "", access, o->signature, o->name);
+ }
+}
+
+static void
+dump_interface (GDBusConnection *c,
+ const gchar *name,
+ const GDBusInterfaceInfo *o,
+ guint indent,
+ const gchar *object_path)
+{
+ guint n;
+ GHashTable *properties;
+
+ properties = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_variant_unref);
+
+ /* Try to get properties */
+ if (c != NULL && name != NULL && object_path != NULL)
+ {
+ GVariant *result;
+ result = g_dbus_connection_invoke_method_sync (c,
+ name,
+ object_path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)", o->name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000,
+ NULL,
+ NULL);
+ if (result != NULL)
+ {
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(a{sv})")))
+ {
+ GVariantIter *iter;
+ GVariant *item;
+ g_variant_get (result,
+ "(a{sv})",
+ &iter);
+ while ((item = g_variant_iter_next_value (iter)))
+ {
+ const gchar *key;
+ GVariant *value;
+ g_variant_get (item,
+ "{sv}",
+ &key,
+ &value);
+
+ g_hash_table_insert (properties, g_strdup (key), g_variant_ref (value));
+ }
+ }
+ g_variant_unref (result);
+ }
+ }
+
+ g_print ("%*sinterface %s {\n", indent, "", o->name);
+ if (o->methods != NULL)
+ {
+ g_print ("%*s methods:\n", indent, "");
+ for (n = 0; o->methods[n] != NULL; n++)
+ dump_method (o->methods[n], indent + 4);
+ }
+ if (o->signals != NULL)
+ {
+ g_print ("%*s signals:\n", indent, "");
+ for (n = 0; o->signals[n] != NULL; n++)
+ dump_signal (o->signals[n], indent + 4);
+ }
+ if (o->properties != NULL)
+ {
+ g_print ("%*s properties:\n", indent, "");
+ for (n = 0; o->properties[n] != NULL; n++)
+ {
+ dump_property (o->properties[n],
+ indent + 4,
+ g_hash_table_lookup (properties, (o->properties[n])->name));
+ }
+ }
+ g_print ("%*s};\n",
+ indent, "");
+
+ g_hash_table_unref (properties);
+}
+
+static void
+dump_node (GDBusConnection *c,
+ const gchar *name,
+ const GDBusNodeInfo *o,
+ guint indent,
+ const gchar *object_path)
+{
+ guint n;
+ const gchar *object_path_to_print;
+
+ object_path_to_print = object_path;
+ if (o->path != NULL)
+ object_path_to_print = o->path;
+
+ g_print ("%*snode %s", indent, "", object_path_to_print != NULL ? object_path_to_print : "(not set)");
+ if (o->interfaces != NULL || o->nodes != NULL)
+ {
+ g_print (" {\n");
+ for (n = 0; o->interfaces != NULL && o->interfaces[n] != NULL; n++)
+ dump_interface (c, name, o->interfaces[n], indent + 2, object_path);
+ for (n = 0; o->nodes != NULL && o->nodes[n] != NULL; n++)
+ dump_node (NULL, NULL, o->nodes[n], indent + 2, NULL);
+ g_print ("%*s};\n",
+ indent, "");
+ }
+ else
+ {
+ g_print ("\n");
+ }
+}
+
+static gchar *opt_introspect_dest = NULL;
+static gchar *opt_introspect_object_path = NULL;
+
+static const GOptionEntry introspect_entries[] =
+{
+ { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_introspect_dest, N_("Destination name to introspect"), NULL},
+ { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_introspect_object_path, N_("Object path to introspect"), NULL},
+ { NULL }
+};
+
+static gboolean
+handle_introspect (gint *argc,
+ gchar **argv[],
+ gboolean request_completion,
+ const gchar *completion_cur,
+ const gchar *completion_prev)
+{
+ gint ret;
+ GOptionContext *o;
+ gchar *s;
+ GError *error;
+ GDBusConnection *c;
+ GVariant *result;
+ const gchar *xml_data;
+ GDBusNodeInfo *node;
+ gboolean complete_names;
+ gboolean complete_paths;
+
+ ret = FALSE;
+ c = NULL;
+ node = NULL;
+ result = NULL;
+
+ modify_argv0_for_command (argc, argv, "introspect");
+
+ o = g_option_context_new (NULL);
+ if (request_completion)
+ g_option_context_set_ignore_unknown_options (o, TRUE);
+ g_option_context_set_help_enabled (o, FALSE);
+ g_option_context_set_summary (o, _("Introspect a remote object."));
+ g_option_context_add_main_entries (o, introspect_entries, NULL /* GETTEXT_PACKAGE*/);
+ g_option_context_add_group (o, connection_get_group ());
+
+ complete_names = FALSE;
+ if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
+ {
+ complete_names = TRUE;
+ remove_arg ((*argc) - 1, argc, argv);
+ }
+
+ complete_paths = FALSE;
+ if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
+ {
+ complete_paths = TRUE;
+ remove_arg ((*argc) - 1, argc, argv);
+ }
+
+ if (!g_option_context_parse (o, argc, argv, NULL))
+ {
+ if (!request_completion)
+ {
+ s = g_option_context_get_help (o, FALSE, NULL);
+ g_printerr ("%s", s);
+ g_free (s);
+ goto out;
+ }
+ }
+
+ error = NULL;
+ c = connection_get_dbus_connection (&error);
+ if (c == NULL)
+ {
+ if (request_completion)
+ {
+ if (g_strcmp0 (completion_prev, "--address") == 0)
+ {
+ g_print ("unix:\n"
+ "tcp:\n"
+ "nonce-tcp:\n");
+ }
+ else
+ {
+ g_print ("--system \n--session \n--address \n");
+ }
+ }
+ else
+ {
+ g_printerr (_("Error connecting: %s\n"), error->message);
+ g_error_free (error);
+ }
+ goto out;
+ }
+
+ if (g_dbus_connection_get_unique_name (c) != NULL)
+ {
+ if (complete_names)
+ {
+ print_names (c, FALSE);
+ goto out;
+ }
+ /* this only makes sense on message bus connections */
+ if (opt_introspect_dest == NULL)
+ {
+ if (request_completion)
+ g_print ("--dest \n");
+ else
+ g_printerr (_("Error: Destination is not specified\n"));
+ goto out;
+ }
+ if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
+ {
+ print_names (c, g_str_has_prefix (opt_introspect_dest, ":"));
+ goto out;
+ }
+ }
+ if (complete_paths)
+ {
+ print_paths (c, opt_introspect_dest, "/");
+ goto out;
+ }
+ if (opt_introspect_object_path == NULL)
+ {
+ if (request_completion)
+ g_print ("--object-path \n");
+ else
+ g_printerr (_("Error: Object path is not specified\n"));
+ goto out;
+ }
+ if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
+ {
+ gchar *p;
+ s = g_strdup (opt_introspect_object_path);
+ p = strrchr (s, '/');
+ if (p != NULL)
+ {
+ if (p == s)
+ p++;
+ *p = '\0';
+ }
+ print_paths (c, opt_introspect_dest, s);
+ g_free (s);
+ goto out;
+ }
+ if (!request_completion && !g_variant_is_object_path (opt_introspect_object_path))
+ {
+ g_printerr (_("Error: %s is not a valid object path\n"), opt_introspect_object_path);
+ goto out;
+ }
+
+ /* All done with completion now */
+ if (request_completion)
+ goto out;
+
+ result = g_dbus_connection_invoke_method_sync (c,
+ opt_introspect_dest,
+ opt_introspect_object_path,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 3000, /* 3 sec */
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_printerr (_("Error: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+ {
+ g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+ g_variant_get_type_string (result));
+ goto out;
+ }
+ g_variant_get (result, "(s)", &xml_data);
+
+ error = NULL;
+ node = g_dbus_node_info_new_for_xml (xml_data, &error);
+ if (node == NULL)
+ {
+ g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ dump_node (c, opt_introspect_dest, node, 0, opt_introspect_object_path);
+
+ ret = TRUE;
+
+ out:
+ if (node != NULL)
+ g_dbus_node_info_unref (node);
+ if (result != NULL)
+ g_variant_unref (result);
+ if (c != NULL)
+ g_object_unref (c);
+ g_option_context_free (o);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+pick_word_at (const gchar *s,
+ gint cursor,
+ gint *out_word_begins_at)
+{
+ gint begin;
+ gint end;
+
+ if (s[0] == '\0')
+ {
+ if (out_word_begins_at != NULL)
+ *out_word_begins_at = -1;
+ return NULL;
+ }
+
+ if (g_ascii_isspace (s[cursor]) && ((cursor > 0 && g_ascii_isspace(s[cursor-1])) || cursor == 0))
+ {
+ if (out_word_begins_at != NULL)
+ *out_word_begins_at = cursor;
+ return g_strdup ("");
+ }
+
+ while (!g_ascii_isspace (s[cursor - 1]) && cursor > 0)
+ cursor--;
+ begin = cursor;
+
+ end = begin;
+ while (!g_ascii_isspace (s[end]) && s[end] != '\0')
+ end++;
+
+ if (out_word_begins_at != NULL)
+ *out_word_begins_at = begin;
+
+ return g_strndup (s + begin, end - begin);
+}
+
+gint
+main (gint argc, gchar *argv[])
+{
+ gint ret;
+ const gchar *command;
+ gboolean request_completion;
+ gchar *completion_cur;
+ gchar *completion_prev;
+
+ ret = 1;
+ completion_cur = NULL;
+ completion_prev = NULL;
+
+ g_type_init ();
+
+ if (argc < 2)
+ {
+ usage (&argc, &argv, FALSE);
+ goto out;
+ }
+
+ request_completion = FALSE;
+
+ //completion_debug ("---- argc=%d --------------------------------------------------------", argc);
+
+ again:
+ command = argv[1];
+ if (g_strcmp0 (command, "help") == 0)
+ {
+ if (request_completion)
+ {
+ /* do nothing */
+ }
+ else
+ {
+ usage (&argc, &argv, TRUE);
+ ret = 0;
+ }
+ goto out;
+ }
+ else if (g_strcmp0 (command, "call") == 0)
+ {
+ if (handle_call (&argc,
+ &argv,
+ request_completion,
+ completion_cur,
+ completion_prev))
+ ret = 0;
+ goto out;
+ }
+ else if (g_strcmp0 (command, "introspect") == 0)
+ {
+ if (handle_introspect (&argc,
+ &argv,
+ request_completion,
+ completion_cur,
+ completion_prev))
+ ret = 0;
+ goto out;
+ }
+ else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion)
+ {
+ const gchar *completion_line;
+ gchar **completion_argv;
+ gint completion_argc;
+ gint completion_point;
+ gchar *endp;
+ gint cur_begin;
+
+ request_completion = TRUE;
+
+ completion_line = argv[2];
+ completion_point = strtol (argv[3], &endp, 10);
+ if (endp == argv[3] || *endp != '\0')
+ goto out;
+
+#if 0
+ completion_debug ("completion_point=%d", completion_point);
+ completion_debug ("----");
+ completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789");
+ completion_debug ("`%s'", completion_line);
+ completion_debug (" %*s^",
+ completion_point, "");
+ completion_debug ("----");
+#endif
+
+ if (!g_shell_parse_argv (completion_line,
+ &completion_argc,
+ &completion_argv,
+ NULL))
+ {
+ /* it's very possible the command line can't be parsed (for
+ * example, missing quotes etc) - in that case, we just
+ * don't autocomplete at all
+ */
+ goto out;
+ }
+
+ /* compute cur and prev */
+ completion_prev = NULL;
+ completion_cur = pick_word_at (completion_line, completion_point, &cur_begin);
+ if (cur_begin > 0)
+ {
+ gint prev_end;
+ for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--)
+ {
+ if (!g_ascii_isspace (completion_line[prev_end]))
+ {
+ completion_prev = pick_word_at (completion_line, prev_end, NULL);
+ break;
+ }
+ }
+ }
+#if 0
+ completion_debug (" cur=`%s'", completion_cur);
+ completion_debug ("prev=`%s'", completion_prev);
+#endif
+
+ argc = completion_argc;
+ argv = completion_argv;
+
+ ret = 0;
+
+ goto again;
+ }
+ else
+ {
+ if (request_completion)
+ {
+ g_print ("help \ncall \nintrospect \n");
+ ret = 0;
+ goto out;
+ }
+ else
+ {
+ g_printerr ("Unknown command `%s'\n", command);
+ usage (&argc, &argv, FALSE);
+ goto out;
+ }
+ }
+
+ out:
+ g_free (completion_cur);
+ g_free (completion_prev);
+ return ret;
+}
diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c
new file mode 100644
index 000000000..e50e73781
--- /dev/null
+++ b/gio/gdbusaddress.c
@@ -0,0 +1,1004 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusaddress.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gdbusprivate.h"
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/**
+ * SECTION:gdbusaddress
+ * @title: D-Bus Addresses
+ * @short_description: D-Bus connection endpoints
+ * @include: gdbus/gdbus.h
+ *
+ * Routines for working with D-Bus addresses.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_is_address:
+ * @string: A string.
+ *
+ * Checks if @string is a D-Bus address.
+ *
+ * This doesn't check if @string is actually supported by #GDBusServer
+ * or #GDBusConnection - use g_dbus_is_supported_address() to do more
+ * checks.
+ *
+ * Returns: %TRUE if @string is a valid D-Bus address, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_address (const gchar *string)
+{
+ guint n;
+ gchar **a;
+ gboolean ret;
+
+ ret = FALSE;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+
+ a = g_strsplit (string, ";", 0);
+ for (n = 0; a[n] != NULL; n++)
+ {
+ if (!_g_dbus_address_parse_entry (a[n],
+ NULL,
+ NULL,
+ NULL))
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_strfreev (a);
+ return ret;
+}
+
+static gboolean
+is_valid_unix (const gchar *address_entry,
+ GHashTable *key_value_pairs,
+ GError **error)
+{
+ gboolean ret;
+ GList *keys;
+ GList *l;
+ const gchar *path;
+ const gchar *tmpdir;
+ const gchar *abstract;
+
+ ret = FALSE;
+ keys = NULL;
+ path = NULL;
+ tmpdir = NULL;
+ abstract = NULL;
+
+ keys = g_hash_table_get_keys (key_value_pairs);
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *key = l->data;
+ if (g_strcmp0 (key, "path") == 0)
+ path = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "tmpdir") == 0)
+ tmpdir = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "abstract") == 0)
+ abstract = g_hash_table_lookup (key_value_pairs, key);
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Unsupported key `%s' in address entry `%s'"),
+ key,
+ address_entry);
+ goto out;
+ }
+ }
+
+ if (path != NULL)
+ {
+ if (tmpdir != NULL || abstract != NULL)
+ goto meaningless;
+ /* TODO: validate path */
+ }
+ else if (tmpdir != NULL)
+ {
+ if (path != NULL || abstract != NULL)
+ goto meaningless;
+ /* TODO: validate tmpdir */
+ }
+ else if (abstract != NULL)
+ {
+ if (path != NULL || tmpdir != NULL)
+ goto meaningless;
+ /* TODO: validate abstract */
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Address `%s' is invalid (need exactly one of path, tmpdir or abstract keys"),
+ address_entry);
+ goto out;
+ }
+
+
+ ret= TRUE;
+ goto out;
+
+ meaningless:
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Meaningless key/value pair combination in address entry `%s'"),
+ address_entry);
+
+ out:
+ g_list_free (keys);
+
+ return ret;
+}
+
+static gboolean
+is_valid_nonce_tcp (const gchar *address_entry,
+ GHashTable *key_value_pairs,
+ GError **error)
+{
+ gboolean ret;
+ GList *keys;
+ GList *l;
+ const gchar *host;
+ const gchar *port;
+ const gchar *family;
+ const gchar *nonce_file;
+ gint port_num;
+ gchar *endp;
+
+ ret = FALSE;
+ keys = NULL;
+ host = NULL;
+ port = NULL;
+ family = NULL;
+ nonce_file = NULL;
+
+ keys = g_hash_table_get_keys (key_value_pairs);
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *key = l->data;
+ if (g_strcmp0 (key, "host") == 0)
+ host = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "port") == 0)
+ port = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "family") == 0)
+ family = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "noncefile") == 0)
+ nonce_file = g_hash_table_lookup (key_value_pairs, key);
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Unsupported key `%s' in address entry `%s'"),
+ key,
+ address_entry);
+ goto out;
+ }
+ }
+
+ if (port != NULL)
+ {
+ port_num = strtol (port, &endp, 10);
+ if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the port attribute is malformed"),
+ address_entry);
+ goto out;
+ }
+ }
+
+ if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the family attribute is malformed"),
+ address_entry);
+ goto out;
+ }
+
+ ret= TRUE;
+
+ out:
+ g_list_free (keys);
+
+ return ret;
+}
+
+static gboolean
+is_valid_tcp (const gchar *address_entry,
+ GHashTable *key_value_pairs,
+ GError **error)
+{
+ gboolean ret;
+ GList *keys;
+ GList *l;
+ const gchar *host;
+ const gchar *port;
+ const gchar *family;
+ gint port_num;
+ gchar *endp;
+
+ ret = FALSE;
+ keys = NULL;
+ host = NULL;
+ port = NULL;
+ family = NULL;
+
+ keys = g_hash_table_get_keys (key_value_pairs);
+ for (l = keys; l != NULL; l = l->next)
+ {
+ const gchar *key = l->data;
+ if (g_strcmp0 (key, "host") == 0)
+ host = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "port") == 0)
+ port = g_hash_table_lookup (key_value_pairs, key);
+ else if (g_strcmp0 (key, "family") == 0)
+ family = g_hash_table_lookup (key_value_pairs, key);
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Unsupported key `%s' in address entry `%s'"),
+ key,
+ address_entry);
+ goto out;
+ }
+ }
+
+ if (port != NULL)
+ {
+ port_num = strtol (port, &endp, 10);
+ if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the port attribute is malformed"),
+ address_entry);
+ goto out;
+ }
+ }
+
+ if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the family attribute is malformed"),
+ address_entry);
+ goto out;
+ }
+
+ ret= TRUE;
+
+ out:
+ g_list_free (keys);
+
+ return ret;
+}
+
+/**
+ * g_dbus_is_supported_address:
+ * @string: A string.
+ * @error: Return location for error or %NULL.
+ *
+ * Like g_dbus_is_address() but also checks if the library suppors the
+ * transports in @string and that key/value pairs for each transport
+ * are valid.
+ *
+ * Returns: %TRUE if @string is a valid D-Bus address that is
+ * supported by this library, %FALSE if @error is set.
+ */
+gboolean
+g_dbus_is_supported_address (const gchar *string,
+ GError **error)
+{
+ guint n;
+ gchar **a;
+ gboolean ret;
+
+ ret = FALSE;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ a = g_strsplit (string, ";", 0);
+ for (n = 0; a[n] != NULL; n++)
+ {
+ gchar *transport_name;
+ GHashTable *key_value_pairs;
+ gboolean supported;
+
+ if (!_g_dbus_address_parse_entry (a[n],
+ &transport_name,
+ &key_value_pairs,
+ error))
+ goto out;
+
+ supported = FALSE;
+ if (g_strcmp0 (transport_name, "unix") == 0)
+ supported = is_valid_unix (a[n], key_value_pairs, error);
+ else if (g_strcmp0 (transport_name, "tcp") == 0)
+ supported = is_valid_tcp (a[n], key_value_pairs, error);
+ else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
+ supported = is_valid_nonce_tcp (a[n], key_value_pairs, error);
+
+ g_free (transport_name);
+ g_hash_table_unref (key_value_pairs);
+
+ if (!supported)
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_strfreev (a);
+
+ g_assert (ret || (!ret && (error == NULL || *error != NULL)));
+
+ return ret;
+}
+
+gboolean
+_g_dbus_address_parse_entry (const gchar *address_entry,
+ gchar **out_transport_name,
+ GHashTable **out_key_value_pairs,
+ GError **error)
+{
+ gboolean ret;
+ GHashTable *key_value_pairs;
+ gchar *transport_name;
+ gchar **kv_pairs;
+ const gchar *s;
+ guint n;
+
+ ret = FALSE;
+ kv_pairs = NULL;
+ transport_name = NULL;
+ key_value_pairs = NULL;
+
+ s = strchr (address_entry, ':');
+ if (s == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Address element `%s', does not contain a colon (:)"),
+ address_entry);
+ goto out;
+ }
+
+ transport_name = g_strndup (address_entry, s - address_entry);
+ key_value_pairs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ kv_pairs = g_strsplit (s + 1, ",", 0);
+ for (n = 0; kv_pairs != NULL && kv_pairs[n] != NULL; n++)
+ {
+ const gchar *kv_pair = kv_pairs[n];
+ gchar *key;
+ gchar *value;
+
+ s = strchr (kv_pair, '=');
+ if (s == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Key/Value pair %d, `%s', in address element `%s', does not contain an equal sign"),
+ n,
+ kv_pair,
+ address_entry);
+ goto out;
+ }
+
+ /* TODO: actually validate that no illegal characters are present before and after then '=' sign */
+ key = g_uri_unescape_segment (kv_pair, s, NULL);
+ value = g_uri_unescape_segment (s + 1, kv_pair + strlen (kv_pair), NULL);
+ g_hash_table_insert (key_value_pairs, key, value);
+ }
+
+ ret = TRUE;
+
+out:
+ g_strfreev (kv_pairs);
+ if (ret)
+ {
+ if (out_transport_name != NULL)
+ *out_transport_name = transport_name;
+ else
+ g_free (transport_name);
+ if (out_key_value_pairs != NULL)
+ *out_key_value_pairs = key_value_pairs;
+ else if (key_value_pairs != NULL)
+ g_hash_table_unref (key_value_pairs);
+ }
+ else
+ {
+ g_free (transport_name);
+ if (key_value_pairs != NULL)
+ g_hash_table_unref (key_value_pairs);
+ }
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: Declare an extension point called GDBusTransport (or similar)
+ * and move code below to extensions implementing said extension
+ * point. That way we can implement a D-Bus transport over X11 without
+ * making libgio link to libX11...
+ */
+static GIOStream *
+g_dbus_address_connect (const gchar *address_entry,
+ const gchar *transport_name,
+ GHashTable *key_value_pairs,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *ret;
+ GSocketConnectable *connectable;
+ const gchar *nonce_file;
+
+ connectable = NULL;
+ ret = NULL;
+ nonce_file = NULL;
+
+ if (FALSE)
+ {
+ }
+#ifdef G_OS_UNIX
+ else if (g_strcmp0 (transport_name, "unix") == 0)
+ {
+ const gchar *path;
+ const gchar *abstract;
+ path = g_hash_table_lookup (key_value_pairs, "path");
+ abstract = g_hash_table_lookup (key_value_pairs, "abstract");
+ if ((path == NULL && abstract == NULL) || (path != NULL && abstract != NULL))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the unix transport requires exactly one of the "
+ "keys `path' or `abstract' to be set"),
+ address_entry);
+ }
+ else if (path != NULL)
+ {
+ connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new (path));
+ }
+ else if (abstract != NULL)
+ {
+ connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new_with_type (abstract,
+ -1,
+ G_UNIX_SOCKET_ADDRESS_ABSTRACT));
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+#endif
+ else if (g_strcmp0 (transport_name, "tcp") == 0 || g_strcmp0 (transport_name, "nonce-tcp") == 0)
+ {
+ const gchar *s;
+ const gchar *host;
+ guint port;
+ gchar *endp;
+ gboolean is_nonce;
+
+ is_nonce = (g_strcmp0 (transport_name, "nonce-tcp") == 0);
+
+ host = g_hash_table_lookup (key_value_pairs, "host");
+ if (host == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the host attribute is missing or malformed"),
+ address_entry);
+ goto out;
+ }
+
+ s = g_hash_table_lookup (key_value_pairs, "port");
+ if (s == NULL)
+ s = "0";
+ port = strtol (s, &endp, 10);
+ if ((*s == '\0' || *endp != '\0') || port < 0 || port >= 65536)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the port attribute is missing or malformed"),
+ address_entry);
+ goto out;
+ }
+
+
+ if (is_nonce)
+ {
+ nonce_file = g_hash_table_lookup (key_value_pairs, "noncefile");
+ if (nonce_file == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error in address `%s' - the noncefile attribute is missing or malformed"),
+ address_entry);
+ goto out;
+ }
+ }
+
+ /* TODO: deal with family */
+ connectable = g_network_address_new (host, port);
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Unknown or unsupported transport `%s' for address `%s'"),
+ transport_name,
+ address_entry);
+ }
+
+ if (connectable != NULL)
+ {
+ GSocketClient *client;
+ GSocketConnection *connection;
+
+ g_assert (ret == NULL);
+ client = g_socket_client_new ();
+ connection = g_socket_client_connect (client,
+ connectable,
+ cancellable,
+ error);
+ g_object_unref (connectable);
+ g_object_unref (client);
+ if (connection == NULL)
+ goto out;
+
+ ret = G_IO_STREAM (connection);
+
+ if (nonce_file != NULL)
+ {
+ gchar *nonce_contents;
+ gsize nonce_length;
+
+ /* TODO: too dangerous to read the entire file? (think denial-of-service etc.) */
+ if (!g_file_get_contents (nonce_file,
+ &nonce_contents,
+ &nonce_length,
+ error))
+ {
+ g_prefix_error (error, _("Error reading nonce file `%s':"), nonce_file);
+ g_object_unref (ret);
+ ret = NULL;
+ goto out;
+ }
+
+ if (nonce_length != 16)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("The nonce-file `%s' was %" G_GSIZE_FORMAT " bytes. Expected 16 bytes."),
+ nonce_file,
+ nonce_length);
+ g_free (nonce_contents);
+ g_object_unref (ret);
+ ret = NULL;
+ goto out;
+ }
+
+ if (!g_output_stream_write_all (g_io_stream_get_output_stream (ret),
+ nonce_contents,
+ nonce_length,
+ NULL,
+ cancellable,
+ error))
+ {
+ g_prefix_error (error, _("Error write contents of nonce file `%s' to stream:"), nonce_file);
+ g_object_unref (ret);
+ ret = NULL;
+ g_free (nonce_contents);
+ goto out;
+ }
+ g_free (nonce_contents);
+ }
+ }
+
+ out:
+
+ return ret;
+}
+
+static GIOStream *
+g_dbus_address_try_connect_one (const gchar *address_entry,
+ gchar **out_guid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *ret;
+ GHashTable *key_value_pairs;
+ gchar *transport_name;
+ const gchar *guid;
+
+ ret = NULL;
+ transport_name = NULL;
+ key_value_pairs = NULL;
+
+ if (!_g_dbus_address_parse_entry (address_entry,
+ &transport_name,
+ &key_value_pairs,
+ error))
+ goto out;
+
+ ret = g_dbus_address_connect (address_entry,
+ transport_name,
+ key_value_pairs,
+ cancellable,
+ error);
+ if (ret == NULL)
+ goto out;
+
+ /* TODO: validate that guid is of correct format */
+ guid = g_hash_table_lookup (key_value_pairs, "guid");
+ if (guid != NULL && out_guid != NULL)
+ *out_guid = g_strdup (guid);
+
+out:
+ g_free (transport_name);
+ if (key_value_pairs != NULL)
+ g_hash_table_unref (key_value_pairs);
+ return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+ gchar *address;
+ GIOStream *stream;
+ gchar *guid;
+} GetStreamData;
+
+static void
+get_stream_data_free (GetStreamData *data)
+{
+ g_free (data->address);
+ if (data->stream != NULL)
+ g_object_unref (data->stream);
+ g_free (data->guid);
+ g_free (data);
+}
+
+static void
+get_stream_thread_func (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GetStreamData *data;
+ GError *error;
+
+ data = g_simple_async_result_get_op_res_gpointer (res);
+
+ error = NULL;
+ data->stream = g_dbus_address_get_stream_sync (data->address,
+ &data->guid,
+ cancellable,
+ &error);
+ if (data->stream == NULL)
+ {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ }
+}
+
+/**
+ * g_dbus_address_get_stream:
+ * @address: A valid D-Bus address.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: Data to pass to @callback.
+ *
+ * Asynchronously connects to an endpoint specified by @address and
+ * sets up the connection so it is in a state to run the client-side
+ * of the D-Bus authentication conversation.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_address_get_stream_finish() to get the result of
+ * the operation.
+ *
+ * This is an asynchronous failable function. See
+ * g_dbus_address_get_stream_sync() for the synchronous version.
+ */
+void
+g_dbus_address_get_stream (const gchar *address,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+ GetStreamData *data;
+
+ g_return_if_fail (address != NULL);
+
+ res = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ g_dbus_address_get_stream);
+ data = g_new0 (GetStreamData, 1);
+ data->address = g_strdup (address);
+ g_simple_async_result_set_op_res_gpointer (res,
+ data,
+ (GDestroyNotify) get_stream_data_free);
+ g_simple_async_result_run_in_thread (res,
+ get_stream_thread_func,
+ G_PRIORITY_DEFAULT,
+ cancellable);
+ g_object_unref (res);
+}
+
+/**
+ * g_dbus_address_get_stream_finish:
+ * @res: A #GAsyncResult obtained from the GAsyncReadyCallback passed to g_dbus_address_get_stream().
+ * @out_guid: %NULL or return location to store the GUID extracted from @address, if any.
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_address_get_stream().
+ *
+ * Returns: A #GIOStream or %NULL if @error is set.
+ */
+GIOStream *
+g_dbus_address_get_stream_finish (GAsyncResult *res,
+ gchar **out_guid,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GetStreamData *data;
+ GIOStream *ret;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_address_get_stream);
+
+ ret = NULL;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ ret = g_object_ref (data->stream);
+ if (out_guid != NULL)
+ *out_guid = g_strdup (data->guid);
+
+ out:
+ return ret;
+}
+
+/**
+ * g_dbus_address_get_stream_sync:
+ * @address: A valid D-Bus address.
+ * @out_guid: %NULL or return location to store the GUID extracted from @address, if any.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects to an endpoint specified by @address and
+ * sets up the connection so it is in a state to run the client-side
+ * of the D-Bus authentication conversation.
+ *
+ * This is a synchronous failable function. See
+ * g_dbus_address_get_stream() for the asynchronous version.
+ *
+ * Returns: A #GIOStream or %NULL if @error is set.
+ */
+GIOStream *
+g_dbus_address_get_stream_sync (const gchar *address,
+ gchar **out_guid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *ret;
+ gchar **addr_array;
+ guint n;
+ GError *last_error;
+
+ g_return_val_if_fail (address != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ ret = NULL;
+ last_error = NULL;
+
+ addr_array = g_strsplit (address, ";", 0);
+ last_error = NULL;
+ for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++)
+ {
+ const gchar *addr = addr_array[n];
+ GError *this_error;
+ this_error = NULL;
+ ret = g_dbus_address_try_connect_one (addr,
+ out_guid,
+ cancellable,
+ &this_error);
+ if (ret != NULL)
+ {
+ goto out;
+ }
+ else
+ {
+ g_assert (this_error != NULL);
+ if (last_error != NULL)
+ g_error_free (last_error);
+ last_error = this_error;
+ }
+ }
+
+ out:
+ if (ret != NULL)
+ {
+ if (last_error != NULL)
+ g_error_free (last_error);
+ }
+ else
+ {
+ g_assert (last_error != NULL);
+ g_propagate_error (error, last_error);
+ }
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: implement for UNIX, Win32 and OS X */
+static gchar *
+get_session_address_platform_specific (void)
+{
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_address_get_for_bus_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously looks up the D-Bus address for the well-known message
+ * bus instance specified by @bus_type. This may involve using various
+ * platform specific mechanisms.
+ *
+ * Returns: A valid D-Bus address string for @bus_type or %NULL if @error is set.
+ */
+gchar *
+g_dbus_address_get_for_bus_sync (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *ret;
+ const gchar *starter_bus;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ ret = NULL;
+
+ switch (bus_type)
+ {
+ case G_BUS_TYPE_SYSTEM:
+ ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"));
+ if (ret == NULL)
+ {
+ ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket");
+ }
+ break;
+
+ case G_BUS_TYPE_SESSION:
+ ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+ if (ret == NULL)
+ {
+ ret = get_session_address_platform_specific ();
+ if (ret == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Cannot determine session bus address (TODO: run dbus-launch to find out)"));
+ }
+ }
+ break;
+
+ case G_BUS_TYPE_STARTER:
+ starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE");
+ if (g_strcmp0 (starter_bus, "session") == 0)
+ {
+ ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, cancellable, error);
+ goto out;
+ }
+ else if (g_strcmp0 (starter_bus, "system") == 0)
+ {
+ ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
+ goto out;
+ }
+ else
+ {
+ if (starter_bus != NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable"
+ " - unknown value `%s'"),
+ starter_bus);
+ }
+ else
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment "
+ "variable is not set"));
+ }
+ }
+ break;
+
+ default:
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Unknown bus type %d"),
+ bus_type);
+ break;
+ }
+
+ out:
+ return ret;
+}
diff --git a/gio/gdbusaddress.h b/gio/gdbusaddress.h
new file mode 100644
index 000000000..feac1a56e
--- /dev/null
+++ b/gio/gdbusaddress.h
@@ -0,0 +1,54 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_ADDRESS_H__
+#define __G_DBUS_ADDRESS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+gboolean g_dbus_is_address (const gchar *string);
+gboolean g_dbus_is_supported_address (const gchar *string,
+ GError **error);
+
+void g_dbus_address_get_stream (const gchar *address,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GIOStream *g_dbus_address_get_stream_finish (GAsyncResult *res,
+ gchar **out_guid,
+ GError **error);
+
+GIOStream *g_dbus_address_get_stream_sync (const gchar *address,
+ gchar **out_guid,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar *g_dbus_address_get_for_bus_sync (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_ADDRESS_H__ */
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
new file mode 100644
index 000000000..850db9aa6
--- /dev/null
+++ b/gio/gdbusauth.c
@@ -0,0 +1,1538 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauth.h"
+#include "gdbusauthmechanismanon.h"
+#include "gdbusauthmechanismexternal.h"
+#include "gdbusauthmechanismsha1.h"
+
+#include "gdbusauthobserver.h"
+
+#include "gdbuserror.h"
+#include "gdbusutils.h"
+#include "gioenumtypes.h"
+#include "gcredentials.h"
+#include "gdbusprivate.h"
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include "gunixcredentialsmessage.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+#define DEBUG_ENABLED 1
+
+static void
+debug_print (const gchar *message, ...)
+{
+#if DEBUG_ENABLED
+ if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+ {
+ gchar *s;
+ GString *str;
+ va_list var_args;
+ guint n;
+
+ va_start (var_args, message);
+ s = g_strdup_vprintf (message, var_args);
+ va_end (var_args);
+
+ str = g_string_new (NULL);
+ for (n = 0; s[n] != '\0'; n++)
+ {
+ if (G_UNLIKELY (s[n] == '\r'))
+ g_string_append (str, "\\r");
+ else if (G_UNLIKELY (s[n] == '\n'))
+ g_string_append (str, "\\n");
+ else
+ g_string_append_c (str, s[n]);
+ }
+ g_print ("GDBus-debug:Auth: %s\n", str->str);
+ g_string_free (str, TRUE);
+ g_free (s);
+ }
+#endif
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* TODO: move to gio */
+
+/**
+ * g_unix_connection_send_credentials:
+ * @connection: A #GUnixConnection.
+ * @credentials: A #GCredentials to send.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Passes the credentials stored in @credentials to the recieving side
+ * of the connection. The recieving end has to call
+ * g_unix_connection_receive_credentials() (or similar) to accept the
+ * credentials.
+ *
+ * The credentials which the sender specifies are checked by the
+ * kernel. A process with effective user ID 0 is allowed to specify
+ * values that do not match its own. This means that the credentials
+ * can be used to authenticate other connections.
+ *
+ * As well as sending the credentials this also writes a single NUL
+ * byte to the stream, as this is required for credentials passing to
+ * work on some implementations.
+ *
+ * Returns: %TRUE on success, %FALSE if @error is set.
+ *
+ * Since: 2.26
+ */
+static gboolean
+g_unix_connection_send_credentials (GUnixConnection *connection,
+ GCredentials *credentials,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSocketControlMessage *scm;
+ GSocket *socket;
+ gboolean ret;
+ GOutputVector vector;
+ guchar nul_byte[1] = {'\0'};
+
+ g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), FALSE);
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+
+ vector.buffer = &nul_byte;
+ vector.size = 1;
+ scm = g_unix_credentials_message_new_with_credentials (credentials);
+ g_object_get (connection, "socket", &socket, NULL);
+ if (g_socket_send_message (socket,
+ NULL, /* address */
+ &vector,
+ 1,
+ &scm,
+ 1,
+ G_SOCKET_MSG_NONE,
+ cancellable,
+ error) != 1)
+ {
+ g_prefix_error (error, _("Error sending credentials: "));
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_object_unref (socket);
+ g_object_unref (scm);
+ return ret;
+}
+
+/**
+ * g_unix_connection_receive_credentials:
+ * @connection: A #GUnixConnection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Receives credentials from the sending end of the connection. The
+ * sending end has to call g_unix_connection_send_credentials() (or
+ * similar) for this to work.
+ *
+ * As well as reading the credentials this also reads (and discards) a
+ * single byte from the stream, as this is required for credentials
+ * passing to work on some implementations.
+ *
+ * Returns: Received credentials on success (free with
+ * g_object_unref()), %NULL if @error is set.
+ *
+ * Since: 2.26
+ */
+static GCredentials *
+g_unix_connection_receive_credentials (GUnixConnection *connection,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GCredentials *ret;
+ GSocketControlMessage **scms;
+ gint nscm;
+ GSocket *socket;
+ gint n;
+ volatile GType credentials_message_gtype;
+ gssize num_bytes_read;
+
+ g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ ret = NULL;
+ scms = NULL;
+
+ g_object_get (connection, "socket", &socket, NULL);
+
+#if 1
+ /* TODO: Move this to gsocket.c... */
+ {
+ int opt_val = 1;
+ if (setsockopt (g_socket_get_fd (socket),
+ SOL_SOCKET,
+ SO_PASSCRED,
+ &opt_val,
+ sizeof opt_val) != 0)
+ {
+ g_warning ("boo, error setting SO_PASSCRED: %m");
+ }
+ }
+#endif
+
+ /* ensure the type of GUnixCredentialsMessage has been registered with the type system */
+ credentials_message_gtype = G_TYPE_UNIX_CREDENTIALS_MESSAGE;
+ num_bytes_read = g_socket_receive_message (socket,
+ NULL, /* GSocketAddress **address */
+ NULL,
+ 0,
+ &scms,
+ &nscm,
+ NULL,
+ cancellable,
+ error);
+ if (num_bytes_read != 1)
+ {
+ /* Handle situation where g_socket_receive_message() returns
+ * 0 bytes and not setting @error
+ */
+ if (num_bytes_read == 0 && error != NULL && *error == NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Expecting to read a single byte for receiving credentials but read zero bytes"));
+ }
+ goto out;
+ }
+
+ if (nscm != 1)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Expecting 1 control message, got %d"),
+ nscm);
+ goto out;
+ }
+
+ if (!G_IS_UNIX_CREDENTIALS_MESSAGE (scms[0]))
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Unexpected type of ancillary data"));
+ goto out;
+ }
+
+ ret = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (scms[0]));
+ g_object_ref (ret);
+
+ out:
+ if (scms != NULL)
+ {
+ for (n = 0; n < nscm; n++)
+ g_object_unref (scms[n]);
+ g_free (scms);
+ }
+ g_object_unref (socket);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ const gchar *name;
+ gint priority;
+ GType gtype;
+} Mechanism;
+
+static void mechanism_free (Mechanism *m);
+
+struct _GDBusAuthPrivate
+{
+ GIOStream *stream;
+
+ /* A list of available Mechanism, sorted according to priority */
+ GList *available_mechanisms;
+};
+
+enum
+{
+ PROP_0,
+ PROP_STREAM
+};
+
+G_DEFINE_TYPE (GDBusAuth, _g_dbus_auth, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_finalize (GObject *object)
+{
+ GDBusAuth *auth = G_DBUS_AUTH (object);
+
+ if (auth->priv->stream != NULL)
+ g_object_unref (auth->priv->stream);
+ g_list_foreach (auth->priv->available_mechanisms, (GFunc) mechanism_free, NULL);
+ g_list_free (auth->priv->available_mechanisms);
+
+ if (G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusAuth *auth = G_DBUS_AUTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ g_value_set_object (value, auth->priv->stream);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_g_dbus_auth_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusAuth *auth = G_DBUS_AUTH (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ auth->priv->stream = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_g_dbus_auth_class_init (GDBusAuthClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->get_property = _g_dbus_auth_get_property;
+ gobject_class->set_property = _g_dbus_auth_set_property;
+ gobject_class->finalize = _g_dbus_auth_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM,
+ g_param_spec_object ("stream",
+ _("IO Stream"),
+ _("The underlying GIOStream used for I/O"),
+ G_TYPE_IO_STREAM,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+}
+
+static void
+mechanism_free (Mechanism *m)
+{
+ g_free (m);
+}
+
+static void
+add_mechanism (GDBusAuth *auth,
+ GType mechanism_type)
+{
+ Mechanism *m;
+
+ m = g_new0 (Mechanism, 1);
+ m->name = _g_dbus_auth_mechanism_get_name (mechanism_type);
+ m->priority = _g_dbus_auth_mechanism_get_priority (mechanism_type);
+ m->gtype = mechanism_type;
+
+ auth->priv->available_mechanisms = g_list_prepend (auth->priv->available_mechanisms, m);
+}
+
+static gint
+mech_compare_func (Mechanism *a, Mechanism *b)
+{
+ gint ret;
+ /* ensure deterministic order */
+ ret = b->priority - a->priority;
+ if (ret == 0)
+ ret = g_strcmp0 (b->name, a->name);
+ return ret;
+}
+
+static void
+_g_dbus_auth_init (GDBusAuth *auth)
+{
+ auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, G_TYPE_DBUS_AUTH, GDBusAuthPrivate);
+
+ /* TODO: trawl extension points */
+ add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_ANON);
+ add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_SHA1);
+ add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL);
+
+ auth->priv->available_mechanisms = g_list_sort (auth->priv->available_mechanisms,
+ (GCompareFunc) mech_compare_func);
+}
+
+static GType
+find_mech_by_name (GDBusAuth *auth,
+ const gchar *name)
+{
+ GType ret;
+ GList *l;
+
+ ret = (GType) 0;
+
+ for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
+ {
+ Mechanism *m = l->data;
+ if (g_strcmp0 (name, m->name) == 0)
+ {
+ ret = m->gtype;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+GDBusAuth *
+_g_dbus_auth_new (GIOStream *stream)
+{
+ return g_object_new (G_TYPE_DBUS_AUTH,
+ "stream", stream,
+ NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* like g_data_input_stream_read_line() but sets error if there's no content to read */
+static gchar *
+_my_g_data_input_stream_read_line (GDataInputStream *dis,
+ gsize *out_line_length,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *ret;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ ret = g_data_input_stream_read_line (dis,
+ out_line_length,
+ cancellable,
+ error);
+ if (ret == NULL && error != NULL && *error == NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Unexpected lack of content trying to read a line"));
+ }
+
+ return ret;
+}
+
+/* This function is to avoid situations like this
+ *
+ * BEGIN\r\nl\0\0\1...
+ *
+ * e.g. where we read into the first D-Bus message while waiting for
+ * the final line from the client (TODO: file bug against gio for
+ * this)
+ */
+static gchar *
+_my_g_input_stream_read_line_safe (GInputStream *i,
+ gsize *out_line_length,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GString *str;
+ gchar c;
+ gssize num_read;
+ gboolean last_was_cr;
+
+ str = g_string_new (NULL);
+
+ last_was_cr = FALSE;
+ while (TRUE)
+ {
+ num_read = g_input_stream_read (i,
+ &c,
+ 1,
+ cancellable,
+ error);
+ if (num_read == -1)
+ goto fail;
+ if (num_read == 0)
+ {
+ if (error != NULL && *error == NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Unexpected lack of content trying to (safely) read a line"));
+ }
+ goto fail;
+ }
+
+ g_string_append_c (str, (gint) c);
+ if (last_was_cr)
+ {
+ if (c == 0x0a)
+ {
+ g_assert (str->len >= 2);
+ g_string_set_size (str, str->len - 2);
+ goto out;
+ }
+ }
+ last_was_cr = (c == 0x0d);
+ }
+
+ out:
+ if (out_line_length != NULL)
+ *out_line_length = str->len;
+ return g_string_free (str, FALSE);
+
+ fail:
+ g_assert (error == NULL || *error != NULL);
+ g_string_free (str, TRUE);
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+append_nibble (GString *s, gint val)
+{
+ g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
+}
+
+static gchar *
+hexdecode (const gchar *str,
+ gsize *out_len,
+ GError **error)
+{
+ gchar *ret;
+ GString *s;
+ guint n;
+
+ ret = NULL;
+ s = g_string_new (NULL);
+
+ for (n = 0; str[n] != '\0'; n += 2)
+ {
+ gint upper_nibble;
+ gint lower_nibble;
+ guint value;
+
+ upper_nibble = g_ascii_xdigit_value (str[n]);
+ lower_nibble = g_ascii_xdigit_value (str[n + 1]);
+ if (upper_nibble == -1 || lower_nibble == -1)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Error hexdecoding string `%s' around position %d",
+ str, n);
+ goto out;
+ }
+ value = (upper_nibble<<4) | lower_nibble;
+ g_string_append_c (s, value);
+ }
+
+ ret = g_string_free (s, FALSE);
+ s = NULL;
+
+ out:
+ if (s != NULL)
+ g_string_free (s, TRUE);
+ return ret;
+}
+
+/* TODO: take len */
+static gchar *
+hexencode (const gchar *str)
+{
+ guint n;
+ GString *s;
+
+ s = g_string_new (NULL);
+ for (n = 0; str[n] != '\0'; n++)
+ {
+ gint val;
+ gint upper_nibble;
+ gint lower_nibble;
+
+ val = ((const guchar *) str)[n];
+ upper_nibble = val >> 4;
+ lower_nibble = val & 0x0f;
+
+ append_nibble (s, upper_nibble);
+ append_nibble (s, lower_nibble);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanism *
+client_choose_mech_and_send_initial_response (GDBusAuth *auth,
+ GCredentials *credentials_that_were_sent,
+ const gchar* const *supported_auth_mechs,
+ GPtrArray *attempted_auth_mechs,
+ GDataOutputStream *dos,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusAuthMechanism *mech;
+ GType auth_mech_to_use_gtype;
+ guint n;
+ guint m;
+ gchar *initial_response;
+ gsize initial_response_len;
+ gchar *encoded;
+ gchar *s;
+
+ again:
+ mech = NULL;
+
+ debug_print ("CLIENT: Trying to choose mechanism");
+
+ /* find an authentication mechanism to try, if any */
+ auth_mech_to_use_gtype = (GType) 0;
+ for (n = 0; supported_auth_mechs[n] != NULL; n++)
+ {
+ gboolean attempted_already;
+ attempted_already = FALSE;
+ for (m = 0; m < attempted_auth_mechs->len; m++)
+ {
+ if (g_strcmp0 (supported_auth_mechs[n], attempted_auth_mechs->pdata[m]) == 0)
+ {
+ attempted_already = TRUE;
+ break;
+ }
+ }
+ if (!attempted_already)
+ {
+ auth_mech_to_use_gtype = find_mech_by_name (auth, supported_auth_mechs[n]);
+ if (auth_mech_to_use_gtype != (GType) 0)
+ break;
+ }
+ }
+
+ if (auth_mech_to_use_gtype == (GType) 0)
+ {
+ guint n;
+ gchar *available;
+ GString *tried_str;
+
+ debug_print ("CLIENT: Exhausted all available mechanisms");
+
+ available = g_strjoinv (", ", (gchar **) supported_auth_mechs);
+
+ tried_str = g_string_new (NULL);
+ for (n = 0; n < attempted_auth_mechs->len; n++)
+ {
+ if (n > 0)
+ g_string_append (tried_str, ", ");
+ g_string_append (tried_str, attempted_auth_mechs->pdata[n]);
+ }
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Exhausted all available authentication mechanisms (tried: %s) (available: %s)"),
+ tried_str->str,
+ available);
+ g_string_free (tried_str, TRUE);
+ g_free (available);
+ goto out;
+ }
+
+ /* OK, decided on a mechanism - let's do this thing */
+ mech = g_object_new (auth_mech_to_use_gtype,
+ "stream", auth->priv->stream,
+ "credentials", credentials_that_were_sent,
+ NULL);
+ debug_print ("CLIENT: Trying mechanism `%s'", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+ g_ptr_array_add (attempted_auth_mechs, (gpointer) _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+
+ /* the auth mechanism may not be supported
+ * (for example, EXTERNAL only works if credentials were exchanged)
+ */
+ if (!_g_dbus_auth_mechanism_is_supported (mech))
+ {
+ debug_print ("CLIENT: Mechanism `%s' says it is not supported", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+ g_object_unref (mech);
+ mech = NULL;
+ goto again;
+ }
+
+ initial_response_len = -1;
+ initial_response = _g_dbus_auth_mechanism_client_initiate (mech,
+ &initial_response_len);
+#if 0
+ g_printerr ("using auth mechanism with name `%s' of type `%s' with initial response `%s'\n",
+ _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
+ g_type_name (G_TYPE_FROM_INSTANCE (mech)),
+ initial_response);
+#endif
+ if (initial_response != NULL)
+ {
+ //g_printerr ("initial_response = `%s'\n", initial_response);
+ encoded = hexencode (initial_response);
+ s = g_strdup_printf ("AUTH %s %s\r\n",
+ _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
+ encoded);
+ g_free (initial_response);
+ g_free (encoded);
+ }
+ else
+ {
+ s = g_strdup_printf ("AUTH %s\r\n", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+ }
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_object_unref (mech);
+ mech = NULL;
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+
+ out:
+ return mech;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+ CLIENT_STATE_WAITING_FOR_DATA,
+ CLIENT_STATE_WAITING_FOR_OK,
+ CLIENT_STATE_WAITING_FOR_REJECT,
+ CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD
+} ClientState;
+
+gchar *
+_g_dbus_auth_run_client (GDBusAuth *auth,
+ GDBusCapabilityFlags offered_capabilities,
+ GDBusCapabilityFlags *out_negotiated_capabilities,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *s;
+ GDataInputStream *dis;
+ GDataOutputStream *dos;
+ GCredentials *credentials;
+ gchar *ret_guid;
+ gchar *line;
+ gsize line_length;
+ gchar **supported_auth_mechs;
+ GPtrArray *attempted_auth_mechs;
+ GDBusAuthMechanism *mech;
+ ClientState state;
+ GDBusCapabilityFlags negotiated_capabilities;
+
+ debug_print ("CLIENT: initiating");
+
+ ret_guid = NULL;
+ supported_auth_mechs = NULL;
+ attempted_auth_mechs = g_ptr_array_new ();
+ mech = NULL;
+ negotiated_capabilities = 0;
+ credentials = NULL;
+
+ dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
+ dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
+
+ g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+#ifdef G_OS_UNIX
+ if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ())
+ {
+ credentials = g_credentials_new_for_process ();
+ if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (auth->priv->stream),
+ credentials,
+ cancellable,
+ error))
+ goto out;
+ }
+ else
+ {
+ if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
+ goto out;
+ }
+#else
+ if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
+ goto out;
+#endif
+
+ if (credentials != NULL)
+ {
+ if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+ {
+ s = g_credentials_to_string (credentials);
+ debug_print ("CLIENT: sent credentials `%s'", s);
+ g_free (s);
+ }
+ }
+ else
+ {
+ debug_print ("CLIENT: didn't send any credentials");
+ }
+
+ /* TODO: to reduce rountrips, try to pick an auth mechanism to start with */
+
+ /* Get list of supported authentication mechanisms */
+ s = "AUTH\r\n";
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ state = CLIENT_STATE_WAITING_FOR_REJECT;
+
+ while (TRUE)
+ {
+ switch (state)
+ {
+ case CLIENT_STATE_WAITING_FOR_REJECT:
+ debug_print ("CLIENT: WaitingForReject");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ if (line == NULL)
+ goto out;
+ debug_print ("CLIENT: WaitingForReject, read '%s'", line);
+ foobar:
+ if (!g_str_has_prefix (line, "REJECTED "))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "In WaitingForReject: Expected `REJECTED am1 am2 ... amN', got `%s'",
+ line);
+ g_free (line);
+ goto out;
+ }
+ if (supported_auth_mechs == NULL)
+ {
+ supported_auth_mechs = g_strsplit (line + sizeof ("REJECTED ") - 1, " ", 0);
+#if 0
+ for (n = 0; supported_auth_mechs != NULL && supported_auth_mechs[n] != NULL; n++)
+ g_printerr ("supported_auth_mechs[%d] = `%s'\n", n, supported_auth_mechs[n]);
+#endif
+ }
+ g_free (line);
+ mech = client_choose_mech_and_send_initial_response (auth,
+ credentials,
+ (const gchar* const *) supported_auth_mechs,
+ attempted_auth_mechs,
+ dos,
+ cancellable,
+ error);
+ if (mech == NULL)
+ goto out;
+ if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA)
+ state = CLIENT_STATE_WAITING_FOR_DATA;
+ else
+ state = CLIENT_STATE_WAITING_FOR_OK;
+ break;
+
+ case CLIENT_STATE_WAITING_FOR_OK:
+ debug_print ("CLIENT: WaitingForOK");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ if (line == NULL)
+ goto out;
+ debug_print ("CLIENT: WaitingForOK, read `%s'", line);
+ if (g_str_has_prefix (line, "OK "))
+ {
+ if (!g_dbus_is_guid (line + 3))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Invalid OK response `%s'",
+ line);
+ g_free (line);
+ goto out;
+ }
+ ret_guid = g_strdup (line + 3);
+ g_free (line);
+
+ if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+ {
+ s = "NEGOTIATE_UNIX_FD\r\n";
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ state = CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD;
+ }
+ else
+ {
+ s = "BEGIN\r\n";
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ /* and we're done! */
+ goto out;
+ }
+ }
+ else if (g_str_has_prefix (line, "REJECTED "))
+ {
+ goto foobar;
+ }
+ else
+ {
+ /* TODO: handle other valid responses */
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "In WaitingForOk: unexpected response `%s'",
+ line);
+ g_free (line);
+ goto out;
+ }
+ break;
+
+ case CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD:
+ debug_print ("CLIENT: WaitingForAgreeUnixFD");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ if (line == NULL)
+ goto out;
+ debug_print ("CLIENT: WaitingForAgreeUnixFD, read=`%s'", line);
+ if (g_strcmp0 (line, "AGREE_UNIX_FD") == 0)
+ {
+ negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+ s = "BEGIN\r\n";
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ /* and we're done! */
+ goto out;
+ }
+ else if (g_str_has_prefix (line, "ERROR") && (line[5] == 0 || g_ascii_isspace (line[5])))
+ {
+ //g_strstrip (line + 5); g_debug ("bah, no unix_fd: `%s'", line + 5);
+ g_free (line);
+ s = "BEGIN\r\n";
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ /* and we're done! */
+ goto out;
+ }
+ else
+ {
+ /* TODO: handle other valid responses */
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "In WaitingForAgreeUnixFd: unexpected response `%s'",
+ line);
+ g_free (line);
+ goto out;
+ }
+ break;
+
+ case CLIENT_STATE_WAITING_FOR_DATA:
+ debug_print ("CLIENT: WaitingForData");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ if (line == NULL)
+ goto out;
+ debug_print ("CLIENT: WaitingForData, read=`%s'", line);
+ if (g_str_has_prefix (line, "DATA "))
+ {
+ gchar *encoded;
+ gchar *decoded_data;
+ gsize decoded_data_len;
+
+ encoded = g_strdup (line + 5);
+ g_free (line);
+ g_strstrip (encoded);
+ decoded_data = hexdecode (encoded, &decoded_data_len, error);
+ g_free (encoded);
+ if (decoded_data == NULL)
+ {
+ g_prefix_error (error, "DATA response is malformed: ");
+ /* invalid encoding, disconnect! */
+ goto out;
+ }
+ _g_dbus_auth_mechanism_client_data_receive (mech, decoded_data, decoded_data_len);
+ g_free (decoded_data);
+
+ if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND)
+ {
+ gchar *data;
+ gsize data_len;
+ gchar *encoded_data;
+ data = _g_dbus_auth_mechanism_client_data_send (mech, &data_len);
+ encoded_data = hexencode (data);
+ s = g_strdup_printf ("DATA %s\r\n", encoded_data);
+ g_free (encoded_data);
+ g_free (data);
+ debug_print ("CLIENT: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+ }
+ state = CLIENT_STATE_WAITING_FOR_OK;
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "In WaitingForData: unexpected response `%s'",
+ line);
+ g_free (line);
+ goto out;
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ }; /* main authentication client loop */
+
+ out:
+ if (mech != NULL)
+ g_object_unref (mech);
+ g_ptr_array_unref (attempted_auth_mechs);
+ g_strfreev (supported_auth_mechs);
+ g_object_ref (dis);
+ g_object_ref (dos);
+
+ /* ensure return value is NULL if error is set */
+ if (error != NULL && *error != NULL)
+ {
+ g_free (ret_guid);
+ ret_guid = NULL;
+ }
+
+ if (ret_guid != NULL)
+ {
+ if (out_negotiated_capabilities != NULL)
+ *out_negotiated_capabilities = negotiated_capabilities;
+ }
+
+ if (credentials != NULL)
+ g_object_unref (credentials);
+
+ debug_print ("CLIENT: Done, authenticated=%d", ret_guid != NULL);
+
+ return ret_guid;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_auth_mechanisms (GDBusAuth *auth,
+ gboolean allow_anonymous,
+ const gchar *prefix,
+ const gchar *suffix,
+ const gchar *separator)
+{
+ GList *l;
+ GString *str;
+ gboolean need_sep;
+
+ str = g_string_new (prefix);
+ need_sep = FALSE;
+ for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
+ {
+ Mechanism *m = l->data;
+
+ if (!allow_anonymous && g_strcmp0 (m->name, "ANONYMOUS") == 0)
+ continue;
+
+ if (need_sep)
+ g_string_append (str, separator);
+ g_string_append (str, m->name);
+ need_sep = TRUE;
+ }
+
+ g_string_append (str, suffix);
+ return g_string_free (str, FALSE);
+}
+
+
+typedef enum
+{
+ SERVER_STATE_WAITING_FOR_AUTH,
+ SERVER_STATE_WAITING_FOR_DATA,
+ SERVER_STATE_WAITING_FOR_BEGIN
+} ServerState;
+
+gboolean
+_g_dbus_auth_run_server (GDBusAuth *auth,
+ GDBusAuthObserver *observer,
+ const gchar *guid,
+ gboolean allow_anonymous,
+ GDBusCapabilityFlags offered_capabilities,
+ GDBusCapabilityFlags *out_negotiated_capabilities,
+ GCredentials **out_received_credentials,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ ServerState state;
+ GDataInputStream *dis;
+ GDataOutputStream *dos;
+ GError *local_error;
+ guchar byte;
+ gchar *line;
+ gsize line_length;
+ GDBusAuthMechanism *mech;
+ gchar *s;
+ GDBusCapabilityFlags negotiated_capabilities;
+ GCredentials *credentials;
+
+ debug_print ("SERVER: initiating");
+
+ ret = FALSE;
+ dis = NULL;
+ dos = NULL;
+ mech = NULL;
+ negotiated_capabilities = 0;
+ credentials = NULL;
+
+ if (!g_dbus_is_guid (guid))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "The given guid `%s' is not valid",
+ guid);
+ goto out;
+ }
+
+ dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
+ dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
+
+ g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+ /* first read the NUL-byte (TODO: read credentials if using a unix domain socket) */
+#ifdef G_OS_UNIX
+ if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ())
+ {
+ local_error = NULL;
+ credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream),
+ cancellable,
+ &local_error);
+ if (credentials == NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ }
+ else
+ {
+ local_error = NULL;
+ byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ }
+#else
+ local_error = NULL;
+ byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+#endif
+ if (credentials != NULL)
+ {
+ if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+ {
+ s = g_credentials_to_string (credentials);
+ debug_print ("SERVER: received credentials `%s'", s);
+ g_free (s);
+ }
+ }
+ else
+ {
+ debug_print ("SERVER: didn't receive any credentials");
+ }
+
+ state = SERVER_STATE_WAITING_FOR_AUTH;
+ while (TRUE)
+ {
+ switch (state)
+ {
+ case SERVER_STATE_WAITING_FOR_AUTH:
+ debug_print ("SERVER: WaitingForAuth");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ debug_print ("SERVER: WaitingForAuth, read `%s'", line);
+ if (line == NULL)
+ goto out;
+ if (g_strcmp0 (line, "AUTH") == 0)
+ {
+ s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+ g_free (line);
+ }
+ else if (g_str_has_prefix (line, "AUTH "))
+ {
+ gchar **tokens;
+ const gchar *encoded;
+ const gchar *mech_name;
+ GType auth_mech_to_use_gtype;
+
+ tokens = g_strsplit (line, " ", 0);
+ g_free (line);
+
+ switch (g_strv_length (tokens))
+ {
+ case 2:
+ /* no initial response */
+ mech_name = tokens[1];
+ encoded = NULL;
+ break;
+
+ case 3:
+ /* initial response */
+ mech_name = tokens[1];
+ encoded = tokens[2];
+ break;
+
+ default:
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Unexpected line `%s' while in WaitingForAuth state",
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ /* TODO: record that the client has attempted to use this mechanism */
+ //g_debug ("client is trying `%s'", mech_name);
+
+ auth_mech_to_use_gtype = find_mech_by_name (auth, mech_name);
+ if ((auth_mech_to_use_gtype == (GType) 0) ||
+ (!allow_anonymous && g_strcmp0 (mech_name, "ANONYMOUS") == 0))
+ {
+ /* We don't support this auth mechanism */
+ g_strfreev (tokens);
+ s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+
+ /* stay in WAITING FOR AUTH */
+ state = SERVER_STATE_WAITING_FOR_AUTH;
+ }
+ else
+ {
+ gchar *initial_response;
+ gsize initial_response_len;
+
+ mech = g_object_new (auth_mech_to_use_gtype,
+ "stream", auth->priv->stream,
+ "credentials", credentials,
+ NULL);
+
+ initial_response = NULL;
+ initial_response_len = 0;
+ if (encoded != NULL)
+ {
+ initial_response = hexdecode (encoded, &initial_response_len, error);
+ if (initial_response == NULL)
+ {
+ g_prefix_error (error, "Initial response is malformed: ");
+ /* invalid encoding, disconnect! */
+ g_strfreev (tokens);
+ goto out;
+ }
+ }
+
+ _g_dbus_auth_mechanism_server_initiate (mech,
+ initial_response,
+ initial_response_len);
+ g_free (initial_response);
+ g_strfreev (tokens);
+
+ change_state:
+ switch (_g_dbus_auth_mechanism_server_get_state (mech))
+ {
+ case G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED:
+ if (observer != NULL &&
+ g_dbus_auth_observer_deny_authenticated_peer (observer,
+ auth->priv->stream,
+ credentials))
+ {
+ /* disconnect */
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Cancelled via GDBusAuthObserver::deny-authenticated-peer"));
+ goto out;
+ }
+ else
+ {
+ s = g_strdup_printf ("OK %s\r\n", guid);
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+ state = SERVER_STATE_WAITING_FOR_BEGIN;
+ }
+ break;
+
+ case G_DBUS_AUTH_MECHANISM_STATE_REJECTED:
+ s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+ state = SERVER_STATE_WAITING_FOR_AUTH;
+ break;
+
+ case G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA:
+ state = SERVER_STATE_WAITING_FOR_DATA;
+ break;
+
+ case G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND:
+ {
+ gchar *data;
+ gsize data_len;
+ gchar *encoded_data;
+ data = _g_dbus_auth_mechanism_server_data_send (mech, &data_len);
+ encoded_data = hexencode (data);
+ s = g_strdup_printf ("DATA %s\r\n", encoded_data);
+ g_free (encoded_data);
+ g_free (data);
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ {
+ g_free (s);
+ goto out;
+ }
+ g_free (s);
+ }
+ goto change_state;
+ break;
+
+ default:
+ /* TODO */
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Unexpected line `%s' while in WaitingForAuth state",
+ line);
+ g_free (line);
+ goto out;
+ }
+ break;
+
+ case SERVER_STATE_WAITING_FOR_DATA:
+ debug_print ("SERVER: WaitingForData");
+ line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ debug_print ("SERVER: WaitingForData, read `%s'", line);
+ if (line == NULL)
+ goto out;
+ if (g_str_has_prefix (line, "DATA "))
+ {
+ gchar *encoded;
+ gchar *decoded_data;
+ gsize decoded_data_len;
+
+ encoded = g_strdup (line + 5);
+ g_free (line);
+ g_strstrip (encoded);
+ decoded_data = hexdecode (encoded, &decoded_data_len, error);
+ g_free (encoded);
+ if (decoded_data == NULL)
+ {
+ g_prefix_error (error, "DATA response is malformed: ");
+ /* invalid encoding, disconnect! */
+ goto out;
+ }
+ _g_dbus_auth_mechanism_server_data_receive (mech, decoded_data, decoded_data_len);
+ g_free (decoded_data);
+ /* oh man, this goto-crap is so ugly.. really need to rewrite the state machine */
+ goto change_state;
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Unexpected line `%s' while in WaitingForData state",
+ line);
+ g_free (line);
+ }
+ goto out;
+
+ case SERVER_STATE_WAITING_FOR_BEGIN:
+ debug_print ("SERVER: WaitingForBegin");
+ /* Use extremely slow (but reliable) line reader - this basically
+ * does a recvfrom() system call per character
+ *
+ * (the problem with using GDataInputStream's read_line is that because of
+ * buffering it might start reading into the first D-Bus message that
+ * appears after "BEGIN\r\n"....)
+ */
+ line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
+ &line_length,
+ cancellable,
+ error);
+ debug_print ("SERVER: WaitingForBegin, read `%s'", line);
+ if (line == NULL)
+ goto out;
+ if (g_strcmp0 (line, "BEGIN") == 0)
+ {
+ /* YAY, done! */
+ ret = TRUE;
+ g_free (line);
+ goto out;
+ }
+ else if (g_strcmp0 (line, "NEGOTIATE_UNIX_FD") == 0)
+ {
+ if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+ {
+ negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+ s = "AGREE_UNIX_FD\r\n";
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ }
+ else
+ {
+ s = "ERROR \"fd passing not offered\"\r\n";
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ }
+ }
+ else
+ {
+ g_debug ("Unexpected line `%s' while in WaitingForBegin state", line);
+ g_free (line);
+ s = "ERROR \"Unknown Command\"\r\n";
+ debug_print ("SERVER: writing `%s'", s);
+ if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+ goto out;
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Not implemented (server)");
+
+ out:
+ if (mech != NULL)
+ g_object_unref (mech);
+ if (dis != NULL)
+ g_object_ref (dis);
+ if (dis != NULL)
+ g_object_ref (dos);
+
+ /* ensure return value is FALSE if error is set */
+ if (error != NULL && *error != NULL)
+ {
+ ret = FALSE;
+ }
+
+ if (ret)
+ {
+ if (out_negotiated_capabilities != NULL)
+ *out_negotiated_capabilities = negotiated_capabilities;
+ if (out_received_credentials != NULL)
+ *out_received_credentials = credentials != NULL ? g_object_ref (credentials) : NULL;
+ }
+
+ if (credentials != NULL)
+ g_object_unref (credentials);
+
+ debug_print ("SERVER: Done, authenticated=%d", ret);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauth.h b/gio/gdbusauth.h
new file mode 100644
index 000000000..2fc750017
--- /dev/null
+++ b/gio/gdbusauth.h
@@ -0,0 +1,86 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauth.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_H__
+#define __G_DBUS_AUTH_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH (_g_dbus_auth_get_type ())
+#define G_DBUS_AUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH, GDBusAuth))
+#define G_DBUS_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH, GDBusAuthClass))
+#define G_DBUS_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH, GDBusAuthClass))
+#define G_IS_DBUS_AUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH))
+#define G_IS_DBUS_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH))
+
+typedef struct _GDBusAuth GDBusAuth;
+typedef struct _GDBusAuthClass GDBusAuthClass;
+typedef struct _GDBusAuthPrivate GDBusAuthPrivate;
+
+struct _GDBusAuthClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+};
+
+struct _GDBusAuth
+{
+ GObject parent_instance;
+ GDBusAuthPrivate *priv;
+};
+
+GType _g_dbus_auth_get_type (void) G_GNUC_CONST;
+GDBusAuth *_g_dbus_auth_new (GIOStream *stream);
+
+/* TODO: need a way to set allowed authentication mechanisms */
+
+/* TODO: need a way to convey credentials etc. */
+
+/* TODO: need a way to convey negotiated features (e.g. returning flags from e.g. GDBusConnectionFeatures) */
+
+/* TODO: need to expose encode()/decode() from the AuthMechanism (and whether it is needed at all) */
+
+gboolean _g_dbus_auth_run_server (GDBusAuth *auth,
+ GDBusAuthObserver *observer,
+ const gchar *guid,
+ gboolean allow_anonymous,
+ GDBusCapabilityFlags offered_capabilities,
+ GDBusCapabilityFlags *out_negotiated_capabilities,
+ GCredentials **out_received_credentials,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar *_g_dbus_auth_run_client (GDBusAuth *auth,
+ GDBusCapabilityFlags offered_capabilities,
+ GDBusCapabilityFlags *out_negotiated_capabilities,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_H__ */
diff --git a/gio/gdbusauthmechanism.c b/gio/gdbusauthmechanism.c
new file mode 100644
index 000000000..72eff74be
--- /dev/null
+++ b/gio/gdbusauthmechanism.c
@@ -0,0 +1,342 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanism.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "giostream.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _GDBusAuthMechanismPrivate
+{
+ GIOStream *stream;
+ GCredentials *credentials;
+};
+
+enum
+{
+ PROP_0,
+ PROP_STREAM,
+ PROP_CREDENTIALS
+};
+
+G_DEFINE_ABSTRACT_TYPE (GDBusAuthMechanism, _g_dbus_auth_mechanism, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_finalize (GObject *object)
+{
+ GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+ if (mechanism->priv->stream != NULL)
+ g_object_unref (mechanism->priv->stream);
+ if (mechanism->priv->credentials != NULL)
+ g_object_unref (mechanism->priv->credentials);
+
+ if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (_g_dbus_auth_mechanism_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ g_value_set_object (value, mechanism->priv->stream);
+ break;
+
+ case PROP_CREDENTIALS:
+ g_value_set_object (value, mechanism->priv->credentials);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_g_dbus_auth_mechanism_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ mechanism->priv->stream = g_value_dup_object (value);
+ break;
+
+ case PROP_CREDENTIALS:
+ mechanism->priv->credentials = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_g_dbus_auth_mechanism_class_init (GDBusAuthMechanismClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthMechanismPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->get_property = _g_dbus_auth_mechanism_get_property;
+ gobject_class->set_property = _g_dbus_auth_mechanism_set_property;
+ gobject_class->finalize = _g_dbus_auth_mechanism_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM,
+ g_param_spec_object ("stream",
+ _("IO Stream"),
+ _("The underlying GIOStream used for I/O"),
+ G_TYPE_IO_STREAM,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusAuthMechanism:credentials:
+ *
+ * If authenticating as a server, this property contains the
+ * received credentials, if any.
+ *
+ * If authenticating as a client, the property contains the
+ * credentials that were sent, if any.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CREDENTIALS,
+ g_param_spec_object ("credentials",
+ _("Credentials"),
+ _("The credentials of the remote peer"),
+ G_TYPE_CREDENTIALS,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+}
+
+static void
+_g_dbus_auth_mechanism_init (GDBusAuthMechanism *mechanism)
+{
+ /* not used for now */
+ mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+ G_TYPE_DBUS_AUTH_MECHANISM,
+ GDBusAuthMechanismPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GIOStream *
+_g_dbus_auth_mechanism_get_stream (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return mechanism->priv->stream;
+}
+
+GCredentials *
+_g_dbus_auth_mechanism_get_credentials (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return mechanism->priv->credentials;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+const gchar *
+_g_dbus_auth_mechanism_get_name (GType mechanism_type)
+{
+ const gchar *name;
+ GDBusAuthMechanismClass *klass;
+
+ g_return_val_if_fail (g_type_is_a (mechanism_type, G_TYPE_DBUS_AUTH_MECHANISM), NULL);
+
+ klass = g_type_class_ref (mechanism_type);
+ g_assert (klass != NULL);
+ name = klass->get_name ();
+ //g_type_class_unref (klass);
+
+ return name;
+}
+
+gint
+_g_dbus_auth_mechanism_get_priority (GType mechanism_type)
+{
+ gint priority;
+ GDBusAuthMechanismClass *klass;
+
+ g_return_val_if_fail (g_type_is_a (mechanism_type, G_TYPE_DBUS_AUTH_MECHANISM), 0);
+
+ klass = g_type_class_ref (mechanism_type);
+ g_assert (klass != NULL);
+ priority = klass->get_priority ();
+ //g_type_class_unref (klass);
+
+ return priority;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean
+_g_dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), FALSE);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->is_supported (mechanism);
+}
+
+gchar *
+_g_dbus_auth_mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->encode_data (mechanism, data, data_len, out_data_len);
+}
+
+
+gchar *
+_g_dbus_auth_mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->decode_data (mechanism, data, data_len, out_data_len);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusAuthMechanismState
+_g_dbus_auth_mechanism_server_get_state (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_state (mechanism);
+}
+
+void
+_g_dbus_auth_mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len)
+{
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_initiate (mechanism, initial_response, initial_response_len);
+}
+
+void
+_g_dbus_auth_mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_receive (mechanism, data, data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_send (mechanism, out_data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_reject_reason (mechanism);
+}
+
+void
+_g_dbus_auth_mechanism_server_shutdown (GDBusAuthMechanism *mechanism)
+{
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_shutdown (mechanism);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusAuthMechanismState
+_g_dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_state (mechanism);
+}
+
+gchar *
+_g_dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_initiate (mechanism,
+ out_initial_response_len);
+}
+
+void
+_g_dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_receive (mechanism, data, data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_send (mechanism, out_data_len);
+}
+
+void
+_g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism)
+{
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_shutdown (mechanism);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h
new file mode 100644
index 000000000..e00cb1753
--- /dev/null
+++ b/gio/gdbusauthmechanism.h
@@ -0,0 +1,174 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanism.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_H__
+#define __G_DBUS_AUTH_MECHANISM_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM (_g_dbus_auth_mechanism_get_type ())
+#define G_DBUS_AUTH_MECHANISM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanism))
+#define G_DBUS_AUTH_MECHANISM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass))
+#define G_DBUS_AUTH_MECHANISM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass))
+#define G_IS_DBUS_AUTH_MECHANISM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM))
+#define G_IS_DBUS_AUTH_MECHANISM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM))
+
+typedef struct _GDBusAuthMechanism GDBusAuthMechanism;
+typedef struct _GDBusAuthMechanismClass GDBusAuthMechanismClass;
+typedef struct _GDBusAuthMechanismPrivate GDBusAuthMechanismPrivate;
+
+typedef enum {
+ G_DBUS_AUTH_MECHANISM_STATE_INVALID,
+ G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA,
+ G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND,
+ G_DBUS_AUTH_MECHANISM_STATE_REJECTED,
+ G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED,
+} GDBusAuthMechanismState;
+
+struct _GDBusAuthMechanismClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+
+ /* VTable */
+
+ /* TODO: server_initiate and client_initiate probably needs to have a
+ * GCredentials parameter...
+ */
+
+ gint (*get_priority) (void);
+ const gchar *(*get_name) (void);
+
+ /* functions shared by server/client */
+ gboolean (*is_supported) (GDBusAuthMechanism *mechanism);
+ gchar *(*encode_data) (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+ gchar *(*decode_data) (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+
+ /* functions for server-side authentication */
+ GDBusAuthMechanismState (*server_get_state) (GDBusAuthMechanism *mechanism);
+ void (*server_initiate) (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len);
+ void (*server_data_receive) (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+ gchar *(*server_data_send) (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+ gchar *(*server_get_reject_reason) (GDBusAuthMechanism *mechanism);
+ void (*server_shutdown) (GDBusAuthMechanism *mechanism);
+
+ /* functions for client-side authentication */
+ GDBusAuthMechanismState (*client_get_state) (GDBusAuthMechanism *mechanism);
+ gchar *(*client_initiate) (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len);
+ void (*client_data_receive) (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+ gchar *(*client_data_send) (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+ void (*client_shutdown) (GDBusAuthMechanism *mechanism);
+
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+ void (*_g_reserved9) (void);
+ void (*_g_reserved10) (void);
+ void (*_g_reserved11) (void);
+ void (*_g_reserved12) (void);
+ void (*_g_reserved13) (void);
+ void (*_g_reserved14) (void);
+ void (*_g_reserved15) (void);
+ void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanism
+{
+ GObject parent_instance;
+ GDBusAuthMechanismPrivate *priv;
+};
+
+GType _g_dbus_auth_mechanism_get_type (void) G_GNUC_CONST;
+
+gint _g_dbus_auth_mechanism_get_priority (GType mechanism_type);
+const gchar *_g_dbus_auth_mechanism_get_name (GType mechanism_type);
+
+GIOStream *_g_dbus_auth_mechanism_get_stream (GDBusAuthMechanism *mechanism);
+GCredentials *_g_dbus_auth_mechanism_get_credentials (GDBusAuthMechanism *mechanism);
+
+gboolean _g_dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism);
+gchar *_g_dbus_auth_mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+gchar *_g_dbus_auth_mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+
+GDBusAuthMechanismState _g_dbus_auth_mechanism_server_get_state (GDBusAuthMechanism *mechanism);
+void _g_dbus_auth_mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len);
+void _g_dbus_auth_mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+gchar *_g_dbus_auth_mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+gchar *_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism);
+void _g_dbus_auth_mechanism_server_shutdown (GDBusAuthMechanism *mechanism);
+
+GDBusAuthMechanismState _g_dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism);
+gchar *_g_dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len);
+void _g_dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+gchar *_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+void _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism);
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_H__ */
diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c
new file mode 100644
index 000000000..4c666ec1f
--- /dev/null
+++ b/gio/gdbusauthmechanismanon.c
@@ -0,0 +1,327 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismanon.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+
+struct _GDBusAuthMechanismAnonPrivate
+{
+ gboolean is_client;
+ gboolean is_server;
+ GDBusAuthMechanismState state;
+};
+
+static gint mechanism_get_priority (void);
+static const gchar *mechanism_get_name (void);
+
+static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism);
+static void mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len);
+static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism);
+static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism);
+static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len);
+static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismAnon, _g_dbus_auth_mechanism_anon, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_anon_finalize (GObject *object)
+{
+ //GDBusAuthMechanismAnon *mechanism = G_DBUS_AUTH_MECHANISM_ANON (object);
+
+ if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_anon_class_init (GDBusAuthMechanismAnonClass *klass)
+{
+ GObjectClass *gobject_class;
+ GDBusAuthMechanismClass *mechanism_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthMechanismAnonPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = _g_dbus_auth_mechanism_anon_finalize;
+
+ mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+ mechanism_class->get_priority = mechanism_get_priority;
+ mechanism_class->get_name = mechanism_get_name;
+ mechanism_class->is_supported = mechanism_is_supported;
+ mechanism_class->encode_data = mechanism_encode_data;
+ mechanism_class->decode_data = mechanism_decode_data;
+ mechanism_class->server_get_state = mechanism_server_get_state;
+ mechanism_class->server_initiate = mechanism_server_initiate;
+ mechanism_class->server_data_receive = mechanism_server_data_receive;
+ mechanism_class->server_data_send = mechanism_server_data_send;
+ mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason;
+ mechanism_class->server_shutdown = mechanism_server_shutdown;
+ mechanism_class->client_get_state = mechanism_client_get_state;
+ mechanism_class->client_initiate = mechanism_client_initiate;
+ mechanism_class->client_data_receive = mechanism_client_data_receive;
+ mechanism_class->client_data_send = mechanism_client_data_send;
+ mechanism_class->client_shutdown = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_anon_init (GDBusAuthMechanismAnon *mechanism)
+{
+ mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+ G_TYPE_DBUS_AUTH_MECHANISM_ANON,
+ GDBusAuthMechanismAnonPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static gint
+mechanism_get_priority (void)
+{
+ /* We prefer ANONYMOUS to most other mechanism (such as DBUS_COOKIE_SHA1) but not to EXTERNAL */
+ return 50;
+}
+
+
+static const gchar *
+mechanism_get_name (void)
+{
+ return "ANONYMOUS";
+}
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), FALSE);
+ return TRUE;
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+ g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+ if (initial_response != NULL)
+ g_debug ("ANONYMOUS: initial_response was `%s'", initial_response);
+
+ m->priv->is_server = TRUE;
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ /* can never end up here because we are never in the WAITING_FOR_DATA state */
+ g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+ /* can never end up here because we are never in the REJECTED state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+ m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+ g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+ m->priv->is_client = TRUE;
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+ *out_initial_response_len = -1;
+
+ /* just return our library name and version */
+ return g_strdup ("GDBus 0.1");
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ /* can never end up here because we are never in the WAITING_FOR_DATA state */
+ g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+ m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismanon.h b/gio/gdbusauthmechanismanon.h
new file mode 100644
index 000000000..d3c2c2472
--- /dev/null
+++ b/gio/gdbusauthmechanismanon.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismanon.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_ANON_H__
+#define __G_DBUS_AUTH_MECHANISM_ANON_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_ANON (_g_dbus_auth_mechanism_anon_get_type ())
+#define G_DBUS_AUTH_MECHANISM_ANON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnon))
+#define G_DBUS_AUTH_MECHANISM_ANON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass))
+#define G_DBUS_AUTH_MECHANISM_ANON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass))
+#define G_IS_DBUS_AUTH_MECHANISM_ANON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON))
+#define G_IS_DBUS_AUTH_MECHANISM_ANON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON))
+
+typedef struct _GDBusAuthMechanismAnon GDBusAuthMechanismAnon;
+typedef struct _GDBusAuthMechanismAnonClass GDBusAuthMechanismAnonClass;
+typedef struct _GDBusAuthMechanismAnonPrivate GDBusAuthMechanismAnonPrivate;
+
+struct _GDBusAuthMechanismAnonClass
+{
+ /*< private >*/
+ GDBusAuthMechanismClass parent_class;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+ void (*_g_reserved9) (void);
+ void (*_g_reserved10) (void);
+ void (*_g_reserved11) (void);
+ void (*_g_reserved12) (void);
+ void (*_g_reserved13) (void);
+ void (*_g_reserved14) (void);
+ void (*_g_reserved15) (void);
+ void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismAnon
+{
+ GDBusAuthMechanism parent_instance;
+ GDBusAuthMechanismAnonPrivate *priv;
+};
+
+GType _g_dbus_auth_mechanism_anon_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_ANON_H__ */
diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c
new file mode 100644
index 000000000..bf8d9318a
--- /dev/null
+++ b/gio/gdbusauthmechanismexternal.c
@@ -0,0 +1,416 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismexternal.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+struct _GDBusAuthMechanismExternalPrivate
+{
+ gboolean is_client;
+ gboolean is_server;
+ GDBusAuthMechanismState state;
+};
+
+static gint mechanism_get_priority (void);
+static const gchar *mechanism_get_name (void);
+
+static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism);
+static void mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len);
+static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism);
+static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism);
+static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len);
+static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismExternal, _g_dbus_auth_mechanism_external, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_external_finalize (GObject *object)
+{
+ //GDBusAuthMechanismExternal *mechanism = G_DBUS_AUTH_MECHANISM_EXTERNAL (object);
+
+ if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *klass)
+{
+ GObjectClass *gobject_class;
+ GDBusAuthMechanismClass *mechanism_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthMechanismExternalPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = _g_dbus_auth_mechanism_external_finalize;
+
+ mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+ mechanism_class->get_name = mechanism_get_name;
+ mechanism_class->get_priority = mechanism_get_priority;
+ mechanism_class->is_supported = mechanism_is_supported;
+ mechanism_class->encode_data = mechanism_encode_data;
+ mechanism_class->decode_data = mechanism_decode_data;
+ mechanism_class->server_get_state = mechanism_server_get_state;
+ mechanism_class->server_initiate = mechanism_server_initiate;
+ mechanism_class->server_data_receive = mechanism_server_data_receive;
+ mechanism_class->server_data_send = mechanism_server_data_send;
+ mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason;
+ mechanism_class->server_shutdown = mechanism_server_shutdown;
+ mechanism_class->client_get_state = mechanism_client_get_state;
+ mechanism_class->client_initiate = mechanism_client_initiate;
+ mechanism_class->client_data_receive = mechanism_client_data_receive;
+ mechanism_class->client_data_send = mechanism_client_data_send;
+ mechanism_class->client_shutdown = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_external_init (GDBusAuthMechanismExternal *mechanism)
+{
+ mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+ G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL,
+ GDBusAuthMechanismExternalPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), FALSE);
+ /* This mechanism is only available if credentials has been exchanged */
+ if (_g_dbus_auth_mechanism_get_credentials (mechanism) != NULL)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gint
+mechanism_get_priority (void)
+{
+ /* We prefer EXTERNAL to most other mechanism (DBUS_COOKIE_SHA1 and ANONYMOUS) */
+ return 100;
+}
+
+static const gchar *
+mechanism_get_name (void)
+{
+ return "EXTERNAL";
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static gboolean
+data_matches_credentials (const gchar *data,
+ GCredentials *credentials)
+{
+ gboolean match;
+
+ match = FALSE;
+
+ if (credentials == NULL)
+ goto out;
+
+ if (data == NULL || strlen (data) == 0)
+ goto out;
+
+#if defined(G_OS_UNIX)
+ {
+ gint64 alleged_uid;
+ gchar *endp;
+
+ /* on UNIX, this is the uid as a string in base 10 */
+ alleged_uid = g_ascii_strtoll (data, &endp, 10);
+ if (*endp == '\0')
+ {
+ if (g_credentials_has_unix_user (credentials) &&
+ g_credentials_get_unix_user (credentials) == alleged_uid)
+ {
+ match = TRUE;
+ }
+ }
+ }
+#elif defined(G_OS_WIN32)
+ {
+ const gchar *alleged_sid;
+
+ /* on Win32, this is the User SID */
+ alleged_sid = data;
+ if (g_credentials_has_windows_user (credentials) &&
+ g_strcmp0 (g_credentials_get_windows_user (credentials), alleged_sid) == 0)
+ {
+ match = TRUE;
+ }
+ }
+#else
+#error Dont know how to read credentials on this OS. Please implement.
+#endif
+
+ out:
+ return match;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+ g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+ m->priv->is_server = TRUE;
+
+ if (initial_response != NULL)
+ {
+ if (data_matches_credentials (initial_response, _g_dbus_auth_mechanism_get_credentials (mechanism)))
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+ }
+ else
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ }
+ }
+ else
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+ }
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ if (data_matches_credentials (data, _g_dbus_auth_mechanism_get_credentials (mechanism)))
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+ }
+ else
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ }
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+ /* can never end up here because we are never in the REJECTED state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+ m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+ gchar *initial_response;
+ GCredentials *credentials;
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+ g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+ m->priv->is_client = TRUE;
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+ *out_initial_response_len = -1;
+
+ credentials = _g_dbus_auth_mechanism_get_credentials (mechanism);
+ g_assert (credentials != NULL);
+
+ /* return the uid */
+#if defined(G_OS_UNIX)
+ initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, g_credentials_get_unix_user (credentials));
+#elif defined(G_OS_WIN32)
+ initial_response = g_strdup_printf ("%s", g_credentials_get_windows_user ());
+#else
+#warning Dont know how to send credentials on this OS. Please implement.
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+#endif
+ return initial_response;
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ /* can never end up here because we are never in the WAITING_FOR_DATA state */
+ g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+ m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismexternal.h b/gio/gdbusauthmechanismexternal.h
new file mode 100644
index 000000000..39e7fa217
--- /dev/null
+++ b/gio/gdbusauthmechanismexternal.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismexternal.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__
+#define __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL (_g_dbus_auth_mechanism_external_get_type ())
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternal))
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass))
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass))
+#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL))
+#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL))
+
+typedef struct _GDBusAuthMechanismExternal GDBusAuthMechanismExternal;
+typedef struct _GDBusAuthMechanismExternalClass GDBusAuthMechanismExternalClass;
+typedef struct _GDBusAuthMechanismExternalPrivate GDBusAuthMechanismExternalPrivate;
+
+struct _GDBusAuthMechanismExternalClass
+{
+ /*< private >*/
+ GDBusAuthMechanismClass parent_class;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+ void (*_g_reserved9) (void);
+ void (*_g_reserved10) (void);
+ void (*_g_reserved11) (void);
+ void (*_g_reserved12) (void);
+ void (*_g_reserved13) (void);
+ void (*_g_reserved14) (void);
+ void (*_g_reserved15) (void);
+ void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismExternal
+{
+ GDBusAuthMechanism parent_instance;
+ GDBusAuthMechanismExternalPrivate *priv;
+};
+
+GType _g_dbus_auth_mechanism_external_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__ */
diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c
new file mode 100644
index 000000000..ee94e49a0
--- /dev/null
+++ b/gio/gdbusauthmechanismsha1.c
@@ -0,0 +1,1216 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismsha1.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#endif
+
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+struct _GDBusAuthMechanismSha1Private
+{
+ gboolean is_client;
+ gboolean is_server;
+ GDBusAuthMechanismState state;
+
+ /* used on the client side */
+ gchar *to_send;
+
+ /* used on the server side */
+ gchar *cookie;
+ gchar *server_challenge;
+};
+
+static gint mechanism_get_priority (void);
+static const gchar *mechanism_get_name (void);
+
+static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len);
+static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism);
+static void mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len);
+static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism);
+static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism);
+static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism);
+static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len);
+static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len);
+static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len);
+static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismSha1, _g_dbus_auth_mechanism_sha1, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_sha1_finalize (GObject *object)
+{
+ GDBusAuthMechanismSha1 *mechanism = G_DBUS_AUTH_MECHANISM_SHA1 (object);
+
+ g_free (mechanism->priv->to_send);
+
+ g_free (mechanism->priv->cookie);
+ g_free (mechanism->priv->server_challenge);
+
+ if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass)
+{
+ GObjectClass *gobject_class;
+ GDBusAuthMechanismClass *mechanism_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthMechanismSha1Private));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = _g_dbus_auth_mechanism_sha1_finalize;
+
+ mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+ mechanism_class->get_priority = mechanism_get_priority;
+ mechanism_class->get_name = mechanism_get_name;
+ mechanism_class->is_supported = mechanism_is_supported;
+ mechanism_class->encode_data = mechanism_encode_data;
+ mechanism_class->decode_data = mechanism_decode_data;
+ mechanism_class->server_get_state = mechanism_server_get_state;
+ mechanism_class->server_initiate = mechanism_server_initiate;
+ mechanism_class->server_data_receive = mechanism_server_data_receive;
+ mechanism_class->server_data_send = mechanism_server_data_send;
+ mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason;
+ mechanism_class->server_shutdown = mechanism_server_shutdown;
+ mechanism_class->client_get_state = mechanism_client_get_state;
+ mechanism_class->client_initiate = mechanism_client_initiate;
+ mechanism_class->client_data_receive = mechanism_client_data_receive;
+ mechanism_class->client_data_send = mechanism_client_data_send;
+ mechanism_class->client_shutdown = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_sha1_init (GDBusAuthMechanismSha1 *mechanism)
+{
+ mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+ G_TYPE_DBUS_AUTH_MECHANISM_SHA1,
+ GDBusAuthMechanismSha1Private);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+mechanism_get_priority (void)
+{
+ return 0;
+}
+
+static const gchar *
+mechanism_get_name (void)
+{
+ return "DBUS_COOKIE_SHA1";
+}
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), FALSE);
+ return TRUE;
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len,
+ gsize *out_data_len)
+{
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+random_ascii (void)
+{
+ gint ret;
+ ret = g_random_int_range (0, 60);
+ if (ret < 25)
+ ret += 'A';
+ else if (ret < 50)
+ ret += 'a' - 25;
+ else
+ ret += '0' - 50;
+ return ret;
+}
+
+static gchar *
+random_ascii_string (guint len)
+{
+ GString *challenge;
+ guint n;
+
+ challenge = g_string_new (NULL);
+ for (n = 0; n < len; n++)
+ g_string_append_c (challenge, random_ascii ());
+ return g_string_free (challenge, FALSE);
+}
+
+static gchar *
+random_blob (guint len)
+{
+ GString *challenge;
+ guint n;
+
+ challenge = g_string_new (NULL);
+ for (n = 0; n < len; n++)
+ g_string_append_c (challenge, g_random_int_range (0, 256));
+ return g_string_free (challenge, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* ensure keyring dir exists and permissions are correct */
+static gchar *
+ensure_keyring_directory (GError **error)
+{
+ gchar *path;
+ const gchar *e;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ e = g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR");
+ if (e != NULL)
+ {
+ path = g_strdup (e);
+ }
+ else
+ {
+ path = g_build_filename (g_get_home_dir (),
+ ".dbus-keyrings",
+ NULL);
+ }
+
+ if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+ {
+ if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL)
+ {
+#ifdef G_OS_UNIX
+ struct stat statbuf;
+ if (stat (path, &statbuf) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error statting directory `%s': %s"),
+ path,
+ strerror (errno));
+ g_free (path);
+ path = NULL;
+ goto out;
+ }
+ if ((statbuf.st_mode & 0777) != 0700)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Permissions on directory `%s' are malformed. Expected mode 0700, got 0%o"),
+ path,
+ statbuf.st_mode & 0777);
+ g_free (path);
+ path = NULL;
+ goto out;
+ }
+#else
+#error Please implement permission checking on non-UNIX platforms
+#endif
+ }
+ goto out;
+ }
+
+ if (g_mkdir (path, 0700) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error creating directory `%s': %s"),
+ path,
+ strerror (errno));
+ g_free (path);
+ path = NULL;
+ goto out;
+ }
+
+out:
+ return path;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+append_nibble (GString *s, gint val)
+{
+ g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
+}
+
+static gchar *
+hexencode (const gchar *str,
+ gssize len)
+{
+ guint n;
+ GString *s;
+
+ if (len == -1)
+ len = strlen (str);
+
+ s = g_string_new (NULL);
+ for (n = 0; n < len; n++)
+ {
+ gint val;
+ gint upper_nibble;
+ gint lower_nibble;
+
+ val = ((const guchar *) str)[n];
+ upper_nibble = val >> 4;
+ lower_nibble = val & 0x0f;
+
+ append_nibble (s, upper_nibble);
+ append_nibble (s, lower_nibble);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* looks up an entry in the keyring */
+static gchar *
+keyring_lookup_entry (const gchar *cookie_context,
+ gint cookie_id,
+ GError **error)
+{
+ gchar *ret;
+ gchar *keyring_dir;
+ gchar *contents;
+ gchar *path;
+ guint n;
+ gchar **lines;
+
+ g_return_val_if_fail (cookie_context != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = NULL;
+ path = NULL;
+ contents = NULL;
+ lines = NULL;
+
+ keyring_dir = ensure_keyring_directory (error);
+ if (keyring_dir == NULL)
+ goto out;
+
+ path = g_build_filename (keyring_dir, cookie_context, NULL);
+
+ if (!g_file_get_contents (path,
+ &contents,
+ NULL,
+ error))
+ {
+ g_prefix_error (error,
+ _("Error opening keyring `%s' for reading: "),
+ path);
+ goto out;
+ }
+ g_assert (contents != NULL);
+
+ lines = g_strsplit (contents, "\n", 0);
+ for (n = 0; lines[n] != NULL; n++)
+ {
+ const gchar *line = lines[n];
+ gchar **tokens;
+ gchar *endp;
+ gint line_id;
+ guint64 line_when;
+
+ if (line[0] == '\0')
+ continue;
+
+ tokens = g_strsplit (line, " ", 0);
+ if (g_strv_length (tokens) != 3)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ line_id = g_ascii_strtoll (tokens[0], &endp, 10);
+ if (*endp != '\0')
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("First token of line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ line_when = g_ascii_strtoll (tokens[1], &endp, 10);
+ if (*endp != '\0')
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ if (line_id == cookie_id)
+ {
+ /* YAY, success */
+ ret = tokens[2]; /* steal pointer */
+ tokens[2] = NULL;
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ g_strfreev (tokens);
+ }
+
+ /* BOOH, didn't find the cookie */
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Didn't find cookie with id %d in the keyring at `%s'"),
+ cookie_id,
+ path);
+
+ out:
+ g_free (keyring_dir);
+ g_free (path);
+ g_free (contents);
+ g_strfreev (lines);
+ return ret;
+}
+
+/* function for logging important events that the system administrator should take notice of */
+static void
+_log (const gchar *message,
+ ...)
+{
+ gchar *s;
+ va_list var_args;
+
+ va_start (var_args, message);
+ s = g_strdup_vprintf (message, var_args);
+ va_end (var_args);
+
+ /* TODO: might want to send this to syslog instead */
+ g_printerr ("GDBus-DBUS_COOKIE_SHA1: %s\n", s);
+ g_free (s);
+}
+
+static gint
+keyring_acquire_lock (const gchar *path,
+ GError **error)
+{
+ gchar *lock;
+ gint ret;
+ guint num_tries;
+ guint num_create_tries;
+
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = -1;
+ lock = g_strdup_printf ("%s.lock", path);
+
+ /* This is what the D-Bus spec says
+ *
+ * Create a lockfile name by appending ".lock" to the name of the
+ * cookie file. The server should attempt to create this file using
+ * O_CREAT | O_EXCL. If file creation fails, the lock
+ * fails. Servers should retry for a reasonable period of time,
+ * then they may choose to delete an existing lock to keep users
+ * from having to manually delete a stale lock. [1]
+ *
+ * [1] : Lockfiles are used instead of real file locking fcntl() because
+ * real locking implementations are still flaky on network filesystems
+ */
+
+ num_create_tries = 0;
+#ifdef EEXISTS
+ again:
+#endif
+ num_tries = 0;
+ while (g_file_test (lock, G_FILE_TEST_EXISTS))
+ {
+ /* sleep 10ms, then try again */
+ g_usleep (1000*10);
+ num_tries++;
+ if (num_tries == 50)
+ {
+ /* ok, we slept 50*10ms = 0.5 seconds.. Conclude that the lock-file must be
+ * stale (nuke the it from orbit)
+ */
+ if (g_unlink (lock) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error deleting stale lock-file `%s': %s"),
+ lock,
+ strerror (errno));
+ goto out;
+ }
+ _log ("Deleted stale lock-file `%s'", lock);
+ break;
+ }
+ }
+
+ ret = g_open (lock, O_CREAT |
+#ifdef O_EXCL
+ O_EXCL,
+#else
+ 0,
+#endif
+ 0700);
+ if (ret == -1)
+ {
+#ifdef EEXISTS
+ /* EEXIST: pathname already exists and O_CREAT and O_EXCL were used. */
+ if (errno == EEXISTS)
+ {
+ num_create_tries++;
+ if (num_create_tries < 5)
+ goto again;
+ }
+#endif
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error creating lock-file `%s': %s"),
+ lock,
+ strerror (errno));
+ goto out;
+ }
+
+ out:
+ g_free (lock);
+ return ret;
+}
+
+static gboolean
+keyring_release_lock (const gchar *path,
+ gint lock_fd,
+ GError **error)
+{
+ gchar *lock;
+ gboolean ret;
+
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (lock_fd != -1, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+ lock = g_strdup_printf ("%s.lock", path);
+ if (g_unlink (lock) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error unlinking lock-file `%s': %s"),
+ lock,
+ strerror (errno));
+ goto out;
+ }
+ if (close (lock_fd) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error closing (unlinked) lock-file `%s': %s"),
+ lock,
+ strerror (errno));
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_free (lock);
+ return ret;
+}
+
+
+/* adds an entry to the keyring, taking care of locking and deleting stale/future entries */
+static gboolean
+keyring_generate_entry (const gchar *cookie_context,
+ gint *out_id,
+ gchar **out_cookie,
+ GError **error)
+{
+ gboolean ret;
+ gchar *keyring_dir;
+ gchar *path;
+ gchar *contents;
+ GError *local_error;
+ gchar **lines;
+ gint max_line_id;
+ GString *new_contents;
+ guint64 now;
+ gboolean have_id;
+ gint use_id;
+ gchar *use_cookie;
+ gboolean changed_file;
+ gint lock_fd;
+
+ g_return_val_if_fail (cookie_context != NULL, FALSE);
+ g_return_val_if_fail (out_id != NULL, FALSE);
+ g_return_val_if_fail (out_cookie != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+ path = NULL;
+ contents = NULL;
+ lines = NULL;
+ new_contents = NULL;
+ have_id = FALSE;
+ use_cookie = NULL;
+ lock_fd = -1;
+
+ keyring_dir = ensure_keyring_directory (error);
+ if (keyring_dir == NULL)
+ goto out;
+
+ path = g_build_filename (keyring_dir, cookie_context, NULL);
+
+ lock_fd = keyring_acquire_lock (path, error);
+ if (lock_fd == -1)
+ goto out;
+
+ local_error = NULL;
+ contents = NULL;
+ if (!g_file_get_contents (path,
+ &contents,
+ NULL,
+ &local_error))
+ {
+ if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT)
+ {
+ /* file doesn't have to exist */
+ g_error_free (local_error);
+ }
+ else
+ {
+ g_propagate_prefixed_error (error,
+ local_error,
+ _("Error opening keyring `%s' for writing: "),
+ path);
+ goto out;
+ }
+ }
+
+ new_contents = g_string_new (NULL);
+ now = time (NULL);
+ changed_file = FALSE;
+
+ max_line_id = 0;
+ if (contents != NULL)
+ {
+ guint n;
+ lines = g_strsplit (contents, "\n", 0);
+ for (n = 0; lines[n] != NULL; n++)
+ {
+ const gchar *line = lines[n];
+ gchar **tokens;
+ gchar *endp;
+ gint line_id;
+ guint64 line_when;
+ gboolean keep_entry;
+
+ if (line[0] == '\0')
+ continue;
+
+ tokens = g_strsplit (line, " ", 0);
+ if (g_strv_length (tokens) != 3)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ line_id = g_ascii_strtoll (tokens[0], &endp, 10);
+ if (*endp != '\0')
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("First token of line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ line_when = g_ascii_strtoll (tokens[1], &endp, 10);
+ if (*endp != '\0')
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"),
+ n + 1,
+ path,
+ line);
+ g_strfreev (tokens);
+ goto out;
+ }
+
+ /* D-Bus spec says:
+ *
+ * Once the lockfile has been created, the server loads the
+ * cookie file. It should then delete any cookies that are
+ * old (the timeout can be fairly short), or more than a
+ * reasonable time in the future (so that cookies never
+ * accidentally become permanent, if the clock was set far
+ * into the future at some point). If no recent keys remain,
+ * the server may generate a new key.
+ *
+ */
+ keep_entry = TRUE;
+ if (line_when > now)
+ {
+ /* Oddball case: entry is more recent than our current wall-clock time..
+ * This is OK, it means that another server on another machine but with
+ * same $HOME wrote the entry.
+ *
+ * So discard the entry if it's more than 1 day in the future ("reasonable
+ * time in the future").
+ */
+ if (line_when - now > 24*60*60)
+ {
+ keep_entry = FALSE;
+ _log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now);
+ }
+ }
+ else
+ {
+ /* Discard entry if it's older than 15 minutes ("can be fairly short") */
+ if (now - line_when > 15*60)
+ {
+ keep_entry = FALSE;
+ }
+ }
+
+ if (!keep_entry)
+ {
+ changed_file = FALSE;
+ }
+ else
+ {
+ g_string_append_printf (new_contents,
+ "%d %" G_GUINT64_FORMAT " %s\n",
+ line_id,
+ line_when,
+ tokens[2]);
+ max_line_id = MAX (line_id, max_line_id);
+ /* Only reuse entry if not older than 10 minutes.
+ *
+ * (We need a bit of grace time compared to 15 minutes above.. otherwise
+ * there's a race where we reuse the 14min59.9 secs old entry and a
+ * split-second later another server purges the now 15 minute old entry.)
+ */
+ if (now - line_when < 10 * 60)
+ {
+ if (!have_id)
+ {
+ use_id = line_id;
+ use_cookie = tokens[2]; /* steal memory */
+ tokens[2] = NULL;
+ have_id = TRUE;
+ }
+ }
+ }
+ g_strfreev (tokens);
+ }
+ } /* for each line */
+
+ ret = TRUE;
+
+ if (have_id)
+ {
+ *out_id = use_id;
+ *out_cookie = use_cookie;
+ use_cookie = NULL;
+ }
+ else
+ {
+ gchar *raw_cookie;
+ *out_id = max_line_id + 1;
+ raw_cookie = random_blob (32);
+ *out_cookie = hexencode (raw_cookie, 32);
+ g_free (raw_cookie);
+
+ g_string_append_printf (new_contents,
+ "%d %" G_GUINT64_FORMAT " %s\n",
+ *out_id,
+ (guint64) time (NULL),
+ *out_cookie);
+ changed_file = TRUE;
+ }
+
+ /* and now actually write the cookie file if there are changes (this is atomic) */
+ if (changed_file)
+ {
+ if (!g_file_set_contents (path,
+ new_contents->str,
+ -1,
+ error))
+ {
+ *out_id = 0;
+ *out_cookie = 0;
+ g_free (*out_cookie);
+ ret = FALSE;
+ goto out;
+ }
+ }
+
+ out:
+
+ if (lock_fd != -1)
+ {
+ GError *local_error;
+ local_error = NULL;
+ if (!keyring_release_lock (path, lock_fd, &local_error))
+ {
+ if (error != NULL)
+ {
+ if (*error == NULL)
+ {
+ *error = local_error;
+ }
+ else
+ {
+ g_prefix_error (error,
+ _("(Additionally, releasing the lock for `%s' also failed: %s) "),
+ path,
+ local_error->message);
+ }
+ }
+ else
+ {
+ g_error_free (local_error);
+ }
+ }
+ }
+
+ g_free (keyring_dir);
+ g_free (path);
+ g_strfreev (lines);
+ g_free (contents);
+ if (new_contents != NULL)
+ g_string_free (new_contents, TRUE);
+ g_free (use_cookie);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+generate_sha1 (const gchar *server_challenge,
+ const gchar *client_challenge,
+ const gchar *cookie)
+{
+ GString *str;
+ gchar *sha1;
+
+ str = g_string_new (server_challenge);
+ g_string_append_c (str, ':');
+ g_string_append (str, client_challenge);
+ g_string_append_c (str, ':');
+ g_string_append (str, cookie);
+ sha1 = g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, -1);
+ g_string_free (str, TRUE);
+
+ return sha1;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism *mechanism,
+ const gchar *initial_response,
+ gsize initial_response_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+ g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+ m->priv->is_server = TRUE;
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+
+ if (initial_response != NULL && strlen (initial_response) > 0)
+ {
+#ifdef G_OS_UNIX
+ gint64 uid;
+ gchar *endp;
+
+ uid = g_ascii_strtoll (initial_response, &endp, 10);
+ if (*endp == '\0')
+ {
+ if (uid == getuid ())
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+ }
+ }
+#elif defined(G_OS_WIN32)
+ GCredentials *credentials;
+ credentials = g_credentials_new_for_process ();
+ if (g_strcmp0 (g_credentials_get_windows_user (credentials), initial_response) == 0)
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+ g_object_unref (credentials);
+#else
+#error Please implement for your OS
+#endif
+ }
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+ gchar **tokens;
+ const gchar *client_challenge;
+ const gchar *alleged_sha1;
+ gchar *sha1;
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ tokens = NULL;
+ sha1 = NULL;
+
+ tokens = g_strsplit (data, " ", 0);
+ if (g_strv_length (tokens) != 2)
+ {
+ g_warning ("Malformed data `%s'", data);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+
+ client_challenge = tokens[0];
+ alleged_sha1 = tokens[1];
+
+ sha1 = generate_sha1 (m->priv->server_challenge, client_challenge, m->priv->cookie);
+
+ if (g_strcmp0 (sha1, alleged_sha1) == 0)
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+ }
+ else
+ {
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ }
+
+ out:
+ g_strfreev (tokens);
+ g_free (sha1);
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+ gchar *s;
+ gint cookie_id;
+ const gchar *cookie_context;
+ GError *error;
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ s = NULL;
+
+ /* TODO: use GDBusAuthObserver here to get the cookie context to use? */
+ cookie_context = "org_gtk_gdbus_general";
+
+ error = NULL;
+ if (!keyring_generate_entry (cookie_context,
+ &cookie_id,
+ &m->priv->cookie,
+ &error))
+ {
+ g_warning ("Error adding entry to keyring: %s", error->message);
+ g_error_free (error);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+
+ m->priv->server_challenge = random_ascii_string (16);
+ s = g_strdup_printf ("%s %d %s",
+ cookie_context,
+ cookie_id,
+ m->priv->server_challenge);
+
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+
+ out:
+ return s;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+ /* can never end up here because we are never in the REJECTED state */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+ g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+ m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+ return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism *mechanism,
+ gsize *out_initial_response_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+ gchar *initial_response;
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+ g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+ m->priv->is_client = TRUE;
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+
+ *out_initial_response_len = -1;
+
+#ifdef G_OS_UNIX
+ initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ());
+#elif defined (G_OS_WIN32)
+ {
+ GCredentials *credentials;
+ credentials = g_credentials_new_for_process ();
+ initial_response = g_strdup (g_credentials_get_windows_user (credentials));
+ g_object_unref (credentials);
+ }
+#else
+#endif
+ g_assert (initial_response != NULL);
+
+ return initial_response;
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
+ const gchar *data,
+ gsize data_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+ gchar **tokens;
+ const gchar *cookie_context;
+ guint cookie_id;
+ const gchar *server_challenge;
+ gchar *client_challenge;
+ gchar *endp;
+ gchar *cookie;
+ GError *error;
+ gchar *sha1;
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+ g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+ tokens = NULL;
+ cookie = NULL;
+ client_challenge = NULL;
+
+ tokens = g_strsplit (data, " ", 0);
+ if (g_strv_length (tokens) != 3)
+ {
+ g_warning ("Malformed data `%s'", data);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+
+ cookie_context = tokens[0];
+ cookie_id = g_ascii_strtoll (tokens[1], &endp, 10);
+ if (*endp != '\0')
+ {
+ g_warning ("Malformed cookie_id `%s'", tokens[1]);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+ server_challenge = tokens[2];
+
+ error = NULL;
+ cookie = keyring_lookup_entry (cookie_context, cookie_id, &error);
+ if (cookie == NULL)
+ {
+ g_warning ("Problems looking up entry in keyring: %s", error->message);
+ g_error_free (error);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+
+ client_challenge = random_ascii_string (16);
+ sha1 = generate_sha1 (server_challenge, client_challenge, cookie);
+ m->priv->to_send = g_strdup_printf ("%s %s", client_challenge, sha1);
+ g_free (sha1);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+
+ out:
+ g_strfreev (tokens);
+ g_free (cookie);
+ g_free (client_challenge);
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism *mechanism,
+ gsize *out_data_len)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+ g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+ g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+ g_assert (m->priv->to_send != NULL);
+
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+ return g_strdup (m->priv->to_send);
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism *mechanism)
+{
+ GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+ g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+ g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+ m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismsha1.h b/gio/gdbusauthmechanismsha1.h
new file mode 100644
index 000000000..67839fd11
--- /dev/null
+++ b/gio/gdbusauthmechanismsha1.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismsha1.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_SHA1_H__
+#define __G_DBUS_AUTH_MECHANISM_SHA1_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_SHA1 (_g_dbus_auth_mechanism_sha1_get_type ())
+#define G_DBUS_AUTH_MECHANISM_SHA1(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1))
+#define G_DBUS_AUTH_MECHANISM_SHA1_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class))
+#define G_DBUS_AUTH_MECHANISM_SHA1_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class))
+#define G_IS_DBUS_AUTH_MECHANISM_SHA1(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1))
+#define G_IS_DBUS_AUTH_MECHANISM_SHA1_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1))
+
+typedef struct _GDBusAuthMechanismSha1 GDBusAuthMechanismSha1;
+typedef struct _GDBusAuthMechanismSha1Class GDBusAuthMechanismSha1Class;
+typedef struct _GDBusAuthMechanismSha1Private GDBusAuthMechanismSha1Private;
+
+struct _GDBusAuthMechanismSha1Class
+{
+ /*< private >*/
+ GDBusAuthMechanismClass parent_class;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+ void (*_g_reserved9) (void);
+ void (*_g_reserved10) (void);
+ void (*_g_reserved11) (void);
+ void (*_g_reserved12) (void);
+ void (*_g_reserved13) (void);
+ void (*_g_reserved14) (void);
+ void (*_g_reserved15) (void);
+ void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismSha1
+{
+ GDBusAuthMechanism parent_instance;
+ GDBusAuthMechanismSha1Private *priv;
+};
+
+GType _g_dbus_auth_mechanism_sha1_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_SHA1_H__ */
diff --git a/gio/gdbusauthobserver.c b/gio/gdbusauthobserver.c
new file mode 100644
index 000000000..f0411aba8
--- /dev/null
+++ b/gio/gdbusauthobserver.c
@@ -0,0 +1,218 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+#include "gcredentials.h"
+#include "gioenumtypes.h"
+#include "giostream.h"
+
+/**
+ * SECTION:gdbusauthobserver
+ * @short_description: Object used for authenticating connections
+ * @include: gdbus/gdbus.h
+ *
+ * The #GDBusAuthObserver type provides a mechanism for participating
+ * in how a #GDBusServer (or a #GDBusConnection) authenticates remote
+ * peers. Simply instantiate a #GDBusAuthObserver and connect to the
+ * signals you are interested in. Note that new signals may be added
+ * in the future
+ *
+ * For example, if you only want to allow D-Bus connections from
+ * processes owned by the same uid as the server, you would do this:
+ * <example id="auth-observer"><title>Controlling Authentication</title><programlisting>
+ * static gboolean
+ * on_deny_authenticated_peer (GDBusAuthObserver *observer,
+ * GIOStream *stream,
+ * GCredentials *credentials,
+ * gpointer user_data)
+ * {
+ * gboolean deny;
+ * deny = TRUE;
+ * if (credentials != NULL &&
+ * g_credentials_has_unix_user (credentials) &&
+ * g_credentials_get_unix_user (credentials) == getuid ())
+ * deny = FALSE;
+ * return deny;
+ * }
+ *
+ * static gboolean
+ * on_new_connection (GDBusServer *server,
+ * GDBusConnection *connection,
+ * gpointer user_data)
+ * {
+ * /<!-- -->* Guaranteed here that @connection is from a process owned by the same user *<!-- -->/
+ * }
+ *
+ * [...]
+ *
+ * GDBusAuthObserver *observer;
+ * GDBusServer *server;
+ * GError *error;
+ *
+ * error = NULL;
+ * observer = g_dbus_auth_observer_new ();
+ * server = g_dbus_server_new_sync ("unix:tmpdir=/tmp/my-app-name",
+ * G_DBUS_SERVER_FLAGS_NONE,
+ * observer,
+ * NULL, /<!-- -->* GCancellable *<!-- -->/
+ * &error);
+ * g_signal_connect (observer,
+ * "deny-authenticated-peer",
+ * G_CALLBACK (on_deny_authenticated_peer),
+ * NULL);
+ * g_signal_connect (server,
+ * "new-connection",
+ * G_CALLBACK (on_new_connection),
+ * NULL);
+ * g_object_unref (observer);
+ * g_dbus_server_start (server);
+ * </programlisting></example>
+ */
+
+struct _GDBusAuthObserverPrivate
+{
+ gint foo;
+};
+
+enum
+{
+ DENY_AUTHENTICATED_PEER_SIGNAL,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GDBusAuthObserver, g_dbus_auth_observer, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_auth_observer_finalize (GObject *object)
+{
+ //GDBusAuthObserver *observer = G_DBUS_AUTH_OBSERVER (object);
+
+ if (G_OBJECT_CLASS (g_dbus_auth_observer_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_auth_observer_parent_class)->finalize (object);
+}
+
+static gboolean
+g_dbus_auth_observer_deny_authenticated_peer_real (GDBusAuthObserver *observer,
+ GIOStream *stream,
+ GCredentials *credentials)
+{
+ return FALSE;
+}
+
+static void
+g_dbus_auth_observer_class_init (GDBusAuthObserverClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_auth_observer_finalize;
+
+ klass->deny_authenticated_peer = g_dbus_auth_observer_deny_authenticated_peer_real;
+
+ /**
+ * GDBusAuthObserver::deny-authenticated-peer:
+ * @observer: The #GDBusAuthObserver emitting the signal.
+ * @stream: A #GIOStream for the #GDBusConnection.
+ * @credentials: Credentials received from the peer or %NULL.
+ *
+ * Emitted to check if a peer that is successfully authenticated
+ * should be denied.
+ *
+ * Returns: %TRUE if the peer should be denied, %FALSE otherwise.
+ */
+ signals[DENY_AUTHENTICATED_PEER_SIGNAL] =
+ g_signal_new ("deny-authenticated-peer",
+ G_TYPE_DBUS_AUTH_OBSERVER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GDBusAuthObserverClass, deny_authenticated_peer),
+ g_signal_accumulator_true_handled,
+ NULL, /* accu_data */
+ _gio_marshal_BOOLEAN__OBJECT_OBJECT,
+ G_TYPE_BOOLEAN,
+ 2,
+ G_TYPE_IO_STREAM,
+ G_TYPE_CREDENTIALS);
+
+
+ g_type_class_add_private (klass, sizeof (GDBusAuthObserverPrivate));
+}
+
+static void
+g_dbus_auth_observer_init (GDBusAuthObserver *observer)
+{
+ /* not used for now */
+ observer->priv = G_TYPE_INSTANCE_GET_PRIVATE (observer,
+ G_TYPE_DBUS_AUTH_OBSERVER,
+ GDBusAuthObserverPrivate);;
+}
+
+/**
+ * g_dbus_auth_observer_new:
+ *
+ * Creates a new #GDBusAuthObserver object.
+ *
+ * Returns: A #GDBusAuthObserver. Free with g_object_unref().
+ */
+GDBusAuthObserver *
+g_dbus_auth_observer_new (void)
+{
+ return g_object_new (G_TYPE_DBUS_AUTH_OBSERVER, NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_auth_observer_deny_authenticated_peer:
+ * @observer: A #GDBusAuthObserver.
+ * @stream: A #GIOStream for the #GDBusConnection.
+ * @credentials: Credentials received from the peer or %NULL.
+ *
+ * Emits the #GDBusAuthObserver::deny-authenticated-peer signal on @observer.
+ *
+ * Returns: %TRUE if the peer should be denied, %FALSE otherwise.
+ */
+gboolean
+g_dbus_auth_observer_deny_authenticated_peer (GDBusAuthObserver *observer,
+ GIOStream *stream,
+ GCredentials *credentials)
+{
+ gboolean denied;
+
+ denied = FALSE;
+ g_signal_emit (observer,
+ signals[DENY_AUTHENTICATED_PEER_SIGNAL],
+ 0,
+ stream,
+ credentials,
+ &denied);
+ return denied;
+}
+
+
diff --git a/gio/gdbusauthobserver.h b/gio/gdbusauthobserver.h
new file mode 100644
index 000000000..6ed52e8f9
--- /dev/null
+++ b/gio/gdbusauthobserver.h
@@ -0,0 +1,100 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_AUTH_OBSERVER_H__
+#define __G_DBUS_AUTH_OBSERVER_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_OBSERVER (g_dbus_auth_observer_get_type ())
+#define G_DBUS_AUTH_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserver))
+#define G_DBUS_AUTH_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass))
+#define G_DBUS_AUTH_OBSERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass))
+#define G_IS_DBUS_AUTH_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_OBSERVER))
+#define G_IS_DBUS_AUTH_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_OBSERVER))
+
+typedef struct _GDBusAuthObserverClass GDBusAuthObserverClass;
+typedef struct _GDBusAuthObserverPrivate GDBusAuthObserverPrivate;
+
+
+/**
+ * GDBusAuthObserverClass:
+ * @deny_authenticated_peer: Signal class handler for the #GDBusAuthObserver::deny-authenticated-peer signal.
+ *
+ * Class structure for #GDBusAuthObserverClass.
+ */
+struct _GDBusAuthObserverClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+
+ /* Signals */
+ gboolean (*deny_authenticated_peer) (GDBusAuthObserver *observer,
+ GIOStream *stream,
+ GCredentials *credentials);
+
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+ void (*_g_reserved9) (void);
+ void (*_g_reserved10) (void);
+ void (*_g_reserved11) (void);
+ void (*_g_reserved12) (void);
+ void (*_g_reserved13) (void);
+ void (*_g_reserved14) (void);
+ void (*_g_reserved15) (void);
+ void (*_g_reserved16) (void);
+};
+
+/**
+ * GDBusAuthObserver:
+ *
+ * The #GDBusAuthObserver structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusAuthObserver
+{
+ GObject parent_instance;
+ GDBusAuthObserverPrivate *priv;
+};
+
+GType g_dbus_auth_observer_get_type (void) G_GNUC_CONST;
+GDBusAuthObserver *g_dbus_auth_observer_new (void);
+gboolean g_dbus_auth_observer_deny_authenticated_peer (GDBusAuthObserver *observer,
+ GIOStream *stream,
+ GCredentials *credentials);
+
+G_END_DECLS
+
+#endif /* _G_DBUS_AUTH_OBSERVER_H__ */
diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c
new file mode 100644
index 000000000..0bcd58f89
--- /dev/null
+++ b/gio/gdbusconnection.c
@@ -0,0 +1,5280 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+/*
+ * TODO for GDBus:
+ *
+ * - would be nice to expose GDBusAuthMechanism and an extension point
+ *
+ * - need to expose an extension point for resolving D-Bus address and
+ * turning them into GIOStream objects. This will allow us to implement
+ * e.g. X11 D-Bus transports without dlopen()'ing or linking against
+ * libX11 from libgio.
+ * - see g_dbus_address_connect() in gdbusaddress.c
+ *
+ * - would be cute to use kernel-specific APIs to resolve fds for
+ * debug output when using G_DBUS_DEBUG=messages, e.g. in addition to
+ *
+ * fd 21: dev=8:1,mode=0100644,ino=1171231,uid=0,gid=0,rdev=0:0,size=234,atime=1273070640,mtime=1267126160,ctime=1267126160
+ *
+ * maybe we can show more information about what fd 21 really is.
+ * Ryan suggests looking in /proc/self/fd for clues / symlinks!
+ * Initial experiments on Linux 2.6 suggests that the symlink looks
+ * like this:
+ *
+ * 3 -> /proc/18068/fd
+ *
+ * e.g. not of much use.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include <gio/gunixfdmessage.h>
+#endif
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "gdbusauth.h"
+
+#include "gdbusutils.h"
+#include "gdbusaddress.h"
+#include "gdbusmessage.h"
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gdbusintrospection.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusprivate.h"
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+
+/**
+ * SECTION:gdbusconnection
+ * @short_description: D-Bus Connections
+ * @include: gdbus/gdbus.h
+ *
+ * <para><note>
+ * This class is rarely used directly in D-Bus clients. If you are
+ * writing an D-Bus client, it is often easier to use the
+ * g_bus_own_name(), g_bus_watch_name() or g_bus_watch_proxy() APIs.
+ * </note></para>
+ *
+ * The #GDBusConnection type is used for D-Bus connections to remote
+ * peers such as a message buses.
+ *
+ * <example id="gdbus-server"><title>D-Bus server example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-server.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * <example id="gdbus-subtree-server"><title>D-Bus subtree example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-subtree.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * <example id="gdbus-unix-fd-client"><title>D-Bus UNIX File Descriptor example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-unix-fd-client.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (message_bus_lock);
+
+static GDBusConnection *the_session_bus = NULL;
+static GDBusConnection *the_system_bus = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+ const gchar *needle)
+{
+ guint n;
+
+ for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+ {
+ if (g_strcmp0 (haystack[n], needle) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_WIN32
+#define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE)
+#else
+// TODO: for some reason this doesn't work on Windows
+#define CONNECTION_ENSURE_LOCK(obj) do { \
+ if (G_UNLIKELY (g_mutex_trylock((obj)->priv->lock))) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ "CONNECTION_ENSURE_LOCK: GDBusConnection object lock is not locked"); \
+ } \
+ } while (FALSE)
+#endif
+
+#define CONNECTION_LOCK(obj) do { \
+ g_mutex_lock ((obj)->priv->lock); \
+ } while (FALSE)
+
+#define CONNECTION_UNLOCK(obj) do { \
+ g_mutex_unlock ((obj)->priv->lock); \
+ } while (FALSE)
+
+struct _GDBusConnectionPrivate
+{
+ /* ------------------------------------------------------------------------ */
+ /* -- General object state ------------------------------------------------ */
+ /* ------------------------------------------------------------------------ */
+
+ /* object-wide lock */
+ GMutex *lock;
+
+ /* A lock used in the init() method of the GInitable interface - see comments
+ * in initable_init() for why a separate lock is needed
+ */
+ GMutex *init_lock;
+
+ /* Set (by loading the contents of /var/lib/dbus/machine-id) the first time
+ * someone calls org.freedesktop.DBus.GetMachineId()
+ */
+ gchar *machine_id;
+
+ /* The underlying stream used for communication */
+ GIOStream *stream;
+
+ /* The object used for authentication (if any) */
+ GDBusAuth *auth;
+
+ /* Set to TRUE if the connection has been closed */
+ gboolean closed;
+
+ /* Last serial used */
+ guint32 last_serial;
+
+ /* The object used to send/receive message */
+ GDBusWorker *worker;
+
+ /* If connected to a message bus, this contains the unique name assigned to
+ * us by the bus (e.g. ":1.42")
+ */
+ gchar *bus_unique_name;
+
+ /* The GUID returned by the other side if we authenticed as a client or
+ * the GUID to use if authenticating as a server
+ */
+ gchar *guid;
+
+ /* set to TRUE exactly when initable_init() has finished running */
+ gboolean is_initialized;
+
+ /* If the connection could not be established during initable_init(), this GError will set */
+ GError *initialization_error;
+
+ /* The result of g_main_context_get_thread_default() when the object
+ * was created (the GObject _init() function) - this is used for delivery
+ * of the :closed GObject signal.
+ */
+ GMainContext *main_context_at_construction;
+
+ /* construct properties */
+ gchar *address;
+ GDBusConnectionFlags flags;
+
+ /* Map used for managing method replies */
+ GHashTable *map_method_serial_to_send_message_data; /* guint32 -> SendMessageData* */
+
+ /* Maps used for managing signal subscription */
+ GHashTable *map_rule_to_signal_data; /* gchar* -> SignalData */
+ GHashTable *map_id_to_signal_data; /* guint -> SignalData */
+ GHashTable *map_sender_to_signal_data_array; /* gchar* -> GPtrArray* of SignalData */
+
+ /* Maps used for managing exported objects and subtrees */
+ GHashTable *map_object_path_to_eo; /* gchar* -> ExportedObject* */
+ GHashTable *map_id_to_ei; /* guint -> ExportedInterface* */
+ GHashTable *map_object_path_to_es; /* gchar* -> ExportedSubtree* */
+ GHashTable *map_id_to_es; /* guint -> ExportedSubtree* */
+
+ /* Structure used for message filters */
+ GPtrArray *filters;
+
+ /* Whether to exit on close */
+ gboolean exit_on_close;
+
+ /* Capabilities negotiated during authentication */
+ GDBusCapabilityFlags capabilities;
+
+ GDBusAuthObserver *authentication_observer;
+ GCredentials *crendentials;
+};
+
+typedef struct ExportedObject ExportedObject;
+static void exported_object_free (ExportedObject *eo);
+
+typedef struct ExportedSubtree ExportedSubtree;
+static void exported_subtree_free (ExportedSubtree *es);
+
+enum
+{
+ CLOSED_SIGNAL,
+ LAST_SIGNAL,
+};
+
+enum
+{
+ PROP_0,
+ PROP_STREAM,
+ PROP_ADDRESS,
+ PROP_FLAGS,
+ PROP_GUID,
+ PROP_UNIQUE_NAME,
+ PROP_CLOSED,
+ PROP_EXIT_ON_CLOSE,
+ PROP_CAPABILITY_FLAGS,
+ PROP_AUTHENTICATION_OBSERVER,
+};
+
+static void distribute_signals (GDBusConnection *connection,
+ GDBusMessage *message);
+
+static void distribute_method_call (GDBusConnection *connection,
+ GDBusMessage *message);
+
+static gboolean handle_generic_unlocked (GDBusConnection *connection,
+ GDBusMessage *message);
+
+
+static void purge_all_signal_subscriptions (GDBusConnection *connection);
+static void purge_all_filters (GDBusConnection *connection);
+
+#define _G_ENSURE_LOCK(name) do { \
+ if (G_UNLIKELY (G_TRYLOCK(name))) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ "_G_ENSURE_LOCK: Lock `" #name "' is not locked"); \
+ } \
+ } while (FALSE) \
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void initable_iface_init (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusConnection, g_dbus_connection, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+ );
+
+static void
+g_dbus_connection_dispose (GObject *object)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+ G_LOCK (message_bus_lock);
+ //g_debug ("disposing %p", connection);
+ if (connection == the_session_bus)
+ {
+ the_session_bus = NULL;
+ }
+ else if (connection == the_system_bus)
+ {
+ the_system_bus = NULL;
+ }
+ if (connection->priv->worker != NULL)
+ {
+ _g_dbus_worker_stop (connection->priv->worker);
+ connection->priv->worker = NULL;
+ }
+ G_UNLOCK (message_bus_lock);
+
+ if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose (object);
+}
+
+static void
+g_dbus_connection_finalize (GObject *object)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+ if (connection->priv->authentication_observer != NULL)
+ g_object_unref (connection->priv->authentication_observer);
+
+ if (connection->priv->auth != NULL)
+ g_object_unref (connection->priv->auth);
+
+ //g_debug ("finalizing %p", connection);
+ if (connection->priv->stream != NULL)
+ {
+ /* We don't really care if closing the stream succeeds or not */
+ g_io_stream_close_async (connection->priv->stream,
+ G_PRIORITY_DEFAULT,
+ NULL, /* GCancellable */
+ NULL, /* GAsyncReadyCallback */
+ NULL); /* userdata */
+ g_object_unref (connection->priv->stream);
+ connection->priv->stream = NULL;
+ }
+
+ g_free (connection->priv->address);
+
+ g_free (connection->priv->guid);
+ g_free (connection->priv->bus_unique_name);
+
+ if (connection->priv->initialization_error != NULL)
+ g_error_free (connection->priv->initialization_error);
+
+ g_hash_table_unref (connection->priv->map_method_serial_to_send_message_data);
+
+ purge_all_signal_subscriptions (connection);
+ g_hash_table_unref (connection->priv->map_rule_to_signal_data);
+ g_hash_table_unref (connection->priv->map_id_to_signal_data);
+ g_hash_table_unref (connection->priv->map_sender_to_signal_data_array);
+
+ g_hash_table_unref (connection->priv->map_id_to_ei);
+ g_hash_table_unref (connection->priv->map_object_path_to_eo);
+ g_hash_table_unref (connection->priv->map_id_to_es);
+ g_hash_table_unref (connection->priv->map_object_path_to_es);
+
+ purge_all_filters (connection);
+ g_ptr_array_unref (connection->priv->filters);
+
+ if (connection->priv->main_context_at_construction != NULL)
+ g_main_context_unref (connection->priv->main_context_at_construction);
+
+ g_free (connection->priv->machine_id);
+
+ g_mutex_free (connection->priv->init_lock);
+ g_mutex_free (connection->priv->lock);
+
+ if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_connection_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ g_value_set_object (value, g_dbus_connection_get_stream (connection));
+ break;
+
+ case PROP_GUID:
+ g_value_set_string (value, g_dbus_connection_get_guid (connection));
+ break;
+
+ case PROP_UNIQUE_NAME:
+ g_value_set_string (value, g_dbus_connection_get_unique_name (connection));
+ break;
+
+ case PROP_CLOSED:
+ g_value_set_boolean (value, g_dbus_connection_is_closed (connection));
+ break;
+
+ case PROP_EXIT_ON_CLOSE:
+ g_value_set_boolean (value, g_dbus_connection_get_exit_on_close (connection));
+ break;
+
+ case PROP_CAPABILITY_FLAGS:
+ g_value_set_flags (value, g_dbus_connection_get_capabilities (connection));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_connection_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_STREAM:
+ connection->priv->stream = g_value_dup_object (value);
+ break;
+
+ case PROP_GUID:
+ connection->priv->guid = g_value_dup_string (value);
+ break;
+
+ case PROP_ADDRESS:
+ connection->priv->address = g_value_dup_string (value);
+ break;
+
+ case PROP_FLAGS:
+ connection->priv->flags = g_value_get_flags (value);
+ break;
+
+ case PROP_EXIT_ON_CLOSE:
+ g_dbus_connection_set_exit_on_close (connection, g_value_get_boolean (value));
+ break;
+
+ case PROP_AUTHENTICATION_OBSERVER:
+ connection->priv->authentication_observer = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_connection_real_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error)
+{
+ if (remote_peer_vanished && connection->priv->exit_on_close)
+ {
+ g_print ("%s: Remote peer vanished. Exiting.\n", G_STRFUNC);
+ raise (SIGTERM);
+ }
+}
+
+static void
+g_dbus_connection_class_init (GDBusConnectionClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusConnectionPrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_connection_finalize;
+ gobject_class->dispose = g_dbus_connection_dispose;
+ gobject_class->set_property = g_dbus_connection_set_property;
+ gobject_class->get_property = g_dbus_connection_get_property;
+
+ klass->closed = g_dbus_connection_real_closed;
+
+ /**
+ * GDBusConnection:stream:
+ *
+ * The underlying #GIOStream used for I/O.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM,
+ g_param_spec_object ("stream",
+ _("IO Stream"),
+ _("The underlying streams used for I/O"),
+ G_TYPE_IO_STREAM,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:address:
+ *
+ * A D-Bus address specifying potential endpoints that can be used
+ * when establishing the connection.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_ADDRESS,
+ g_param_spec_string ("address",
+ _("Address"),
+ _("D-Bus address specifying potential socket endpoints"),
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:flags:
+ *
+ * Flags from the #GDBusConnectionFlags enumeration.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_FLAGS,
+ g_param_spec_flags ("flags",
+ _("Flags"),
+ _("Flags"),
+ G_TYPE_DBUS_CONNECTION_FLAGS,
+ G_DBUS_CONNECTION_FLAGS_NONE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:guid:
+ *
+ * The GUID of the peer performing the role of server when
+ * authenticating.
+ *
+ * If you are constructing a #GDBusConnection and pass
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER in the
+ * #GDBusConnection:flags property then you MUST also set this
+ * property to a valid guid.
+ *
+ * If you are constructing a #GDBusConnection and pass
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT in the
+ * #GDBusConnection:flags property you will be able to read the GUID
+ * of the other peer here after the connection has been succesfully
+ * initialized.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_GUID,
+ g_param_spec_string ("guid",
+ _("GUID"),
+ _("GUID of the server peer"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:unique-name:
+ *
+ * The unique name as assigned by the message bus or %NULL if the
+ * connection is not open or not a message bus connection.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_UNIQUE_NAME,
+ g_param_spec_string ("unique-name",
+ _("unique-name"),
+ _("Unique name of bus connection"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:closed:
+ *
+ * A boolean specifying whether the connection has been closed.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CLOSED,
+ g_param_spec_boolean ("closed",
+ _("Closed"),
+ _("Whether the connection is closed"),
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:exit-on-close:
+ *
+ * A boolean specifying whether the process will be terminated (by
+ * calling <literal>raise(SIGTERM)</literal>) if the connection
+ * is closed by the remote peer.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_EXIT_ON_CLOSE,
+ g_param_spec_boolean ("exit-on-close",
+ _("Exit on close"),
+ _("Whether the process is terminated when the connection is closed"),
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:capabilities:
+ *
+ * Flags from the #GDBusCapabilityFlags enumeration
+ * representing connection features negotiated with the other peer.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CAPABILITY_FLAGS,
+ g_param_spec_flags ("capabilities",
+ _("Capabilities"),
+ _("Capabilities"),
+ G_TYPE_DBUS_CAPABILITY_FLAGS,
+ G_DBUS_CAPABILITY_FLAGS_NONE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection:authentication-observer:
+ *
+ * A #GDBusAuthObserver object to assist in the authentication process or %NULL.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_AUTHENTICATION_OBSERVER,
+ g_param_spec_object ("authentication-observer",
+ _("Authentication Observer"),
+ _("Object used to assist in the authentication process"),
+ G_TYPE_DBUS_AUTH_OBSERVER,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusConnection::closed:
+ * @connection: The #GDBusConnection emitting the signal.
+ * @remote_peer_vanished: %TRUE if @connection is closed because the
+ * remote peer closed its end of the connection.
+ * @error: A #GError with more details about the event or %NULL.
+ *
+ * Emitted when the connection is closed.
+ *
+ * The cause of this event can be
+ * <itemizedlist>
+ * <listitem><para>
+ * If g_dbus_connection_close() is called. In this case
+ * @remote_peer_vanished is set to %FALSE and @error is %NULL.
+ * </para></listitem>
+ * <listitem><para>
+ * If the remote peer closes the connection. In this case
+ * @remote_peer_vanished is set to %TRUE and @error is set.
+ * </para></listitem>
+ * <listitem><para>
+ * If the remote peer sends invalid or malformed data. In this
+ * case @remote_peer_vanished is set to %FALSE and @error
+ * is set.
+ * </para></listitem>
+ * </itemizedlist>
+ *
+ * Upon receiving this signal, you should give up your reference to
+ * @connection. You are guaranteed that this signal is emitted only
+ * once.
+ */
+ signals[CLOSED_SIGNAL] = g_signal_new ("closed",
+ G_TYPE_DBUS_CONNECTION,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GDBusConnectionClass, closed),
+ NULL,
+ NULL,
+ _gio_marshal_VOID__BOOLEAN_BOXED,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_BOOLEAN,
+ G_TYPE_ERROR);
+}
+
+static void
+g_dbus_connection_init (GDBusConnection *connection)
+{
+ connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, G_TYPE_DBUS_CONNECTION, GDBusConnectionPrivate);
+
+ connection->priv->lock = g_mutex_new ();
+ connection->priv->init_lock = g_mutex_new ();
+
+ connection->priv->map_method_serial_to_send_message_data = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ connection->priv->map_rule_to_signal_data = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ connection->priv->map_id_to_signal_data = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+ connection->priv->map_sender_to_signal_data_array = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ connection->priv->map_object_path_to_eo = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) exported_object_free);
+
+ connection->priv->map_id_to_ei = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ connection->priv->map_object_path_to_es = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) exported_subtree_free);
+
+ connection->priv->map_id_to_es = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ connection->priv->main_context_at_construction = g_main_context_get_thread_default ();
+ if (connection->priv->main_context_at_construction != NULL)
+ g_main_context_ref (connection->priv->main_context_at_construction);
+
+ connection->priv->filters = g_ptr_array_new ();
+}
+
+GIOStream *
+g_dbus_connection_get_stream (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ return connection->priv->stream;
+}
+
+
+/**
+ * g_dbus_connection_is_closed:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets whether @connection is closed.
+ *
+ * Returns: %TRUE if the connection is closed, %FALSE otherwise.
+ **/
+gboolean
+g_dbus_connection_is_closed (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ return connection->priv->closed;
+}
+
+/**
+ * g_dbus_connection_get_capabilities:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the capabilities negotiated with the remote peer
+ *
+ * Returns: One or more flags from the #GDBusCapabilityFlags enumeration.
+ */
+GDBusCapabilityFlags
+g_dbus_connection_get_capabilities (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CAPABILITY_FLAGS_NONE);
+ return connection->priv->capabilities;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GDBusConnection *connection;
+ GError *error;
+ gboolean remote_peer_vanished;
+} EmitClosedData;
+
+static void
+emit_closed_data_free (EmitClosedData *data)
+{
+ g_object_unref (data->connection);
+ if (data->error != NULL)
+ g_error_free (data->error);
+ g_free (data);
+}
+
+static gboolean
+emit_closed_in_idle (gpointer user_data)
+{
+ EmitClosedData *data = user_data;
+ gboolean result;
+
+ g_object_notify (G_OBJECT (data->connection), "closed");
+ g_signal_emit (data->connection,
+ signals[CLOSED_SIGNAL],
+ 0,
+ data->remote_peer_vanished,
+ data->error,
+ &result);
+ return FALSE;
+}
+
+/* Can be called from any thread, must hold lock */
+static void
+set_closed_unlocked (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error)
+{
+ GSource *idle_source;
+ EmitClosedData *data;
+
+ CONNECTION_ENSURE_LOCK (connection);
+
+ g_assert (!connection->priv->closed);
+
+ connection->priv->closed = TRUE;
+
+ data = g_new0 (EmitClosedData, 1);
+ data->connection = g_object_ref (connection);
+ data->remote_peer_vanished = remote_peer_vanished;
+ data->error = error != NULL ? g_error_copy (error) : NULL;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ emit_closed_in_idle,
+ data,
+ (GDestroyNotify) emit_closed_data_free);
+ g_source_attach (idle_source, connection->priv->main_context_at_construction);
+ g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_close:
+ * @connection: A #GDBusConnection.
+ *
+ * Closes @connection. Note that this never causes the process to
+ * exit (this might only happen if the other end of a shared message
+ * bus connection disconnects).
+ *
+ * If @connection is already closed, this method does nothing.
+ */
+void
+g_dbus_connection_close (GDBusConnection *connection)
+{
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+ CONNECTION_LOCK (connection);
+ if (!connection->priv->closed)
+ {
+ GError *error = NULL;
+
+ /* TODO: do this async */
+ //g_debug ("closing connection %p's stream %p", connection, connection->priv->stream);
+ if (!g_io_stream_close (connection->priv->stream, NULL, &error))
+ {
+ g_warning ("Error closing stream: %s", error->message);
+ g_error_free (error);
+ }
+
+ set_closed_unlocked (connection, FALSE, NULL);
+ }
+ CONNECTION_UNLOCK (connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+g_dbus_connection_send_message_unlocked (GDBusConnection *connection,
+ GDBusMessage *message,
+ volatile guint32 *out_serial,
+ GError **error)
+{
+ guchar *blob;
+ gsize blob_size;
+ guint32 serial_to_use;
+ gboolean ret;
+
+ CONNECTION_ENSURE_LOCK (connection);
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+
+ /* TODO: check all necessary headers are present */
+
+ ret = FALSE;
+ blob = NULL;
+
+ if (out_serial != NULL)
+ *out_serial = 0;
+
+ if (connection->priv->closed)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_CLOSED,
+ _("The connection is closed"));
+ goto out;
+ }
+
+ blob = g_dbus_message_to_blob (message,
+ &blob_size,
+ error);
+ if (blob == NULL)
+ goto out;
+
+ serial_to_use = ++connection->priv->last_serial; /* TODO: handle overflow */
+
+ switch (blob[0])
+ {
+ case 'l':
+ ((guint32 *) blob)[2] = GUINT32_TO_LE (serial_to_use);
+ break;
+ case 'B':
+ ((guint32 *) blob)[2] = GUINT32_TO_BE (serial_to_use);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+#if 0
+ g_printerr ("Writing message of %" G_GSIZE_FORMAT " bytes (serial %d) on %p:\n",
+ blob_size, serial_to_use, connection);
+ g_printerr ("----\n");
+ hexdump (blob, blob_size);
+ g_printerr ("----\n");
+#endif
+
+ /* TODO: use connection->priv->auth to encode the blob */
+
+ if (out_serial != NULL)
+ {
+ *out_serial = serial_to_use;
+ }
+ g_dbus_message_set_serial (message, serial_to_use);
+
+ _g_dbus_worker_send_message (connection->priv->worker,
+ message,
+ (gchar*) blob,
+ blob_size);
+ blob = NULL; /* since _g_dbus_worker_send_message() steals the blob */
+
+ ret = TRUE;
+
+ out:
+ g_free (blob);
+
+ return ret;
+}
+
+/**
+ * g_dbus_connection_send_message:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Asynchronously sends @message to the peer represented by @connection.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: %TRUE if the message was well-formed and queued for
+ * transmission, %FALSE if @error is set.
+ */
+gboolean
+g_dbus_connection_send_message (GDBusConnection *connection,
+ GDBusMessage *message,
+ volatile guint32 *out_serial,
+ GError **error)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ CONNECTION_LOCK (connection);
+ ret = g_dbus_connection_send_message_unlocked (connection, message, out_serial, error);
+ CONNECTION_UNLOCK (connection);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ volatile gint ref_count;
+ GDBusConnection *connection;
+ guint32 serial;
+ GSimpleAsyncResult *simple;
+
+ GMainContext *main_context;
+
+ GCancellable *cancellable;
+
+ gulong cancellable_handler_id;
+
+ GSource *timeout_source;
+
+ gboolean delivered;
+} SendMessageData;
+
+static SendMessageData *
+send_message_data_ref (SendMessageData *data)
+{
+ g_atomic_int_inc (&data->ref_count);
+ return data;
+}
+
+static void
+send_message_data_unref (SendMessageData *data)
+{
+ if (g_atomic_int_dec_and_test (&data->ref_count))
+ {
+ g_assert (data->timeout_source == NULL);
+ g_assert (data->simple == NULL);
+ g_assert (data->cancellable_handler_id == 0);
+ g_object_unref (data->connection);
+ if (data->cancellable != NULL)
+ g_object_unref (data->cancellable);
+ if (data->main_context != NULL)
+ g_main_context_unref (data->main_context);
+ g_free (data);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* can be called from any thread with lock held - caller must have prepared GSimpleAsyncResult already */
+static void
+send_message_with_reply_deliver (SendMessageData *data)
+{
+ CONNECTION_ENSURE_LOCK (data->connection);
+
+ g_assert (!data->delivered);
+
+ data->delivered = TRUE;
+
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ data->simple = NULL;
+
+ if (data->timeout_source != NULL)
+ {
+ g_source_destroy (data->timeout_source);
+ data->timeout_source = NULL;
+ }
+ if (data->cancellable_handler_id > 0)
+ {
+ g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+ data->cancellable_handler_id = 0;
+ }
+
+ g_warn_if_fail (g_hash_table_remove (data->connection->priv->map_method_serial_to_send_message_data,
+ GUINT_TO_POINTER (data->serial)));
+
+ send_message_data_unref (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock */
+static void
+send_message_data_deliver_reply_unlocked (SendMessageData *data,
+ GDBusMessage *reply)
+{
+ if (data->delivered)
+ goto out;
+
+ g_simple_async_result_set_op_res_gpointer (data->simple,
+ g_object_ref (reply),
+ g_object_unref);
+
+ send_message_with_reply_deliver (data);
+
+ out:
+ ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+send_message_with_reply_cancelled_idle_cb (gpointer user_data)
+{
+ SendMessageData *data = user_data;
+
+ CONNECTION_LOCK (data->connection);
+ if (data->delivered)
+ goto out;
+
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+
+ send_message_with_reply_deliver (data);
+
+ out:
+ CONNECTION_UNLOCK (data->connection);
+ return FALSE;
+}
+
+/* Can be called from any thread with or without lock held */
+static void
+send_message_with_reply_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ SendMessageData *data = user_data;
+ GSource *idle_source;
+
+ /* postpone cancellation to idle handler since we may be called directly
+ * via g_cancellable_connect() (e.g. holding lock)
+ */
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ send_message_with_reply_cancelled_idle_cb,
+ send_message_data_ref (data),
+ (GDestroyNotify) send_message_data_unref);
+ g_source_attach (idle_source, data->main_context);
+ g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+send_message_with_reply_timeout_cb (gpointer user_data)
+{
+ SendMessageData *data = user_data;
+
+ CONNECTION_LOCK (data->connection);
+ if (data->delivered)
+ goto out;
+
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_TIMED_OUT,
+ _("Timeout was reached"));
+
+ send_message_with_reply_deliver (data);
+
+ out:
+ CONNECTION_UNLOCK (data->connection);
+
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_connection_send_message_with_reply_unlocked (GDBusConnection *connection,
+ GDBusMessage *message,
+ gint timeout_msec,
+ volatile guint32 *out_serial,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ SendMessageData *data;
+ GError *error;
+ volatile guint32 serial;
+
+ data = NULL;
+
+ if (out_serial == NULL)
+ out_serial = &serial;
+
+ if (timeout_msec == -1)
+ timeout_msec = 30 * 1000; /* TODO: check 30 secs is the default timeout */
+
+ simple = g_simple_async_result_new (G_OBJECT (connection),
+ callback,
+ user_data,
+ g_dbus_connection_send_message_with_reply);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ {
+ g_simple_async_result_set_error (simple,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ goto out;
+ }
+
+ if (connection->priv->closed)
+ {
+ g_simple_async_result_set_error (simple,
+ G_IO_ERROR,
+ G_IO_ERROR_CLOSED,
+ _("The connection is closed"));
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ goto out;
+ }
+
+ error = NULL;
+ if (!g_dbus_connection_send_message_unlocked (connection, message, out_serial, &error))
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ goto out;
+ }
+
+ data = g_new0 (SendMessageData, 1);
+ data->ref_count = 1;
+ data->connection = g_object_ref (connection);
+ data->simple = simple;
+ data->serial = *out_serial;
+ data->main_context = g_main_context_get_thread_default ();
+ if (data->main_context != NULL)
+ g_main_context_ref (data->main_context);
+
+ if (cancellable != NULL)
+ {
+ data->cancellable = g_object_ref (cancellable);
+ data->cancellable_handler_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (send_message_with_reply_cancelled_cb),
+ send_message_data_ref (data),
+ (GDestroyNotify) send_message_data_unref);
+ g_object_set_data_full (G_OBJECT (simple),
+ "cancellable",
+ g_object_ref (cancellable),
+ (GDestroyNotify) g_object_unref);
+ }
+
+ data->timeout_source = g_timeout_source_new (timeout_msec);
+ g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (data->timeout_source,
+ send_message_with_reply_timeout_cb,
+ send_message_data_ref (data),
+ (GDestroyNotify) send_message_data_unref);
+ g_source_attach (data->timeout_source, data->main_context);
+ g_source_unref (data->timeout_source);
+
+ g_hash_table_insert (connection->priv->map_method_serial_to_send_message_data,
+ GUINT_TO_POINTER (*out_serial),
+ data);
+
+ out:
+ ;
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously sends @message to the peer represented by @connection.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_connection_send_message_with_reply_finish() to get the result of the operation.
+ * See g_dbus_connection_send_message_with_reply_sync() for the synchronous version.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ */
+void
+g_dbus_connection_send_message_with_reply (GDBusConnection *connection,
+ GDBusMessage *message,
+ gint timeout_msec,
+ volatile guint32 *out_serial,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1);
+
+ CONNECTION_LOCK (connection);
+ g_dbus_connection_send_message_with_reply_unlocked (connection,
+ message,
+ timeout_msec,
+ out_serial,
+ cancellable,
+ callback,
+ user_data);
+ CONNECTION_UNLOCK (connection);
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_send_message_with_reply().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_send_message_with_reply().
+ *
+ * Note that @error is only set if a local in-process error
+ * occured. That is to say that the returned #GDBusMessage object may
+ * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use
+ * g_dbus_message_to_gerror() to transcode this to a #GError.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage or %NULL if @error is set.
+ */
+GDBusMessage *
+g_dbus_connection_send_message_with_reply_finish (GDBusConnection *connection,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GDBusMessage *reply;
+ GCancellable *cancellable;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ reply = NULL;
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_connection_send_message_with_reply);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ reply = g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+ cancellable = g_object_get_data (G_OBJECT (simple), "cancellable");
+ if (cancellable != NULL && g_cancellable_is_cancelled (cancellable))
+ {
+ g_object_unref (reply);
+ reply = NULL;
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ }
+ out:
+ return reply;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GAsyncResult *res;
+ GMainContext *context;
+ GMainLoop *loop;
+} SendMessageSyncData;
+
+static void
+send_message_with_reply_sync_cb (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SendMessageSyncData *data = user_data;
+ data->res = g_object_ref (res);
+ g_main_loop_quit (data->loop);
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply_sync:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously sends @message to the peer represented by @connection
+ * and blocks the calling thread until a reply is received or the
+ * timeout is reached. See g_dbus_connection_send_message_with_reply()
+ * for the asynchronous version of this method.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * Note that @error is only set if a local in-process error
+ * occured. That is to say that the returned #GDBusMessage object may
+ * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use
+ * g_dbus_message_to_gerror() to transcode this to a #GError.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage that is the reply to @message or %NULL if @error is set.
+ */
+GDBusMessage *
+g_dbus_connection_send_message_with_reply_sync (GDBusConnection *connection,
+ GDBusMessage *message,
+ gint timeout_msec,
+ volatile guint32 *out_serial,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SendMessageSyncData *data;
+ GDBusMessage *reply;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data = g_new0 (SendMessageSyncData, 1);
+ data->context = g_main_context_new ();
+ data->loop = g_main_loop_new (data->context, FALSE);
+
+ g_main_context_push_thread_default (data->context);
+
+ g_dbus_connection_send_message_with_reply (connection,
+ message,
+ timeout_msec,
+ out_serial,
+ cancellable,
+ (GAsyncReadyCallback) send_message_with_reply_sync_cb,
+ data);
+ g_main_loop_run (data->loop);
+ reply = g_dbus_connection_send_message_with_reply_finish (connection,
+ data->res,
+ error);
+
+ g_main_context_pop_thread_default (data->context);
+
+ g_main_context_unref (data->context);
+ g_main_loop_unref (data->loop);
+ g_object_unref (data->res);
+ g_free (data);
+
+ return reply;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GDBusMessageFilterFunction func;
+ gpointer user_data;
+} FilterCallback;
+
+typedef struct
+{
+ guint id;
+ GDBusMessageFilterFunction filter_function;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+} FilterData;
+
+/* Called in worker's thread - we must not block */
+static void
+on_worker_message_received (GDBusWorker *worker,
+ GDBusMessage *message,
+ gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+ FilterCallback *filters;
+ gboolean consumed_by_filter;
+ guint num_filters;
+ guint n;
+
+ //g_debug ("in on_worker_message_received");
+
+ g_object_ref (connection);
+
+ /* First collect the set of callback functions */
+ CONNECTION_LOCK (connection);
+ num_filters = connection->priv->filters->len;
+ filters = g_new0 (FilterCallback, num_filters);
+ for (n = 0; n < num_filters; n++)
+ {
+ FilterData *data = connection->priv->filters->pdata[n];
+ filters[n].func = data->filter_function;
+ filters[n].user_data = data->user_data;
+ }
+ CONNECTION_UNLOCK (connection);
+
+ /* the call the filters in order (without holding the lock) */
+ consumed_by_filter = FALSE;
+ for (n = 0; n < num_filters; n++)
+ {
+ consumed_by_filter = filters[n].func (connection,
+ message,
+ filters[n].user_data);
+ if (consumed_by_filter)
+ break;
+ }
+
+ /* Standard dispatch unless the filter ate the message */
+ if (!consumed_by_filter)
+ {
+ GDBusMessageType message_type;
+
+ message_type = g_dbus_message_get_type (message);
+ if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN || message_type == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ guint32 reply_serial;
+ SendMessageData *send_message_data;
+
+ reply_serial = g_dbus_message_get_reply_serial (message);
+ CONNECTION_LOCK (connection);
+ send_message_data = g_hash_table_lookup (connection->priv->map_method_serial_to_send_message_data,
+ GUINT_TO_POINTER (reply_serial));
+ if (send_message_data != NULL)
+ {
+ //g_debug ("delivering reply/error for serial %d for %p", reply_serial, connection);
+ send_message_data_deliver_reply_unlocked (send_message_data, message);
+ }
+ else
+ {
+ //g_debug ("message reply/error for serial %d but no SendMessageData found for %p", reply_serial, connection);
+ }
+ CONNECTION_UNLOCK (connection);
+ }
+ else if (message_type == G_DBUS_MESSAGE_TYPE_SIGNAL)
+ {
+ CONNECTION_LOCK (connection);
+ distribute_signals (connection, message);
+ CONNECTION_UNLOCK (connection);
+ }
+ else if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ CONNECTION_LOCK (connection);
+ distribute_method_call (connection, message);
+ CONNECTION_UNLOCK (connection);
+ }
+ }
+
+ g_object_unref (connection);
+ g_free (filters);
+}
+
+/* Called in worker's thread - we must not block */
+static void
+on_worker_closed (GDBusWorker *worker,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+
+ //g_debug ("in on_worker_closed: %s", error->message);
+
+ CONNECTION_LOCK (connection);
+ if (!connection->priv->closed)
+ set_closed_unlocked (connection, remote_peer_vanished, error);
+ CONNECTION_UNLOCK (connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Determines the biggest set of capabilities we can support on this connection */
+static GDBusCapabilityFlags
+get_offered_capabilities_max (GDBusConnection *connection)
+{
+ GDBusCapabilityFlags ret;
+ ret = G_DBUS_CAPABILITY_FLAGS_NONE;
+#ifdef G_OS_UNIX
+ if (G_IS_UNIX_CONNECTION (connection->priv->stream))
+ {
+ ret |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+ }
+#endif
+ return ret;
+}
+
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (initable);
+ gboolean ret;
+
+ /* This method needs to be idempotent to work with the singleton
+ * pattern. See the docs for g_initable_init(). We implement this by
+ * locking.
+ *
+ * Unfortunately we can't use the main lock since the on_worker_*()
+ * callbacks above needs the lock during initialization (for message
+ * bus connections we do a synchronous Hello() call on the bus).
+ */
+ g_mutex_lock (connection->priv->init_lock);
+
+ ret = FALSE;
+
+ if (connection->priv->is_initialized)
+ {
+ if (connection->priv->stream != NULL)
+ ret = TRUE;
+ else
+ g_assert (connection->priv->initialization_error != NULL);
+ goto out;
+ }
+ g_assert (connection->priv->initialization_error == NULL);
+
+ /* The user can pass multiple (but mutally exclusive) construct
+ * properties:
+ *
+ * - stream (of type GIOStream)
+ * - address (of type gchar*)
+ *
+ * At the end of the day we end up with a non-NULL GIOStream
+ * object in connection->priv->stream.
+ */
+ if (connection->priv->address != NULL)
+ {
+ g_assert (connection->priv->stream == NULL);
+
+ if ((connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) ||
+ (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS))
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Unsupported flags encountered when constructing a client-side connection"));
+ goto out;
+ }
+
+ connection->priv->stream = g_dbus_address_get_stream_sync (connection->priv->address,
+ NULL, /* TODO: out_guid */
+ cancellable,
+ &connection->priv->initialization_error);
+ if (connection->priv->stream == NULL)
+ goto out;
+ }
+ else if (connection->priv->stream != NULL)
+ {
+ /* nothing to do */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ /* Authenticate the connection */
+ if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER)
+ {
+ g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT));
+ g_assert (connection->priv->guid != NULL);
+ connection->priv->auth = _g_dbus_auth_new (connection->priv->stream);
+ if (!_g_dbus_auth_run_server (connection->priv->auth,
+ connection->priv->authentication_observer,
+ connection->priv->guid,
+ (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS),
+ get_offered_capabilities_max (connection),
+ &connection->priv->capabilities,
+ &connection->priv->crendentials,
+ cancellable,
+ &connection->priv->initialization_error))
+ goto out;
+ }
+ else if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT)
+ {
+ g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER));
+ g_assert (connection->priv->guid == NULL);
+ connection->priv->auth = _g_dbus_auth_new (connection->priv->stream);
+ connection->priv->guid = _g_dbus_auth_run_client (connection->priv->auth,
+ get_offered_capabilities_max (connection),
+ &connection->priv->capabilities,
+ cancellable,
+ &connection->priv->initialization_error);
+ if (connection->priv->guid == NULL)
+ goto out;
+ }
+
+ if (connection->priv->authentication_observer != NULL)
+ {
+ g_object_unref (connection->priv->authentication_observer);
+ connection->priv->authentication_observer = NULL;
+ }
+
+ //g_output_stream_flush (G_SOCKET_CONNECTION (connection->priv->stream)
+
+ //g_debug ("haz unix fd passing powers: %d", connection->priv->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+ /* Hack used until
+ *
+ * https://bugzilla.gnome.org/show_bug.cgi?id=616458
+ *
+ * has been resolved
+ */
+ if (G_IS_SOCKET_CONNECTION (connection->priv->stream))
+ {
+ g_socket_set_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection->priv->stream)), FALSE);
+ }
+
+ connection->priv->worker = _g_dbus_worker_new (connection->priv->stream,
+ connection->priv->capabilities,
+ on_worker_message_received,
+ on_worker_closed,
+ connection);
+
+ /* if a bus connection, invoke org.freedesktop.DBus.Hello - this is how we're getting a name */
+ if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+ {
+ GVariant *hello_result;
+ const gchar *s;
+
+ hello_result = g_dbus_connection_invoke_method_sync (connection,
+ "org.freedesktop.DBus", /* name */
+ "/org/freedesktop/DBus", /* path */
+ "org.freedesktop.DBus", /* interface */
+ "Hello",
+ NULL, /* parameters */
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL, /* TODO: cancellable */
+ &connection->priv->initialization_error);
+ if (hello_result == NULL)
+ goto out;
+
+ g_variant_get (hello_result, "(s)", &s);
+ connection->priv->bus_unique_name = g_strdup (s);
+ g_variant_unref (hello_result);
+ //g_debug ("unique name is `%s'", connection->priv->bus_unique_name);
+ }
+
+ connection->priv->is_initialized = TRUE;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ g_assert (connection->priv->initialization_error != NULL);
+ g_propagate_error (error, g_error_copy (connection->priv->initialization_error));
+ }
+
+ g_mutex_unlock (connection->priv->init_lock);
+
+ return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+ initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+async_init_thread (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+
+ if (!g_initable_init (G_INITABLE (object), cancellable, &error))
+ {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ }
+}
+
+static void
+async_initable_init_async (GAsyncInitable *initable,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+
+ g_return_if_fail (G_IS_INITABLE (initable));
+
+ res = g_simple_async_result_new (G_OBJECT (initable), callback, user_data,
+ async_initable_init_async);
+ g_simple_async_result_run_in_thread (res, async_init_thread,
+ io_priority, cancellable);
+ g_object_unref (res);
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ return TRUE; /* Errors handled by base impl */
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+ /* We basically just want to use GIO's default implementation - though that one is
+ * unfortunately broken, see #615111. So we copy-paste a fixed-up version.
+ */
+ async_initable_iface->init_async = async_initable_init_async;
+ async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_new:
+ * @stream: A #GIOStream.
+ * @guid: The GUID to use if a authenticating as a server or %NULL.
+ * @flags: Flags describing how to make the connection.
+ * @authentication_observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously sets up a D-Bus connection for exchanging D-Bus messages
+ * with the end represented by @stream.
+ *
+ * If %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER is set in @flags,
+ * @auth_observer (if not %NULL) is used to assist in the client
+ * authentication process.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_connection_new_finish() to get the result of the
+ * operation.
+ *
+ * This is a asynchronous failable constructor. See
+ * g_dbus_connection_new_sync() for the synchronous
+ * version.
+ */
+void
+g_dbus_connection_new (GIOStream *stream,
+ const gchar *guid,
+ GDBusConnectionFlags flags,
+ GDBusAuthObserver *authentication_observer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (G_IS_IO_STREAM (stream));
+ g_async_initable_new_async (G_TYPE_DBUS_CONNECTION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ "stream", stream,
+ "guid", guid,
+ "flags", flags,
+ "authentication-observer", authentication_observer,
+ NULL);
+}
+
+/**
+ * g_dbus_connection_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_new().
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *object;
+ GObject *source_object;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ source_object = g_async_result_get_source_object (res);
+ g_assert (source_object != NULL);
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+ res,
+ error);
+ g_object_unref (source_object);
+ if (object != NULL)
+ return G_DBUS_CONNECTION (object);
+ else
+ return NULL;
+}
+
+/**
+ * g_dbus_connection_new_sync:
+ * @stream: A #GIOStream.
+ * @guid: The GUID to use if a authenticating as a server or %NULL.
+ * @flags: Flags describing how to make the connection.
+ * @authentication_observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously sets up a D-Bus connection for exchanging D-Bus messages
+ * with the end represented by @stream.
+ *
+ * If %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER is set in @flags,
+ * @auth_observer (if not %NULL) is used to assist in the client
+ * authentication process.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_connection_new() for the asynchronous version.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_sync (GIOStream *stream,
+ const gchar *guid,
+ GDBusConnectionFlags flags,
+ GDBusAuthObserver *authentication_observer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_initable_new (G_TYPE_DBUS_CONNECTION,
+ cancellable,
+ error,
+ "stream", stream,
+ "guid", guid,
+ "flags", flags,
+ "authentication-observer", authentication_observer,
+ NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_new_for_address:
+ * @address: A D-Bus address.
+ * @flags: Flags describing how to make the connection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects and sets up a D-Bus client connection for
+ * exchanging D-Bus messages with an endpoint specified by @address
+ * which must be in the D-Bus address format.
+ *
+ * This constructor can only be used to initiate client-side
+ * connections - use g_dbus_connection_new() if you need to act as the
+ * server. In particular, @flags cannot contain the
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_connection_new_finish() to get the result of the
+ * operation.
+ *
+ * This is a asynchronous failable constructor. See
+ * g_dbus_connection_new_for_address_sync() for the synchronous
+ * version.
+ */
+void
+g_dbus_connection_new_for_address (const gchar *address,
+ GDBusConnectionFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (address != NULL);
+ g_async_initable_new_async (G_TYPE_DBUS_CONNECTION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ "address", address,
+ "flags", flags,
+ NULL);
+}
+
+/**
+ * g_dbus_connection_new_for_address_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_new_for_address().
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_for_address_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *object;
+ GObject *source_object;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ source_object = g_async_result_get_source_object (res);
+ g_assert (source_object != NULL);
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+ res,
+ error);
+ g_object_unref (source_object);
+ if (object != NULL)
+ return G_DBUS_CONNECTION (object);
+ else
+ return NULL;
+}
+
+/**
+ * g_dbus_connection_new_for_address_sync:
+ * @address: A D-Bus address.
+ * @flags: Flags describing how to make the connection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects and sets up a D-Bus client connection for
+ * exchanging D-Bus messages with an endpoint specified by @address
+ * which must be in the D-Bus address format.
+ *
+ * This constructor can only be used to initiate client-side
+ * connections - use g_dbus_connection_new_sync() if you need to act
+ * as the server. In particular, @flags cannot contain the
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_connection_new_for_address() for the asynchronous version.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_for_address_sync (const gchar *address,
+ GDBusConnectionFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (address != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ return g_initable_new (G_TYPE_DBUS_CONNECTION,
+ cancellable,
+ error,
+ "address", address,
+ "flags", flags,
+ NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_set_exit_on_close:
+ * @connection: A #GDBusConnection.
+ * @exit_on_close: Whether the process should be terminated
+ * when @connection is closed by the remote peer.
+ *
+ * Sets whether the process should be terminated when @connection is
+ * closed by the remote peer. See #GDBusConnection:exit-on-close for
+ * more details.
+ */
+void
+g_dbus_connection_set_exit_on_close (GDBusConnection *connection,
+ gboolean exit_on_close)
+{
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+ connection->priv->exit_on_close = exit_on_close;
+}
+
+/**
+ * g_dbus_connection_get_exit_on_close:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets whether the process is terminated when @connection is
+ * closed by the remote peer. See
+ * #GDBusConnection:exit-on-close for more details.
+ *
+ * Returns: Whether the process is terminated when @connection is
+ * closed by the remote peer.
+ */
+gboolean
+g_dbus_connection_get_exit_on_close (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ return connection->priv->exit_on_close;
+}
+
+/**
+ * g_dbus_connection_get_guid:
+ * @connection: A #GDBusConnection.
+ *
+ * The GUID of the peer performing the role of server when
+ * authenticating. See #GDBusConnection:guid for more details.
+ *
+ * Returns: The GUID. Do not free this string, it is owned by
+ * @connection.
+ **/
+const gchar *
+g_dbus_connection_get_guid (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ return connection->priv->guid;
+}
+
+/**
+ * g_dbus_connection_get_unique_name:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the unique name of @connection as assigned by the message
+ * bus. This can also be used to figure out if @connection is a
+ * message bus connection.
+ *
+ * Returns: The unique name or %NULL if @connection is not a message
+ * bus connection. Do not free this string, it is owned by
+ * @connection.
+ **/
+const gchar *
+g_dbus_connection_get_unique_name (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ return connection->priv->bus_unique_name;
+}
+
+/**
+ * g_dbus_connection_get_peer_credentials:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the credentials of the authenticated peer. This will always
+ * return %NULL unless @connection acted as a server
+ * (e.g. %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER was passed)
+ * when set up and the client passed credentials as part of the
+ * authentication process.
+ *
+ * In a message bus setup, the message bus is always the server and
+ * each application is a client. So this method will always return
+ * %NULL for message bus clients.
+ *
+ * Returns: A #GCredentials or %NULL if not available. Do not free
+ * this object, it is owned by @connection.
+ */
+GCredentials *
+g_dbus_connection_get_peer_credentials (GDBusConnection *connection)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ return connection->priv->crendentials;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint _global_filter_id = 1;
+
+/**
+ * g_dbus_connection_add_filter:
+ * @connection: A #GDBusConnection.
+ * @filter_function: A filter function.
+ * @user_data: User data to pass to @filter_function.
+ * @user_data_free_func: Function to free @user_data with when filter
+ * is removed or %NULL.
+ *
+ * Adds a message filter. Filters are handlers that are run on all
+ * incoming messages, prior to standard dispatch. Filters are run in
+ * the order that they were added. The same handler can be added as a
+ * filter more than once, in which case it will be run more than once.
+ * Filters added during a filter callback won't be run on the message
+ * being processed.
+ *
+ * Note that filters are run in a dedicated message handling thread so
+ * they can't block and, generally, can't do anything but signal a
+ * worker thread. Also note that filters are rarely needed - use API
+ * such as g_dbus_connection_send_message_with_reply(),
+ * g_dbus_connection_signal_subscribe() or
+ * g_dbus_connection_invoke_method() instead.
+ *
+ * Returns: A filter identifier that can be used with
+ * g_dbus_connection_remove_filter().
+ */
+guint
+g_dbus_connection_add_filter (GDBusConnection *connection,
+ GDBusMessageFilterFunction filter_function,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ FilterData *data;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+ g_return_val_if_fail (filter_function != NULL, 0);
+
+ CONNECTION_LOCK (connection);
+ data = g_new0 (FilterData, 1);
+ data->id = _global_filter_id++; /* TODO: overflow etc. */
+ data->filter_function = filter_function;
+ data->user_data = user_data;
+ data->user_data_free_func = user_data_free_func;
+ g_ptr_array_add (connection->priv->filters, data);
+ CONNECTION_UNLOCK (connection);
+
+ return data->id;
+}
+
+/* only called from finalize(), removes all filters */
+static void
+purge_all_filters (GDBusConnection *connection)
+{
+ guint n;
+ for (n = 0; n < connection->priv->filters->len; n++)
+ {
+ FilterData *data = connection->priv->filters->pdata[n];
+ if (data->user_data_free_func != NULL)
+ data->user_data_free_func (data->user_data);
+ g_free (data);
+ }
+}
+
+void
+g_dbus_connection_remove_filter (GDBusConnection *connection,
+ guint filter_id)
+{
+ guint n;
+ FilterData *to_destroy;
+
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+ CONNECTION_LOCK (connection);
+ to_destroy = NULL;
+ for (n = 0; n < connection->priv->filters->len; n++)
+ {
+ FilterData *data = connection->priv->filters->pdata[n];
+ if (data->id == filter_id)
+ {
+ g_ptr_array_remove_index (connection->priv->filters, n);
+ to_destroy = data;
+ break;
+ }
+ }
+ CONNECTION_UNLOCK (connection);
+
+ /* do free without holding lock */
+ if (to_destroy != NULL)
+ {
+ if (to_destroy->user_data_free_func != NULL)
+ to_destroy->user_data_free_func (to_destroy->user_data);
+ g_free (to_destroy);
+ }
+ else
+ {
+ g_warning ("g_dbus_connection_remove_filter: No filter found for filter_id %d", filter_id);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ gchar *rule;
+ gchar *sender;
+ gchar *interface_name;
+ gchar *member;
+ gchar *object_path;
+ gchar *arg0;
+ GArray *subscribers;
+} SignalData;
+
+typedef struct
+{
+ GDBusSignalCallback callback;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+ guint id;
+ GMainContext *context;
+} SignalSubscriber;
+
+static void
+signal_data_free (SignalData *data)
+{
+ g_free (data->rule);
+ g_free (data->sender);
+ g_free (data->interface_name);
+ g_free (data->member);
+ g_free (data->object_path);
+ g_free (data->arg0);
+ g_array_free (data->subscribers, TRUE);
+ g_free (data);
+}
+
+static gchar *
+args_to_rule (const gchar *sender,
+ const gchar *interface_name,
+ const gchar *member,
+ const gchar *object_path,
+ const gchar *arg0)
+{
+ GString *rule;
+
+ rule = g_string_new ("type='signal'");
+ if (sender != NULL)
+ g_string_append_printf (rule, ",sender='%s'", sender);
+ if (interface_name != NULL)
+ g_string_append_printf (rule, ",interface='%s'", interface_name);
+ if (member != NULL)
+ g_string_append_printf (rule, ",member='%s'", member);
+ if (object_path != NULL)
+ g_string_append_printf (rule, ",path='%s'", object_path);
+ if (arg0 != NULL)
+ g_string_append_printf (rule, ",arg0='%s'", arg0);
+
+ return g_string_free (rule, FALSE);
+}
+
+static guint _global_subscriber_id = 1;
+static guint _global_registration_id = 1;
+static guint _global_subtree_registration_id = 1;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling */
+static void
+add_match_rule (GDBusConnection *connection,
+ const gchar *match_rule)
+{
+ GError *error;
+ GDBusMessage *message;
+
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+ "/org/freedesktop/DBus", /* path */
+ "org.freedesktop.DBus", /* interface */
+ "AddMatch");
+ g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule));
+
+ error = NULL;
+ if (!g_dbus_connection_send_message_unlocked (connection,
+ message,
+ NULL,
+ &error))
+ {
+ g_critical ("Error while sending AddMatch() message: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling */
+static void
+remove_match_rule (GDBusConnection *connection,
+ const gchar *match_rule)
+{
+ GError *error;
+ GDBusMessage *message;
+
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+ "/org/freedesktop/DBus", /* path */
+ "org.freedesktop.DBus", /* interface */
+ "RemoveMatch");
+ g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule));
+
+ error = NULL;
+ if (!g_dbus_connection_send_message_unlocked (connection,
+ message,
+ NULL,
+ &error))
+ {
+ g_critical ("Error while sending RemoveMatch() message: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_signal_data_for_name_lost_or_acquired (SignalData *signal_data)
+{
+ return g_strcmp0 (signal_data->sender, "org.freedesktop.DBus") == 0 &&
+ g_strcmp0 (signal_data->interface_name, "org.freedesktop.DBus") == 0 &&
+ g_strcmp0 (signal_data->object_path, "/org/freedesktop/DBus") == 0 &&
+ (g_strcmp0 (signal_data->member, "NameLost") == 0 ||
+ g_strcmp0 (signal_data->member, "NameAcquired") == 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_signal_subscribe:
+ * @connection: A #GDBusConnection.
+ * @sender: Sender name to match on. Must be either <literal>org.freedesktop.DBus</literal> (for listening to signals from the message bus daemon) or a unique name or %NULL to listen from all senders.
+ * @interface_name: D-Bus interface name to match on or %NULL to match on all interfaces.
+ * @member: D-Bus signal name to match on or %NULL to match on all signals.
+ * @object_path: Object path to match on or %NULL to match on all object paths.
+ * @arg0: Contents of first string argument to match on or %NULL to match on all kinds of arguments.
+ * @callback: Callback to invoke when there is a signal matching the requested data.
+ * @user_data: User data to pass to @callback.
+ * @user_data_free_func: Function to free @user_data with when subscription is removed or %NULL.
+ *
+ * Subscribes to signals on @connection and invokes @callback with a
+ * whenever the signal is received. Note that @callback
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * It is considered a programming error to use this function if @connection is closed.
+ *
+ * Note that if @sender is not <literal>org.freedesktop.DBus</literal> (for listening to signals from the
+ * message bus daemon), then it needs to be a unique bus name or %NULL (for listening to signals from any
+ * name) - you cannot pass a name like <literal>com.example.MyApp</literal>.
+ * Use e.g. g_bus_watch_name() to find the unique name for the owner of the name you are interested in. Also note
+ * that this function does not remove a subscription if @sender vanishes from the bus. You have to manually
+ * call g_dbus_connection_signal_unsubscribe() to remove a subscription.
+ *
+ * Returns: A subscription identifier that can be used with g_dbus_connection_signal_unsubscribe().
+ **/
+guint
+g_dbus_connection_signal_subscribe (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *interface_name,
+ const gchar *member,
+ const gchar *object_path,
+ const gchar *arg0,
+ GDBusSignalCallback callback,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ gchar *rule;
+ SignalData *signal_data;
+ SignalSubscriber subscriber;
+ GPtrArray *signal_data_array;
+
+ /* Right now we abort if AddMatch() fails since it can only fail with the bus being in
+ * an OOM condition. We might want to change that but that would involve making
+ * g_dbus_connection_signal_subscribe() asynchronous and having the call sites
+ * handle that. And there's really no sensible way of handling this short of retrying
+ * to add the match rule... and then there's the little thing that, hey, maybe there's
+ * a reason the bus in an OOM condition.
+ *
+ * Doable, but not really sure it's worth it...
+ */
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+ g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+ g_return_val_if_fail (sender == NULL || ((strcmp (sender, "org.freedesktop.DBus") == 0 || sender[0] == ':') &&
+ (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)), 0);
+ g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0);
+ g_return_val_if_fail (member == NULL || g_dbus_is_member_name (member), 0);
+ g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ CONNECTION_LOCK (connection);
+
+ rule = args_to_rule (sender, interface_name, member, object_path, arg0);
+
+ if (sender == NULL)
+ sender = "";
+
+ subscriber.callback = callback;
+ subscriber.user_data = user_data;
+ subscriber.user_data_free_func = user_data_free_func;
+ subscriber.id = _global_subscriber_id++; /* TODO: overflow etc. */
+ subscriber.context = g_main_context_get_thread_default ();
+ if (subscriber.context != NULL)
+ g_main_context_ref (subscriber.context);
+
+ /* see if we've already have this rule */
+ signal_data = g_hash_table_lookup (connection->priv->map_rule_to_signal_data, rule);
+ if (signal_data != NULL)
+ {
+ g_array_append_val (signal_data->subscribers, subscriber);
+ g_free (rule);
+ goto out;
+ }
+
+ signal_data = g_new0 (SignalData, 1);
+ signal_data->rule = rule;
+ signal_data->sender = g_strdup (sender);
+ signal_data->interface_name = g_strdup (interface_name);
+ signal_data->member = g_strdup (member);
+ signal_data->object_path = g_strdup (object_path);
+ signal_data->arg0 = g_strdup (arg0);
+ signal_data->subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+ g_array_append_val (signal_data->subscribers, subscriber);
+
+ g_hash_table_insert (connection->priv->map_rule_to_signal_data,
+ signal_data->rule,
+ signal_data);
+
+ /* Add the match rule to the bus...
+ *
+ * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
+ * always send such messages to to us.
+ */
+ if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+ {
+ if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+ {
+ add_match_rule (connection, signal_data->rule);
+ }
+ }
+
+ out:
+ g_hash_table_insert (connection->priv->map_id_to_signal_data,
+ GUINT_TO_POINTER (subscriber.id),
+ signal_data);
+
+ signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+ signal_data->sender);
+ if (signal_data_array == NULL)
+ {
+ signal_data_array = g_ptr_array_new ();
+ g_hash_table_insert (connection->priv->map_sender_to_signal_data_array,
+ g_strdup (signal_data->sender),
+ signal_data_array);
+ }
+ g_ptr_array_add (signal_data_array, signal_data);
+
+ CONNECTION_UNLOCK (connection);
+
+ return subscriber.id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling this */
+static void
+unsubscribe_id_internal (GDBusConnection *connection,
+ guint subscription_id,
+ GArray *out_removed_subscribers)
+{
+ SignalData *signal_data;
+ GPtrArray *signal_data_array;
+ guint n;
+
+ signal_data = g_hash_table_lookup (connection->priv->map_id_to_signal_data,
+ GUINT_TO_POINTER (subscription_id));
+ if (signal_data == NULL)
+ {
+ /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */
+ goto out;
+ }
+
+ for (n = 0; n < signal_data->subscribers->len; n++)
+ {
+ SignalSubscriber *subscriber;
+
+ subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n));
+ if (subscriber->id != subscription_id)
+ continue;
+
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_signal_data,
+ GUINT_TO_POINTER (subscription_id)));
+ g_array_append_val (out_removed_subscribers, *subscriber);
+ g_array_remove_index (signal_data->subscribers, n);
+
+ if (signal_data->subscribers->len == 0)
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_rule_to_signal_data, signal_data->rule));
+
+ signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+ signal_data->sender);
+ g_warn_if_fail (signal_data_array != NULL);
+ g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data));
+
+ if (signal_data_array->len == 0)
+ {
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_sender_to_signal_data_array, signal_data->sender));
+
+ /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
+ if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+ {
+ if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+ {
+ remove_match_rule (connection, signal_data->rule);
+ }
+ }
+
+ signal_data_free (signal_data);
+ }
+
+ goto out;
+ }
+
+ g_assert_not_reached ();
+
+ out:
+ ;
+}
+
+/**
+ * g_dbus_connection_signal_unsubscribe:
+ * @connection: A #GDBusConnection.
+ * @subscription_id: A subscription id obtained from g_dbus_connection_signal_subscribe().
+ *
+ * Unsubscribes from signals.
+ **/
+void
+g_dbus_connection_signal_unsubscribe (GDBusConnection *connection,
+ guint subscription_id)
+{
+ GArray *subscribers;
+ guint n;
+
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+ subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+
+ CONNECTION_LOCK (connection);
+ unsubscribe_id_internal (connection,
+ subscription_id,
+ subscribers);
+ CONNECTION_UNLOCK (connection);
+
+ /* invariant */
+ g_assert (subscribers->len == 0 || subscribers->len == 1);
+
+ /* call GDestroyNotify without lock held */
+ for (n = 0; n < subscribers->len; n++)
+ {
+ SignalSubscriber *subscriber;
+ subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+ if (subscriber->user_data_free_func != NULL)
+ subscriber->user_data_free_func (subscriber->user_data);
+ if (subscriber->context != NULL)
+ g_main_context_unref (subscriber->context);
+ }
+
+ g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ guint subscription_id;
+ GDBusSignalCallback callback;
+ gpointer user_data;
+ GDBusMessage *message;
+ GDBusConnection *connection;
+ const gchar *sender;
+ const gchar *path;
+ const gchar *interface;
+ const gchar *member;
+} SignalInstance;
+
+/* called on delivery thread (e.g. where g_dbus_connection_signal_subscribe() was called) with
+ * no locks held
+ */
+static gboolean
+emit_signal_instance_in_idle_cb (gpointer data)
+{
+ SignalInstance *signal_instance = data;
+ GVariant *parameters;
+ gboolean has_subscription;
+
+ parameters = g_dbus_message_get_body (signal_instance->message);
+ if (parameters == NULL)
+ {
+ parameters = g_variant_new ("()");
+ g_variant_ref_sink (parameters);
+ }
+ else
+ {
+ g_variant_ref_sink (parameters);
+ }
+
+#if 0
+ g_debug ("in emit_signal_instance_in_idle_cb (sender=%s path=%s interface=%s member=%s params=%s)",
+ signal_instance->sender,
+ signal_instance->path,
+ signal_instance->interface,
+ signal_instance->member,
+ g_variant_print (parameters, TRUE));
+#endif
+
+ /* Careful here, don't do the callback if we no longer has the subscription */
+ CONNECTION_LOCK (signal_instance->connection);
+ has_subscription = FALSE;
+ if (g_hash_table_lookup (signal_instance->connection->priv->map_id_to_signal_data,
+ GUINT_TO_POINTER (signal_instance->subscription_id)) != NULL)
+ has_subscription = TRUE;
+ CONNECTION_UNLOCK (signal_instance->connection);
+
+ if (has_subscription)
+ {
+ signal_instance->callback (signal_instance->connection,
+ signal_instance->sender,
+ signal_instance->path,
+ signal_instance->interface,
+ signal_instance->member,
+ parameters,
+ signal_instance->user_data);
+ }
+ if (parameters != NULL)
+ g_variant_unref (parameters);
+
+ return FALSE;
+}
+
+static void
+signal_instance_free (SignalInstance *signal_instance)
+{
+ g_object_unref (signal_instance->message);
+ g_object_unref (signal_instance->connection);
+ g_free (signal_instance);
+}
+
+/* called in message handler thread WITH lock held */
+static void
+schedule_callbacks (GDBusConnection *connection,
+ GPtrArray *signal_data_array,
+ GDBusMessage *message,
+ const gchar *sender)
+{
+ guint n, m;
+ const gchar *interface;
+ const gchar *member;
+ const gchar *path;
+ const gchar *arg0;
+
+ interface = NULL;
+ member = NULL;
+ path = NULL;
+ arg0 = NULL;
+
+ interface = g_dbus_message_get_interface (message);
+ member = g_dbus_message_get_member (message);
+ path = g_dbus_message_get_path (message);
+ arg0 = g_dbus_message_get_arg0 (message);
+
+#if 0
+ g_debug ("sender = `%s'", sender);
+ g_debug ("interface = `%s'", interface);
+ g_debug ("member = `%s'", member);
+ g_debug ("path = `%s'", path);
+ g_debug ("arg0 = `%s'", arg0);
+#endif
+
+ /* TODO: if this is slow, then we can change signal_data_array into
+ * map_object_path_to_signal_data_array or something.
+ */
+ for (n = 0; n < signal_data_array->len; n++)
+ {
+ SignalData *signal_data = signal_data_array->pdata[n];
+
+ if (signal_data->interface_name != NULL && g_strcmp0 (signal_data->interface_name, interface) != 0)
+ continue;
+
+ if (signal_data->member != NULL && g_strcmp0 (signal_data->member, member) != 0)
+ continue;
+
+ if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0)
+ continue;
+
+ if (signal_data->arg0 != NULL && g_strcmp0 (signal_data->arg0, arg0) != 0)
+ continue;
+
+ for (m = 0; m < signal_data->subscribers->len; m++)
+ {
+ SignalSubscriber *subscriber;
+ GSource *idle_source;
+ SignalInstance *signal_instance;
+
+ subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m));
+
+ signal_instance = g_new0 (SignalInstance, 1);
+ signal_instance->subscription_id = subscriber->id;
+ signal_instance->callback = subscriber->callback;
+ signal_instance->user_data = subscriber->user_data;
+ signal_instance->message = g_object_ref (message);
+ signal_instance->connection = g_object_ref (connection);
+ signal_instance->sender = sender;
+ signal_instance->path = path;
+ signal_instance->interface = interface;
+ signal_instance->member = member;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ emit_signal_instance_in_idle_cb,
+ signal_instance,
+ (GDestroyNotify) signal_instance_free);
+ g_source_attach (idle_source, subscriber->context);
+ g_source_unref (idle_source);
+ }
+ }
+}
+
+/* called in message handler thread with lock held */
+static void
+distribute_signals (GDBusConnection *connection,
+ GDBusMessage *message)
+{
+ GPtrArray *signal_data_array;
+ const gchar *sender;
+
+ sender = g_dbus_message_get_sender (message);
+
+ /* collect subcsribers that match on sender */
+ if (sender != NULL)
+ {
+ signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, sender);
+ if (signal_data_array != NULL) {
+ schedule_callbacks (connection, signal_data_array, message, sender);
+ }
+ }
+
+ /* collect subcsribers not matching on sender */
+ signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, "");
+ if (signal_data_array != NULL)
+ {
+ schedule_callbacks (connection, signal_data_array, message, sender);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* only called from finalize(), removes all subscriptions */
+static void
+purge_all_signal_subscriptions (GDBusConnection *connection)
+{
+ GHashTableIter iter;
+ gpointer key;
+ GArray *ids;
+ GArray *subscribers;
+ guint n;
+
+ ids = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_hash_table_iter_init (&iter, connection->priv->map_id_to_signal_data);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ guint subscription_id = GPOINTER_TO_UINT (key);
+ g_array_append_val (ids, subscription_id);
+ }
+
+ subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+ for (n = 0; n < ids->len; n++)
+ {
+ guint subscription_id = g_array_index (ids, guint, n);
+ unsubscribe_id_internal (connection,
+ subscription_id,
+ subscribers);
+ }
+ g_array_free (ids, TRUE);
+
+ /* call GDestroyNotify without lock held */
+ for (n = 0; n < subscribers->len; n++)
+ {
+ SignalSubscriber *subscriber;
+ subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+ if (subscriber->user_data_free_func != NULL)
+ subscriber->user_data_free_func (subscriber->user_data);
+ if (subscriber->context != NULL)
+ g_main_context_unref (subscriber->context);
+ }
+
+ g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedObject
+{
+ gchar *object_path;
+ GDBusConnection *connection;
+
+ /* maps gchar* -> ExportedInterface* */
+ GHashTable *map_if_name_to_ei;
+};
+
+/* only called with lock held */
+static void
+exported_object_free (ExportedObject *eo)
+{
+ g_free (eo->object_path);
+ g_hash_table_unref (eo->map_if_name_to_ei);
+ g_free (eo);
+}
+
+typedef struct
+{
+ ExportedObject *eo;
+
+ guint id;
+ gchar *interface_name;
+ const GDBusInterfaceVTable *vtable;
+ const GDBusInterfaceInfo *introspection_data;
+
+ GMainContext *context;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+} ExportedInterface;
+
+/* called with lock held */
+static void
+exported_interface_free (ExportedInterface *ei)
+{
+ if (ei->user_data_free_func != NULL)
+ {
+ /* TODO: push to thread-default mainloop */
+ ei->user_data_free_func (ei->user_data);
+ }
+ if (ei->context != NULL)
+ {
+ g_main_context_unref (ei->context);
+ }
+ g_free (ei->interface_name);
+ g_free (ei);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GDBusConnection *connection;
+ GDBusMessage *message;
+ gpointer user_data;
+ const char *property_name;
+ const GDBusInterfaceVTable *vtable;
+ const GDBusInterfaceInfo *interface_info;
+ const GDBusPropertyInfo *property_info;
+} PropertyData;
+
+static void
+property_data_free (PropertyData *data)
+{
+ g_object_unref (data->connection);
+ g_object_unref (data->message);
+ g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_property_in_idle_cb (gpointer _data)
+{
+ PropertyData *data = _data;
+ GVariant *value;
+ GError *error;
+ GDBusMessage *reply;
+
+ error = NULL;
+ value = data->vtable->get_property (data->connection,
+ g_dbus_message_get_sender (data->message),
+ g_dbus_message_get_path (data->message),
+ data->interface_info->name,
+ data->property_name,
+ &error,
+ data->user_data);
+
+
+ if (value != NULL)
+ {
+ g_assert_no_error (error);
+
+ g_variant_ref_sink (value);
+ reply = g_dbus_message_new_method_reply (data->message);
+ g_dbus_message_set_body (reply, g_variant_new ("(v)", value));
+ g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+ g_variant_unref (value);
+ g_object_unref (reply);
+ }
+ else
+ {
+ gchar *dbus_error_name;
+
+ g_assert (error != NULL);
+
+ dbus_error_name = g_dbus_error_encode_gerror (error);
+ reply = g_dbus_message_new_method_error_literal (data->message,
+ dbus_error_name,
+ error->message);
+ g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+ g_free (dbus_error_name);
+ g_error_free (error);
+ g_object_unref (reply);
+ }
+
+ return FALSE;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_set_property_in_idle_cb (gpointer _data)
+{
+ PropertyData *data = _data;
+ GError *error;
+ GDBusMessage *reply;
+ GVariant *value;
+
+ error = NULL;
+ value = NULL;
+
+ g_variant_get (g_dbus_message_get_body (data->message),
+ "(ssv)",
+ NULL,
+ NULL,
+ &value);
+
+ /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the type
+ * of the given value is wrong
+ */
+ if (g_strcmp0 (g_variant_get_type_string (value), data->property_info->signature) != 0)
+ {
+ reply = g_dbus_message_new_method_error (data->message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("Error setting property `%s': Expected type `%s' but got `%s'"),
+ data->property_info->name,
+ data->property_info->signature,
+ g_variant_get_type_string (value));
+ goto out;
+ }
+
+ if (!data->vtable->set_property (data->connection,
+ g_dbus_message_get_sender (data->message),
+ g_dbus_message_get_path (data->message),
+ data->interface_info->name,
+ data->property_name,
+ value,
+ &error,
+ data->user_data))
+ {
+ gchar *dbus_error_name;
+ g_assert (error != NULL);
+ dbus_error_name = g_dbus_error_encode_gerror (error);
+ reply = g_dbus_message_new_method_error_literal (data->message,
+ dbus_error_name,
+ error->message);
+ g_free (dbus_error_name);
+ g_error_free (error);
+ }
+ else
+ {
+ reply = g_dbus_message_new_method_reply (data->message);
+ }
+
+ out:
+ g_assert (reply != NULL);
+ g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+
+ return FALSE;
+}
+
+/* called with lock held */
+static gboolean
+validate_and_maybe_schedule_property_getset (GDBusConnection *connection,
+ GDBusMessage *message,
+ gboolean is_get,
+ const GDBusInterfaceInfo *introspection_data,
+ const GDBusInterfaceVTable *vtable,
+ GMainContext *main_context,
+ gpointer user_data)
+{
+ gboolean handled;
+ const char *interface_name;
+ const char *property_name;
+ const GDBusPropertyInfo *property_info;
+ GSource *idle_source;
+ PropertyData *property_data;
+ GDBusMessage *reply;
+
+ handled = FALSE;
+
+ if (is_get)
+ g_variant_get (g_dbus_message_get_body (message),
+ "(ss)",
+ &interface_name,
+ &property_name);
+ else
+ g_variant_get (g_dbus_message_get_body (message),
+ "(ssv)",
+ &interface_name,
+ &property_name,
+ NULL);
+
+
+ if (is_get)
+ {
+ if (vtable == NULL || vtable->get_property == NULL)
+ goto out;
+ }
+ else
+ {
+ if (vtable == NULL || vtable->set_property == NULL)
+ goto out;
+ }
+
+ /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs
+ */
+ property_info = NULL;
+
+ /* TODO: the cost of this is O(n) - it might be worth caching the result */
+ property_info = g_dbus_interface_info_lookup_property (introspection_data, property_name);
+ if (property_info == NULL)
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("No such property `%s'"),
+ property_name);
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ if (is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("Property `%s' is not readable"),
+ property_name);
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+ else if (!is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("Property `%s' is not writable"),
+ property_name);
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ /* ok, got the property info - call user code in an idle handler */
+ property_data = g_new0 (PropertyData, 1);
+ property_data->connection = g_object_ref (connection);
+ property_data->message = g_object_ref (message);
+ property_data->user_data = user_data;
+ property_data->property_name = property_name;
+ property_data->vtable = vtable;
+ property_data->interface_info = introspection_data;
+ property_data->property_info = property_info;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ is_get ? invoke_get_property_in_idle_cb : invoke_set_property_in_idle_cb,
+ property_data,
+ (GDestroyNotify) property_data_free);
+ g_source_attach (idle_source, main_context);
+ g_source_unref (idle_source);
+
+ handled = TRUE;
+
+ out:
+ return handled;
+}
+
+/* called with lock held */
+static gboolean
+handle_getset_property (GDBusConnection *connection,
+ ExportedObject *eo,
+ GDBusMessage *message,
+ gboolean is_get)
+{
+ ExportedInterface *ei;
+ gboolean handled;
+ const char *interface_name;
+ const char *property_name;
+
+ handled = FALSE;
+
+ if (is_get)
+ g_variant_get (g_dbus_message_get_body (message),
+ "(ss)",
+ &interface_name,
+ &property_name);
+ else
+ g_variant_get (g_dbus_message_get_body (message),
+ "(ssv)",
+ &interface_name,
+ &property_name,
+ NULL);
+
+ /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+ * no such interface registered
+ */
+ ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+ if (ei == NULL)
+ {
+ GDBusMessage *reply;
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("No such interface `%s'"),
+ interface_name);
+ g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ handled = validate_and_maybe_schedule_property_getset (eo->connection,
+ message,
+ is_get,
+ ei->introspection_data,
+ ei->vtable,
+ ei->context,
+ ei->user_data);
+ out:
+ return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GDBusConnection *connection;
+ GDBusMessage *message;
+ gpointer user_data;
+ const GDBusInterfaceVTable *vtable;
+ const GDBusInterfaceInfo *interface_info;
+} PropertyGetAllData;
+
+static void
+property_get_all_data_free (PropertyData *data)
+{
+ g_object_unref (data->connection);
+ g_object_unref (data->message);
+ g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_all_properties_in_idle_cb (gpointer _data)
+{
+ PropertyGetAllData *data = _data;
+ GVariantBuilder *builder;
+ GVariant *packed;
+ GVariant *result;
+ GError *error;
+ GDBusMessage *reply;
+ guint n;
+
+ error = NULL;
+
+ /* TODO: Right now we never fail this call - we just omit values if
+ * a get_property() call is failing.
+ *
+ * We could fail the whole call if just a single get_property() call
+ * returns an error. We need clarification in the D-Bus spec about this.
+ */
+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ for (n = 0; data->interface_info->properties != NULL && data->interface_info->properties[n] != NULL; n++)
+ {
+ const GDBusPropertyInfo *property_info = data->interface_info->properties[n];
+ GVariant *value;
+
+ if (!(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+ continue;
+
+ value = data->vtable->get_property (data->connection,
+ g_dbus_message_get_sender (data->message),
+ g_dbus_message_get_path (data->message),
+ data->interface_info->name,
+ property_info->name,
+ NULL,
+ data->user_data);
+
+ if (value == NULL)
+ continue;
+
+ g_variant_ref_sink (value);
+ g_variant_builder_add (builder,
+ "{sv}",
+ property_info->name,
+ value);
+ g_variant_unref (value);
+ }
+ result = g_variant_builder_end (builder);
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value (builder, result); /* steals result since result is floating */
+ packed = g_variant_builder_end (builder);
+
+ reply = g_dbus_message_new_method_reply (data->message);
+ g_dbus_message_set_body (reply, packed);
+ g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+
+ return FALSE;
+}
+
+/* called with lock held */
+static gboolean
+validate_and_maybe_schedule_property_get_all (GDBusConnection *connection,
+ GDBusMessage *message,
+ const GDBusInterfaceInfo *introspection_data,
+ const GDBusInterfaceVTable *vtable,
+ GMainContext *main_context,
+ gpointer user_data)
+{
+ gboolean handled;
+ const char *interface_name;
+ GSource *idle_source;
+ PropertyGetAllData *property_get_all_data;
+
+ handled = FALSE;
+
+ g_variant_get (g_dbus_message_get_body (message),
+ "(s)",
+ &interface_name);
+
+ if (vtable == NULL || vtable->get_property == NULL)
+ goto out;
+
+ /* ok, got the property info - call user in an idle handler */
+ property_get_all_data = g_new0 (PropertyGetAllData, 1);
+ property_get_all_data->connection = g_object_ref (connection);
+ property_get_all_data->message = g_object_ref (message);
+ property_get_all_data->user_data = user_data;
+ property_get_all_data->vtable = vtable;
+ property_get_all_data->interface_info = introspection_data;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ invoke_get_all_properties_in_idle_cb,
+ property_get_all_data,
+ (GDestroyNotify) property_get_all_data_free);
+ g_source_attach (idle_source, main_context);
+ g_source_unref (idle_source);
+
+ handled = TRUE;
+
+ out:
+ return handled;
+}
+
+/* called with lock held */
+static gboolean
+handle_get_all_properties (GDBusConnection *connection,
+ ExportedObject *eo,
+ GDBusMessage *message)
+{
+ ExportedInterface *ei;
+ gboolean handled;
+ const char *interface_name;
+
+ handled = FALSE;
+
+ g_variant_get (g_dbus_message_get_body (message),
+ "(s)",
+ &interface_name);
+
+ /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+ * no such interface registered
+ */
+ ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+ if (ei == NULL)
+ {
+ GDBusMessage *reply;
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("No such interface"),
+ interface_name);
+ g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ handled = validate_and_maybe_schedule_property_get_all (eo->connection,
+ message,
+ ei->introspection_data,
+ ei->vtable,
+ ei->context,
+ ei->user_data);
+ out:
+ return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_append_header (GString *s)
+{
+ g_string_append (s,
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<!-- GDBus 0.1 -->\n"
+ "<node>\n");
+}
+
+static void
+introspect_append_standard_interfaces (GString *s)
+{
+ g_string_append (s,
+ " <interface name=\"org.freedesktop.DBus.Properties\">\n"
+ " <method name=\"Get\">\n"
+ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+ " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n"
+ " </method>\n"
+ " <method name=\"GetAll\">\n"
+ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+ " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n"
+ " </method>\n"
+ " <method name=\"Set\">\n"
+ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+ " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n"
+ " </method>\n"
+ " <signal name=\"PropertiesChanged\">\n"
+ " <arg type=\"s\" name=\"interface_name\"/>\n"
+ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"
+ " </signal>\n"
+ " </interface>\n"
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+ " <method name=\"Introspect\">\n"
+ " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name=\"org.freedesktop.DBus.Peer\">\n"
+ " <method name=\"Ping\"/>\n"
+ " <method name=\"GetMachineId\">\n"
+ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n"
+ " </method>\n"
+ " </interface>\n");
+}
+
+static void
+maybe_add_path (const gchar *path, gsize path_len, const gchar *object_path, GHashTable *set)
+{
+ if (g_str_has_prefix (object_path, path) && strlen (object_path) >= path_len)
+ {
+ const gchar *begin;
+ const gchar *end;
+ gchar *s;
+
+ begin = object_path + path_len;
+ end = strchr (begin, '/');
+
+ if (end != NULL)
+ {
+ s = g_strndup (begin, end - begin);
+ }
+ else
+ {
+ s = g_strdup (begin);
+ }
+
+ if (g_hash_table_lookup (set, s) == NULL)
+ {
+ g_hash_table_insert (set, s, GUINT_TO_POINTER (1));
+ }
+ else
+ {
+ g_free (s);
+ }
+ }
+}
+
+/* TODO: we want a nicer public interface for this */
+static gchar **
+g_dbus_connection_list_registered_unlocked (GDBusConnection *connection,
+ const gchar *path)
+{
+ GPtrArray *p;
+ gchar **ret;
+ GHashTableIter hash_iter;
+ const gchar *object_path;
+ gsize path_len;
+ GHashTable *set;
+ GList *keys;
+ GList *l;
+
+ CONNECTION_ENSURE_LOCK (connection);
+
+ path_len = strlen (path);
+ if (path_len > 1)
+ path_len++;
+
+ set = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_eo);
+ while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL))
+ maybe_add_path (path, path_len, object_path, set);
+
+ g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_es);
+ while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL))
+ maybe_add_path (path, path_len, object_path, set);
+
+ p = g_ptr_array_new ();
+ keys = g_hash_table_get_keys (set);
+ for (l = keys; l != NULL; l = l->next)
+ {
+ g_ptr_array_add (p, l->data);
+ }
+ g_hash_table_unref (set);
+ g_list_free (keys);
+
+ g_ptr_array_add (p, NULL);
+ ret = (gchar **) g_ptr_array_free (p, FALSE);
+ return ret;
+}
+
+static gchar **
+g_dbus_connection_list_registered (GDBusConnection *connection,
+ const gchar *path)
+{
+ gchar **ret;
+ CONNECTION_LOCK (connection);
+ ret = g_dbus_connection_list_registered_unlocked (connection, path);
+ CONNECTION_UNLOCK (connection);
+ return ret;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+handle_introspect (GDBusConnection *connection,
+ ExportedObject *eo,
+ GDBusMessage *message)
+{
+ guint n;
+ GString *s;
+ GDBusMessage *reply;
+ GHashTableIter hash_iter;
+ ExportedInterface *ei;
+ gchar **registered;
+
+ /* first the header with the standard interfaces */
+ s = g_string_new (NULL);
+ introspect_append_header (s);
+ introspect_append_standard_interfaces (s);
+
+ /* then include the registered interfaces */
+ g_hash_table_iter_init (&hash_iter, eo->map_if_name_to_ei);
+ while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &ei))
+ {
+ g_dbus_interface_info_generate_xml (ei->introspection_data, 2, s);
+ }
+
+ /* finally include nodes registered below us */
+ registered = g_dbus_connection_list_registered_unlocked (connection, eo->object_path);
+ for (n = 0; registered != NULL && registered[n] != NULL; n++)
+ {
+ g_string_append_printf (s, " <node name=\"%s\"/>\n", registered[n]);
+ }
+ g_strfreev (registered);
+ g_string_append (s, "</node>\n");
+
+ reply = g_dbus_message_new_method_reply (message);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ g_string_free (s, TRUE);
+
+ return TRUE;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_method_in_idle_cb (gpointer user_data)
+{
+ GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
+ GDBusInterfaceVTable *vtable;
+
+ vtable = g_object_get_data (G_OBJECT (invocation), "g-dbus-interface-vtable");
+ g_assert (vtable != NULL && vtable->method_call != NULL);
+
+ vtable->method_call (g_dbus_method_invocation_get_connection (invocation),
+ g_dbus_method_invocation_get_sender (invocation),
+ g_dbus_method_invocation_get_object_path (invocation),
+ g_dbus_method_invocation_get_interface_name (invocation),
+ g_dbus_method_invocation_get_method_name (invocation),
+ g_dbus_method_invocation_get_parameters (invocation),
+ g_object_ref (invocation),
+ g_dbus_method_invocation_get_user_data (invocation));
+
+ return FALSE;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+validate_and_maybe_schedule_method_call (GDBusConnection *connection,
+ GDBusMessage *message,
+ const GDBusInterfaceInfo *introspection_data,
+ const GDBusInterfaceVTable *vtable,
+ GMainContext *main_context,
+ gpointer user_data)
+{
+ GDBusMethodInvocation *invocation;
+ const GDBusMethodInfo *method_info;
+ GDBusMessage *reply;
+ GVariant *parameters;
+ GSource *idle_source;
+ gboolean handled;
+ gchar *in_signature;
+
+ handled = FALSE;
+
+ /* TODO: the cost of this is O(n) - it might be worth caching the result */
+ method_info = g_dbus_interface_info_lookup_method (introspection_data, g_dbus_message_get_member (message));
+
+ /* if the method doesn't exist, return the org.freedesktop.DBus.Error.UnknownMethod
+ * error to the caller
+ */
+ if (method_info == NULL)
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ _("No such method `%s'"),
+ g_dbus_message_get_member (message));
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ /* Check that the incoming args are of the right type - if they are not, return
+ * the org.freedesktop.DBus.Error.InvalidArgs error to the caller
+ *
+ * TODO: might also be worth caching the combined signature.
+ */
+ in_signature = _g_dbus_compute_complete_signature (method_info->in_args, FALSE);
+ if (g_strcmp0 (g_dbus_message_get_signature (message), in_signature) != 0)
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("Signature of message, `%s', does not match expected signature `%s'"),
+ g_dbus_message_get_signature (message),
+ in_signature);
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ g_free (in_signature);
+ handled = TRUE;
+ goto out;
+ }
+ g_free (in_signature);
+
+ parameters = g_dbus_message_get_body (message);
+ if (parameters == NULL)
+ {
+ parameters = g_variant_new ("()");
+ g_variant_ref_sink (parameters);
+ }
+ else
+ {
+ g_variant_ref (parameters);
+ }
+
+ /* schedule the call in idle */
+ invocation = g_dbus_method_invocation_new (g_dbus_message_get_sender (message),
+ g_dbus_message_get_path (message),
+ g_dbus_message_get_interface (message),
+ g_dbus_message_get_member (message),
+ method_info,
+ connection,
+ message,
+ parameters,
+ user_data);
+ g_variant_unref (parameters);
+ g_object_set_data (G_OBJECT (invocation),
+ "g-dbus-interface-vtable",
+ (gpointer) vtable);
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ invoke_method_in_idle_cb,
+ invocation,
+ g_object_unref);
+ g_source_attach (idle_source, main_context);
+ g_source_unref (idle_source);
+
+ handled = TRUE;
+
+ out:
+
+ return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in message handler thread with lock held */
+static gboolean
+obj_message_func (GDBusConnection *connection,
+ ExportedObject *eo,
+ GDBusMessage *message)
+{
+ const gchar *interface_name;
+ const gchar *member;
+ const gchar *signature;
+ gboolean handled;
+
+ handled = FALSE;
+
+ interface_name = g_dbus_message_get_interface (message);
+ member = g_dbus_message_get_member (message);
+ signature = g_dbus_message_get_signature (message);
+
+ /* see if we have an interface for handling this call */
+ if (interface_name != NULL)
+ {
+ ExportedInterface *ei;
+ ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+ if (ei != NULL)
+ {
+ /* we do - invoke the handler in idle in the right thread */
+
+ /* handle no vtable or handler being present */
+ if (ei->vtable == NULL || ei->vtable->method_call == NULL)
+ goto out;
+
+ handled = validate_and_maybe_schedule_method_call (connection,
+ message,
+ ei->introspection_data,
+ ei->vtable,
+ ei->context,
+ ei->user_data);
+ goto out;
+ }
+ }
+
+ if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
+ g_strcmp0 (member, "Introspect") == 0 &&
+ g_strcmp0 (signature, "") == 0)
+ {
+ handled = handle_introspect (connection, eo, message);
+ goto out;
+ }
+ else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+ g_strcmp0 (member, "Get") == 0 &&
+ g_strcmp0 (signature, "ss") == 0)
+ {
+ handled = handle_getset_property (connection, eo, message, TRUE);
+ goto out;
+ }
+ else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+ g_strcmp0 (member, "Set") == 0 &&
+ g_strcmp0 (signature, "ssv") == 0)
+ {
+ handled = handle_getset_property (connection, eo, message, FALSE);
+ goto out;
+ }
+ else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+ g_strcmp0 (member, "GetAll") == 0 &&
+ g_strcmp0 (signature, "s") == 0)
+ {
+ handled = handle_get_all_properties (connection, eo, message);
+ goto out;
+ }
+
+ out:
+ return handled;
+}
+
+/**
+ * g_dbus_connection_register_object:
+ * @connection: A #GDBusConnection.
+ * @object_path: The object path to register at.
+ * @interface_name: The D-Bus interface to register.
+ * @introspection_data: Introspection data for the interface.
+ * @vtable: A #GDBusInterfaceVTable to call into or %NULL.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the object path is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers callbacks for exported objects at @object_path with the
+ * D-Bus interface @interface_name.
+ *
+ * Calls to functions in @vtable (and @user_data_free_func) will
+ * happen in the <link linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * Note that all #GVariant values passed to functions in @vtable will match
+ * the signature given in @introspection_data - if a remote caller passes
+ * incorrect values, the <literal>org.freedesktop.DBus.Error.InvalidArgs</literal>
+ * is returned to the remote caller.
+ *
+ * Additionally, if the remote caller attempts to invoke methods or
+ * access properties not mentioned in @introspection_data the
+ * <literal>org.freedesktop.DBus.Error.UnknownMethod</literal> resp.
+ * <literal>org.freedesktop.DBus.Error.InvalidArgs</literal> errors
+ * are returned to the caller.
+ *
+ * It is considered a programming error if the
+ * #GDBusInterfaceGetPropertyFunc function in @vtable returns a
+ * #GVariant of incorrect type.
+ *
+ * If an existing callback is already registered at @object_path and
+ * @interface_name, then @error is set to #G_IO_ERROR_EXISTS.
+ *
+ * See <xref linkend="gdbus-server"/> for an example of how to use this method.
+ *
+ * Returns: 0 if @error is set, otherwise a registration id (never 0)
+ * that can be used with g_dbus_connection_unregister_object() .
+ */
+guint
+g_dbus_connection_register_object (GDBusConnection *connection,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const GDBusInterfaceInfo *introspection_data,
+ const GDBusInterfaceVTable *vtable,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func,
+ GError **error)
+{
+ ExportedObject *eo;
+ ExportedInterface *ei;
+ guint ret;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+ g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+ g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0);
+ g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0);
+ g_return_val_if_fail (introspection_data != NULL, 0);
+ g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+ ret = 0;
+
+ CONNECTION_LOCK (connection);
+
+ eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path);
+ if (eo == NULL)
+ {
+ eo = g_new0 (ExportedObject, 1);
+ eo->object_path = g_strdup (object_path);
+ eo->connection = connection;
+ eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) exported_interface_free);
+ g_hash_table_insert (connection->priv->map_object_path_to_eo, eo->object_path, eo);
+ }
+
+ ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+ if (ei != NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_EXISTS,
+ _("An object is already exported for the interface %s at %s"),
+ interface_name,
+ object_path);
+ goto out;
+ }
+
+ ei = g_new0 (ExportedInterface, 1);
+ ei->id = _global_registration_id++; /* TODO: overflow etc. */
+ ei->eo = eo;
+ ei->user_data = user_data;
+ ei->user_data_free_func = user_data_free_func;
+ ei->vtable = vtable;
+ ei->introspection_data = introspection_data;
+ ei->interface_name = g_strdup (interface_name);
+ ei->context = g_main_context_get_thread_default ();
+ if (ei->context != NULL)
+ g_main_context_ref (ei->context);
+
+ g_hash_table_insert (eo->map_if_name_to_ei,
+ (gpointer) ei->interface_name,
+ ei);
+ g_hash_table_insert (connection->priv->map_id_to_ei,
+ GUINT_TO_POINTER (ei->id),
+ ei);
+
+ ret = ei->id;
+
+ out:
+ CONNECTION_UNLOCK (connection);
+
+ return ret;
+}
+
+/**
+ * g_dbus_connection_unregister_object:
+ * @connection: A #GDBusConnection.
+ * @registration_id: A registration id obtained from g_dbus_connection_register_object().
+ *
+ * Unregisters an object.
+ *
+ * Returns: %TRUE if the object was unregistered, %FALSE otherwise.
+ */
+gboolean
+g_dbus_connection_unregister_object (GDBusConnection *connection,
+ guint registration_id)
+{
+ ExportedInterface *ei;
+ ExportedObject *eo;
+ gboolean ret;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+ ret = FALSE;
+
+ CONNECTION_LOCK (connection);
+
+ ei = g_hash_table_lookup (connection->priv->map_id_to_ei,
+ GUINT_TO_POINTER (registration_id));
+ if (ei == NULL)
+ {
+ goto out;
+ }
+
+ eo = ei->eo;
+
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_ei, GUINT_TO_POINTER (ei->id)));
+ g_warn_if_fail (g_hash_table_remove (eo->map_if_name_to_ei, ei->interface_name));
+ /* unregister object path if we have no more exported interfaces */
+ if (g_hash_table_size (eo->map_if_name_to_ei) == 0)
+ {
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_eo,
+ eo->object_path));
+ }
+
+ ret = TRUE;
+
+ out:
+ CONNECTION_UNLOCK (connection);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_emit_signal:
+ * @connection: A #GDBusConnection.
+ * @destination_bus_name: The unique bus name for the destination for the signal or %NULL to emit to all listeners.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to emit a signal on.
+ * @signal_name: The name of the signal to emit.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @error: Return location for error or %NULL.
+ *
+ * Emits a signal.
+ *
+ * This can only fail if @parameters is not compatible with the D-Bus protocol.
+ *
+ * Returns: %TRUE unless @error is set.
+ */
+gboolean
+g_dbus_connection_emit_signal (GDBusConnection *connection,
+ const gchar *destination_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ GError **error)
+{
+ GDBusMessage *message;
+ gboolean ret;
+
+ message = NULL;
+ ret = FALSE;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+ g_return_val_if_fail (destination_bus_name == NULL || g_dbus_is_name (destination_bus_name), FALSE);
+ g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), FALSE);
+ g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), FALSE);
+ g_return_val_if_fail (signal_name != NULL && g_dbus_is_member_name (signal_name), FALSE);
+ g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), FALSE);
+
+ message = g_dbus_message_new_signal (object_path,
+ interface_name,
+ signal_name);
+
+ if (destination_bus_name != NULL)
+ {
+ g_dbus_message_set_header (message,
+ G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION,
+ g_variant_new_string (destination_bus_name));
+ }
+
+ if (parameters != NULL)
+ g_dbus_message_set_body (message, parameters);
+
+ ret = g_dbus_connection_send_message (connection, message, NULL, error);
+ g_object_unref (message);
+
+ return ret;
+}
+
+static void
+add_invoke_method_flags (GDBusMessage *message,
+ GDBusInvokeMethodFlags flags)
+{
+ if (flags & G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START)
+ g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_NO_AUTO_START);
+}
+
+/**
+ * g_dbus_connection_invoke_method:
+ * @connection: A #GDBusConnection.
+ * @bus_name: A unique or well-known bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @parameters contains a value
+ * not compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_connection_invoke_method_finish() to get the result of the operation.
+ * See g_dbus_connection_invoke_method_sync() for the synchronous version of this
+ * function.
+ */
+void
+g_dbus_connection_invoke_method (GDBusConnection *connection,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDBusMessage *message;
+
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+ g_return_if_fail (bus_name == NULL || g_dbus_is_name (bus_name));
+ g_return_if_fail (object_path != NULL && g_variant_is_object_path (object_path));
+ g_return_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name));
+ g_return_if_fail (method_name != NULL && g_dbus_is_member_name (method_name));
+ g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1);
+ g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+
+ message = g_dbus_message_new_method_call (bus_name,
+ object_path,
+ interface_name,
+ method_name);
+ add_invoke_method_flags (message, flags);
+ if (parameters != NULL)
+ g_dbus_message_set_body (message, parameters);
+
+ g_dbus_connection_send_message_with_reply (connection,
+ message,
+ timeout_msec,
+ NULL, /* volatile guint32 *out_serial */
+ cancellable,
+ callback,
+ user_data);
+
+ if (message != NULL)
+ g_object_unref (message);
+}
+
+static GVariant *
+decode_method_reply (GDBusMessage *reply, GError **error)
+{
+ GVariant *result;
+
+ result = NULL;
+ switch (g_dbus_message_get_type (reply))
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ result = g_dbus_message_get_body (reply);
+ if (result == NULL)
+ {
+ result = g_variant_new ("()");
+ g_variant_ref_sink (result);
+ }
+ else
+ {
+ g_variant_ref (result);
+ }
+ break;
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ g_dbus_message_to_gerror (reply, error);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return result;
+}
+
+/**
+ * g_dbus_connection_invoke_method_finish:
+ * @connection: A #GDBusConnection.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_connection_invoke_method_finish (GDBusConnection *connection,
+ GAsyncResult *res,
+ GError **error)
+{
+ GDBusMessage *reply;
+ GVariant *result;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ result = NULL;
+
+ reply = g_dbus_connection_send_message_with_reply_finish (connection, res, error);
+ if (reply == NULL)
+ goto out;
+
+ result = decode_method_reply (reply, error);
+
+ g_object_unref (reply);
+
+ out:
+ return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_invoke_method_sync:
+ * @connection: A #GDBusConnection.
+ * @bus_name: A unique or well-known bus name.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the
+ * operation will fail with %G_IO_ERROR_CANCELLED. If @parameters
+ * contains a value not compatible with the D-Bus protocol, the operation
+ * fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * g_dbus_connection_invoke_method() for the asynchronous version of
+ * this method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_connection_invoke_method_sync (GDBusConnection *connection,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusMessage *message;
+ GDBusMessage *reply;
+ GVariant *result;
+
+ message = NULL;
+ reply = NULL;
+ result = NULL;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (bus_name == NULL || g_dbus_is_name (bus_name), NULL);
+ g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), NULL);
+ g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), NULL);
+ g_return_val_if_fail (method_name != NULL && g_dbus_is_member_name (method_name), NULL);
+ g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL);
+ g_return_val_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+
+ message = g_dbus_message_new_method_call (bus_name,
+ object_path,
+ interface_name,
+ method_name);
+ add_invoke_method_flags (message, flags);
+ if (parameters != NULL)
+ g_dbus_message_set_body (message, parameters);
+
+ reply = g_dbus_connection_send_message_with_reply_sync (connection,
+ message,
+ timeout_msec,
+ NULL, /* volatile guint32 *out_serial */
+ cancellable,
+ error);
+
+ if (reply == NULL)
+ goto out;
+
+ result = decode_method_reply (reply, error);
+
+ out:
+ if (message != NULL)
+ g_object_unref (message);
+ if (reply != NULL)
+ g_object_unref (reply);
+
+ return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedSubtree
+{
+ guint id;
+ gchar *object_path;
+ GDBusConnection *connection;
+ const GDBusSubtreeVTable *vtable;
+ GDBusSubtreeFlags flags;
+
+ GMainContext *context;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+};
+
+static void
+exported_subtree_free (ExportedSubtree *es)
+{
+ if (es->user_data_free_func != NULL)
+ {
+ /* TODO: push to thread-default mainloop */
+ es->user_data_free_func (es->user_data);
+ }
+ if (es->context != NULL)
+ {
+ g_main_context_unref (es->context);
+ }
+ g_free (es->object_path);
+ g_free (es);
+}
+
+/* called without lock held */
+static gboolean
+handle_subtree_introspect (GDBusConnection *connection,
+ ExportedSubtree *es,
+ GDBusMessage *message)
+{
+ GString *s;
+ gboolean handled;
+ GDBusMessage *reply;
+ gchar **children;
+ gboolean is_root;
+ const gchar *sender;
+ const gchar *requested_object_path;
+ const gchar *requested_node;
+ GPtrArray *interfaces;
+ guint n;
+ gchar **subnode_paths;
+
+ handled = FALSE;
+
+ requested_object_path = g_dbus_message_get_path (message);
+ sender = g_dbus_message_get_sender (message);
+ is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+ s = g_string_new (NULL);
+ introspect_append_header (s);
+
+ /* Strictly we don't need the children in dynamic mode, but we avoid the
+ * conditionals to preserve code clarity
+ */
+ children = es->vtable->enumerate (es->connection,
+ sender,
+ es->object_path,
+ es->user_data);
+
+ if (!is_root)
+ {
+ requested_node = strrchr (requested_object_path, '/') + 1;
+
+ /* Assert existence of object if we are not dynamic */
+ if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+ !_g_strv_has_string ((const gchar * const *) children, requested_node))
+ goto out;
+ }
+ else
+ {
+ requested_node = "/";
+ }
+
+ interfaces = es->vtable->introspect (es->connection,
+ sender,
+ es->object_path,
+ requested_node,
+ es->user_data);
+ if (interfaces != NULL)
+ {
+ if (interfaces->len > 0)
+ {
+ /* we're in business */
+ introspect_append_standard_interfaces (s);
+
+ for (n = 0; n < interfaces->len; n++)
+ {
+ const GDBusInterfaceInfo *interface_info = interfaces->pdata[n];
+ g_dbus_interface_info_generate_xml (interface_info, 2, s);
+ }
+ }
+ g_ptr_array_unref (interfaces);
+ }
+
+ /* then include <node> entries from the Subtree for the root */
+ if (is_root)
+ {
+ for (n = 0; children != NULL && children[n] != NULL; n++)
+ {
+ g_string_append_printf (s, " <node name=\"%s\"/>\n", children[n]);
+ }
+ }
+
+ /* finally include nodes registered below us */
+ subnode_paths = g_dbus_connection_list_registered (es->connection, requested_object_path);
+ for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++)
+ {
+ g_string_append_printf (s, " <node name=\"%s\"/>\n", subnode_paths[n]);
+ }
+ g_strfreev (subnode_paths);
+
+ g_string_append (s, "</node>\n");
+
+ reply = g_dbus_message_new_method_reply (message);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+ g_dbus_connection_send_message (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+
+ handled = TRUE;
+
+ out:
+ g_string_free (s, TRUE);
+ g_strfreev (children);
+ return handled;
+}
+
+/* called without lock held */
+static gboolean
+handle_subtree_method_invocation (GDBusConnection *connection,
+ ExportedSubtree *es,
+ GDBusMessage *message)
+{
+ gboolean handled;;
+ const gchar *sender;
+ const gchar *interface_name;
+ const gchar *member;
+ const gchar *signature;
+ const gchar *requested_object_path;
+ const gchar *requested_node;
+ gboolean is_root;
+ gchar **children;
+ const GDBusInterfaceInfo *introspection_data;
+ const GDBusInterfaceVTable *interface_vtable;
+ gpointer interface_user_data;
+ guint n;
+ GPtrArray *interfaces;
+ gboolean is_property_get;
+ gboolean is_property_set;
+ gboolean is_property_get_all;
+
+ handled = FALSE;
+ interfaces = NULL;
+
+ requested_object_path = g_dbus_message_get_path (message);
+ sender = g_dbus_message_get_sender (message);
+ interface_name = g_dbus_message_get_interface (message);
+ member = g_dbus_message_get_member (message);
+ signature = g_dbus_message_get_signature (message);
+ is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+ is_property_get = FALSE;
+ is_property_set = FALSE;
+ is_property_get_all = FALSE;
+ if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0)
+ {
+ if (g_strcmp0 (member, "Get") == 0 && g_strcmp0 (signature, "ss") == 0)
+ {
+ is_property_get = TRUE;
+ }
+ else if (g_strcmp0 (member, "Set") == 0 && g_strcmp0 (signature, "ssv") == 0)
+ {
+ is_property_set = TRUE;
+ }
+ else if (g_strcmp0 (member, "GetAll") == 0 && g_strcmp0 (signature, "s") == 0)
+ {
+ is_property_get_all = TRUE;
+ }
+ }
+
+ children = es->vtable->enumerate (es->connection,
+ sender,
+ es->object_path,
+ es->user_data);
+
+ if (!is_root)
+ {
+ requested_node = strrchr (requested_object_path, '/') + 1;
+
+ /* If not dynamic, skip if requested node is not part of children */
+ if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+ !_g_strv_has_string ((const gchar * const *) children, requested_node))
+ goto out;
+ }
+ else
+ {
+ requested_node = "/";
+ }
+
+ /* get introspection data for the node */
+ interfaces = es->vtable->introspect (es->connection,
+ sender,
+ requested_object_path,
+ requested_node,
+ es->user_data);
+ g_assert (interfaces != NULL);
+ introspection_data = NULL;
+ for (n = 0; n < interfaces->len; n++)
+ {
+ const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n];
+ if (g_strcmp0 (id_n->name, interface_name) == 0)
+ {
+ introspection_data = id_n;
+ }
+ }
+
+ /* dispatch the call if the user wants to handle it */
+ if (introspection_data != NULL)
+ {
+ /* figure out where to dispatch the method call */
+ interface_user_data = NULL;
+ interface_vtable = es->vtable->dispatch (es->connection,
+ sender,
+ es->object_path,
+ interface_name,
+ requested_node,
+ &interface_user_data,
+ es->user_data);
+ if (interface_vtable == NULL)
+ goto out;
+
+ CONNECTION_LOCK (connection);
+ handled = validate_and_maybe_schedule_method_call (es->connection,
+ message,
+ introspection_data,
+ interface_vtable,
+ es->context,
+ interface_user_data);
+ CONNECTION_UNLOCK (connection);
+ }
+ /* handle org.freedesktop.DBus.Properties interface if not explicitly handled */
+ else if (is_property_get || is_property_set || is_property_get_all)
+ {
+ if (is_property_get)
+ g_variant_get (g_dbus_message_get_body (message), "(ss)", &interface_name, NULL);
+ else if (is_property_set)
+ g_variant_get (g_dbus_message_get_body (message), "(ssv)", &interface_name, NULL, NULL);
+ else if (is_property_get_all)
+ g_variant_get (g_dbus_message_get_body (message), "(s)", &interface_name, NULL, NULL);
+ else
+ g_assert_not_reached ();
+
+ /* see if the object supports this interface at all */
+ for (n = 0; n < interfaces->len; n++)
+ {
+ const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n];
+ if (g_strcmp0 (id_n->name, interface_name) == 0)
+ {
+ introspection_data = id_n;
+ }
+ }
+
+ /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the user-code
+ * claims it won't support the interface
+ */
+ if (introspection_data == NULL)
+ {
+ GDBusMessage *reply;
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.InvalidArgs",
+ _("No such interface `%s'"),
+ interface_name);
+ g_dbus_connection_send_message (es->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ handled = TRUE;
+ goto out;
+ }
+
+ /* figure out where to dispatch the property get/set/getall calls */
+ interface_user_data = NULL;
+ interface_vtable = es->vtable->dispatch (es->connection,
+ sender,
+ es->object_path,
+ interface_name,
+ requested_node,
+ &interface_user_data,
+ es->user_data);
+ if (interface_vtable == NULL)
+ goto out;
+
+ if (is_property_get || is_property_set)
+ {
+ CONNECTION_LOCK (connection);
+ handled = validate_and_maybe_schedule_property_getset (es->connection,
+ message,
+ is_property_get,
+ introspection_data,
+ interface_vtable,
+ es->context,
+ interface_user_data);
+ CONNECTION_UNLOCK (connection);
+ }
+ else if (is_property_get_all)
+ {
+ CONNECTION_LOCK (connection);
+ handled = validate_and_maybe_schedule_property_get_all (es->connection,
+ message,
+ introspection_data,
+ interface_vtable,
+ es->context,
+ interface_user_data);
+ CONNECTION_UNLOCK (connection);
+ }
+ }
+
+ out:
+ if (interfaces != NULL)
+ g_ptr_array_unref (interfaces);
+ g_strfreev (children);
+ return handled;
+}
+
+typedef struct
+{
+ GDBusMessage *message;
+ ExportedSubtree *es;
+} SubtreeDeferredData;
+
+static void
+subtree_deferred_data_free (SubtreeDeferredData *data)
+{
+ g_object_unref (data->message);
+ g_free (data);
+}
+
+/* called without lock held in the thread where the caller registered the subtree */
+static gboolean
+process_subtree_vtable_message_in_idle_cb (gpointer _data)
+{
+ SubtreeDeferredData *data = _data;
+ gboolean handled;
+
+ handled = FALSE;
+
+ if (g_strcmp0 (g_dbus_message_get_interface (data->message), "org.freedesktop.DBus.Introspectable") == 0 &&
+ g_strcmp0 (g_dbus_message_get_member (data->message), "Introspect") == 0 &&
+ g_strcmp0 (g_dbus_message_get_signature (data->message), "") == 0)
+ {
+ handled = handle_subtree_introspect (data->es->connection,
+ data->es,
+ data->message);
+ }
+ else
+ {
+ handled = handle_subtree_method_invocation (data->es->connection,
+ data->es,
+ data->message);
+ }
+
+
+ if (!handled)
+ {
+ CONNECTION_LOCK (data->es->connection);
+ handled = handle_generic_unlocked (data->es->connection, data->message);
+ CONNECTION_UNLOCK (data->es->connection);
+ }
+
+ /* if we couldn't handle the request, just bail with the UnknownMethod error */
+ if (!handled)
+ {
+ GDBusMessage *reply;
+ reply = g_dbus_message_new_method_error (data->message,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ _("Method `%s' on interface `%s' with signature `%s' does not exist"),
+ g_dbus_message_get_member (data->message),
+ g_dbus_message_get_interface (data->message),
+ g_dbus_message_get_signature (data->message));
+ g_dbus_connection_send_message (data->es->connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ }
+
+ return FALSE;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+subtree_message_func (GDBusConnection *connection,
+ ExportedSubtree *es,
+ GDBusMessage *message)
+{
+ GSource *idle_source;
+ SubtreeDeferredData *data;
+
+ data = g_new0 (SubtreeDeferredData, 1);
+ data->message = g_object_ref (message);
+ data->es = es;
+
+ /* defer this call to an idle handler in the right thread */
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+ g_source_set_callback (idle_source,
+ process_subtree_vtable_message_in_idle_cb,
+ data,
+ (GDestroyNotify) subtree_deferred_data_free);
+ g_source_attach (idle_source, es->context);
+ g_source_unref (idle_source);
+
+ /* since we own the entire subtree, handlers for objects not in the subtree have been
+ * tried already by libdbus-1 - so we just need to ensure that we're always going
+ * to reply to the message
+ */
+ return TRUE;
+}
+
+/**
+ * g_dbus_connection_register_subtree:
+ * @connection: A #GDBusConnection.
+ * @object_path: The object path to register the subtree at.
+ * @vtable: A #GDBusSubtreeVTable to enumerate, introspect and dispatch nodes in the subtree.
+ * @flags: Flags used to fine tune the behavior of the subtree.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the subtree is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers a whole subtree of <quote>dynamic</quote> objects.
+ *
+ * The @enumerate and @introspection functions in @vtable are used to
+ * convey, to remote callers, what nodes exist in the subtree rooted
+ * by @object_path.
+ *
+ * When handling remote calls into any node in the subtree, first the
+ * @enumerate function is used to check if the node exists. If the node exists
+ * or the #G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set
+ * the @introspection function is used to check if the node supports the
+ * requested method. If so, the @dispatch function is used to determine
+ * where to dispatch the call. The collected #GDBusInterfaceVTable and
+ * #gpointer will be used to call into the interface vtable for processing
+ * the request.
+ *
+ * All calls into user-provided code will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * If an existing subtree is already registered at @object_path or
+ * then @error is set to #G_IO_ERROR_EXISTS.
+ *
+ * Note that it is valid to register regular objects (using
+ * g_dbus_connection_register_object()) in a subtree registered with
+ * g_dbus_connection_register_subtree() - if so, the subtree handler
+ * is tried as the last resort. One way to think about a subtree
+ * handler is to consider it a <quote>fallback handler</quote>
+ * for object paths not registered via g_dbus_connection_register_object()
+ * or other bindings.
+ *
+ * See <xref linkend="gdbus-subtree-server"/> for an example of how to use this method.
+ *
+ * Returns: 0 if @error is set, otherwise a subtree registration id (never 0)
+ * that can be used with g_dbus_connection_unregister_subtree() .
+ */
+guint
+g_dbus_connection_register_subtree (GDBusConnection *connection,
+ const gchar *object_path,
+ const GDBusSubtreeVTable *vtable,
+ GDBusSubtreeFlags flags,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func,
+ GError **error)
+{
+ guint ret;
+ ExportedSubtree *es;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+ g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0);
+ g_return_val_if_fail (vtable != NULL, 0);
+ g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+ ret = 0;
+
+ CONNECTION_LOCK (connection);
+
+ es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path);
+ if (es != NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_EXISTS,
+ _("A subtree is already exported for %s"),
+ object_path);
+ goto out;
+ }
+
+ es = g_new0 (ExportedSubtree, 1);
+ es->object_path = g_strdup (object_path);
+ es->connection = connection;
+
+ es->vtable = vtable;
+ es->flags = flags;
+ es->id = _global_subtree_registration_id++; /* TODO: overflow etc. */
+ es->user_data = user_data;
+ es->user_data_free_func = user_data_free_func;
+ es->context = g_main_context_get_thread_default ();
+ if (es->context != NULL)
+ g_main_context_ref (es->context);
+
+ g_hash_table_insert (connection->priv->map_object_path_to_es, es->object_path, es);
+ g_hash_table_insert (connection->priv->map_id_to_es,
+ GUINT_TO_POINTER (es->id),
+ es);
+
+ ret = es->id;
+
+ out:
+ CONNECTION_UNLOCK (connection);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_unregister_subtree:
+ * @connection: A #GDBusConnection.
+ * @registration_id: A subtree registration id obtained from g_dbus_connection_register_subtree().
+ *
+ * Unregisters a subtree.
+ *
+ * Returns: %TRUE if the subtree was unregistered, %FALSE otherwise.
+ */
+gboolean
+g_dbus_connection_unregister_subtree (GDBusConnection *connection,
+ guint registration_id)
+{
+ ExportedSubtree *es;
+ gboolean ret;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+ ret = FALSE;
+
+ CONNECTION_LOCK (connection);
+
+ es = g_hash_table_lookup (connection->priv->map_id_to_es,
+ GUINT_TO_POINTER (registration_id));
+ if (es == NULL)
+ {
+ goto out;
+ }
+
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_es, GUINT_TO_POINTER (es->id)));
+ g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_es, es->object_path));
+
+ ret = TRUE;
+
+ out:
+ CONNECTION_UNLOCK (connection);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must be called with lock held */
+static void
+handle_generic_ping_unlocked (GDBusConnection *connection,
+ const gchar *object_path,
+ GDBusMessage *message)
+{
+ GDBusMessage *reply;
+ reply = g_dbus_message_new_method_reply (message);
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+}
+
+/* must be called with lock held */
+static void
+handle_generic_get_machine_id_unlocked (GDBusConnection *connection,
+ const gchar *object_path,
+ GDBusMessage *message)
+{
+ GDBusMessage *reply;
+
+ reply = NULL;
+ if (connection->priv->machine_id == NULL)
+ {
+ GError *error;
+ error = NULL;
+ /* TODO: use PACKAGE_LOCALSTATEDIR ? */
+ if (!g_file_get_contents ("/var/lib/dbus/machine-id",
+ &connection->priv->machine_id,
+ NULL,
+ &error))
+ {
+ reply = g_dbus_message_new_method_error (message,
+ "org.freedesktop.DBus.Error.Failed",
+ _("Unable to load /var/lib/dbus/machine-id: %s"),
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_strstrip (connection->priv->machine_id);
+ /* TODO: validate value */
+ }
+ }
+
+ if (reply == NULL)
+ {
+ reply = g_dbus_message_new_method_reply (message);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)", connection->priv->machine_id));
+ }
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+}
+
+/* must be called with lock held */
+static void
+handle_generic_introspect_unlocked (GDBusConnection *connection,
+ const gchar *object_path,
+ GDBusMessage *message)
+{
+ guint n;
+ GString *s;
+ gchar **registered;
+ GDBusMessage *reply;
+
+ /* first the header */
+ s = g_string_new (NULL);
+ introspect_append_header (s);
+
+ registered = g_dbus_connection_list_registered_unlocked (connection, object_path);
+ for (n = 0; registered != NULL && registered[n] != NULL; n++)
+ {
+ g_string_append_printf (s, " <node name=\"%s\"/>\n", registered[n]);
+ }
+ g_strfreev (registered);
+ g_string_append (s, "</node>\n");
+
+ reply = g_dbus_message_new_method_reply (message);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+ g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+ g_object_unref (reply);
+ g_string_free (s, TRUE);
+}
+
+/* must be called with lock held */
+static gboolean
+handle_generic_unlocked (GDBusConnection *connection,
+ GDBusMessage *message)
+{
+ gboolean handled;
+ const gchar *interface_name;
+ const gchar *member;
+ const gchar *signature;
+ const gchar *path;
+
+ CONNECTION_ENSURE_LOCK (connection);
+
+ handled = FALSE;
+
+ interface_name = g_dbus_message_get_interface (message);
+ member = g_dbus_message_get_member (message);
+ signature = g_dbus_message_get_signature (message);
+ path = g_dbus_message_get_path (message);
+
+ if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
+ g_strcmp0 (member, "Introspect") == 0 &&
+ g_strcmp0 (signature, "") == 0)
+ {
+ handle_generic_introspect_unlocked (connection, path, message);
+ handled = TRUE;
+ }
+ else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 &&
+ g_strcmp0 (member, "Ping") == 0 &&
+ g_strcmp0 (signature, "") == 0)
+ {
+ handle_generic_ping_unlocked (connection, path, message);
+ handled = TRUE;
+ }
+ else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 &&
+ g_strcmp0 (member, "GetMachineId") == 0 &&
+ g_strcmp0 (signature, "") == 0)
+ {
+ handle_generic_get_machine_id_unlocked (connection, path, message);
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in message handler thread with lock held */
+static void
+distribute_method_call (GDBusConnection *connection,
+ GDBusMessage *message)
+{
+ ExportedObject *eo;
+ ExportedSubtree *es;
+ const gchar *object_path;
+ const gchar *interface_name;
+ const gchar *member;
+ const gchar *signature;
+ const gchar *path;
+ gchar *subtree_path;
+ gchar *needle;
+
+ g_assert (g_dbus_message_get_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+ interface_name = g_dbus_message_get_interface (message);
+ member = g_dbus_message_get_member (message);
+ signature = g_dbus_message_get_signature (message);
+ path = g_dbus_message_get_path (message);
+ subtree_path = g_strdup (path);
+ needle = strrchr (subtree_path, '/');
+ if (needle != NULL && needle != subtree_path)
+ {
+ *needle = '\0';
+ }
+ else
+ {
+ g_free (subtree_path);
+ subtree_path = NULL;
+ }
+
+#if 0
+ g_debug ("interface = `%s'", interface_name);
+ g_debug ("member = `%s'", member);
+ g_debug ("signature = `%s'", signature);
+ g_debug ("path = `%s'", path);
+ g_debug ("subtree_path = `%s'", subtree_path != NULL ? subtree_path : "N/A");
+#endif
+
+ object_path = g_dbus_message_get_path (message);
+ g_assert (object_path != NULL);
+
+ eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path);
+ if (eo != NULL)
+ {
+ if (obj_message_func (connection, eo, message))
+ goto out;
+ }
+
+ es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path);
+ if (es != NULL)
+ {
+ if (subtree_message_func (connection, es, message))
+ goto out;
+ }
+
+ if (subtree_path != NULL)
+ {
+ es = g_hash_table_lookup (connection->priv->map_object_path_to_es, subtree_path);
+ if (es != NULL)
+ {
+ if (subtree_message_func (connection, es, message))
+ goto out;
+ }
+ }
+
+ if (handle_generic_unlocked (connection, message))
+ goto out;
+
+ /* if we end up here, the message has not been not handled */
+
+ out:
+ g_free (subtree_path);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusConnection **
+message_bus_get_singleton (GBusType bus_type,
+ GError **error)
+{
+ GDBusConnection **ret;
+ const gchar *starter_bus;
+
+ ret = NULL;
+
+ switch (bus_type)
+ {
+ default:
+ g_assert_not_reached ();
+ break;
+
+ case G_BUS_TYPE_SESSION:
+ ret = &the_session_bus;
+ break;
+
+ case G_BUS_TYPE_SYSTEM:
+ ret = &the_system_bus;
+ break;
+
+ case G_BUS_TYPE_STARTER:
+ starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE");
+ if (g_strcmp0 (starter_bus, "session") == 0)
+ {
+ ret = message_bus_get_singleton (G_BUS_TYPE_SESSION, error);
+ goto out;
+ }
+ else if (g_strcmp0 (starter_bus, "system") == 0)
+ {
+ ret = message_bus_get_singleton (G_BUS_TYPE_SYSTEM, error);
+ goto out;
+ }
+ else
+ {
+ if (starter_bus != NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable"
+ " - unknown value `%s'"),
+ starter_bus);
+ }
+ else
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment "
+ "variable is not set"));
+ }
+ }
+ break;
+ }
+ out:
+ return ret;
+}
+
+static GDBusConnection *
+get_uninitialized_connection (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusConnection **singleton;
+ GDBusConnection *ret;
+
+ ret = NULL;
+
+ G_LOCK (message_bus_lock);
+ singleton = message_bus_get_singleton (bus_type, error);
+ if (singleton == NULL)
+ goto out;
+
+ if (*singleton == NULL)
+ {
+ gchar *address;
+ address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error);
+ if (address == NULL)
+ goto out;
+ ret = *singleton = g_object_new (G_TYPE_DBUS_CONNECTION,
+ "address", address,
+ "flags", G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ "exit-on-close", TRUE,
+ NULL);
+ }
+ else
+ {
+ ret = g_object_ref (*singleton);
+ }
+
+ g_assert (ret != NULL);
+
+ out:
+ G_UNLOCK (message_bus_lock);
+ return ret;
+}
+
+/**
+ * g_bus_get_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects to the message bus specified by @bus_type.
+ * Note that the returned object may shared with other callers,
+ * e.g. if two separate parts of a process calls this function with
+ * the same @bus_type, they will share the same object.
+ *
+ * This is a synchronous failable function. See g_bus_get() and
+ * g_bus_get_finish() for the asynchronous version.
+ *
+ * The returned object is a singleton, that is, shared with other
+ * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the
+ * event that you need a private message bus connection, use
+ * g_dbus_address_get_for_bus() and
+ * g_dbus_connection_new_for_address().
+ *
+ * Note that the returned #GDBusConnection object will (usually) have
+ * the #GDBusConnection:exit-on-close property set to %TRUE.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_bus_get_sync (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusConnection *connection;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ connection = get_uninitialized_connection (bus_type, cancellable, error);
+ if (connection == NULL)
+ goto out;
+
+ if (!g_initable_init (G_INITABLE (connection), cancellable, error))
+ {
+ g_object_unref (connection);
+ connection = NULL;
+ }
+
+ out:
+ return connection;
+}
+
+static void
+bus_get_async_initable_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ GError *error;
+
+ error = NULL;
+ if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source_object),
+ res,
+ &error))
+ {
+ g_assert (error != NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ g_object_unref (source_object);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (simple,
+ source_object,
+ g_object_unref);
+ }
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+/**
+ * g_bus_get:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects to the message bus specified by @bus_type.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_bus_get_finish() to get the result of the operation.
+ *
+ * This is a asynchronous failable function. See g_bus_get_sync() for
+ * the synchronous version.
+ */
+void
+g_bus_get (GBusType bus_type,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDBusConnection *connection;
+ GSimpleAsyncResult *simple;
+ GError *error;
+
+ simple = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ g_bus_get);
+
+ error = NULL;
+ connection = get_uninitialized_connection (bus_type, cancellable, &error);
+ if (connection == NULL)
+ {
+ g_assert (error != NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ }
+ else
+ {
+ g_async_initable_init_async (G_ASYNC_INITABLE (connection),
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ bus_get_async_initable_cb,
+ simple);
+ }
+}
+
+/**
+ * g_bus_get_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_bus_get().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_bus_get().
+ *
+ * The returned object is a singleton, that is, shared with other
+ * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the
+ * event that you need a private message bus connection, use
+ * g_dbus_address_get_for_bus() and
+ * g_dbus_connection_new_for_address().
+ *
+ * Note that the returned #GDBusConnection object will (usually) have
+ * the #GDBusConnection:exit-on-close property set to %TRUE.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_bus_get_finish (GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GObject *object;
+ GDBusConnection *ret;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_bus_get);
+
+ ret = NULL;
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ object = g_simple_async_result_get_op_res_gpointer (simple);
+ g_assert (object != NULL);
+ ret = g_object_ref (G_DBUS_CONNECTION (object));
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h
new file mode 100644
index 000000000..4eeed48b4
--- /dev/null
+++ b/gio/gdbusconnection.h
@@ -0,0 +1,467 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_CONNECTION_H__
+#define __G_DBUS_CONNECTION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_CONNECTION (g_dbus_connection_get_type ())
+#define G_DBUS_CONNECTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_CONNECTION, GDBusConnection))
+#define G_DBUS_CONNECTION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass))
+#define G_DBUS_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass))
+#define G_IS_DBUS_CONNECTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_CONNECTION))
+#define G_IS_DBUS_CONNECTION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_CONNECTION))
+
+typedef struct _GDBusConnectionClass GDBusConnectionClass;
+typedef struct _GDBusConnectionPrivate GDBusConnectionPrivate;
+
+/**
+ * GDBusConnection:
+ *
+ * The #GDBusConnection structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusConnection
+{
+ /*< private >*/
+ GObject parent_instance;
+ GDBusConnectionPrivate *priv;
+};
+
+/**
+ * GDBusConnectionClass:
+ * @closed: Signal class handler for the #GDBusConnection::closed signal.
+ *
+ * Class structure for #GDBusConnection.
+ */
+struct _GDBusConnectionClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ /* Signals */
+ void (*closed) (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error);
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+GType g_dbus_connection_get_type (void) G_GNUC_CONST;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void g_bus_get (GBusType bus_type,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GDBusConnection *g_bus_get_finish (GAsyncResult *res,
+ GError **error);
+GDBusConnection *g_bus_get_sync (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void g_dbus_connection_new (GIOStream *stream,
+ const gchar *guid,
+ GDBusConnectionFlags flags,
+ GDBusAuthObserver *auth_observer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GDBusConnection *g_dbus_connection_new_finish (GAsyncResult *res,
+ GError **error);
+GDBusConnection *g_dbus_connection_new_sync (GIOStream *stream,
+ const gchar *guid,
+ GDBusConnectionFlags flags,
+ GDBusAuthObserver *auth_observer,
+ GCancellable *cancellable,
+ GError **error);
+
+void g_dbus_connection_new_for_address (const gchar *address,
+ GDBusConnectionFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GDBusConnection *g_dbus_connection_new_for_address_finish (GAsyncResult *res,
+ GError **error);
+GDBusConnection *g_dbus_connection_new_for_address_sync (const gchar *address,
+ GDBusConnectionFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean g_dbus_connection_is_closed (GDBusConnection *connection);
+void g_dbus_connection_close (GDBusConnection *connection);
+GIOStream *g_dbus_connection_get_stream (GDBusConnection *connection);
+const gchar *g_dbus_connection_get_guid (GDBusConnection *connection);
+const gchar *g_dbus_connection_get_unique_name (GDBusConnection *connection);
+GCredentials *g_dbus_connection_get_peer_credentials (GDBusConnection *connection);
+gboolean g_dbus_connection_get_exit_on_close (GDBusConnection *connection);
+void g_dbus_connection_set_exit_on_close (GDBusConnection *connection,
+ gboolean exit_on_close);
+GDBusCapabilityFlags g_dbus_connection_get_capabilities (GDBusConnection *connection);
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean g_dbus_connection_send_message (GDBusConnection *connection,
+ GDBusMessage *message,
+ volatile guint32 *out_serial,
+ GError **error);
+void g_dbus_connection_send_message_with_reply (GDBusConnection *connection,
+ GDBusMessage *message,
+ gint timeout_msec,
+ volatile guint32 *out_serial,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GDBusMessage *g_dbus_connection_send_message_with_reply_finish (GDBusConnection *connection,
+ GAsyncResult *res,
+ GError **error);
+GDBusMessage *g_dbus_connection_send_message_with_reply_sync (GDBusConnection *connection,
+ GDBusMessage *message,
+ gint timeout_msec,
+ volatile guint32 *out_serial,
+ GCancellable *cancellable,
+ GError **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean g_dbus_connection_emit_signal (GDBusConnection *connection,
+ const gchar *destination_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ GError **error);
+void g_dbus_connection_invoke_method (GDBusConnection *connection,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GVariant *g_dbus_connection_invoke_method_finish (GDBusConnection *connection,
+ GAsyncResult *res,
+ GError **error);
+GVariant *g_dbus_connection_invoke_method_sync (GDBusConnection *connection,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GError **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+/**
+ * GDBusInterfaceMethodCallFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @parameters: A #GVariant tuple with parameters.
+ * @invocation: A #GDBusMethodInvocation object that can be used to return a value or error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @method_call function in #GDBusInterfaceVTable.
+ */
+typedef void (*GDBusInterfaceMethodCallFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data);
+
+/**
+ * GDBusInterfaceGetPropertyFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @error: Return location for error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @get_property function in #GDBusInterfaceVTable.
+ *
+ * Returns: A newly-allocated #GVariant with the value for @property_name or %NULL if @error is set.
+ */
+typedef GVariant *(*GDBusInterfaceGetPropertyFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data);
+
+/**
+ * GDBusInterfaceSetPropertyFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @value: The value to set the property to.
+ * @error: Return location for error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @set_property function in #GDBusInterfaceVTable.
+ *
+ * Returns: %TRUE if the property was set to @value, %FALSE if @error is set.
+ */
+typedef gboolean (*GDBusInterfaceSetPropertyFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data);
+
+/**
+ * GDBusInterfaceVTable:
+ * @method_call: Function for handling incoming method calls.
+ * @get_property: Function for getting a property.
+ * @set_property: Function for setting a property.
+ *
+ * Virtual table for handling properties and method calls for a D-Bus
+ * interface.
+ *
+ * If you want to handle getting/setting D-Bus properties asynchronously, simply
+ * register an object with the <literal>org.freedesktop.DBus.Properties</literal>
+ * D-Bus interface using g_dbus_connection_register_object().
+ */
+struct _GDBusInterfaceVTable
+{
+ GDBusInterfaceMethodCallFunc method_call;
+ GDBusInterfaceGetPropertyFunc get_property;
+ GDBusInterfaceSetPropertyFunc set_property;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+guint g_dbus_connection_register_object (GDBusConnection *connection,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const GDBusInterfaceInfo *introspection_data,
+ const GDBusInterfaceVTable *vtable,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func,
+ GError **error);
+gboolean g_dbus_connection_unregister_object (GDBusConnection *connection,
+ guint registration_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusSubtreeEnumerateFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @enumerate function in #GDBusSubtreeVTable.
+ *
+ * Returns: A newly allocated array of strings for node names that are children of @object_path.
+ */
+typedef gchar** (*GDBusSubtreeEnumerateFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ gpointer user_data);
+
+/**
+ * GDBusSubtreeIntrospectFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @introspect function in #GDBusSubtreeVTable.
+ *
+ * Returns: A newly-allocated #GPtrArray with pointers to #GDBusInterfaceInfo describing
+ * the interfaces implemented by @node.
+ */
+typedef GPtrArray *(*GDBusSubtreeIntrospectFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *node,
+ gpointer user_data);
+
+/**
+ * GDBusSubtreeDispatchFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @interface_name: The D-Bus interface name that the method call or property access is for.
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ * @out_user_data: Return location for user data to pass to functions in the returned #GDBusInterfaceVTable (never %NULL).
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @dispatch function in #GDBusSubtreeVTable.
+ *
+ * Returns: A #GDBusInterfaceVTable or %NULL if you don't want to handle the methods.
+ */
+typedef const GDBusInterfaceVTable * (*GDBusSubtreeDispatchFunc) (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *node,
+ gpointer *out_user_data,
+ gpointer user_data);
+
+/**
+ * GDBusSubtreeVTable:
+ * @enumerate: Function for enumerating child nodes.
+ * @introspect: Function for introspecting a child node.
+ * @dispatch: Function for dispatching a remote call on a child node.
+ *
+ * Virtual table for handling subtrees registered with g_dbus_connection_register_subtree().
+ */
+struct _GDBusSubtreeVTable
+{
+ GDBusSubtreeEnumerateFunc enumerate;
+ GDBusSubtreeIntrospectFunc introspect;
+ GDBusSubtreeDispatchFunc dispatch;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+guint g_dbus_connection_register_subtree (GDBusConnection *connection,
+ const gchar *object_path,
+ const GDBusSubtreeVTable *vtable,
+ GDBusSubtreeFlags flags,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func,
+ GError **error);
+gboolean g_dbus_connection_unregister_subtree (GDBusConnection *connection,
+ guint registration_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusSignalCallback:
+ * @connection: A #GDBusConnection.
+ * @sender_name: The unique bus name of the sender of the signal.
+ * @object_path: The object path that the signal was emitted on.
+ * @interface_name: The name of the signal.
+ * @signal_name: The name of the signal.
+ * @parameters: A #GVariant tuple with parameters for the signal.
+ * @user_data: User data passed when subscribing to the signal.
+ *
+ * Signature for callback function used in g_dbus_connection_signal_subscribe().
+ */
+typedef void (*GDBusSignalCallback) (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+guint g_dbus_connection_signal_subscribe (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *interface_name,
+ const gchar *member,
+ const gchar *object_path,
+ const gchar *arg0,
+ GDBusSignalCallback callback,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+void g_dbus_connection_signal_unsubscribe (GDBusConnection *connection,
+ guint subscription_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusMessageFilterFunction:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @user_data: User data passed when adding the filter.
+ *
+ * Signature for function used in g_dbus_connection_add_filter().
+ *
+ * Returns: %TRUE if the filter handled @message, %FALSE to let other
+ * handlers run.
+ */
+typedef gboolean (*GDBusMessageFilterFunction) (GDBusConnection *connection,
+ GDBusMessage *message,
+ gpointer user_data);
+
+guint g_dbus_connection_add_filter (GDBusConnection *connection,
+ GDBusMessageFilterFunction filter_function,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+
+void g_dbus_connection_remove_filter (GDBusConnection *connection,
+ guint filter_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_CONNECTION_H__ */
diff --git a/gio/gdbuserror.c b/gio/gdbuserror.c
new file mode 100644
index 000000000..3b0e080fc
--- /dev/null
+++ b/gio/gdbuserror.c
@@ -0,0 +1,847 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbuserror.h"
+#include "gioenums.h"
+#include "gioenumtypes.h"
+#include "gioerror.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbuserror
+ * @title: GDBusError
+ * @short_description: Mapping D-Bus errors to and from #GError
+ * @include: gdbus/gdbus.h
+ *
+ * All facilities that return errors from remote methods (such as
+ * g_dbus_connection_invoke_method_sync()) use #GError to represent
+ * both D-Bus errors (e.g. errors returned from the other peer) and
+ * locally in-process generated errors.
+ *
+ * To check if a returned #GError is an error from a remote peer, use
+ * g_dbus_error_is_remote_error(). To get the actual D-Bus error name,
+ * use g_dbus_error_get_remote_error(). Before presenting an error,
+ * always use g_dbus_error_strip_remote_error().
+ *
+ * In addition, facilities used to return errors to a remote peer also
+ * use #GError. See g_dbus_method_invocation_return_error() for
+ * discussion about how the D-Bus error name is set.
+ *
+ * Applications can associate a #GError error domain with a set of D-Bus errors in order to
+ * automatically map from D-Bus errors to #GError and back. This
+ * is typically done in the function returning the #GQuark for the
+ * error domain:
+ * <example id="error-registration"><title>Error Registration</title><programlisting>
+ * /<!-- -->* foo-bar-error.h: *<!-- -->/
+ *
+ * #define FOO_BAR_ERROR (foo_bar_error_quark ())
+ * GQuark foo_bar_error_quark (void);
+ *
+ * typedef enum
+ * {
+ * FOO_BAR_ERROR_FAILED,
+ * FOO_BAR_ERROR_ANOTHER_ERROR,
+ * FOO_BAR_ERROR_SOME_THIRD_ERROR,
+ * } FooBarError;
+ *
+ * /<!-- -->* foo-bar-error.c: *<!-- -->/
+ *
+ * static const GDBusErrorEntry foo_bar_error_entries[] =
+ * {
+ * {FOO_BAR_ERROR_FAILED, "org.project.Foo.Bar.Error.Failed"},
+ * {FOO_BAR_ERROR_ANOTHER_ERROR, "org.project.Foo.Bar.Error.AnotherError"},
+ * {FOO_BAR_ERROR_SOME_THIRD_ERROR, "org.project.Foo.Bar.Error.SomeThirdError"},
+ * };
+ *
+ * GQuark
+ * foo_bar_error_quark (void)
+ * {
+ * static volatile gsize quark_volatile = 0;
+ * g_dbus_error_register_error_domain ("foo-bar-error-quark",
+ * &quark_volatile,
+ * foo_bar_error_entries,
+ * G_N_ELEMENTS (foo_bar_error_entries));
+ * G_STATIC_ASSERT (G_N_ELEMENTS (foo_bar_error_entries) - 1 == FOO_BAR_ERROR_SOME_THIRD_ERROR);
+ * return (GQuark) quark_volatile;
+ * }
+ * </programlisting></example>
+ * With this setup, a D-Bus peer can transparently pass e.g. %FOO_BAR_ERROR_ANOTHER_ERROR and
+ * other peers will see the D-Bus error name <literal>org.project.Foo.Bar.Error.AnotherError</literal>.
+ * If the other peer is using GDBus, the peer will see also %FOO_BAR_ERROR_ANOTHER_ERROR instead
+ * of %G_IO_ERROR_DBUS_ERROR. Note that GDBus clients can still recover
+ * <literal>org.project.Foo.Bar.Error.AnotherError</literal> using g_dbus_error_get_remote_error().
+ *
+ * Note that errors in the %G_DBUS_ERROR error domain is intended only
+ * for returning errors from a remote message bus process. Errors
+ * generated locally in-process by e.g. #GDBusConnection is from the
+ * %G_IO_ERROR domain.
+ */
+
+static const GDBusErrorEntry g_dbus_error_entries[] =
+{
+ {G_DBUS_ERROR_FAILED, "org.freedesktop.DBus.Error.Failed"},
+ {G_DBUS_ERROR_NO_MEMORY, "org.freedesktop.DBus.Error.NoMemory"},
+ {G_DBUS_ERROR_SERVICE_UNKNOWN, "org.freedesktop.DBus.Error.ServiceUnknown"},
+ {G_DBUS_ERROR_NAME_HAS_NO_OWNER, "org.freedesktop.DBus.Error.NameHasNoOwner"},
+ {G_DBUS_ERROR_NO_REPLY, "org.freedesktop.DBus.Error.NoReply"},
+ {G_DBUS_ERROR_IO_ERROR, "org.freedesktop.DBus.Error.IOError"},
+ {G_DBUS_ERROR_BAD_ADDRESS, "org.freedesktop.DBus.Error.BadAddress"},
+ {G_DBUS_ERROR_NOT_SUPPORTED, "org.freedesktop.DBus.Error.NotSupported"},
+ {G_DBUS_ERROR_LIMITS_EXCEEDED, "org.freedesktop.DBus.Error.LimitsExceeded"},
+ {G_DBUS_ERROR_ACCESS_DENIED, "org.freedesktop.DBus.Error.AccessDenied"},
+ {G_DBUS_ERROR_AUTH_FAILED, "org.freedesktop.DBus.Error.AuthFailed"},
+ {G_DBUS_ERROR_NO_SERVER, "org.freedesktop.DBus.Error.NoServer"},
+ {G_DBUS_ERROR_TIMEOUT, "org.freedesktop.DBus.Error.Timeout"},
+ {G_DBUS_ERROR_NO_NETWORK, "org.freedesktop.DBus.Error.NoNetwork"},
+ {G_DBUS_ERROR_ADDRESS_IN_USE, "org.freedesktop.DBus.Error.AddressInUse"},
+ {G_DBUS_ERROR_DISCONNECTED, "org.freedesktop.DBus.Error.Disconnected"},
+ {G_DBUS_ERROR_INVALID_ARGS, "org.freedesktop.DBus.Error.InvalidArgs"},
+ {G_DBUS_ERROR_FILE_NOT_FOUND, "org.freedesktop.DBus.Error.FileNotFound"},
+ {G_DBUS_ERROR_FILE_EXISTS, "org.freedesktop.DBus.Error.FileExists"},
+ {G_DBUS_ERROR_UNKNOWN_METHOD, "org.freedesktop.DBus.Error.UnknownMethod"},
+ {G_DBUS_ERROR_TIMED_OUT, "org.freedesktop.DBus.Error.TimedOut"},
+ {G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "org.freedesktop.DBus.Error.MatchRuleNotFound"},
+ {G_DBUS_ERROR_MATCH_RULE_INVALID, "org.freedesktop.DBus.Error.MatchRuleInvalid"},
+ {G_DBUS_ERROR_SPAWN_EXEC_FAILED, "org.freedesktop.DBus.Error.Spawn.ExecFailed"},
+ {G_DBUS_ERROR_SPAWN_FORK_FAILED, "org.freedesktop.DBus.Error.Spawn.ForkFailed"},
+ {G_DBUS_ERROR_SPAWN_CHILD_EXITED, "org.freedesktop.DBus.Error.Spawn.ChildExited"},
+ {G_DBUS_ERROR_SPAWN_CHILD_SIGNALED, "org.freedesktop.DBus.Error.Spawn.ChildSignaled"},
+ {G_DBUS_ERROR_SPAWN_FAILED, "org.freedesktop.DBus.Error.Spawn.Failed"},
+ {G_DBUS_ERROR_SPAWN_SETUP_FAILED, "org.freedesktop.DBus.Error.Spawn.FailedToSetup"},
+ {G_DBUS_ERROR_SPAWN_CONFIG_INVALID, "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"},
+ {G_DBUS_ERROR_SPAWN_SERVICE_INVALID, "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"},
+ {G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"},
+ {G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"},
+ {G_DBUS_ERROR_SPAWN_FILE_INVALID, "org.freedesktop.DBus.Error.Spawn.FileInvalid"},
+ {G_DBUS_ERROR_SPAWN_NO_MEMORY, "org.freedesktop.DBus.Error.Spawn.NoMemory"},
+ {G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "org.freedesktop.DBus.Error.UnixProcessIdUnknown"},
+ {G_DBUS_ERROR_INVALID_SIGNATURE, "org.freedesktop.DBus.Error.InvalidSignature"},
+ {G_DBUS_ERROR_INVALID_FILE_CONTENT, "org.freedesktop.DBus.Error.InvalidFileContent"},
+ {G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"},
+ {G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, "org.freedesktop.DBus.Error.AdtAuditDataUnknown"},
+ {G_DBUS_ERROR_OBJECT_PATH_IN_USE, "org.freedesktop.DBus.Error.ObjectPathInUse"},
+};
+
+GQuark
+g_dbus_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+ g_dbus_error_register_error_domain ("g-dbus-error-quark",
+ &quark_volatile,
+ g_dbus_error_entries,
+ G_N_ELEMENTS (g_dbus_error_entries));
+ G_STATIC_ASSERT (G_N_ELEMENTS (g_dbus_error_entries) - 1 == G_DBUS_ERROR_OBJECT_PATH_IN_USE);
+ return (GQuark) quark_volatile;
+}
+
+/**
+ * g_dbus_error_register_error_domain:
+ * @error_domain_quark_name: The error domain name.
+ * @quark_volatile: A pointer where to store the #GQuark.
+ * @entries: A pointer to @num_entries #GDBusErrorEntry struct items.
+ * @num_entries: Number of items to register.
+ *
+ * Helper function for associating a #GError error domain with D-Bus error names.
+ */
+void
+g_dbus_error_register_error_domain (const gchar *error_domain_quark_name,
+ volatile gsize *quark_volatile,
+ const GDBusErrorEntry *entries,
+ guint num_entries)
+{
+ g_return_if_fail (error_domain_quark_name != NULL);
+ g_return_if_fail (quark_volatile != NULL);
+ g_return_if_fail (entries != NULL);
+ g_return_if_fail (num_entries > 0);
+
+ if (g_once_init_enter (quark_volatile))
+ {
+ guint n;
+ GQuark quark;
+
+ quark = g_quark_from_static_string (error_domain_quark_name);
+
+ for (n = 0; n < num_entries; n++)
+ {
+ g_warn_if_fail (g_dbus_error_register_error (quark,
+ entries[n].error_code,
+ entries[n].dbus_error_name));
+ }
+ g_once_init_leave (quark_volatile, quark);
+ }
+}
+
+static gboolean
+_g_dbus_error_decode_gerror (const gchar *dbus_name,
+ GQuark *out_error_domain,
+ gint *out_error_code)
+{
+ gboolean ret;
+ guint n;
+ GString *s;
+ gchar *domain_quark_string;
+
+ ret = FALSE;
+ s = NULL;
+
+ if (g_str_has_prefix (dbus_name, "org.gtk.GDBus.UnmappedGError.Quark._"))
+ {
+ s = g_string_new (NULL);
+
+ for (n = sizeof "org.gtk.GDBus.UnmappedGError.Quark._" - 1;
+ dbus_name[n] != '.' && dbus_name[n] != '\0';
+ n++)
+ {
+ if (g_ascii_isalnum (dbus_name[n]))
+ {
+ g_string_append_c (s, dbus_name[n]);
+ }
+ else if (dbus_name[n] == '_')
+ {
+ guint nibble_top;
+ guint nibble_bottom;
+
+ n++;
+
+ nibble_top = dbus_name[n];
+ if (nibble_top >= '0' && nibble_top <= '9')
+ nibble_top -= '0';
+ else if (nibble_top >= 'a' && nibble_top <= 'f')
+ nibble_top -= ('a' - 10);
+ else
+ goto not_mapped;
+
+ n++;
+
+ nibble_bottom = dbus_name[n];
+ if (nibble_bottom >= '0' && nibble_bottom <= '9')
+ nibble_bottom -= '0';
+ else if (nibble_bottom >= 'a' && nibble_bottom <= 'f')
+ nibble_bottom -= ('a' - 10);
+ else
+ goto not_mapped;
+
+ g_string_append_c (s, (nibble_top<<4) | nibble_bottom);
+ }
+ else
+ {
+ goto not_mapped;
+ }
+ }
+
+ if (!g_str_has_prefix (dbus_name + n, ".Code"))
+ goto not_mapped;
+
+ domain_quark_string = g_string_free (s, FALSE);
+ s = NULL;
+
+ if (out_error_domain != NULL)
+ *out_error_domain = g_quark_from_string (domain_quark_string);
+ g_free (domain_quark_string);
+
+ if (out_error_code != NULL)
+ *out_error_code = atoi (dbus_name + n + sizeof ".Code" - 1);
+
+ ret = TRUE;
+ }
+
+ not_mapped:
+
+ if (s != NULL)
+ g_string_free (s, TRUE);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GQuark error_domain;
+ gint error_code;
+} QuarkCodePair;
+
+static guint
+quark_code_pair_hash_func (const QuarkCodePair *pair)
+{
+ gint val;
+ val = pair->error_domain + pair->error_code;
+ return g_int_hash (&val);
+}
+
+static gboolean
+quark_code_pair_equal_func (const QuarkCodePair *a,
+ const QuarkCodePair *b)
+{
+ return (a->error_domain == b->error_domain) && (a->error_code == b->error_code);
+}
+
+typedef struct
+{
+ QuarkCodePair pair;
+ gchar *dbus_error_name;
+} RegisteredError;
+
+static void
+registered_error_free (RegisteredError *re)
+{
+ g_free (re->dbus_error_name);
+ g_free (re);
+}
+
+G_LOCK_DEFINE_STATIC (error_lock);
+
+/* maps from QuarkCodePair* -> RegisteredError* */
+static GHashTable *quark_code_pair_to_re = NULL;
+
+/* maps from gchar* -> RegisteredError* */
+static GHashTable *dbus_error_name_to_re = NULL;
+
+/**
+ * g_dbus_error_register_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Creates an association to map between @dbus_error_name and
+ * #GError<!-- -->s specified by @error_domain and @error_code.
+ *
+ * This is typically done in the routine that returns the #GQuark for
+ * an error domain.
+ *
+ * Returns: %TRUE if the association was created, %FALSE if it already
+ * exists.
+ */
+gboolean
+g_dbus_error_register_error (GQuark error_domain,
+ gint error_code,
+ const gchar *dbus_error_name)
+{
+ gboolean ret;
+ QuarkCodePair pair;
+ RegisteredError *re;
+
+ g_return_val_if_fail (dbus_error_name != NULL, FALSE);
+
+ ret = FALSE;
+
+ G_LOCK (error_lock);
+
+ if (quark_code_pair_to_re == NULL)
+ {
+ g_assert (dbus_error_name_to_re == NULL); /* check invariant */
+ quark_code_pair_to_re = g_hash_table_new ((GHashFunc) quark_code_pair_hash_func,
+ (GEqualFunc) quark_code_pair_equal_func);
+ dbus_error_name_to_re = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) registered_error_free);
+ }
+
+ if (g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name) != NULL)
+ goto out;
+
+ pair.error_domain = error_domain;
+ pair.error_code = error_code;
+
+ if (g_hash_table_lookup (quark_code_pair_to_re, &pair) != NULL)
+ goto out;
+
+ re = g_new0 (RegisteredError, 1);
+ re->pair = pair;
+ re->dbus_error_name = g_strdup (dbus_error_name);
+
+ g_hash_table_insert (quark_code_pair_to_re, &(re->pair), re);
+ g_hash_table_insert (dbus_error_name_to_re, re->dbus_error_name, re);
+
+ ret = TRUE;
+
+ out:
+ G_UNLOCK (error_lock);
+ return ret;
+}
+
+/**
+ * g_dbus_error_unregister_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Destroys an association previously set up with g_dbus_error_register_error().
+ *
+ * Returns: %TRUE if the association was destroyed, %FALSE if it wasn't found.
+ */
+gboolean
+g_dbus_error_unregister_error (GQuark error_domain,
+ gint error_code,
+ const gchar *dbus_error_name)
+{
+ gboolean ret;
+ RegisteredError *re;
+ guint hash_size;
+
+ g_return_val_if_fail (dbus_error_name != NULL, FALSE);
+
+ ret = FALSE;
+
+ G_LOCK (error_lock);
+
+ if (dbus_error_name_to_re == NULL)
+ {
+ g_assert (quark_code_pair_to_re == NULL); /* check invariant */
+ goto out;
+ }
+
+ re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+ if (re == NULL)
+ {
+ QuarkCodePair pair;
+ pair.error_domain = error_domain;
+ pair.error_code = error_code;
+ g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &pair) == NULL); /* check invariant */
+ goto out;
+ }
+ g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &(re->pair)) == re); /* check invariant */
+
+ g_warn_if_fail (g_hash_table_remove (quark_code_pair_to_re, &(re->pair)));
+ g_warn_if_fail (g_hash_table_remove (dbus_error_name_to_re, re));
+
+ /* destroy hashes if empty */
+ hash_size = g_hash_table_size (dbus_error_name_to_re);
+ if (hash_size == 0)
+ {
+ g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == 0); /* check invariant */
+
+ g_hash_table_unref (dbus_error_name_to_re);
+ dbus_error_name_to_re = NULL;
+ g_hash_table_unref (quark_code_pair_to_re);
+ quark_code_pair_to_re = NULL;
+ }
+ else
+ {
+ g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == hash_size); /* check invariant */
+ }
+
+ out:
+ G_UNLOCK (error_lock);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_error_is_remote_error:
+ * @error: A #GError.
+ *
+ * Checks if @error represents an error received via D-Bus from a remote peer. If so,
+ * use g_dbus_error_get_remote_error() to get the name of the error.
+ *
+ * Returns: %TRUE if @error represents an error from a remote peer,
+ * %FALSE otherwise.
+ */
+gboolean
+g_dbus_error_is_remote_error (const GError *error)
+{
+ g_return_val_if_fail (error != NULL, FALSE);
+ return g_str_has_prefix (error->message, "GDBus.Error:");
+}
+
+
+/**
+ * g_dbus_error_get_remote_error:
+ * @error: A #GError.
+ *
+ * Gets the D-Bus error name used for @error, if any.
+ *
+ * This function is guaranteed to return a D-Bus error name for all #GError<!-- -->s returned from
+ * functions handling remote method calls (e.g. g_dbus_connection_invoke_method_finish())
+ * unless g_dbus_error_strip_remote_error() has been used on @error.
+ *
+ * Returns: An allocated string or %NULL if the D-Bus error name could not be found. Free with g_free().
+ */
+gchar *
+g_dbus_error_get_remote_error (const GError *error)
+{
+ RegisteredError *re;
+ gchar *ret;
+
+ g_return_val_if_fail (error != NULL, NULL);
+
+ /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+ _g_dbus_initialize ();
+
+ ret = NULL;
+
+ G_LOCK (error_lock);
+
+ re = NULL;
+ if (quark_code_pair_to_re != NULL)
+ {
+ QuarkCodePair pair;
+ pair.error_domain = error->domain;
+ pair.error_code = error->code;
+ g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+ re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+ }
+
+ if (re != NULL)
+ {
+ ret = g_strdup (re->dbus_error_name);
+ }
+ else
+ {
+ if (g_str_has_prefix (error->message, "GDBus.Error:"))
+ {
+ const gchar *begin;
+ const gchar *end;
+ begin = error->message + sizeof ("GDBus.Error:") -1;
+ end = strstr (begin, ":");
+ if (end != NULL && end[1] == ' ')
+ {
+ ret = g_strndup (begin, end - begin);
+ }
+ }
+ }
+
+ G_UNLOCK (error_lock);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_error_new_for_dbus_error:
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ *
+ * Creates a #GError based on the contents of @dbus_error_name and
+ * @dbus_error_message.
+ *
+ * Errors registered with g_dbus_error_register_error() will be looked
+ * up using @dbus_error_name and if a match is found, the error domain
+ * and code is used. Applications can use g_dbus_error_get_remote_error()
+ * to recover @dbus_error_name.
+ *
+ * If a match against a registered error is not found and the D-Bus
+ * error name is in a form as returned by g_dbus_error_encode_gerror()
+ * the error domain and code encoded in the name is used to
+ * create the #GError. Also, @dbus_error_name is added to the error message
+ * such that it can be recovered with g_dbus_error_get_remote_error().
+ *
+ * Otherwise, a #GError with the error code %G_IO_ERROR_DBUS_ERROR
+ * in the #G_IO_ERROR error domain is returned. Also, @dbus_error_name is
+ * added to the error message such that it can be recovered with
+ * g_dbus_error_get_remote_error().
+ *
+ * In all three cases, @dbus_error_name can always be recovered from the
+ * returned #GError using the g_dbus_error_get_remote_error() function
+ * (unless g_dbus_error_strip_remote_error() hasn't been used on the returned error).
+ *
+ * This function is typically only used in object mappings to prepare
+ * #GError instances for applications. Regular applications should not use
+ * it.
+ *
+ * Returns: An allocated #GError. Free with g_error_free().
+ */
+GError *
+g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name,
+ const gchar *dbus_error_message)
+{
+ GError *error;
+ RegisteredError *re;
+
+ g_return_val_if_fail (dbus_error_name != NULL, NULL);
+ g_return_val_if_fail (dbus_error_message != NULL, NULL);
+
+ /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+ _g_dbus_initialize ();
+
+ G_LOCK (error_lock);
+
+ re = NULL;
+ if (dbus_error_name_to_re != NULL)
+ {
+ g_assert (quark_code_pair_to_re != NULL); /* check invariant */
+ re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+ }
+
+ if (re != NULL)
+ {
+ error = g_error_new (re->pair.error_domain,
+ re->pair.error_code,
+ "GDBus.Error:%s: %s",
+ dbus_error_name,
+ dbus_error_message);
+ }
+ else
+ {
+ GQuark error_domain = 0;
+ gint error_code = 0;
+
+ if (_g_dbus_error_decode_gerror (dbus_error_name,
+ &error_domain,
+ &error_code))
+ {
+ error = g_error_new (error_domain,
+ error_code,
+ "GDBus.Error:%s: %s",
+ dbus_error_name,
+ dbus_error_message);
+ }
+ else
+ {
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_DBUS_ERROR,
+ "GDBus.Error:%s: %s",
+ dbus_error_name,
+ dbus_error_message);
+ }
+ }
+
+ G_UNLOCK (error_lock);
+ return error;
+}
+
+/**
+ * g_dbus_error_set_dbus_error:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @...: Arguments for @format.
+ *
+ * Does nothing if @error is %NULL. Otherwise sets *@error to
+ * a new #GError created with g_dbus_error_new_for_dbus_error()
+ * with @dbus_error_message prepend with @format (unless %NULL).
+ */
+void
+g_dbus_error_set_dbus_error (GError **error,
+ const gchar *dbus_error_name,
+ const gchar *dbus_error_message,
+ const gchar *format,
+ ...)
+{
+ g_return_if_fail (error == NULL || *error == NULL);
+ g_return_if_fail (dbus_error_name != NULL);
+ g_return_if_fail (dbus_error_message != NULL);
+
+ if (error == NULL)
+ return;
+
+ if (format == NULL)
+ {
+ *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+ }
+ else
+ {
+ va_list var_args;
+ va_start (var_args, format);
+ g_dbus_error_set_dbus_error_valist (error,
+ dbus_error_name,
+ dbus_error_message,
+ format,
+ var_args);
+ va_end (var_args);
+ }
+}
+
+/**
+ * g_dbus_error_set_dbus_error_valist:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @var_args: Arguments for @format.
+ *
+ * Like g_dbus_error_set_dbus_error() but intended for language bindings.
+ */
+void
+g_dbus_error_set_dbus_error_valist (GError **error,
+ const gchar *dbus_error_name,
+ const gchar *dbus_error_message,
+ const gchar *format,
+ va_list var_args)
+{
+ g_return_if_fail (error == NULL || *error == NULL);
+ g_return_if_fail (dbus_error_name != NULL);
+ g_return_if_fail (dbus_error_message != NULL);
+
+ if (error == NULL)
+ return;
+
+ if (format != NULL)
+ {
+ gchar *message;
+ gchar *s;
+ message = g_strdup_vprintf (format, var_args);
+ s = g_strdup_printf ("%s: %s", message, dbus_error_message);
+ *error = g_dbus_error_new_for_dbus_error (dbus_error_name, s);
+ g_free (s);
+ g_free (message);
+ }
+ else
+ {
+ *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+ }
+}
+
+/**
+ * g_dbus_error_strip_remote_error:
+ * @error: A #GError.
+ *
+ * Looks for extra information in the error message used to recover
+ * the D-Bus error name and strips it if found. If stripped, the
+ * message field in @error will correspond exactly to what was
+ * received on the wire.
+ *
+ * This is typically used when presenting errors to the end user.
+ *
+ * Returns: %TRUE if information was stripped, %FALSE otherwise.
+ */
+gboolean
+g_dbus_error_strip_remote_error (GError *error)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (error != NULL, FALSE);
+
+ ret = FALSE;
+
+ if (g_str_has_prefix (error->message, "GDBus.Error:"))
+ {
+ const gchar *begin;
+ const gchar *end;
+ gchar *new_message;
+
+ begin = error->message + sizeof ("GDBus.Error:") -1;
+ end = strstr (begin, ":");
+ if (end != NULL && end[1] == ' ')
+ {
+ new_message = g_strdup (end + 2);
+ g_free (error->message);
+ error->message = new_message;
+ ret = TRUE;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * g_dbus_error_encode_gerror:
+ * @error: A #GError.
+ *
+ * Creates a D-Bus error name to use for @error. If @error matches
+ * a registered error (cf. g_dbus_error_register_error()), the corresponding
+ * D-Bus error name will be returned.
+ *
+ * Otherwise the a name of the form
+ * <literal>org.gtk.GDBus.UnmappedGError.Quark._ESCAPED_QUARK_NAME.Code_ERROR_CODE</literal>
+ * will be used. This allows other GDBus applications to map the error
+ * on the wire back to a #GError using g_dbus_error_new_for_dbus_error().
+ *
+ * This function is typically only used in object mappings to put a
+ * #GError on the wire. Regular applications should not use it.
+ *
+ * Returns: A D-Bus error name (never %NULL). Free with g_free().
+ */
+gchar *
+g_dbus_error_encode_gerror (const GError *error)
+{
+ RegisteredError *re;
+ gchar *error_name;
+
+ g_return_val_if_fail (error != NULL, NULL);
+
+ /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+ _g_dbus_initialize ();
+
+ error_name = NULL;
+
+ G_LOCK (error_lock);
+ re = NULL;
+ if (quark_code_pair_to_re != NULL)
+ {
+ QuarkCodePair pair;
+ pair.error_domain = error->domain;
+ pair.error_code = error->code;
+ g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+ re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+ }
+ if (re != NULL)
+ {
+ error_name = g_strdup (re->dbus_error_name);
+ G_UNLOCK (error_lock);
+ }
+ else
+ {
+ const gchar *domain_as_string;
+ GString *s;
+ guint n;
+
+ G_UNLOCK (error_lock);
+
+ /* We can't make a lot of assumptions about what domain_as_string
+ * looks like and D-Bus is extremely picky about error names so
+ * hex-encode it for transport across the wire.
+ */
+ domain_as_string = g_quark_to_string (error->domain);
+ s = g_string_new ("org.gtk.GDBus.UnmappedGError.Quark._");
+ for (n = 0; domain_as_string[n] != 0; n++)
+ {
+ gint c = domain_as_string[n];
+ if (g_ascii_isalnum (c))
+ {
+ g_string_append_c (s, c);
+ }
+ else
+ {
+ guint nibble_top;
+ guint nibble_bottom;
+ g_string_append_c (s, '_');
+ nibble_top = ((int) domain_as_string[n]) >> 4;
+ nibble_bottom = ((int) domain_as_string[n]) & 0x0f;
+ if (nibble_top < 10)
+ nibble_top += '0';
+ else
+ nibble_top += 'a' - 10;
+ if (nibble_bottom < 10)
+ nibble_bottom += '0';
+ else
+ nibble_bottom += 'a' - 10;
+ g_string_append_c (s, nibble_top);
+ g_string_append_c (s, nibble_bottom);
+ }
+ }
+ g_string_append_printf (s, ".Code%d", error->code);
+ error_name = g_string_free (s, FALSE);
+ }
+
+ return error_name;
+}
diff --git a/gio/gdbuserror.h b/gio/gdbuserror.h
new file mode 100644
index 000000000..d15724cf5
--- /dev/null
+++ b/gio/gdbuserror.h
@@ -0,0 +1,92 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_ERROR_H__
+#define __G_DBUS_ERROR_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * G_DBUS_ERROR:
+ *
+ * Error domain for errors generated by a remote message bus. Errors
+ * in this domain will be from the #GDBusError enumeration. See
+ * #GError for more information on error domains.
+ *
+ * Note that errors in this error domain is intended only for
+ * returning errors from a remote message bus process. Errors
+ * generated locally in-process by e.g. #GDBusConnection is from the
+ * %G_IO_ERROR domain.
+ */
+#define G_DBUS_ERROR g_dbus_error_quark()
+
+GQuark g_dbus_error_quark (void);
+
+/* Used by applications to check, get and strip the D-Bus error name */
+gboolean g_dbus_error_is_remote_error (const GError *error);
+gchar *g_dbus_error_get_remote_error (const GError *error);
+gboolean g_dbus_error_strip_remote_error (GError *error);
+
+/**
+ * GDBusErrorEntry:
+ * @error_code: An error code.
+ * @dbus_error_name: The D-Bus error name to associate with @error_code.
+ *
+ * Struct used in g_dbus_error_register_error_domain().
+ */
+struct _GDBusErrorEntry
+{
+ gint error_code;
+ const gchar *dbus_error_name;
+};
+
+gboolean g_dbus_error_register_error (GQuark error_domain,
+ gint error_code,
+ const gchar *dbus_error_name);
+gboolean g_dbus_error_unregister_error (GQuark error_domain,
+ gint error_code,
+ const gchar *dbus_error_name);
+void g_dbus_error_register_error_domain (const gchar *error_domain_quark_name,
+ volatile gsize *quark_volatile,
+ const GDBusErrorEntry *entries,
+ guint num_entries);
+
+/* Only used by object mappings to map back and forth to GError */
+GError *g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name,
+ const gchar *dbus_error_message);
+void g_dbus_error_set_dbus_error (GError **error,
+ const gchar *dbus_error_name,
+ const gchar *dbus_error_message,
+ const gchar *format,
+ ...);
+void g_dbus_error_set_dbus_error_valist (GError **error,
+ const gchar *dbus_error_name,
+ const gchar *dbus_error_message,
+ const gchar *format,
+ va_list var_args);
+gchar *g_dbus_error_encode_gerror (const GError *error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_ERROR_H__ */
diff --git a/gio/gdbusintrospection.c b/gio/gdbusintrospection.c
new file mode 100644
index 000000000..63b945e0e
--- /dev/null
+++ b/gio/gdbusintrospection.c
@@ -0,0 +1,2009 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusintrospection.h"
+
+/**
+ * SECTION:gdbusintrospection
+ * @title: Introspection XML
+ * @short_description: Parse and Generate Introspection XML
+ * @include: gdbus/gdbus.h
+ *
+ * Various data structures and convenience routines to parse and
+ * generate D-Bus introspection XML.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* See also https://bugzilla.gnome.org/show_bug.cgi?id=449565 ... */
+#define _MY_DEFINE_BOXED_TYPE(TypeName, type_name) \
+ GType \
+ type_name##_get_type (void) \
+ { \
+ static volatile gsize type_volatile = 0; \
+ if (g_once_init_enter (&type_volatile)) \
+ { \
+ GType type = g_boxed_type_register_static (g_intern_static_string (#TypeName), \
+ (GBoxedCopyFunc) type_name##_ref, \
+ (GBoxedFreeFunc) type_name##_unref); \
+ g_once_init_leave (&type_volatile, type); \
+ } \
+ return (GType) type_volatile; \
+ }
+
+_MY_DEFINE_BOXED_TYPE (GDBusNodeInfo, g_dbus_node_info);
+_MY_DEFINE_BOXED_TYPE (GDBusInterfaceInfo, g_dbus_interface_info);
+_MY_DEFINE_BOXED_TYPE (GDBusMethodInfo, g_dbus_method_info);
+_MY_DEFINE_BOXED_TYPE (GDBusPropertyInfo, g_dbus_property_info);
+_MY_DEFINE_BOXED_TYPE (GDBusArgInfo, g_dbus_arg_info);
+_MY_DEFINE_BOXED_TYPE (GDBusAnnotationInfo, g_dbus_annotation_info);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ /* stuff we are currently collecting */
+ GPtrArray *args;
+ GPtrArray *out_args;
+ GPtrArray *methods;
+ GPtrArray *signals;
+ GPtrArray *properties;
+ GPtrArray *interfaces;
+ GPtrArray *nodes;
+ GPtrArray *annotations;
+
+ /* A list of GPtrArray's containing annotations */
+ GSList *annotations_stack;
+
+ /* A list of GPtrArray's containing interfaces */
+ GSList *interfaces_stack;
+
+ /* A list of GPtrArray's containing nodes */
+ GSList *nodes_stack;
+
+ /* Whether the direction was "in" for last parsed arg */
+ gboolean last_arg_was_in;
+
+ /* Number of args currently being collected; used for assigning
+ * names to args without a "name" attribute
+ */
+ guint num_args;
+
+} ParseData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_ref:
+ * @info: A #GDBusNodeInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusNodeInfo *
+g_dbus_node_info_ref (GDBusNodeInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_interface_info_ref:
+ * @info: A #GDBusInterfaceInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusInterfaceInfo *
+g_dbus_interface_info_ref (GDBusInterfaceInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_method_info_ref:
+ * @info: A #GDBusMethodInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusMethodInfo *
+g_dbus_method_info_ref (GDBusMethodInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_signal_info_ref:
+ * @info: A #GDBusSignalInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusSignalInfo *
+g_dbus_signal_info_ref (GDBusSignalInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_property_info_ref:
+ * @info: A #GDBusPropertyInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusPropertyInfo *
+g_dbus_property_info_ref (GDBusPropertyInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_arg_info_ref:
+ * @info: A #GDBusArgInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusArgInfo *
+g_dbus_arg_info_ref (GDBusArgInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/**
+ * g_dbus_node_info_ref:
+ * @info: A #GDBusNodeInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusAnnotationInfo *
+g_dbus_annotation_info_ref (GDBusAnnotationInfo *info)
+{
+ if (info->ref_count == -1)
+ return info;
+ g_atomic_int_inc (&info->ref_count);
+ return info;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+free_null_terminated_array (gpointer array, GDestroyNotify unref_func)
+{
+ guint n;
+ gpointer *p = array;
+ if (p == NULL)
+ return;
+ for (n = 0; p[n] != NULL; n++)
+ unref_func (p[n]);
+ g_free (p);
+}
+
+/**
+ * g_dbus_annotation_info_unref:
+ * @info: A #GDBusAnnotationInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_annotation_info_unref (GDBusAnnotationInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->key);
+ g_free (info->value);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_arg_info_unref:
+ * @info: A #GDBusArgInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_arg_info_unref (GDBusArgInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->name);
+ g_free (info->signature);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_method_info_unref:
+ * @info: A #GDBusMethodInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_method_info_unref (GDBusMethodInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->name);
+ free_null_terminated_array (info->in_args, (GDestroyNotify) g_dbus_arg_info_unref);
+ free_null_terminated_array (info->out_args, (GDestroyNotify) g_dbus_arg_info_unref);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_signal_info_unref:
+ * @info: A #GDBusSignalInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_signal_info_unref (GDBusSignalInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->name);
+ free_null_terminated_array (info->args, (GDestroyNotify) g_dbus_arg_info_unref);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_property_info_unref:
+ * @info: A #GDBusPropertyInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_property_info_unref (GDBusPropertyInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->name);
+ g_free (info->signature);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_interface_info_unref:
+ * @info: A #GDBusInterfaceInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_interface_info_unref (GDBusInterfaceInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->name);
+ free_null_terminated_array (info->methods, (GDestroyNotify) g_dbus_method_info_unref);
+ free_null_terminated_array (info->signals, (GDestroyNotify) g_dbus_signal_info_unref);
+ free_null_terminated_array (info->properties, (GDestroyNotify) g_dbus_property_info_unref);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/**
+ * g_dbus_node_info_unref:
+ * @info: A #GDBusNodeInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_node_info_unref (GDBusNodeInfo *info)
+{
+ if (info->ref_count == -1)
+ return;
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_free (info->path);
+ free_null_terminated_array (info->interfaces, (GDestroyNotify) g_dbus_interface_info_unref);
+ free_null_terminated_array (info->nodes, (GDestroyNotify) g_dbus_node_info_unref);
+ free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+ g_free (info);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_annotation_info_set (ParseData *data,
+ GDBusAnnotationInfo *info,
+ const gchar *key,
+ const gchar *value,
+ GDBusAnnotationInfo **embedded_annotations)
+{
+ info->ref_count = 1;
+
+ if (key != NULL)
+ info->key = g_strdup (key);
+
+ if (value != NULL)
+ info->value = g_strdup (value);
+
+ if (embedded_annotations != NULL)
+ info->annotations = embedded_annotations;
+}
+
+static void
+g_dbus_arg_info_set (ParseData *data,
+ GDBusArgInfo *info,
+ const gchar *name,
+ const gchar *signature,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ /* name may be NULL - TODO: compute name? */
+ if (name != NULL)
+ info->name = g_strdup (name);
+
+ if (signature != NULL)
+ info->signature = g_strdup (signature);
+
+ if (annotations != NULL)
+ info->annotations = annotations;
+}
+
+static void
+g_dbus_method_info_set (ParseData *data,
+ GDBusMethodInfo *info,
+ const gchar *name,
+ guint in_num_args,
+ GDBusArgInfo **in_args,
+ guint out_num_args,
+ GDBusArgInfo **out_args,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ if (name != NULL)
+ info->name = g_strdup (name);
+
+ if (in_num_args != 0)
+ {
+ //info->in_num_args = in_num_args;
+ info->in_args = in_args;
+ }
+
+ if (out_num_args != 0)
+ {
+ //info->out_num_args = out_num_args;
+ info->out_args = out_args;
+ }
+
+ if (annotations != NULL)
+ info->annotations = annotations;
+}
+
+static void
+g_dbus_signal_info_set (ParseData *data,
+ GDBusSignalInfo *info,
+ const gchar *name,
+ guint num_args,
+ GDBusArgInfo **args,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ if (name != NULL)
+ info->name = g_strdup (name);
+
+ if (num_args != 0)
+ {
+ //info->num_args = num_args;
+ info->args = args;
+ }
+
+ if (annotations != NULL)
+ {
+ info->annotations = annotations;
+ }
+}
+
+static void
+g_dbus_property_info_set (ParseData *data,
+ GDBusPropertyInfo *info,
+ const gchar *name,
+ const gchar *signature,
+ GDBusPropertyInfoFlags flags,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ if (name != NULL)
+ info->name = g_strdup (name);
+
+ if (flags != G_DBUS_PROPERTY_INFO_FLAGS_NONE)
+ info->flags = flags;
+
+ if (signature != NULL)
+ {
+ info->signature = g_strdup (signature);
+ }
+
+ if (annotations != NULL)
+ {
+ info->annotations = annotations;
+ }
+}
+
+static void
+g_dbus_interface_info_set (ParseData *data,
+ GDBusInterfaceInfo *info,
+ const gchar *name,
+ guint num_methods,
+ GDBusMethodInfo **methods,
+ guint num_signals,
+ GDBusSignalInfo **signals,
+ guint num_properties,
+ GDBusPropertyInfo **properties,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ if (name != NULL)
+ {
+ info->name = g_strdup (name);
+ }
+
+ if (num_methods != 0)
+ {
+ //info->num_methods = num_methods;
+ info->methods = methods;
+ }
+
+ if (num_signals != 0)
+ {
+ //info->num_signals = num_signals;
+ info->signals = signals;
+ }
+
+ if (num_properties != 0)
+ {
+ //info->num_properties = num_properties;
+ info->properties = properties;
+ }
+
+ if (annotations != NULL)
+ {
+ info->annotations = annotations;
+ }
+}
+
+static void
+g_dbus_node_info_set (ParseData *data,
+ GDBusNodeInfo *info,
+ const gchar *path,
+ guint num_interfaces,
+ GDBusInterfaceInfo **interfaces,
+ guint num_nodes,
+ GDBusNodeInfo **nodes,
+ GDBusAnnotationInfo **annotations)
+{
+ info->ref_count = 1;
+
+ if (path != NULL)
+ {
+ info->path = g_strdup (path);
+ /* TODO: relative / absolute path snafu */
+ }
+
+ if (num_interfaces != 0)
+ {
+ //info->num_interfaces = num_interfaces;
+ info->interfaces = interfaces;
+ }
+
+ if (num_nodes != 0)
+ {
+ //info->num_nodes = num_nodes;
+ info->nodes = nodes;
+ }
+
+ if (annotations != NULL)
+ {
+ info->annotations = annotations;
+ }
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_annotation_info_generate_xml (const GDBusAnnotationInfo *info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<annotation name=\"%s\" value=\"%s\"",
+ indent, "",
+ info->key,
+ info->value);
+
+ if (info->annotations == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</annotation>\n",
+ indent, "");
+ }
+
+}
+
+static void
+g_dbus_arg_info_generate_xml (const GDBusArgInfo *info,
+ guint indent,
+ const gchar *extra_attributes,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<arg type=\"%s\"",
+ indent, "",
+ info->signature);
+
+ if (info->name != NULL)
+ g_string_append_printf (string_builder, " name=\"%s\"", info->name);
+
+ if (extra_attributes != NULL)
+ g_string_append_printf (string_builder, " %s", extra_attributes);
+
+ if (info->annotations == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</arg>\n", indent, "");
+ }
+
+}
+
+static void
+g_dbus_method_info_generate_xml (const GDBusMethodInfo *info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<method name=\"%s\"",
+ indent, "",
+ info->name);
+
+ if (info->annotations == NULL && info->in_args == NULL && info->out_args == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; info->in_args != NULL && info->in_args[n] != NULL; n++)
+ g_dbus_arg_info_generate_xml (info->in_args[n],
+ indent + 2,
+ "direction=\"in\"",
+ string_builder);
+
+ for (n = 0; info->out_args != NULL && info->out_args[n] != NULL; n++)
+ g_dbus_arg_info_generate_xml (info->out_args[n],
+ indent + 2,
+ "direction=\"out\"",
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</method>\n", indent, "");
+ }
+}
+
+static void
+g_dbus_signal_info_generate_xml (const GDBusSignalInfo *info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<signal name=\"%s\"",
+ indent, "",
+ info->name);
+
+ if (info->annotations == NULL && info->args == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; info->args != NULL && info->args[n] != NULL; n++)
+ g_dbus_arg_info_generate_xml (info->args[n],
+ indent + 2,
+ NULL,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</signal>\n", indent, "");
+ }
+}
+
+static void
+g_dbus_property_info_generate_xml (const GDBusPropertyInfo *info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+ const gchar *access_string;
+
+ if ((info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) &&
+ (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+ {
+ access_string = "readwrite";
+ }
+ else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+ {
+ access_string = "read";
+ }
+ else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
+ {
+ access_string = "write";
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ g_string_append_printf (string_builder, "%*s<property type=\"%s\" name=\"%s\" access=\"%s\"",
+ indent, "",
+ info->signature,
+ info->name,
+ access_string);
+
+ if (info->annotations == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</property>\n", indent, "");
+ }
+
+}
+
+/**
+ * g_dbus_interface_info_generate_xml:
+ * @info: A #GDBusNodeInfo
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML
+ * documents at run-time for handling the
+ * <literal>org.freedesktop.DBus.Introspectable.Introspect</literal>
+ * method.
+ */
+void
+g_dbus_interface_info_generate_xml (const GDBusInterfaceInfo *info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<interface name=\"%s\">\n",
+ indent, "",
+ info->name);
+
+ for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++)
+ g_dbus_method_info_generate_xml (info->methods[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++)
+ g_dbus_signal_info_generate_xml (info->signals[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++)
+ g_dbus_property_info_generate_xml (info->properties[n],
+ indent + 2,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</interface>\n", indent, "");
+}
+
+/**
+ * g_dbus_node_info_generate_xml:
+ * @node_info: A #GDBusNodeInfo.
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @node_info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML documents at run-time for
+ * handling the <literal>org.freedesktop.DBus.Introspectable.Introspect</literal> method.
+ */
+void
+g_dbus_node_info_generate_xml (const GDBusNodeInfo *node_info,
+ guint indent,
+ GString *string_builder)
+{
+ guint n;
+
+ g_string_append_printf (string_builder, "%*s<node", indent, "");
+ if (node_info->path != NULL)
+ g_string_append_printf (string_builder, " name=\"%s\"", node_info->path);
+
+ if (node_info->interfaces == NULL && node_info->nodes == NULL && node_info->annotations == NULL)
+ {
+ g_string_append (string_builder, "/>\n");
+ }
+ else
+ {
+ g_string_append (string_builder, ">\n");
+
+ for (n = 0; node_info->annotations != NULL && node_info->annotations[n] != NULL; n++)
+ g_dbus_annotation_info_generate_xml (node_info->annotations[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; node_info->interfaces != NULL && node_info->interfaces[n] != NULL; n++)
+ g_dbus_interface_info_generate_xml (node_info->interfaces[n],
+ indent + 2,
+ string_builder);
+
+ for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++)
+ g_dbus_node_info_generate_xml (node_info->nodes[n],
+ indent + 2,
+ string_builder);
+
+ g_string_append_printf (string_builder, "%*s</node>\n", indent, "");
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo **
+parse_data_steal_annotations (ParseData *data, guint *out_num_elements)
+{
+ GDBusAnnotationInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->annotations->len;
+ if (data->annotations == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->annotations, NULL);
+ ret = (GDBusAnnotationInfo **) g_ptr_array_free (data->annotations, FALSE);
+ }
+ data->annotations = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusArgInfo **
+parse_data_steal_args (ParseData *data, guint *out_num_elements)
+{
+ GDBusArgInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->args->len;
+ if (data->args == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->args, NULL);
+ ret = (GDBusArgInfo **) g_ptr_array_free (data->args, FALSE);
+ }
+ data->args = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusArgInfo **
+parse_data_steal_out_args (ParseData *data, guint *out_num_elements)
+{
+ GDBusArgInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->out_args->len;
+ if (data->out_args == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->out_args, NULL);
+ ret = (GDBusArgInfo **) g_ptr_array_free (data->out_args, FALSE);
+ }
+ data->out_args = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusMethodInfo **
+parse_data_steal_methods (ParseData *data, guint *out_num_elements)
+{
+ GDBusMethodInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->methods->len;
+ if (data->methods == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->methods, NULL);
+ ret = (GDBusMethodInfo **) g_ptr_array_free (data->methods, FALSE);
+ }
+ data->methods = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusSignalInfo **
+parse_data_steal_signals (ParseData *data, guint *out_num_elements)
+{
+ GDBusSignalInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->signals->len;
+ if (data->signals == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->signals, NULL);
+ ret = (GDBusSignalInfo **) g_ptr_array_free (data->signals, FALSE);
+ }
+ data->signals = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusPropertyInfo **
+parse_data_steal_properties (ParseData *data, guint *out_num_elements)
+{
+ GDBusPropertyInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->properties->len;
+ if (data->properties == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->properties, NULL);
+ ret = (GDBusPropertyInfo **) g_ptr_array_free (data->properties, FALSE);
+ }
+ data->properties = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusInterfaceInfo **
+parse_data_steal_interfaces (ParseData *data, guint *out_num_elements)
+{
+ GDBusInterfaceInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->interfaces->len;
+ if (data->interfaces == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->interfaces, NULL);
+ ret = (GDBusInterfaceInfo **) g_ptr_array_free (data->interfaces, FALSE);
+ }
+ data->interfaces = g_ptr_array_new ();
+ return ret;
+}
+
+static GDBusNodeInfo **
+parse_data_steal_nodes (ParseData *data, guint *out_num_elements)
+{
+ GDBusNodeInfo **ret;
+ if (out_num_elements != NULL)
+ *out_num_elements = data->nodes->len;
+ if (data->nodes == NULL)
+ ret = NULL;
+ else
+ {
+ g_ptr_array_add (data->nodes, NULL);
+ ret = (GDBusNodeInfo **) g_ptr_array_free (data->nodes, FALSE);
+ }
+ data->nodes = g_ptr_array_new ();
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parse_data_free_annotations (ParseData *data)
+{
+ if (data->annotations == NULL)
+ return;
+ g_ptr_array_foreach (data->annotations, (GFunc) g_dbus_annotation_info_unref, NULL);
+ g_ptr_array_free (data->annotations, TRUE);
+ data->annotations = NULL;
+}
+
+static void
+parse_data_free_args (ParseData *data)
+{
+ if (data->args == NULL)
+ return;
+ g_ptr_array_foreach (data->args, (GFunc) g_dbus_arg_info_unref, NULL);
+ g_ptr_array_free (data->args, TRUE);
+ data->args = NULL;
+}
+
+static void
+parse_data_free_out_args (ParseData *data)
+{
+ if (data->out_args == NULL)
+ return;
+ g_ptr_array_foreach (data->out_args, (GFunc) g_dbus_arg_info_unref, NULL);
+ g_ptr_array_free (data->out_args, TRUE);
+ data->out_args = NULL;
+}
+
+static void
+parse_data_free_methods (ParseData *data)
+{
+ if (data->methods == NULL)
+ return;
+ g_ptr_array_foreach (data->methods, (GFunc) g_dbus_method_info_unref, NULL);
+ g_ptr_array_free (data->methods, TRUE);
+ data->methods = NULL;
+}
+
+static void
+parse_data_free_signals (ParseData *data)
+{
+ if (data->signals == NULL)
+ return;
+ g_ptr_array_foreach (data->signals, (GFunc) g_dbus_signal_info_unref, NULL);
+ g_ptr_array_free (data->signals, TRUE);
+ data->signals = NULL;
+}
+
+static void
+parse_data_free_properties (ParseData *data)
+{
+ if (data->properties == NULL)
+ return;
+ g_ptr_array_foreach (data->properties, (GFunc) g_dbus_property_info_unref, NULL);
+ g_ptr_array_free (data->properties, TRUE);
+ data->properties = NULL;
+}
+
+static void
+parse_data_free_interfaces (ParseData *data)
+{
+ if (data->interfaces == NULL)
+ return;
+ g_ptr_array_foreach (data->interfaces, (GFunc) g_dbus_interface_info_unref, NULL);
+ g_ptr_array_free (data->interfaces, TRUE);
+ data->interfaces = NULL;
+}
+
+static void
+parse_data_free_nodes (ParseData *data)
+{
+ if (data->nodes == NULL)
+ return;
+ g_ptr_array_foreach (data->nodes, (GFunc) g_dbus_node_info_unref, NULL);
+ g_ptr_array_free (data->nodes, TRUE);
+ data->nodes = NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo *
+parse_data_get_annotation (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->annotations, g_new0 (GDBusAnnotationInfo, 1));
+ return data->annotations->pdata[data->annotations->len - 1];
+}
+
+static GDBusArgInfo *
+parse_data_get_arg (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->args, g_new0 (GDBusArgInfo, 1));
+ return data->args->pdata[data->args->len - 1];
+}
+
+static GDBusArgInfo *
+parse_data_get_out_arg (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->out_args, g_new0 (GDBusArgInfo, 1));
+ return data->out_args->pdata[data->out_args->len - 1];
+}
+
+static GDBusMethodInfo *
+parse_data_get_method (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->methods, g_new0 (GDBusMethodInfo, 1));
+ return data->methods->pdata[data->methods->len - 1];
+}
+
+static GDBusSignalInfo *
+parse_data_get_signal (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->signals, g_new0 (GDBusSignalInfo, 1));
+ return data->signals->pdata[data->signals->len - 1];
+}
+
+static GDBusPropertyInfo *
+parse_data_get_property (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->properties, g_new0 (GDBusPropertyInfo, 1));
+ return data->properties->pdata[data->properties->len - 1];
+}
+
+static GDBusInterfaceInfo *
+parse_data_get_interface (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->interfaces, g_new0 (GDBusInterfaceInfo, 1));
+ return data->interfaces->pdata[data->interfaces->len - 1];
+}
+
+static GDBusNodeInfo *
+parse_data_get_node (ParseData *data, gboolean create_new)
+{
+ if (create_new)
+ g_ptr_array_add (data->nodes, g_new0 (GDBusNodeInfo, 1));
+ return data->nodes->pdata[data->nodes->len - 1];
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static ParseData *
+parse_data_new (void)
+{
+ ParseData *data;
+
+ data = g_new0 (ParseData, 1);
+
+ /* initialize arrays */
+ parse_data_steal_annotations (data, NULL);
+ parse_data_steal_args (data, NULL);
+ parse_data_steal_out_args (data, NULL);
+ parse_data_steal_methods (data, NULL);
+ parse_data_steal_signals (data, NULL);
+ parse_data_steal_properties (data, NULL);
+ parse_data_steal_interfaces (data, NULL);
+ parse_data_steal_nodes (data, NULL);
+
+ return data;
+}
+
+static void
+parse_data_free (ParseData *data)
+{
+ GSList *l;
+
+ /* free stack of annotation arrays */
+ for (l = data->annotations_stack; l != NULL; l = l->next)
+ {
+ GPtrArray *annotations = l->data;
+ g_ptr_array_foreach (annotations, (GFunc) g_dbus_annotation_info_unref, NULL);
+ g_ptr_array_free (annotations, TRUE);
+ }
+ g_slist_free (data->annotations_stack);
+
+ /* free stack of interface arrays */
+ for (l = data->interfaces_stack; l != NULL; l = l->next)
+ {
+ GPtrArray *interfaces = l->data;
+ g_ptr_array_foreach (interfaces, (GFunc) g_dbus_interface_info_unref, NULL);
+ g_ptr_array_free (interfaces, TRUE);
+ }
+ g_slist_free (data->interfaces_stack);
+
+ /* free stack of node arrays */
+ for (l = data->nodes_stack; l != NULL; l = l->next)
+ {
+ GPtrArray *nodes = l->data;
+ g_ptr_array_foreach (nodes, (GFunc) g_dbus_node_info_unref, NULL);
+ g_ptr_array_free (nodes, TRUE);
+ }
+ g_slist_free (data->nodes_stack);
+
+ /* free arrays (data->annotations, data->interfaces and data->nodes have been freed above) */
+ parse_data_free_args (data);
+ parse_data_free_out_args (data);
+ parse_data_free_methods (data);
+ parse_data_free_signals (data);
+ parse_data_free_properties (data);
+
+ g_free (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *data = user_data;
+ GSList *stack;
+ const gchar *name;
+ const gchar *type;
+ const gchar *access;
+ const gchar *direction;
+ const gchar *value;
+
+ name = NULL;
+ type = NULL;
+ access = NULL;
+ direction = NULL;
+ value = NULL;
+
+ stack = (GSList *) g_markup_parse_context_get_element_stack (context);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ if (strcmp (element_name, "node") == 0)
+ {
+ if (!(g_slist_length (stack) >= 1 || strcmp (stack->next->data, "node") != 0))
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<node> elements can only be top-level or embedded in other <node> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+ /* some hand-written introspection XML documents use this */
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "xmlns:doc", NULL,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ g_dbus_node_info_set (data,
+ parse_data_get_node (data, TRUE),
+ name,
+ 0, NULL,
+ 0, NULL,
+ NULL);
+
+ /* push the currently retrieved interfaces and nodes on the stack and prepare new arrays */
+ data->interfaces_stack = g_slist_prepend (data->interfaces_stack, data->interfaces);
+ data->interfaces = NULL;
+ parse_data_steal_interfaces (data, NULL);
+
+ data->nodes_stack = g_slist_prepend (data->nodes_stack, data->nodes);
+ data->nodes = NULL;
+ parse_data_steal_nodes (data, NULL);
+
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "interface") == 0)
+ {
+ if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "node") != 0)
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<interface> elements can only be embedded in <node> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ g_dbus_interface_info_set (data,
+ parse_data_get_interface (data, TRUE),
+ name,
+ 0, NULL,
+ 0, NULL,
+ 0, NULL,
+ NULL);
+
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "method") == 0)
+ {
+ if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<method> elements can only be embedded in <interface> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ g_dbus_method_info_set (data,
+ parse_data_get_method (data, TRUE),
+ name,
+ 0, NULL,
+ 0, NULL,
+ NULL);
+
+ data->num_args = 0;
+
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "signal") == 0)
+ {
+ if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<signal> elements can only be embedded in <interface> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ g_dbus_signal_info_set (data,
+ parse_data_get_signal (data, TRUE),
+ name,
+ 0, NULL,
+ NULL);
+
+ data->num_args = 0;
+
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "property") == 0)
+ {
+ GDBusPropertyInfoFlags flags;
+
+ if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<property> elements can only be embedded in <interface> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_STRING, "type", &type,
+ G_MARKUP_COLLECT_STRING, "access", &access,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ if (strcmp (access, "read") == 0)
+ flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE;
+ else if (strcmp (access, "write") == 0)
+ flags = G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+ else if (strcmp (access, "readwrite") == 0)
+ flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Unknown value '%s' of access attribute for element <property>",
+ access);
+ goto out;
+ }
+
+ g_dbus_property_info_set (data,
+ parse_data_get_property (data, TRUE),
+ name,
+ type,
+ flags,
+ NULL);
+
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "arg") == 0)
+ {
+ gboolean is_in;
+ gchar *name_to_use;
+
+ if (g_slist_length (stack) < 2 ||
+ (strcmp (stack->next->data, "method") != 0 &&
+ strcmp (stack->next->data, "signal") != 0))
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<arg> elements can only be embedded in <method> or <signal> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "direction", &direction,
+ G_MARKUP_COLLECT_STRING, "type", &type,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ is_in = FALSE;
+ if (direction != NULL)
+ {
+ if (strcmp (direction, "in") == 0)
+ is_in = TRUE;
+ else if (strcmp (direction, "out") == 0)
+ is_in = FALSE;
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Unknown value '%s' of direction attribute",
+ direction);
+ goto out;
+ }
+ }
+
+ if (is_in && strcmp (stack->next->data, "signal") == 0)
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Only direction 'out' is allowed for <arg> elements embedded in <signal>");
+ goto out;
+ }
+
+ if (name == NULL)
+ name_to_use = g_strdup_printf ("arg_%d", data->num_args);
+ else
+ name_to_use = g_strdup (name);
+ data->num_args++;
+
+ if (is_in)
+ {
+ g_dbus_arg_info_set (data,
+ parse_data_get_arg (data, TRUE),
+ name_to_use,
+ type,
+ NULL);
+ data->last_arg_was_in = TRUE;
+ }
+ else
+ {
+ g_dbus_arg_info_set (data,
+ parse_data_get_out_arg (data, TRUE),
+ name_to_use,
+ type,
+ NULL);
+ data->last_arg_was_in = FALSE;
+
+ }
+
+ g_free (name_to_use);
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else if (strcmp (element_name, "annotation") == 0)
+ {
+ if (g_slist_length (stack) < 2 ||
+ (strcmp (stack->next->data, "node") != 0 &&
+ strcmp (stack->next->data, "interface") != 0 &&
+ strcmp (stack->next->data, "signal") != 0 &&
+ strcmp (stack->next->data, "method") != 0 &&
+ strcmp (stack->next->data, "property") != 0 &&
+ strcmp (stack->next->data, "arg") != 0 &&
+ strcmp (stack->next->data, "annotation") != 0))
+ {
+ g_set_error_literal (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "<annotation> elements can only be embedded in <node>, <interface>, <signal>, <method>, <property>, <arg> or <annotation> elements");
+ goto out;
+ }
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_STRING, "value", &value,
+ G_MARKUP_COLLECT_INVALID))
+ goto out;
+
+ g_dbus_annotation_info_set (data,
+ parse_data_get_annotation (data, TRUE),
+ name,
+ value,
+ NULL);
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+ else
+ {
+ /* don't bail on unknown elements; just ignore them */
+ }
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ /* push the currently retrieved annotations on the stack and prepare a new one */
+ data->annotations_stack = g_slist_prepend (data->annotations_stack, data->annotations);
+ data->annotations = NULL;
+ parse_data_steal_annotations (data, NULL);
+
+ out:
+ ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo **
+steal_annotations (ParseData *data)
+{
+ return parse_data_steal_annotations (data, NULL);
+}
+
+
+static void
+parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *data = user_data;
+ gboolean have_popped_annotations;
+
+ have_popped_annotations = FALSE;
+
+ if (strcmp (element_name, "node") == 0)
+ {
+ guint num_nodes;
+ guint num_interfaces;
+ GDBusNodeInfo **nodes;
+ GDBusInterfaceInfo **interfaces;
+
+ nodes = parse_data_steal_nodes (data, &num_nodes);
+ interfaces = parse_data_steal_interfaces (data, &num_interfaces);
+
+ /* destroy the nodes, interfaces for scope we're exiting and and pop the nodes, interfaces from the
+ * scope we're reentering
+ */
+ parse_data_free_interfaces (data);
+ data->interfaces = (GPtrArray *) data->interfaces_stack->data;
+ data->interfaces_stack = g_slist_remove (data->interfaces_stack, data->interfaces_stack->data);
+
+ parse_data_free_nodes (data);
+ data->nodes = (GPtrArray *) data->nodes_stack->data;
+ data->nodes_stack = g_slist_remove (data->nodes_stack, data->nodes_stack->data);
+
+ g_dbus_node_info_set (data,
+ parse_data_get_node (data, FALSE),
+ NULL,
+ num_interfaces,
+ interfaces,
+ num_nodes,
+ nodes,
+ steal_annotations (data));
+
+ }
+ else if (strcmp (element_name, "interface") == 0)
+ {
+ guint num_methods;
+ guint num_signals;
+ guint num_properties;
+ GDBusMethodInfo **methods;
+ GDBusSignalInfo **signals;
+ GDBusPropertyInfo **properties;
+
+ methods = parse_data_steal_methods (data, &num_methods);
+ signals = parse_data_steal_signals (data, &num_signals);
+ properties = parse_data_steal_properties (data, &num_properties);
+
+ g_dbus_interface_info_set (data,
+ parse_data_get_interface (data, FALSE),
+ NULL,
+ num_methods,
+ methods,
+ num_signals,
+ signals,
+ num_properties,
+ properties,
+ steal_annotations (data));
+
+ }
+ else if (strcmp (element_name, "method") == 0)
+ {
+ guint in_num_args;
+ guint out_num_args;
+ GDBusArgInfo **in_args;
+ GDBusArgInfo **out_args;
+
+ in_args = parse_data_steal_args (data, &in_num_args);
+ out_args = parse_data_steal_out_args (data, &out_num_args);
+
+ g_dbus_method_info_set (data,
+ parse_data_get_method (data, FALSE),
+ NULL,
+ in_num_args,
+ in_args,
+ out_num_args,
+ out_args,
+ steal_annotations (data));
+ }
+ else if (strcmp (element_name, "signal") == 0)
+ {
+ guint num_args;
+ GDBusArgInfo **args;
+
+ args = parse_data_steal_out_args (data, &num_args);
+
+ g_dbus_signal_info_set (data,
+ parse_data_get_signal (data, FALSE),
+ NULL,
+ num_args,
+ args,
+ steal_annotations (data));
+ }
+ else if (strcmp (element_name, "property") == 0)
+ {
+ g_dbus_property_info_set (data,
+ parse_data_get_property (data, FALSE),
+ NULL,
+ NULL,
+ G_DBUS_PROPERTY_INFO_FLAGS_NONE,
+ steal_annotations (data));
+ }
+ else if (strcmp (element_name, "arg") == 0)
+ {
+ g_dbus_arg_info_set (data,
+ data->last_arg_was_in ? parse_data_get_arg (data, FALSE) : parse_data_get_out_arg (data, FALSE),
+ NULL,
+ NULL,
+ steal_annotations (data));
+ }
+ else if (strcmp (element_name, "annotation") == 0)
+ {
+ GDBusAnnotationInfo **embedded_annotations;
+
+ embedded_annotations = steal_annotations (data);
+
+ /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+ parse_data_free_annotations (data);
+ data->annotations = (GPtrArray *) data->annotations_stack->data;
+ data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+
+ have_popped_annotations = TRUE;
+
+ g_dbus_annotation_info_set (data,
+ parse_data_get_annotation (data, FALSE),
+ NULL,
+ NULL,
+ embedded_annotations);
+ }
+ else
+ {
+ /* don't bail on unknown elements; just ignore them */
+ }
+
+ if (!have_popped_annotations)
+ {
+ /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+ parse_data_free_annotations (data);
+ data->annotations = (GPtrArray *) data->annotations_stack->data;
+ data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ gint line_number;
+ gint char_number;
+
+ g_markup_parse_context_get_position (context, &line_number, &char_number);
+
+ g_prefix_error (&error, "%d:%d: ",
+ line_number,
+ char_number);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_new_for_xml:
+ * @xml_data: Valid D-Bus introspection XML.
+ * @error: Return location for error.
+ *
+ * Parses @xml_data and returns a #GDBusNodeInfo representing the data.
+ *
+ * Returns: A #GDBusNodeInfo structure or %NULL if @error is set. Free
+ * with g_dbus_node_info_unref().
+ */
+GDBusNodeInfo *
+g_dbus_node_info_new_for_xml (const gchar *xml_data,
+ GError **error)
+{
+ GDBusNodeInfo *ret;
+ GMarkupParseContext *context;
+ GMarkupParser *parser;
+ guint num_nodes;
+ ParseData *data;
+
+ ret = NULL;
+ parser = NULL;
+ context = NULL;
+
+ parser = g_new0 (GMarkupParser, 1);
+ parser->start_element = parser_start_element;
+ parser->end_element = parser_end_element;
+ parser->error = parser_error;
+
+ data = parse_data_new ();
+ context = g_markup_parse_context_new (parser,
+ 0,
+ data,
+ (GDestroyNotify) parse_data_free);
+
+ if (!g_markup_parse_context_parse (context,
+ xml_data,
+ strlen (xml_data),
+ error))
+ goto out;
+
+ GDBusNodeInfo **ughret;
+ ughret = parse_data_steal_nodes (data, &num_nodes);
+
+ if (num_nodes != 1)
+ {
+ guint n;
+
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Expected a single node in introspection XML, found %d",
+ num_nodes);
+
+ /* clean up */
+ for (n = 0; n < num_nodes; n++)
+ {
+ for (n = 0; n < num_nodes; n++)
+ g_dbus_node_info_unref (&(ret[n]));
+ }
+ g_free (ret);
+ ret = NULL;
+ }
+
+ ret = ughret[0];
+ g_free (ughret);
+
+ out:
+ if (parser != NULL)
+ g_free (parser);
+ if (context != NULL)
+ g_markup_parse_context_free (context);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_annotation_info_lookup:
+ * @annotations: A %NULL-terminated array of annotations or %NULL.
+ * @name: The name of the annotation to look up.
+ *
+ * Looks up the value of an annotation.
+ *
+ * This cost of this function is O(n) in number of annotations.
+ *
+ * Returns: The value or %NULL if not found. Do not free, it is owned by @annotations.
+ */
+const gchar *
+g_dbus_annotation_info_lookup (const GDBusAnnotationInfo **annotations,
+ const gchar *name)
+{
+ guint n;
+ const gchar *ret;
+
+ ret = NULL;
+ for (n = 0; annotations != NULL && annotations[n]->key != NULL; n++)
+ {
+ if (g_strcmp0 (annotations[n]->key, name) == 0)
+ {
+ ret = annotations[n]->value;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_method:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus method name (typically in CamelCase)
+ *
+ * Looks up information about a method.
+ *
+ * This cost of this function is O(n) in number of methods.
+ *
+ * Returns: A #GDBusMethodInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusMethodInfo *
+g_dbus_interface_info_lookup_method (const GDBusInterfaceInfo *info,
+ const gchar *name)
+{
+ guint n;
+ const GDBusMethodInfo *result;
+
+ for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++)
+ {
+ const GDBusMethodInfo *i = info->methods[n];
+
+ if (g_strcmp0 (i->name, name) == 0)
+ {
+ result = i;
+ goto out;
+ }
+ }
+
+ result = NULL;
+
+ out:
+ return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_signal:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus signal name (typically in CamelCase)
+ *
+ * Looks up information about a signal.
+ *
+ * This cost of this function is O(n) in number of signals.
+ *
+ * Returns: A #GDBusSignalInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusSignalInfo *
+g_dbus_interface_info_lookup_signal (const GDBusInterfaceInfo *info,
+ const gchar *name)
+{
+ guint n;
+ const GDBusSignalInfo *result;
+
+ for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++)
+ {
+ const GDBusSignalInfo *i = info->signals[n];
+
+ if (g_strcmp0 (i->name, name) == 0)
+ {
+ result = i;
+ goto out;
+ }
+ }
+
+ result = NULL;
+
+ out:
+ return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_property:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus property name (typically in CamelCase).
+ *
+ * Looks up information about a property.
+ *
+ * This cost of this function is O(n) in number of properties.
+ *
+ * Returns: A #GDBusPropertyInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusPropertyInfo *
+g_dbus_interface_info_lookup_property (const GDBusInterfaceInfo *info,
+ const gchar *name)
+{
+ guint n;
+ const GDBusPropertyInfo *result;
+
+ for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++)
+ {
+ const GDBusPropertyInfo *i = info->properties[n];
+
+ if (g_strcmp0 (i->name, name) == 0)
+ {
+ result = i;
+ goto out;
+ }
+ }
+
+ result = NULL;
+
+ out:
+ return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_lookup_interface:
+ * @node_info: A #GDBusNodeInfo.
+ * @name: A D-Bus interface name.
+ *
+ * Looks up information about an interface.
+ *
+ * This cost of this function is O(n) in number of interfaces.
+ *
+ * Returns: A #GDBusInterfaceInfo or %NULL if not found. Do not free, it is owned by @node_info.
+ **/
+const GDBusInterfaceInfo *
+g_dbus_node_info_lookup_interface (const GDBusNodeInfo *node_info,
+ const gchar *name)
+{
+ guint n;
+ const GDBusInterfaceInfo *result;
+
+ for (n = 0; node_info->interfaces != NULL && node_info->interfaces[n] != NULL; n++)
+ {
+ const GDBusInterfaceInfo *i = node_info->interfaces[n];
+
+ if (g_strcmp0 (i->name, name) == 0)
+ {
+ result = i;
+ goto out;
+ }
+ }
+
+ result = NULL;
+
+ out:
+ return result;
+}
diff --git a/gio/gdbusintrospection.h b/gio/gdbusintrospection.h
new file mode 100644
index 000000000..7aea8d9cb
--- /dev/null
+++ b/gio/gdbusintrospection.h
@@ -0,0 +1,255 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_INTROSPECTION_H__
+#define __G_DBUS_INTROSPECTION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GDBusAnnotationInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @key: The name of the annotation, e.g. "org.freedesktop.DBus.Deprecated".
+ * @value: The value of the annotation.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about an annotation.
+ */
+struct _GDBusAnnotationInfo
+{
+ volatile gint ref_count;
+ gchar *key;
+ gchar *value;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusArgInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: Name of the argument, e.g. @unix_user_id.
+ * @signature: D-Bus signature of the argument (a single complete type).
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about an argument for a method or a signal.
+ */
+struct _GDBusArgInfo
+{
+ volatile gint ref_count;
+ gchar *name;
+ gchar *signature;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusMethodInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus method, e.g. @RequestName.
+ * @in_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no in arguments.
+ * @out_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no out arguments.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a method on an D-Bus interface.
+ */
+struct _GDBusMethodInfo
+{
+ volatile gint ref_count;
+ gchar *name;
+ GDBusArgInfo **in_args;
+ GDBusArgInfo **out_args;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusSignalInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus signal, e.g. "NameOwnerChanged".
+ * @args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no arguments.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a signal on a D-Bus interface.
+ */
+struct _GDBusSignalInfo
+{
+ volatile gint ref_count;
+ gchar *name;
+ GDBusArgInfo **args;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusPropertyInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus property, e.g. "SupportedFilesystems".
+ * @signature: The D-Bus signature of the property (a single complete type).
+ * @flags: Access control flags for the property.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus property on a D-Bus interface.
+ */
+struct _GDBusPropertyInfo
+{
+ volatile gint ref_count;
+ gchar *name;
+ gchar *signature;
+ GDBusPropertyInfoFlags flags;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusInterfaceInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus interface, e.g. "org.freedesktop.DBus.Properties".
+ * @methods: A pointer to a %NULL-terminated array of pointers to #GDBusMethodInfo structures or %NULL if there are no methods.
+ * @signals: A pointer to a %NULL-terminated array of pointers to #GDBusSignalInfo structures or %NULL if there are no signals.
+ * @properties: A pointer to a %NULL-terminated array of pointers to #GDBusPropertyInfo structures or %NULL if there are no properties.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus interface.
+ */
+struct _GDBusInterfaceInfo
+{
+ volatile gint ref_count;
+ gchar *name;
+ GDBusMethodInfo **methods;
+ GDBusSignalInfo **signals;
+ GDBusPropertyInfo **properties;
+ GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusNodeInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @path: The path of the node or %NULL if omitted. Note that this may be a relative path. See the D-Bus specification for more details.
+ * @interfaces: A pointer to a %NULL-terminated array of pointers to #GDBusInterfaceInfo structures or %NULL if there are no interfaces.
+ * @nodes: A pointer to a %NULL-terminated array of pointers to #GDBusNodeInfo structures or %NULL if there are no nodes.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about nodes in a remote object hierarchy.
+ */
+struct _GDBusNodeInfo
+{
+ volatile gint ref_count;
+ gchar *path;
+ GDBusInterfaceInfo **interfaces;
+ GDBusNodeInfo **nodes;
+ GDBusAnnotationInfo **annotations;
+};
+
+const gchar *g_dbus_annotation_info_lookup (const GDBusAnnotationInfo **annotations,
+ const gchar *name);
+const GDBusMethodInfo *g_dbus_interface_info_lookup_method (const GDBusInterfaceInfo *info,
+ const gchar *name);
+const GDBusSignalInfo *g_dbus_interface_info_lookup_signal (const GDBusInterfaceInfo *info,
+ const gchar *name);
+const GDBusPropertyInfo *g_dbus_interface_info_lookup_property (const GDBusInterfaceInfo *info,
+ const gchar *name);
+void g_dbus_interface_info_generate_xml (const GDBusInterfaceInfo *info,
+ guint indent,
+ GString *string_builder);
+
+GDBusNodeInfo *g_dbus_node_info_new_for_xml (const gchar *xml_data,
+ GError **error);
+const GDBusInterfaceInfo *g_dbus_node_info_lookup_interface (const GDBusNodeInfo *info,
+ const gchar *name);
+void g_dbus_node_info_generate_xml (const GDBusNodeInfo *info,
+ guint indent,
+ GString *string_builder);
+
+GDBusNodeInfo *g_dbus_node_info_ref (GDBusNodeInfo *info);
+GDBusInterfaceInfo *g_dbus_interface_info_ref (GDBusInterfaceInfo *info);
+GDBusMethodInfo *g_dbus_method_info_ref (GDBusMethodInfo *info);
+GDBusSignalInfo *g_dbus_signal_info_ref (GDBusSignalInfo *info);
+GDBusPropertyInfo *g_dbus_property_info_ref (GDBusPropertyInfo *info);
+GDBusArgInfo *g_dbus_arg_info_ref (GDBusArgInfo *info);
+GDBusAnnotationInfo *g_dbus_annotation_info_ref (GDBusAnnotationInfo *info);
+
+void g_dbus_node_info_unref (GDBusNodeInfo *info);
+void g_dbus_interface_info_unref (GDBusInterfaceInfo *info);
+void g_dbus_method_info_unref (GDBusMethodInfo *info);
+void g_dbus_signal_info_unref (GDBusSignalInfo *info);
+void g_dbus_property_info_unref (GDBusPropertyInfo *info);
+void g_dbus_arg_info_unref (GDBusArgInfo *info);
+void g_dbus_annotation_info_unref (GDBusAnnotationInfo *info);
+
+
+/**
+ * G_TYPE_DBUS_NODE_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusNodeInfo.
+ */
+#define G_TYPE_DBUS_NODE_INFO (g_dbus_node_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_INTERFACE_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusInterfaceInfo.
+ */
+#define G_TYPE_DBUS_INTERFACE_INFO (g_dbus_interface_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_METHOD_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusMethodInfo.
+ */
+#define G_TYPE_DBUS_METHOD_INFO (g_dbus_method_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_SIGNAL_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusSignalInfo.
+ */
+#define G_TYPE_DBUS_SIGNAL_INFO (g_dbus_signal_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_PROPERTY_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusPropertyInfo.
+ */
+#define G_TYPE_DBUS_PROPERTY_INFO (g_dbus_property_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_ARG_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusArgInfo.
+ */
+#define G_TYPE_DBUS_ARG_INFO (g_dbus_arg_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_ANNOTATION_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusAnnotationInfo.
+ */
+#define G_TYPE_DBUS_ANNOTATION_INFO (g_dbus_annotation_info_get_type ())
+
+GType g_dbus_node_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_interface_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_method_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_signal_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_property_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_arg_info_get_type (void) G_GNUC_CONST;
+GType g_dbus_annotation_info_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __G_DBUS_INTROSPECTION_H__ */
diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c
new file mode 100644
index 000000000..668b1c856
--- /dev/null
+++ b/gio/gdbusmessage.c
@@ -0,0 +1,2421 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusmessage.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "ginputstream.h"
+#include "gdatainputstream.h"
+#include "gmemoryinputstream.h"
+#include "goutputstream.h"
+#include "gdataoutputstream.h"
+#include "gmemoryoutputstream.h"
+#include "gseekable.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+
+/**
+ * SECTION:gdbusmessage
+ * @short_description: D-Bus Message
+ * @include: gdbus/gdbus.h
+ *
+ * A type for representing D-Bus messages that can be sent or received
+ * on a #GDBusConnection.
+ */
+
+struct _GDBusMessagePrivate
+{
+ GDBusMessageType type;
+ GDBusMessageFlags flags;
+ guchar major_protocol_version;
+ guint32 serial;
+ GHashTable *headers;
+ GVariant *body;
+#ifdef G_OS_UNIX
+ GUnixFDList *fd_list;
+#endif
+};
+
+#define g_dbus_message_get_type g_dbus_message_get_gtype
+G_DEFINE_TYPE (GDBusMessage, g_dbus_message, G_TYPE_OBJECT);
+#undef g_dbus_message_get_type
+
+static void
+g_dbus_message_finalize (GObject *object)
+{
+ GDBusMessage *message = G_DBUS_MESSAGE (object);
+
+ if (message->priv->headers != NULL)
+ g_hash_table_unref (message->priv->headers);
+ if (message->priv->body != NULL)
+ g_variant_unref (message->priv->body);
+#ifdef G_OS_UNIX
+ if (message->priv->fd_list != NULL)
+ g_object_unref (message->priv->fd_list);
+#endif
+
+ if (G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_message_class_init (GDBusMessageClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (klass, sizeof (GDBusMessagePrivate));
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_message_finalize;
+}
+
+static void
+g_dbus_message_init (GDBusMessage *message)
+{
+ message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message, G_TYPE_DBUS_MESSAGE, GDBusMessagePrivate);
+
+ message->priv->headers = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_variant_unref);
+}
+
+/**
+ * g_dbus_message_new:
+ *
+ * Creates a new empty #GDBusMessage.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new (void)
+{
+ return g_object_new (G_TYPE_DBUS_MESSAGE, NULL);
+}
+
+/**
+ * g_dbus_message_new_method_call:
+ * @name: A valid D-Bus name or %NULL.
+ * @path: A valid object path.
+ * @interface: A valid D-Bus interface name or %NULL.
+ * @method: A valid method name.
+ *
+ * Creates a new #GDBusMessage for a method call.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_call (const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ const gchar *method)
+{
+ GDBusMessage *message;
+
+ g_return_val_if_fail (name == NULL || g_dbus_is_name (name), NULL);
+ g_return_val_if_fail (g_variant_is_object_path (path), NULL);
+ g_return_val_if_fail (g_dbus_is_member_name (method), NULL);
+ g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL);
+
+ message = g_dbus_message_new ();
+ message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_CALL;
+
+ if (name != NULL)
+ g_dbus_message_set_destination (message, name);
+ g_dbus_message_set_path (message, path);
+ g_dbus_message_set_member (message, method);
+ if (interface != NULL)
+ g_dbus_message_set_interface (message, interface);
+
+ return message;
+}
+
+/**
+ * g_dbus_message_new_signal:
+ * @path: A valid object path.
+ * @interface: A valid D-Bus interface name or %NULL.
+ * @signal: A valid signal name.
+ *
+ * Creates a new #GDBusMessage for a signal emission.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_signal (const gchar *path,
+ const gchar *interface,
+ const gchar *signal)
+{
+ GDBusMessage *message;
+
+ g_return_val_if_fail (g_variant_is_object_path (path), NULL);
+ g_return_val_if_fail (g_dbus_is_member_name (signal), NULL);
+ g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL);
+
+ message = g_dbus_message_new ();
+ message->priv->type = G_DBUS_MESSAGE_TYPE_SIGNAL;
+ message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+ g_dbus_message_set_path (message, path);
+ g_dbus_message_set_member (message, signal);
+
+ if (interface != NULL)
+ g_dbus_message_set_interface (message, interface);
+
+ return message;
+}
+
+
+/**
+ * g_dbus_message_new_method_reply:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ *
+ * Creates a new #GDBusMessage that is a reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_reply (GDBusMessage *method_call_message)
+{
+ GDBusMessage *message;
+ const gchar *sender;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL);
+ g_return_val_if_fail (g_dbus_message_get_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL);
+ g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL);
+
+ message = g_dbus_message_new ();
+ message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN;
+ message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+ g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message));
+ sender = g_dbus_message_get_sender (method_call_message);
+ if (sender != NULL)
+ g_dbus_message_set_destination (message, sender);
+
+ return message;
+}
+
+/**
+ * g_dbus_message_new_method_error:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message_format: The D-Bus error message in a printf() format.
+ * @...: Arguments for @error_message_format.
+ *
+ * Creates a new #GDBusMessage that is an error reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message_format,
+ ...)
+{
+ GDBusMessage *ret;
+ va_list var_args;
+
+ va_start (var_args, error_message_format);
+ ret = g_dbus_message_new_method_error_valist (method_call_message,
+ error_name,
+ error_message_format,
+ var_args);
+ va_end (var_args);
+
+ return ret;
+}
+
+/**
+ * g_dbus_message_new_method_error_literal:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message: The D-Bus error message.
+ *
+ * Creates a new #GDBusMessage that is an error reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message)
+{
+ GDBusMessage *message;
+ const gchar *sender;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL);
+ g_return_val_if_fail (g_dbus_message_get_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL);
+ g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL);
+ g_return_val_if_fail (g_dbus_is_name (error_name), NULL);
+ g_return_val_if_fail (error_message != NULL, NULL);
+
+ message = g_dbus_message_new ();
+ message->priv->type = G_DBUS_MESSAGE_TYPE_ERROR;
+ message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+ g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message));
+ g_dbus_message_set_error_name (message, error_name);
+ g_dbus_message_set_body (message, g_variant_new ("(s)", error_message));
+
+ sender = g_dbus_message_get_sender (method_call_message);
+ if (sender != NULL)
+ g_dbus_message_set_destination (message, sender);
+
+ return message;
+}
+
+/**
+ * g_dbus_message_new_method_error_valist:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message_format: The D-Bus error message in a printf() format.
+ * @var_args: Arguments for @error_message_format.
+ *
+ * Like g_dbus_message_new_method_error() but intended for language bindings.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message_format,
+ va_list var_args)
+{
+ GDBusMessage *ret;
+ gchar *error_message;
+ error_message = g_strdup_vprintf (error_message_format, var_args);
+ ret = g_dbus_message_new_method_error_literal (method_call_message,
+ error_name,
+ error_message);
+ g_free (error_message);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for the type */
+
+/**
+ * g_dbus_message_get_type:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the type of @message.
+ *
+ * Returns: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration).
+ */
+GDBusMessageType
+g_dbus_message_get_type (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_TYPE_INVALID);
+ return message->priv->type;
+}
+
+/**
+ * g_dbus_message_set_type:
+ * @message: A #GDBusMessage.
+ * @type: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration).
+ *
+ * Sets @message to be of @type.
+ */
+void
+g_dbus_message_set_type (GDBusMessage *message,
+ GDBusMessageType type)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (type >=0 && type < 256);
+ message->priv->type = type;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for flags */
+
+/**
+ * g_dbus_message_get_flags:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the flags for @message.
+ *
+ * Returns: Flags that are set (typically values from the #GDBusMessageFlags enumeration bitwise ORed together).
+ */
+GDBusMessageFlags
+g_dbus_message_get_flags (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_FLAGS_NONE);
+ return message->priv->flags;
+}
+
+/**
+ * g_dbus_message_set_flags:
+ * @message: A #GDBusMessage.
+ * @flags: Flags for @message that are set (typically values from the #GDBusMessageFlags
+ * enumeration bitwise ORed together).
+ *
+ * Sets the flags to set on @message.
+ */
+void
+g_dbus_message_set_flags (GDBusMessage *message,
+ GDBusMessageFlags flags)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (flags >=0 && flags < 256);
+ message->priv->flags = flags;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_serial:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the serial for @message.
+ *
+ * Returns: A #guint32.
+ */
+guint32
+g_dbus_message_get_serial (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+ return message->priv->serial;
+}
+
+/**
+ * g_dbus_message_set_serial:
+ * @message: A #GDBusMessage.
+ * @serial: A #guint32.
+ *
+ * Sets the serial for @message.
+ */
+void
+g_dbus_message_set_serial (GDBusMessage *message,
+ guint32 serial)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ message->priv->serial = serial;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for header_field */
+
+/**
+ * g_dbus_message_get_header:
+ * @message: A #GDBusMessage.
+ * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration)
+ *
+ * Gets a header field on @message.
+ *
+ * Returns: A #GVariant with the value if the header was found, %NULL
+ * otherwise. Do not free, it is owned by @message.
+ */
+GVariant *
+g_dbus_message_get_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ g_return_val_if_fail (header_field >=0 && header_field < 256, NULL);
+ return g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+}
+
+/**
+ * g_dbus_message_set_header:
+ * @message: A #GDBusMessage.
+ * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration)
+ * @value: A #GVariant to set the header field or %NULL to clear the header field.
+ *
+ * Sets a header field on @message.
+ *
+ * If @value is floating, @message assumes ownership of @value.
+ */
+void
+g_dbus_message_set_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ GVariant *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (header_field >=0 && header_field < 256);
+ if (value == NULL)
+ {
+ g_hash_table_remove (message->priv->headers, GUINT_TO_POINTER (header_field));
+ }
+ else
+ {
+ g_hash_table_insert (message->priv->headers, GUINT_TO_POINTER (header_field), g_variant_ref_sink (value));
+ }
+}
+
+/**
+ * g_dbus_message_get_header_fields:
+ * @message: A #GDBusMessage.
+ *
+ * Gets an array of all header fields on @message that are set.
+ *
+ * Returns: An array of header fields terminated by
+ * %G_DBUS_MESSAGE_HEADER_FIELD_INVALID. Each element is a
+ * #guchar. Free with g_free().
+ */
+guchar *
+g_dbus_message_get_header_fields (GDBusMessage *message)
+{
+ GList *keys;
+ guchar *ret;
+ guint num_keys;
+ GList *l;
+ guint n;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+ keys = g_hash_table_get_keys (message->priv->headers);
+ num_keys = g_list_length (keys);
+ ret = g_new (guchar, num_keys + 1);
+ for (l = keys, n = 0; l != NULL; l = l->next, n++)
+ ret[n] = GPOINTER_TO_UINT (l->data);
+ g_assert (n == num_keys);
+ ret[n] = G_DBUS_MESSAGE_HEADER_FIELD_INVALID;
+ g_list_free (keys);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_body:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the body of a message.
+ *
+ * Returns: A #GVariant or %NULL if the body is empty. Do not free, it is owned by @message.
+ */
+GVariant *
+g_dbus_message_get_body (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return message->priv->body;
+}
+
+/**
+ * g_dbus_message_set_body:
+ * @message: A #GDBusMessage.
+ * @body: Either %NULL or a #GVariant that is a tuple.
+ *
+ * Sets the body @message. As a side-effect the
+ * %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field is set to the
+ * type string of @body (or cleared if @body is %NULL).
+ *
+ * If @body is floating, @message assumes ownership of @body.
+ */
+void
+g_dbus_message_set_body (GDBusMessage *message,
+ GVariant *body)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail ((body == NULL) || g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE));
+
+ if (message->priv->body != NULL)
+ g_variant_unref (message->priv->body);
+ if (body == NULL)
+ {
+ message->priv->body = NULL;
+ g_dbus_message_set_signature (message, NULL);
+ }
+ else
+ {
+ const gchar *type_string;
+ gsize type_string_len;
+ gchar *signature;
+
+ message->priv->body = g_variant_ref_sink (body);
+
+ type_string = g_variant_get_type_string (body);
+ type_string_len = strlen (type_string);
+ g_assert (type_string_len >= 2);
+ signature = g_strndup (type_string + 1, type_string_len - 2);
+ g_dbus_message_set_signature (message, signature);
+ g_free (signature);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+/**
+ * g_dbus_message_get_unix_fd_list:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the UNIX file descriptors associated with @message, if any.
+ *
+ * This method is only available on UNIX.
+ *
+ * Returns: A #GUnixFDList or %NULL if no file descriptors are
+ * associated. Do not free, this object is owned by @message.
+ */
+GUnixFDList *
+g_dbus_message_get_unix_fd_list (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return message->priv->fd_list;
+}
+
+/**
+ * g_dbus_message_set_unix_fd_list:
+ * @message: A #GDBusMessage.
+ * @fd_list: A #GUnixFDList or %NULL.
+ *
+ * Sets the UNIX file descriptors associated with @message. As a
+ * side-effect the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header
+ * field is set to the number of fds in @fd_list (or cleared if
+ * @fd_list is %NULL).
+ *
+ * This method is only available on UNIX.
+ */
+void
+g_dbus_message_set_unix_fd_list (GDBusMessage *message,
+ GUnixFDList *fd_list)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list));
+ if (message->priv->fd_list != NULL)
+ g_object_unref (message->priv->fd_list);
+ if (fd_list != NULL)
+ {
+ message->priv->fd_list = g_object_ref (fd_list);
+ g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list));
+ }
+ else
+ {
+ message->priv->fd_list = NULL;
+ g_dbus_message_set_num_unix_fds (message, 0);
+ }
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_input_padding (GMemoryInputStream *mis,
+ gsize padding_size,
+ GError **error)
+{
+ gsize offset;
+ gsize wanted_offset;
+
+ offset = g_seekable_tell (G_SEEKABLE (mis));
+ wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size;
+
+ return g_seekable_seek (G_SEEKABLE (mis), wanted_offset, G_SEEK_SET, NULL, error);
+}
+
+static gchar *
+read_string (GMemoryInputStream *mis,
+ GDataInputStream *dis,
+ gsize len,
+ GError **error)
+{
+ GString *s;
+ gchar buf[256];
+ gsize remaining;
+ guchar nul;
+ GError *local_error;
+
+ s = g_string_new (NULL);
+
+ remaining = len;
+ while (remaining > 0)
+ {
+ gsize to_read;
+ gssize num_read;
+
+ to_read = MIN (remaining, sizeof (buf));
+ num_read = g_input_stream_read (G_INPUT_STREAM (mis),
+ buf,
+ to_read,
+ NULL,
+ error);
+ if (num_read < 0)
+ goto fail;
+ if (num_read == 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Wanted to read %" G_GSIZE_FORMAT " bytes but got EOF"),
+ to_read);
+ goto fail;
+ }
+
+ remaining -= num_read;
+ g_string_append_len (s, buf, num_read);
+ }
+
+ local_error = NULL;
+ nul = g_data_input_stream_read_byte (dis, NULL, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto fail;
+ }
+ if (nul != '\0')
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Expected NUL byte after the string `%s' but found `%c' (%d)"),
+ s->str, nul, nul);
+ goto fail;
+ }
+
+ return g_string_free (s, FALSE);
+
+ fail:
+ g_string_free (s, TRUE);
+ return NULL;
+}
+
+static GVariant *
+parse_value_from_blob (GMemoryInputStream *mis,
+ GDataInputStream *dis,
+ const GVariantType *type,
+ GError **error)
+{
+ GVariant *ret;
+ GError *local_error;
+
+ local_error = NULL;
+ if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+ {
+ gboolean v;
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_boolean (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+ {
+ guchar v;
+ v = g_data_input_stream_read_byte (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_byte (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+ {
+ gint16 v;
+ if (!ensure_input_padding (mis, 2, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_int16 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_int16 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+ {
+ guint16 v;
+ if (!ensure_input_padding (mis, 2, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_uint16 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_uint16 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+ {
+ gint32 v;
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_int32 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_int32 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+ {
+ guint32 v;
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_uint32 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+ {
+ gint64 v;
+ if (!ensure_input_padding (mis, 8, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_int64 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_int64 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+ {
+ guint64 v;
+ if (!ensure_input_padding (mis, 8, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_uint64 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ ret = g_variant_new_uint64 (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+ {
+ guint64 v;
+ gdouble *encoded;
+ if (!ensure_input_padding (mis, 8, &local_error))
+ goto fail;
+ v = g_data_input_stream_read_uint64 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ /* TODO: hmm */
+ encoded = (gdouble *) &v;
+ ret = g_variant_new_double (*encoded);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+ {
+ guint32 len;
+ gchar *v;
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ v = read_string (mis, dis, (gsize) len, &local_error);
+ if (v == NULL)
+ goto fail;
+ ret = g_variant_new_string (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+ {
+ guint32 len;
+ gchar *v;
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ v = read_string (mis, dis, (gsize) len, &local_error);
+ if (v == NULL)
+ goto fail;
+ if (!g_variant_is_object_path (v))
+ {
+ g_set_error (&local_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Parsed value `%s' is not a valid D-Bus object path"),
+ v);
+ goto fail;
+ }
+ ret = g_variant_new_object_path (v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+ {
+ guchar len;
+ gchar *v;
+ len = g_data_input_stream_read_byte (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ v = read_string (mis, dis, (gsize) len, &local_error);
+ if (v == NULL)
+ goto fail;
+ if (!g_variant_is_signature (v))
+ {
+ g_set_error (&local_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Parsed value `%s' is not a valid D-Bus signature"),
+ v);
+ goto fail;
+ }
+ ret = g_variant_new_signature (v);
+ }
+ else if (g_variant_type_is_array (type))
+ {
+ guint32 array_len;
+ goffset offset;
+ goffset target;
+ const GVariantType *element_type;
+ GVariantBuilder *builder;
+
+ if (!ensure_input_padding (mis, 4, &local_error))
+ goto fail;
+ array_len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+
+ if (array_len > (2<<26))
+ {
+ g_set_error (&local_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Encountered array of length %" G_GUINT32_FORMAT " bytes. Maximum length is 2<<26 bytes."),
+ array_len);
+ goto fail;
+ }
+
+ builder = g_variant_builder_new (type);
+ element_type = g_variant_type_element (type);
+
+ /* TODO: optimize array of primitive types */
+
+ offset = g_seekable_tell (G_SEEKABLE (mis));
+ target = offset + array_len;
+ while (offset < target)
+ {
+ GVariant *item;
+ item = parse_value_from_blob (mis, dis, element_type, &local_error);
+ if (item == NULL)
+ {
+ g_variant_builder_unref (builder);
+ goto fail;
+ }
+ g_variant_builder_add_value (builder, item);
+ offset = g_seekable_tell (G_SEEKABLE (mis));
+ }
+
+ ret = g_variant_builder_end (builder);
+ }
+ else if (g_variant_type_is_dict_entry (type))
+ {
+ const GVariantType *key_type;
+ const GVariantType *value_type;
+ GVariant *key;
+ GVariant *value;
+
+ if (!ensure_input_padding (mis, 8, &local_error))
+ goto fail;
+
+ key_type = g_variant_type_key (type);
+ key = parse_value_from_blob (mis, dis, key_type, &local_error);
+ if (key == NULL)
+ goto fail;
+
+ value_type = g_variant_type_value (type);
+ value = parse_value_from_blob (mis, dis, value_type, &local_error);
+ if (value == NULL)
+ {
+ g_variant_unref (key);
+ goto fail;
+ }
+ ret = g_variant_new_dict_entry (key, value);
+ }
+ else if (g_variant_type_is_tuple (type))
+ {
+ const GVariantType *element_type;
+ GVariantBuilder *builder;
+
+ if (!ensure_input_padding (mis, 8, &local_error))
+ goto fail;
+
+ builder = g_variant_builder_new (type);
+ element_type = g_variant_type_first (type);
+ while (element_type != NULL)
+ {
+ GVariant *item;
+ item = parse_value_from_blob (mis, dis, element_type, &local_error);
+ if (item == NULL)
+ {
+ g_variant_builder_unref (builder);
+ goto fail;
+ }
+ g_variant_builder_add_value (builder, item);
+
+ element_type = g_variant_type_next (element_type);
+ }
+ ret = g_variant_builder_end (builder);
+ }
+ else if (g_variant_type_is_variant (type))
+ {
+ guchar siglen;
+ gchar *sig;
+ GVariantType *variant_type;
+ GVariant *value;
+
+ siglen = g_data_input_stream_read_byte (dis, NULL, &local_error);
+ if (local_error != NULL)
+ goto fail;
+ sig = read_string (mis, dis, (gsize) siglen, &local_error);
+ if (sig == NULL)
+ goto fail;
+ if (!g_variant_is_signature (sig))
+ {
+ g_set_error (&local_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Parsed value `%s' for variant is not a valid D-Bus signature"),
+ sig);
+ goto fail;
+ }
+ variant_type = g_variant_type_new (sig);
+ value = parse_value_from_blob (mis, dis, variant_type, &local_error);
+ g_variant_type_free (variant_type);
+ if (value == NULL)
+ goto fail;
+ ret = g_variant_new_variant (value);
+ }
+ else
+ {
+ gchar *s;
+ s = g_variant_type_dup_string (type);
+ g_set_error (&local_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error deserializing GVariant with type-string `%s' from the D-Bus wire format"),
+ s);
+ g_free (s);
+ goto fail;
+ }
+
+ g_assert (ret != NULL);
+ return ret;
+
+ fail:
+ g_propagate_error (error, local_error);
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* message_header must be at least 16 bytes */
+
+/**
+ * g_dbus_message_bytes_needed:
+ * @blob: A blob represent a binary D-Bus message.
+ * @blob_len: The length of @blob (must be at least 16).
+ * @error: Return location for error or %NULL.
+ *
+ * Utility function to calculate how many bytes are needed to
+ * completely deserialize the D-Bus message stored at @blob.
+ *
+ * Returns: Number of bytes needed or -1 if @error is set (e.g. if
+ * @blob contains invalid data or not enough data is available to
+ * determine the size).
+ */
+gssize
+g_dbus_message_bytes_needed (guchar *blob,
+ gsize blob_len,
+ GError **error)
+{
+ gssize ret;
+
+ ret = -1;
+
+ g_return_val_if_fail (blob != NULL, -1);
+ g_return_val_if_fail (error == NULL || *error == NULL, -1);
+ g_return_val_if_fail (blob_len >= 16, -1);
+
+ if (blob[0] == 'l')
+ {
+ /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */
+ ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]);
+ /* round up so it's a multiple of 8 */
+ ret = 8 * ((ret + 7)/8);
+ /* finally add the body size */
+ ret += GUINT32_FROM_LE (((guint32 *) blob)[1]);
+ }
+ else if (blob[0] == 'B')
+ {
+ /* TODO */
+ g_assert_not_reached ();
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Unable to determine message blob length - given blob is malformed");
+ }
+
+ if (ret > (2<<27))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Blob indicates that message exceeds maximum message length (128MiB)");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_new_from_blob:
+ * @blob: A blob represent a binary D-Bus message.
+ * @blob_len: The length of @blob.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a new #GDBusMessage from the data stored at @blob.
+ *
+ * Returns: A new #GDBusMessage or %NULL if @error is set. Free with
+ * g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_from_blob (guchar *blob,
+ gsize blob_len,
+ GError **error)
+{
+ gboolean ret;
+ GMemoryInputStream *mis;
+ GDataInputStream *dis;
+ GDBusMessage *message;
+ guchar endianness;
+ guchar major_protocol_version;
+ GDataStreamByteOrder byte_order;
+ guint32 message_body_len;
+ GVariant *headers;
+ GVariant *item;
+ GVariantIter iter;
+ GVariant *signature;
+
+ ret = FALSE;
+
+ g_return_val_if_fail (blob != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ g_return_val_if_fail (blob_len >= 12, NULL);
+
+ message = g_dbus_message_new ();
+
+ mis = G_MEMORY_INPUT_STREAM (g_memory_input_stream_new_from_data (blob, blob_len, NULL));
+ dis = g_data_input_stream_new (G_INPUT_STREAM (mis));
+
+ endianness = g_data_input_stream_read_byte (dis, NULL, NULL);
+ switch (endianness)
+ {
+ case 'l':
+ byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN;
+ break;
+ case 'B':
+ byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN;
+ break;
+ default:
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Invalid endianness value. Expected 'l' or 'B' but found '%c' (%d)"),
+ endianness, endianness);
+ goto out;
+ }
+ g_data_input_stream_set_byte_order (dis, byte_order);
+
+ message->priv->type = g_data_input_stream_read_byte (dis, NULL, NULL);
+ message->priv->flags = g_data_input_stream_read_byte (dis, NULL, NULL);
+ major_protocol_version = g_data_input_stream_read_byte (dis, NULL, NULL);
+ if (major_protocol_version != 1)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Invalid major protocol version. Expected 1 but found %d"),
+ major_protocol_version);
+ goto out;
+ }
+ message_body_len = g_data_input_stream_read_uint32 (dis, NULL, NULL);
+ message->priv->serial = g_data_input_stream_read_uint32 (dis, NULL, NULL);
+
+ headers = parse_value_from_blob (mis,
+ dis,
+ G_VARIANT_TYPE ("a{yv}"),
+ error);
+ if (headers == NULL)
+ goto out;
+ g_variant_ref_sink (headers);
+ g_variant_iter_init (&iter, headers);
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ guchar header_field;
+ GVariant *value;
+ g_variant_get (item,
+ "{yv}",
+ &header_field,
+ &value);
+ g_dbus_message_set_header (message, header_field, value);
+ }
+ g_variant_unref (headers);
+
+ signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+ if (signature != NULL)
+ {
+ const gchar *signature_str;
+ gsize signature_str_len;
+
+ signature_str = g_variant_get_string (signature, NULL);
+ signature_str_len = strlen (signature_str);
+
+ /* signature but no body */
+ if (message_body_len == 0 && signature_str_len > 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Signature header with signature `%s' found but message body is empty"),
+ signature_str);
+ goto out;
+ }
+ else if (signature_str_len > 0)
+ {
+ GVariantType *variant_type;
+ gchar *tupled_signature_str;
+
+ if (!g_variant_is_signature (signature_str))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Parsed value `%s' is not a valid D-Bus signature (for body)"),
+ signature_str);
+ goto out;
+ }
+ tupled_signature_str = g_strdup_printf ("(%s)", signature_str);
+ variant_type = g_variant_type_new (tupled_signature_str);
+ g_free (tupled_signature_str);
+ message->priv->body = parse_value_from_blob (mis,
+ dis,
+ variant_type,
+ error);
+ if (message->priv->body == NULL)
+ {
+ g_variant_type_free (variant_type);
+ goto out;
+ }
+ g_variant_ref_sink (message->priv->body);
+ g_variant_type_free (variant_type);
+ }
+ }
+ else
+ {
+ /* no signature, this is only OK if the body is empty */
+ if (message_body_len != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("No signature header in message but the message body is %" G_GUINT32_FORMAT " bytes"),
+ message_body_len);
+ goto out;
+ }
+ }
+
+
+ ret = TRUE;
+
+ out:
+ g_object_unref (dis);
+ g_object_unref (mis);
+
+ if (ret)
+ {
+ return message;
+ }
+ else
+ {
+ if (message != NULL)
+ g_object_unref (message);
+ return NULL;
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gsize
+ensure_output_padding (GMemoryOutputStream *mos,
+ GDataOutputStream *dos,
+ gsize padding_size)
+{
+ gsize offset;
+ gsize wanted_offset;
+ gsize padding_needed;
+ guint n;
+
+ offset = g_memory_output_stream_get_data_size (mos);
+ wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size;
+ padding_needed = wanted_offset - offset;
+
+ for (n = 0; n < padding_needed; n++)
+ g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+
+ return padding_needed;
+}
+
+static gboolean
+append_value_to_blob (GVariant *value,
+ GMemoryOutputStream *mos,
+ GDataOutputStream *dos,
+ gsize *out_padding_added,
+ GError **error)
+{
+ const GVariantType *type;
+ gsize padding_added;
+
+ padding_added = 0;
+
+ type = g_variant_get_type (value);
+ if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+ {
+ gboolean v = g_variant_get_boolean (value);
+ padding_added = ensure_output_padding (mos, dos, 4);
+ g_data_output_stream_put_uint32 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+ {
+ guint8 v = g_variant_get_byte (value);
+ g_data_output_stream_put_byte (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+ {
+ gint16 v = g_variant_get_int16 (value);
+ padding_added = ensure_output_padding (mos, dos, 2);
+ g_data_output_stream_put_int16 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+ {
+ guint16 v = g_variant_get_uint16 (value);
+ padding_added = ensure_output_padding (mos, dos, 2);
+ g_data_output_stream_put_uint16 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+ {
+ gint32 v = g_variant_get_int32 (value);
+ padding_added = ensure_output_padding (mos, dos, 4);
+ g_data_output_stream_put_int32 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+ {
+ guint32 v = g_variant_get_uint32 (value);
+ padding_added = ensure_output_padding (mos, dos, 4);
+ g_data_output_stream_put_uint32 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+ {
+ gint64 v = g_variant_get_int64 (value);
+ padding_added = ensure_output_padding (mos, dos, 8);
+ g_data_output_stream_put_int64 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+ {
+ guint64 v = g_variant_get_uint64 (value);
+ padding_added = ensure_output_padding (mos, dos, 8);
+ g_data_output_stream_put_uint64 (dos, v, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+ {
+ guint64 *encoded;
+ gdouble v = g_variant_get_double (value);
+ padding_added = ensure_output_padding (mos, dos, 8);
+ /* TODO: hmm */
+ encoded = (guint64 *) &v;
+ g_data_output_stream_put_uint64 (dos, *encoded, NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+ {
+ const gchar *v = g_variant_get_string (value, NULL);
+ gsize len;
+ padding_added = ensure_output_padding (mos, dos, 4);
+ len = strlen (v);
+ g_data_output_stream_put_uint32 (dos, len, NULL, NULL);
+ g_data_output_stream_put_string (dos, v, NULL, NULL);
+ g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+ {
+ /* TODO: validate object path */
+ const gchar *v = g_variant_get_string (value, NULL);
+ gsize len;
+ padding_added = ensure_output_padding (mos, dos, 4);
+ len = strlen (v);
+ g_data_output_stream_put_uint32 (dos, len, NULL, NULL);
+ g_data_output_stream_put_string (dos, v, NULL, NULL);
+ g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+ {
+ /* TODO: validate signature (including max len being 255) */
+ const gchar *v = g_variant_get_string (value, NULL);
+ gsize len;
+ len = strlen (v);
+ g_data_output_stream_put_byte (dos, len, NULL, NULL);
+ g_data_output_stream_put_string (dos, v, NULL, NULL);
+ g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+ }
+ else if (g_variant_type_is_array (type))
+ {
+ GVariant *item;
+ GVariantIter iter;
+ goffset array_len_offset;
+ goffset array_payload_begin_offset;
+ goffset cur_offset;
+ gsize array_len;
+ guint n;
+
+ padding_added = ensure_output_padding (mos, dos, 4);
+
+ /* array length - will be filled in later */
+ array_len_offset = g_memory_output_stream_get_data_size (mos);
+ g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL);
+
+ /* From the D-Bus spec:
+ *
+ * "A UINT32 giving the length of the array data in bytes,
+ * followed by alignment padding to the alignment boundary of
+ * the array element type, followed by each array element. The
+ * array length is from the end of the alignment padding to
+ * the end of the last element, i.e. it does not include the
+ * padding after the length, or any padding after the last
+ * element."
+ *
+ * Thus, we need to count how much padding the first element
+ * contributes and subtract that from the array length.
+ */
+ array_payload_begin_offset = g_memory_output_stream_get_data_size (mos);
+
+ g_variant_iter_init (&iter, value);
+ n = 0;
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ gsize padding_added_for_item;
+ if (!append_value_to_blob (item, mos, dos, &padding_added_for_item, error))
+ goto fail;
+ if (n == 0)
+ {
+ array_payload_begin_offset += padding_added_for_item;
+ }
+ n++;
+ }
+
+ cur_offset = g_memory_output_stream_get_data_size (mos);
+
+ array_len = cur_offset - array_payload_begin_offset;
+
+ if (!g_seekable_seek (G_SEEKABLE (mos), array_len_offset, G_SEEK_SET, NULL, error))
+ goto fail;
+
+ g_data_output_stream_put_uint32 (dos, array_len, NULL, NULL);
+
+ if (!g_seekable_seek (G_SEEKABLE (mos), cur_offset, G_SEEK_SET, NULL, error))
+ goto fail;
+ }
+ else if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type))
+ {
+ GVariant *item;
+ GVariantIter iter;
+
+ padding_added = ensure_output_padding (mos, dos, 8);
+
+ g_variant_iter_init (&iter, value);
+
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ if (!append_value_to_blob (item, mos, dos, NULL, error))
+ goto fail;
+ }
+ }
+ else if (g_variant_type_is_variant (type))
+ {
+ GVariant *child;
+ const gchar *signature;
+ child = g_variant_get_child_value (value, 0);
+ signature = g_variant_get_type_string (child);
+ /* TODO: validate signature (including max len being 255) */
+ g_data_output_stream_put_byte (dos, strlen (signature), NULL, NULL);
+ g_data_output_stream_put_string (dos, signature, NULL, NULL);
+ g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+ if (!append_value_to_blob (child, mos, dos, NULL, error))
+ {
+ g_variant_unref (child);
+ goto fail;
+ }
+ g_variant_unref (child);
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Error serializing GVariant with type-string `%s' to the D-Bus wire format"),
+ g_variant_get_type_string (value));
+ goto fail;
+ }
+
+ if (out_padding_added != NULL)
+ *out_padding_added = padding_added;
+
+ return TRUE;
+
+ fail:
+ return FALSE;
+}
+
+static gboolean
+append_body_to_blob (GVariant *value,
+ GMemoryOutputStream *mos,
+ GDataOutputStream *dos,
+ GError **error)
+{
+ gboolean ret;
+ GVariant *item;
+ GVariantIter iter;
+
+ ret = FALSE;
+
+ if (!g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Expected a tuple for the body of the GDBusMessage.");
+ goto fail;
+ }
+
+ g_variant_iter_init (&iter, value);
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ if (!append_value_to_blob (item, mos, dos, NULL, error))
+ goto fail;
+ }
+ return TRUE;
+
+ fail:
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_to_blob:
+ * @message: A #GDBusMessage.
+ * @out_size: Return location for size of generated blob.
+ * @error: Return location for error.
+ *
+ * Serializes @message to a blob.
+ *
+ * Returns: A pointer to a valid binary D-Bus message of @out_size bytes
+ * generated by @message or %NULL if @error is set. Free with g_free().
+ */
+guchar *
+g_dbus_message_to_blob (GDBusMessage *message,
+ gsize *out_size,
+ GError **error)
+{
+ GMemoryOutputStream *mos;
+ GDataOutputStream *dos;
+ guchar *ret;
+ gsize size;
+ GDataStreamByteOrder byte_order;
+ goffset body_len_offset;
+ goffset body_start_offset;
+ gsize body_size;
+ GVariant *header_fields;
+ GVariantBuilder *builder;
+ GHashTableIter hash_iter;
+ gpointer key;
+ GVariant *header_value;
+ GVariant *signature;
+ const gchar *signature_str;
+ gint num_fds_in_message;
+ gint num_fds_according_to_header;
+
+ ret = NULL;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ g_return_val_if_fail (out_size != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ mos = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, g_free));
+ dos = g_data_output_stream_new (G_OUTPUT_STREAM (mos));
+
+ /* TODO: detect endianess... */
+ byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN;
+ g_data_output_stream_set_byte_order (dos, byte_order);
+
+ /* Core header */
+ g_data_output_stream_put_byte (dos, byte_order == G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN ? 'l' : 'B', NULL, NULL);
+ g_data_output_stream_put_byte (dos, message->priv->type, NULL, NULL);
+ g_data_output_stream_put_byte (dos, message->priv->flags, NULL, NULL);
+ g_data_output_stream_put_byte (dos, 1, NULL, NULL); /* major protocol version */
+ body_len_offset = g_memory_output_stream_get_data_size (mos);
+ /* body length - will be filled in later */
+ g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL);
+ g_data_output_stream_put_uint32 (dos, message->priv->serial, NULL, NULL);
+
+ num_fds_in_message = 0;
+#ifdef G_OS_UNIX
+ if (message->priv->fd_list != NULL)
+ num_fds_in_message = g_unix_fd_list_get_length (message->priv->fd_list);
+#endif
+ num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message);
+ /* TODO: check we have all the right header fields and that they are the correct value etc etc */
+ if (num_fds_in_message != num_fds_according_to_header)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Message has %d fds but the header field indicates %d fds"),
+ num_fds_in_message,
+ num_fds_according_to_header);
+ goto out;
+ }
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a{yv}"));//G_VARIANT_TYPE_ARRAY);
+ g_hash_table_iter_init (&hash_iter, message->priv->headers);
+ while (g_hash_table_iter_next (&hash_iter, &key, (gpointer) &header_value))
+ {
+ g_variant_builder_add (builder,
+ "{yv}",
+ (guchar) GPOINTER_TO_UINT (key),
+ header_value);
+ }
+ header_fields = g_variant_new ("a{yv}", builder);
+
+ if (!append_value_to_blob (header_fields, mos, dos, NULL, error))
+ {
+ g_variant_unref (header_fields);
+ goto out;
+ }
+ g_variant_unref (header_fields);
+
+ /* header size must be a multiple of 8 */
+ ensure_output_padding (mos, dos, 8);
+
+ body_start_offset = g_memory_output_stream_get_data_size (mos);
+
+ signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+ signature_str = NULL;
+ if (signature != NULL)
+ signature_str = g_variant_get_string (signature, NULL);
+ if (message->priv->body != NULL)
+ {
+ gchar *tupled_signature_str;
+ tupled_signature_str = g_strdup_printf ("(%s)", signature_str);
+ if (signature == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Message body has signature `%s' but there is no signature header"),
+ signature_str);
+ g_free (tupled_signature_str);
+ goto out;
+ }
+ else if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->priv->body)) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Message body has type signature `%s' but signature in the header field is `%s'"),
+ tupled_signature_str, g_variant_get_type_string (message->priv->body));
+ g_free (tupled_signature_str);
+ goto out;
+ }
+ g_free (tupled_signature_str);
+ if (!append_body_to_blob (message->priv->body, mos, dos, error))
+ goto out;
+ }
+ else
+ {
+ if (signature != NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Message body is empty but signature in the header field is `(%s)'"),
+ signature_str);
+ goto out;
+ }
+ }
+
+ /* OK, we're done writing the message - set the body length */
+ size = g_memory_output_stream_get_data_size (mos);
+ body_size = size - body_start_offset;
+
+ if (!g_seekable_seek (G_SEEKABLE (mos), body_len_offset, G_SEEK_SET, NULL, error))
+ goto out;
+
+ g_data_output_stream_put_uint32 (dos, body_size, NULL, NULL);
+
+ *out_size = size;
+ ret = g_memdup (g_memory_output_stream_get_data (mos), size);
+
+ out:
+ g_object_unref (dos);
+ g_object_unref (mos);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint32
+get_uint32_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field)
+{
+ GVariant *value;
+ guint32 ret;
+
+ ret = 0;
+ value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+ if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
+ ret = g_variant_get_uint32 (value);
+
+ return ret;
+}
+
+static const gchar *
+get_string_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field)
+{
+ GVariant *value;
+ const gchar *ret;
+
+ ret = NULL;
+ value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+ if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ ret = g_variant_get_string (value, NULL);
+
+ return ret;
+}
+
+static const gchar *
+get_object_path_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field)
+{
+ GVariant *value;
+ const gchar *ret;
+
+ ret = NULL;
+ value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+ if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH))
+ ret = g_variant_get_string (value, NULL);
+
+ return ret;
+}
+
+static const gchar *
+get_signature_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field)
+{
+ GVariant *value;
+ const gchar *ret;
+
+ ret = NULL;
+ value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+ if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_SIGNATURE))
+ ret = g_variant_get_string (value, NULL);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+set_uint32_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ guint32 value)
+{
+ g_dbus_message_set_header (message,
+ header_field,
+ g_variant_new_uint32 (value));
+}
+
+static void
+set_string_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ const gchar *value)
+{
+ g_dbus_message_set_header (message,
+ header_field,
+ value == NULL ? NULL : g_variant_new_string (value));
+}
+
+static void
+set_object_path_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ const gchar *value)
+{
+ g_dbus_message_set_header (message,
+ header_field,
+ value == NULL ? NULL : g_variant_new_object_path (value));
+}
+
+static void
+set_signature_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ const gchar *value)
+{
+ g_dbus_message_set_header (message,
+ header_field,
+ value == NULL ? NULL : g_variant_new_signature (value));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_reply_serial:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field.
+ *
+ * Returns: The value.
+ */
+guint32
+g_dbus_message_get_reply_serial (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+ return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL);
+}
+
+/**
+ * g_dbus_message_set_reply_serial:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field.
+ */
+void
+g_dbus_message_set_reply_serial (GDBusMessage *message,
+ guint32 value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_interface:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_interface (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE);
+}
+
+/**
+ * g_dbus_message_set_interface:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field.
+ */
+void
+g_dbus_message_set_interface (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_dbus_is_interface_name (value));
+ set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_member:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_member (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER);
+}
+
+/**
+ * g_dbus_message_set_member:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field.
+ */
+void
+g_dbus_message_set_member (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_dbus_is_member_name (value));
+ set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_path:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_path (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH);
+}
+
+/**
+ * g_dbus_message_set_path:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field.
+ */
+void
+g_dbus_message_set_path (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_variant_is_object_path (value));
+ set_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_sender:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_sender (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER);
+}
+
+/**
+ * g_dbus_message_set_sender:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field.
+ */
+void
+g_dbus_message_set_sender (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_dbus_is_name (value));
+ set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_destination:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_destination (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION);
+}
+
+/**
+ * g_dbus_message_set_destination:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field.
+ */
+void
+g_dbus_message_set_destination (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_dbus_is_name (value));
+ set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_error_name:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_error_name (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME);
+}
+
+/**
+ * g_dbus_message_set_error_name:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field.
+ */
+void
+g_dbus_message_set_error_name (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_dbus_is_interface_name (value));
+ set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_signature:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_signature (GDBusMessage *message)
+{
+ const gchar *ret;
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ ret = get_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+ if (ret == NULL)
+ ret = "";
+ return ret;
+}
+
+/**
+ * g_dbus_message_set_signature:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field.
+ */
+void
+g_dbus_message_set_signature (GDBusMessage *message,
+ const gchar *value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (value == NULL || g_variant_is_signature (value));
+ set_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_arg0:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience to get the first item in the body of @message.
+ *
+ * Returns: The string item or %NULL if the first item in the body of
+ * @message is not a string.
+ */
+const gchar *
+g_dbus_message_get_arg0 (GDBusMessage *message)
+{
+ const gchar *ret;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+ ret = NULL;
+
+ if (message->priv->body != NULL && g_variant_is_of_type (message->priv->body, G_VARIANT_TYPE_TUPLE))
+ {
+ GVariant *item;
+ item = g_variant_get_child_value (message->priv->body, 0);
+ if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING))
+ ret = g_variant_get_string (item, NULL);
+ }
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_num_unix_fds:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field.
+ *
+ * Returns: The value.
+ */
+guint32
+g_dbus_message_get_num_unix_fds (GDBusMessage *message)
+{
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+ return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS);
+}
+
+/**
+ * g_dbus_message_set_num_unix_fds:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field.
+ */
+void
+g_dbus_message_set_num_unix_fds (GDBusMessage *message,
+ guint32 value)
+{
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_to_gerror:
+ * @message: A #GDBusMessage.
+ * @error: The #GError to set.
+ *
+ * If @message is not of type %G_DBUS_MESSAGE_TYPE_ERROR does
+ * nothing and returns %FALSE.
+ *
+ * Otherwise this method encodes the error in @message as a #GError
+ * using g_dbus_error_set_dbus_error() using the information in the
+ * %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field of @message as
+ * well as the first string item in @message's body.
+ *
+ * Returns: %TRUE if @error was set, %FALSE otherwise.
+ */
+gboolean
+g_dbus_message_to_gerror (GDBusMessage *message,
+ GError **error)
+{
+ gboolean ret;
+ const gchar *error_name;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+
+ ret = FALSE;
+ if (message->priv->type != G_DBUS_MESSAGE_TYPE_ERROR)
+ goto out;
+
+ error_name = g_dbus_message_get_error_name (message);
+ if (error_name != NULL)
+ {
+ GVariant *body;
+
+ body = g_dbus_message_get_body (message);
+
+ if (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
+ {
+ const gchar *error_message;
+ g_variant_get (body, "(s)", &error_message);
+ g_dbus_error_set_dbus_error (error,
+ error_name,
+ error_message,
+ NULL);
+ }
+ else
+ {
+ /* these two situations are valid, yet pretty rare */
+ if (body != NULL)
+ {
+ g_dbus_error_set_dbus_error (error,
+ error_name,
+ "",
+ _("Error return with body of type `%s'"),
+ g_variant_get_type_string (body));
+ }
+ else
+ {
+ g_dbus_error_set_dbus_error (error,
+ error_name,
+ "",
+ _("Error return with empty body"));
+ }
+ }
+ }
+ else
+ {
+ /* TOOD: this shouldn't happen - should check this at message serialization
+ * time and disconnect the peer.
+ */
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Error return without error-name header!");
+ }
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+enum_to_string (GType enum_type, gint value)
+{
+ gchar *ret;
+ GEnumClass *klass;
+ GEnumValue *enum_value;
+
+ klass = g_type_class_ref (enum_type);
+ enum_value = g_enum_get_value (klass, value);
+ if (enum_value != NULL)
+ ret = g_strdup (enum_value->value_nick);
+ else
+ ret = g_strdup_printf ("unknown (value %d)", value);
+ g_type_class_unref (klass);
+ return ret;
+}
+
+static gchar *
+flags_to_string (GType flags_type, guint value)
+{
+ GString *s;
+ GFlagsClass *klass;
+ guint n;
+
+ klass = g_type_class_ref (flags_type);
+ s = g_string_new (NULL);
+ for (n = 0; n < 32; n++)
+ {
+ if ((value & (1<<n)) != 0)
+ {
+ GFlagsValue *flags_value;
+ flags_value = g_flags_get_first_value (klass, (1<<n));
+ if (s->len > 0)
+ g_string_append_c (s, ',');
+ if (flags_value != NULL)
+ g_string_append (s, flags_value->value_nick);
+ else
+ g_string_append_printf (s, "unknown (bit %d)", n);
+ }
+ }
+ if (s->len == 0)
+ g_string_append (s, "none");
+ g_type_class_unref (klass);
+ return g_string_free (s, FALSE);;
+}
+
+static gint
+_sort_keys_func (gconstpointer a,
+ gconstpointer b)
+{
+ gint ia;
+ gint ib;
+
+ ia = GPOINTER_TO_INT (a);
+ ib = GPOINTER_TO_INT (b);
+
+ return ia - ib;
+}
+
+/**
+ * g_dbus_message_print:
+ * @message: A #GDBusMessage.
+ * @indent: Indentation level.
+ *
+ * Produces a human-readable multi-line description of @message.
+ *
+ * The contents of the description has no ABI guarantees, the contents
+ * and formatting is subject to change at any time. Typical output
+ * looks something like this:
+ * <programlisting>
+ * Type: method-call
+ * Flags: none
+ * Version: 0
+ * Serial: 4
+ * Headers:
+ * path -> objectpath '/org/gtk/GDBus/TestObject'
+ * interface -> 'org.gtk.GDBus.TestInterface'
+ * member -> 'GimmeStdout'
+ * destination -> ':1.146'
+ * Body: ()
+ * UNIX File Descriptors:
+ * (none)
+ * </programlisting>
+ * or
+ * <programlisting>
+ * Type: method-return
+ * Flags: no-reply-expected
+ * Version: 0
+ * Serial: 477
+ * Headers:
+ * reply-serial -> uint32 4
+ * destination -> ':1.159'
+ * sender -> ':1.146'
+ * num-unix-fds -> uint32 1
+ * Body: ()
+ * UNIX File Descriptors:
+ * fd 12: dev=0:10,mode=020620,ino=5,uid=500,gid=5,rdev=136:2,size=0,atime=1273085037,mtime=1273085851,ctime=1272982635
+ * </programlisting>
+ *
+ * Returns: A string that should be freed with g_free().
+ */
+gchar *
+g_dbus_message_print (GDBusMessage *message,
+ guint indent)
+{
+ GString *str;
+ gchar *s;
+ GList *keys;
+ GList *l;
+
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+ str = g_string_new (NULL);
+
+ s = enum_to_string (G_TYPE_DBUS_MESSAGE_TYPE, message->priv->type);
+ g_string_append_printf (str, "%*sType: %s\n", indent, "", s);
+ g_free (s);
+ s = flags_to_string (G_TYPE_DBUS_MESSAGE_FLAGS, message->priv->flags);
+ g_string_append_printf (str, "%*sFlags: %s\n", indent, "", s);
+ g_free (s);
+ g_string_append_printf (str, "%*sVersion: %d\n", indent, "", message->priv->major_protocol_version);
+ g_string_append_printf (str, "%*sSerial: %d\n", indent, "", message->priv->serial);
+
+ g_string_append_printf (str, "%*sHeaders:\n", indent, "");
+ keys = g_hash_table_get_keys (message->priv->headers);
+ keys = g_list_sort (keys, _sort_keys_func);
+ if (keys != NULL)
+ {
+ for (l = keys; l != NULL; l = l->next)
+ {
+ gint key = GPOINTER_TO_INT (l->data);
+ GVariant *value;
+ gchar *value_str;
+
+ value = g_hash_table_lookup (message->priv->headers, l->data);
+ g_assert (value != NULL);
+
+ s = enum_to_string (G_TYPE_DBUS_MESSAGE_HEADER_FIELD, key);
+ value_str = g_variant_print (value, TRUE);
+ g_string_append_printf (str, "%*s %s -> %s\n", indent, "", s, value_str);
+ g_free (s);
+ g_free (value_str);
+ }
+ }
+ else
+ {
+ g_string_append_printf (str, "%*s (none)\n", indent, "");
+ }
+ g_string_append_printf (str, "%*sBody: ", indent, "");
+ if (message->priv->body != NULL)
+ {
+ g_variant_print_string (message->priv->body,
+ str,
+ TRUE);
+ }
+ else
+ {
+ g_string_append (str, "()");
+ }
+ g_string_append (str, "\n");
+#ifdef G_OS_UNIX
+ g_string_append_printf (str, "%*sUNIX File Descriptors:\n", indent, "");
+ if (message->priv->fd_list != NULL)
+ {
+ gint num_fds;
+ const gint *fds;
+ gint n;
+
+ fds = g_unix_fd_list_peek_fds (message->priv->fd_list, &num_fds);
+ if (num_fds > 0)
+ {
+ for (n = 0; n < num_fds; n++)
+ {
+ GString *fs;
+ struct stat statbuf;
+ fs = g_string_new (NULL);
+ if (fstat (fds[n], &statbuf) == 0)
+ {
+ g_string_append_printf (fs, "%s" "dev=%d:%d", fs->len > 0 ? "," : "",
+ major (statbuf.st_dev), minor (statbuf.st_dev));
+ g_string_append_printf (fs, "%s" "mode=0%o", fs->len > 0 ? "," : "",
+ statbuf.st_mode);
+ g_string_append_printf (fs, "%s" "ino=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+ (guint64) statbuf.st_ino);
+ g_string_append_printf (fs, "%s" "uid=%d", fs->len > 0 ? "," : "",
+ statbuf.st_uid);
+ g_string_append_printf (fs, "%s" "gid=%d", fs->len > 0 ? "," : "",
+ statbuf.st_gid);
+ g_string_append_printf (fs, "%s" "rdev=%d:%d", fs->len > 0 ? "," : "",
+ major (statbuf.st_rdev), minor (statbuf.st_rdev));
+ g_string_append_printf (fs, "%s" "size=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+ (guint64) statbuf.st_size);
+ g_string_append_printf (fs, "%s" "atime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+ (guint64) statbuf.st_atime);
+ g_string_append_printf (fs, "%s" "mtime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+ (guint64) statbuf.st_mtime);
+ g_string_append_printf (fs, "%s" "ctime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+ (guint64) statbuf.st_ctime);
+ }
+ else
+ {
+ g_string_append_printf (fs, "(fstat failed: %s)", strerror (errno));
+ }
+ g_string_append_printf (str, "%*s fd %d: %s\n", indent, "", fds[n], fs->str);
+ g_string_free (fs, TRUE);
+ }
+ }
+ else
+ {
+ g_string_append_printf (str, "%*s (empty)\n", indent, "");
+ }
+ }
+ else
+ {
+ g_string_append_printf (str, "%*s (none)\n", indent, "");
+ }
+#endif
+
+ return g_string_free (str, FALSE);
+}
+
diff --git a/gio/gdbusmessage.h b/gio/gdbusmessage.h
new file mode 100644
index 000000000..4fb1873b5
--- /dev/null
+++ b/gio/gdbusmessage.h
@@ -0,0 +1,172 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_MESSAGE_H__
+#define __G_DBUS_MESSAGE_H__
+
+#include <gio/giotypes.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixfdlist.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_MESSAGE (g_dbus_message_get_gtype ())
+#define G_DBUS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_MESSAGE, GDBusMessage))
+#define G_DBUS_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_MESSAGE, GDBusMessageClass))
+#define G_DBUS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_MESSAGE, GDBusMessageClass))
+#define G_IS_DBUS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_MESSAGE))
+#define G_IS_DBUS_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_MESSAGE))
+
+typedef struct _GDBusMessageClass GDBusMessageClass;
+typedef struct _GDBusMessagePrivate GDBusMessagePrivate;
+
+/**
+ * GDBusMessageClass:
+ *
+ * Class structure for #GDBusMessage.
+ */
+struct _GDBusMessageClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+};
+
+/**
+ * GDBusMessage:
+ *
+ * The #GDBusMessage structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GDBusMessage
+{
+ /*< private >*/
+ GObject parent_instance;
+ GDBusMessagePrivate *priv;
+};
+
+GType g_dbus_message_get_gtype (void) G_GNUC_CONST;
+GDBusMessage *g_dbus_message_new (void);
+GDBusMessage *g_dbus_message_new_signal (const gchar *path,
+ const gchar *interface,
+ const gchar *signal);
+GDBusMessage *g_dbus_message_new_method_call (const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ const gchar *method);
+GDBusMessage *g_dbus_message_new_method_reply (GDBusMessage *method_call_message);
+GDBusMessage *g_dbus_message_new_method_error (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message_format,
+ ...);
+GDBusMessage *g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message_format,
+ va_list var_args);
+GDBusMessage *g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message,
+ const gchar *error_name,
+ const gchar *error_message);
+gchar *g_dbus_message_print (GDBusMessage *message,
+ guint indent);
+
+GDBusMessageType g_dbus_message_get_type (GDBusMessage *message);
+void g_dbus_message_set_type (GDBusMessage *message,
+ GDBusMessageType type);
+GDBusMessageFlags g_dbus_message_get_flags (GDBusMessage *message);
+void g_dbus_message_set_flags (GDBusMessage *message,
+ GDBusMessageFlags flags);
+guint32 g_dbus_message_get_serial (GDBusMessage *message);
+void g_dbus_message_set_serial (GDBusMessage *message,
+ guint32 serial);
+GVariant *g_dbus_message_get_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field);
+void g_dbus_message_set_header (GDBusMessage *message,
+ GDBusMessageHeaderField header_field,
+ GVariant *value);
+guchar *g_dbus_message_get_header_fields (GDBusMessage *message);
+GVariant *g_dbus_message_get_body (GDBusMessage *message);
+void g_dbus_message_set_body (GDBusMessage *message,
+ GVariant *body);
+#ifdef G_OS_UNIX
+GUnixFDList *g_dbus_message_get_unix_fd_list (GDBusMessage *message);
+void g_dbus_message_set_unix_fd_list (GDBusMessage *message,
+ GUnixFDList *fd_list);
+#endif
+
+guint32 g_dbus_message_get_reply_serial (GDBusMessage *message);
+void g_dbus_message_set_reply_serial (GDBusMessage *message,
+ guint32 value);
+
+const gchar *g_dbus_message_get_interface (GDBusMessage *message);
+void g_dbus_message_set_interface (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_member (GDBusMessage *message);
+void g_dbus_message_set_member (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_path (GDBusMessage *message);
+void g_dbus_message_set_path (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_sender (GDBusMessage *message);
+void g_dbus_message_set_sender (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_destination (GDBusMessage *message);
+void g_dbus_message_set_destination (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_error_name (GDBusMessage *message);
+void g_dbus_message_set_error_name (GDBusMessage *message,
+ const gchar *value);
+
+const gchar *g_dbus_message_get_signature (GDBusMessage *message);
+void g_dbus_message_set_signature (GDBusMessage *message,
+ const gchar *value);
+
+guint32 g_dbus_message_get_num_unix_fds (GDBusMessage *message);
+void g_dbus_message_set_num_unix_fds (GDBusMessage *message,
+ guint32 value);
+
+const gchar *g_dbus_message_get_arg0 (GDBusMessage *message);
+
+
+GDBusMessage *g_dbus_message_new_from_blob (guchar *blob,
+ gsize blob_len,
+ GError **error);
+
+gssize g_dbus_message_bytes_needed (guchar *blob,
+ gsize blob_len,
+ GError **error);
+
+guchar *g_dbus_message_to_blob (GDBusMessage *message,
+ gsize *out_size,
+ GError **error);
+
+gboolean g_dbus_message_to_gerror (GDBusMessage *message,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_MESSAGE_H__ */
diff --git a/gio/gdbusmethodinvocation.c b/gio/gdbusmethodinvocation.c
new file mode 100644
index 000000000..dc6950bea
--- /dev/null
+++ b/gio/gdbusmethodinvocation.c
@@ -0,0 +1,795 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusconnection.h"
+#include "gdbusmessage.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusintrospection.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusmethodinvocation
+ * @short_description: Object for handling remote calls
+ * @include: gdbus/gdbus.h
+ *
+ * Instances of the #GDBusMethodInvocation class are used when
+ * handling D-Bus method calls. It provides a way to asynchronously
+ * return results and errors.
+ */
+
+struct _GDBusMethodInvocationPrivate
+{
+ /* construct-only properties */
+ gchar *sender;
+ gchar *object_path;
+ gchar *interface_name;
+ gchar *method_name;
+ const GDBusMethodInfo *method_info;
+ GDBusConnection *connection;
+ GDBusMessage *message;
+ GVariant *parameters;
+ gpointer user_data;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SENDER,
+ PROP_OBJECT_PATH,
+ PROP_INTERFACE_NAME,
+ PROP_METHOD_NAME,
+ PROP_METHOD_INFO,
+ PROP_CONNECTION,
+ PROP_PARAMETERS,
+ PROP_MESSAGE,
+ PROP_USER_DATA
+};
+
+G_DEFINE_TYPE (GDBusMethodInvocation, g_dbus_method_invocation, G_TYPE_OBJECT);
+
+static void
+g_dbus_method_invocation_finalize (GObject *object)
+{
+ GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+ g_free (invocation->priv->sender);
+ g_free (invocation->priv->object_path);
+ g_free (invocation->priv->interface_name);
+ g_free (invocation->priv->method_name);
+ g_object_unref (invocation->priv->connection);
+ g_object_unref (invocation->priv->message);
+ g_variant_unref (invocation->priv->parameters);
+
+ if (G_OBJECT_CLASS (g_dbus_method_invocation_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_method_invocation_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_method_invocation_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_SENDER:
+ g_value_set_string (value, g_dbus_method_invocation_get_sender (invocation));
+ break;
+
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, g_dbus_method_invocation_get_object_path (invocation));
+ break;
+
+ case PROP_INTERFACE_NAME:
+ g_value_set_string (value, g_dbus_method_invocation_get_interface_name (invocation));
+ break;
+
+ case PROP_METHOD_NAME:
+ g_value_set_string (value, g_dbus_method_invocation_get_method_name (invocation));
+ break;
+
+ case PROP_METHOD_INFO:
+ g_value_set_boxed (value, g_dbus_method_invocation_get_method_info (invocation));
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, g_dbus_method_invocation_get_connection (invocation));
+ break;
+
+ case PROP_PARAMETERS:
+ g_value_set_boxed (value, g_dbus_method_invocation_get_parameters (invocation));
+ break;
+
+ case PROP_MESSAGE:
+ g_value_set_object (value, g_dbus_method_invocation_get_message (invocation));
+ break;
+
+ case PROP_USER_DATA:
+ g_value_set_pointer (value, g_dbus_method_invocation_get_user_data (invocation));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_method_invocation_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_SENDER:
+ invocation->priv->sender = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ invocation->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_INTERFACE_NAME:
+ invocation->priv->interface_name = g_value_dup_string (value);
+ break;
+
+ case PROP_METHOD_NAME:
+ invocation->priv->method_name = g_value_dup_string (value);
+ break;
+
+ case PROP_METHOD_INFO:
+ invocation->priv->method_info = g_value_dup_boxed (value);
+ break;
+
+ case PROP_CONNECTION:
+ invocation->priv->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_PARAMETERS:
+ invocation->priv->parameters = g_value_dup_boxed (value);
+ break;
+
+ case PROP_MESSAGE:
+ invocation->priv->message = g_value_dup_object (value);
+ break;
+
+ case PROP_USER_DATA:
+ invocation->priv->user_data = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+g_dbus_method_invocation_class_init (GDBusMethodInvocationClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_method_invocation_finalize;
+ gobject_class->set_property = g_dbus_method_invocation_set_property;
+ gobject_class->get_property = g_dbus_method_invocation_get_property;
+
+ /**
+ * GDBusMethodInvocation:sender:
+ *
+ * The bus name that invoked the method or %NULL if the connection is not a bus connection.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SENDER,
+ g_param_spec_string ("sender",
+ _("Sender"),
+ _("The bus name that invoked the method."),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:object-path:
+ *
+ * The object path the method was invoked on.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ _("Object Path"),
+ _("The object path the method was invoked on."),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:interface-name:
+ *
+ * The name of the D-Bus interface the method was invoked on.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_INTERFACE_NAME,
+ g_param_spec_string ("interface-name",
+ _("Interface Name"),
+ _("The name of the D-Bus interface the method was invoked on."),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:method-name:
+ *
+ * The name of the method that was invoked.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_METHOD_NAME,
+ g_param_spec_string ("method-name",
+ _("Method Name"),
+ _("The name of the method that was invoked."),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:method-info:
+ *
+ * Information about the method that was invoked, if any.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_METHOD_INFO,
+ g_param_spec_boxed ("method-info",
+ _("Method Info"),
+ _("Information about the method that was invoked, if any."),
+ G_TYPE_DBUS_METHOD_INFO,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:connection:
+ *
+ * The #GDBusConnection the method was invoked on.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CONNECTION,
+ g_param_spec_object ("connection",
+ _("Connection"),
+ _("The #GDBusConnection the method was invoked on."),
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:message:
+ *
+ * The D-Bus message.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_MESSAGE,
+ g_param_spec_object ("message",
+ _("Message"),
+ _("The D-Bus Message."),
+ G_TYPE_DBUS_MESSAGE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:parameters:
+ *
+ * The parameters as a #GVariant tuple.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_PARAMETERS,
+ g_param_spec_boxed ("parameters",
+ _("Parameters"),
+ _("The parameters as a #GVariant tuple."),
+ G_TYPE_VARIANT,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusMethodInvocation:user-data:
+ *
+ * The @user_data #gpointer passed to g_dbus_connection_register_object().
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_USER_DATA,
+ g_param_spec_pointer ("user-data",
+ _("User Data"),
+ _("The gpointer passed to g_dbus_connection_register_object()."),
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_type_class_add_private (klass, sizeof (GDBusMethodInvocationPrivate));
+}
+
+static void
+g_dbus_method_invocation_init (GDBusMethodInvocation *invocation)
+{
+ invocation->priv = G_TYPE_INSTANCE_GET_PRIVATE (invocation,
+ G_TYPE_DBUS_METHOD_INVOCATION,
+ GDBusMethodInvocationPrivate);
+}
+
+/**
+ * g_dbus_method_invocation_get_sender:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the bus name that invoked the method.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_sender (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->sender;
+}
+
+/**
+ * g_dbus_method_invocation_get_object_path:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the object path the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->object_path;
+}
+
+/**
+ * g_dbus_method_invocation_get_interface_name:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the name of the D-Bus interface the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->interface_name;
+}
+
+/**
+ * g_dbus_method_invocation_get_method_info:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets information about the method call, if any.
+ *
+ * Returns: A #GDBusMethodInfo or %NULL. Do not free, it is owned by @invocation.
+ */
+const GDBusMethodInfo *
+g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->method_info;
+}
+
+/**
+ * g_dbus_method_invocation_get_method_name:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the name of the method that was invoked.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_method_name (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->method_name;
+}
+
+/**
+ * g_dbus_method_invocation_get_connection:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the #GDBusConnection the method was invoked on.
+ *
+ * Returns: A #GDBusConnection. Do not free, it is owned by @invocation.
+ */
+GDBusConnection *
+g_dbus_method_invocation_get_connection (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->connection;
+}
+
+/**
+ * g_dbus_method_invocation_get_message:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the #GDBusMessage for the method invocation. This is useful if
+ * you need to use low-level protocol features, such as UNIX file
+ * descriptor passing, that cannot be properly expressed in the
+ * #GVariant API.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage. Do not free, it is owned by @invocation.
+ */
+GDBusMessage *
+g_dbus_method_invocation_get_message (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->message;
+}
+
+/**
+ * g_dbus_method_invocation_get_parameters:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the parameters of the method invocation.
+ *
+ * Returns: A #GVariant. Do not free, it is owned by @invocation.
+ */
+GVariant *
+g_dbus_method_invocation_get_parameters (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->parameters;
+}
+
+/**
+ * g_dbus_method_invocation_get_user_data:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * Returns: A #gpointer.
+ */
+gpointer
+g_dbus_method_invocation_get_user_data (GDBusMethodInvocation *invocation)
+{
+ g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+ return invocation->priv->user_data;
+}
+
+/**
+ * g_dbus_method_invocation_new:
+ * @sender: The bus name that invoked the method or %NULL if @connection is not a bus connection.
+ * @object_path: The object path the method was invoked on.
+ * @interface_name: The name of the D-Bus interface the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @method_info: Information about the method call or %NULL.
+ * @connection: The #GDBusConnection the method was invoked on.
+ * @message: The D-Bus message as a #GDBusMessage.
+ * @parameters: The parameters as a #GVariant tuple.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * Creates a new #GDBusMethodInvocation object.
+ *
+ * Returns: A #GDBusMethodInvocation. Free with g_object_unref().
+ */
+GDBusMethodInvocation *
+g_dbus_method_invocation_new (const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ const GDBusMethodInfo *method_info,
+ GDBusConnection *connection,
+ GDBusMessage *message,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ g_return_val_if_fail (sender == NULL || g_dbus_is_name (sender), NULL);
+ g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
+ g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), NULL);
+ g_return_val_if_fail (g_dbus_is_member_name (method_name), NULL);
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+ g_return_val_if_fail (g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+
+ return G_DBUS_METHOD_INVOCATION (g_object_new (G_TYPE_DBUS_METHOD_INVOCATION,
+ "sender", sender,
+ "object-path", object_path,
+ "interface-name", interface_name,
+ "method-name", method_name,
+ "method-info", method_info,
+ "connection", connection,
+ "message", message,
+ "parameters", parameters,
+ "user-data", user_data,
+ NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_method_invocation_return_value:
+ * @invocation: A #GDBusMethodInvocation.
+ * @parameters: A #GVariant tuple with out parameters for the method or %NULL if not passing any parameters.
+ *
+ * Finishes handling a D-Bus method call by returning @parameters.
+ *
+ * It is an error if @parameters is not of the right format.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_value (GDBusMethodInvocation *invocation,
+ GVariant *parameters)
+{
+ GDBusMessage *reply;
+ GError *error;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+
+ if (parameters != NULL)
+ g_variant_ref_sink (parameters);
+
+ /* if we have introspection data, check that the signature of @parameters is correct */
+ if (invocation->priv->method_info != NULL)
+ {
+ gchar *signature;
+ const gchar *type_string;
+
+ type_string = "()";
+ if (parameters != NULL)
+ type_string = g_variant_get_type_string (parameters);
+ signature = _g_dbus_compute_complete_signature (invocation->priv->method_info->out_args, TRUE);
+
+ if (g_strcmp0 (type_string, signature) != 0)
+ {
+ g_warning (_("Type of return value is incorrect, got `%s', expected `%s'"),
+ type_string,
+ signature);
+ g_free (signature);
+ goto out;
+ }
+ g_free (signature);
+ }
+
+ reply = g_dbus_message_new_method_reply (invocation->priv->message);
+ g_dbus_message_set_body (reply, parameters);
+ error = NULL;
+ if (!g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, &error))
+ {
+ g_warning (_("Error sending message: %s"), error->message);
+ g_error_free (error);
+ }
+ g_object_unref (reply);
+
+ out:
+ g_object_unref (invocation);
+ if (parameters != NULL)
+ g_variant_unref (parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_method_invocation_return_error:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @...: Parameters for @format.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * See g_dbus_error_encode_gerror() for details about what error name
+ * will be returned on the wire. In a nutshell, if the given error is
+ * registered using g_dbus_error_register_error() the name given
+ * during registration is used. Otherwise, a name of the form
+ * <literal>org.gtk.GDBus.UnmappedGError.Quark...</literal> is
+ * used. This provides transparent mapping of #GError between
+ * applications using GDBus.
+ *
+ * If you are writing an application intended to be portable,
+ * <emphasis>always</emphasis> register errors with g_dbus_error_register_error()
+ * or use g_dbus_method_invocation_return_dbus_error().
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *format,
+ ...)
+{
+ va_list var_args;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail (format != NULL);
+
+ va_start (var_args, format);
+ g_dbus_method_invocation_return_error_valist (invocation,
+ domain,
+ code,
+ format,
+ var_args);
+ va_end (var_args);
+}
+
+/**
+ * g_dbus_method_invocation_return_error_valist:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @var_args: #va_list of parameters for @format.
+ *
+ * Like g_dbus_method_invocation_return_error() but intended for
+ * language bindings.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error_valist (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *format,
+ va_list var_args)
+{
+ gchar *literal_message;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail (format != NULL);
+
+ literal_message = g_strdup_vprintf (format, var_args);
+ g_dbus_method_invocation_return_error_literal (invocation,
+ domain,
+ code,
+ literal_message);
+ g_free (literal_message);
+}
+
+/**
+ * g_dbus_method_invocation_return_error_literal:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @message: The error message.
+ *
+ * Like g_dbus_method_invocation_return_error() but without printf()-style formatting.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *message)
+{
+ GError *error;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail (message != NULL);
+
+ error = g_error_new_literal (domain, code, message);
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+}
+
+/**
+ * g_dbus_method_invocation_return_gerror:
+ * @invocation: A #GDBusMethodInvocation.
+ * @error: A #GError.
+ *
+ * Like g_dbus_method_invocation_return_error() but takes a #GError
+ * instead of the error domain, error code and message.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_gerror (GDBusMethodInvocation *invocation,
+ const GError *error)
+{
+ gchar *dbus_error_name;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail (error != NULL);
+
+ dbus_error_name = g_dbus_error_encode_gerror (error);
+
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ dbus_error_name,
+ error->message);
+ g_free (dbus_error_name);
+}
+
+/**
+ * g_dbus_method_invocation_return_dbus_error:
+ * @invocation: A #GDBusMethodInvocation.
+ * @error_name: A valid D-Bus error name.
+ * @error_message: A valid D-Bus error message.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_dbus_error (GDBusMethodInvocation *invocation,
+ const gchar *error_name,
+ const gchar *error_message)
+{
+ GDBusMessage *reply;
+
+ g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+ g_return_if_fail (error_name != NULL && g_dbus_is_name (error_name));
+ g_return_if_fail (error_message != NULL);
+
+ reply = g_dbus_message_new_method_error_literal (invocation->priv->message,
+ error_name,
+ error_message);
+ g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, NULL);
+ g_object_unref (reply);
+
+ g_object_unref (invocation);
+}
diff --git a/gio/gdbusmethodinvocation.h b/gio/gdbusmethodinvocation.h
new file mode 100644
index 000000000..65d0f9924
--- /dev/null
+++ b/gio/gdbusmethodinvocation.h
@@ -0,0 +1,119 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_METHOD_INVOCATION_H__
+#define __G_DBUS_METHOD_INVOCATION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_METHOD_INVOCATION (g_dbus_method_invocation_get_type ())
+#define G_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocation))
+#define G_DBUS_METHOD_INVOCATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass))
+#define G_DBUS_METHOD_INVOCATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass))
+#define G_IS_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_METHOD_INVOCATION))
+#define G_IS_DBUS_METHOD_INVOCATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_METHOD_INVOCATION))
+
+typedef struct _GDBusMethodInvocationClass GDBusMethodInvocationClass;
+typedef struct _GDBusMethodInvocationPrivate GDBusMethodInvocationPrivate;
+
+/**
+ * GDBusMethodInvocation:
+ *
+ * The #GDBusMethodInvocation structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusMethodInvocation
+{
+ /*< private >*/
+ GObject parent_instance;
+ GDBusMethodInvocationPrivate *priv;
+};
+
+/**
+ * GDBusMethodInvocationClass:
+ *
+ * Class structure for #GDBusMethodInvocation.
+ */
+struct _GDBusMethodInvocationClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+GType g_dbus_method_invocation_get_type (void) G_GNUC_CONST;
+GDBusMethodInvocation *g_dbus_method_invocation_new (const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ const GDBusMethodInfo *method_info,
+ GDBusConnection *connection,
+ GDBusMessage *message,
+ GVariant *parameters,
+ gpointer user_data);
+const gchar *g_dbus_method_invocation_get_sender (GDBusMethodInvocation *invocation);
+const gchar *g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation);
+const gchar *g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation);
+const gchar *g_dbus_method_invocation_get_method_name (GDBusMethodInvocation *invocation);
+const GDBusMethodInfo *g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation);
+GDBusConnection *g_dbus_method_invocation_get_connection (GDBusMethodInvocation *invocation);
+GDBusMessage *g_dbus_method_invocation_get_message (GDBusMethodInvocation *invocation);
+GVariant *g_dbus_method_invocation_get_parameters (GDBusMethodInvocation *invocation);
+gpointer g_dbus_method_invocation_get_user_data (GDBusMethodInvocation *invocation);
+
+void g_dbus_method_invocation_return_value (GDBusMethodInvocation *invocation,
+ GVariant *parameters);
+void g_dbus_method_invocation_return_error (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *format,
+ ...);
+void g_dbus_method_invocation_return_error_valist (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *format,
+ va_list var_args);
+void g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation,
+ GQuark domain,
+ gint code,
+ const gchar *message);
+void g_dbus_method_invocation_return_gerror (GDBusMethodInvocation *invocation,
+ const GError *error);
+void g_dbus_method_invocation_return_dbus_error (GDBusMethodInvocation *invocation,
+ const gchar *error_name,
+ const gchar *error_message);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_METHOD_INVOCATION_H__ */
diff --git a/gio/gdbusnameowning.c b/gio/gdbusnameowning.c
new file mode 100644
index 000000000..30901651f
--- /dev/null
+++ b/gio/gdbusnameowning.c
@@ -0,0 +1,713 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnameowning.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnameowning
+ * @title: Owning Bus Names
+ * @short_description: Simple API for owning bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for owning bus names.
+ *
+ * <example id="gdbus-owning-names"><title>Simple application owning a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-own-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+ PREVIOUS_CALL_NONE = 0,
+ PREVIOUS_CALL_ACQUIRED,
+ PREVIOUS_CALL_LOST,
+} PreviousCall;
+
+typedef struct
+{
+ volatile gint ref_count;
+ guint id;
+ GBusNameOwnerFlags flags;
+ gchar *name;
+ GBusAcquiredCallback bus_acquired_handler;
+ GBusNameAcquiredCallback name_acquired_handler;
+ GBusNameLostCallback name_lost_handler;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+ GMainContext *main_context;
+
+ PreviousCall previous_call;
+
+ GDBusConnection *connection;
+ gulong disconnected_signal_handler_id;
+ guint name_acquired_subscription_id;
+ guint name_lost_subscription_id;
+
+ gboolean cancelled;
+
+ gboolean needs_release;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+
+static Client *
+client_ref (Client *client)
+{
+ g_atomic_int_inc (&client->ref_count);
+ return client;
+}
+
+static void
+client_unref (Client *client)
+{
+ if (g_atomic_int_dec_and_test (&client->ref_count))
+ {
+ if (client->connection != NULL)
+ {
+ if (client->disconnected_signal_handler_id > 0)
+ g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+ if (client->name_acquired_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+ if (client->name_lost_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+ g_object_unref (client->connection);
+ }
+ if (client->main_context != NULL)
+ g_main_context_unref (client->main_context);
+ g_free (client->name);
+ if (client->user_data_free_func != NULL)
+ client->user_data_free_func (client->user_data);
+ g_free (client);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+typedef enum
+{
+ CALL_TYPE_NAME_ACQUIRED,
+ CALL_TYPE_NAME_LOST
+} CallType;
+
+typedef struct
+{
+ Client *client;
+
+ /* keep this separate because client->connection may
+ * be set to NULL after scheduling the call
+ */
+ GDBusConnection *connection;
+
+ /* set to TRUE to call acquired */
+ CallType call_type;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+ if (data->connection != NULL)
+ g_object_unref (data->connection);
+ client_unref (data->client);
+ g_free (data);
+}
+
+static void
+actually_do_call (Client *client, GDBusConnection *connection, CallType call_type)
+{
+ switch (call_type)
+ {
+ case CALL_TYPE_NAME_ACQUIRED:
+ if (client->name_acquired_handler != NULL)
+ {
+ client->name_acquired_handler (connection,
+ client->name,
+ client->user_data);
+ }
+ break;
+
+ case CALL_TYPE_NAME_LOST:
+ if (client->name_lost_handler != NULL)
+ {
+ client->name_lost_handler (connection,
+ client->name,
+ client->user_data);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+ CallHandlerData *data = _data;
+ actually_do_call (data->client, data->connection, data->call_type);
+ return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client, CallType call_type)
+{
+ CallHandlerData *data;
+ GSource *idle_source;
+
+ data = g_new0 (CallHandlerData, 1);
+ data->client = client_ref (client);
+ data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+ data->call_type = call_type;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+ g_source_set_callback (idle_source,
+ call_in_idle_cb,
+ data,
+ (GDestroyNotify) call_handler_data_free);
+ g_source_attach (idle_source, client->main_context);
+ g_source_unref (idle_source);
+}
+
+static void
+do_call (Client *client, CallType call_type)
+{
+ /* only schedule in idle if we're not in the right thread */
+ if (g_main_context_get_thread_default () != client->main_context)
+ schedule_call_in_idle (client, call_type);
+ else
+ actually_do_call (client, client->connection, call_type);
+}
+
+static void
+call_acquired_handler (Client *client)
+{
+ if (client->previous_call != PREVIOUS_CALL_ACQUIRED)
+ {
+ client->previous_call = PREVIOUS_CALL_ACQUIRED;
+ if (!client->cancelled)
+ {
+ do_call (client, CALL_TYPE_NAME_ACQUIRED);
+ }
+ }
+}
+
+static void
+call_lost_handler (Client *client)
+{
+ if (client->previous_call != PREVIOUS_CALL_LOST)
+ {
+ client->previous_call = PREVIOUS_CALL_LOST;
+ if (!client->cancelled)
+ {
+ do_call (client, CALL_TYPE_NAME_LOST);
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_lost_or_acquired (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ const gchar *name;
+
+ if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+ g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+ g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+ goto out;
+
+ if (g_strcmp0 (signal_name, "NameLost") == 0)
+ {
+ g_variant_get (parameters, "(s)", &name);
+ if (g_strcmp0 (name, client->name) == 0)
+ {
+ call_lost_handler (client);
+ }
+ }
+ else if (g_strcmp0 (signal_name, "NameAcquired") == 0)
+ {
+ g_variant_get (parameters, "(s)", &name);
+ if (g_strcmp0 (name, client->name) == 0)
+ {
+ call_acquired_handler (client);
+ }
+ }
+ out:
+ ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+request_name_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ GVariant *result;
+ guint32 request_name_reply;
+ gboolean subscribe;
+
+ request_name_reply = 0;
+ result = NULL;
+
+ result = g_dbus_connection_invoke_method_finish (client->connection,
+ res,
+ NULL);
+ if (result != NULL)
+ {
+ g_variant_get (result, "(u)", &request_name_reply);
+ g_variant_unref (result);
+ }
+
+ subscribe = FALSE;
+
+ switch (request_name_reply)
+ {
+ case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */
+ /* We got the name - now listen for NameLost and NameAcquired */
+ call_acquired_handler (client);
+ subscribe = TRUE;
+ client->needs_release = TRUE;
+ break;
+
+ case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */
+ /* Waiting in line - listen for NameLost and NameAcquired */
+ call_lost_handler (client);
+ subscribe = TRUE;
+ client->needs_release = TRUE;
+ break;
+
+ default:
+ /* assume we couldn't get the name - explicit fallthrough */
+ case 3: /* DBUS_REQUEST_NAME_REPLY_EXISTS */
+ case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */
+ /* Some other part of the process is already owning the name */
+ call_lost_handler (client);
+ break;
+ }
+
+ if (subscribe)
+ {
+ /* start listening to NameLost and NameAcquired messages */
+ client->name_lost_subscription_id =
+ g_dbus_connection_signal_subscribe (client->connection,
+ "org.freedesktop.DBus",
+ "org.freedesktop.DBus",
+ "NameLost",
+ "/org/freedesktop/DBus",
+ client->name,
+ on_name_lost_or_acquired,
+ client,
+ NULL);
+ client->name_acquired_subscription_id =
+ g_dbus_connection_signal_subscribe (client->connection,
+ "org.freedesktop.DBus",
+ "org.freedesktop.DBus",
+ "NameAcquired",
+ "/org/freedesktop/DBus",
+ client->name,
+ on_name_lost_or_acquired,
+ client,
+ NULL);
+ }
+
+ client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ if (client->disconnected_signal_handler_id > 0)
+ g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+ if (client->name_acquired_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+ if (client->name_lost_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+ g_object_unref (client->connection);
+ client->disconnected_signal_handler_id = 0;
+ client->name_acquired_subscription_id = 0;
+ client->name_lost_subscription_id = 0;
+ client->connection = NULL;
+
+ call_lost_handler (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+ /* listen for disconnection */
+ client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+ "closed",
+ G_CALLBACK (on_connection_disconnected),
+ client);
+
+ /* attempt to acquire the name */
+ g_dbus_connection_invoke_method (client->connection,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "RequestName", /* method name */
+ g_variant_new ("(su)",
+ client->name,
+ client->flags),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) request_name_cb,
+ client_ref (client));
+}
+
+
+static void
+connection_get_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ client->connection = g_bus_get_finish (res, NULL);
+ if (client->connection == NULL)
+ {
+ call_lost_handler (client);
+ goto out;
+ }
+
+ /* No need to schedule this in idle as we're already in the thread
+ * that the user called g_bus_own_name() from. This is because
+ * g_bus_get() guarantees that.
+ *
+ * Also, we need to ensure that the handler is invoked *before*
+ * we call RequestName(). Otherwise there is a race.
+ */
+ if (client->bus_acquired_handler != NULL)
+ {
+ client->bus_acquired_handler (client->connection,
+ client->name,
+ client->user_data);
+ }
+
+ has_connection (client);
+
+ out:
+ client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_bus_own_name_on_connection:
+ * @connection: A #GDBusConnection that is not closed.
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL.
+ * @name_lost_handler: Handler to invoke when @name is lost or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Like g_bus_own_name() but takes a #GDBusConnection instead of a
+ * #GBusType.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unown_name() to stop owning the name.
+ **/
+guint
+g_bus_own_name_on_connection (GDBusConnection *connection,
+ const gchar *name,
+ GBusNameOwnerFlags flags,
+ GBusNameAcquiredCallback name_acquired_handler,
+ GBusNameLostCallback name_lost_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ Client *client;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+ g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+ g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0);
+
+ G_LOCK (lock);
+
+ client = g_new0 (Client, 1);
+ client->ref_count = 1;
+ client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+ client->name = g_strdup (name);
+ client->flags = flags;
+ client->name_acquired_handler = name_acquired_handler;
+ client->name_lost_handler = name_lost_handler;
+ client->user_data = user_data;
+ client->user_data_free_func = user_data_free_func;
+ client->main_context = g_main_context_get_thread_default ();
+ if (client->main_context != NULL)
+ g_main_context_ref (client->main_context);
+
+ client->connection = g_object_ref (connection);
+
+ if (map_id_to_client == NULL)
+ {
+ map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+ }
+ g_hash_table_insert (map_id_to_client,
+ GUINT_TO_POINTER (client->id),
+ client);
+
+ G_UNLOCK (lock);
+
+ has_connection (client);
+
+ return client->id;
+}
+
+/**
+ * g_bus_own_name:
+ * @bus_type: The type of bus to own a name on.
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @bus_acquired_handler: Handler to invoke when connected to the bus of type @bus_type or %NULL.
+ * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL.
+ * @name_lost_handler: Handler to invoke when @name is lost or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts acquiring @name on the bus specified by @bus_type and calls
+ * @name_acquired_handler and @name_lost_handler when the name is
+ * acquired respectively lost. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the @name_acquired_handler and @name_lost_handler
+ * callbacks will be invoked after calling this function - there are three
+ * possible cases:
+ * <itemizedlist>
+ * <listitem><para>
+ * @name_lost_handler with a %NULL connection (if a connection to the bus can't be made).
+ * </para></listitem>
+ * <listitem><para>
+ * @bus_acquired_handler then @name_lost_handler (if the name can't be obtained)
+ * </para></listitem>
+ * <listitem><para>
+ * @bus_acquired_handler then @name_acquired_handler (if the name was obtained).
+ * </para></listitem>
+ * </itemizedlist>
+ * When you are done owning the name, just call g_bus_unown_name()
+ * with the owner id this function returns.
+ *
+ * If the name is acquired or lost (for example another application
+ * could acquire the name if you allow replacement or the application
+ * currently owning the name exits), the handlers are also invoked. If the
+ * #GDBusConnection that is used for attempting to own the name
+ * closes, then @name_lost_handler is invoked since it is no
+ * longer possible for other processes to access the process.
+ *
+ * You cannot use g_bus_own_name() several times (unless interleaved
+ * with calls to g_bus_unown_name()) - only the first call will work.
+ *
+ * Another guarantee is that invocations of @name_acquired_handler
+ * and @name_lost_handler are guaranteed to alternate; that
+ * is, if @name_acquired_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_lost_handler. The reverse is also true.
+ *
+ * If you plan on exporting objects (using e.g. g_dbus_connection_register_object()), note
+ * that it is generally too late to export the objects in @name_acquired_handler. Instead,
+ * you can do this in @bus_acquired_handler since you are guaranteed that this will
+ * run before @name is requested from the bus.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to own names and export objects, see <xref linkend="gdbus-owning-names"/>. Simply
+ * register objects to be exported in @bus_acquired_handler and
+ * unregister the objects (if any) in @name_lost_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unown_name() to stop owning the name.
+ **/
+guint
+g_bus_own_name (GBusType bus_type,
+ const gchar *name,
+ GBusNameOwnerFlags flags,
+ GBusAcquiredCallback bus_acquired_handler,
+ GBusNameAcquiredCallback name_acquired_handler,
+ GBusNameLostCallback name_lost_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ Client *client;
+
+ g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+ g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0);
+
+ G_LOCK (lock);
+
+ client = g_new0 (Client, 1);
+ client->ref_count = 1;
+ client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+ client->name = g_strdup (name);
+ client->flags = flags;
+ client->bus_acquired_handler = bus_acquired_handler;
+ client->name_acquired_handler = name_acquired_handler;
+ client->name_lost_handler = name_lost_handler;
+ client->user_data = user_data;
+ client->user_data_free_func = user_data_free_func;
+ client->main_context = g_main_context_get_thread_default ();
+ if (client->main_context != NULL)
+ g_main_context_ref (client->main_context);
+
+ if (map_id_to_client == NULL)
+ {
+ map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+ }
+ g_hash_table_insert (map_id_to_client,
+ GUINT_TO_POINTER (client->id),
+ client);
+
+ g_bus_get (bus_type,
+ NULL,
+ connection_get_cb,
+ client_ref (client));
+
+ G_UNLOCK (lock);
+
+ return client->id;
+}
+
+/**
+ * g_bus_unown_name:
+ * @owner_id: An identifier obtained from g_bus_own_name()
+ *
+ * Stops owning a name.
+ */
+void
+g_bus_unown_name (guint owner_id)
+{
+ Client *client;
+
+ g_return_if_fail (owner_id > 0);
+
+ client = NULL;
+
+ G_LOCK (lock);
+ if (owner_id == 0 || map_id_to_client == NULL ||
+ (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (owner_id))) == NULL)
+ {
+ g_warning ("Invalid id %d passed to g_bus_unown_name()", owner_id);
+ goto out;
+ }
+
+ client->cancelled = TRUE;
+ g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (owner_id)));
+
+ out:
+ G_UNLOCK (lock);
+
+ /* do callback without holding lock */
+ if (client != NULL)
+ {
+ /* Release the name if needed */
+ if (client->needs_release && client->connection != NULL)
+ {
+ GVariant *result;
+ GError *error;
+ guint32 release_name_reply;
+
+ /* TODO: it kinda sucks having to do a sync call to release the name - but if
+ * we don't, then a subsequent grab of the name will make the bus daemon return
+ * IN_QUEUE which will trigger name_lost().
+ *
+ * I believe this is a bug in the bus daemon.
+ */
+ error = NULL;
+ result = g_dbus_connection_invoke_method_sync (client->connection,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "ReleaseName", /* method name */
+ g_variant_new ("(s)", client->name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (result == NULL)
+ {
+ g_warning ("Error releasing name %s: %s", client->name, error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_variant_get (result, "(u)", &release_name_reply);
+ if (release_name_reply != 1 /* DBUS_RELEASE_NAME_REPLY_RELEASED */)
+ {
+ g_warning ("Unexpected reply %d when releasing name %s", release_name_reply, client->name);
+ }
+ g_variant_unref (result);
+ }
+ }
+
+ if (client->disconnected_signal_handler_id > 0)
+ g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+ if (client->name_acquired_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+ if (client->name_lost_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+ client->disconnected_signal_handler_id = 0;
+ client->name_acquired_subscription_id = 0;
+ client->name_lost_subscription_id = 0;
+ if (client->connection != NULL)
+ {
+ g_object_unref (client->connection);
+ client->connection = NULL;
+ }
+
+ client_unref (client);
+ }
+}
diff --git a/gio/gdbusnameowning.h b/gio/gdbusnameowning.h
new file mode 100644
index 000000000..fc063e036
--- /dev/null
+++ b/gio/gdbusnameowning.h
@@ -0,0 +1,88 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_NAME_OWNING_H__
+#define __G_DBUS_NAME_OWNING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusAcquiredCallback:
+ * @connection: The #GDBusConnection to a message bus.
+ * @name: The name that is requested to be owned.
+ * @user_data: User data passed to g_bus_own_name().
+ *
+ * Invoked when a connection to a message bus has been obtained.
+ */
+typedef void (*GBusAcquiredCallback) (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data);
+
+/**
+ * GBusNameAcquiredCallback:
+ * @connection: The #GDBusConnection on which to acquired the name.
+ * @name: The name being owned.
+ * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection().
+ *
+ * Invoked when the name is acquired.
+ */
+typedef void (*GBusNameAcquiredCallback) (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data);
+
+/**
+ * GBusNameLostCallback:
+ * @connection: The #GDBusConnection on which to acquire the name or %NULL if
+ * the connection was disconnected.
+ * @name: The name being owned.
+ * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection().
+ *
+ * Invoked when the name is lost or @connection has been closed.
+ */
+typedef void (*GBusNameLostCallback) (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data);
+
+guint g_bus_own_name (GBusType bus_type,
+ const gchar *name,
+ GBusNameOwnerFlags flags,
+ GBusAcquiredCallback bus_acquired_handler,
+ GBusNameAcquiredCallback name_acquired_handler,
+ GBusNameLostCallback name_lost_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+
+guint g_bus_own_name_on_connection (GDBusConnection *connection,
+ const gchar *name,
+ GBusNameOwnerFlags flags,
+ GBusNameAcquiredCallback name_acquired_handler,
+ GBusNameLostCallback name_lost_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+
+void g_bus_unown_name (guint owner_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_NAME_OWNING_H__ */
diff --git a/gio/gdbusnamewatching.c b/gio/gdbusnamewatching.c
new file mode 100644
index 000000000..92e04cc18
--- /dev/null
+++ b/gio/gdbusnamewatching.c
@@ -0,0 +1,620 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnamewatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnamewatching
+ * @title: Watching Bus Names
+ * @short_description: Simple API for watching bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus names.
+ *
+ * <example id="gdbus-watching-names"><title>Simple application watching a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-watch-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+ PREVIOUS_CALL_NONE = 0,
+ PREVIOUS_CALL_APPEARED,
+ PREVIOUS_CALL_VANISHED,
+} PreviousCall;
+
+typedef struct
+{
+ volatile gint ref_count;
+ guint id;
+ gchar *name;
+ GBusNameWatcherFlags flags;
+ gchar *name_owner;
+ GBusNameAppearedCallback name_appeared_handler;
+ GBusNameVanishedCallback name_vanished_handler;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+ GMainContext *main_context;
+
+ GDBusConnection *connection;
+ gulong disconnected_signal_handler_id;
+ guint name_owner_changed_subscription_id;
+
+ PreviousCall previous_call;
+
+ gboolean cancelled;
+ gboolean initialized;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+static Client *
+client_ref (Client *client)
+{
+ g_atomic_int_inc (&client->ref_count);
+ return client;
+}
+
+static void
+client_unref (Client *client)
+{
+ if (g_atomic_int_dec_and_test (&client->ref_count))
+ {
+ if (client->connection != NULL)
+ {
+ if (client->name_owner_changed_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+ if (client->disconnected_signal_handler_id > 0)
+ g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+ g_object_unref (client->connection);
+ }
+ g_free (client->name);
+ g_free (client->name_owner);
+ if (client->main_context != NULL)
+ g_main_context_unref (client->main_context);
+ if (client->user_data_free_func != NULL)
+ client->user_data_free_func (client->user_data);
+ g_free (client);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+ CALL_TYPE_NAME_APPEARED,
+ CALL_TYPE_NAME_VANISHED
+} CallType;
+
+typedef struct
+{
+ Client *client;
+
+ /* keep this separate because client->connection may
+ * be set to NULL after scheduling the call
+ */
+ GDBusConnection *connection;
+
+ /* ditto */
+ gchar *name_owner;
+
+ CallType call_type;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+ if (data->connection != NULL)
+ g_object_unref (data->connection);
+ g_free (data->name_owner);
+ client_unref (data->client);
+ g_free (data);
+}
+
+static void
+actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type)
+{
+ switch (call_type)
+ {
+ case CALL_TYPE_NAME_APPEARED:
+ if (client->name_appeared_handler != NULL)
+ {
+ client->name_appeared_handler (connection,
+ client->name,
+ name_owner,
+ client->user_data);
+ }
+ break;
+
+ case CALL_TYPE_NAME_VANISHED:
+ if (client->name_vanished_handler != NULL)
+ {
+ client->name_vanished_handler (connection,
+ client->name,
+ client->user_data);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+ CallHandlerData *data = _data;
+ actually_do_call (data->client, data->connection, data->name_owner, data->call_type);
+ return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client, CallType call_type)
+{
+ CallHandlerData *data;
+ GSource *idle_source;
+
+ data = g_new0 (CallHandlerData, 1);
+ data->client = client_ref (client);
+ data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+ data->name_owner = g_strdup (client->name_owner);
+ data->call_type = call_type;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+ g_source_set_callback (idle_source,
+ call_in_idle_cb,
+ data,
+ (GDestroyNotify) call_handler_data_free);
+ g_source_attach (idle_source, client->main_context);
+ g_source_unref (idle_source);
+}
+
+static void
+do_call (Client *client, CallType call_type)
+{
+ /* only schedule in idle if we're not in the right thread */
+ if (g_main_context_get_thread_default () != client->main_context)
+ schedule_call_in_idle (client, call_type);
+ else
+ actually_do_call (client, client->connection, client->name_owner, call_type);
+}
+
+static void
+call_appeared_handler (Client *client)
+{
+ if (client->previous_call != PREVIOUS_CALL_APPEARED)
+ {
+ client->previous_call = PREVIOUS_CALL_APPEARED;
+ if (!client->cancelled && client->name_appeared_handler != NULL)
+ {
+ do_call (client, CALL_TYPE_NAME_APPEARED);
+ }
+ }
+}
+
+static void
+call_vanished_handler (Client *client,
+ gboolean ignore_cancelled)
+{
+ if (client->previous_call != PREVIOUS_CALL_VANISHED)
+ {
+ client->previous_call = PREVIOUS_CALL_VANISHED;
+ if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL)
+ {
+ do_call (client, CALL_TYPE_NAME_VANISHED);
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ if (client->name_owner_changed_subscription_id > 0)
+ g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+ if (client->disconnected_signal_handler_id > 0)
+ g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+ g_object_unref (client->connection);
+ client->disconnected_signal_handler_id = 0;
+ client->name_owner_changed_subscription_id = 0;
+ client->connection = NULL;
+
+ call_vanished_handler (client, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_owner_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ const gchar *name;
+ const gchar *old_owner;
+ const gchar *new_owner;
+
+ if (!client->initialized)
+ goto out;
+
+ if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+ g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+ g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+ goto out;
+
+ g_variant_get (parameters,
+ "(sss)",
+ &name,
+ &old_owner,
+ &new_owner);
+
+ /* we only care about a specific name */
+ if (g_strcmp0 (name, client->name) != 0)
+ goto out;
+
+ if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
+ {
+ g_free (client->name_owner);
+ client->name_owner = NULL;
+ call_vanished_handler (client, FALSE);
+ }
+
+ if (new_owner != NULL && strlen (new_owner) > 0)
+ {
+ g_warn_if_fail (client->name_owner == NULL);
+ g_free (client->name_owner);
+ client->name_owner = g_strdup (new_owner);
+ call_appeared_handler (client);
+ }
+
+ out:
+ ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_name_owner_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ GVariant *result;
+ const char *name_owner;
+
+ name_owner = NULL;
+ result = NULL;
+
+ result = g_dbus_connection_invoke_method_finish (client->connection,
+ res,
+ NULL);
+ if (result != NULL)
+ {
+ g_variant_get (result, "(s)", &name_owner);
+ }
+
+ if (name_owner != NULL)
+ {
+ g_warn_if_fail (client->name_owner == NULL);
+ client->name_owner = g_strdup (name_owner);
+ call_appeared_handler (client);
+ }
+ else
+ {
+ call_vanished_handler (client, FALSE);
+ }
+
+ client->initialized = TRUE;
+
+ if (result != NULL)
+ g_variant_unref (result);
+ client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+invoke_get_name_owner (Client *client)
+{
+ g_dbus_connection_invoke_method (client->connection,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetNameOwner", /* method name */
+ g_variant_new ("(s)", client->name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) get_name_owner_cb,
+ client_ref (client));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+start_service_by_name_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ GVariant *result;
+
+ result = NULL;
+
+ result = g_dbus_connection_invoke_method_finish (client->connection,
+ res,
+ NULL);
+ if (result != NULL)
+ {
+ guint32 start_service_result;
+ g_variant_get (result, "(u)", &start_service_result);
+
+ if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */
+ {
+ invoke_get_name_owner (client);
+ }
+ else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */
+ {
+ invoke_get_name_owner (client);
+ }
+ else
+ {
+ g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result);
+ call_vanished_handler (client, FALSE);
+ client->initialized = TRUE;
+ }
+ }
+ else
+ {
+ /* Errors are not unexpected; the bus will reply e.g.
+ *
+ * org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2
+ * was not provided by any .service files
+ *
+ * so just report vanished.
+ */
+ call_vanished_handler (client, FALSE);
+ client->initialized = TRUE;
+ }
+
+ if (result != NULL)
+ g_variant_unref (result);
+ client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+ /* listen for disconnection */
+ client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+ "closed",
+ G_CALLBACK (on_connection_disconnected),
+ client);
+
+ /* start listening to NameOwnerChanged messages immediately */
+ client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection,
+ "org.freedesktop.DBus", /* name */
+ "org.freedesktop.DBus", /* if */
+ "NameOwnerChanged", /* signal */
+ "/org/freedesktop/DBus", /* path */
+ client->name,
+ on_name_owner_changed,
+ client,
+ NULL);
+
+ if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ g_dbus_connection_invoke_method (client->connection,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "StartServiceByName", /* method name */
+ g_variant_new ("(su)", client->name, 0),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) start_service_by_name_cb,
+ client_ref (client));
+ }
+ else
+ {
+ /* check owner */
+ invoke_get_name_owner (client);
+ }
+}
+
+
+static void
+connection_get_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ client->connection = g_bus_get_finish (res, NULL);
+ if (client->connection == NULL)
+ {
+ call_vanished_handler (client, FALSE);
+ goto out;
+ }
+
+ has_connection (client);
+
+ out:
+ client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_bus_watch_name:
+ * @bus_type: The type of bus to watch a name on.
+ * @name: The name (well-known or unique) to watch.
+ * @flags: Flags from the #GBusNameWatcherFlags enumeration.
+ * @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL.
+ * @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching @name on the bus specified by @bus_type and calls
+ * @name_appeared_handler and @name_vanished_handler when the name is
+ * known to have a owner respectively known to lose its
+ * owner. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the handlers will be invoked after
+ * calling this function. When you are done watching the name, just
+ * call g_bus_unwatch_name() with the watcher id this function
+ * returns.
+ *
+ * If the name vanishes or appears (for example the application owning
+ * the name could restart), the handlers are also invoked. If the
+ * #GDBusConnection that is used for watching the name disconnects, then
+ * @name_vanished_handler is invoked since it is no longer
+ * possible to access the name.
+ *
+ * Another guarantee is that invocations of @name_appeared_handler
+ * and @name_vanished_handler are guaranteed to alternate; that
+ * is, if @name_appeared_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_vanished_handler. The reverse is also true.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to take action when a certain name exists, see <xref
+ * linkend="gdbus-watching-names"/>. Basically, the application
+ * should create object proxies in @name_appeared_handler and destroy
+ * them again (if any) in @name_vanished_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unwatch_name() to stop watching the name.
+ **/
+guint
+g_bus_watch_name (GBusType bus_type,
+ const gchar *name,
+ GBusNameWatcherFlags flags,
+ GBusNameAppearedCallback name_appeared_handler,
+ GBusNameVanishedCallback name_vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ Client *client;
+
+ g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+ g_return_val_if_fail (g_dbus_is_name (name), 0);
+
+ G_LOCK (lock);
+
+ client = g_new0 (Client, 1);
+ client->ref_count = 1;
+ client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+ client->name = g_strdup (name);
+ client->flags = flags;
+ client->name_appeared_handler = name_appeared_handler;
+ client->name_vanished_handler = name_vanished_handler;
+ client->user_data = user_data;
+ client->user_data_free_func = user_data_free_func;
+ client->main_context = g_main_context_get_thread_default ();
+ if (client->main_context != NULL)
+ g_main_context_ref (client->main_context);
+
+ if (map_id_to_client == NULL)
+ {
+ map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+ }
+ g_hash_table_insert (map_id_to_client,
+ GUINT_TO_POINTER (client->id),
+ client);
+
+ g_bus_get (bus_type,
+ NULL,
+ connection_get_cb,
+ client_ref (client));
+
+ G_UNLOCK (lock);
+
+ return client->id;
+}
+
+/**
+ * g_bus_unwatch_name:
+ * @watcher_id: An identifier obtained from g_bus_watch_name()
+ *
+ * Stops watching a name.
+ **/
+void
+g_bus_unwatch_name (guint watcher_id)
+{
+ Client *client;
+
+ g_return_if_fail (watcher_id > 0);
+
+ client = NULL;
+
+ G_LOCK (lock);
+ if (watcher_id == 0 ||
+ map_id_to_client == NULL ||
+ (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+ {
+ g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id);
+ goto out;
+ }
+
+ client->cancelled = TRUE;
+ g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+ G_UNLOCK (lock);
+
+ /* do callback without holding lock */
+ if (client != NULL)
+ {
+ client_unref (client);
+ }
+}
diff --git a/gio/gdbusnamewatching.h b/gio/gdbusnamewatching.h
new file mode 100644
index 000000000..a724d4257
--- /dev/null
+++ b/gio/gdbusnamewatching.h
@@ -0,0 +1,68 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_NAME_WATCHING_H__
+#define __G_DBUS_NAME_WATCHING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusNameAppearedCallback:
+ * @connection: The #GDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @user_data: User data passed to g_bus_watch_name().
+ *
+ * Invoked when the name being watched is known to have to have a owner.
+ */
+typedef void (*GBusNameAppearedCallback) (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data);
+
+/**
+ * GBusNameVanishedCallback:
+ * @connection: The #GDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to g_bus_watch_name().
+ *
+ * Invoked when the name being watched is known not to have to have a owner.
+ */
+typedef void (*GBusNameVanishedCallback) (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data);
+
+
+guint g_bus_watch_name (GBusType bus_type,
+ const gchar *name,
+ GBusNameWatcherFlags flags,
+ GBusNameAppearedCallback name_appeared_handler,
+ GBusNameVanishedCallback name_vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+void g_bus_unwatch_name (guint watcher_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_NAME_WATCHING_H__ */
diff --git a/gio/gdbusprivate.c b/gio/gdbusprivate.c
new file mode 100644
index 000000000..adc6e12f3
--- /dev/null
+++ b/gio/gdbusprivate.c
@@ -0,0 +1,1040 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include <gio/gunixfdmessage.h>
+#include "gunixcredentialsmessage.h"
+#include <unistd.h>
+#endif
+
+#include "giotypes.h"
+#include "gdbusprivate.h"
+#include "gdbusmessage.h"
+#include "gdbuserror.h"
+#include "gdbusintrospection.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+hexdump (const gchar *data, gsize len, guint indent)
+{
+ guint n, m;
+ GString *ret;
+
+ ret = g_string_new (NULL);
+
+ for (n = 0; n < len; n += 16)
+ {
+ g_string_append_printf (ret, "%*s%04x: ", indent, "", n);
+
+ for (m = n; m < n + 16; m++)
+ {
+ if (m > n && (m%4) == 0)
+ g_string_append_c (ret, ' ');
+ if (m < len)
+ g_string_append_printf (ret, "%02x ", (guchar) data[m]);
+ else
+ g_string_append (ret, " ");
+ }
+
+ g_string_append (ret, " ");
+
+ for (m = n; m < len && m < n + 16; m++)
+ g_string_append_c (ret, g_ascii_isprint (data[m]) ? data[m] : '.');
+
+ g_string_append_c (ret, '\n');
+ }
+
+ return g_string_free (ret, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Unfortunately ancillary messages are discarded when reading from a
+ * socket using the GSocketInputStream abstraction. So we provide a
+ * very GInputStream-ish API that uses GSocket in this case (very
+ * similar to GSocketInputStream).
+ */
+
+typedef struct
+{
+ GSocket *socket;
+ GCancellable *cancellable;
+
+ void *buffer;
+ gsize count;
+
+ GSocketControlMessage ***messages;
+ gint *num_messages;
+
+ GSimpleAsyncResult *simple;
+
+ gboolean from_mainloop;
+} ReadWithControlData;
+
+static void
+read_with_control_data_free (ReadWithControlData *data)
+{
+ g_object_unref (data->socket);
+ if (data->cancellable != NULL)
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+static gboolean
+_g_socket_read_with_control_messages_ready (GSocket *socket,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ ReadWithControlData *data = user_data;
+ GError *error;
+ gssize result;
+ GInputVector vector;
+
+ error = NULL;
+ vector.buffer = data->buffer;
+ vector.size = data->count;
+ result = g_socket_receive_message (data->socket,
+ NULL, /* address */
+ &vector,
+ 1,
+ data->messages,
+ data->num_messages,
+ NULL,
+ data->cancellable,
+ &error);
+ if (result >= 0)
+ {
+ g_simple_async_result_set_op_res_gssize (data->simple, result);
+ }
+ else
+ {
+ g_assert (error != NULL);
+ g_simple_async_result_set_from_error (data->simple, error);
+ g_error_free (error);
+ }
+
+ if (data->from_mainloop)
+ g_simple_async_result_complete (data->simple);
+ else
+ g_simple_async_result_complete_in_idle (data->simple);
+
+ return FALSE;
+}
+
+static void
+_g_socket_read_with_control_messages (GSocket *socket,
+ void *buffer,
+ gsize count,
+ GSocketControlMessage ***messages,
+ gint *num_messages,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ReadWithControlData *data;
+
+ data = g_new0 (ReadWithControlData, 1);
+ data->socket = g_object_ref (socket);
+ data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+ data->buffer = buffer;
+ data->count = count;
+ data->messages = messages;
+ data->num_messages = num_messages;
+
+ data->simple = g_simple_async_result_new (G_OBJECT (socket),
+ callback,
+ user_data,
+ _g_socket_read_with_control_messages);
+
+ if (!g_socket_condition_check (socket, G_IO_IN))
+ {
+ GSource *source;
+ data->from_mainloop = TRUE;
+ source = g_socket_create_source (data->socket,
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ cancellable);
+ g_source_set_callback (source,
+ (GSourceFunc) _g_socket_read_with_control_messages_ready,
+ data,
+ (GDestroyNotify) read_with_control_data_free);
+ g_source_attach (source, g_main_context_get_thread_default ());
+ g_source_unref (source);
+ }
+ else
+ {
+ _g_socket_read_with_control_messages_ready (data->socket, G_IO_IN, data);
+ read_with_control_data_free (data);
+ }
+}
+
+static gssize
+_g_socket_read_with_control_messages_finish (GSocket *socket,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ g_return_val_if_fail (G_IS_SOCKET (socket), -1);
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == _g_socket_read_with_control_messages);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return -1;
+ else
+ return g_simple_async_result_get_op_res_gssize (simple);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (shared_thread_lock);
+
+typedef struct
+{
+ gint num_users;
+ GThread *thread;
+ GMainContext *context;
+ GMainLoop *loop;
+} SharedThreadData;
+
+static SharedThreadData *shared_thread_data = NULL;
+
+static gpointer
+shared_thread_func (gpointer data)
+{
+ g_main_context_push_thread_default (shared_thread_data->context);
+ g_main_loop_run (shared_thread_data->loop);
+ g_main_context_pop_thread_default (shared_thread_data->context);
+ return NULL;
+}
+
+typedef void (*GDBusSharedThreadFunc) (gpointer user_data);
+
+typedef struct
+{
+ GDBusSharedThreadFunc func;
+ gpointer user_data;
+ gboolean done;
+} CallerData;
+
+static gboolean
+invoke_caller (gpointer user_data)
+{
+ CallerData *data = user_data;
+ data->func (data->user_data);
+ data->done = TRUE;
+ return FALSE;
+}
+
+static void
+_g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
+ gpointer user_data)
+{
+ GError *error;
+ GSource *idle_source;
+ CallerData *data;
+
+ G_LOCK (shared_thread_lock);
+
+ if (shared_thread_data != NULL)
+ {
+ shared_thread_data->num_users += 1;
+ goto have_thread;
+ }
+
+ shared_thread_data = g_new0 (SharedThreadData, 1);
+ shared_thread_data->num_users = 1;
+
+ error = NULL;
+ shared_thread_data->context = g_main_context_new ();
+ shared_thread_data->loop = g_main_loop_new (shared_thread_data->context, FALSE);
+ shared_thread_data->thread = g_thread_create (shared_thread_func,
+ NULL,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+
+ have_thread:
+
+ data = g_new0 (CallerData, 1);
+ data->func = func;
+ data->user_data = user_data;
+ data->done = FALSE;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ invoke_caller,
+ data,
+ NULL);
+ g_source_attach (idle_source, shared_thread_data->context);
+ g_source_unref (idle_source);
+
+ /* wait for the user code to run.. hmm.. probably use a condition variable instead */
+ while (!data->done)
+ g_thread_yield ();
+
+ g_free (data);
+
+ G_UNLOCK (shared_thread_lock);
+}
+
+static void
+_g_dbus_shared_thread_unref (void)
+{
+ /* TODO: actually destroy the shared thread here */
+#if 0
+ G_LOCK (shared_thread_lock);
+ g_assert (shared_thread_data != NULL);
+ shared_thread_data->num_users -= 1;
+ if (shared_thread_data->num_users == 0)
+ {
+ g_main_loop_quit (shared_thread_data->loop);
+ //g_thread_join (shared_thread_data->thread);
+ g_main_loop_unref (shared_thread_data->loop);
+ g_main_context_unref (shared_thread_data->context);
+ g_free (shared_thread_data);
+ shared_thread_data = NULL;
+ G_UNLOCK (shared_thread_lock);
+ }
+ else
+ {
+ G_UNLOCK (shared_thread_lock);
+ }
+#endif
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct GDBusWorker
+{
+ volatile gint ref_count;
+ gboolean stopped;
+ GIOStream *stream;
+ GDBusCapabilityFlags capabilities;
+ GCancellable *cancellable;
+ GDBusWorkerMessageReceivedCallback message_received_callback;
+ GDBusWorkerDisconnectedCallback disconnected_callback;
+ gpointer user_data;
+
+ GThread *thread;
+
+ /* if not NULL, stream is GSocketConnection */
+ GSocket *socket;
+
+ /* used for reading */
+ GMutex *read_lock;
+ gchar *read_buffer;
+ gsize read_buffer_allocated_size;
+ gsize read_buffer_cur_size;
+ gsize read_buffer_bytes_wanted;
+ GUnixFDList *read_fd_list;
+ GSocketControlMessage **read_ancillary_messages;
+ gint read_num_ancillary_messages;
+
+ /* used for writing */
+ GMutex *write_lock;
+ GQueue *write_queue;
+ gboolean write_is_pending;
+};
+
+struct _MessageToWriteData ;
+typedef struct _MessageToWriteData MessageToWriteData;
+
+static void message_to_write_data_free (MessageToWriteData *data);
+
+static GDBusWorker *
+_g_dbus_worker_ref (GDBusWorker *worker)
+{
+ g_atomic_int_inc (&worker->ref_count);
+ return worker;
+}
+
+static void
+_g_dbus_worker_unref (GDBusWorker *worker)
+{
+ if (g_atomic_int_dec_and_test (&worker->ref_count))
+ {
+ _g_dbus_shared_thread_unref ();
+
+ g_object_unref (worker->stream);
+
+ g_mutex_free (worker->read_lock);
+ g_object_unref (worker->cancellable);
+ if (worker->read_fd_list != NULL)
+ g_object_unref (worker->read_fd_list);
+
+ g_mutex_free (worker->write_lock);
+ g_queue_foreach (worker->write_queue,
+ (GFunc) message_to_write_data_free,
+ NULL);
+ g_queue_free (worker->write_queue);
+ g_free (worker);
+ }
+}
+
+static void
+_g_dbus_worker_emit_disconnected (GDBusWorker *worker,
+ gboolean remote_peer_vanished,
+ GError *error)
+{
+ if (!worker->stopped)
+ worker->disconnected_callback (worker, remote_peer_vanished, error, worker->user_data);
+}
+
+static void
+_g_dbus_worker_emit_message (GDBusWorker *worker,
+ GDBusMessage *message)
+{
+ if (!worker->stopped)
+ worker->message_received_callback (worker, message, worker->user_data);
+}
+
+static void _g_dbus_worker_do_read_unlocked (GDBusWorker *worker);
+
+/* called in private thread shared by all GDBusConnection instances (without read-lock held) */
+static void
+_g_dbus_worker_do_read_cb (GInputStream *input_stream,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GDBusWorker *worker = user_data;
+ GError *error;
+ gssize bytes_read;
+
+ g_mutex_lock (worker->read_lock);
+
+ /* If already stopped, don't even process the reply */
+ if (worker->stopped)
+ goto out;
+
+ error = NULL;
+ if (worker->socket == NULL)
+ bytes_read = g_input_stream_read_finish (g_io_stream_get_input_stream (worker->stream),
+ res,
+ &error);
+ else
+ bytes_read = _g_socket_read_with_control_messages_finish (worker->socket,
+ res,
+ &error);
+ if (worker->read_num_ancillary_messages > 0)
+ {
+ gint n;
+ for (n = 0; n < worker->read_num_ancillary_messages; n++)
+ {
+ GSocketControlMessage *control_message = G_SOCKET_CONTROL_MESSAGE (worker->read_ancillary_messages[n]);
+
+ if (FALSE)
+ {
+ }
+#ifdef G_OS_UNIX
+ else if (G_IS_UNIX_FD_MESSAGE (control_message))
+ {
+ GUnixFDMessage *fd_message;
+ gint *fds;
+ gint num_fds;
+
+ fd_message = G_UNIX_FD_MESSAGE (control_message);
+ fds = g_unix_fd_message_steal_fds (fd_message, &num_fds);
+ if (worker->read_fd_list == NULL)
+ {
+ worker->read_fd_list = g_unix_fd_list_new_from_array (fds, num_fds);
+ }
+ else
+ {
+ gint n;
+ for (n = 0; n < num_fds; n++)
+ {
+ /* TODO: really want a append_steal() */
+ g_unix_fd_list_append (worker->read_fd_list, fds[n], NULL);
+ close (fds[n]);
+ }
+ }
+ g_free (fds);
+ }
+ else if (G_IS_UNIX_CREDENTIALS_MESSAGE (control_message))
+ {
+ /* do nothing */
+ }
+#endif
+ else
+ {
+ if (error == NULL)
+ {
+ g_set_error (&error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Unexpected ancillary message of type %s received from peer",
+ g_type_name (G_TYPE_FROM_INSTANCE (control_message)));
+ _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+ g_error_free (error);
+ g_object_unref (control_message);
+ n++;
+ while (n < worker->read_num_ancillary_messages)
+ g_object_unref (worker->read_ancillary_messages[n++]);
+ g_free (worker->read_ancillary_messages);
+ goto out;
+ }
+ }
+ g_object_unref (control_message);
+ }
+ g_free (worker->read_ancillary_messages);
+ }
+
+ if (bytes_read == -1)
+ {
+ _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+ g_error_free (error);
+ goto out;
+ }
+
+#if 0
+ g_debug ("read %d bytes (is_closed=%d blocking=%d condition=0x%02x) stream %p, %p",
+ (gint) bytes_read,
+ g_socket_is_closed (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
+ g_socket_get_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
+ g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)),
+ G_IO_IN | G_IO_OUT | G_IO_HUP),
+ worker->stream,
+ worker);
+#endif
+
+ /* TODO: hmm, hmm... */
+ if (bytes_read == 0)
+ {
+ g_set_error (&error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Underlying GIOStream returned 0 bytes on an async read");
+ _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ worker->read_buffer_cur_size += bytes_read;
+ if (worker->read_buffer_bytes_wanted == worker->read_buffer_cur_size)
+ {
+ /* OK, got what we asked for! */
+ if (worker->read_buffer_bytes_wanted == 16)
+ {
+ gssize message_len;
+ /* OK, got the header - determine how many more bytes are needed */
+ error = NULL;
+ message_len = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer,
+ 16,
+ &error);
+ if (message_len == -1)
+ {
+ g_warning ("_g_dbus_worker_do_read_cb: error determing bytes needed: %s", error->message);
+ _g_dbus_worker_emit_disconnected (worker, FALSE, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ worker->read_buffer_bytes_wanted = message_len;
+ _g_dbus_worker_do_read_unlocked (worker);
+ }
+ else
+ {
+ GDBusMessage *message;
+ error = NULL;
+
+ /* TODO: use connection->priv->auth to decode the message */
+
+ message = g_dbus_message_new_from_blob ((guchar *) worker->read_buffer,
+ worker->read_buffer_cur_size,
+ &error);
+ if (message == NULL)
+ {
+ _g_dbus_worker_emit_disconnected (worker, FALSE, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (worker->read_fd_list != NULL)
+ {
+ g_dbus_message_set_unix_fd_list (message, worker->read_fd_list);
+ worker->read_fd_list = NULL;
+ }
+
+ if (G_UNLIKELY (_g_dbus_debug_message ()))
+ {
+ gchar *s;
+ g_print ("========================================================================\n"
+ "GDBus-debug:Message:\n"
+ " <<<< RECEIVED D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
+ worker->read_buffer_cur_size);
+ s = g_dbus_message_print (message, 2);
+ g_print ("%s", s);
+ g_free (s);
+ s = hexdump (worker->read_buffer, worker->read_buffer_cur_size, 2);
+ g_print ("%s\n", s);
+ g_free (s);
+ }
+
+ /* yay, got a message, go deliver it */
+ _g_dbus_worker_emit_message (worker, message);
+ g_object_unref (message);
+
+ /* start reading another message! */
+ worker->read_buffer_bytes_wanted = 0;
+ worker->read_buffer_cur_size = 0;
+ _g_dbus_worker_do_read_unlocked (worker);
+ }
+ }
+ else
+ {
+ /* didn't get all the bytes we requested - so repeat the request... */
+ _g_dbus_worker_do_read_unlocked (worker);
+ }
+
+ out:
+ g_mutex_unlock (worker->read_lock);
+
+ /* gives up the reference acquired when calling g_input_stream_read_async() */
+ _g_dbus_worker_unref (worker);
+}
+
+/* called in private thread shared by all GDBusConnection instances (with read-lock held) */
+static void
+_g_dbus_worker_do_read_unlocked (GDBusWorker *worker)
+{
+ /* if bytes_wanted is zero, it means start reading a message */
+ if (worker->read_buffer_bytes_wanted == 0)
+ {
+ worker->read_buffer_cur_size = 0;
+ worker->read_buffer_bytes_wanted = 16;
+ }
+
+ /* ensure we have a (big enough) buffer */
+ if (worker->read_buffer == NULL || worker->read_buffer_bytes_wanted > worker->read_buffer_allocated_size)
+ {
+ /* TODO: 4096 is randomly chosen; might want a better chosen default minimum */
+ worker->read_buffer_allocated_size = MAX (worker->read_buffer_bytes_wanted, 4096);
+ worker->read_buffer = g_realloc (worker->read_buffer, worker->read_buffer_allocated_size);
+ }
+
+ if (worker->socket == NULL)
+ g_input_stream_read_async (g_io_stream_get_input_stream (worker->stream),
+ worker->read_buffer + worker->read_buffer_cur_size,
+ worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size,
+ G_PRIORITY_DEFAULT,
+ worker->cancellable,
+ (GAsyncReadyCallback) _g_dbus_worker_do_read_cb,
+ _g_dbus_worker_ref (worker));
+ else
+ {
+ worker->read_ancillary_messages = NULL;
+ worker->read_num_ancillary_messages = 0;
+ _g_socket_read_with_control_messages (worker->socket,
+ worker->read_buffer + worker->read_buffer_cur_size,
+ worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size,
+ &worker->read_ancillary_messages,
+ &worker->read_num_ancillary_messages,
+ G_PRIORITY_DEFAULT,
+ worker->cancellable,
+ (GAsyncReadyCallback) _g_dbus_worker_do_read_cb,
+ _g_dbus_worker_ref (worker));
+ }
+}
+
+/* called in private thread shared by all GDBusConnection instances (without read-lock held) */
+static void
+_g_dbus_worker_do_read (GDBusWorker *worker)
+{
+ g_mutex_lock (worker->read_lock);
+ _g_dbus_worker_do_read_unlocked (worker);
+ g_mutex_unlock (worker->read_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _MessageToWriteData
+{
+ GDBusMessage *message;
+ gchar *blob;
+ gsize blob_size;
+};
+
+static void
+message_to_write_data_free (MessageToWriteData *data)
+{
+ g_object_unref (data->message);
+ g_free (data->blob);
+ g_free (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in private thread shared by all GDBusConnection instances (with write-lock held) */
+static gboolean
+write_message (GDBusWorker *worker,
+ MessageToWriteData *data,
+ GError **error)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (data->blob_size > 16, FALSE);
+
+ ret = FALSE;
+
+ /* First, the initial 16 bytes - special case UNIX sockets here
+ * since it may involve writing an ancillary message with file
+ * descriptors
+ */
+#ifdef G_OS_UNIX
+ {
+ GOutputVector vector;
+ GSocketControlMessage *message;
+ GUnixFDList *fd_list;
+ gssize bytes_written;
+
+ fd_list = g_dbus_message_get_unix_fd_list (data->message);
+
+ message = NULL;
+ if (fd_list != NULL)
+ {
+ if (!G_IS_UNIX_CONNECTION (worker->stream))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Tried sending a file descriptor on unsupported stream of type %s",
+ g_type_name (G_TYPE_FROM_INSTANCE (worker->stream)));
+ goto out;
+ }
+ else if (!(worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING))
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Tried sending a file descriptor but remote peer does not support this capability");
+ goto out;
+ }
+ message = g_unix_fd_message_new_with_fd_list (fd_list);
+ }
+
+ vector.buffer = data->blob;
+ vector.size = 16;
+
+ bytes_written = g_socket_send_message (worker->socket,
+ NULL, /* address */
+ &vector,
+ 1,
+ message != NULL ? &message : NULL,
+ message != NULL ? 1 : 0,
+ G_SOCKET_MSG_NONE,
+ worker->cancellable,
+ error);
+ if (bytes_written == -1)
+ {
+ g_prefix_error (error, _("Error writing first 16 bytes of message to socket: "));
+ if (message != NULL)
+ g_object_unref (message);
+ goto out;
+ }
+ if (message != NULL)
+ g_object_unref (message);
+
+ if (bytes_written < 16)
+ {
+ /* TODO: I think this needs to be handled ... are we guaranteed that the ancillary
+ * messages are sent?
+ */
+ g_assert_not_reached ();
+ }
+ }
+#else
+ /* write the first 16 bytes (guaranteed to return an error if everything can't be written) */
+ if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
+ (const gchar *) data->blob,
+ 16,
+ NULL, /* bytes_written */
+ worker->cancellable, /* cancellable */
+ error))
+ goto out;
+#endif
+
+ /* Then write the rest of the message (guaranteed to return an error if everything can't be written) */
+ if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
+ (const gchar *) data->blob + 16,
+ data->blob_size - 16,
+ NULL, /* bytes_written */
+ worker->cancellable, /* cancellable */
+ error))
+ goto out;
+
+ ret = TRUE;
+
+ if (G_UNLIKELY (_g_dbus_debug_message ()))
+ {
+ gchar *s;
+ g_print ("========================================================================\n"
+ "GDBus-debug:Message:\n"
+ " >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
+ data->blob_size);
+ s = g_dbus_message_print (data->message, 2);
+ g_print ("%s", s);
+ g_free (s);
+ s = hexdump (data->blob, data->blob_size, 2);
+ g_print ("%s\n", s);
+ g_free (s);
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
+static gboolean
+write_message_in_idle_cb (gpointer user_data)
+{
+ GDBusWorker *worker = user_data;
+ gboolean more_writes_are_pending;
+ MessageToWriteData *data;
+ GError *error;
+
+ g_mutex_lock (worker->write_lock);
+
+ data = g_queue_pop_head (worker->write_queue);
+ g_assert (data != NULL);
+
+ error = NULL;
+ if (!write_message (worker,
+ data,
+ &error))
+ {
+ /* TODO: handle */
+ _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+ g_error_free (error);
+ }
+ message_to_write_data_free (data);
+
+ more_writes_are_pending = (g_queue_get_length (worker->write_queue) > 0);
+
+ worker->write_is_pending = more_writes_are_pending;
+ g_mutex_unlock (worker->write_lock);
+
+ return more_writes_are_pending;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* can be called from any thread - steals blob */
+void
+_g_dbus_worker_send_message (GDBusWorker *worker,
+ GDBusMessage *message,
+ gchar *blob,
+ gsize blob_len)
+{
+ MessageToWriteData *data;
+
+ g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+ g_return_if_fail (blob != NULL);
+ g_return_if_fail (blob_len > 16);
+
+ data = g_new0 (MessageToWriteData, 1);
+ data->message = g_object_ref (message);
+ data->blob = blob; /* steal! */
+ data->blob_size = blob_len;
+
+ g_mutex_lock (worker->write_lock);
+ g_queue_push_tail (worker->write_queue, data);
+ if (!worker->write_is_pending)
+ {
+ GSource *idle_source;
+
+ worker->write_is_pending = TRUE;
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ write_message_in_idle_cb,
+ _g_dbus_worker_ref (worker),
+ (GDestroyNotify) _g_dbus_worker_unref);
+ g_source_attach (idle_source, shared_thread_data->context);
+ g_source_unref (idle_source);
+ }
+ g_mutex_unlock (worker->write_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_worker_thread_begin_func (gpointer user_data)
+{
+ GDBusWorker *worker = user_data;
+
+ worker->thread = g_thread_self ();
+
+ /* begin reading */
+ _g_dbus_worker_do_read (worker);
+}
+
+GDBusWorker *
+_g_dbus_worker_new (GIOStream *stream,
+ GDBusCapabilityFlags capabilities,
+ GDBusWorkerMessageReceivedCallback message_received_callback,
+ GDBusWorkerDisconnectedCallback disconnected_callback,
+ gpointer user_data)
+{
+ GDBusWorker *worker;
+
+ g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
+ g_return_val_if_fail (message_received_callback != NULL, NULL);
+ g_return_val_if_fail (disconnected_callback != NULL, NULL);
+
+ worker = g_new0 (GDBusWorker, 1);
+ worker->ref_count = 1;
+
+ worker->read_lock = g_mutex_new ();
+ worker->message_received_callback = message_received_callback;
+ worker->disconnected_callback = disconnected_callback;
+ worker->user_data = user_data;
+ worker->stream = g_object_ref (stream);
+ worker->capabilities = capabilities;
+ worker->cancellable = g_cancellable_new ();
+
+ worker->write_lock = g_mutex_new ();
+ worker->write_queue = g_queue_new ();
+
+ if (G_IS_SOCKET_CONNECTION (worker->stream))
+ worker->socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream));
+
+ _g_dbus_shared_thread_ref (_g_dbus_worker_thread_begin_func, worker);
+
+ return worker;
+}
+
+/* This can be called from any thread - frees worker - guarantees no callbacks
+ * will ever be issued again
+ */
+void
+_g_dbus_worker_stop (GDBusWorker *worker)
+{
+ /* If we're called in the worker thread it means we are called from
+ * a worker callback. This is fine, we just can't lock in that case since
+ * we're already holding the lock...
+ */
+ if (g_thread_self () != worker->thread)
+ g_mutex_lock (worker->read_lock);
+ worker->stopped = TRUE;
+ if (g_thread_self () != worker->thread)
+ g_mutex_unlock (worker->read_lock);
+
+ g_cancellable_cancel (worker->cancellable);
+ _g_dbus_worker_unref (worker);
+}
+
+#define G_DBUS_DEBUG_AUTHENTICATION (1<<0)
+#define G_DBUS_DEBUG_MESSAGE (1<<1)
+#define G_DBUS_DEBUG_ALL 0xffffffff
+static gint _gdbus_debug_flags = 0;
+
+gboolean
+_g_dbus_debug_authentication (void)
+{
+ _g_dbus_initialize ();
+ return (_gdbus_debug_flags & G_DBUS_DEBUG_AUTHENTICATION) != 0;
+}
+
+gboolean
+_g_dbus_debug_message (void)
+{
+ _g_dbus_initialize ();
+ return (_gdbus_debug_flags & G_DBUS_DEBUG_MESSAGE) != 0;
+}
+
+/**
+ * _g_dbus_initialize:
+ *
+ * Does various one-time init things such as
+ *
+ * - registering the G_DBUS_ERROR error domain
+ * - parses the G_DBUS_DEBUG environment variable
+ */
+void
+_g_dbus_initialize (void)
+{
+ static volatile gsize initialized = 0;
+
+ if (g_once_init_enter (&initialized))
+ {
+ volatile GQuark g_dbus_error_domain;
+ const gchar *debug;
+
+ g_dbus_error_domain = G_DBUS_ERROR;
+
+ debug = g_getenv ("G_DBUS_DEBUG");
+ if (debug != NULL)
+ {
+ gchar **tokens;
+ guint n;
+ tokens = g_strsplit (debug, ",", 0);
+ for (n = 0; tokens[n] != NULL; n++)
+ {
+ if (g_strcmp0 (tokens[n], "authentication") == 0)
+ _gdbus_debug_flags |= G_DBUS_DEBUG_AUTHENTICATION;
+ else if (g_strcmp0 (tokens[n], "message") == 0)
+ _gdbus_debug_flags |= G_DBUS_DEBUG_MESSAGE;
+ else if (g_strcmp0 (tokens[n], "all") == 0)
+ _gdbus_debug_flags |= G_DBUS_DEBUG_ALL;
+ }
+ g_strfreev (tokens);
+ }
+
+ g_once_init_leave (&initialized, 1);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gchar *
+_g_dbus_compute_complete_signature (GDBusArgInfo **args,
+ gboolean include_parentheses)
+{
+ GString *s;
+ guint n;
+
+ if (include_parentheses)
+ s = g_string_new ("(");
+ else
+ s = g_string_new ("");
+ if (args != NULL)
+ for (n = 0; args[n] != NULL; n++)
+ g_string_append (s, args[n]->signature);
+
+ if (include_parentheses)
+ g_string_append_c (s, ')');
+
+ return g_string_free (s, FALSE);
+}
diff --git a/gio/gdbusprivate.h b/gio/gdbusprivate.h
new file mode 100644
index 000000000..ef7fa0ac5
--- /dev/null
+++ b/gio/gdbusprivate.h
@@ -0,0 +1,83 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusprivate.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_PRIVATE_H__
+#define __G_DBUS_PRIVATE_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct GDBusWorker GDBusWorker;
+
+typedef void (*GDBusWorkerMessageReceivedCallback) (GDBusWorker *worker,
+ GDBusMessage *message,
+ gpointer user_data);
+
+typedef void (*GDBusWorkerDisconnectedCallback) (GDBusWorker *worker,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data);
+
+/* This function may be called from any thread - callbacks will be in the shared private message thread
+ * and must not block.
+ */
+GDBusWorker *_g_dbus_worker_new (GIOStream *stream,
+ GDBusCapabilityFlags capabilities,
+ GDBusWorkerMessageReceivedCallback message_received_callback,
+ GDBusWorkerDisconnectedCallback disconnected_callback,
+ gpointer user_data);
+
+/* can be called from any thread - steals blob */
+void _g_dbus_worker_send_message (GDBusWorker *worker,
+ GDBusMessage *message,
+ gchar *blob,
+ gsize blob_len);
+
+/* can be called from any thread */
+void _g_dbus_worker_stop (GDBusWorker *worker);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void _g_dbus_initialize (void);
+gboolean _g_dbus_debug_authentication (void);
+gboolean _g_dbus_debug_message (void);
+
+gboolean _g_dbus_address_parse_entry (const gchar *address_entry,
+ gchar **out_transport_name,
+ GHashTable **out_key_value_pairs,
+ GError **error);
+
+gchar * _g_dbus_compute_complete_signature (GDBusArgInfo **args,
+ gboolean include_parentheses);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PRIVATE_H__ */
diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c
new file mode 100644
index 000000000..39094d0b4
--- /dev/null
+++ b/gio/gdbusproxy.c
@@ -0,0 +1,1542 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "gdbusutils.h"
+#include "gdbusproxy.h"
+#include "gioenumtypes.h"
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gio-marshal.h"
+#include "ginitable.h"
+#include "gasyncinitable.h"
+#include "gioerror.h"
+#include "gasyncresult.h"
+#include "gsimpleasyncresult.h"
+
+/**
+ * SECTION:gdbusproxy
+ * @short_description: Base class for proxies
+ * @include: gdbus/gdbus.h
+ *
+ * #GDBusProxy is a base class used for proxies to access a D-Bus
+ * interface on a remote object. A #GDBusProxy can only be constructed
+ * for unique name bus and does not track whether the name
+ * vanishes. Use g_bus_watch_proxy() to construct #GDBusProxy proxies
+ * for owners of a well-known names.
+ */
+
+struct _GDBusProxyPrivate
+{
+ GDBusConnection *connection;
+ GDBusProxyFlags flags;
+ gchar *unique_bus_name;
+ gchar *object_path;
+ gchar *interface_name;
+ gint timeout_msec;
+
+ /* gchar* -> GVariant* */
+ GHashTable *properties;
+
+ GDBusInterfaceInfo *expected_interface;
+
+ guint properties_changed_subscriber_id;
+ guint signals_subscriber_id;
+};
+
+enum
+{
+ PROP_0,
+ PROP_G_CONNECTION,
+ PROP_G_UNIQUE_BUS_NAME,
+ PROP_G_FLAGS,
+ PROP_G_OBJECT_PATH,
+ PROP_G_INTERFACE_NAME,
+ PROP_G_DEFAULT_TIMEOUT,
+ PROP_G_INTERFACE_INFO
+};
+
+enum
+{
+ PROPERTIES_CHANGED_SIGNAL,
+ SIGNAL_SIGNAL,
+ LAST_SIGNAL,
+};
+
+static void g_dbus_proxy_constructed (GObject *object);
+
+guint signals[LAST_SIGNAL] = {0};
+
+static void initable_iface_init (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusProxy, g_dbus_proxy, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+ );
+
+static void
+g_dbus_proxy_finalize (GObject *object)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+ if (proxy->priv->properties_changed_subscriber_id > 0)
+ {
+ g_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+ proxy->priv->properties_changed_subscriber_id);
+ }
+
+ if (proxy->priv->signals_subscriber_id > 0)
+ {
+ g_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+ proxy->priv->signals_subscriber_id);
+ }
+
+ g_object_unref (proxy->priv->connection);
+ g_free (proxy->priv->unique_bus_name);
+ g_free (proxy->priv->object_path);
+ g_free (proxy->priv->interface_name);
+ if (proxy->priv->properties != NULL)
+ g_hash_table_unref (proxy->priv->properties);
+
+ if (proxy->priv->expected_interface != NULL)
+ g_dbus_interface_info_unref (proxy->priv->expected_interface);
+
+ if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_proxy_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_proxy_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+ switch (prop_id)
+ {
+ case PROP_G_CONNECTION:
+ g_value_set_object (value, proxy->priv->connection);
+ break;
+
+ case PROP_G_FLAGS:
+ g_value_set_flags (value, proxy->priv->flags);
+ break;
+
+ case PROP_G_UNIQUE_BUS_NAME:
+ g_value_set_string (value, proxy->priv->unique_bus_name);
+ break;
+
+ case PROP_G_OBJECT_PATH:
+ g_value_set_string (value, proxy->priv->object_path);
+ break;
+
+ case PROP_G_INTERFACE_NAME:
+ g_value_set_string (value, proxy->priv->interface_name);
+ break;
+
+ case PROP_G_DEFAULT_TIMEOUT:
+ g_value_set_int (value, proxy->priv->timeout_msec);
+ break;
+
+ case PROP_G_INTERFACE_INFO:
+ g_value_set_boxed (value, g_dbus_proxy_get_interface_info (proxy));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_proxy_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+ switch (prop_id)
+ {
+ case PROP_G_CONNECTION:
+ proxy->priv->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_G_FLAGS:
+ proxy->priv->flags = g_value_get_flags (value);
+ break;
+
+ case PROP_G_UNIQUE_BUS_NAME:
+ proxy->priv->unique_bus_name = g_value_dup_string (value);
+ break;
+
+ case PROP_G_OBJECT_PATH:
+ proxy->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_G_INTERFACE_NAME:
+ proxy->priv->interface_name = g_value_dup_string (value);
+ break;
+
+ case PROP_G_DEFAULT_TIMEOUT:
+ g_dbus_proxy_set_default_timeout (proxy, g_value_get_int (value));
+ break;
+
+ case PROP_G_INTERFACE_INFO:
+ g_dbus_proxy_set_interface_info (proxy, g_value_get_boxed (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_proxy_class_init (GDBusProxyClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_proxy_finalize;
+ gobject_class->set_property = g_dbus_proxy_set_property;
+ gobject_class->get_property = g_dbus_proxy_get_property;
+ gobject_class->constructed = g_dbus_proxy_constructed;
+
+ /* Note that all property names are prefixed to avoid collisions with D-Bus property names
+ * in derived classes */
+
+ /**
+ * GDBusProxy:g-interface-info:
+ *
+ * Ensure that interactions with this proxy conform to the given
+ * interface. For example, when completing a method call, if the
+ * type signature of the message isn't what's expected, the given
+ * #GError is set. Signals that have a type signature mismatch are
+ * simply dropped.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_INTERFACE_INFO,
+ g_param_spec_boxed ("g-interface-info",
+ _("Interface Information"),
+ _("Interface Information"),
+ G_TYPE_DBUS_INTERFACE_INFO,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-connection:
+ *
+ * The #GDBusConnection the proxy is for.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_CONNECTION,
+ g_param_spec_object ("g-connection",
+ _("g-connection"),
+ _("The connection the proxy is for"),
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-flags:
+ *
+ * Flags from the #GDBusProxyFlags enumeration.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_FLAGS,
+ g_param_spec_flags ("g-flags",
+ _("g-flags"),
+ _("Flags for the proxy"),
+ G_TYPE_DBUS_PROXY_FLAGS,
+ G_DBUS_PROXY_FLAGS_NONE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-unique-bus-name:
+ *
+ * The unique bus name the proxy is for.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_UNIQUE_BUS_NAME,
+ g_param_spec_string ("g-unique-bus-name",
+ _("g-unique-bus-name"),
+ _("The unique bus name the proxy is for"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-object-path:
+ *
+ * The object path the proxy is for.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_OBJECT_PATH,
+ g_param_spec_string ("g-object-path",
+ _("g-object-path"),
+ _("The object path the proxy is for"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-interface-name:
+ *
+ * The D-Bus interface name the proxy is for.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_INTERFACE_NAME,
+ g_param_spec_string ("g-interface-name",
+ _("g-interface-name"),
+ _("The D-Bus interface name the proxy is for"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy:g-default-timeout:
+ *
+ * The timeout to use if -1 (specifying default timeout) is passed
+ * as @timeout_msec in the g_dbus_proxy_invoke_method() and
+ * g_dbus_proxy_invoke_method_sync() functions.
+ *
+ * This allows applications to set a proxy-wide timeout for all
+ * remote method invocations on the proxy. If this property is -1,
+ * the default timeout (typically 25 seconds) is used. If set to
+ * %G_MAXINT, then no timeout is used.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_G_DEFAULT_TIMEOUT,
+ g_param_spec_int ("g-default-timeout",
+ _("Default Timeout"),
+ _("Timeout for remote method invocation"),
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusProxy::g-properties-changed:
+ * @proxy: The #GDBusProxy emitting the signal.
+ * @changed_properties: A #GHashTable containing the properties that changed.
+ *
+ * Emitted when one or more D-Bus properties on @proxy changes. The cached properties
+ * are already replaced when this signal fires.
+ */
+ signals[PROPERTIES_CHANGED_SIGNAL] = g_signal_new ("g-properties-changed",
+ G_TYPE_DBUS_PROXY,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GDBusProxyClass, g_properties_changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_HASH_TABLE);
+
+ /**
+ * GDBusProxy::g-signal:
+ * @proxy: The #GDBusProxy emitting the signal.
+ * @sender_name: The sender of the signal or %NULL if the connection is not a bus connection.
+ * @signal_name: The name of the signal.
+ * @parameters: A #GVariant tuple with parameters for the signal.
+ *
+ * Emitted when a signal from the remote object and interface that @proxy is for, has been received.
+ **/
+ signals[SIGNAL_SIGNAL] = g_signal_new ("g-signal",
+ G_TYPE_DBUS_PROXY,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GDBusProxyClass, g_signal),
+ NULL,
+ NULL,
+ _gio_marshal_VOID__STRING_STRING_BOXED,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_VARIANT);
+
+
+ g_type_class_add_private (klass, sizeof (GDBusProxyPrivate));
+}
+
+static void
+g_dbus_proxy_init (GDBusProxy *proxy)
+{
+ proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, G_TYPE_DBUS_PROXY, GDBusProxyPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_get_cached_property_names:
+ * @proxy: A #GDBusProxy.
+ * @error: Return location for error or %NULL.
+ *
+ * Gets the names of all cached properties on @proxy.
+ *
+ * Returns: A %NULL-terminated array of strings or %NULL if @error is set. Free with
+ * g_strfreev().
+ */
+gchar **
+g_dbus_proxy_get_cached_property_names (GDBusProxy *proxy,
+ GError **error)
+{
+ gchar **names;
+ GPtrArray *p;
+ GHashTableIter iter;
+ const gchar *key;
+
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ names = NULL;
+
+ if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Properties are not available (proxy created with G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+ goto out;
+ }
+
+ p = g_ptr_array_new ();
+
+ g_hash_table_iter_init (&iter, proxy->priv->properties);
+ while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL))
+ {
+ g_ptr_array_add (p, g_strdup (key));
+ }
+ g_ptr_array_sort (p, (GCompareFunc) g_strcmp0);
+ g_ptr_array_add (p, NULL);
+
+ names = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+ return names;
+}
+
+/**
+ * g_dbus_proxy_get_cached_property:
+ * @proxy: A #GDBusProxy.
+ * @property_name: Property name.
+ * @error: Return location for error or %NULL.
+ *
+ * Looks up the value for a property from the cache. This call does no blocking IO.
+ *
+ * Normally you will not need to modify the returned variant since it is updated automatically
+ * in response to <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+ * D-Bus signals (which also causes #GDBusProxy::g-properties-changed to be emitted).
+ *
+ * However, for properties for which said D-Bus signal is not emitted, you
+ * can catch other signals and modify the returned variant accordingly (remember to emit
+ * #GDBusProxy::g-properties-changed yourself).
+ *
+ * Returns: A reference to the #GVariant instance that holds the value for @property_name or
+ * %NULL if @error is set. Free the reference with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_get_cached_property (GDBusProxy *proxy,
+ const gchar *property_name,
+ GError **error)
+{
+ GVariant *value;
+
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ g_return_val_if_fail (property_name != NULL, NULL);
+
+ value = NULL;
+
+ if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Properties are not available (proxy created with G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+ goto out;
+ }
+
+ value = g_hash_table_lookup (proxy->priv->properties, property_name);
+ if (value == NULL)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("No property with name %s"),
+ property_name);
+ goto out;
+ }
+
+ g_variant_ref (value);
+
+ out:
+
+ return value;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_signal_received (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+
+ g_signal_emit (proxy,
+ signals[SIGNAL_SIGNAL],
+ 0,
+ sender_name,
+ signal_name,
+ parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_properties_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+ GError *error;
+ const gchar *interface_name_for_signal;
+ GVariantIter *iter;
+ GVariant *item;
+ GHashTable *changed_properties;
+
+ error = NULL;
+ iter = NULL;
+
+#if 0 // TODO!
+ /* Ignore this signal if properties are not yet available
+ *
+ * (can happen in the window between subscribing to PropertiesChanged() and until
+ * org.freedesktop.DBus.Properties.GetAll() returns)
+ */
+ if (!proxy->priv->properties_available)
+ goto out;
+#endif
+
+ if (strcmp (g_variant_get_type_string (parameters), "(sa{sv})") != 0)
+ {
+ g_warning ("Value for PropertiesChanged signal with type `%s' does not match `(sa{sv})'",
+ g_variant_get_type_string (parameters));
+ goto out;
+ }
+
+ g_variant_get (parameters,
+ "(sa{sv})",
+ &interface_name_for_signal,
+ &iter);
+
+ if (g_strcmp0 (interface_name_for_signal, proxy->priv->interface_name) != 0)
+ goto out;
+
+ changed_properties = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_variant_unref);
+
+ while ((item = g_variant_iter_next_value (iter)))
+ {
+ const gchar *key;
+ GVariant *value;
+
+ g_variant_get (item,
+ "{sv}",
+ &key,
+ &value);
+
+ g_hash_table_insert (proxy->priv->properties,
+ g_strdup (key),
+ value); /* steals value */
+
+ g_hash_table_insert (changed_properties,
+ g_strdup (key),
+ g_variant_ref (value));
+ }
+
+
+ /* emit signal */
+ g_signal_emit (proxy, signals[PROPERTIES_CHANGED_SIGNAL], 0, changed_properties);
+
+ g_hash_table_unref (changed_properties);
+
+ out:
+ if (iter != NULL)
+ g_variant_iter_free (iter);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_proxy_constructed (GObject *object)
+{
+ if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+subscribe_to_signals (GDBusProxy *proxy)
+{
+ if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+ {
+ /* subscribe to PropertiesChanged() */
+ proxy->priv->properties_changed_subscriber_id =
+ g_dbus_connection_signal_subscribe (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ proxy->priv->object_path,
+ proxy->priv->interface_name,
+ on_properties_changed,
+ proxy,
+ NULL);
+ }
+
+ if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS))
+ {
+ /* subscribe to all signals for the object */
+ proxy->priv->signals_subscriber_id =
+ g_dbus_connection_signal_subscribe (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ proxy->priv->interface_name,
+ NULL, /* member */
+ proxy->priv->object_path,
+ NULL, /* arg0 */
+ on_signal_received,
+ proxy,
+ NULL);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+process_get_all_reply (GDBusProxy *proxy,
+ GVariant *result)
+{
+ GVariantIter iter;
+ GVariant *item;
+
+ if (strcmp (g_variant_get_type_string (result), "(a{sv})") != 0)
+ {
+ g_warning ("Value for GetAll reply with type `%s' does not match `(a{sv})'",
+ g_variant_get_type_string (result));
+ goto out;
+ }
+
+ proxy->priv->properties = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_variant_unref);
+
+ g_variant_iter_init (&iter, g_variant_get_child_value (result, 0));
+ while ((item = g_variant_iter_next_value (&iter)) != NULL)
+ {
+ const gchar *key;
+ GVariant *value;
+
+ g_variant_get (item,
+ "{sv}",
+ &key,
+ &value);
+ //g_print ("got %s -> %s\n", key, g_variant_markup_print (value, FALSE, 0, 0));
+
+ g_hash_table_insert (proxy->priv->properties,
+ g_strdup (key),
+ value); /* steals value */
+ }
+ out:
+ ;
+}
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (initable);
+ GVariant *result;
+ gboolean ret;
+
+ ret = FALSE;
+
+ if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+ {
+ /* load all properties synchronously */
+ result = g_dbus_connection_invoke_method_sync (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ proxy->priv->object_path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)", proxy->priv->interface_name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1, /* timeout */
+ cancellable,
+ error);
+ if (result == NULL)
+ goto out;
+
+ process_get_all_reply (proxy, result);
+
+ g_variant_unref (result);
+ }
+
+ subscribe_to_signals (proxy);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+ initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_all_cb (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ GVariant *result;
+ GError *error;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ if (result == NULL)
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (simple,
+ result,
+ (GDestroyNotify) g_variant_unref);
+ }
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+async_initable_init_async (GAsyncInitable *initable,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (initable);
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new (G_OBJECT (proxy),
+ callback,
+ user_data,
+ NULL);
+
+ if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+ {
+ /* load all properties asynchronously */
+ g_dbus_connection_invoke_method (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ proxy->priv->object_path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)", proxy->priv->interface_name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1, /* timeout */
+ cancellable,
+ (GAsyncReadyCallback) get_all_cb,
+ simple);
+ }
+ else
+ {
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ }
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable *initable,
+ GAsyncResult *res,
+ GError **error)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (initable);
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GVariant *result;
+ gboolean ret;
+
+ ret = FALSE;
+
+ result = g_simple_async_result_get_op_res_gpointer (simple);
+ if (result == NULL)
+ {
+ if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+ {
+ g_simple_async_result_propagate_error (simple, error);
+ goto out;
+ }
+ }
+ else
+ {
+ process_get_all_reply (proxy, result);
+ }
+
+ subscribe_to_signals (proxy);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+ async_initable_iface->init_async = async_initable_init_async;
+ async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_new:
+ * @connection: A #GDBusConnection.
+ * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: Callback function to invoke when the proxy is ready.
+ * @user_data: User data to pass to @callback.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and asynchronously loads D-Bus properties unless the
+ * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used. Connect to the
+ * #GDBusProxy::g-properties-changed signal to get notified about property changes.
+ *
+ * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #GDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a failable asynchronous constructor - when the proxy is
+ * ready, @callback will be invoked and you can use
+ * g_dbus_proxy_new_finish() to get the result.
+ *
+ * See g_dbus_proxy_new_sync() and for a synchronous version of this constructor.
+ **/
+void
+g_dbus_proxy_new (GDBusConnection *connection,
+ GType object_type,
+ GDBusProxyFlags flags,
+ GDBusInterfaceInfo *info,
+ const gchar *unique_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+ g_return_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY));
+ g_return_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
+ g_dbus_is_unique_name (unique_bus_name));
+ g_return_if_fail (g_variant_is_object_path (object_path));
+ g_return_if_fail (g_dbus_is_interface_name (interface_name));
+
+ g_async_initable_new_async (object_type,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ "g-flags", flags,
+ "g-interface-info", info,
+ "g-unique-bus-name", unique_bus_name,
+ "g-connection", connection,
+ "g-object-path", object_path,
+ "g-interface-name", interface_name,
+ NULL);
+}
+
+/**
+ * g_dbus_proxy_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to g_dbus_proxy_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes creating a #GDBusProxy.
+ *
+ * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
+ **/
+GDBusProxy *
+g_dbus_proxy_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *object;
+ GObject *source_object;
+
+ source_object = g_async_result_get_source_object (res);
+ g_assert (source_object != NULL);
+
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+ res,
+ error);
+ g_object_unref (source_object);
+
+ if (object != NULL)
+ return G_DBUS_PROXY (object);
+ else
+ return NULL;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_new_sync:
+ * @connection: A #GDBusConnection.
+ * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and synchronously loads D-Bus properties unless the
+ * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used.
+ *
+ * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #GDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a synchronous failable constructor. See g_dbus_proxy_new()
+ * and g_dbus_proxy_new_finish() for the asynchronous version.
+ *
+ * Returns: A #GDBusProxy or %NULL if error is set. Free with g_object_unref().
+ **/
+GDBusProxy *
+g_dbus_proxy_new_sync (GDBusConnection *connection,
+ GType object_type,
+ GDBusProxyFlags flags,
+ GDBusInterfaceInfo *info,
+ const gchar *unique_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInitable *initable;
+
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY), NULL);
+ g_return_val_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
+ g_dbus_is_unique_name (unique_bus_name), NULL);
+ g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
+ g_return_val_if_fail (g_dbus_is_interface_name (interface_name), NULL);
+
+ initable = g_initable_new (object_type,
+ cancellable,
+ error,
+ "g-flags", flags,
+ "g-interface-info", info,
+ "g-unique-bus-name", unique_bus_name,
+ "g-connection", connection,
+ "g-object-path", object_path,
+ "g-interface-name", interface_name,
+ NULL);
+ if (initable != NULL)
+ return G_DBUS_PROXY (initable);
+ else
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_get_connection:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the connection @proxy is for.
+ *
+ * Returns: A #GDBusConnection owned by @proxy. Do not free.
+ **/
+GDBusConnection *
+g_dbus_proxy_get_connection (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ return proxy->priv->connection;
+}
+
+/**
+ * g_dbus_proxy_get_flags:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the flags that @proxy was constructed with.
+ *
+ * Returns: Flags from the #GDBusProxyFlags enumeration.
+ **/
+GDBusProxyFlags
+g_dbus_proxy_get_flags (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), 0);
+ return proxy->priv->flags;
+}
+
+/**
+ * g_dbus_proxy_get_unique_bus_name:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the unique bus name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_unique_bus_name (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ return proxy->priv->unique_bus_name;
+}
+
+/**
+ * g_dbus_proxy_get_object_path:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the object path @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_object_path (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ return proxy->priv->object_path;
+}
+
+/**
+ * g_dbus_proxy_get_interface_name:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the D-Bus interface name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_interface_name (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ return proxy->priv->interface_name;
+}
+
+/**
+ * g_dbus_proxy_get_default_timeout:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the g_dbus_proxy_invoke_method() and
+ * g_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #GDBusProxy:g-default-timeout property for more details.
+ *
+ * Returns: Timeout to use for @proxy.
+ */
+gint
+g_dbus_proxy_get_default_timeout (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), -1);
+ return proxy->priv->timeout_msec;
+}
+
+/**
+ * g_dbus_proxy_set_default_timeout:
+ * @proxy: A #GDBusProxy.
+ * @timeout_msec: Timeout in milliseconds.
+ *
+ * Sets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the g_dbus_proxy_invoke_method() and
+ * g_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #GDBusProxy:g-default-timeout property for more details.
+ */
+void
+g_dbus_proxy_set_default_timeout (GDBusProxy *proxy,
+ gint timeout_msec)
+{
+ g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+ g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0);
+
+ /* TODO: locking? */
+ if (proxy->priv->timeout_msec != timeout_msec)
+ {
+ proxy->priv->timeout_msec = timeout_msec;
+ g_object_notify (G_OBJECT (proxy), "g-default-timeout");
+ }
+}
+
+/**
+ * g_dbus_proxy_get_interface_info:
+ * @proxy: A #GDBusProxy
+ *
+ * Returns the #GDBusInterfaceInfo, if any, specifying the minimal
+ * interface that @proxy conforms to.
+ *
+ * See the #GDBusProxy:g-interface-info property for more details.
+ *
+ * Returns: A #GDBusInterfaceInfo or %NULL. Do not unref the returned
+ * object, it is owned by @proxy.
+ */
+GDBusInterfaceInfo *
+g_dbus_proxy_get_interface_info (GDBusProxy *proxy)
+{
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ return proxy->priv->expected_interface;
+}
+
+/**
+ * g_dbus_proxy_set_interface_info:
+ * @proxy: A #GDBusProxy
+ * @info: Minimum interface this proxy conforms to or %NULL to unset.
+ *
+ * Ensure that interactions with @proxy conform to the given
+ * interface. For example, when completing a method call, if the type
+ * signature of the message isn't what's expected, the given #GError
+ * is set. Signals that have a type signature mismatch are simply
+ * dropped.
+ *
+ * See the #GDBusProxy:g-interface-info property for more details.
+ */
+void
+g_dbus_proxy_set_interface_info (GDBusProxy *proxy,
+ GDBusInterfaceInfo *info)
+{
+ g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+ if (proxy->priv->expected_interface != NULL)
+ g_dbus_interface_info_unref (proxy->priv->expected_interface);
+ proxy->priv->expected_interface = info != NULL ? g_dbus_interface_info_ref (info) : NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+maybe_split_method_name (const gchar *method_name,
+ gchar **out_interface_name,
+ const gchar **out_method_name)
+{
+ gboolean was_split;
+
+ was_split = FALSE;
+ g_assert (out_interface_name != NULL);
+ g_assert (out_method_name != NULL);
+ *out_interface_name = NULL;
+ *out_method_name = NULL;
+
+ if (strchr (method_name, '.') != NULL)
+ {
+ gchar *p;
+ gchar *last_dot;
+
+ p = g_strdup (method_name);
+ last_dot = strrchr (p, '.');
+ *last_dot = '\0';
+
+ *out_interface_name = p;
+ *out_method_name = last_dot + 1;
+
+ was_split = TRUE;
+ }
+
+ return was_split;
+}
+
+
+static void
+reply_cb (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ GVariant *value;
+ GError *error;
+
+ error = NULL;
+ value = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (simple,
+ error);
+ g_error_free (error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (simple,
+ value,
+ (GDestroyNotify) g_variant_unref);
+ }
+
+ /* no need to complete in idle since the method GDBusConnection already does */
+ g_simple_async_result_complete (simple);
+}
+
+static const GDBusMethodInfo *
+lookup_method_info_or_warn (GDBusProxy *proxy,
+ const char *method_name)
+{
+ const GDBusMethodInfo *info;
+
+ if (!proxy->priv->expected_interface)
+ return NULL;
+
+ info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name);
+ if (!info)
+ g_warning ("Trying to invoke method %s which isn't in expected interface %s",
+ method_name, proxy->priv->expected_interface->name);
+
+ return info;
+}
+
+static gboolean
+validate_method_return (const char *method_name,
+ GVariant *value,
+ const GDBusMethodInfo *expected_method_info,
+ GError **error)
+{
+ const gchar *type_string;
+ gchar *signature;
+ gboolean ret;
+
+ ret = TRUE;
+ signature = NULL;
+
+ if (value == NULL || expected_method_info == NULL)
+ goto out;
+
+ /* Shouldn't happen... */
+ if (g_variant_classify (value) != G_VARIANT_CLASS_TUPLE)
+ goto out;
+
+ type_string = g_variant_get_type_string (value);
+ signature = _g_dbus_compute_complete_signature (expected_method_info->out_args, TRUE);
+ if (g_strcmp0 (type_string, signature) != 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Method `%s' returned signature `%s', but expected `%s'"),
+ method_name,
+ type_string,
+ signature);
+ ret = FALSE;
+ }
+
+ out:
+ g_free (signature);
+ return ret;
+}
+
+/**
+ * g_dbus_proxy_invoke_method:
+ * @proxy: A #GDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #GDBusConnection associated with @proxy is closed then
+ * the operation will fail with %G_IO_ERROR_CLOSED. If
+ * @cancellable is canceled, the operation will fail with
+ * %G_IO_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_proxy_invoke_method_finish() to get the result of the operation.
+ * See g_dbus_proxy_invoke_method_sync() for the
+ * synchronous version of this method.
+ */
+void
+g_dbus_proxy_invoke_method (GDBusProxy *proxy,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ gboolean was_split;
+ gchar *split_interface_name;
+ const gchar *split_method_name;
+ const GDBusMethodInfo *expected_method_info;
+ const gchar *target_method_name;
+ const gchar *target_interface_name;
+
+ g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+ g_return_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name));
+ g_return_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+ g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0);
+
+ simple = g_simple_async_result_new (G_OBJECT (proxy),
+ callback,
+ user_data,
+ g_dbus_proxy_invoke_method);
+
+ was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+ target_method_name = was_split ? split_method_name : method_name;
+ target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name;
+
+ g_object_set_data_full (G_OBJECT (simple), "-gdbus-proxy-method-name", g_strdup (target_method_name), g_free);
+
+ /* Just warn here */
+ expected_method_info = lookup_method_info_or_warn (proxy, target_method_name);
+
+ g_dbus_connection_invoke_method (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ proxy->priv->object_path,
+ target_interface_name,
+ target_method_name,
+ parameters,
+ flags,
+ timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+ cancellable,
+ (GAsyncReadyCallback) reply_cb,
+ simple);
+
+ g_free (split_interface_name);
+}
+
+/**
+ * g_dbus_proxy_invoke_method_finish:
+ * @proxy: A #GDBusProxy.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_proxy_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_proxy_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_invoke_method_finish (GDBusProxy *proxy,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GVariant *value;
+ const char *method_name;
+ const GDBusMethodInfo *expected_method_info;
+
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_proxy_invoke_method);
+
+ value = NULL;
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ value = g_simple_async_result_get_op_res_gpointer (simple);
+ method_name = g_object_get_data (G_OBJECT (simple), "-gdbus-proxy-method-name");
+
+ /* We may not have a method name for internally-generated proxy calls like GetAll */
+ if (value && method_name && proxy->priv->expected_interface)
+ {
+ expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name);
+ if (!validate_method_return (method_name, value, expected_method_info, error))
+ {
+ g_variant_unref (value);
+ value = NULL;
+ }
+ }
+
+ out:
+ return value;
+}
+
+/**
+ * g_dbus_proxy_invoke_method_sync:
+ * @proxy: A #GDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #GDBusConnection associated with @proxy is disconnected then
+ * the operation will fail with %G_IO_ERROR_CLOSED. If
+ * @cancellable is canceled, the operation will fail with
+ * %G_IO_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * g_dbus_proxy_invoke_method() for the asynchronous version of this
+ * method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_invoke_method_sync (GDBusProxy *proxy,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariant *ret;
+ gboolean was_split;
+ gchar *split_interface_name;
+ const gchar *split_method_name;
+ const GDBusMethodInfo *expected_method_info;
+ const char *target_method_name;
+ const char *target_interface_name;
+
+ g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+ g_return_val_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name), NULL);
+ g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+ g_return_val_if_fail (timeout_msec == -1 || timeout_msec >= 0, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+ target_method_name = was_split ? split_method_name : method_name;
+ target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name;
+
+ if (proxy->priv->expected_interface)
+ {
+ expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, target_method_name);
+ if (!expected_method_info)
+ g_warning ("Trying to invoke method %s which isn't in expected interface %s",
+ target_method_name, target_interface_name);
+ }
+ else
+ {
+ expected_method_info = NULL;
+ }
+
+ ret = g_dbus_connection_invoke_method_sync (proxy->priv->connection,
+ proxy->priv->unique_bus_name,
+ proxy->priv->object_path,
+ target_interface_name,
+ target_method_name,
+ parameters,
+ flags,
+ timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+ cancellable,
+ error);
+ if (!validate_method_return (target_method_name, ret, expected_method_info, error))
+ {
+ g_variant_unref (ret);
+ ret = NULL;
+ }
+
+ g_free (split_interface_name);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusproxy.h b/gio/gdbusproxy.h
new file mode 100644
index 000000000..1bc9902d0
--- /dev/null
+++ b/gio/gdbusproxy.h
@@ -0,0 +1,146 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_PROXY_H__
+#define __G_DBUS_PROXY_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusintrospection.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_PROXY (g_dbus_proxy_get_type ())
+#define G_DBUS_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_PROXY, GDBusProxy))
+#define G_DBUS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_PROXY, GDBusProxyClass))
+#define G_DBUS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_PROXY, GDBusProxyClass))
+#define G_IS_DBUS_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_PROXY))
+#define G_IS_DBUS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_PROXY))
+
+typedef struct _GDBusProxyClass GDBusProxyClass;
+typedef struct _GDBusProxyPrivate GDBusProxyPrivate;
+
+/**
+ * GDBusProxy:
+ *
+ * The #GDBusProxy structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusProxy
+{
+ /*< private >*/
+ GObject parent_instance;
+ GDBusProxyPrivate *priv;
+};
+
+/**
+ * GDBusProxyClass:
+ * @g_properties_changed: Signal class handler for the #GDBusProxy::g-properties-changed signal.
+ * @g_signal: Signal class handler for the #GDBusProxy::g-signal signal.
+ *
+ * Class structure for #GDBusProxy.
+ */
+struct _GDBusProxyClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ /* Signals */
+ void (*g_properties_changed) (GDBusProxy *proxy,
+ GHashTable *changed_properties);
+ void (*g_signal) (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters);
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+GType g_dbus_proxy_get_type (void) G_GNUC_CONST;
+void g_dbus_proxy_new (GDBusConnection *connection,
+ GType object_type,
+ GDBusProxyFlags flags,
+ GDBusInterfaceInfo *info,
+ const gchar *unique_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GDBusProxy *g_dbus_proxy_new_finish (GAsyncResult *res,
+ GError **error);
+GDBusProxy *g_dbus_proxy_new_sync (GDBusConnection *connection,
+ GType object_type,
+ GDBusProxyFlags flags,
+ GDBusInterfaceInfo *info,
+ const gchar *unique_bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GCancellable *cancellable,
+ GError **error);
+GDBusConnection *g_dbus_proxy_get_connection (GDBusProxy *proxy);
+GDBusProxyFlags g_dbus_proxy_get_flags (GDBusProxy *proxy);
+const gchar *g_dbus_proxy_get_unique_bus_name (GDBusProxy *proxy);
+const gchar *g_dbus_proxy_get_object_path (GDBusProxy *proxy);
+const gchar *g_dbus_proxy_get_interface_name (GDBusProxy *proxy);
+gint g_dbus_proxy_get_default_timeout (GDBusProxy *proxy);
+void g_dbus_proxy_set_default_timeout (GDBusProxy *proxy,
+ gint timeout_msec);
+GDBusInterfaceInfo *g_dbus_proxy_get_interface_info (GDBusProxy *proxy);
+void g_dbus_proxy_set_interface_info (GDBusProxy *proxy,
+ GDBusInterfaceInfo *info);
+GVariant *g_dbus_proxy_get_cached_property (GDBusProxy *proxy,
+ const gchar *property_name,
+ GError **error);
+gchar **g_dbus_proxy_get_cached_property_names (GDBusProxy *proxy,
+ GError **error);
+void g_dbus_proxy_invoke_method (GDBusProxy *proxy,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GVariant *g_dbus_proxy_invoke_method_finish (GDBusProxy *proxy,
+ GAsyncResult *res,
+ GError **error);
+GVariant *g_dbus_proxy_invoke_method_sync (GDBusProxy *proxy,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusInvokeMethodFlags flags,
+ gint timeout_msec,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_H__ */
diff --git a/gio/gdbusproxywatching.c b/gio/gdbusproxywatching.c
new file mode 100644
index 000000000..4f85c1de5
--- /dev/null
+++ b/gio/gdbusproxywatching.c
@@ -0,0 +1,397 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnamewatching.h"
+#include "gdbusproxywatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusproxy.h"
+#include "gdbusnamewatching.h"
+#include "gcancellable.h"
+
+/**
+ * SECTION:gdbusproxywatching
+ * @title: Watching Proxies
+ * @short_description: Simple API for watching proxies
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus proxies.
+ *
+ * <example id="gdbus-watching-proxy"><title>Simple application watching a proxy</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-watch-proxy.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ guint id;
+ GBusProxyAppearedCallback proxy_appeared_handler;
+ GBusProxyVanishedCallback proxy_vanished_handler;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+ GMainContext *main_context;
+
+ gchar *name;
+ gchar *name_owner;
+ GDBusConnection *connection;
+ guint name_watcher_id;
+
+ GCancellable *cancellable;
+
+ gchar *object_path;
+ gchar *interface_name;
+ GType interface_type;
+ GDBusProxyFlags proxy_flags;
+ GDBusProxy *proxy;
+
+ gboolean initial_construction;
+} Client;
+
+static void
+client_unref (Client *client)
+{
+ /* ensure we're only called from g_bus_unwatch_proxy */
+ g_assert (client->name_watcher_id == 0);
+
+ g_free (client->name_owner);
+ if (client->connection != NULL)
+ g_object_unref (client->connection);
+ if (client->proxy != NULL)
+ g_object_unref (client->proxy);
+
+ g_free (client->name);
+ g_free (client->object_path);
+ g_free (client->interface_name);
+
+ if (client->main_context != NULL)
+ g_main_context_unref (client->main_context);
+
+ if (client->user_data_free_func != NULL)
+ client->user_data_free_func (client->user_data);
+ g_free (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+proxy_constructed_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Client *client = user_data;
+ GDBusProxy *proxy;
+ GError *error;
+
+ error = NULL;
+ proxy = g_dbus_proxy_new_finish (res, &error);
+ if (proxy == NULL)
+ {
+ /* g_warning ("error while constructing proxy: %s", error->message); */
+ g_error_free (error);
+
+ /* handle initial construction, send out vanished if the name
+ * is there but we constructing a proxy fails
+ */
+ if (client->initial_construction)
+ {
+ if (client->proxy_vanished_handler != NULL)
+ {
+ client->proxy_vanished_handler (client->connection,
+ client->name,
+ client->user_data);
+ }
+ client->initial_construction = FALSE;
+ }
+ }
+ else
+ {
+ g_assert (client->proxy == NULL);
+ g_assert (client->cancellable != NULL);
+ client->proxy = G_DBUS_PROXY (proxy);
+
+ g_object_unref (client->cancellable);
+ client->cancellable = NULL;
+
+ /* perform callback */
+ if (client->proxy_appeared_handler != NULL)
+ {
+ client->proxy_appeared_handler (client->connection,
+ client->name,
+ client->name_owner,
+ client->proxy,
+ client->user_data);
+ }
+ client->initial_construction = FALSE;
+ }
+}
+
+static void
+on_name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ //g_debug ("\n\nname appeared (owner `%s')", name_owner);
+
+ /* invariants */
+ g_assert (client->name_owner == NULL);
+ g_assert (client->connection == NULL);
+ g_assert (client->cancellable == NULL);
+
+ client->name_owner = g_strdup (name_owner);
+ client->connection = g_object_ref (connection);
+ client->cancellable = g_cancellable_new ();
+
+ g_dbus_proxy_new (client->connection,
+ client->interface_type,
+ client->proxy_flags,
+ NULL, /* GDBusInterfaceInfo */
+ client->name_owner,
+ client->object_path,
+ client->interface_name,
+ client->cancellable,
+ proxy_constructed_cb,
+ client);
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ Client *client = user_data;
+
+ /*g_debug ("\n\nname vanished");*/
+
+ g_free (client->name_owner);
+ if (client->connection != NULL)
+ g_object_unref (client->connection);
+ client->name_owner = NULL;
+ client->connection = NULL;
+
+ /* free the proxy if we have it */
+ if (client->proxy != NULL)
+ {
+ g_assert (client->cancellable == NULL);
+
+ g_object_unref (client->proxy);
+ client->proxy = NULL;
+
+ /* if we have the proxy, it means we last sent out a 'appeared'
+ * callback - so send out a 'vanished' callback
+ */
+ if (client->proxy_vanished_handler != NULL)
+ {
+ client->proxy_vanished_handler (client->connection,
+ client->name,
+ client->user_data);
+ }
+ client->initial_construction = FALSE;
+ }
+ else
+ {
+ /* otherwise cancel construction of the proxy if applicable */
+ if (client->cancellable != NULL)
+ {
+ g_cancellable_cancel (client->cancellable);
+ g_object_unref (client->cancellable);
+ client->cancellable = NULL;
+ }
+ else
+ {
+ /* handle initial construction, send out vanished if
+ * the name isn't there
+ */
+ if (client->initial_construction)
+ {
+ if (client->proxy_vanished_handler != NULL)
+ {
+ client->proxy_vanished_handler (client->connection,
+ client->name,
+ client->user_data);
+ }
+ client->initial_construction = FALSE;
+ }
+ }
+ }
+}
+
+/**
+ * g_bus_watch_proxy:
+ * @bus_type: The type of bus to watch a name on (can't be #G_BUS_TYPE_NONE).
+ * @name: The name (well-known or unique) to watch.
+ * @flags: Flags from the #GBusNameWatcherFlags enumeration.
+ * @object_path: The object path of the remote object to watch.
+ * @interface_name: The D-Bus interface name for the proxy.
+ * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type.
+ * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy.
+ * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the
+ * requested proxy is available.
+ * @proxy_vanished_handler: Handler to invoke when @name is known to not exist
+ * and the previously created proxy is no longer available.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching a remote object at @object_path owned by @name on
+ * the bus specified by @bus_type. When the object is available, a
+ * #GDBusProxy (or derived class cf. @interface_type) instance is
+ * constructed for the @interface_name D-Bus interface and then
+ * @proxy_appeared_handler will be called when the proxy is ready and
+ * all properties have been loaded. When @name vanishes,
+ * @proxy_vanished_handler is called.
+ *
+ * This function makes it very simple to write applications that wants
+ * to watch a well-known remote object on a well-known name, see <xref
+ * linkend="gdbus-watching-proxy"/>. Basically, the application simply
+ * starts using the proxy when @proxy_appeared_handler is called and
+ * stops using it when @proxy_vanished_handler is called. Callbacks
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * Applications typically use this function to watch the
+ * <quote>manager</quote> object of a well-known name. Upon acquiring
+ * a proxy for the manager object, applications typically construct
+ * additional proxies in response to the result of enumeration methods
+ * on the manager object.
+ *
+ * Many of the comment that applies to g_bus_watch_name() also applies
+ * here. For example, you are guaranteed that one of the handlers will
+ * be invoked (on the main thread) after calling this function and
+ * also that the two handlers alternate. When you are done watching the
+ * proxy, just call g_bus_unwatch_proxy().
+ *
+ * Returns: An identifier (never 0) that can be used with
+ * g_bus_unwatch_proxy() to stop watching the remote object.
+ **/
+guint
+g_bus_watch_proxy (GBusType bus_type,
+ const gchar *name,
+ GBusNameWatcherFlags flags,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GType interface_type,
+ GDBusProxyFlags proxy_flags,
+ GBusProxyAppearedCallback proxy_appeared_handler,
+ GBusProxyVanishedCallback proxy_vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func)
+{
+ Client *client;
+
+ g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+ g_return_val_if_fail (g_dbus_is_name (name), 0);
+ g_return_val_if_fail (g_variant_is_object_path (object_path), 0);
+ g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0);
+ g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0);
+
+ G_LOCK (lock);
+
+ client = g_new0 (Client, 1);
+ client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+ client->name = g_strdup (name);
+ client->proxy_appeared_handler = proxy_appeared_handler;
+ client->proxy_vanished_handler = proxy_vanished_handler;
+ client->user_data = user_data;
+ client->user_data_free_func = user_data_free_func;
+ client->main_context = g_main_context_get_thread_default ();
+ if (client->main_context != NULL)
+ g_main_context_ref (client->main_context);
+ client->name_watcher_id = g_bus_watch_name (bus_type,
+ name,
+ flags,
+ on_name_appeared,
+ on_name_vanished,
+ client,
+ NULL);
+
+ client->object_path = g_strdup (object_path);
+ client->interface_name = g_strdup (interface_name);
+ client->interface_type = interface_type;
+ client->proxy_flags = proxy_flags;
+ client->initial_construction = TRUE;
+
+ if (map_id_to_client == NULL)
+ {
+ map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+ }
+ g_hash_table_insert (map_id_to_client,
+ GUINT_TO_POINTER (client->id),
+ client);
+
+ G_UNLOCK (lock);
+
+ return client->id;
+}
+
+/**
+ * g_bus_unwatch_proxy:
+ * @watcher_id: An identifier obtained from g_bus_watch_proxy()
+ *
+ * Stops watching proxy.
+ */
+void
+g_bus_unwatch_proxy (guint watcher_id)
+{
+ Client *client;
+
+ g_return_if_fail (watcher_id > 0);
+
+ client = NULL;
+
+ G_LOCK (lock);
+ if (watcher_id == 0 ||
+ map_id_to_client == NULL ||
+ (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+ {
+ g_warning ("Invalid id %d passed to g_bus_unwatch_proxy()", watcher_id);
+ goto out;
+ }
+
+ g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+ G_UNLOCK (lock);
+
+ if (client != NULL)
+ {
+ g_bus_unwatch_name (client->name_watcher_id);
+ client->name_watcher_id = 0;
+ client_unref (client);
+ }
+}
diff --git a/gio/gdbusproxywatching.h b/gio/gdbusproxywatching.h
new file mode 100644
index 000000000..3544269d3
--- /dev/null
+++ b/gio/gdbusproxywatching.h
@@ -0,0 +1,77 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_PROXY_WATCHING_H__
+#define __G_DBUS_PROXY_WATCHING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusProxyAppearedCallback:
+ * @connection: The #GDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @proxy: A #GDBusProxy (or derived) instance with all properties loaded.
+ * @user_data: User data passed to g_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched is ready for use - the passed
+ * @proxy object is valid until the #GBusProxyVanishedCallback
+ * callback is invoked.
+ */
+typedef void (*GBusProxyAppearedCallback) (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy,
+ gpointer user_data);
+
+/**
+ * GBusProxyVanishedCallback:
+ * @connection: The #GDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to g_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched has vanished. The #GDBusProxy
+ * object passed in the #GBusProxyAppearedCallback callback is no
+ * longer valid.
+ */
+typedef void (*GBusProxyVanishedCallback) (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data);
+
+guint g_bus_watch_proxy (GBusType bus_type,
+ const gchar *name,
+ GBusNameWatcherFlags flags,
+ const gchar *object_path,
+ const gchar *interface_name,
+ GType interface_type,
+ GDBusProxyFlags proxy_flags,
+ GBusProxyAppearedCallback proxy_appeared_handler,
+ GBusProxyVanishedCallback proxy_vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_free_func);
+void g_bus_unwatch_proxy (guint watcher_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_WATCHING_H__ */
diff --git a/gio/gdbusserver.c b/gio/gdbusserver.c
new file mode 100644
index 000000000..0d0deeaa2
--- /dev/null
+++ b/gio/gdbusserver.c
@@ -0,0 +1,1043 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+#include "giotypes.h"
+#include "gdbusaddress.h"
+#include "gdbusutils.h"
+#include "gdbusconnection.h"
+#include "gdbusserver.h"
+#include "gioenumtypes.h"
+#include "gdbusprivate.h"
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+
+/**
+ * SECTION:gdbusserver
+ * @short_description: Helper for accepting connections
+ * @include: gdbus/gdbus.h
+ *
+ * #GDBusServer is a helper for listening to and accepting D-Bus
+ * connections.
+ *
+ * <example id="gdbus-peer-to-peer"><title>D-Bus peer-to-peer example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../../gio/tests/gdbus-example-peer.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+struct _GDBusServerPrivate
+{
+ GDBusServerFlags flags;
+ gchar *address;
+ gchar *guid;
+
+ guchar *nonce;
+ gchar *nonce_file;
+
+ gchar *client_address;
+
+ GSocketListener *listener;
+ gboolean is_using_listener;
+
+ /* The result of g_main_context_get_thread_default() when the object
+ * was created (the GObject _init() function) - this is used for delivery
+ * of the :new-connection GObject signal.
+ */
+ GMainContext *main_context_at_construction;
+
+ gboolean active;
+
+ GDBusAuthObserver *authentication_observer;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ADDRESS,
+ PROP_CLIENT_ADDRESS,
+ PROP_FLAGS,
+ PROP_GUID,
+ PROP_ACTIVE,
+ PROP_AUTHENTICATION_OBSERVER,
+};
+
+enum
+{
+ NEW_CONNECTION_SIGNAL,
+ LAST_SIGNAL,
+};
+
+guint _signals[LAST_SIGNAL] = {0};
+
+static void initable_iface_init (GInitableIface *initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusServer, g_dbus_server, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+ );
+
+static void
+g_dbus_server_finalize (GObject *object)
+{
+ GDBusServer *server = G_DBUS_SERVER (object);
+
+ if (server->priv->authentication_observer != NULL)
+ g_object_unref (server->priv->authentication_observer);
+
+ if (server->priv->listener != NULL)
+ g_object_unref (server->priv->listener);
+
+ g_free (server->priv->address);
+ g_free (server->priv->guid);
+ g_free (server->priv->client_address);
+ if (server->priv->nonce != NULL)
+ {
+ memset (server->priv->nonce, '\0', 16);
+ g_free (server->priv->nonce);
+ }
+ /* we could unlink the nonce file but I don't
+ * think it's really worth the effort/risk
+ */
+ g_free (server->priv->nonce_file);
+
+ if (server->priv->main_context_at_construction != NULL)
+ g_main_context_unref (server->priv->main_context_at_construction);
+
+ if (G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_server_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusServer *server = G_DBUS_SERVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_FLAGS:
+ g_value_set_flags (value, server->priv->flags);
+ break;
+
+ case PROP_GUID:
+ g_value_set_string (value, server->priv->guid);
+ break;
+
+ case PROP_ADDRESS:
+ g_value_set_string (value, server->priv->address);
+ break;
+
+ case PROP_CLIENT_ADDRESS:
+ g_value_set_string (value, server->priv->client_address);
+ break;
+
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, server->priv->active);
+ break;
+
+ case PROP_AUTHENTICATION_OBSERVER:
+ g_value_set_object (value, server->priv->authentication_observer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_server_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GDBusServer *server = G_DBUS_SERVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_FLAGS:
+ server->priv->flags = g_value_get_flags (value);
+ break;
+
+ case PROP_GUID:
+ server->priv->guid = g_value_dup_string (value);
+ break;
+
+ case PROP_ADDRESS:
+ server->priv->address = g_value_dup_string (value);
+ break;
+
+ case PROP_AUTHENTICATION_OBSERVER:
+ server->priv->authentication_observer = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_dbus_server_class_init (GDBusServerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_dbus_server_finalize;
+ gobject_class->set_property = g_dbus_server_set_property;
+ gobject_class->get_property = g_dbus_server_get_property;
+
+ /**
+ * GDBusServer:flags:
+ *
+ * Flags from the #GDBusServerFlags enumeration.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_FLAGS,
+ g_param_spec_flags ("flags",
+ _("Flags"),
+ _("Flags for the server"),
+ G_TYPE_DBUS_SERVER_FLAGS,
+ G_DBUS_SERVER_FLAGS_NONE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer:guid:
+ *
+ * The guid of the server.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_GUID,
+ g_param_spec_string ("guid",
+ _("GUID"),
+ _("The guid of the server"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer:address:
+ *
+ * The D-Bus address to listen on.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_ADDRESS,
+ g_param_spec_string ("address",
+ _("Address"),
+ _("The address to listen on"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer:client-address:
+ *
+ * The D-Bus address that clients can use.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_ADDRESS,
+ g_param_spec_string ("client-address",
+ _("Client Address"),
+ _("The address clients can use"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer:active:
+ *
+ * Whether the server is currently active.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_ACTIVE,
+ g_param_spec_string ("active",
+ _("Active"),
+ _("Whether the server is currently active"),
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer:authentication-observer:
+ *
+ * A #GDBusAuthObserver object to assist in the authentication process or %NULL.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_AUTHENTICATION_OBSERVER,
+ g_param_spec_object ("authentication-observer",
+ _("Authentication Observer"),
+ _("Object used to assist in the authentication process"),
+ G_TYPE_DBUS_AUTH_OBSERVER,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ /**
+ * GDBusServer::new-connection:
+ * @server: The #GDBusServer emitting the signal.
+ * @connection: A #GDBusConnection for the new connection.
+ *
+ * Emitted when a new authenticated connection has been made. Use
+ * g_dbus_connection_get_peer_credentials() to figure out what
+ * identity (if any), was authenticated.
+ *
+ * If you want to accept the connection, simply ref the @connection
+ * object. Then call g_dbus_connection_close() and unref it when you
+ * are done with it. A typical thing to do when accepting a
+ * connection is to listen to the #GDBusConnection::closed signal.
+ *
+ * If #GDBusServer:flags contains %G_DBUS_SERVER_FLAGS_RUN_IN_THREAD
+ * then the signal is emitted in a new thread dedicated to the
+ * connection. Otherwise the signal is emitted in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread that @server was constructed in.
+ */
+ _signals[NEW_CONNECTION_SIGNAL] = g_signal_new ("new-connection",
+ G_TYPE_DBUS_SERVER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GDBusServerClass, new_connection),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_DBUS_CONNECTION);
+
+
+ g_type_class_add_private (klass, sizeof (GDBusServerPrivate));
+}
+
+static void
+g_dbus_server_init (GDBusServer *server)
+{
+ server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, G_TYPE_DBUS_SERVER, GDBusServerPrivate);
+
+ server->priv->main_context_at_construction = g_main_context_get_thread_default ();
+ if (server->priv->main_context_at_construction != NULL)
+ g_main_context_ref (server->priv->main_context_at_construction);
+}
+
+static gboolean
+on_run (GSocketService *service,
+ GSocketConnection *socket_connection,
+ GObject *source_object,
+ gpointer user_data);
+
+/**
+ * g_dbus_server_new_sync:
+ * @address: A D-Bus address.
+ * @flags: Flags from the #GDBusServerFlags enumeration.
+ * @guid: A D-Bus GUID.
+ * @observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for server or %NULL.
+ *
+ * Creates a new D-Bus server that listens on the first address in
+ * @address that works.
+ *
+ * Once constructed, you can use g_dbus_server_get_client_address() to
+ * get a D-Bus address string that clients can use to connect.
+ *
+ * Connect to the #GDBusServer::new-connection signal to handle
+ * incoming connections.
+ *
+ * The returned #GDBusServer isn't active - you have to start it with
+ * g_dbus_server_start().
+ *
+ * See <xref linkend="gdbus-peer-to-peer"/> for how #GDBusServer can
+ * be used.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_server_new() for the asynchronous version.
+ *
+ * Returns: A #GDBusServer or %NULL if @error is set. Free with
+ * g_object_unref().
+ */
+GDBusServer *
+g_dbus_server_new_sync (const gchar *address,
+ GDBusServerFlags flags,
+ const gchar *guid,
+ GDBusAuthObserver *observer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusServer *server;
+
+ g_return_val_if_fail (address != NULL, NULL);
+ g_return_val_if_fail (g_dbus_is_guid (guid), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ server = g_initable_new (G_TYPE_DBUS_SERVER,
+ cancellable,
+ error,
+ "address", address,
+ "flags", flags,
+ "guid", guid,
+ "authentication-observer", observer,
+ NULL);
+ if (server != NULL)
+ {
+ /* Right now we don't have any transport not using the listener... */
+ g_assert (server->priv->is_using_listener);
+ g_signal_connect (G_SOCKET_SERVICE (server->priv->listener),
+ "run",
+ G_CALLBACK (on_run),
+ server);
+ }
+
+ return server;
+}
+
+/**
+ * g_dbus_server_get_client_address:
+ * @server: A #GDBusServer.
+ *
+ * Gets a D-Bus address string that can be used by clients to connect
+ * to @server.
+ *
+ * Returns: A D-Bus address string. Do not free, the string is owned
+ * by @server.
+ */
+const gchar *
+g_dbus_server_get_client_address (GDBusServer *server)
+{
+ g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
+ return server->priv->client_address;
+}
+
+/**
+ * g_dbus_server_get_guid:
+ * @server: A #GDBusServer.
+ *
+ * Gets the GUID for @server.
+ *
+ * Returns: A D-Bus GUID. Do not free this string, it is owned by @server.
+ */
+const gchar *
+g_dbus_server_get_guid (GDBusServer *server)
+{
+ g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
+ return server->priv->guid;
+}
+
+/**
+ * g_dbus_server_get_flags:
+ * @server: A #GDBusServer.
+ *
+ * Gets the flags for @server.
+ *
+ * Returns: A set of flags from the #GDBusServerFlags enumeration.
+ */
+GDBusServerFlags
+g_dbus_server_get_flags (GDBusServer *server)
+{
+ g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
+ return server->priv->flags;
+}
+
+/**
+ * g_dbus_server_is_active:
+ * @server: A #GDBusServer.
+ *
+ * Gets whether @server is active.
+ *
+ * Returns: %TRUE if server is active, %FALSE otherwise.
+ */
+gboolean
+g_dbus_server_is_active (GDBusServer *server)
+{
+ g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
+ return server->priv->active;
+}
+
+/**
+ * g_dbus_server_start:
+ * @server: A #GDBusServer.
+ *
+ * Starts @server.
+ */
+void
+g_dbus_server_start (GDBusServer *server)
+{
+ g_return_if_fail (G_IS_DBUS_SERVER (server));
+ if (server->priv->active)
+ return;
+ /* Right now we don't have any transport not using the listener... */
+ g_assert (server->priv->is_using_listener);
+ g_socket_service_start (G_SOCKET_SERVICE (server->priv->listener));
+ server->priv->active = TRUE;
+ g_object_notify (G_OBJECT (server), "active");
+}
+
+/**
+ * g_dbus_server_stop:
+ * @server: A #GDBusServer.
+ *
+ * Stops @server.
+ */
+void
+g_dbus_server_stop (GDBusServer *server)
+{
+ g_return_if_fail (G_IS_DBUS_SERVER (server));
+ if (!server->priv->active)
+ return;
+ /* Right now we don't have any transport not using the listener... */
+ g_assert (server->priv->is_using_listener);
+ g_socket_service_stop (G_SOCKET_SERVICE (server->priv->listener));
+ server->priv->active = FALSE;
+ g_object_notify (G_OBJECT (server), "active");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+
+static gint
+random_ascii (void)
+{
+ gint ret;
+ ret = g_random_int_range (0, 60);
+ if (ret < 25)
+ ret += 'A';
+ else if (ret < 50)
+ ret += 'a' - 25;
+ else
+ ret += '0' - 50;
+ return ret;
+}
+
+/* note that address_entry has already been validated => exactly one of path, tmpdir or abstract keys are set */
+static gboolean
+try_unix (GDBusServer *server,
+ const gchar *address_entry,
+ GHashTable *key_value_pairs,
+ GError **error)
+{
+ gboolean ret;
+ const gchar *path;
+ const gchar *tmpdir;
+ const gchar *abstract;
+ GSocketAddress *address;
+
+ ret = FALSE;
+ address = NULL;
+
+ path = g_hash_table_lookup (key_value_pairs, "path");
+ tmpdir = g_hash_table_lookup (key_value_pairs, "tmpdir");
+ abstract = g_hash_table_lookup (key_value_pairs, "abstract");
+
+ if (path != NULL)
+ {
+ address = g_unix_socket_address_new (path);
+ }
+ else if (tmpdir != NULL)
+ {
+ gint n;
+ GString *s;
+ GError *local_error;
+
+ retry:
+ s = g_string_new (tmpdir);
+ g_string_append (s, "/dbus-");
+ for (n = 0; n < 8; n++)
+ g_string_append_c (s, random_ascii ());
+
+ /* prefer abstract namespace if available */
+ if (g_unix_socket_address_abstract_names_supported ())
+ address = g_unix_socket_address_new_with_type (s->str,
+ -1,
+ G_UNIX_SOCKET_ADDRESS_ABSTRACT);
+ else
+ address = g_unix_socket_address_new (s->str);
+ g_string_free (s, TRUE);
+
+ local_error = NULL;
+ if (!g_socket_listener_add_address (server->priv->listener,
+ address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, /* source_object */
+ NULL, /* effective_address */
+ &local_error))
+ {
+ if (local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_ADDRESS_IN_USE)
+ {
+ g_error_free (local_error);
+ goto retry;
+ }
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ ret = TRUE;
+ goto out;
+ }
+ else if (abstract != NULL)
+ {
+ if (!g_unix_socket_address_abstract_names_supported ())
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Abstract name space not supported"));
+ goto out;
+ }
+ address = g_unix_socket_address_new_with_type (abstract,
+ -1,
+ G_UNIX_SOCKET_ADDRESS_ABSTRACT);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (!g_socket_listener_add_address (server->priv->listener,
+ address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, /* source_object */
+ NULL, /* effective_address */
+ error))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+
+ if (address != NULL)
+ {
+ /* Fill out client_address if the connection attempt worked */
+ if (ret)
+ {
+ server->priv->is_using_listener = TRUE;
+
+ switch (g_unix_socket_address_get_address_type (G_UNIX_SOCKET_ADDRESS (address)))
+ {
+ case G_UNIX_SOCKET_ADDRESS_ABSTRACT:
+ server->priv->client_address = g_strdup_printf ("unix:abstract=%s",
+ g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address)));
+ break;
+
+ case G_UNIX_SOCKET_ADDRESS_PATH:
+ server->priv->client_address = g_strdup_printf ("unix:path=%s",
+ g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address)));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ g_object_unref (address);
+ }
+ return ret;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* note that address_entry has already been validated =>
+ * both host and port (guranteed to be a number in [0, 65535]) are set (family is optional)
+ */
+static gboolean
+try_tcp (GDBusServer *server,
+ const gchar *address_entry,
+ GHashTable *key_value_pairs,
+ gboolean do_nonce,
+ GError **error)
+{
+ gboolean ret;
+ const gchar *host;
+ const gchar *port;
+ const gchar *family;
+ gint port_num;
+ GSocketAddress *address;
+ GResolver *resolver;
+ GList *resolved_addresses;
+ GList *l;
+
+ ret = FALSE;
+ address = NULL;
+ resolver = NULL;
+ resolved_addresses = NULL;
+
+ host = g_hash_table_lookup (key_value_pairs, "host");
+ port = g_hash_table_lookup (key_value_pairs, "port");
+ family = g_hash_table_lookup (key_value_pairs, "family");
+ if (g_hash_table_lookup (key_value_pairs, "noncefile") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Cannot specify nonce file when creating a server"));
+ goto out;
+ }
+
+ if (host == NULL)
+ host = "localhost";
+ if (port == NULL)
+ port = "0";
+ port_num = strtol (port, NULL, 10);
+
+ resolver = g_resolver_get_default ();
+ resolved_addresses = g_resolver_lookup_by_name (resolver,
+ host,
+ NULL,
+ error);
+ if (resolved_addresses == NULL)
+ {
+ goto out;
+ }
+ /* TODO: handle family */
+ for (l = resolved_addresses; l != NULL; l = l->next)
+ {
+ GInetAddress *address = G_INET_ADDRESS (l->data);
+ GSocketAddress *socket_address;
+ GSocketAddress *effective_address;
+
+ socket_address = g_inet_socket_address_new (address, port_num);
+ if (!g_socket_listener_add_address (server->priv->listener,
+ socket_address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_TCP,
+ NULL, /* GObject *source_object */
+ &effective_address,
+ error))
+ {
+ g_object_unref (socket_address);
+ goto out;
+ }
+ if (port_num == 0)
+ {
+ /* make sure we allocate the same port number for other listeners */
+ port_num = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (effective_address));
+ }
+ g_object_unref (effective_address);
+ g_object_unref (socket_address);
+ }
+
+ if (do_nonce)
+ {
+ gint fd;
+ guint n;
+ gsize bytes_written;
+ gsize bytes_remaining;
+
+ server->priv->nonce = g_new0 (guchar, 16);
+ for (n = 0; n < 16; n++)
+ server->priv->nonce[n] = g_random_int_range (0, 256);
+ fd = g_file_open_tmp ("gdbus-nonce-file-XXXXXX",
+ &server->priv->nonce_file,
+ error);
+ if (fd == -1)
+ {
+ g_socket_listener_close (server->priv->listener);
+ goto out;
+ }
+ again:
+ bytes_written = 0;
+ bytes_remaining = 16;
+ while (bytes_remaining > 0)
+ {
+ gssize ret;
+ ret = write (fd, server->priv->nonce + bytes_written, bytes_remaining);
+ if (ret == -1)
+ {
+ if (errno == EINTR)
+ goto again;
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error writing nonce file at `%s': %s"),
+ server->priv->nonce_file,
+ strerror (errno));
+ goto out;
+ }
+ bytes_written += ret;
+ bytes_remaining -= ret;
+ }
+ close (fd);
+ server->priv->client_address = g_strdup_printf ("nonce-tcp:host=%s,port=%d,noncefile=%s",
+ host,
+ port_num,
+ server->priv->nonce_file);
+ }
+ else
+ {
+ server->priv->client_address = g_strdup_printf ("tcp:host=%s,port=%d", host, port_num);
+ }
+ server->priv->is_using_listener = TRUE;
+ ret = TRUE;
+
+ out:
+ g_list_foreach (resolved_addresses, (GFunc) g_object_unref, NULL);
+ g_list_free (resolved_addresses);
+ g_object_unref (resolver);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GDBusServer *server;
+ GDBusConnection *connection;
+} EmitIdleData;
+
+static void
+emit_idle_data_free (EmitIdleData *data)
+{
+ g_object_unref (data->server);
+ g_object_unref (data->connection);
+ g_free (data);
+}
+
+static gboolean
+emit_new_connection_in_idle (gpointer user_data)
+{
+ EmitIdleData *data = user_data;
+
+ g_signal_emit (data->server,
+ _signals[NEW_CONNECTION_SIGNAL],
+ 0,
+ data->connection);
+ g_object_unref (data->connection);
+
+ return FALSE;
+}
+
+/* Called in new thread */
+static gboolean
+on_run (GSocketService *service,
+ GSocketConnection *socket_connection,
+ GObject *source_object,
+ gpointer user_data)
+{
+ GDBusServer *server = G_DBUS_SERVER (user_data);
+ GDBusConnection *connection;
+ GDBusConnectionFlags connection_flags;
+
+ if (server->priv->nonce != NULL)
+ {
+ gchar buf[16];
+ gsize bytes_read;
+
+ if (!g_input_stream_read_all (g_io_stream_get_input_stream (G_IO_STREAM (socket_connection)),
+ buf,
+ 16,
+ &bytes_read,
+ NULL, /* GCancellable */
+ NULL)) /* GError */
+ goto out;
+
+ if (bytes_read != 16)
+ goto out;
+
+ if (memcmp (buf, server->priv->nonce, 16) != 0)
+ goto out;
+ }
+
+ connection_flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER;
+ if (server->priv->flags & G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS)
+ connection_flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
+
+ connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
+ server->priv->guid,
+ connection_flags,
+ server->priv->authentication_observer,
+ NULL, /* GCancellable */
+ NULL); /* GError */
+ if (connection == NULL)
+ goto out;
+
+ if (server->priv->flags & G_DBUS_SERVER_FLAGS_RUN_IN_THREAD)
+ {
+ g_signal_emit (server,
+ _signals[NEW_CONNECTION_SIGNAL],
+ 0,
+ connection);
+ g_object_unref (connection);
+ }
+ else
+ {
+ GSource *idle_source;
+ EmitIdleData *data;
+
+ data = g_new0 (EmitIdleData, 1);
+ data->server = g_object_ref (server);
+ data->connection = g_object_ref (connection);
+
+ idle_source = g_idle_source_new ();
+ g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (idle_source,
+ emit_new_connection_in_idle,
+ data,
+ (GDestroyNotify) emit_idle_data_free);
+ g_source_attach (idle_source, server->priv->main_context_at_construction);
+ g_source_unref (idle_source);
+ }
+
+ out:
+ return TRUE;
+}
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusServer *server = G_DBUS_SERVER (initable);
+ gboolean ret;
+ guint n;
+ gchar **addr_array;
+ GError *last_error;
+
+ ret = FALSE;
+ last_error = NULL;
+
+ if (!g_dbus_is_guid (server->priv->guid))
+ {
+ g_set_error (&last_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("The string `%s' is not a valid D-Bus GUID"),
+ server->priv->guid);
+ goto out;
+ }
+
+ server->priv->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1));
+
+ addr_array = g_strsplit (server->priv->address, ";", 0);
+ last_error = NULL;
+ for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++)
+ {
+ const gchar *address_entry = addr_array[n];
+ GHashTable *key_value_pairs;
+ gchar *transport_name;
+ GError *this_error;
+
+ this_error = NULL;
+ if (g_dbus_is_supported_address (address_entry,
+ &this_error) &&
+ _g_dbus_address_parse_entry (address_entry,
+ &transport_name,
+ &key_value_pairs,
+ &this_error))
+ {
+
+ if (FALSE)
+ {
+ }
+#ifdef G_OS_UNIX
+ else if (g_strcmp0 (transport_name, "unix") == 0)
+ {
+ ret = try_unix (server, address_entry, key_value_pairs, &this_error);
+ }
+#endif
+ else if (g_strcmp0 (transport_name, "tcp") == 0)
+ {
+ ret = try_tcp (server, address_entry, key_value_pairs, FALSE, &this_error);
+ }
+ else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
+ {
+ ret = try_tcp (server, address_entry, key_value_pairs, TRUE, &this_error);
+ }
+ else
+ {
+ g_set_error (&this_error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Cannot listen on unsupported transport `%s'"),
+ transport_name);
+ }
+
+ g_free (transport_name);
+ if (key_value_pairs != NULL)
+ g_hash_table_unref (key_value_pairs);
+
+ if (ret)
+ {
+ g_assert (this_error == NULL);
+ goto out;
+ }
+ }
+
+ if (this_error != NULL)
+ {
+ if (last_error != NULL)
+ g_error_free (last_error);
+ last_error = this_error;
+ }
+ }
+
+ if (!ret)
+ goto out;
+
+ out:
+ if (ret)
+ {
+ if (last_error != NULL)
+ g_error_free (last_error);
+ }
+ else
+ {
+ g_assert (last_error != NULL);
+ g_propagate_error (error, last_error);
+ }
+ return ret;
+}
+
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+ initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusserver.h b/gio/gdbusserver.h
new file mode 100644
index 000000000..404385459
--- /dev/null
+++ b/gio/gdbusserver.h
@@ -0,0 +1,97 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_SERVER_H__
+#define __G_DBUS_SERVER_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_SERVER (g_dbus_server_get_type ())
+#define G_DBUS_SERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_SERVER, GDBusServer))
+#define G_DBUS_SERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_SERVER, GDBusServerClass))
+#define G_DBUS_SERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_SERVER, GDBusServerClass))
+#define G_IS_DBUS_SERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_SERVER))
+#define G_IS_DBUS_SERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_SERVER))
+
+typedef struct _GDBusServerClass GDBusServerClass;
+typedef struct _GDBusServerPrivate GDBusServerPrivate;
+
+/**
+ * GDBusServer:
+ *
+ * The #GDBusServer structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusServer
+{
+ /*< private >*/
+ GObject parent_instance;
+ GDBusServerPrivate *priv;
+};
+
+/**
+ * GDBusServerClass:
+ * @new_connection: Signal class handler for the #GDBusServer::new-connection signal.
+ *
+ * Class structure for #GDBusServer.
+ */
+struct _GDBusServerClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ /* Signals */
+ void (*new_connection) (GDBusServer *server,
+ GDBusConnection *connection);
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+ void (*_g_reserved7) (void);
+ void (*_g_reserved8) (void);
+};
+
+GType g_dbus_server_get_type (void) G_GNUC_CONST;
+GDBusServer *g_dbus_server_new_sync (const gchar *address,
+ GDBusServerFlags flags,
+ const gchar *guid,
+ GDBusAuthObserver *observer,
+ GCancellable *cancellable,
+ GError **error);
+const gchar *g_dbus_server_get_client_address (GDBusServer *server);
+const gchar *g_dbus_server_get_guid (GDBusServer *server);
+GDBusServerFlags g_dbus_server_get_flags (GDBusServer *server);
+void g_dbus_server_start (GDBusServer *server);
+void g_dbus_server_stop (GDBusServer *server);
+gboolean g_dbus_server_is_active (GDBusServer *server);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_SERVER_H__ */
diff --git a/gio/gdbusutils.c b/gio/gdbusutils.c
new file mode 100644
index 000000000..b1ec0ea1c
--- /dev/null
+++ b/gio/gdbusutils.c
@@ -0,0 +1,364 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+
+/**
+ * SECTION:gdbusutils
+ * @title: D-Bus Utilities
+ * @short_description: Various utilities related to D-Bus.
+ * @include: gdbus/gdbus.h
+ *
+ * Various utility routines related to D-Bus.
+ */
+
+static gboolean
+is_valid_bus_name_character (gint c,
+ gboolean allow_hyphen)
+{
+ return
+ (c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c == '_') ||
+ (allow_hyphen && c == '-');
+}
+
+static gboolean
+is_valid_initial_bus_name_character (gint c,
+ gboolean allow_initial_digit,
+ gboolean allow_hyphen)
+{
+ if (allow_initial_digit)
+ return is_valid_bus_name_character (c, allow_hyphen);
+ else
+ return
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c == '_') ||
+ (allow_hyphen && c == '-');
+}
+
+static gboolean
+is_valid_name (const gchar *start,
+ guint len,
+ gboolean allow_initial_digit,
+ gboolean allow_hyphen)
+{
+ gboolean ret;
+ const gchar *s;
+ const gchar *end;
+ gboolean has_dot;
+
+ ret = FALSE;
+
+ if (len == 0)
+ goto out;
+
+ s = start;
+ end = s + len;
+ has_dot = FALSE;
+ while (s != end)
+ {
+ if (*s == '.')
+ {
+ s += 1;
+ if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, allow_initial_digit, allow_hyphen)))
+ goto out;
+ has_dot = TRUE;
+ }
+ else if (G_UNLIKELY (!is_valid_bus_name_character (*s, allow_hyphen)))
+ {
+ goto out;
+ }
+ s += 1;
+ }
+
+ if (G_UNLIKELY (!has_dot))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/**
+ * g_dbus_is_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus bus name (either unique or well-known).
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_name (const gchar *string)
+{
+ guint len;
+ gboolean ret;
+ const gchar *s;
+ const gchar *end;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+
+ ret = FALSE;
+
+ len = strlen (string);
+ if (G_UNLIKELY (len == 0 || len > 255))
+ goto out;
+
+ s = string;
+ end = s + len;
+ if (*s == ':')
+ {
+ /* handle unique name */
+ if (!is_valid_name (s + 1, len - 1, TRUE, TRUE))
+ goto out;
+ ret = TRUE;
+ goto out;
+ }
+ else if (G_UNLIKELY (*s == '.'))
+ {
+ /* can't start with a . */
+ goto out;
+ }
+ else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, TRUE)))
+ goto out;
+
+ ret = is_valid_name (s + 1, len - 1, FALSE, TRUE);
+
+ out:
+ return ret;
+}
+
+/**
+ * g_dbus_is_unique_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus unique bus name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_unique_name (const gchar *string)
+{
+ gboolean ret;
+ guint len;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+
+ ret = FALSE;
+
+ len = strlen (string);
+ if (G_UNLIKELY (len == 0 || len > 255))
+ goto out;
+
+ if (G_UNLIKELY (*string != ':'))
+ goto out;
+
+ if (G_UNLIKELY (!is_valid_name (string + 1, len - 1, TRUE, TRUE)))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/**
+ * g_dbus_is_member_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus member (e.g. signal or method) name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_member_name (const gchar *string)
+{
+ gboolean ret;
+ guint n;
+
+ ret = FALSE;
+ if (G_UNLIKELY (string == NULL))
+ goto out;
+
+ if (G_UNLIKELY (!is_valid_initial_bus_name_character (string[0], FALSE, FALSE)))
+ goto out;
+
+ for (n = 1; string[n] != '\0'; n++)
+ {
+ if (G_UNLIKELY (!is_valid_bus_name_character (string[n], FALSE)))
+ {
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/**
+ * g_dbus_is_interface_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus interface name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_interface_name (const gchar *string)
+{
+ guint len;
+ gboolean ret;
+ const gchar *s;
+ const gchar *end;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+
+ ret = FALSE;
+
+ len = strlen (string);
+ if (G_UNLIKELY (len == 0 || len > 255))
+ goto out;
+
+ s = string;
+ end = s + len;
+ if (G_UNLIKELY (*s == '.'))
+ {
+ /* can't start with a . */
+ goto out;
+ }
+ else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, FALSE)))
+ goto out;
+
+ ret = is_valid_name (s + 1, len - 1, FALSE, FALSE);
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: maybe move to glib? if so, it should conform to http://en.wikipedia.org/wiki/Guid and/or
+ * http://tools.ietf.org/html/rfc4122 - specifically it should have hyphens then.
+ */
+
+/**
+ * g_dbus_generate_guid:
+ *
+ * Generate a D-Bus GUID that can be used with
+ * e.g. g_dbus_connection_new().
+ *
+ * See the D-Bus specification regarding what strings are valid D-Bus
+ * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant).
+ *
+ * Returns: A valid D-Bus GUID. Free with g_free().
+ */
+gchar *
+g_dbus_generate_guid (void)
+{
+ GString *s;
+ GTimeVal now;
+ guint32 r1;
+ guint32 r2;
+ guint32 r3;
+
+ s = g_string_new (NULL);
+
+ r1 = g_random_int ();
+ r2 = g_random_int ();
+ r3 = g_random_int ();
+ g_get_current_time (&now);
+
+ g_string_append_printf (s, "%08x", r1);
+ g_string_append_printf (s, "%08x", r2);
+ g_string_append_printf (s, "%08x", r3);
+ g_string_append_printf (s, "%08x", (guint32) now.tv_sec);
+
+ return g_string_free (s, FALSE);
+}
+
+/**
+ * g_dbus_is_guid:
+ * @string: The string to check.
+ *
+ * Checks if @string is a D-Bus GUID.
+ *
+ * See the D-Bus specification regarding what strings are valid D-Bus
+ * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant).
+ *
+ * Returns: %TRUE if @string is a guid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_guid (const gchar *string)
+{
+ gboolean ret;
+ guint n;
+
+ g_return_val_if_fail (string != NULL, FALSE);
+
+ ret = FALSE;
+
+ for (n = 0; n < 32; n++)
+ {
+ if (!g_ascii_isxdigit (string[n]))
+ goto out;
+ }
+ if (string[32] != '\0')
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+/**
+ * g_dbus_is_activated:
+ *
+ * Determine if the process has been activated by a message bus.
+ *
+ * Returns: %TRUE if this process has been started by a message bus, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_activated (void)
+{
+ /* TODO: technically this will make child processes forked by us
+ * return TRUE too..
+ */
+ return g_getenv ("DBUS_STARTER_BUS_TYPE") != NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusutils.h b/gio/gdbusutils.h
new file mode 100644
index 000000000..e3e606bb0
--- /dev/null
+++ b/gio/gdbusutils.h
@@ -0,0 +1,42 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_DBUS_UTILS_H__
+#define __G_DBUS_UTILS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+gboolean g_dbus_is_guid (const gchar *string);
+gchar *g_dbus_generate_guid (void);
+
+gboolean g_dbus_is_name (const gchar *string);
+gboolean g_dbus_is_unique_name (const gchar *string);
+gboolean g_dbus_is_member_name (const gchar *string);
+gboolean g_dbus_is_interface_name (const gchar *string);
+
+gboolean g_dbus_is_activated (void);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_UTILS_H__ */
diff --git a/gio/gio-marshal.list b/gio/gio-marshal.list
index 8439304bf..8473ea70d 100644
--- a/gio/gio-marshal.list
+++ b/gio/gio-marshal.list
@@ -6,3 +6,5 @@ BOOLEAN:OBJECT,OBJECT
VOID:STRING,BOXED,BOXED
BOOL:POINTER,INT
BOOL:UINT
+VOID:STRING,STRING,BOXED
+VOID:BOOL,BOXED
diff --git a/gio/gio.h b/gio/gio.h
index ac0dc7fbf..ca55cd674 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -97,6 +97,22 @@
#include <gio/gzlibcompressor.h>
#include <gio/gzlibdecompressor.h>
+#include <gio/gdbusutils.h>
+#include <gio/gdbusaddress.h>
+#include <gio/gdbusmessage.h>
+#include <gio/gdbusconnection.h>
+#include <gio/gdbuserror.h>
+#include <gio/gdbusnameowning.h>
+#include <gio/gdbusnamewatching.h>
+#include <gio/gdbusproxywatching.h>
+#include <gio/gdbusproxy.h>
+#include <gio/gdbusintrospection.h>
+#include <gio/gdbusmethodinvocation.h>
+#include <gio/gdbusserver.h>
+#include <gio/gcredentials.h>
+#include <gio/gunixcredentialsmessage.h>
+#include <gio/gdbusauthobserver.h>
+
#undef __GIO_GIO_H_INSIDE__
#endif /* __G_IO_H__ */
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 9c98ffd9b..c1d2e9f60 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -429,6 +429,11 @@ typedef enum {
* @G_IO_ERROR_ADDRESS_IN_USE: The requested address is already in use. Since 2.22
* @G_IO_ERROR_PARTIAL_INPUT: Need more input to finish operation. Since 2.24
* @G_IO_ERROR_INVALID_DATA: There input data was invalid. Since 2.24
+ * @G_IO_ERROR_DBUS_ERROR: A remote object generated an error that
+ * doesn't correspond to a locally registered #GError error
+ * domain. Use g_dbus_error_get_remote_error() to extract the D-Bus
+ * error name and g_dbus_error_strip_remote_error() to fix up the
+ * message so it matches what was received on the wire. Since 2.26.
*
* Error codes returned by GIO functions.
*
@@ -469,7 +474,8 @@ typedef enum {
G_IO_ERROR_NOT_INITIALIZED,
G_IO_ERROR_ADDRESS_IN_USE,
G_IO_ERROR_PARTIAL_INPUT,
- G_IO_ERROR_INVALID_DATA
+ G_IO_ERROR_INVALID_DATA,
+ G_IO_ERROR_DBUS_ERROR
} GIOErrorEnum;
@@ -732,6 +738,370 @@ typedef enum {
G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED
} GUnixSocketAddressType;
+/**
+ * GBusType:
+ * @G_BUS_TYPE_NONE: Not a message bus connection.
+ * @G_BUS_TYPE_SESSION: The login session message bus.
+ * @G_BUS_TYPE_SYSTEM: The system-wide message bus.
+ * @G_BUS_TYPE_STARTER: Connect to the bus that activated the program.
+ *
+ * An enumeration to specify the type of a #GDBusConnection.
+ */
+typedef enum
+{
+ G_BUS_TYPE_NONE = -1,
+ G_BUS_TYPE_SESSION = 0,
+ G_BUS_TYPE_SYSTEM = 1,
+ G_BUS_TYPE_STARTER = 2
+} GBusType;
+
+/**
+ * GBusNameOwnerFlags:
+ * @G_BUS_NAME_OWNER_FLAGS_NONE: No flags set.
+ * @G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT: Allow another message bus connection to claim the the name.
+ * @G_BUS_NAME_OWNER_FLAGS_REPLACE: If another message bus connection owns the name and have
+ * specified #G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then take the name from the other connection.
+ *
+ * Flags used in g_bus_own_name().
+ */
+typedef enum
+{
+ G_BUS_NAME_OWNER_FLAGS_NONE = 0, /*< nick=none >*/
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT = (1<<0), /*< nick=allow-replacement >*/
+ G_BUS_NAME_OWNER_FLAGS_REPLACE = (1<<1), /*< nick=replace >*/
+} GBusNameOwnerFlags;
+
+/**
+ * GBusNameWatcherFlags:
+ * @G_BUS_NAME_WATCHER_FLAGS_NONE: No flags set.
+ * @G_BUS_NAME_WATCHER_FLAGS_AUTO_START: If no-one owns the name when
+ * beginning to watch the name, ask the bus to launch an owner for the
+ * name.
+ *
+ * Flags used in g_bus_watch_name().
+ */
+typedef enum
+{
+ G_BUS_NAME_WATCHER_FLAGS_NONE = 0,
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START = (1<<0)
+} GBusNameWatcherFlags;
+
+/**
+ * GDBusProxyFlags:
+ * @G_DBUS_PROXY_FLAGS_NONE: No flags set.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES: Don't load properties.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS: Don't connect to signals on the remote object.
+ *
+ * Flags used when constructing an instance of a #GDBusProxy derived class.
+ */
+typedef enum
+{
+ G_DBUS_PROXY_FLAGS_NONE = 0, /*< nick=none >*/
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0), /*< nick=do-not-load-properties >*/
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), /*< nick=do-not-connect-signals >*/
+} GDBusProxyFlags;
+
+/**
+ * GDBusError:
+ * @G_DBUS_ERROR_FAILED:
+ * A generic error; "something went wrong" - see the error message for
+ * more.
+ * @G_DBUS_ERROR_NO_MEMORY:
+ * There was not enough memory to complete an operation.
+ * @G_DBUS_ERROR_SERVICE_UNKNOWN:
+ * The bus doesn't know how to launch a service to supply the bus name
+ * you wanted.
+ * @G_DBUS_ERROR_NAME_HAS_NO_OWNER:
+ * The bus name you referenced doesn't exist (i.e. no application owns
+ * it).
+ * @G_DBUS_ERROR_NO_REPLY:
+ * No reply to a message expecting one, usually means a timeout occurred.
+ * @G_DBUS_ERROR_IO_ERROR:
+ * Something went wrong reading or writing to a socket, for example.
+ * @G_DBUS_ERROR_BAD_ADDRESS:
+ * A D-Bus bus address was malformed.
+ * @G_DBUS_ERROR_NOT_SUPPORTED:
+ * Requested operation isn't supported (like ENOSYS on UNIX).
+ * @G_DBUS_ERROR_LIMITS_EXCEEDED:
+ * Some limited resource is exhausted.
+ * @G_DBUS_ERROR_ACCESS_DENIED:
+ * Security restrictions don't allow doing what you're trying to do.
+ * @G_DBUS_ERROR_AUTH_FAILED:
+ * Authentication didn't work.
+ * @G_DBUS_ERROR_NO_SERVER:
+ * Unable to connect to server (probably caused by ECONNREFUSED on a
+ * socket).
+ * @G_DBUS_ERROR_TIMEOUT:
+ * Certain timeout errors, possibly ETIMEDOUT on a socket. Note that
+ * %G_DBUS_ERROR_NO_REPLY is used for message reply timeouts. Warning:
+ * this is confusingly-named given that %G_DBUS_ERROR_TIMED_OUT also
+ * exists. We can't fix it for compatibility reasons so just be
+ * careful.
+ * @G_DBUS_ERROR_NO_NETWORK:
+ * No network access (probably ENETUNREACH on a socket).
+ * @G_DBUS_ERROR_ADDRESS_IN_USE:
+ * Can't bind a socket since its address is in use (i.e. EADDRINUSE).
+ * @G_DBUS_ERROR_DISCONNECTED:
+ * The connection is disconnected and you're trying to use it.
+ * @G_DBUS_ERROR_INVALID_ARGS:
+ * Invalid arguments passed to a method call.
+ * @G_DBUS_ERROR_FILE_NOT_FOUND:
+ * Missing file.
+ * @G_DBUS_ERROR_FILE_EXISTS:
+ * Existing file and the operation you're using does not silently overwrite.
+ * @G_DBUS_ERROR_UNKNOWN_METHOD:
+ * Method name you invoked isn't known by the object you invoked it on.
+ * @G_DBUS_ERROR_TIMED_OUT:
+ * Certain timeout errors, e.g. while starting a service. Warning: this is
+ * confusingly-named given that %G_DBUS_ERROR_TIMEOUT also exists. We
+ * can't fix it for compatibility reasons so just be careful.
+ * @G_DBUS_ERROR_MATCH_RULE_NOT_FOUND:
+ * Tried to remove or modify a match rule that didn't exist.
+ * @G_DBUS_ERROR_MATCH_RULE_INVALID:
+ * The match rule isn't syntactically valid.
+ * @G_DBUS_ERROR_SPAWN_EXEC_FAILED:
+ * While starting a new process, the exec() call failed.
+ * @G_DBUS_ERROR_SPAWN_FORK_FAILED:
+ * While starting a new process, the fork() call failed.
+ * @G_DBUS_ERROR_SPAWN_CHILD_EXITED:
+ * While starting a new process, the child exited with a status code.
+ * @G_DBUS_ERROR_SPAWN_CHILD_SIGNALED:
+ * While starting a new process, the child exited on a signal.
+ * @G_DBUS_ERROR_SPAWN_FAILED:
+ * While starting a new process, something went wrong.
+ * @G_DBUS_ERROR_SPAWN_SETUP_FAILED:
+ * We failed to setup the environment correctly.
+ * @G_DBUS_ERROR_SPAWN_CONFIG_INVALID:
+ * We failed to setup the config parser correctly.
+ * @G_DBUS_ERROR_SPAWN_SERVICE_INVALID:
+ * Bus name was not valid.
+ * @G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+ * Service file not found in system-services directory.
+ * @G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID:
+ * Permissions are incorrect on the setuid helper.
+ * @G_DBUS_ERROR_SPAWN_FILE_INVALID:
+ * Service file invalid (Name, User or Exec missing).
+ * @G_DBUS_ERROR_SPAWN_NO_MEMORY:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @G_DBUS_ERROR_INVALID_SIGNATURE:
+ * A type signature is not valid.
+ * @G_DBUS_ERROR_INVALID_FILE_CONTENT:
+ * A file contains invalid syntax or is otherwise broken.
+ * @G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN:
+ * Asked for SELinux security context and it wasn't available.
+ * @G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN:
+ * Asked for ADT audit data and it wasn't available.
+ * @G_DBUS_ERROR_OBJECT_PATH_IN_USE:
+ * There's already an object with the requested object path.
+ *
+ * Error codes for the %G_DBUS_ERROR error domain.
+ */
+typedef enum
+{
+ /* Well-known errors in the org.freedesktop.DBus.Error namespace */
+ G_DBUS_ERROR_FAILED, /* org.freedesktop.DBus.Error.Failed */
+ G_DBUS_ERROR_NO_MEMORY, /* org.freedesktop.DBus.Error.NoMemory */
+ G_DBUS_ERROR_SERVICE_UNKNOWN, /* org.freedesktop.DBus.Error.ServiceUnknown */
+ G_DBUS_ERROR_NAME_HAS_NO_OWNER, /* org.freedesktop.DBus.Error.NameHasNoOwner */
+ G_DBUS_ERROR_NO_REPLY, /* org.freedesktop.DBus.Error.NoReply */
+ G_DBUS_ERROR_IO_ERROR, /* org.freedesktop.DBus.Error.IOError */
+ G_DBUS_ERROR_BAD_ADDRESS, /* org.freedesktop.DBus.Error.BadAddress */
+ G_DBUS_ERROR_NOT_SUPPORTED, /* org.freedesktop.DBus.Error.NotSupported */
+ G_DBUS_ERROR_LIMITS_EXCEEDED, /* org.freedesktop.DBus.Error.LimitsExceeded */
+ G_DBUS_ERROR_ACCESS_DENIED, /* org.freedesktop.DBus.Error.AccessDenied */
+ G_DBUS_ERROR_AUTH_FAILED, /* org.freedesktop.DBus.Error.AuthFailed */
+ G_DBUS_ERROR_NO_SERVER, /* org.freedesktop.DBus.Error.NoServer */
+ G_DBUS_ERROR_TIMEOUT, /* org.freedesktop.DBus.Error.Timeout */
+ G_DBUS_ERROR_NO_NETWORK, /* org.freedesktop.DBus.Error.NoNetwork */
+ G_DBUS_ERROR_ADDRESS_IN_USE, /* org.freedesktop.DBus.Error.AddressInUse */
+ G_DBUS_ERROR_DISCONNECTED, /* org.freedesktop.DBus.Error.Disconnected */
+ G_DBUS_ERROR_INVALID_ARGS, /* org.freedesktop.DBus.Error.InvalidArgs */
+ G_DBUS_ERROR_FILE_NOT_FOUND, /* org.freedesktop.DBus.Error.FileNotFound */
+ G_DBUS_ERROR_FILE_EXISTS, /* org.freedesktop.DBus.Error.FileExists */
+ G_DBUS_ERROR_UNKNOWN_METHOD, /* org.freedesktop.DBus.Error.UnknownMethod */
+ G_DBUS_ERROR_TIMED_OUT, /* org.freedesktop.DBus.Error.TimedOut */
+ G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, /* org.freedesktop.DBus.Error.MatchRuleNotFound */
+ G_DBUS_ERROR_MATCH_RULE_INVALID, /* org.freedesktop.DBus.Error.MatchRuleInvalid */
+ G_DBUS_ERROR_SPAWN_EXEC_FAILED, /* org.freedesktop.DBus.Error.Spawn.ExecFailed */
+ G_DBUS_ERROR_SPAWN_FORK_FAILED, /* org.freedesktop.DBus.Error.Spawn.ForkFailed */
+ G_DBUS_ERROR_SPAWN_CHILD_EXITED, /* org.freedesktop.DBus.Error.Spawn.ChildExited */
+ G_DBUS_ERROR_SPAWN_CHILD_SIGNALED, /* org.freedesktop.DBus.Error.Spawn.ChildSignaled */
+ G_DBUS_ERROR_SPAWN_FAILED, /* org.freedesktop.DBus.Error.Spawn.Failed */
+ G_DBUS_ERROR_SPAWN_SETUP_FAILED, /* org.freedesktop.DBus.Error.Spawn.FailedToSetup */
+ G_DBUS_ERROR_SPAWN_CONFIG_INVALID, /* org.freedesktop.DBus.Error.Spawn.ConfigInvalid */
+ G_DBUS_ERROR_SPAWN_SERVICE_INVALID, /* org.freedesktop.DBus.Error.Spawn.ServiceNotValid */
+ G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, /* org.freedesktop.DBus.Error.Spawn.ServiceNotFound */
+ G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, /* org.freedesktop.DBus.Error.Spawn.PermissionsInvalid */
+ G_DBUS_ERROR_SPAWN_FILE_INVALID, /* org.freedesktop.DBus.Error.Spawn.FileInvalid */
+ G_DBUS_ERROR_SPAWN_NO_MEMORY, /* org.freedesktop.DBus.Error.Spawn.NoMemory */
+ G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, /* org.freedesktop.DBus.Error.UnixProcessIdUnknown */
+ G_DBUS_ERROR_INVALID_SIGNATURE, /* org.freedesktop.DBus.Error.InvalidSignature */
+ G_DBUS_ERROR_INVALID_FILE_CONTENT, /* org.freedesktop.DBus.Error.InvalidFileContent */
+ G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, /* org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown */
+ G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, /* org.freedesktop.DBus.Error.AdtAuditDataUnknown */
+ G_DBUS_ERROR_OBJECT_PATH_IN_USE, /* org.freedesktop.DBus.Error.ObjectPathInUse */
+} GDBusError;
+/* TODO: remember to update g_dbus_error_quark() in gdbuserror.c if you extend this enumeration */
+
+/**
+ * GDBusConnectionFlags:
+ * @G_DBUS_CONNECTION_FLAGS_NONE: No flags set.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT: Perform authentication against server.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER: Perform authentication against client.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: When
+ * authenticating as a server, allow the anonymous authentication
+ * method.
+ * @G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION: Pass this flag if connecting to a peer that is a
+ * message bus. This means that the Hello() method will be invoked as part of the connection setup.
+ *
+ * Flags used when creating a new #GDBusConnection.
+ */
+typedef enum {
+ G_DBUS_CONNECTION_FLAGS_NONE = 0,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT = (1<<0),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER = (1<<1),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<2),
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION = (1<<3)
+} GDBusConnectionFlags;
+
+/**
+ * GDBusCapabilityFlags:
+ * @G_DBUS_CAPABILITY_FLAGS_NONE: No flags set.
+ * @G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING: The connection
+ * supports exchanging UNIX file descriptors with the remote peer.
+ *
+ * Capabilities negotiated with the remote peer.
+ */
+typedef enum {
+ G_DBUS_CAPABILITY_FLAGS_NONE = 0,
+ G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING = (1<<0),
+} GDBusCapabilityFlags;
+
+/**
+ * GDBusInvokeMethodFlags:
+ * @G_DBUS_INVOKE_METHOD_FLAGS_NONE: No flags set.
+ * @G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START: The bus must not launch
+ * an owner for the destination name in response to this method
+ * invocation.
+ *
+ * Flags used in g_dbus_connection_invoke_method() and similar APIs.
+ */
+typedef enum {
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE = 0,
+ G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START = (1<<0),
+} GDBusInvokeMethodFlags;
+
+/**
+ * GDBusMessageType:
+ * @G_DBUS_MESSAGE_TYPE_INVALID: Message is of invalid type.
+ * @G_DBUS_MESSAGE_TYPE_METHOD_CALL: Method call.
+ * @G_DBUS_MESSAGE_TYPE_METHOD_RETURN: Method reply.
+ * @G_DBUS_MESSAGE_TYPE_ERROR: Error reply.
+ * @G_DBUS_MESSAGE_TYPE_SIGNAL: Signal emission.
+ *
+ * Message types used in #GDBusMessage.
+ */
+typedef enum {
+ G_DBUS_MESSAGE_TYPE_INVALID,
+ G_DBUS_MESSAGE_TYPE_METHOD_CALL,
+ G_DBUS_MESSAGE_TYPE_METHOD_RETURN,
+ G_DBUS_MESSAGE_TYPE_ERROR,
+ G_DBUS_MESSAGE_TYPE_SIGNAL
+} GDBusMessageType;
+
+/**
+ * GDBusMessageFlags:
+ * @G_DBUS_MESSAGE_FLAGS_NONE: No flags set.
+ * @G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED: A reply is not expected.
+ * @G_DBUS_MESSAGE_FLAGS_NO_AUTO_START: The bus must not launch an
+ * owner for the destination name in response to this message.
+ *
+ * Message flags used in #GDBusMessage.
+ */
+typedef enum {
+ G_DBUS_MESSAGE_FLAGS_NONE = 0,
+ G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED = (1<<0),
+ G_DBUS_MESSAGE_FLAGS_NO_AUTO_START = (1<<1)
+} GDBusMessageFlags;
+
+/**
+ * GDBusMessageHeaderField:
+ * @G_DBUS_MESSAGE_HEADER_FIELD_INVALID: Not a valid header field.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_PATH: The object path.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: The interface name.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: The method or signal name.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: The name of the error that occurred.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: The serial number the message is a reply to.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: The name the message is intended for.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_SENDER: Unique name of the sender of the message (filled in by the bus).
+ * @G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: The signature of the message body.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: The number of UNIX file descriptors that accompany the message.
+ *
+ * Header fields used in #GDBusMessage.
+ */
+typedef enum {
+ G_DBUS_MESSAGE_HEADER_FIELD_INVALID,
+ G_DBUS_MESSAGE_HEADER_FIELD_PATH,
+ G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE,
+ G_DBUS_MESSAGE_HEADER_FIELD_MEMBER,
+ G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME,
+ G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL,
+ G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION,
+ G_DBUS_MESSAGE_HEADER_FIELD_SENDER,
+ G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE,
+ G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS
+} GDBusMessageHeaderField;
+
+/**
+ * GDBusPropertyInfoFlags:
+ * @G_DBUS_PROPERTY_INFO_FLAGS_NONE: No flags set.
+ * @G_DBUS_PROPERTY_INFO_FLAGS_READABLE: Property is readable.
+ * @G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE: Property is writable.
+ *
+ * Flags describing the access control of a D-Bus property.
+ */
+typedef enum
+{
+ G_DBUS_PROPERTY_INFO_FLAGS_NONE = 0,
+ G_DBUS_PROPERTY_INFO_FLAGS_READABLE = (1<<0),
+ G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE = (1<<1),
+} GDBusPropertyInfoFlags;
+
+/**
+ * GDBusSubtreeFlags:
+ * @G_DBUS_SUBTREE_FLAGS_NONE: No flags set.
+ * @G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES: Method calls to objects not in the enumerated range
+ * will still be dispatched. This is useful if you want
+ * to dynamically spawn objects in the subtree.
+ *
+ * Flags passed to g_dbus_connection_register_subtree().
+ */
+typedef enum
+{
+ G_DBUS_SUBTREE_FLAGS_NONE = 0,
+ G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES = (1<<0),
+} GDBusSubtreeFlags;
+
+/**
+ * GDBusServerFlags:
+ * @G_DBUS_SERVER_FLAGS_NONE: No flags set.
+ * @G_DBUS_SERVER_FLAGS_RUN_IN_THREAD: All #GDBusServer::new-connection
+ * signals will run in separated dedicated threads (see signal for
+ * details).
+ * @G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: Allow the anonymous
+ * authentication method.
+ *
+ * Flags used when creating a #GDBusServer.
+ */
+typedef enum
+{
+ G_DBUS_SERVER_FLAGS_NONE = 0,
+ G_DBUS_SERVER_FLAGS_RUN_IN_THREAD = (1<<0),
+ G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<1)
+} GDBusServerFlags;
+
G_END_DECLS
#endif /* __GIO_ENUMS_H__ */
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 2abc87dd6..22a00266c 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -333,6 +333,25 @@ struct _GOutputVector {
gsize size;
};
+typedef struct _GCredentials GCredentials;
+typedef struct _GUnixCredentialsMessage GUnixCredentialsMessage;
+typedef struct _GDBusMessage GDBusMessage;
+typedef struct _GDBusConnection GDBusConnection;
+typedef struct _GMessageBusConnection GMessageBusConnection;
+typedef struct _GDBusProxy GDBusProxy;
+typedef struct _GDBusMethodInvocation GDBusMethodInvocation;
+typedef struct _GDBusServer GDBusServer;
+typedef struct _GDBusAuthObserver GDBusAuthObserver;
+typedef struct _GDBusErrorEntry GDBusErrorEntry;
+typedef struct _GDBusInterfaceVTable GDBusInterfaceVTable;
+typedef struct _GDBusSubtreeVTable GDBusSubtreeVTable;
+typedef struct _GDBusAnnotationInfo GDBusAnnotationInfo;
+typedef struct _GDBusArgInfo GDBusArgInfo;
+typedef struct _GDBusMethodInfo GDBusMethodInfo;
+typedef struct _GDBusSignalInfo GDBusSignalInfo;
+typedef struct _GDBusPropertyInfo GDBusPropertyInfo;
+typedef struct _GDBusInterfaceInfo GDBusInterfaceInfo;
+typedef struct _GDBusNodeInfo GDBusNodeInfo;
G_END_DECLS
diff --git a/gio/gunixcredentialsmessage.c b/gio/gunixcredentialsmessage.c
new file mode 100644
index 000000000..6af8c297c
--- /dev/null
+++ b/gio/gunixcredentialsmessage.c
@@ -0,0 +1,341 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2009 Codethink Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: David Zeuthen <davidz@redhat.com>
+ */
+
+/**
+ * SECTION: gunixcredentialsmessage
+ * @title: GUnixCredentialsMessage
+ * @short_description: A GSocketControlMessage containing credentials
+ * @see_also: #GUnixConnection, #GSocketControlMessage
+ *
+ * This #GSocketControlMessage contains a #GCredentials instance. It
+ * may be sent using g_socket_send_message() and received using
+ * g_socket_receive_message() over UNIX sockets (ie: sockets in the
+ * %G_SOCKET_ADDRESS_UNIX family).
+ *
+ * For an easier way to send and receive credentials over
+ * stream-oriented UNIX sockets, see g_unix_connection_send_credentials() and
+ * g_unix_connection_receive_credentials().
+ **/
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+#ifdef __linux__
+
+#define _GNU_SOURCE
+#define __USE_GNU
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <fcntl.h>
+#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 1
+
+#else
+/* TODO: please add support for your UNIX flavor */
+#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 0
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#include <string.h>
+#include <errno.h>
+
+#include "gunixcredentialsmessage.h"
+#include "gcredentials.h"
+
+struct _GUnixCredentialsMessagePrivate
+{
+ GCredentials *credentials;
+};
+
+enum
+{
+ PROP_0,
+ PROP_CREDENTIALS
+};
+
+G_DEFINE_TYPE (GUnixCredentialsMessage, g_unix_credentials_message, G_TYPE_SOCKET_CONTROL_MESSAGE);
+
+static gsize
+g_unix_credentials_message_get_size (GSocketControlMessage *message)
+{
+#ifdef __linux__
+ return sizeof (struct ucred);
+#else
+ return 0;
+#endif
+}
+
+static int
+g_unix_credentials_message_get_level (GSocketControlMessage *message)
+{
+ return SOL_SOCKET;
+}
+
+static int
+g_unix_credentials_message_get_msg_type (GSocketControlMessage *message)
+{
+#ifdef __linux__
+ return SCM_CREDENTIALS;
+#else
+ return 0;
+#endif
+}
+
+static GSocketControlMessage *
+g_unix_credentials_message_deserialize (gint level,
+ gint type,
+ gsize size,
+ gpointer data)
+{
+ GSocketControlMessage *message;
+
+ message = NULL;
+
+#ifdef __linux__
+ {
+ GCredentials *credentials;
+ struct ucred *ucred;
+
+ if (level != SOL_SOCKET || type != SCM_CREDENTIALS)
+ goto out;
+
+ if (size != sizeof (struct ucred))
+ {
+ g_warning ("Expected a struct ucred (%" G_GSIZE_FORMAT " bytes) but "
+ "got %" G_GSIZE_FORMAT " bytes of data",
+ sizeof (struct ucred),
+ size);
+ goto out;
+ }
+
+ ucred = data;
+
+ credentials = g_credentials_new ();
+ g_credentials_set_unix_user (credentials, ucred->uid);
+ g_credentials_set_unix_group (credentials, ucred->gid);
+ g_credentials_set_unix_process (credentials, ucred->pid);
+ message = g_unix_credentials_message_new_with_credentials (credentials);
+ g_object_unref (credentials);
+ out:
+ ;
+ }
+#endif
+
+ return message;
+}
+
+static void
+g_unix_credentials_message_serialize (GSocketControlMessage *_message,
+ gpointer data)
+{
+ GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (_message);
+#ifdef __linux__
+ {
+ struct ucred *ucred = data;
+ ucred->uid = g_credentials_get_unix_user (message->priv->credentials);
+ ucred->gid = g_credentials_get_unix_group (message->priv->credentials);
+ ucred->pid = g_credentials_get_unix_process (message->priv->credentials);
+ }
+#endif
+}
+
+static void
+g_unix_credentials_message_finalize (GObject *object)
+{
+ GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+ if (message->priv->credentials != NULL)
+ g_object_unref (message->priv->credentials);
+
+ if (G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->finalize (object);
+}
+
+static void
+g_unix_credentials_message_init (GUnixCredentialsMessage *message)
+{
+ message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message,
+ G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+ GUnixCredentialsMessagePrivate);
+}
+
+static void
+g_unix_credentials_message_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CREDENTIALS:
+ g_value_set_object (value, message->priv->credentials);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_unix_credentials_message_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CREDENTIALS:
+ message->priv->credentials = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+g_unix_credentials_message_constructed (GObject *object)
+{
+ GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+ if (message->priv->credentials == NULL)
+ message->priv->credentials = g_credentials_new_for_process ();
+
+ if (G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed (object);
+}
+
+static void
+g_unix_credentials_message_class_init (GUnixCredentialsMessageClass *class)
+{
+ GSocketControlMessageClass *scm_class;
+ GObjectClass *gobject_class;
+
+ g_type_class_add_private (class, sizeof (GUnixCredentialsMessagePrivate));
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->get_property = g_unix_credentials_message_get_property;
+ gobject_class->set_property = g_unix_credentials_message_set_property;
+ gobject_class->finalize = g_unix_credentials_message_finalize;
+ gobject_class->constructed = g_unix_credentials_message_constructed;
+
+ scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class);
+ scm_class->get_size = g_unix_credentials_message_get_size;
+ scm_class->get_level = g_unix_credentials_message_get_level;
+ scm_class->get_type = g_unix_credentials_message_get_msg_type;
+ scm_class->serialize = g_unix_credentials_message_serialize;
+ scm_class->deserialize = g_unix_credentials_message_deserialize;
+
+ /**
+ * GUnixCredentialsMessage:credentials:
+ *
+ * The credentials stored in the message.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_CREDENTIALS,
+ g_param_spec_object ("credentials",
+ _("Credentials"),
+ _("The credentials stored in the message"),
+ G_TYPE_CREDENTIALS,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_unix_credentials_message_is_supported:
+ *
+ * Checks if passing a #GCredential on a #GSocket is supported on this platform.
+ *
+ * Returns: %TRUE if supported, %FALSE otherwise
+ *
+ * Since: 2.26
+ */
+gboolean
+g_unix_credentials_message_is_supported (void)
+{
+ return G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_unix_credentials_message_new:
+ *
+ * Creates a new #GUnixCredentialsMessage with credentials matching the current processes.
+ *
+ * Returns: a new #GUnixCredentialsMessage
+ *
+ * Since: 2.26
+ */
+GSocketControlMessage *
+g_unix_credentials_message_new (void)
+{
+ g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL);
+ return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+ NULL);
+}
+
+/**
+ * g_unix_credentials_message_new:
+ * @credentials: A #GCredentials object.
+ *
+ * Creates a new #GUnixCredentialsMessage holding @credentials.
+ *
+ * Returns: a new #GUnixCredentialsMessage
+ *
+ * Since: 2.26
+ */
+GSocketControlMessage *
+g_unix_credentials_message_new_with_credentials (GCredentials *credentials)
+{
+ g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+ g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL);
+ return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+ "credentials", credentials,
+ NULL);
+}
+
+/**
+ * g_unix_credentials_message_get_credentials:
+ * @message: A #GUnixCredentialsMessage.
+ *
+ * Gets the credentials stored in @message.
+ *
+ * Returns: A #GCredentials instance. Do not free, it is owned by @message.
+ */
+GCredentials *
+g_unix_credentials_message_get_credentials (GUnixCredentialsMessage *message)
+{
+ g_return_val_if_fail (G_IS_UNIX_CREDENTIALS_MESSAGE (message), NULL);
+ return message->priv->credentials;
+}
+
diff --git a/gio/gunixcredentialsmessage.h b/gio/gunixcredentialsmessage.h
new file mode 100644
index 000000000..1db0146e2
--- /dev/null
+++ b/gio/gunixcredentialsmessage.h
@@ -0,0 +1,68 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2009 Codethink Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __G_UNIX_CREDENTIALS_MESSAGE_H__
+#define __G_UNIX_CREDENTIALS_MESSAGE_H__
+
+#include <gio/giotypes.h>
+#include <gio/gsocketcontrolmessage.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_UNIX_CREDENTIALS_MESSAGE (g_unix_credentials_message_get_type ())
+#define G_UNIX_CREDENTIALS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessage))
+#define G_UNIX_CREDENTIALS_MESSAGE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass))
+#define G_IS_UNIX_CREDENTIALS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE))
+#define G_IS_UNIX_CREDENTIALS_MESSAGE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE))
+#define G_UNIX_CREDENTIALS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass))
+
+typedef struct _GUnixCredentialsMessagePrivate GUnixCredentialsMessagePrivate;
+typedef struct _GUnixCredentialsMessageClass GUnixCredentialsMessageClass;
+
+struct _GUnixCredentialsMessageClass
+{
+ GSocketControlMessageClass parent_class;
+
+ /*< private >*/
+
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+};
+
+struct _GUnixCredentialsMessage
+{
+ GSocketControlMessage parent_instance;
+ GUnixCredentialsMessagePrivate *priv;
+};
+
+GType g_unix_credentials_message_get_type (void) G_GNUC_CONST;
+GSocketControlMessage *g_unix_credentials_message_new (void);
+GSocketControlMessage *g_unix_credentials_message_new_with_credentials (GCredentials *credentials);
+GCredentials *g_unix_credentials_message_get_credentials (GUnixCredentialsMessage *message);
+
+gboolean g_unix_credentials_message_is_supported (void);
+
+G_END_DECLS
+
+#endif /* __G_UNIX_CREDENTIALS_MESSAGE_H__ */
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 8be6313ce..4aab457e4 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -1,3 +1,5 @@
+NULL =
+
include $(top_srcdir)/Makefile.decl
INCLUDES = \
@@ -35,19 +37,39 @@ TEST_PROGS += \
srvtarget \
contexts \
gsettings \
- gschema-compile
+ gschema-compile \
+ gdbus-addresses \
+ gdbus-connection \
+ gdbus-names \
+ gdbus-proxy \
+ gdbus-introspection \
+ gdbus-threading \
+ gdbus-export \
+ gdbus-error \
+ gdbus-peer \
+ gdbus-exit-on-close \
+ $(NULL)
+
+SAMPLE_PROGS = \
+ resolver \
+ socket-server \
+ socket-client \
+ echo-server \
+ httpd \
+ send-data \
+ filter-cat \
+ gdbus-example-own-name \
+ gdbus-example-watch-name \
+ gdbus-example-watch-proxy \
+ gdbus-example-server \
+ gdbus-example-subtree \
+ gdbus-example-peer \
+ $(NULL)
-SAMPLE_PROGS = \
- resolver \
- socket-server \
- socket-client \
- echo-server \
- httpd \
- send-data \
- filter-cat
if OS_UNIX
TEST_PROGS += live-g-file desktop-app-info unix-fd #unix-streams
+SAMPLE_PROGS += gdbus-example-unix-fd-client
endif
if OS_WIN32
@@ -150,6 +172,64 @@ gsettings_LDADD = $(progs_ldadd)
gschema_compile_SOURCES = gschema-compile.c
gschema_compile_LDADD = $(progs_ldadd)
+if HAVE_DBUS1
+TEST_PROGS += gdbus-serialization
+gdbus_serialization_SOURCES = gdbus-serialization.c gdbus-tests.h gdbus-tests.c
+gdbus_serialization_CFLAGS = $(DBUS1_CFLAGS)
+gdbus_serialization_LDADD = $(progs_ldadd) $(DBUS1_LIBS)
+endif
+
+gdbus_addresses_SOURCES = gdbus-addresses.c
+gdbus_addresses_LDADD = $(progs_ldadd)
+
+gdbus_connection_SOURCES = gdbus-connection.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_connection_LDADD = $(progs_ldadd)
+
+gdbus_names_SOURCES = gdbus-names.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_names_LDADD = $(progs_ldadd)
+
+gdbus_proxy_SOURCES = gdbus-proxy.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_proxy_LDADD = $(progs_ldadd)
+
+gdbus_introspection_SOURCES = gdbus-introspection.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_introspection_LDADD = $(progs_ldadd)
+
+gdbus_threading_SOURCES = gdbus-threading.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_threading_LDADD = $(progs_ldadd)
+
+gdbus_export_SOURCES = gdbus-export.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_export_LDADD = $(progs_ldadd)
+
+gdbus_error_SOURCES = gdbus-error.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_error_LDADD = $(progs_ldadd)
+
+gdbus_peer_SOURCES = gdbus-peer.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_peer_LDADD = $(progs_ldadd)
+
+gdbus_exit_on_close_SOURCES = gdbus-exit-on-close.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_exit_on_close_LDADD = $(progs_ldadd)
+
+gdbus_example_watch_name_SOURCES = gdbus-example-watch-name.c
+gdbus_example_watch_name_LDADD = $(progs_ldadd)
+
+gdbus_example_watch_proxy_SOURCES = gdbus-example-watch-proxy.c
+gdbus_example_watch_proxy_LDADD = $(progs_ldadd)
+
+gdbus_example_own_name_SOURCES = gdbus-example-own-name.c
+gdbus_example_own_name_LDADD = $(progs_ldadd)
+
+gdbus_example_server_SOURCES = gdbus-example-server.c
+gdbus_example_server_LDADD = $(progs_ldadd)
+
+gdbus_example_unix_fd_client_SOURCES = gdbus-example-unix-fd-client.c
+gdbus_example_unix_fd_client_LDADD = $(progs_ldadd)
+
+gdbus_example_subtree_SOURCES = gdbus-example-subtree.c
+gdbus_example_subtree_LDADD = $(progs_ldadd)
+
+gdbus_example_peer_SOURCES = gdbus-example-peer.c
+gdbus_example_peer_LDADD = $(progs_ldadd)
+
EXTRA_DIST += \
socket-common.c \
org.gtk.test.gschema \
diff --git a/gio/tests/gdbus-addresses.c b/gio/tests/gdbus-addresses.c
new file mode 100644
index 000000000..97e5922ff
--- /dev/null
+++ b/gio/tests/gdbus-addresses.c
@@ -0,0 +1,77 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+static void
+test_unix_address (void)
+{
+ g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar", NULL));
+ g_assert (g_dbus_is_supported_address ("unix:path=/tmp/dbus-test", NULL));
+ g_assert (g_dbus_is_supported_address ("unix:abstract=/tmp/dbus-another-test", NULL));
+ g_assert (g_dbus_is_address ("unix:foo=bar"));
+ g_assert (!g_dbus_is_supported_address ("unix:foo=bar", NULL));
+ g_assert (!g_dbus_is_address ("unix:path=/foo;abstract=/bar"));
+ g_assert (!g_dbus_is_supported_address ("unix:path=/foo;abstract=/bar", NULL));
+ g_assert (g_dbus_is_supported_address ("unix:path=/tmp/concrete;unix:abstract=/tmp/abstract", NULL));
+ g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar"));
+
+ g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid"));
+ g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid", NULL));
+}
+#endif
+
+static void
+test_nonce_tcp_address (void)
+{
+ g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar", NULL));
+ g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv6", NULL));
+ g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv4", NULL));
+
+ g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=blah", NULL));
+ g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=420000,noncefile=/foo/bar,family=ipv4", NULL));
+ g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=x42,noncefile=/foo/bar,family=ipv4", NULL));
+ g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=42x,noncefile=/foo/bar,family=ipv4", NULL));
+ g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=420000,noncefile=/foo/bar,family=ipv4", NULL));
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+#ifdef G_OS_UNIX
+ g_test_add_func ("/gdbus/unix-address", test_unix_address);
+#endif
+ g_test_add_func ("/gdbus/nonce-tcp-address", test_nonce_tcp_address);
+ return g_test_run();
+}
+
diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c
new file mode 100644
index 000000000..1174d6f29
--- /dev/null
+++ b/gio/tests/gdbus-connection.c
@@ -0,0 +1,653 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection life-cycle testing */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_life_cycle (void)
+{
+ GDBusConnection *c;
+ GDBusConnection *c2;
+ GError *error;
+
+ error = NULL;
+
+ /*
+ * Check for correct behavior when no bus is present
+ *
+ */
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ _g_assert_error_domain (error, G_IO_ERROR);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_assert (c == NULL);
+ g_error_free (error);
+ error = NULL;
+
+ /*
+ * Check for correct behavior when a bus is present
+ */
+ session_bus_up ();
+ /* case 1 */
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c != NULL);
+ g_assert (!g_dbus_connection_is_closed (c));
+
+ /*
+ * Check that singleton handling work
+ */
+ c2 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c2 != NULL);
+ g_assert (c == c2);
+ g_object_unref (c2);
+
+ /*
+ * Check that private connections work
+ */
+ c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c2 != NULL);
+ g_assert (c != c2);
+ g_object_unref (c2);
+
+ c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c2 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c2));
+ g_dbus_connection_close (c2);
+ _g_assert_signal_received (c2, "closed");
+ g_assert (g_dbus_connection_is_closed (c2));
+ g_object_unref (c2);
+
+ /*
+ * Check for correct behavior when the bus goes away
+ *
+ */
+ g_assert (!g_dbus_connection_is_closed (c));
+ g_dbus_connection_set_exit_on_close (c, FALSE);
+ session_bus_down ();
+ if (!g_dbus_connection_is_closed (c))
+ _g_assert_signal_received (c, "closed");
+ g_assert (g_dbus_connection_is_closed (c));
+
+ _g_object_wait_for_single_ref (c);
+ g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that sending and receiving messages work as expected */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+msg_cb_expect_error_disconnected (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ g_assert (result == NULL);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_unknown_method (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
+ g_assert (g_dbus_error_is_remote_error (error));
+ g_assert (result == NULL);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_success (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_unref (result);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ g_assert (result == NULL);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_cancelled_2 (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ g_assert (result == NULL);
+
+ g_main_loop_quit (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_send (void)
+{
+ GDBusConnection *c;
+ GCancellable *ca;
+
+ session_bus_up ();
+
+ /* First, get an unopened connection */
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c != NULL);
+ g_assert (!g_dbus_connection_is_closed (c));
+
+ /*
+ * Check that we never actually send a message if the GCancellable
+ * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED
+ * when the actual connection is not up.
+ */
+ ca = g_cancellable_new ();
+ g_cancellable_cancel (ca);
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ ca,
+ (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+ NULL);
+ g_main_loop_run (loop);
+ g_object_unref (ca);
+
+ /*
+ * Check that we get a reply to the GetId() method call.
+ */
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) msg_cb_expect_success,
+ NULL);
+ g_main_loop_run (loop);
+
+ /*
+ * Check that we get an error reply to the NonExistantMethod() method call.
+ */
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "NonExistantMethod", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) msg_cb_expect_error_unknown_method,
+ NULL);
+ g_main_loop_run (loop);
+
+ /*
+ * Check that cancellation works when the message is already in flight.
+ */
+ ca = g_cancellable_new ();
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ ca,
+ (GAsyncReadyCallback) msg_cb_expect_error_cancelled_2,
+ NULL);
+ g_cancellable_cancel (ca);
+ g_main_loop_run (loop);
+ g_object_unref (ca);
+
+ /*
+ * Check that we get an error when sending to a connection that is disconnected.
+ */
+ g_dbus_connection_set_exit_on_close (c, FALSE);
+ session_bus_down ();
+ _g_assert_signal_received (c, "closed");
+ g_assert (g_dbus_connection_is_closed (c));
+
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) msg_cb_expect_error_disconnected,
+ NULL);
+ g_main_loop_run (loop);
+
+ _g_object_wait_for_single_ref (c);
+ g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection signal tests */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_signal_handler (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ gint *counter = user_data;
+ *counter += 1;
+
+ /*g_debug ("in test_connection_signal_handler (sender=%s path=%s interface=%s member=%s)",
+ sender_name,
+ object_path,
+ interface_name,
+ signal_name);*/
+
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+test_connection_signal_quit_mainloop (gpointer user_data)
+{
+ gboolean *quit_mainloop_fired = user_data;
+ *quit_mainloop_fired = TRUE;
+ g_main_loop_quit (loop);
+ return TRUE;
+}
+
+static void
+test_connection_signals (void)
+{
+ GDBusConnection *c1;
+ GDBusConnection *c2;
+ GDBusConnection *c3;
+ guint s1;
+ guint s2;
+ guint s3;
+ gint count_s1;
+ gint count_s2;
+ gint count_name_owner_changed;
+ GError *error;
+ gboolean ret;
+ GVariant *result;
+
+ error = NULL;
+
+ /*
+ * Bring up first separate connections
+ */
+ session_bus_up ();
+ /* if running with dbus-monitor, it claims the name :1.0 - so if we don't run with the monitor
+ * emulate this
+ */
+ if (g_getenv ("G_DBUS_MONITOR") == NULL)
+ {
+ c1 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c1 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c1));
+ g_object_unref (c1);
+ }
+ c1 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c1 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c1));
+ g_assert_cmpstr (g_dbus_connection_get_unique_name (c1), ==, ":1.1");
+
+ /*
+ * Install two signal handlers for the first connection
+ *
+ * - Listen to the signal "Foo" from :1.2 (e.g. c2)
+ * - Listen to the signal "Foo" from anyone (e.g. both c2 and c3)
+ *
+ * and then count how many times this signal handler was invoked.
+ */
+ s1 = g_dbus_connection_signal_subscribe (c1,
+ ":1.2",
+ "org.gtk.GDBus.ExampleInterface",
+ "Foo",
+ "/org/gtk/GDBus/ExampleInterface",
+ NULL,
+ test_connection_signal_handler,
+ &count_s1,
+ NULL);
+ s2 = g_dbus_connection_signal_subscribe (c1,
+ NULL, /* match any sender */
+ "org.gtk.GDBus.ExampleInterface",
+ "Foo",
+ "/org/gtk/GDBus/ExampleInterface",
+ NULL,
+ test_connection_signal_handler,
+ &count_s2,
+ NULL);
+ s3 = g_dbus_connection_signal_subscribe (c1,
+ "org.freedesktop.DBus", /* sender */
+ "org.freedesktop.DBus", /* interface */
+ "NameOwnerChanged", /* member */
+ "/org/freedesktop/DBus", /* path */
+ NULL,
+ test_connection_signal_handler,
+ &count_name_owner_changed,
+ NULL);
+ g_assert (s1 != 0);
+ g_assert (s2 != 0);
+ g_assert (s3 != 0);
+
+ count_s1 = 0;
+ count_s2 = 0;
+ count_name_owner_changed = 0;
+
+ /*
+ * Bring up two other connections
+ */
+ c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c2 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c2));
+ g_assert_cmpstr (g_dbus_connection_get_unique_name (c2), ==, ":1.2");
+ c3 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c3 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c3));
+ g_assert_cmpstr (g_dbus_connection_get_unique_name (c3), ==, ":1.3");
+
+ /*
+ * Make c2 emit "Foo" - we should catch it twice
+ *
+ * Note that there is no way to be sure that the signal subscriptions
+ * on c1 are effective yet - for all we know, the AddMatch() messages
+ * could sit waiting in a buffer somewhere between this process and
+ * the message bus. And emitting signals on c2 (a completely other
+ * socket!) will not necessarily change this.
+ *
+ * To ensure this is not the case, do a synchronous call on c1.
+ */
+ result = g_dbus_connection_invoke_method_sync (c1,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL, /* parameters */
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_unref (result);
+ /* now, emit the signal on c2 */
+ ret = g_dbus_connection_emit_signal (c2,
+ NULL, /* destination bus name */
+ "/org/gtk/GDBus/ExampleInterface",
+ "org.gtk.GDBus.ExampleInterface",
+ "Foo",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ while (!(count_s1 == 1 && count_s2 == 1))
+ g_main_loop_run (loop);
+ g_assert_cmpint (count_s1, ==, 1);
+ g_assert_cmpint (count_s2, ==, 1);
+
+ /*
+ * Make c3 emit "Foo" - we should catch it only once
+ */
+ ret = g_dbus_connection_emit_signal (c3,
+ NULL, /* destination bus name */
+ "/org/gtk/GDBus/ExampleInterface",
+ "org.gtk.GDBus.ExampleInterface",
+ "Foo",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ while (!(count_s1 == 1 && count_s2 == 2))
+ g_main_loop_run (loop);
+ g_assert_cmpint (count_s1, ==, 1);
+ g_assert_cmpint (count_s2, ==, 2);
+
+ /*
+ * Also to check the total amount of NameOwnerChanged signals - use a 5 second ceiling
+ * to avoid spinning forever
+ */
+ gboolean quit_mainloop_fired;
+ guint quit_mainloop_id;
+ quit_mainloop_fired = FALSE;
+ quit_mainloop_id = g_timeout_add (5000, test_connection_signal_quit_mainloop, &quit_mainloop_fired);
+ while (count_name_owner_changed != 2 && !quit_mainloop_fired)
+ g_main_loop_run (loop);
+ g_source_remove (quit_mainloop_id);
+ g_assert_cmpint (count_s1, ==, 1);
+ g_assert_cmpint (count_s2, ==, 2);
+ g_assert_cmpint (count_name_owner_changed, ==, 2);
+
+ g_dbus_connection_signal_unsubscribe (c1, s1);
+ g_dbus_connection_signal_unsubscribe (c1, s2);
+ g_dbus_connection_signal_unsubscribe (c1, s3);
+
+ _g_object_wait_for_single_ref (c1);
+ _g_object_wait_for_single_ref (c2);
+ _g_object_wait_for_single_ref (c3);
+
+ g_object_unref (c1);
+ g_object_unref (c2);
+ g_object_unref (c3);
+
+ session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ guint num_handled;
+ guint32 serial;
+} FilterData;
+
+static gboolean
+filter_func (GDBusConnection *connection,
+ GDBusMessage *message,
+ gpointer user_data)
+{
+ FilterData *data = user_data;
+ guint32 reply_serial;
+
+ reply_serial = g_dbus_message_get_reply_serial (message);
+ if (reply_serial == data->serial)
+ data->num_handled += 1;
+
+ return FALSE;
+}
+
+static void
+test_connection_filter (void)
+{
+ GDBusConnection *c;
+ FilterData data;
+ GDBusMessage *m;
+ GDBusMessage *r;
+ GError *error;
+ guint filter_id;
+
+ memset (&data, '\0', sizeof (FilterData));
+
+ session_bus_up ();
+
+ error = NULL;
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c != NULL);
+
+ filter_id = g_dbus_connection_add_filter (c,
+ filter_func,
+ &data,
+ NULL);
+
+ m = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+ "/org/freedesktop/DBus", /* path */
+ "org.freedesktop.DBus", /* interface */
+ "GetNameOwner");
+ g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus"));
+ error = NULL;
+ g_dbus_connection_send_message (c, m, &data.serial, &error);
+ g_assert_no_error (error);
+
+ while (data.num_handled == 0)
+ g_thread_yield ();
+
+ g_dbus_connection_send_message (c, m, &data.serial, &error);
+ g_assert_no_error (error);
+
+ while (data.num_handled == 1)
+ g_thread_yield ();
+
+ r = g_dbus_connection_send_message_with_reply_sync (c,
+ m,
+ -1,
+ &data.serial,
+ NULL, /* GCancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (r != NULL);
+ g_object_unref (r);
+ g_assert_cmpint (data.num_handled, ==, 3);
+
+ g_dbus_connection_remove_filter (c, filter_id);
+
+ r = g_dbus_connection_send_message_with_reply_sync (c,
+ m,
+ -1,
+ &data.serial,
+ NULL, /* GCancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (r != NULL);
+ g_object_unref (r);
+ g_assert_cmpint (data.num_handled, ==, 3);
+
+ _g_object_wait_for_single_ref (c);
+ g_object_unref (c);
+ g_object_unref (m);
+
+ session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ g_test_add_func ("/gdbus/connection-life-cycle", test_connection_life_cycle);
+ g_test_add_func ("/gdbus/connection-send", test_connection_send);
+ g_test_add_func ("/gdbus/connection-signals", test_connection_signals);
+ g_test_add_func ("/gdbus/connection-filter", test_connection_filter);
+ return g_test_run();
+}
diff --git a/gio/tests/gdbus-error.c b/gio/tests/gdbus-error.c
new file mode 100644
index 000000000..342771002
--- /dev/null
+++ b/gio/tests/gdbus-error.c
@@ -0,0 +1,198 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that registered errors are properly mapped */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_registered_error (const gchar *given_dbus_error_name,
+ GQuark error_domain,
+ gint error_code)
+{
+ GError *error;
+ gchar *dbus_error_name;
+
+ error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+ g_assert_error (error, error_domain, error_code);
+ g_assert (g_dbus_error_is_remote_error (error));
+ g_assert (g_dbus_error_strip_remote_error (error));
+ g_assert_cmpstr (error->message, ==, "test message");
+ dbus_error_name = g_dbus_error_get_remote_error (error);
+ g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+ g_free (dbus_error_name);
+ g_error_free (error);
+}
+
+static void
+test_registered_errors (void)
+{
+ /* Here we check that we are able to map to GError and back for registered
+ * errors.
+ *
+ * For example, if "org.freedesktop.DBus.Error.AddressInUse" is
+ * associated with (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED), check
+ * that
+ *
+ * - Creating a GError for e.g. "org.freedesktop.DBus.Error.AddressInUse"
+ * has (error_domain, code) == (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED)
+ *
+ * - That it is possible to recover e.g. "org.freedesktop.DBus.Error.AddressInUse"
+ * as the D-Bus error name when dealing with an error with (error_domain, code) ==
+ * (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED)
+ *
+ * We just check a couple of well-known errors.
+ */
+ check_registered_error ("org.freedesktop.DBus.Error.Failed",
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED);
+ check_registered_error ("org.freedesktop.DBus.Error.AddressInUse",
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ADDRESS_IN_USE);
+ check_registered_error ("org.freedesktop.DBus.Error.UnknownMethod",
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_UNKNOWN_METHOD);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_unregistered_error (const gchar *given_dbus_error_name)
+{
+ GError *error;
+ gchar *dbus_error_name;
+
+ error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+ g_assert (g_dbus_error_is_remote_error (error));
+ dbus_error_name = g_dbus_error_get_remote_error (error);
+ g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+ g_free (dbus_error_name);
+
+ /* strip the message */
+ g_assert (g_dbus_error_strip_remote_error (error));
+ g_assert_cmpstr (error->message, ==, "test message");
+
+ /* check that we can no longer recover the D-Bus error name */
+ g_assert (g_dbus_error_get_remote_error (error) == NULL);
+
+ g_error_free (error);
+
+}
+
+static void
+test_unregistered_errors (void)
+{
+ /* Here we check that we are able to map to GError and back for unregistered
+ * errors.
+ *
+ * For example, if "com.example.Error.Failed" is not registered, then check
+ *
+ * - Creating a GError for e.g. "com.example.Error.Failed" has (error_domain, code) ==
+ * (G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)
+ *
+ * - That it is possible to recover e.g. "com.example.Error.Failed" from that
+ * GError.
+ *
+ * We just check a couple of random errors.
+ */
+
+ check_unregistered_error ("com.example.Error.Failed");
+ check_unregistered_error ("foobar.buh");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_transparent_gerror (GQuark error_domain,
+ gint error_code)
+{
+ GError *error;
+ gchar *given_dbus_error_name;
+ gchar *dbus_error_name;
+
+ error = g_error_new (error_domain, error_code, "test message");
+ given_dbus_error_name = g_dbus_error_encode_gerror (error);
+ g_assert (g_str_has_prefix (given_dbus_error_name, "org.gtk.GDBus.UnmappedGError.Quark"));
+ g_error_free (error);
+
+ error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+ g_assert_error (error, error_domain, error_code);
+ g_assert (g_dbus_error_is_remote_error (error));
+ dbus_error_name = g_dbus_error_get_remote_error (error);
+ g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+ g_free (dbus_error_name);
+ g_free (given_dbus_error_name);
+
+ /* strip the message */
+ g_assert (g_dbus_error_strip_remote_error (error));
+ g_assert_cmpstr (error->message, ==, "test message");
+
+ /* check that we can no longer recover the D-Bus error name */
+ g_assert (g_dbus_error_get_remote_error (error) == NULL);
+
+ g_error_free (error);
+}
+
+static void
+test_transparent_gerror (void)
+{
+ /* Here we check that we are able to transparent pass unregistered GError's
+ * over the wire.
+ *
+ * For example, if G_IO_ERROR_FAILED is not registered, then check
+ *
+ * - g_dbus_error_encode_gerror() returns something of the form
+ * org.gtk.GDBus.UnmappedGError.Quark_HEXENCODED_QUARK_NAME_.Code_ERROR_CODE
+ *
+ * - mapping back the D-Bus error name gives us G_IO_ERROR_FAILED
+ *
+ * - That it is possible to recover the D-Bus error name from the
+ * GError.
+ *
+ * We just check a couple of random errors.
+ */
+
+ check_transparent_gerror (G_IO_ERROR, G_IO_ERROR_FAILED);
+ check_transparent_gerror (G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/gdbus/registered-errors", test_registered_errors);
+ g_test_add_func ("/gdbus/unregistered-errors", test_unregistered_errors);
+ g_test_add_func ("/gdbus/transparent-gerror", test_transparent_gerror);
+
+ return g_test_run();
+}
diff --git a/gio/tests/gdbus-example-own-name.c b/gio/tests/gdbus-example-own-name.c
new file mode 100644
index 000000000..733c29ea4
--- /dev/null
+++ b/gio/tests/gdbus-example-own-name.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ /* This is where we'd export some objects on the bus */
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("Acquired the name %s on the session bus\n", name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("Lost the name %s on the session bus\n", name);
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint owner_id;
+ GMainLoop *loop;
+ GBusNameOwnerFlags flags;
+ gboolean opt_replace;
+ gboolean opt_allow_replacement;
+ gchar *opt_name;
+ GOptionContext *opt_context;
+ GError *error;
+ GOptionEntry opt_entries[] =
+ {
+ { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing name if possible", NULL },
+ { "allow-replacement", 'a', 0, G_OPTION_ARG_NONE, &opt_allow_replacement, "Allow replacement", NULL },
+ { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to acquire", NULL },
+ { NULL}
+ };
+
+ g_type_init ();
+
+ error = NULL;
+ opt_name = NULL;
+ opt_replace = FALSE;
+ opt_allow_replacement = FALSE;
+ opt_context = g_option_context_new ("g_bus_own_name() example");
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s", error->message);
+ return 1;
+ }
+ if (opt_name == NULL)
+ {
+ g_printerr ("Incorrect usage, try --help.\n");
+ return 1;
+ }
+
+ flags = G_BUS_NAME_OWNER_FLAGS_NONE;
+ if (opt_replace)
+ flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+ if (opt_allow_replacement)
+ flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ opt_name,
+ flags,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unown_name (owner_id);
+
+ return 0;
+}
diff --git a/gio/tests/gdbus-example-peer.c b/gio/tests/gdbus-example-peer.c
new file mode 100644
index 000000000..a1041d9c7
--- /dev/null
+++ b/gio/tests/gdbus-example-peer.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+/*
+
+Usage examples (modulo addresses / credentials).
+
+UNIX domain socket transport:
+
+ Server:
+ $ ./gdbus-example-peer --server --address unix:abstract=myaddr
+ Server is listening at: unix:abstract=myaddr
+ Client connected.
+ Peer credentials: GCredentials:unix-user=500,unix-group=500,unix-process=13378
+ Negotiated capabilities: unix-fd-passing=1
+ Client said: Hey, it's 1273093080 already!
+
+ Client:
+ $ ./gdbus-example-peer --address unix:abstract=myaddr
+ Connected.
+ Negotiated capabilities: unix-fd-passing=1
+ Server said: You said 'Hey, it's 1273093080 already!'. KTHXBYE!
+
+Nonce-secured TCP transport on the same host:
+
+ Server:
+ $ ./gdbus-example-peer --server --address nonce-tcp:
+ Server is listening at: nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV
+ Client connected.
+ Peer credentials: (no credentials received)
+ Negotiated capabilities: unix-fd-passing=0
+ Client said: Hey, it's 1273093206 already!
+
+ Client:
+ $ ./gdbus-example-peer -address nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV
+ Connected.
+ Negotiated capabilities: unix-fd-passing=0
+ Server said: You said 'Hey, it's 1273093206 already!'. KTHXBYE!
+
+TCP transport on two different hosts with a shared home directory:
+
+ Server:
+ host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0
+ Server is listening at: tcp:host=0.0.0.0,port=46314
+ Client connected.
+ Peer credentials: (no credentials received)
+ Negotiated capabilities: unix-fd-passing=0
+ Client said: Hey, it's 1273093337 already!
+
+ Client:
+ host2 $ ./gdbus-example-peer -a tcp:host=host1,port=46314
+ Connected.
+ Negotiated capabilities: unix-fd-passing=0
+ Server said: You said 'Hey, it's 1273093337 already!'. KTHXBYE!
+
+TCP transport on two different hosts without authentication:
+
+ Server:
+ host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0 --allow-anonymous
+ Server is listening at: tcp:host=0.0.0.0,port=59556
+ Client connected.
+ Peer credentials: (no credentials received)
+ Negotiated capabilities: unix-fd-passing=0
+ Client said: Hey, it's 1273093652 already!
+
+ Client:
+ host2 $ ./gdbus-example-peer -a tcp:host=host1,port=59556
+ Connected.
+ Negotiated capabilities: unix-fd-passing=0
+ Server said: You said 'Hey, it's 1273093652 already!'. KTHXBYE!
+
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gtk.GDBus.TestPeerInterface'>"
+ " <method name='HelloWorld'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ if (g_strcmp0 (method_name, "HelloWorld") == 0)
+ {
+ const gchar *greeting;
+ gchar *response;
+
+ g_variant_get (parameters, "(s)", &greeting);
+ response = g_strdup_printf ("You said '%s'. KTHXBYE!", greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+ g_print ("Client said: %s\n", greeting);
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ NULL,
+ NULL,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_new_connection (GDBusServer *server,
+ GDBusConnection *connection,
+ gpointer user_data)
+{
+ guint registration_id;
+ GCredentials *credentials;
+ gchar *s;
+
+ credentials = g_dbus_connection_get_peer_credentials (connection);
+ if (credentials == NULL)
+ s = g_strdup ("(no credentials received)");
+ else
+ s = g_credentials_to_string (credentials);
+
+
+ g_print ("Client connected.\n"
+ "Peer credentials: %s\n"
+ "Negotiated capabilities: unix-fd-passing=%d\n",
+ s,
+ g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+ g_object_ref (connection);
+ registration_id = g_dbus_connection_register_object (connection,
+ "/org/gtk/GDBus/TestObject",
+ "org.gtk.GDBus.TestPeerInterface",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ NULL, /* user_data */
+ NULL, /* user_data_free_func */
+ NULL); /* GError** */
+ g_assert (registration_id > 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc, char *argv[])
+{
+ gint ret;
+ gboolean opt_server;
+ gchar *opt_address;
+ GOptionContext *opt_context;
+ gboolean opt_allow_anonymous;
+ GError *error;
+ GOptionEntry opt_entries[] =
+ {
+ { "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL },
+ { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL },
+ { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL },
+ { NULL}
+ };
+
+ ret = 1;
+
+ g_type_init ();
+
+ opt_address = NULL;
+ opt_server = FALSE;
+ opt_allow_anonymous = FALSE;
+
+ opt_context = g_option_context_new ("peer-to-peer example");
+ error = NULL;
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s\n", error->message);
+ g_error_free (error);
+ goto out;
+ }
+ if (opt_address == NULL)
+ {
+ g_printerr ("Incorrect usage, try --help.\n");
+ goto out;
+ }
+ if (!opt_server && opt_allow_anonymous)
+ {
+ g_printerr ("The --allow-anonymous option only makes sense when used with --server.\n");
+ goto out;
+ }
+
+ /* We are lazy here - we don't want to manually provide
+ * the introspection data structures - so we just build
+ * them from XML.
+ */
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ if (opt_server)
+ {
+ GDBusServer *server;
+ gchar *guid;
+ GMainLoop *loop;
+ GDBusServerFlags server_flags;
+
+ guid = g_dbus_generate_guid ();
+
+ server_flags = G_DBUS_SERVER_FLAGS_NONE;
+ if (opt_allow_anonymous)
+ server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
+
+ error = NULL;
+ server = g_dbus_server_new_sync (opt_address,
+ server_flags,
+ guid,
+ NULL, /* GDBusAuthObserver */
+ NULL, /* GCancellable */
+ &error);
+ g_dbus_server_start (server);
+ g_free (guid);
+
+ if (server == NULL)
+ {
+ g_printerr ("Error creating server at address %s: %s\n", opt_address, error->message);
+ g_error_free (error);
+ goto out;
+ }
+ g_print ("Server is listening at: %s\n", g_dbus_server_get_client_address (server));
+ g_signal_connect (server,
+ "new-connection",
+ G_CALLBACK (on_new_connection),
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_object_unref (server);
+ g_main_loop_unref (loop);
+ }
+ else
+ {
+ GDBusConnection *connection;
+ const gchar *greeting_response;
+ GVariant *value;
+ gchar *greeting;
+
+ error = NULL;
+ connection = g_dbus_connection_new_for_address_sync (opt_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* GCancellable */
+ &error);
+ if (connection == NULL)
+ {
+ g_printerr ("Error connecting to D-Bus address %s: %s\n", opt_address, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_print ("Connected.\n"
+ "Negotiated capabilities: unix-fd-passing=%d\n",
+ g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+ greeting = g_strdup_printf ("Hey, it's %" G_GUINT64_FORMAT " already!", (guint64) time (NULL));
+ value = g_dbus_connection_invoke_method_sync (connection,
+ NULL, /* bus_name */
+ "/org/gtk/GDBus/TestObject",
+ "org.gtk.GDBus.TestPeerInterface",
+ "HelloWorld",
+ g_variant_new ("(s)", greeting),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (value == NULL)
+ {
+ g_printerr ("Error invoking HelloWorld(): %s\n", error->message);
+ g_error_free (error);
+ goto out;
+ }
+ g_variant_get (value, "(s)", &greeting_response);
+ g_print ("Server said: %s\n", greeting_response);
+ g_variant_unref (value);
+
+ g_object_unref (connection);
+ }
+ g_dbus_node_info_unref (introspection_data);
+
+ ret = 0;
+
+ out:
+ return ret;
+}
diff --git a/gio/tests/gdbus-example-server.c b/gio/tests/gdbus-example-server.c
new file mode 100644
index 000000000..fe667c040
--- /dev/null
+++ b/gio/tests/gdbus-example-server.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+
+#ifdef G_OS_UNIX
+/* For STDOUT_FILENO */
+#include <unistd.h>
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gtk.GDBus.TestInterface'>"
+ " <method name='HelloWorld'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " <method name='EmitSignal'>"
+ " <arg type='d' name='speed_in_mph' direction='in'/>"
+ " </method>"
+ " <method name='GimmeStdout'/>"
+ " <signal name='VelocityChanged'>"
+ " <arg type='d' name='speed_in_mph'/>"
+ " <arg type='s' name='speed_as_string'/>"
+ " </signal>"
+ " <property type='s' name='FluxCapicitorName' access='read'/>"
+ " <property type='s' name='Title' access='readwrite'/>"
+ " <property type='s' name='ReadingAlwaysThrowsError' access='read'/>"
+ " <property type='s' name='WritingAlwaysThrowsError' access='readwrite'/>"
+ " <property type='s' name='OnlyWritable' access='write'/>"
+ " <property type='s' name='Foo' access='read'/>"
+ " <property type='s' name='Bar' access='read'/>"
+ " </interface>"
+ "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ if (g_strcmp0 (method_name, "HelloWorld") == 0)
+ {
+ const gchar *greeting;
+
+ g_variant_get (parameters, "(s)", &greeting);
+
+ if (g_strcmp0 (greeting, "Return Unregistered") == 0)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED_HANDLED,
+ "As requested, here's a GError not registered (G_IO_ERROR_FAILED_HANDLED)");
+ }
+ else if (g_strcmp0 (greeting, "Return Registered") == 0)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,
+ "As requested, here's a GError that is registered (G_DBUS_ERROR_MATCH_RULE_NOT_FOUND)");
+ }
+ else if (g_strcmp0 (greeting, "Return Raw") == 0)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gtk.GDBus.SomeErrorName",
+ "As requested, here's a raw D-Bus error");
+ }
+ else
+ {
+ gchar *response;
+ response = g_strdup_printf ("You greeted me with '%s'. Thanks!", greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+ }
+ }
+ else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+ {
+ GError *local_error;
+ gdouble speed_in_mph;
+ gchar *speed_as_string;
+
+ g_variant_get (parameters, "(d)", &speed_in_mph);
+ speed_as_string = g_strdup_printf ("%g mph!", speed_in_mph);
+
+ local_error = NULL;
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ object_path,
+ interface_name,
+ "VelocityChanged",
+ g_variant_new ("(ds)",
+ speed_in_mph,
+ speed_as_string),
+ &local_error);
+ g_assert_no_error (local_error);
+ g_free (speed_as_string);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (g_strcmp0 (method_name, "GimmeStdout") == 0)
+ {
+#ifdef G_OS_UNIX
+ if (g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+ {
+ GDBusMessage *reply;
+ GUnixFDList *fd_list;
+ GError *error;
+
+ fd_list = g_unix_fd_list_new ();
+ error = NULL;
+ g_unix_fd_list_append (fd_list, STDOUT_FILENO, &error);
+ g_assert_no_error (error);
+
+ reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
+ g_dbus_message_set_unix_fd_list (reply, fd_list);
+
+ error = NULL;
+ g_dbus_connection_send_message (connection,
+ reply,
+ NULL, /* out_serial */
+ &error);
+ g_assert_no_error (error);
+
+ g_object_unref (invocation);
+ g_object_unref (fd_list);
+ g_object_unref (reply);
+ }
+ else
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gtk.GDBus.Failed",
+ "Your message bus daemon does not support file descriptor passing (need D-Bus >= 1.3.0)");
+ }
+#else
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gtk.GDBus.NotOnUnix",
+ "Your OS does not support file descriptor passing");
+#endif
+ }
+}
+
+static gchar *_global_title = NULL;
+
+static gboolean swap_a_and_b = FALSE;
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GVariant *ret;
+
+ ret = NULL;
+ if (g_strcmp0 (property_name, "FluxCapicitorName") == 0)
+ {
+ ret = g_variant_new_string ("DeLorean");
+ }
+ else if (g_strcmp0 (property_name, "Title") == 0)
+ {
+ if (_global_title == NULL)
+ _global_title = g_strdup ("Back To C!");
+ ret = g_variant_new_string (_global_title);
+ }
+ else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Hello %s. I thought I said reading this property "
+ "always results in an error. kthxbye",
+ sender);
+ }
+ else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+ {
+ ret = g_variant_new_string ("There's no home like home");
+ }
+ else if (g_strcmp0 (property_name, "Foo") == 0)
+ {
+ ret = g_variant_new_string (swap_a_and_b ? "Tock" : "Tick");
+ }
+ else if (g_strcmp0 (property_name, "Bar") == 0)
+ {
+ ret = g_variant_new_string (swap_a_and_b ? "Tick" : "Tock");
+ }
+
+ return ret;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ if (g_strcmp0 (property_name, "Title") == 0)
+ {
+ if (g_strcmp0 (_global_title, g_variant_get_string (value, NULL)) != 0)
+ {
+ GVariantBuilder *builder;
+ GError *local_error;
+
+ g_free (_global_title);
+ _global_title = g_variant_dup_string (value, NULL);
+
+ local_error = NULL;
+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add (builder,
+ "{sv}",
+ "Title",
+ g_variant_new_string (_global_title));
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ object_path,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv})",
+ interface_name,
+ builder),
+ &local_error);
+ g_assert_no_error (local_error);
+ }
+ }
+ else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+ {
+ /* do nothing - they can't read it after all! */
+ }
+ else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Hello AGAIN %s. I thought I said writing this property "
+ "always results in an error. kthxbye",
+ sender);
+ }
+
+ return *error == NULL;
+}
+
+
+/* for now */
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ handle_get_property,
+ handle_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_timeout_cb (gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+ GVariantBuilder *builder;
+ GError *error;
+
+ swap_a_and_b = !swap_a_and_b;
+
+ error = NULL;
+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add (builder,
+ "{sv}",
+ "Foo",
+ g_variant_new_string (swap_a_and_b ? "Tock" : "Tick"));
+ g_variant_builder_add (builder,
+ "{sv}",
+ "Bar",
+ g_variant_new_string (swap_a_and_b ? "Tick" : "Tock"));
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ "/org/gtk/GDBus/TestObject",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv})",
+ "org.gtk.GDBus.TestInterface",
+ builder),
+ &error);
+ g_assert_no_error (error);
+
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ guint registration_id;
+
+ registration_id = g_dbus_connection_register_object (connection,
+ "/org/gtk/GDBus/TestObject",
+ "org.gtk.GDBus.TestInterface",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ NULL, /* user_data */
+ NULL, /* user_data_free_func */
+ NULL); /* GError** */
+ g_assert (registration_id > 0);
+
+ /* swap value of properties Foo and Bar every two seconds */
+ g_timeout_add_seconds (2,
+ on_timeout_cb,
+ connection);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint owner_id;
+ GMainLoop *loop;
+
+ g_type_init ();
+
+ /* We are lazy here - we don't want to manually provide
+ * the introspection data structures - so we just build
+ * them from XML.
+ */
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.TestServer",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unown_name (owner_id);
+
+ g_dbus_node_info_unref (introspection_data);
+
+ return 0;
+}
diff --git a/gio/tests/gdbus-example-subtree.c b/gio/tests/gdbus-example-subtree.c
new file mode 100644
index 000000000..e8da6da88
--- /dev/null
+++ b/gio/tests/gdbus-example-subtree.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+static const GDBusInterfaceInfo *manager_interface_info = NULL;
+static const GDBusInterfaceInfo *block_interface_info = NULL;
+static const GDBusInterfaceInfo *partition_interface_info = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gtk.GDBus.Example.Manager'>"
+ " <method name='Hello'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " </interface>"
+ " <interface name='org.gtk.GDBus.Example.Block'>"
+ " <method name='Hello'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " <property type='i' name='Major' access='read'/>"
+ " <property type='i' name='Minor' access='read'/>"
+ " <property type='s' name='Notes' access='readwrite'/>"
+ " </interface>"
+ " <interface name='org.gtk.GDBus.Example.Partition'>"
+ " <method name='Hello'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " <property type='i' name='PartitionNumber' access='read'/>"
+ " <property type='s' name='Notes' access='readwrite'/>"
+ " </interface>"
+ "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+manager_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ const gchar *greeting;
+ gchar *response;
+
+ g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Manager");
+ g_assert_cmpstr (method_name, ==, "Hello");
+
+ g_variant_get (parameters, "(s)", &greeting);
+
+ response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+ interface_name,
+ method_name,
+ (const gchar *) user_data,
+ object_path,
+ greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+}
+
+const GDBusInterfaceVTable manager_vtable =
+{
+ manager_method_call,
+ NULL, /* get_property */
+ NULL /* set_property */
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+block_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Block");
+
+ if (g_strcmp0 (method_name, "Hello") == 0)
+ {
+ const gchar *greeting;
+ gchar *response;
+
+ g_variant_get (parameters, "(s)", &greeting);
+
+ response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+ interface_name,
+ method_name,
+ (const gchar *) user_data,
+ object_path,
+ greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+ }
+ else if (g_strcmp0 (method_name, "DoStuff") == 0)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gtk.GDBus.TestSubtree.Error.Failed",
+ "This method intentionally always fails");
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static GVariant *
+block_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GVariant *ret;
+ const gchar *node;
+ gint major;
+ gint minor;
+
+ node = strrchr (object_path, '/') + 1;
+ if (g_str_has_prefix (node, "sda"))
+ major = 8;
+ else
+ major = 9;
+ if (strlen (node) == 4)
+ minor = node[3] - '0';
+ else
+ minor = 0;
+
+ ret = NULL;
+ if (g_strcmp0 (property_name, "Major") == 0)
+ {
+ ret = g_variant_new_int32 (major);
+ }
+ else if (g_strcmp0 (property_name, "Minor") == 0)
+ {
+ ret = g_variant_new_int32 (minor);
+ }
+ else if (g_strcmp0 (property_name, "Notes") == 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Hello %s. I thought I said reading this property "
+ "always results in an error. kthxbye",
+ sender);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return ret;
+}
+
+static gboolean
+block_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ /* TODO */
+ g_assert_not_reached ();
+}
+
+const GDBusInterfaceVTable block_vtable =
+{
+ block_method_call,
+ block_get_property,
+ block_set_property,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+partition_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ const gchar *greeting;
+ gchar *response;
+
+ g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Partition");
+ g_assert_cmpstr (method_name, ==, "Hello");
+
+ g_variant_get (parameters, "(s)", &greeting);
+
+ response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+ interface_name,
+ method_name,
+ (const gchar *) user_data,
+ object_path,
+ greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+}
+
+const GDBusInterfaceVTable partition_vtable =
+{
+ partition_method_call,
+ //partition_get_property,
+ //partition_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar **
+subtree_enumerate (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ gpointer user_data)
+{
+ gchar **nodes;
+ GPtrArray *p;
+
+ p = g_ptr_array_new ();
+ g_ptr_array_add (p, g_strdup ("sda"));
+ g_ptr_array_add (p, g_strdup ("sda1"));
+ g_ptr_array_add (p, g_strdup ("sda2"));
+ g_ptr_array_add (p, g_strdup ("sda3"));
+ g_ptr_array_add (p, g_strdup ("sdb"));
+ g_ptr_array_add (p, g_strdup ("sdb1"));
+ g_ptr_array_add (p, g_strdup ("sdc"));
+ g_ptr_array_add (p, g_strdup ("sdc1"));
+ g_ptr_array_add (p, NULL);
+ nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+ return nodes;
+}
+
+static GPtrArray *
+subtree_introspect (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *node,
+ gpointer user_data)
+{
+ GPtrArray *p;
+
+ p = g_ptr_array_new ();
+ if (g_strcmp0 (node, "/") == 0)
+ {
+ g_ptr_array_add (p, (gpointer) manager_interface_info);
+ }
+ else
+ {
+ g_ptr_array_add (p, (gpointer) block_interface_info);
+ if (strlen (node) == 4)
+ g_ptr_array_add (p, (gpointer) partition_interface_info);
+ }
+
+ return p;
+}
+
+static const GDBusInterfaceVTable *
+subtree_dispatch (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *node,
+ gpointer *out_user_data,
+ gpointer user_data)
+{
+ const GDBusInterfaceVTable *vtable_to_return;
+ gpointer user_data_to_return;
+
+ if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Manager") == 0)
+ {
+ user_data_to_return = "The Root";
+ vtable_to_return = &manager_vtable;
+ }
+ else
+ {
+ if (strlen (node) == 4)
+ user_data_to_return = "A partition";
+ else
+ user_data_to_return = "A block device";
+
+ if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Block") == 0)
+ vtable_to_return = &block_vtable;
+ else if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Partition") == 0)
+ vtable_to_return = &partition_vtable;
+ else
+ g_assert_not_reached ();
+ }
+
+ *out_user_data = user_data_to_return;
+
+ return vtable_to_return;
+}
+
+const GDBusSubtreeVTable subtree_vtable =
+{
+ subtree_enumerate,
+ subtree_introspect,
+ subtree_dispatch
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ guint registration_id;
+
+ registration_id = g_dbus_connection_register_subtree (connection,
+ "/org/gtk/GDBus/TestSubtree/Devices",
+ &subtree_vtable,
+ G_DBUS_SUBTREE_FLAGS_NONE,
+ NULL, /* user_data */
+ NULL, /* user_data_free_func */
+ NULL); /* GError** */
+ g_assert (registration_id > 0);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint owner_id;
+ GMainLoop *loop;
+
+ g_type_init ();
+
+ /* We are lazy here - we don't want to manually provide
+ * the introspection data structures - so we just build
+ * them from XML.
+ */
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ manager_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Manager");
+ block_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Block");
+ partition_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Partition");
+ g_assert (manager_interface_info != NULL);
+ g_assert (block_interface_info != NULL);
+ g_assert (partition_interface_info != NULL);
+
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.TestSubtree",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unown_name (owner_id);
+
+ g_dbus_node_info_unref (introspection_data);
+
+ return 0;
+}
diff --git a/gio/tests/gdbus-example-unix-fd-client.c b/gio/tests/gdbus-example-unix-fd-client.c
new file mode 100644
index 000000000..6a66431c3
--- /dev/null
+++ b/gio/tests/gdbus-example-unix-fd-client.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <time.h>
+
+#include <gio/gio.h>
+
+/* see gdbus-example-server.c for the server implementation */
+static gint
+get_server_stdout (GDBusConnection *connection,
+ const gchar *name_owner,
+ GError **error)
+{
+ GDBusMessage *method_call_message;
+ GDBusMessage *method_reply_message;
+ GUnixFDList *fd_list;
+ gint fd;
+
+ fd = -1;
+ method_call_message = NULL;
+ method_reply_message = NULL;
+
+ method_call_message = g_dbus_message_new_method_call (name_owner,
+ "/org/gtk/GDBus/TestObject",
+ "org.gtk.GDBus.TestInterface",
+ "GimmeStdout");
+ method_reply_message = g_dbus_connection_send_message_with_reply_sync (connection,
+ method_call_message,
+ -1,
+ NULL, /* out_serial */
+ NULL, /* cancellable */
+ error);
+ if (method_reply_message == NULL)
+ goto out;
+
+ if (g_dbus_message_get_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ g_dbus_message_to_gerror (method_reply_message, error);
+ goto out;
+ }
+
+ fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
+ fd = g_unix_fd_list_get (fd_list, 0, error);
+
+ out:
+ g_object_unref (method_call_message);
+ g_object_unref (method_reply_message);
+
+ return fd;
+}
+
+static void
+on_name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ gint fd;
+ GError *error;
+
+ error = NULL;
+ fd = get_server_stdout (connection, name_owner, &error);
+ if (fd == -1)
+ {
+ g_printerr ("Error invoking GimmeStdout(): %s\n",
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ else
+ {
+ gchar now_buf[256];
+ time_t now;
+ gssize len;
+ gchar *str;
+
+ now = time (NULL);
+ strftime (now_buf,
+ sizeof now_buf,
+ "%c",
+ localtime (&now));
+
+ str = g_strdup_printf ("On %s, gdbus-example-unix-fd-client with pid %d was here!\n",
+ now_buf,
+ (gint) getpid ());
+ len = strlen (str);
+ g_warn_if_fail (write (fd, str, len) == len);
+ close (fd);
+
+ g_print ("Wrote the following on server's stdout:\n%s", str);
+
+ g_free (str);
+ exit (0);
+ }
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_printerr ("Failed to get name owner for %s\n"
+ "Is ./gdbus-example-server running?\n",
+ name);
+ exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint watcher_id;
+ GMainLoop *loop;
+
+ g_type_init ();
+
+ watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.TestServer",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_name_appeared,
+ on_name_vanished,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_name (watcher_id);
+ return 0;
+}
diff --git a/gio/tests/gdbus-example-watch-name.c b/gio/tests/gdbus-example-watch-name.c
new file mode 100644
index 000000000..39d0aed8d
--- /dev/null
+++ b/gio/tests/gdbus-example-watch-name.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+
+static gchar *opt_name = NULL;
+static gboolean opt_system_bus = FALSE;
+static gboolean opt_auto_start = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+ { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to watch", NULL },
+ { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+ { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL},
+ { NULL}
+};
+
+static void
+on_name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ g_print ("Name %s on %s is owned by %s\n",
+ name,
+ opt_system_bus ? "the system bus" : "the session bus",
+ name_owner);
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("Name %s does not exist on %s\n",
+ name,
+ opt_system_bus ? "the system bus" : "the session bus");
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint watcher_id;
+ GMainLoop *loop;
+ GOptionContext *opt_context;
+ GError *error;
+ GBusNameWatcherFlags flags;
+
+ g_type_init ();
+
+ error = NULL;
+ opt_context = g_option_context_new ("g_bus_watch_name() example");
+ g_option_context_set_summary (opt_context,
+ "Example: to watch the power manager on the session bus, use:\n"
+ "\n"
+ " ./example-watch-name -n org.gnome.PowerManager");
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s", error->message);
+ goto out;
+ }
+ if (opt_name == NULL)
+ {
+ g_printerr ("Incorrect usage, try --help.\n");
+ goto out;
+ }
+
+ flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
+ if (opt_auto_start)
+ flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START;
+
+ watcher_id = g_bus_watch_name (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+ opt_name,
+ flags,
+ on_name_appeared,
+ on_name_vanished,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_name (watcher_id);
+
+ out:
+ g_option_context_free (opt_context);
+ g_free (opt_name);
+
+ return 0;
+}
diff --git a/gio/tests/gdbus-example-watch-proxy.c b/gio/tests/gdbus-example-watch-proxy.c
new file mode 100644
index 000000000..9a6176ea5
--- /dev/null
+++ b/gio/tests/gdbus-example-watch-proxy.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+
+static gchar *opt_name = NULL;
+static gchar *opt_object_path = NULL;
+static gchar *opt_interface = NULL;
+static gboolean opt_system_bus = FALSE;
+static gboolean opt_auto_start = FALSE;
+static gboolean opt_no_properties = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+ { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name of the remote object to watch", NULL },
+ { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "Object path of the remote object", NULL },
+ { "interface", 'i', 0, G_OPTION_ARG_STRING, &opt_interface, "D-Bus interface of remote object", NULL },
+ { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+ { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL},
+ { "no-properties", 'p', 0, G_OPTION_ARG_NONE, &opt_no_properties, "Do not load properties", NULL},
+ { NULL}
+};
+
+static void
+print_properties (GDBusProxy *proxy)
+{
+ gchar **property_names;
+ guint n;
+
+ g_print (" properties:\n");
+
+ property_names = g_dbus_proxy_get_cached_property_names (proxy, NULL);
+ for (n = 0; property_names != NULL && property_names[n] != NULL; n++)
+ {
+ const gchar *key = property_names[n];
+ GVariant *value;
+ gchar *value_str;
+ value = g_dbus_proxy_get_cached_property (proxy, key, NULL);
+ value_str = g_variant_print (value, TRUE);
+ g_print (" %s -> %s\n", key, value_str);
+ g_variant_unref (value);
+ g_free (value_str);
+ }
+ g_strfreev (property_names);
+}
+
+static void
+on_properties_changed (GDBusProxy *proxy,
+ GHashTable *changed_properties,
+ gpointer user_data)
+{
+ GHashTableIter iter;
+ const gchar *key;
+ GVariant *value;
+
+ g_print (" *** Properties Changed:\n");
+
+ g_hash_table_iter_init (&iter, changed_properties);
+ while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value))
+ {
+ gchar *value_str;
+ value_str = g_variant_print (value, TRUE);
+ g_print (" %s -> %s\n", key, value_str);
+ g_free (value_str);
+ }
+}
+
+static void
+on_signal (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ gchar *parameters_str;
+
+ parameters_str = g_variant_print (parameters, TRUE);
+ g_print (" *** Received Signal: %s: %s\n",
+ signal_name,
+ parameters_str);
+ g_free (parameters_str);
+}
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy,
+ gpointer user_data)
+{
+ g_print ("+++ Acquired proxy object for remote object owned by %s\n"
+ " bus: %s\n"
+ " name: %s\n"
+ " object path: %s\n"
+ " interface: %s\n",
+ name_owner,
+ opt_system_bus ? "System Bus" : "Session Bus",
+ opt_name,
+ opt_object_path,
+ opt_interface);
+
+ print_properties (proxy);
+
+ g_signal_connect (proxy,
+ "g-properties-changed",
+ G_CALLBACK (on_properties_changed),
+ NULL);
+
+ g_signal_connect (proxy,
+ "g-signal",
+ G_CALLBACK (on_signal),
+ NULL);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("--- Cannot create proxy object for\n"
+ " bus: %s\n"
+ " name: %s\n"
+ " object path: %s\n"
+ " interface: %s\n",
+ opt_system_bus ? "System Bus" : "Session Bus",
+ opt_name,
+ opt_object_path,
+ opt_interface);
+}
+
+int
+main (int argc, char *argv[])
+{
+ guint watcher_id;
+ GMainLoop *loop;
+ GOptionContext *opt_context;
+ GError *error;
+ GBusNameWatcherFlags flags;
+ GDBusProxyFlags proxy_flags;
+
+ g_type_init ();
+
+ opt_context = g_option_context_new ("g_bus_watch_proxy() example");
+ g_option_context_set_summary (opt_context,
+ "Example: to watch the object of gdbus-example-server, use:\n"
+ "\n"
+ " ./gdbus-example-watch-proxy -n org.gtk.GDBus.TestServer \\\n"
+ " -o /org/gtk/GDBus/TestObject \\\n"
+ " -i org.gtk.GDBus.TestInterface");
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ error = NULL;
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s", error->message);
+ goto out;
+ }
+ if (opt_name == NULL || opt_object_path == NULL || opt_interface == NULL)
+ {
+ g_printerr ("Incorrect usage, try --help.\n");
+ goto out;
+ }
+
+ flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
+ if (opt_auto_start)
+ flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START;
+
+ proxy_flags = G_DBUS_PROXY_FLAGS_NONE;
+ if (opt_no_properties)
+ proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
+
+ watcher_id = g_bus_watch_proxy (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+ opt_name,
+ flags,
+ opt_object_path,
+ opt_interface,
+ G_TYPE_DBUS_PROXY,
+ proxy_flags,
+ on_proxy_appeared,
+ on_proxy_vanished,
+ NULL,
+ NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_proxy (watcher_id);
+
+ out:
+ g_option_context_free (opt_context);
+ g_free (opt_name);
+ g_free (opt_object_path);
+ g_free (opt_interface);
+
+ return 0;
+}
diff --git a/gio/tests/gdbus-exit-on-close.c b/gio/tests/gdbus-exit-on-close.c
new file mode 100644
index 000000000..7f75519ce
--- /dev/null
+++ b/gio/tests/gdbus-exit-on-close.c
@@ -0,0 +1,82 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+nuke_session_bus_cb (gpointer data)
+{
+ g_main_loop_quit (loop);
+ return FALSE;
+}
+
+static void
+test_exit_on_close (void)
+{
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR))
+ {
+ GDBusConnection *c;
+ session_bus_up ();
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c != NULL);
+ g_assert (!g_dbus_connection_is_closed (c));
+ g_timeout_add (50,
+ nuke_session_bus_cb,
+ NULL);
+ g_main_loop_run (loop);
+ session_bus_down ();
+ g_main_loop_run (loop);
+ }
+ g_test_trap_assert_stdout ("*Remote peer vanished. Exiting.*");
+ g_test_trap_assert_failed();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ g_test_add_func ("/gdbus/exit-on-close", test_exit_on_close);
+ return g_test_run();
+}
diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c
new file mode 100644
index 000000000..6e33d2f4f
--- /dev/null
+++ b/gio/tests/gdbus-export.c
@@ -0,0 +1,1410 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+static GDBusConnection *c = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that we can export objects, the hierarchy is correct and the right handlers are invoked */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const GDBusArgInfo foo_method1_in_args =
+{
+ -1,
+ "an_input_string",
+ "s",
+ NULL
+};
+static const GDBusArgInfo * const foo_method1_in_arg_pointers[] = {&foo_method1_in_args, NULL};
+
+static const GDBusArgInfo foo_method1_out_args =
+{
+ -1,
+ "an_output_string",
+ "s",
+ NULL
+};
+static const GDBusArgInfo * const foo_method1_out_arg_pointers[] = {&foo_method1_out_args, NULL};
+
+static const GDBusMethodInfo foo_method_info_method1 =
+{
+ -1,
+ "Method1",
+ (GDBusArgInfo **) &foo_method1_in_arg_pointers,
+ (GDBusArgInfo **) &foo_method1_out_arg_pointers,
+ NULL
+};
+static const GDBusMethodInfo foo_method_info_method2 =
+{
+ -1,
+ "Method2",
+ NULL,
+ NULL,
+ NULL
+};
+static const GDBusMethodInfo * const foo_method_info_pointers[] = {&foo_method_info_method1, &foo_method_info_method2, NULL};
+
+static const GDBusSignalInfo foo_signal_info =
+{
+ -1,
+ "SignalAlpha",
+ NULL,
+ NULL
+};
+static const GDBusSignalInfo * const foo_signal_info_pointers[] = {&foo_signal_info, NULL};
+
+static const GDBusPropertyInfo foo_property_info[] =
+{
+ {
+ -1,
+ "PropertyUno",
+ "s",
+ G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
+ NULL
+ },
+ {
+ -1,
+ "NotWritable",
+ "s",
+ G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+ NULL
+ },
+ {
+ -1,
+ "NotReadable",
+ "s",
+ G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
+ NULL
+ }
+};
+static const GDBusPropertyInfo * const foo_property_info_pointers[] =
+{
+ &foo_property_info[0],
+ &foo_property_info[1],
+ &foo_property_info[2],
+ NULL
+};
+
+static const GDBusInterfaceInfo foo_interface_info =
+{
+ -1,
+ "org.example.Foo",
+ (GDBusMethodInfo **) &foo_method_info_pointers,
+ (GDBusSignalInfo **) &foo_signal_info_pointers,
+ (GDBusPropertyInfo **)&foo_property_info_pointers,
+ NULL,
+};
+
+static void
+foo_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ if (g_strcmp0 (method_name, "Method1") == 0)
+ {
+ const gchar *input;
+ gchar *output;
+ g_variant_get (parameters, "(s)", &input);
+ output = g_strdup_printf ("You passed the string `%s'. Jolly good!", input);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", output));
+ g_free (output);
+ }
+ else
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.example.SomeError",
+ "How do you like them apples, buddy!");
+ }
+}
+
+static GVariant *
+foo_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GVariant *ret;
+ gchar *s;
+ s = g_strdup_printf ("Property `%s' Is What It Is!", property_name);
+ ret = g_variant_new_string (s);
+ g_free (s);
+ return ret;
+}
+
+static gboolean
+foo_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ gchar *s;
+ s = g_variant_print (value, TRUE);
+ g_set_error (error,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_SPAWN_FILE_INVALID,
+ "Returning some error instead of writing the value `%s' to the property `%s'",
+ property_name, s);
+ g_free (s);
+ return FALSE;
+}
+
+static const GDBusInterfaceVTable foo_vtable =
+{
+ foo_method_call,
+ foo_get_property,
+ foo_set_property
+};
+
+/* -------------------- */
+
+static const GDBusMethodInfo bar_method_info[] =
+{
+ {
+ -1,
+ "MethodA",
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ -1,
+ "MethodB",
+ NULL,
+ NULL,
+ NULL
+ }
+};
+static const GDBusMethodInfo * const bar_method_info_pointers[] = {&bar_method_info[0], &bar_method_info[1], NULL};
+
+static const GDBusSignalInfo bar_signal_info[] =
+{
+ {
+ -1,
+ "SignalMars",
+ NULL,
+ NULL
+ }
+};
+static const GDBusSignalInfo * const bar_signal_info_pointers[] = {&bar_signal_info[0], NULL};
+
+static const GDBusPropertyInfo bar_property_info[] =
+{
+ {
+ -1,
+ "PropertyDuo",
+ "s",
+ G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+ NULL
+ }
+};
+static const GDBusPropertyInfo * const bar_property_info_pointers[] = {&bar_property_info[0], NULL};
+
+static const GDBusInterfaceInfo bar_interface_info =
+{
+ -1,
+ "org.example.Bar",
+ (GDBusMethodInfo **) bar_method_info_pointers,
+ (GDBusSignalInfo **) bar_signal_info_pointers,
+ (GDBusPropertyInfo **) bar_property_info_pointers,
+ NULL,
+};
+
+/* -------------------- */
+
+static const GDBusMethodInfo dyna_method_info[] =
+{
+ {
+ -1,
+ "DynaCyber",
+ NULL,
+ NULL,
+ NULL
+ }
+};
+static const GDBusMethodInfo * const dyna_method_info_pointers[] = {&dyna_method_info[0], NULL};
+
+static const GDBusInterfaceInfo dyna_interface_info =
+{
+ -1,
+ "org.example.Dyna",
+ (GDBusMethodInfo **) dyna_method_info_pointers,
+ NULL, /* no signals */
+ NULL, /* no properties */
+ NULL,
+};
+
+static void
+dyna_cyber (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GPtrArray *data = user_data;
+ gchar *node_name;
+ guint n;
+
+ node_name = strrchr (object_path, '/') + 1;
+
+ /* Add new node if it is not already known */
+ for (n = 0; n < data->len ; n++)
+ {
+ if (g_strcmp0 (g_ptr_array_index (data, n), node_name) == 0)
+ goto out;
+ }
+ g_ptr_array_add (data, g_strdup(node_name));
+
+ out:
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static const GDBusInterfaceVTable dyna_interface_vtable =
+{
+ dyna_cyber,
+ NULL,
+ NULL
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_callback (GDBusProxy *proxy,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ const gchar *s;
+ gchar **xml_data = user_data;
+ GVariant *result;
+ GError *error;
+
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_finish (proxy,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_get (result, "(s)", &s);
+ *xml_data = g_strdup (s);
+ g_variant_unref (result);
+
+ g_main_loop_quit (loop);
+}
+
+static gchar **
+get_nodes_at (GDBusConnection *c,
+ const gchar *object_path)
+{
+ GError *error;
+ GDBusProxy *proxy;
+ gchar *xml_data;
+ GPtrArray *p;
+ GDBusNodeInfo *node_info;
+ guint n;
+
+ error = NULL;
+ proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ NULL,
+ g_dbus_connection_get_unique_name (c),
+ object_path,
+ "org.freedesktop.DBus.Introspectable",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (proxy != NULL);
+
+ /* do this async to avoid libdbus-1 deadlocks */
+ xml_data = NULL;
+ g_dbus_proxy_invoke_method (proxy,
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) introspect_callback,
+ &xml_data);
+ g_main_loop_run (loop);
+ g_assert (xml_data != NULL);
+
+ node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_assert_no_error (error);
+ g_assert (node_info != NULL);
+
+ p = g_ptr_array_new ();
+ for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++)
+ {
+ const GDBusNodeInfo *sub_node_info = node_info->nodes[n];
+ g_ptr_array_add (p, g_strdup (sub_node_info->path));
+ }
+ g_ptr_array_add (p, NULL);
+
+ g_object_unref (proxy);
+ g_free (xml_data);
+ g_dbus_node_info_unref (node_info);
+
+ return (gchar **) g_ptr_array_free (p, FALSE);
+}
+
+static gboolean
+has_interface (GDBusConnection *c,
+ const gchar *object_path,
+ const gchar *interface_name)
+{
+ GError *error;
+ GDBusProxy *proxy;
+ gchar *xml_data;
+ GDBusNodeInfo *node_info;
+ gboolean ret;
+
+ error = NULL;
+ proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ NULL,
+ g_dbus_connection_get_unique_name (c),
+ object_path,
+ "org.freedesktop.DBus.Introspectable",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (proxy != NULL);
+
+ /* do this async to avoid libdbus-1 deadlocks */
+ xml_data = NULL;
+ g_dbus_proxy_invoke_method (proxy,
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) introspect_callback,
+ &xml_data);
+ g_main_loop_run (loop);
+ g_assert (xml_data != NULL);
+
+ node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_assert_no_error (error);
+ g_assert (node_info != NULL);
+
+ ret = (g_dbus_node_info_lookup_interface (node_info, interface_name) != NULL);
+
+ g_object_unref (proxy);
+ g_free (xml_data);
+ g_dbus_node_info_unref (node_info);
+
+ return ret;
+}
+
+static guint
+count_interfaces (GDBusConnection *c,
+ const gchar *object_path)
+{
+ GError *error;
+ GDBusProxy *proxy;
+ gchar *xml_data;
+ GDBusNodeInfo *node_info;
+ guint ret;
+
+ error = NULL;
+ proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ NULL,
+ g_dbus_connection_get_unique_name (c),
+ object_path,
+ "org.freedesktop.DBus.Introspectable",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (proxy != NULL);
+
+ /* do this async to avoid libdbus-1 deadlocks */
+ xml_data = NULL;
+ g_dbus_proxy_invoke_method (proxy,
+ "Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) introspect_callback,
+ &xml_data);
+ g_main_loop_run (loop);
+ g_assert (xml_data != NULL);
+
+ node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_assert_no_error (error);
+ g_assert (node_info != NULL);
+
+ ret = 0;
+ while (node_info->interfaces != NULL && node_info->interfaces[ret] != NULL)
+ ret++;
+
+ g_object_unref (proxy);
+ g_free (xml_data);
+ g_dbus_node_info_unref (node_info);
+
+ return ret;
+}
+
+static void
+dyna_create_callback (GDBusProxy *proxy,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *result;
+ GError *error;
+
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_finish (proxy,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_unref (result);
+
+ g_main_loop_quit (loop);
+}
+
+/* Dynamically create @object_name under /foo/dyna */
+static void
+dyna_create (GDBusConnection *c,
+ const gchar *object_name)
+{
+ GError *error;
+ GDBusProxy *proxy;
+ gchar *object_path;
+
+ object_path = g_strconcat ("/foo/dyna/", object_name, NULL);
+
+ error = NULL;
+ proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ NULL,
+ g_dbus_connection_get_unique_name (c),
+ object_path,
+ "org.example.Dyna",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (proxy != NULL);
+
+ /* do this async to avoid libdbus-1 deadlocks */
+ g_dbus_proxy_invoke_method (proxy,
+ "DynaCyber",
+ g_variant_new ("()"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) dyna_create_callback,
+ NULL);
+ g_main_loop_run (loop);
+
+ g_assert_no_error (error);
+
+ g_object_unref (proxy);
+ g_free (object_path);
+
+ return;
+}
+
+typedef struct
+{
+ guint num_unregistered_calls;
+ guint num_unregistered_subtree_calls;
+ guint num_subtree_nodes;
+} ObjectRegistrationData;
+
+static void
+on_object_unregistered (gpointer user_data)
+{
+ ObjectRegistrationData *data = user_data;
+
+ data->num_unregistered_calls++;
+}
+
+static void
+on_subtree_unregistered (gpointer user_data)
+{
+ ObjectRegistrationData *data = user_data;
+
+ data->num_unregistered_subtree_calls++;
+}
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+ const gchar *needle)
+{
+ guint n;
+
+ for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+ {
+ if (g_strcmp0 (haystack[n], needle) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* -------------------- */
+
+static gchar **
+subtree_enumerate (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ gpointer user_data)
+{
+ ObjectRegistrationData *data = user_data;
+ GPtrArray *p;
+ gchar **nodes;
+ guint n;
+
+ p = g_ptr_array_new ();
+
+ for (n = 0; n < data->num_subtree_nodes; n++)
+ {
+ g_ptr_array_add (p, g_strdup_printf ("vp%d", n));
+ g_ptr_array_add (p, g_strdup_printf ("evp%d", n));
+ }
+ g_ptr_array_add (p, NULL);
+
+ nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+ return nodes;
+}
+
+/* Only allows certain objects, and aborts on unknowns */
+static GPtrArray *
+subtree_introspect (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *node,
+ gpointer user_data)
+{
+ GPtrArray *interfaces;
+
+ /* VPs implement the Foo interface, EVPs implement the Bar interface. The root
+ * does not implement any interfaces
+ */
+ interfaces = g_ptr_array_new ();
+ if (g_str_has_prefix (node, "vp"))
+ {
+ g_ptr_array_add (interfaces, (gpointer) &foo_interface_info);
+ }
+ else if (g_str_has_prefix (node, "evp"))
+ {
+ g_ptr_array_add (interfaces, (gpointer) &bar_interface_info);
+ }
+ else if (g_strcmp0 (node, "/") == 0)
+ {
+ /* do nothing */
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return interfaces;
+}
+
+static const GDBusInterfaceVTable *
+subtree_dispatch (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *node,
+ gpointer *out_user_data,
+ gpointer user_data)
+{
+ if (g_strcmp0 (interface_name, "org.example.Foo") == 0)
+ return &foo_vtable;
+ else
+ return NULL;
+}
+
+static const GDBusSubtreeVTable subtree_vtable =
+{
+ subtree_enumerate,
+ subtree_introspect,
+ subtree_dispatch
+};
+
+/* -------------------- */
+
+static gchar **
+dynamic_subtree_enumerate (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ gpointer user_data)
+{
+ GPtrArray *data = user_data;
+ gchar **nodes = g_new (gchar*, data->len + 1);
+ guint n;
+
+ for (n = 0; n < data->len; n++)
+ {
+ nodes[n] = g_strdup (g_ptr_array_index (data, n));
+ }
+ nodes[data->len] = NULL;
+
+ return nodes;
+}
+
+/* Allow all objects to be introspected */
+static GPtrArray *
+dynamic_subtree_introspect (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *node,
+ gpointer user_data)
+{
+ GPtrArray *interfaces;
+
+ /* All nodes (including the root node) implements the Dyna interface */
+ interfaces = g_ptr_array_new ();
+ g_ptr_array_add (interfaces, (gpointer) &dyna_interface_info);
+
+ return interfaces;
+}
+
+static const GDBusInterfaceVTable *
+dynamic_subtree_dispatch (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *node,
+ gpointer *out_user_data,
+ gpointer user_data)
+{
+ *out_user_data = user_data;
+ return &dyna_interface_vtable;
+}
+
+static const GDBusSubtreeVTable dynamic_subtree_vtable =
+{
+ dynamic_subtree_enumerate,
+ dynamic_subtree_introspect,
+ dynamic_subtree_dispatch
+};
+
+/* -------------------- */
+
+static gpointer
+test_dispatch_thread_func (gpointer user_data)
+{
+ const gchar *object_path = user_data;
+ GDBusProxy *foo_proxy;
+ GVariant *value;
+ GVariant *inner;
+ GError *error;
+ gchar *s;
+ const gchar *value_str;
+
+ foo_proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ g_dbus_connection_get_unique_name (c),
+ object_path,
+ "org.example.Foo",
+ NULL,
+ &error);
+ g_assert (foo_proxy != NULL);
+
+ /* generic interfaces */
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Peer.Ping",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (value != NULL);
+ g_variant_unref (value);
+
+ /* user methods */
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "Method1",
+ g_variant_new ("(s)", "winwinwin"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (value != NULL);
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)")));
+ g_variant_get (value, "(s)", &value_str);
+ g_assert_cmpstr (value_str, ==, "You passed the string `winwinwin'. Jolly good!");
+ g_variant_unref (value);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "Method2",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.example.SomeError: How do you like them apples, buddy!");
+ g_error_free (error);
+ g_assert (value == NULL);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "Method2",
+ g_variant_new ("(s)", "failfailfail"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Signature of message, `s', does not match expected signature `'");
+ g_error_free (error);
+ g_assert (value == NULL);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "NonExistantMethod",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method `NonExistantMethod'");
+ g_error_free (error);
+ g_assert (value == NULL);
+
+ /* user properties */
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.Get",
+ g_variant_new ("(ss)",
+ "org.example.Foo",
+ "PropertyUno"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (value != NULL);
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(v)")));
+ g_variant_get (value, "(v)", &inner);
+ g_assert (g_variant_is_of_type (inner, G_VARIANT_TYPE_STRING));
+ g_assert_cmpstr (g_variant_get_string (inner, NULL), ==, "Property `PropertyUno' Is What It Is!");
+ g_variant_unref (value);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.Get",
+ g_variant_new ("(ss)",
+ "org.example.Foo",
+ "ThisDoesntExist"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert (value == NULL);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: No such property `ThisDoesntExist'");
+ g_error_free (error);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.Get",
+ g_variant_new ("(ss)",
+ "org.example.Foo",
+ "NotReadable"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert (value == NULL);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotReadable' is not readable");
+ g_error_free (error);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new ("(ssv)",
+ "org.example.Foo",
+ "NotReadable",
+ g_variant_new_string ("But Writable you are!")),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert (value == NULL);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value `NotReadable' to the property `'But Writable you are!''");
+ g_error_free (error);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new ("(ssv)",
+ "org.example.Foo",
+ "NotWritable",
+ g_variant_new_uint32 (42)),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert (value == NULL);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotWritable' is not writable");
+ g_error_free (error);
+
+ error = NULL;
+ value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+ "org.freedesktop.DBus.Properties.GetAll",
+ g_variant_new ("(s)",
+ "org.example.Foo"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (value != NULL);
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(a{sv})")));
+ s = g_variant_print (value, TRUE);
+ g_assert_cmpstr (s, ==, "({'PropertyUno': <\"Property `PropertyUno' Is What It Is!\">, 'NotWritable': <\"Property `NotWritable' Is What It Is!\">},)");
+ g_free (s);
+ g_variant_unref (value);
+
+ g_object_unref (foo_proxy);
+
+ g_main_loop_quit (loop);
+ return NULL;
+}
+
+static void
+test_dispatch (const gchar *object_path)
+{
+ GThread *thread;
+ GError *error;
+
+ /* run this in a thread to avoid deadlocks */
+ error = NULL;
+ thread = g_thread_create (test_dispatch_thread_func,
+ (gpointer) object_path,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+ g_assert (thread != NULL);
+ g_main_loop_run (loop);
+ g_thread_join (thread);
+}
+
+static void
+test_object_registration (void)
+{
+ GError *error;
+ ObjectRegistrationData data;
+ GPtrArray *dyna_data;
+ gchar **nodes;
+ guint boss_foo_reg_id;
+ guint boss_bar_reg_id;
+ guint worker1_foo_reg_id;
+ guint worker2_bar_reg_id;
+ guint intern1_foo_reg_id;
+ guint intern2_bar_reg_id;
+ guint intern2_foo_reg_id;
+ guint intern3_bar_reg_id;
+ guint registration_id;
+ guint subtree_registration_id;
+ guint non_subtree_object_path_foo_reg_id;
+ guint non_subtree_object_path_bar_reg_id;
+ guint dyna_subtree_registration_id;
+ guint num_successful_registrations;
+
+ data.num_unregistered_calls = 0;
+ data.num_unregistered_subtree_calls = 0;
+ data.num_subtree_nodes = 0;
+
+ num_successful_registrations = 0;
+
+ error = NULL;
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c != NULL);
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss",
+ foo_interface_info.name,
+ &foo_interface_info,
+ &foo_vtable,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ boss_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ boss_bar_reg_id = registration_id;
+ num_successful_registrations++;
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/worker1",
+ foo_interface_info.name,
+ &foo_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ worker1_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/worker2",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ worker2_bar_reg_id = registration_id;
+ num_successful_registrations++;
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern1",
+ foo_interface_info.name,
+ &foo_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ intern1_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ /* ... and try again at another path */
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern2",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ intern2_bar_reg_id = registration_id;
+ num_successful_registrations++;
+
+ /* register at the same path/interface - this should fail */
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern2",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ error = NULL;
+ g_assert (registration_id == 0);
+
+ /* register at different interface - shouldn't fail */
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern2",
+ foo_interface_info.name,
+ &foo_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ intern2_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ /* unregister it via the id */
+ g_assert (g_dbus_connection_unregister_object (c, registration_id));
+ g_assert_cmpint (data.num_unregistered_calls, ==, 1);
+ intern2_foo_reg_id = 0;
+
+ /* register it back */
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern2",
+ foo_interface_info.name,
+ &foo_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ intern2_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/interns/intern3",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ intern3_bar_reg_id = registration_id;
+ num_successful_registrations++;
+
+ /* now register a whole subtree at /foo/boss/executives */
+ subtree_registration_id = g_dbus_connection_register_subtree (c,
+ "/foo/boss/executives",
+ &subtree_vtable,
+ G_DBUS_SUBTREE_FLAGS_NONE,
+ &data,
+ on_subtree_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (subtree_registration_id > 0);
+ /* try registering it again.. this should fail */
+ registration_id = g_dbus_connection_register_subtree (c,
+ "/foo/boss/executives",
+ &subtree_vtable,
+ G_DBUS_SUBTREE_FLAGS_NONE,
+ &data,
+ on_subtree_unregistered,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ error = NULL;
+ g_assert (registration_id == 0);
+
+ /* unregister it, then register it again */
+ g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 0);
+ g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
+ g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+ subtree_registration_id = g_dbus_connection_register_subtree (c,
+ "/foo/boss/executives",
+ &subtree_vtable,
+ G_DBUS_SUBTREE_FLAGS_NONE,
+ &data,
+ on_subtree_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (subtree_registration_id > 0);
+
+ /* try to register something under /foo/boss/executives - this should work
+ * because registered subtrees and registered objects can coexist.
+ *
+ * Make the exported object implement *two* interfaces so we can check
+ * that the right introspection handler is invoked.
+ */
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/executives/non_subtree_object",
+ bar_interface_info.name,
+ &bar_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ non_subtree_object_path_bar_reg_id = registration_id;
+ num_successful_registrations++;
+ registration_id = g_dbus_connection_register_object (c,
+ "/foo/boss/executives/non_subtree_object",
+ foo_interface_info.name,
+ &foo_interface_info,
+ NULL,
+ &data,
+ on_object_unregistered,
+ &error);
+ g_assert_no_error (error);
+ g_assert (registration_id > 0);
+ non_subtree_object_path_foo_reg_id = registration_id;
+ num_successful_registrations++;
+
+ /* now register a dynamic subtree, spawning objects as they are called */
+ dyna_data = g_ptr_array_new ();
+ dyna_subtree_registration_id = g_dbus_connection_register_subtree (c,
+ "/foo/dyna",
+ &dynamic_subtree_vtable,
+ G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
+ dyna_data,
+ (GDestroyNotify)g_ptr_array_unref,
+ &error);
+ g_assert_no_error (error);
+ g_assert (dyna_subtree_registration_id > 0);
+
+ /* First assert that we have no nodes in the dynamic subtree */
+ nodes = get_nodes_at (c, "/foo/dyna");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 0);
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/foo/dyna"), ==, 4);
+
+ /* Install three nodes in the dynamic subtree via the dyna_data backdoor and
+ * assert that they show up correctly in the introspection data */
+ g_ptr_array_add (dyna_data, "lol");
+ g_ptr_array_add (dyna_data, "cat");
+ g_ptr_array_add (dyna_data, "cheezburger");
+ nodes = get_nodes_at (c, "/foo/dyna");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 3);
+ g_assert_cmpstr (nodes[0], ==, "lol");
+ g_assert_cmpstr (nodes[1], ==, "cat");
+ g_assert_cmpstr (nodes[2], ==, "cheezburger");
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4);
+ g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4);
+ g_assert_cmpint (count_interfaces (c, "/foo/dyna/cheezburger"), ==, 4);
+
+ /* Call a non-existing object path and assert that it has been created */
+ dyna_create (c, "dynamicallycreated");
+ nodes = get_nodes_at (c, "/foo/dyna");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 4);
+ g_assert_cmpstr (nodes[0], ==, "lol");
+ g_assert_cmpstr (nodes[1], ==, "cat");
+ g_assert_cmpstr (nodes[2], ==, "cheezburger");
+ g_assert_cmpstr (nodes[3], ==, "dynamicallycreated");
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4);
+
+ /* now check that the object hierarachy is properly generated... yes, it's a bit
+ * perverse that we round-trip to the bus to introspect ourselves ;-)
+ */
+ nodes = get_nodes_at (c, "/");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 1);
+ g_assert_cmpstr (nodes[0], ==, "foo");
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/"), ==, 0);
+
+ nodes = get_nodes_at (c, "/foo");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 2);
+ g_assert_cmpstr (nodes[0], ==, "boss");
+ g_assert_cmpstr (nodes[1], ==, "dyna");
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/foo"), ==, 0);
+
+ nodes = get_nodes_at (c, "/foo/boss");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 4);
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives"));
+ g_strfreev (nodes);
+ /* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */
+ g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5);
+ g_assert (has_interface (c, "/foo/boss", foo_interface_info.name));
+ g_assert (has_interface (c, "/foo/boss", bar_interface_info.name));
+
+ /* check subtree nodes - we should have only non_subtree_object in /foo/boss/executives
+ * because data.num_subtree_nodes is 0
+ */
+ nodes = get_nodes_at (c, "/foo/boss/executives");
+ g_assert (nodes != NULL);
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+ g_assert_cmpint (g_strv_length (nodes), ==, 1);
+ g_strfreev (nodes);
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0);
+
+ /* now change data.num_subtree_nodes and check */
+ data.num_subtree_nodes = 2;
+ nodes = get_nodes_at (c, "/foo/boss/executives");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 5);
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+ /* check that /foo/boss/executives/non_subtree_object is not handled by the
+ * subtree handlers - we can do this because objects from subtree handlers
+ * has exactly one interface and non_subtree_object has two
+ */
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/non_subtree_object"), ==, 5);
+ g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", foo_interface_info.name));
+ g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", bar_interface_info.name));
+ /* check that the vp and evp objects are handled by the subtree handlers */
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp0"), ==, 4);
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp1"), ==, 4);
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp0"), ==, 4);
+ g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp1"), ==, 4);
+ g_assert (has_interface (c, "/foo/boss/executives/vp0", foo_interface_info.name));
+ g_assert (has_interface (c, "/foo/boss/executives/vp1", foo_interface_info.name));
+ g_assert (has_interface (c, "/foo/boss/executives/evp0", bar_interface_info.name));
+ g_assert (has_interface (c, "/foo/boss/executives/evp1", bar_interface_info.name));
+ g_strfreev (nodes);
+ data.num_subtree_nodes = 3;
+ nodes = get_nodes_at (c, "/foo/boss/executives");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 7);
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2"));
+ g_strfreev (nodes);
+
+ /* check that calls are properly dispatched to the functions in foo_vtable for objects
+ * implementing the org.example.Foo interface
+ *
+ * We do this for both a regular registered object (/foo/boss) and also for an object
+ * registered through the subtree mechanism.
+ */
+ test_dispatch ("/foo/boss");
+ test_dispatch ("/foo/boss/executives/vp0");
+
+ /* To prevent from exiting and attaching a D-Bus tool like D-Feet; uncomment: */
+#if 0
+ g_debug ("Point D-feet or other tool at: %s", session_bus_get_temporary_address());
+ g_main_loop_run (loop);
+#endif
+
+ /* check that unregistering the subtree handler works */
+ g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+ g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
+ g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 2);
+ nodes = get_nodes_at (c, "/foo/boss/executives");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 1);
+ g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+ g_strfreev (nodes);
+
+ g_assert (g_dbus_connection_unregister_object (c, boss_foo_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, boss_bar_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, worker1_foo_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, worker2_bar_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, intern1_foo_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, intern2_bar_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, intern2_foo_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, intern3_bar_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_bar_reg_id));
+ g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_foo_reg_id));
+
+ g_assert_cmpint (data.num_unregistered_calls, ==, num_successful_registrations);
+
+ /* check that we no longer export any objects - TODO: it looks like there's a bug in
+ * libdbus-1 here: libdbus still reports the '/foo' object; so disable the test for now
+ */
+#if 0
+ nodes = get_nodes_at (c, "/");
+ g_assert (nodes != NULL);
+ g_assert_cmpint (g_strv_length (nodes), ==, 0);
+ g_strfreev (nodes);
+#endif
+
+ g_object_unref (c);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint ret;
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ session_bus_up ();
+
+ /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+ * until one can connect to the bus but that's not how things work right now
+ */
+ usleep (500 * 1000);
+
+ g_test_add_func ("/gdbus/object-registration", test_object_registration);
+ /* TODO: check that we spit out correct introspection data */
+ /* TODO: check that registering a whole subtree works */
+
+ ret = g_test_run();
+
+ /* tear down bus */
+ session_bus_down ();
+
+ return ret;
+}
diff --git a/gio/tests/gdbus-introspection.c b/gio/tests/gdbus-introspection.c
new file mode 100644
index 000000000..1b3d7a51f
--- /dev/null
+++ b/gio/tests/gdbus-introspection.c
@@ -0,0 +1,169 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test introspection parser */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspection_on_proxy_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy,
+ gpointer user_data)
+{
+ GError *error;
+ const gchar *xml_data;
+ GDBusNodeInfo *node_info;
+ const GDBusInterfaceInfo *interface_info;
+ const GDBusMethodInfo *method_info;
+ const GDBusSignalInfo *signal_info;
+ GVariant *result;
+
+ error = NULL;
+
+ /*
+ * Invoke Introspect(), then parse the output.
+ */
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "org.freedesktop.DBus.Introspectable.Introspect",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_get (result, "(s)", &xml_data);
+
+ node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+ g_assert_no_error (error);
+ g_assert (node_info != NULL);
+
+ /* for now we only check a couple of things. TODO: check more things */
+
+ interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.NonExistantInterface");
+ g_assert (interface_info == NULL);
+
+ interface_info = g_dbus_node_info_lookup_interface (node_info, "org.freedesktop.DBus.Introspectable");
+ g_assert (interface_info != NULL);
+ method_info = g_dbus_interface_info_lookup_method (interface_info, "NonExistantMethod");
+ g_assert (method_info == NULL);
+ method_info = g_dbus_interface_info_lookup_method (interface_info, "Introspect");
+ g_assert (method_info != NULL);
+ g_assert (method_info->in_args == NULL);
+ g_assert (method_info->out_args != NULL);
+ g_assert (method_info->out_args[0] != NULL);
+ g_assert (method_info->out_args[1] == NULL);
+ g_assert_cmpstr (method_info->out_args[0]->signature, ==, "s");
+
+ interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.Frob");
+ g_assert (interface_info != NULL);
+ signal_info = g_dbus_interface_info_lookup_signal (interface_info, "TestSignal");
+ g_assert (signal_info != NULL);
+ g_assert (signal_info->args != NULL);
+ g_assert (signal_info->args[0] != NULL);
+ g_assert_cmpstr (signal_info->args[0]->signature, ==, "s");
+ g_assert (signal_info->args[1] != NULL);
+ g_assert_cmpstr (signal_info->args[1]->signature, ==, "o");
+ g_assert (signal_info->args[2] != NULL);
+ g_assert_cmpstr (signal_info->args[2]->signature, ==, "v");
+ g_assert (signal_info->args[3] == NULL);
+
+ g_dbus_node_info_unref (node_info);
+ g_variant_unref (result);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+introspection_on_proxy_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+test_introspection_parser (void)
+{
+ guint watcher_id;
+
+ session_bus_up ();
+
+ watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+ "com.example.TestService",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ "/com/example/TestObject",
+ "com.example.Frob",
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_NONE,
+ introspection_on_proxy_appeared,
+ introspection_on_proxy_vanished,
+ NULL,
+ NULL);
+
+ /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+ * until one can connect to the bus but that's not how things work right now
+ */
+ usleep (500 * 1000);
+ /* this is safe; testserver will exit once the bus goes away */
+ g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_proxy (watcher_id);
+
+ /* tear down bus */
+ session_bus_down ();
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ g_test_add_func ("/gdbus/introspection-parser", test_introspection_parser);
+ return g_test_run();
+}
diff --git a/gio/tests/gdbus-names.c b/gio/tests/gdbus-names.c
new file mode 100644
index 000000000..9008fa018
--- /dev/null
+++ b/gio/tests/gdbus-names.c
@@ -0,0 +1,749 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that g_bus_own_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GMainLoop *loop;
+ gboolean expect_null_connection;
+ guint num_bus_acquired;
+ guint num_acquired;
+ guint num_lost;
+ guint num_free_func;
+} OwnNameData;
+
+static void
+own_name_data_free_func (OwnNameData *data)
+{
+ data->num_free_func++;
+ g_main_loop_quit (loop);
+}
+
+static void
+bus_acquired_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ OwnNameData *data = user_data;
+ g_dbus_connection_set_exit_on_close (connection, FALSE);
+ data->num_bus_acquired += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+name_acquired_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ OwnNameData *data = user_data;
+ data->num_acquired += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+name_lost_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ OwnNameData *data = user_data;
+ if (data->expect_null_connection)
+ {
+ g_assert (connection == NULL);
+ }
+ else
+ {
+ g_assert (connection != NULL);
+ g_dbus_connection_set_exit_on_close (connection, FALSE);
+ }
+ data->num_lost += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+test_bus_own_name (void)
+{
+ guint id;
+ guint id2;
+ OwnNameData data;
+ OwnNameData data2;
+ const gchar *name;
+ GDBusConnection *c;
+ GError *error;
+ gboolean name_has_owner_reply;
+ GDBusConnection *c2;
+ GVariant *result;
+
+ error = NULL;
+ name = "org.gtk.GDBus.Name1";
+
+ /*
+ * First check that name_lost_handler() is invoked if there is no bus.
+ *
+ * Also make sure name_lost_handler() isn't invoked when unowning the name.
+ */
+ data.num_bus_acquired = 0;
+ data.num_free_func = 0;
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = TRUE;
+ id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired_handler,
+ name_acquired_handler,
+ name_lost_handler,
+ &data,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 1);
+ g_bus_unown_name (id);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 1);
+ g_assert_cmpint (data.num_free_func, ==, 1);
+
+ /*
+ * Bring up a bus, then own a name and check bus_acquired_handler() then name_acquired_handler() is invoked.
+ */
+ session_bus_up ();
+ data.num_bus_acquired = 0;
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = FALSE;
+ id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired_handler,
+ name_acquired_handler,
+ name_lost_handler,
+ &data,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+
+ /*
+ * Check that the name was actually acquired.
+ */
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c != NULL);
+ g_assert (!g_dbus_connection_is_closed (c));
+ result = g_dbus_connection_invoke_method_sync (c,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "NameHasOwner", /* method name */
+ g_variant_new ("(s)", name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_get (result, "(b)", &name_has_owner_reply);
+ g_assert (name_has_owner_reply);
+ g_variant_unref (result);
+
+ /*
+ * Stop owning the name - this should invoke our free func
+ */
+ g_bus_unown_name (id);
+ g_assert_cmpint (data.num_free_func, ==, 2);
+
+ /*
+ * Check that the name was actually released.
+ */
+ result = g_dbus_connection_invoke_method_sync (c,
+ "org.freedesktop.DBus", /* bus name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "NameHasOwner", /* method name */
+ g_variant_new ("(s)", name),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_get (result, "(b)", &name_has_owner_reply);
+ g_assert (!name_has_owner_reply);
+ g_variant_unref (result);
+
+ /*
+ * Own the name again.
+ */
+ data.num_bus_acquired = 0;
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = FALSE;
+ id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired_handler,
+ name_acquired_handler,
+ name_lost_handler,
+ &data,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+
+ /*
+ * Try owning the name with another object on the same connection - this should
+ * fail because we already own the name.
+ */
+ data2.num_free_func = 0;
+ data2.num_bus_acquired = 0;
+ data2.num_acquired = 0;
+ data2.num_lost = 0;
+ data2.expect_null_connection = FALSE;
+ id2 = g_bus_own_name (G_BUS_TYPE_SESSION,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ bus_acquired_handler,
+ name_acquired_handler,
+ name_lost_handler,
+ &data2,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_bus_unown_name (id2);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_assert_cmpint (data2.num_free_func, ==, 1);
+
+ /*
+ * Create a secondary (e.g. private) connection and try owning the name on that
+ * connection. This should fail both with and without _REPLACE because we
+ * didn't specify ALLOW_REPLACEMENT.
+ */
+ c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (c2 != NULL);
+ g_assert (!g_dbus_connection_is_closed (c2));
+ /* first without _REPLACE */
+ data2.num_bus_acquired = 0;
+ data2.num_acquired = 0;
+ data2.num_lost = 0;
+ data2.expect_null_connection = FALSE;
+ data2.num_free_func = 0;
+ id2 = g_bus_own_name_on_connection (c2,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ name_acquired_handler,
+ name_lost_handler,
+ &data2,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_bus_unown_name (id2);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_assert_cmpint (data2.num_free_func, ==, 1);
+ /* then with _REPLACE */
+ data2.num_bus_acquired = 0;
+ data2.num_acquired = 0;
+ data2.num_lost = 0;
+ data2.expect_null_connection = FALSE;
+ data2.num_free_func = 0;
+ id2 = g_bus_own_name_on_connection (c2,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ name_acquired_handler,
+ name_lost_handler,
+ &data2,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_bus_unown_name (id2);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_assert_cmpint (data2.num_free_func, ==, 1);
+
+ /*
+ * Stop owning the name and grab it again with _ALLOW_REPLACEMENT.
+ */
+ data.expect_null_connection = FALSE;
+ g_bus_unown_name (id);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_free_func, ==, 3);
+ /* grab it again */
+ data.num_bus_acquired = 0;
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = FALSE;
+ id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+ bus_acquired_handler,
+ name_acquired_handler,
+ name_lost_handler,
+ &data,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 0);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_bus_acquired, ==, 1);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+
+ /*
+ * Now try to grab the name from the secondary connection.
+ *
+ */
+ /* first without _REPLACE - this won't make us acquire the name */
+ data2.num_bus_acquired = 0;
+ data2.num_acquired = 0;
+ data2.num_lost = 0;
+ data2.expect_null_connection = FALSE;
+ data2.num_free_func = 0;
+ id2 = g_bus_own_name_on_connection (c2,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ name_acquired_handler,
+ name_lost_handler,
+ &data2,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_bus_unown_name (id2);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 1);
+ g_assert_cmpint (data2.num_free_func, ==, 1);
+ /* then with _REPLACE - here we should acquire the name - e.g. owner should lose it
+ * and owner2 should acquire it */
+ data2.num_bus_acquired = 0;
+ data2.num_acquired = 0;
+ data2.num_lost = 0;
+ data2.expect_null_connection = FALSE;
+ data2.num_free_func = 0;
+ id2 = g_bus_own_name_on_connection (c2,
+ name,
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ name_acquired_handler,
+ name_lost_handler,
+ &data2,
+ (GDestroyNotify) own_name_data_free_func);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_assert_cmpint (data2.num_acquired, ==, 0);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ /* wait for handlers for both owner and owner2 to fire */
+ while (data.num_lost == 0 || data2.num_acquired == 0)
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 1);
+ g_assert_cmpint (data2.num_acquired, ==, 1);
+ g_assert_cmpint (data2.num_lost, ==, 0);
+ g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+ /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */
+ g_bus_unown_name (id2);
+ g_assert_cmpint (data2.num_free_func, ==, 1);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_acquired, ==, 2);
+ g_assert_cmpint (data.num_lost, ==, 1);
+
+ /*
+ * Finally, nuke the bus and check name_lost_handler() is invoked.
+ *
+ */
+ data.expect_null_connection = TRUE;
+ session_bus_down ();
+ while (data.num_lost != 2)
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_acquired, ==, 2);
+ g_assert_cmpint (data.num_lost, ==, 2);
+ g_bus_unown_name (id);
+ g_assert_cmpint (data.num_free_func, ==, 4);
+
+ g_object_unref (c);
+ g_object_unref (c2);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that g_bus_watch_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ gboolean expect_null_connection;
+ guint num_acquired;
+ guint num_lost;
+ guint num_appeared;
+ guint num_vanished;
+ guint num_free_func;
+} WatchNameData;
+
+static void
+watch_name_data_free_func (WatchNameData *data)
+{
+ data->num_free_func++;
+ g_main_loop_quit (loop);
+}
+
+static void
+w_bus_acquired_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+w_name_acquired_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ WatchNameData *data = user_data;
+ data->num_acquired += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+w_name_lost_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ WatchNameData *data = user_data;
+ data->num_lost += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+name_appeared_handler (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ WatchNameData *data = user_data;
+ if (data->expect_null_connection)
+ {
+ g_assert (connection == NULL);
+ }
+ else
+ {
+ g_assert (connection != NULL);
+ g_dbus_connection_set_exit_on_close (connection, FALSE);
+ }
+ data->num_appeared += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+name_vanished_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ WatchNameData *data = user_data;
+ if (data->expect_null_connection)
+ {
+ g_assert (connection == NULL);
+ }
+ else
+ {
+ g_assert (connection != NULL);
+ g_dbus_connection_set_exit_on_close (connection, FALSE);
+ }
+ data->num_vanished += 1;
+ g_main_loop_quit (loop);
+}
+
+static void
+test_bus_watch_name (void)
+{
+ WatchNameData data;
+ guint id;
+ guint owner_id;
+
+ /*
+ * First check that name_vanished_handler() is invoked if there is no bus.
+ *
+ * Also make sure name_vanished_handler() isn't invoked when unwatching the name.
+ */
+ data.num_free_func = 0;
+ data.num_appeared = 0;
+ data.num_vanished = 0;
+ data.expect_null_connection = TRUE;
+ id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.Name1",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_handler,
+ name_vanished_handler,
+ &data,
+ (GDestroyNotify) watch_name_data_free_func);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 1);
+ g_bus_unwatch_name (id);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 1);
+ g_assert_cmpint (data.num_free_func, ==, 1);
+
+ /*
+ * Now bring up a bus, own a name, and then start watching it.
+ */
+ session_bus_up ();
+ /* own the name */
+ data.num_free_func = 0;
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = FALSE;
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.Name1",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ w_bus_acquired_handler,
+ w_name_acquired_handler,
+ w_name_lost_handler,
+ &data,
+ (GDestroyNotify) watch_name_data_free_func);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ /* now watch the name */
+ data.num_appeared = 0;
+ data.num_vanished = 0;
+ id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.Name1",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_handler,
+ name_vanished_handler,
+ &data,
+ (GDestroyNotify) watch_name_data_free_func);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_appeared, ==, 1);
+ g_assert_cmpint (data.num_vanished, ==, 0);
+
+ /*
+ * Unwatch the name.
+ */
+ g_bus_unwatch_name (id);
+ g_assert_cmpint (data.num_free_func, ==, 1);
+
+ /* unown the name */
+ g_bus_unown_name (owner_id);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_free_func, ==, 2);
+
+ /*
+ * Create a watcher and then make a name be owned.
+ *
+ * This should trigger name_appeared_handler() ...
+ */
+ /* watch the name */
+ data.num_appeared = 0;
+ data.num_vanished = 0;
+ data.num_free_func = 0;
+ id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.Name1",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_handler,
+ name_vanished_handler,
+ &data,
+ (GDestroyNotify) watch_name_data_free_func);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 0);
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_appeared, ==, 0);
+ g_assert_cmpint (data.num_vanished, ==, 1);
+
+ /* own the name */
+ data.num_acquired = 0;
+ data.num_lost = 0;
+ data.expect_null_connection = FALSE;
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.gtk.GDBus.Name1",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ w_bus_acquired_handler,
+ w_name_acquired_handler,
+ w_name_lost_handler,
+ &data,
+ (GDestroyNotify) watch_name_data_free_func);
+ while (data.num_acquired == 0 || data.num_appeared == 0)
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_acquired, ==, 1);
+ g_assert_cmpint (data.num_lost, ==, 0);
+ g_assert_cmpint (data.num_appeared, ==, 1);
+ g_assert_cmpint (data.num_vanished, ==, 1);
+
+ /*
+ * Nuke the bus and check that the name vanishes and is lost.
+ */
+ data.expect_null_connection = TRUE;
+ session_bus_down ();
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.num_lost, ==, 1);
+ g_assert_cmpint (data.num_vanished, ==, 2);
+
+ g_bus_unwatch_name (id);
+ g_assert_cmpint (data.num_free_func, ==, 1);
+
+ g_bus_unown_name (owner_id);
+ g_assert_cmpint (data.num_free_func, ==, 2);
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_validate_names (void)
+{
+ guint n;
+ static const struct
+ {
+ gboolean name;
+ gboolean unique;
+ gboolean interface;
+ const gchar *string;
+ } names[] = {
+ { 1, 0, 1, "valid.well_known.name"},
+ { 1, 0, 0, "valid.well-known.name"},
+ { 1, 1, 0, ":valid.unique.name"},
+ { 0, 0, 0, "invalid.5well_known.name"},
+ { 0, 0, 0, "4invalid.5well_known.name"},
+ { 1, 1, 0, ":4valid.5unique.name"},
+ { 0, 0, 0, ""},
+ { 1, 0, 1, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name1"}, /* 255 */
+ { 0, 0, 0, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name12"}, /* 256 - too long! */
+ { 0, 0, 0, ".starts.with.a.dot"},
+ { 0, 0, 0, "contains.invalid;.characters"},
+ { 0, 0, 0, "contains.inva/lid.characters"},
+ { 0, 0, 0, "contains.inva[lid.characters"},
+ { 0, 0, 0, "contains.inva]lid.characters"},
+ { 0, 0, 0, "contains.inva_æøå_lid.characters"},
+ { 1, 1, 0, ":1.1"},
+ };
+
+ for (n = 0; n < G_N_ELEMENTS (names); n++)
+ {
+ if (names[n].name)
+ g_assert (g_dbus_is_name (names[n].string));
+ else
+ g_assert (!g_dbus_is_name (names[n].string));
+
+ if (names[n].unique)
+ g_assert (g_dbus_is_unique_name (names[n].string));
+ else
+ g_assert (!g_dbus_is_unique_name (names[n].string));
+
+ if (names[n].interface)
+ g_assert (g_dbus_is_interface_name (names[n].string));
+ else
+ g_assert (!g_dbus_is_interface_name (names[n].string));
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint ret;
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ g_test_add_func ("/gdbus/validate-names", test_validate_names);
+ g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name);
+ g_test_add_func ("/gdbus/bus-watch-name", test_bus_watch_name);
+
+ ret = g_test_run();
+
+ g_main_loop_unref (loop);
+
+ return ret;
+}
diff --git a/gio/tests/gdbus-peer.c b/gio/tests/gdbus-peer.c
new file mode 100644
index 000000000..ae6d68dd4
--- /dev/null
+++ b/gio/tests/gdbus-peer.c
@@ -0,0 +1,746 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+/* for open(2) */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gio/gunixsocketaddress.h>
+
+#include "gdbus-tests.h"
+
+
+#ifdef G_OS_UNIX
+static gboolean is_unix = TRUE;
+#else
+static gboolean is_unix = FALSE;
+#endif
+
+static gchar *test_guid = NULL;
+static GMainLoop *service_loop = NULL;
+static GDBusServer *server = NULL;
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that peer-to-peer connections work */
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+typedef struct
+{
+ gboolean accept_connection;
+ gint num_connection_attempts;
+ GPtrArray *current_connections;
+ guint num_method_calls;
+ gboolean signal_received;
+} PeerData;
+
+static const gchar *test_interface_introspection_xml =
+ "<node>"
+ " <interface name='org.gtk.GDBus.PeerTestInterface'>"
+ " <method name='HelloPeer'>"
+ " <arg type='s' name='greeting' direction='in'/>"
+ " <arg type='s' name='response' direction='out'/>"
+ " </method>"
+ " <method name='EmitSignal'/>"
+ " <method name='OpenFile'>"
+ " <arg type='s' name='path' direction='in'/>"
+ " </method>"
+ " <signal name='PeerSignal'>"
+ " <arg type='s' name='a_string'/>"
+ " </signal>"
+ " <property type='s' name='PeerProperty' access='read'/>"
+ " </interface>"
+ "</node>";
+static const GDBusInterfaceInfo *test_interface_introspection_data = NULL;
+
+static void
+test_interface_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ PeerData *data = user_data;
+
+ data->num_method_calls++;
+
+ g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject");
+ g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface");
+
+ if (g_strcmp0 (method_name, "HelloPeer") == 0)
+ {
+ const gchar *greeting;
+ gchar *response;
+
+ g_variant_get (parameters, "(s)", &greeting);
+
+ response = g_strdup_printf ("You greeted me with '%s'.",
+ greeting);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", response));
+ g_free (response);
+ }
+ else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+ {
+ GError *error;
+
+ error = NULL;
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ "PeerSignal",
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (g_strcmp0 (method_name, "OpenFile") == 0)
+ {
+ const gchar *path;
+ GDBusMessage *reply;
+ GError *error;
+ gint fd;
+ GUnixFDList *fd_list;
+
+ g_variant_get (parameters, "(s)", &path);
+
+ fd_list = g_unix_fd_list_new ();
+
+ error = NULL;
+
+ fd = open (path, O_RDONLY);
+ g_unix_fd_list_append (fd_list, fd, &error);
+ g_assert_no_error (error);
+ close (fd);
+
+ reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
+ g_dbus_message_set_unix_fd_list (reply, fd_list);
+ g_object_unref (invocation);
+
+ error = NULL;
+ g_dbus_connection_send_message (connection,
+ reply,
+ NULL, /* out_serial */
+ &error);
+ g_assert_no_error (error);
+ g_object_unref (reply);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+}
+
+static GVariant *
+test_interface_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject");
+ g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface");
+ g_assert_cmpstr (property_name, ==, "PeerProperty");
+
+ return g_variant_new_string ("ThePropertyValue");
+}
+
+
+static const GDBusInterfaceVTable test_interface_vtable =
+{
+ test_interface_method_call,
+ test_interface_get_property,
+ NULL /* set_property */
+};
+
+static void
+on_proxy_signal_received (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ PeerData *data = user_data;
+
+ data->signal_received = TRUE;
+
+ g_assert (sender_name == NULL);
+ g_assert_cmpstr (signal_name, ==, "PeerSignal");
+ g_main_loop_quit (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_deny_authenticated_peer (GDBusAuthObserver *observer,
+ GIOStream *stream,
+ GCredentials *credentials,
+ gpointer user_data)
+{
+ PeerData *data = user_data;
+ gboolean deny_peer;
+
+ data->num_connection_attempts++;
+
+ deny_peer = FALSE;
+ if (!data->accept_connection)
+ {
+ deny_peer = TRUE;
+ g_main_loop_quit (loop);
+ }
+
+ return deny_peer;
+}
+
+/* Runs in thread we created GDBusServer in (since we didn't pass G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) */
+static void
+on_new_connection (GDBusServer *server,
+ GDBusConnection *connection,
+ gpointer user_data)
+{
+ PeerData *data = user_data;
+ GError *error;
+ guint reg_id;
+
+ //g_print ("Client connected.\n"
+ // "Negotiated capabilities: unix-fd-passing=%d\n",
+ // g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+ g_ptr_array_add (data->current_connections, g_object_ref (connection));
+
+ /* export object on the newly established connection */
+ error = NULL;
+ reg_id = g_dbus_connection_register_object (connection,
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ test_interface_introspection_data,
+ &test_interface_vtable,
+ data,
+ NULL, /* GDestroyNotify for data */
+ &error);
+ g_assert_no_error (error);
+ g_assert (reg_id > 0);
+
+ g_main_loop_quit (loop);
+}
+
+static gpointer
+service_thread_func (gpointer user_data)
+{
+ PeerData *data = user_data;
+ GMainContext *service_context;
+ GDBusAuthObserver *observer;
+ GError *error;
+
+ service_context = g_main_context_new ();
+ g_main_context_push_thread_default (service_context);
+
+ error = NULL;
+ observer = g_dbus_auth_observer_new ();
+ server = g_dbus_server_new_sync (is_unix ? "unix:tmpdir=/tmp/gdbus-test-" : "nonce-tcp:",
+ G_DBUS_SERVER_FLAGS_NONE,
+ test_guid,
+ observer,
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+
+ g_signal_connect (server,
+ "new-connection",
+ G_CALLBACK (on_new_connection),
+ data);
+ g_signal_connect (observer,
+ "deny-authenticated-peer",
+ G_CALLBACK (on_deny_authenticated_peer),
+ data);
+ g_object_unref (observer);
+
+ g_dbus_server_start (server);
+
+ service_loop = g_main_loop_new (service_context, FALSE);
+ g_main_loop_run (service_loop);
+
+ g_main_context_pop_thread_default (service_context);
+
+ g_main_loop_unref (service_loop);
+ g_main_context_unref (service_context);
+
+ /* test code specifically unrefs the server - see below */
+ g_assert (server == NULL);
+
+ return NULL;
+}
+
+#if 0
+static gboolean
+on_incoming_connection (GSocketService *service,
+ GSocketConnection *socket_connection,
+ GObject *source_object,
+ gpointer user_data)
+{
+ PeerData *data = user_data;
+
+ if (data->accept_connection)
+ {
+ GError *error;
+ guint reg_id;
+ GDBusConnection *connection;
+
+ error = NULL;
+ connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
+ test_guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+
+ g_ptr_array_add (data->current_connections, connection);
+
+ /* export object on the newly established connection */
+ error = NULL;
+ reg_id = g_dbus_connection_register_object (connection,
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ &test_interface_introspection_data,
+ &test_interface_vtable,
+ data,
+ NULL, /* GDestroyNotify for data */
+ &error);
+ g_assert_no_error (error);
+ g_assert (reg_id > 0);
+
+ }
+ else
+ {
+ /* don't do anything */
+ }
+
+ data->num_connection_attempts++;
+
+ g_main_loop_quit (loop);
+
+ /* stops other signal handlers from being invoked */
+ return TRUE;
+}
+
+static gpointer
+service_thread_func (gpointer data)
+{
+ GMainContext *service_context;
+ gchar *socket_path;
+ GSocketAddress *address;
+ GError *error;
+
+ service_context = g_main_context_new ();
+ g_main_context_push_thread_default (service_context);
+
+ socket_path = g_strdup_printf ("/tmp/gdbus-test-pid-%d", getpid ());
+ address = g_unix_socket_address_new (socket_path);
+
+ service = g_socket_service_new ();
+ error = NULL;
+ g_socket_listener_add_address (G_SOCKET_LISTENER (service),
+ address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, /* source_object */
+ NULL, /* effective_address */
+ &error);
+ g_assert_no_error (error);
+ g_signal_connect (service,
+ "incoming",
+ G_CALLBACK (on_incoming_connection),
+ data);
+ g_socket_service_start (service);
+
+ service_loop = g_main_loop_new (service_context, FALSE);
+ g_main_loop_run (service_loop);
+
+ g_main_context_pop_thread_default (service_context);
+
+ g_main_loop_unref (service_loop);
+ g_main_context_unref (service_context);
+
+ g_object_unref (address);
+ g_free (socket_path);
+ return NULL;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+check_connection (gpointer user_data)
+{
+ PeerData *data = user_data;
+ guint n;
+
+ for (n = 0; n < data->current_connections->len; n++)
+ {
+ GDBusConnection *c;
+ GIOStream *stream;
+
+ c = G_DBUS_CONNECTION (data->current_connections->pdata[n]);
+ stream = g_dbus_connection_get_stream (c);
+
+ g_debug ("In check_connection for %d: connection %p, stream %p", n, c, stream);
+ g_debug ("closed = %d", g_io_stream_is_closed (stream));
+
+ GSocket *socket;
+ socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
+ g_debug ("socket_closed = %d", g_socket_is_closed (socket));
+ g_debug ("socket_condition_check = %d", g_socket_condition_check (socket, G_IO_IN|G_IO_OUT|G_IO_ERR|G_IO_HUP));
+
+ gchar buf[128];
+ GError *error;
+ gssize num_read;
+ error = NULL;
+ num_read = g_input_stream_read (g_io_stream_get_input_stream (stream),
+ buf,
+ 128,
+ NULL,
+ &error);
+ if (num_read < 0)
+ {
+ g_debug ("error: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_debug ("no error, read %d bytes", (gint) num_read);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_do_disconnect_in_idle (gpointer data)
+{
+ GDBusConnection *c = G_DBUS_CONNECTION (data);
+ g_debug ("GDC %p has ref_count %d", c, G_OBJECT (c)->ref_count);
+ g_dbus_connection_disconnect (c);
+ g_object_unref (c);
+ return FALSE;
+}
+#endif
+
+static void
+test_peer (void)
+{
+ GDBusConnection *c;
+ GDBusConnection *c2;
+ GDBusProxy *proxy;
+ GError *error;
+ PeerData data;
+ GVariant *value;
+ GVariant *result;
+ const gchar *s;
+ GThread *service_thread;
+
+ memset (&data, '\0', sizeof (PeerData));
+ data.current_connections = g_ptr_array_new_with_free_func (g_object_unref);
+
+ /* first try to connect when there is no server */
+ error = NULL;
+ c = g_dbus_connection_new_for_address_sync (is_unix ? "unix:path=/tmp/gdbus-test-does-not-exist-pid" :
+ /* NOTE: Even if something is listening on port 12345 the connection
+ * will fail because the nonce file doesn't exist */
+ "nonce-tcp:host=localhost,port=12345,noncefile=this-does-not-exist-gdbus",
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* cancellable */
+ &error);
+ _g_assert_error_domain (error, G_IO_ERROR);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_clear_error (&error);
+ g_assert (c == NULL);
+
+ /* bring up a server - we run the server in a different thread to avoid deadlocks */
+ error = NULL;
+ service_thread = g_thread_create (service_thread_func,
+ &data,
+ TRUE,
+ &error);
+ while (service_loop == NULL)
+ g_thread_yield ();
+ g_assert (server != NULL);
+
+ /* bring up a connection and accept it */
+ data.accept_connection = TRUE;
+ error = NULL;
+ c = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (c != NULL);
+ while (data.current_connections->len < 1)
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.current_connections->len, ==, 1);
+ g_assert_cmpint (data.num_connection_attempts, ==, 1);
+ //g_assert (g_dbus_connection_get_bus_type (c) == G_BUS_TYPE_NONE);
+ g_assert (g_dbus_connection_get_unique_name (c) == NULL);
+ g_assert_cmpstr (g_dbus_connection_get_guid (c), ==, test_guid);
+
+ /* check that we create a proxy, read properties, receive signals and invoke
+ * the HelloPeer() method. Since the server runs in another thread it's fine
+ * to use synchronous blocking API here.
+ */
+ error = NULL;
+ proxy = g_dbus_proxy_new_sync (c,
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ NULL, /* bus_name */
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ NULL, /* GCancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (proxy != NULL);
+ error = NULL;
+ value = g_dbus_proxy_get_cached_property (proxy, "PeerProperty", &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "ThePropertyValue");
+
+ /* try invoking a method */
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "HelloPeer",
+ g_variant_new ("(s)", "Hey Peer!"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL, /* GCancellable */
+ &error);
+ g_assert_no_error (error);
+ g_variant_get (result, "(s)", &s);
+ g_assert_cmpstr (s, ==, "You greeted me with 'Hey Peer!'.");
+ g_variant_unref (result);
+ g_assert_cmpint (data.num_method_calls, ==, 1);
+
+ /* make the other peer emit a signal - catch it */
+ g_signal_connect (proxy,
+ "g-signal",
+ G_CALLBACK (on_proxy_signal_received),
+ &data);
+ g_assert (!data.signal_received);
+ g_dbus_proxy_invoke_method (proxy,
+ "EmitSignal",
+ NULL, /* no arguments */
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL, /* GCancellable */
+ NULL, /* GAsyncReadyCallback - we don't care about the result */
+ NULL); /* user_data */
+ g_main_loop_run (loop);
+ g_assert (data.signal_received);
+ g_assert_cmpint (data.num_method_calls, ==, 2);
+
+ /* check for UNIX fd passing */
+#ifdef G_OS_UNIX
+ {
+ GDBusMessage *method_call_message;
+ GDBusMessage *method_reply_message;
+ GUnixFDList *fd_list;
+ gint fd;
+ gchar buf[1024];
+ gssize len;
+ gchar *buf2;
+ gsize len2;
+
+ method_call_message = g_dbus_message_new_method_call (NULL, /* name */
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ "OpenFile");
+ g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", "/etc/hosts"));
+ error = NULL;
+ method_reply_message = g_dbus_connection_send_message_with_reply_sync (c,
+ method_call_message,
+ -1,
+ NULL, /* out_serial */
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (g_dbus_message_get_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
+ fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
+ g_assert (fd_list != NULL);
+ g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1);
+ error = NULL;
+ fd = g_unix_fd_list_get (fd_list, 0, &error);
+ g_assert_no_error (error);
+ g_object_unref (method_call_message);
+ g_object_unref (method_reply_message);
+
+ memset (buf, '\0', sizeof (buf));
+ len = read (fd, buf, sizeof (buf) - 1);
+ close (fd);
+
+ error = NULL;
+ g_file_get_contents ("/etc/hosts",
+ &buf2,
+ &len2,
+ &error);
+ g_assert_no_error (error);
+ if (len2 > sizeof (buf))
+ buf2[sizeof (buf)] = '\0';
+ g_assert_cmpstr (buf, ==, buf2);
+ g_free (buf2);
+ }
+#endif /* G_OS_UNIX */
+
+
+ /* bring up a connection - don't accept it - this should fail
+ */
+ data.accept_connection = FALSE;
+ error = NULL;
+ c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* cancellable */
+ &error);
+ _g_assert_error_domain (error, G_IO_ERROR);
+ g_assert (c2 == NULL);
+
+#if 0
+ /* TODO: THIS TEST DOESN'T WORK YET */
+
+ /* bring up a connection - accept it.. then disconnect from the client side - check
+ * that the server side gets the disconnect signal.
+ */
+ error = NULL;
+ data.accept_connection = TRUE;
+ c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (c2 != NULL);
+ g_assert (!g_dbus_connection_get_is_disconnected (c2));
+ while (data.num_connection_attempts < 3)
+ g_main_loop_run (loop);
+ g_assert_cmpint (data.current_connections->len, ==, 2);
+ g_assert_cmpint (data.num_connection_attempts, ==, 3);
+ g_assert (!g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1])));
+ g_idle_add (on_do_disconnect_in_idle, c2);
+ g_debug ("==================================================");
+ g_debug ("==================================================");
+ g_debug ("==================================================");
+ g_debug ("waiting for disconnect on connection %p, stream %p",
+ data.current_connections->pdata[1],
+ g_dbus_connection_get_stream (data.current_connections->pdata[1]));
+
+ g_timeout_add (2000, check_connection, &data);
+ //_g_assert_signal_received (G_DBUS_CONNECTION (data.current_connections->pdata[1]), "closed");
+ g_main_loop_run (loop);
+ g_assert (g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1])));
+ g_ptr_array_set_size (data.current_connections, 1); /* remove disconnected connection object */
+#endif
+
+ /* unref the server and stop listening for new connections
+ *
+ * This won't bring down the established connections - check that c is still connected
+ * by invoking a method
+ */
+ //g_socket_service_stop (service);
+ //g_object_unref (service);
+ g_dbus_server_stop (server);
+ g_object_unref (server);
+ server = NULL;
+
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "HelloPeer",
+ g_variant_new ("(s)", "Hey Again Peer!"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL, /* GCancellable */
+ &error);
+ g_assert_no_error (error);
+ g_variant_get (result, "(s)", &s);
+ g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'.");
+ g_variant_unref (result);
+ g_assert_cmpint (data.num_method_calls, ==, 4);
+
+#if 0
+ /* TODO: THIS TEST DOESN'T WORK YET */
+
+ /* now disconnect from the server side - check that the client side gets the signal */
+ g_assert_cmpint (data.current_connections->len, ==, 1);
+ g_assert (G_DBUS_CONNECTION (data.current_connections->pdata[0]) != c);
+ g_dbus_connection_disconnect (G_DBUS_CONNECTION (data.current_connections->pdata[0]));
+ if (!g_dbus_connection_get_is_disconnected (c))
+ _g_assert_signal_received (c, "closed");
+ g_assert (g_dbus_connection_get_is_disconnected (c));
+#endif
+
+ g_object_unref (c);
+ g_ptr_array_unref (data.current_connections);
+ g_object_unref (proxy);
+
+ g_main_loop_quit (service_loop);
+ g_thread_join (service_thread);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint ret;
+ GDBusNodeInfo *introspection_data = NULL;
+
+ g_type_init ();
+ g_thread_init (NULL);
+ g_test_init (&argc, &argv, NULL);
+
+ introspection_data = g_dbus_node_info_new_for_xml (test_interface_introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+ test_interface_introspection_data = introspection_data->interfaces[0];
+
+ test_guid = g_dbus_generate_guid ();
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ g_test_add_func ("/gdbus/peer-to-peer", test_peer);
+
+ ret = g_test_run();
+
+ g_main_loop_unref (loop);
+ g_free (test_guid);
+ g_dbus_node_info_unref (introspection_data);
+
+ return ret;
+}
diff --git a/gio/tests/gdbus-proxy.c b/gio/tests/gdbus-proxy.c
new file mode 100644
index 000000000..6e45f4217
--- /dev/null
+++ b/gio/tests/gdbus-proxy.c
@@ -0,0 +1,455 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the method aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_methods (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy)
+{
+ GVariant *result;
+ GError *error;
+ const gchar *str;
+ gchar *dbus_error_name;
+
+ /* check that we can invoke a method */
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "HelloWorld",
+ g_variant_new ("(s)", "Hey"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "(s)");
+ g_variant_get (result, "(s)", &str);
+ g_assert_cmpstr (str, ==, "You greeted me with 'Hey'. Thanks!");
+ g_variant_unref (result);
+
+ /* Check that we can completely recover the returned error */
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "HelloWorld",
+ g_variant_new ("(s)", "Yo"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+ g_assert (g_dbus_error_is_remote_error (error));
+ g_assert (g_dbus_error_is_remote_error (error));
+ g_assert (result == NULL);
+ dbus_error_name = g_dbus_error_get_remote_error (error);
+ g_assert_cmpstr (dbus_error_name, ==, "com.example.TestException");
+ g_free (dbus_error_name);
+ g_assert (g_dbus_error_strip_remote_error (error));
+ g_assert_cmpstr (error->message, ==, "Yo is not a proper greeting");
+ g_clear_error (&error);
+
+ /* Check that we get a timeout if the method handling is taking longer than timeout */
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "Sleep",
+ g_variant_new ("(i)", 500 /* msec */),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ 100 /* msec */,
+ NULL,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_assert (result == NULL);
+ g_clear_error (&error);
+
+ /* Check that proxy-default timeouts work. */
+ g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, -1);
+
+ /* the default timeout is 25000 msec so this should work */
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "Sleep",
+ g_variant_new ("(i)", 500 /* msec */),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1, /* use proxy default (e.g. -1 -> e.g. 25000 msec) */
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+
+ /* now set the proxy-default timeout to 250 msec and try the 500 msec call - this should FAIL */
+ g_dbus_proxy_set_default_timeout (proxy, 250);
+ g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, 250);
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "Sleep",
+ g_variant_new ("(i)", 500 /* msec */),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1, /* use proxy default (e.g. 250 msec) */
+ NULL,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_assert (result == NULL);
+ g_clear_error (&error);
+
+ /* clean up after ourselves */
+ g_dbus_proxy_set_default_timeout (proxy, -1);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the property aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_properties (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy)
+{
+ GError *error;
+ GVariant *variant;
+ GVariant *variant2;
+ GVariant *result;
+
+ error = NULL;
+
+ /*
+ * Check that we can read cached properties.
+ *
+ * No need to test all properties - GVariant has already been tested
+ */
+ variant = g_dbus_proxy_get_cached_property (proxy, "y", &error);
+ g_assert_no_error (error);
+ g_assert (variant != NULL);
+ g_assert_cmpint (g_variant_get_byte (variant), ==, 1);
+ g_variant_unref (variant);
+ variant = g_dbus_proxy_get_cached_property (proxy, "o", &error);
+ g_assert_no_error (error);
+ g_assert (variant != NULL);
+ g_assert_cmpstr (g_variant_get_string (variant, NULL), ==, "/some/path");
+ g_variant_unref (variant);
+
+ /*
+ * Now ask the service to change a property and check that #GDBusProxy::g-property-changed
+ * is received. Also check that the cache is updated.
+ */
+ variant2 = g_variant_new_byte (42);
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "FrobSetProperty",
+ g_variant_new ("(sv)",
+ "y",
+ variant2),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+ _g_assert_signal_received (proxy, "g-properties-changed");
+ variant = g_dbus_proxy_get_cached_property (proxy, "y", &error);
+ g_assert_no_error (error);
+ g_assert (variant != NULL);
+ g_assert_cmpint (g_variant_get_byte (variant), ==, 42);
+ g_variant_unref (variant);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the signal aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_proxy_signals_on_signal (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GString *s = user_data;
+
+ g_assert_cmpstr (signal_name, ==, "TestSignal");
+ g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(sov)");
+
+ g_variant_print_string (parameters, s, TRUE);
+}
+
+typedef struct
+{
+ GMainLoop *internal_loop;
+ GString *s;
+} TestSignalData;
+
+static void
+test_proxy_signals_on_emit_signal_cb (GDBusProxy *proxy,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TestSignalData *data = user_data;
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_finish (proxy,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+
+ /* check that the signal was recieved before we got the method result */
+ g_assert (strlen (data->s->str) > 0);
+
+ /* break out of the loop */
+ g_main_loop_quit (data->internal_loop);
+}
+
+static void
+test_signals (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy)
+{
+ GError *error;
+ GString *s;
+ gulong signal_handler_id;
+ TestSignalData data;
+ GVariant *result;
+
+ error = NULL;
+
+ /*
+ * Ask the service to emit a signal and check that we receive it.
+ *
+ * Note that blocking calls don't block in the mainloop so wait for the signal (which
+ * is dispatched before the method reply)
+ */
+ s = g_string_new (NULL);
+ signal_handler_id = g_signal_connect (proxy,
+ "g-signal",
+ G_CALLBACK (test_proxy_signals_on_signal),
+ s);
+
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "EmitSignal",
+ g_variant_new ("(so)",
+ "Accept the next proposition you hear",
+ "/some/path"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+ /* check that we haven't received the signal just yet */
+ g_assert (strlen (s->str) == 0);
+ /* and now wait for the signal */
+ _g_assert_signal_received (proxy, "g-signal");
+ g_assert_cmpstr (s->str,
+ ==,
+ "('Accept the next proposition you hear .. in bed!', objectpath '/some/path/in/bed', <'a variant'>)");
+ g_signal_handler_disconnect (proxy, signal_handler_id);
+ g_string_free (s, TRUE);
+
+ /*
+ * Now do this async to check the signal is received before the method returns.
+ */
+ s = g_string_new (NULL);
+ data.internal_loop = g_main_loop_new (NULL, FALSE);
+ data.s = s;
+ signal_handler_id = g_signal_connect (proxy,
+ "g-signal",
+ G_CALLBACK (test_proxy_signals_on_signal),
+ s);
+ g_dbus_proxy_invoke_method (proxy,
+ "EmitSignal",
+ g_variant_new ("(so)",
+ "You will make a great programmer",
+ "/some/other/path"),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) test_proxy_signals_on_emit_signal_cb,
+ &data);
+ g_main_loop_run (data.internal_loop);
+ g_main_loop_unref (data.internal_loop);
+ g_assert_cmpstr (s->str,
+ ==,
+ "('You will make a great programmer .. in bed!', objectpath '/some/other/path/in/bed', <'a variant'>)");
+ g_signal_handler_disconnect (proxy, signal_handler_id);
+ g_string_free (s, TRUE);
+}
+
+static void
+test_bogus_method_return (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy)
+{
+ GError *error = NULL;
+ GVariant *result;
+
+ result = g_dbus_proxy_invoke_method_sync (proxy,
+ "PairReturn",
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
+ g_assert (result == NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *frob_dbus_interface_xml =
+ "<node>"
+ " <interface name='com.example.Frob'>"
+ /* Deliberately different from gdbus-testserver.py's definition */
+ " <method name='PairReturn'>"
+ " <arg type='u' name='somenumber' direction='in'/>"
+ " <arg type='s' name='somestring' direction='out'/>"
+ " </method>"
+ " <method name='HelloWorld'>"
+ " <arg type='s' name='somestring' direction='in'/>"
+ " <arg type='s' name='somestring' direction='out'/>"
+ " </method>"
+ " <method name='Sleep'>"
+ " <arg type='i' name='timeout' direction='in'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+static GDBusInterfaceInfo *frob_dbus_interface_info;
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy,
+ gpointer user_data)
+{
+ test_methods (connection, name, name_owner, proxy);
+ test_properties (connection, name, name_owner, proxy);
+ test_signals (connection, name, name_owner, proxy);
+
+ /* Now repeat the method tests, with an expected interface set */
+ g_dbus_proxy_set_interface_info (proxy, frob_dbus_interface_info);
+ test_methods (connection, name, name_owner, proxy);
+
+ /* And now one more test where we deliberately set the expected
+ * interface definition incorrectly
+ */
+ test_bogus_method_return (connection, name, name_owner, proxy);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+test_proxy (void)
+{
+ guint watcher_id;
+
+ session_bus_up ();
+
+ /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+ * until one can connect to the bus but that's not how things work right now
+ */
+ usleep (500 * 1000);
+
+ watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+ "com.example.TestService",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ "/com/example/TestObject",
+ "com.example.Frob",
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_NONE,
+ on_proxy_appeared,
+ on_proxy_vanished,
+ NULL,
+ NULL);
+
+ /* this is safe; testserver will exit once the bus goes away */
+ g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_proxy (watcher_id);
+
+ /* tear down bus */
+ session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ gint ret;
+ GDBusNodeInfo *introspection_data = NULL;
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ introspection_data = g_dbus_node_info_new_for_xml (frob_dbus_interface_xml, NULL);
+ g_assert (introspection_data != NULL);
+ frob_dbus_interface_info = introspection_data->interfaces[0];
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ g_test_add_func ("/gdbus/proxy", test_proxy);
+
+ ret = g_test_run();
+
+ g_dbus_node_info_unref (introspection_data);
+ return ret;
+}
diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c
new file mode 100644
index 000000000..91b79f81b
--- /dev/null
+++ b/gio/tests/gdbus-serialization.c
@@ -0,0 +1,650 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <dbus/dbus.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+hexdump (const guchar *str, gsize len)
+{
+ const guchar *data = (const guchar *) str;
+ guint n, m;
+
+ for (n = 0; n < len; n += 16)
+ {
+ g_printerr ("%04x: ", n);
+
+ for (m = n; m < n + 16; m++)
+ {
+ if (m > n && (m%4) == 0)
+ g_printerr (" ");
+ if (m < len)
+ g_printerr ("%02x ", data[m]);
+ else
+ g_printerr (" ");
+ }
+
+ g_printerr (" ");
+
+ for (m = n; m < len && m < n + 16; m++)
+ g_printerr ("%c", g_ascii_isprint (data[m]) ? data[m] : '.');
+
+ g_printerr ("\n");
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+append_gv_to_dbus_iter (DBusMessageIter *iter,
+ GVariant *value,
+ GError **error)
+{
+ const GVariantType *type;
+
+ type = g_variant_get_type (value);
+ if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+ {
+ dbus_bool_t v = g_variant_get_boolean (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_BOOLEAN, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+ {
+ guint8 v = g_variant_get_byte (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_BYTE, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+ {
+ gint16 v = g_variant_get_int16 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_INT16, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+ {
+ guint16 v = g_variant_get_uint16 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT16, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+ {
+ gint32 v = g_variant_get_int32 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_INT32, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+ {
+ guint32 v = g_variant_get_uint32 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT32, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+ {
+ gint64 v = g_variant_get_int64 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_INT64, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+ {
+ guint64 v = g_variant_get_uint64 (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT64, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+ {
+ gdouble v = g_variant_get_double (value);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_DOUBLE, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+ {
+ const gchar *v = g_variant_get_string (value, NULL);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_STRING, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+ {
+ const gchar *v = g_variant_get_string (value, NULL);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_OBJECT_PATH, &v);
+ }
+ else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+ {
+ const gchar *v = g_variant_get_string (value, NULL);
+ dbus_message_iter_append_basic (iter, DBUS_TYPE_SIGNATURE, &v);
+ }
+ else if (g_variant_type_is_variant (type))
+ {
+ DBusMessageIter sub;
+ GVariant *child;
+
+ child = g_variant_get_child_value (value, 0);
+ dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT,
+ g_variant_get_type_string (child),
+ &sub);
+ if (!append_gv_to_dbus_iter (&sub, child, error))
+ {
+ g_variant_unref (child);
+ goto fail;
+ }
+ dbus_message_iter_close_container (iter, &sub);
+ g_variant_unref (child);
+ }
+ else if (g_variant_type_is_array (type))
+ {
+ DBusMessageIter dbus_iter;
+ const gchar *type_string;
+ GVariantIter gv_iter;
+ GVariant *item;
+
+ type_string = g_variant_get_type_string (value);
+ type_string++; /* skip the 'a' */
+
+ dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY,
+ type_string, &dbus_iter);
+ g_variant_iter_init (&gv_iter, value);
+
+ while ((item = g_variant_iter_next_value (&gv_iter)))
+ {
+ if (!append_gv_to_dbus_iter (&dbus_iter, item, error))
+ {
+ goto fail;
+ }
+ }
+
+ dbus_message_iter_close_container (iter, &dbus_iter);
+ }
+ else if (g_variant_type_is_tuple (type))
+ {
+ DBusMessageIter dbus_iter;
+ GVariantIter gv_iter;
+ GVariant *item;
+
+ dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT,
+ NULL, &dbus_iter);
+ g_variant_iter_init (&gv_iter, value);
+
+ while ((item = g_variant_iter_next_value (&gv_iter)))
+ {
+ if (!append_gv_to_dbus_iter (&dbus_iter, item, error))
+ goto fail;
+ }
+
+ dbus_message_iter_close_container (iter, &dbus_iter);
+ }
+ else if (g_variant_type_is_dict_entry (type))
+ {
+ DBusMessageIter dbus_iter;
+ GVariant *key, *val;
+
+ dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY,
+ NULL, &dbus_iter);
+ key = g_variant_get_child_value (value, 0);
+ if (!append_gv_to_dbus_iter (&dbus_iter, key, error))
+ {
+ g_variant_unref (key);
+ goto fail;
+ }
+ g_variant_unref (key);
+
+ val = g_variant_get_child_value (value, 1);
+ if (!append_gv_to_dbus_iter (&dbus_iter, val, error))
+ {
+ g_variant_unref (val);
+ goto fail;
+ }
+ g_variant_unref (val);
+
+ dbus_message_iter_close_container (iter, &dbus_iter);
+ }
+ else
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Error serializing GVariant with type-string `%s' to a D-Bus message",
+ g_variant_get_type_string (value));
+ goto fail;
+ }
+
+ return TRUE;
+
+ fail:
+ return FALSE;
+}
+
+static gboolean
+append_gv_to_dbus_message (DBusMessage *message,
+ GVariant *value,
+ GError **error)
+{
+ gboolean ret;
+ guint n;
+
+ ret = FALSE;
+
+ if (value != NULL)
+ {
+ DBusMessageIter iter;
+ GVariantIter gv_iter;
+ GVariant *item;
+
+ dbus_message_iter_init_append (message, &iter);
+
+ g_variant_iter_init (&gv_iter, value);
+ n = 0;
+ while ((item = g_variant_iter_next_value (&gv_iter)))
+ {
+ if (!append_gv_to_dbus_iter (&iter, item, error))
+ {
+ g_prefix_error (error,
+ "Error encoding in-arg %d: ",
+ n);
+ goto out;
+ }
+ n++;
+ }
+ }
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static void
+print_gv_dbus_message (GVariant *value)
+{
+ DBusMessage *message;
+ char *blob;
+ int blob_len;
+ GError *error;
+
+ message = dbus_message_new (DBUS_MESSAGE_TYPE_METHOD_CALL);
+ dbus_message_set_serial (message, 0x41);
+ dbus_message_set_path (message, "/foo/bar");
+ dbus_message_set_member (message, "Member");
+
+ error = NULL;
+ if (!append_gv_to_dbus_message (message, value, &error))
+ {
+ g_printerr ("Error printing GVariant as DBusMessage: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ dbus_message_marshal (message, &blob, &blob_len);
+ g_printerr ("\n");
+ hexdump ((guchar *) blob, blob_len);
+ out:
+ dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+dbus_1_message_append (GString *s,
+ guint indent,
+ DBusMessageIter *iter)
+{
+ gint arg_type;
+ DBusMessageIter sub;
+
+ g_string_append_printf (s, "%*s", indent, "");
+
+ arg_type = dbus_message_iter_get_arg_type (iter);
+ switch (arg_type)
+ {
+ case DBUS_TYPE_BOOLEAN:
+ {
+ dbus_bool_t value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "bool: %s\n", value ? "true" : "false");
+ break;
+ }
+
+ case DBUS_TYPE_BYTE:
+ {
+ guchar value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "byte: 0x%02x\n", (guint) value);
+ break;
+ }
+
+ case DBUS_TYPE_INT16:
+ {
+ gint16 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "int16: %" G_GINT16_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_UINT16:
+ {
+ guint16 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "uint16: %" G_GUINT16_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_INT32:
+ {
+ gint32 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "int32: %" G_GINT32_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_UINT32:
+ {
+ guint32 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "uint32: %" G_GUINT32_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_INT64:
+ {
+ gint64 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "int64: %" G_GINT64_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_UINT64:
+ {
+ guint64 value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "uint64: %" G_GUINT64_FORMAT "\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_DOUBLE:
+ {
+ gdouble value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "double: %f\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_STRING:
+ {
+ const gchar *value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "string: `%s'\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_OBJECT_PATH:
+ {
+ const gchar *value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "object_path: `%s'\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_SIGNATURE:
+ {
+ const gchar *value;
+ dbus_message_iter_get_basic (iter, &value);
+ g_string_append_printf (s, "signature: `%s'\n", value);
+ break;
+ }
+
+ case DBUS_TYPE_VARIANT:
+ g_string_append_printf (s, "variant:\n");
+ dbus_message_iter_recurse (iter, &sub);
+ while (dbus_message_iter_get_arg_type (&sub))
+ {
+ dbus_1_message_append (s, indent + 2, &sub);
+ dbus_message_iter_next (&sub);
+ }
+ break;
+
+ case DBUS_TYPE_ARRAY:
+ g_string_append_printf (s, "array:\n");
+ dbus_message_iter_recurse (iter, &sub);
+ while (dbus_message_iter_get_arg_type (&sub))
+ {
+ dbus_1_message_append (s, indent + 2, &sub);
+ dbus_message_iter_next (&sub);
+ }
+ break;
+
+ case DBUS_TYPE_STRUCT:
+ g_string_append_printf (s, "struct:\n");
+ dbus_message_iter_recurse (iter, &sub);
+ while (dbus_message_iter_get_arg_type (&sub))
+ {
+ dbus_1_message_append (s, indent + 2, &sub);
+ dbus_message_iter_next (&sub);
+ }
+ break;
+
+ case DBUS_TYPE_DICT_ENTRY:
+ g_string_append_printf (s, "dict_entry:\n");
+ dbus_message_iter_recurse (iter, &sub);
+ while (dbus_message_iter_get_arg_type (&sub))
+ {
+ dbus_1_message_append (s, indent + 2, &sub);
+ dbus_message_iter_next (&sub);
+ }
+ break;
+
+ default:
+ g_printerr ("Error serializing D-Bus message to GVariant. Unsupported arg type `%c' (%d)",
+ arg_type,
+ arg_type);
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gchar *
+dbus_1_message_print (DBusMessage *message)
+{
+ GString *s;
+ guint n;
+ DBusMessageIter iter;
+
+ s = g_string_new (NULL);
+ n = 0;
+ dbus_message_iter_init (message, &iter);
+ while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID)
+ {
+ g_string_append_printf (s, "value %d: ", n);
+ dbus_1_message_append (s, 2, &iter);
+ dbus_message_iter_next (&iter);
+ n++;
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_body_signature (GVariant *value)
+{
+ const gchar *s;
+ gsize len;
+ gchar *ret;
+
+ s = g_variant_get_type_string (value);
+ len = strlen (s);
+ g_assert (len>=2);
+
+ ret = g_strndup (s + 1, len - 2);
+
+ return ret;
+}
+
+static void
+check_serialization (GVariant *value,
+ const gchar *expected_dbus_1_output)
+{
+ guchar *blob;
+ gsize blob_size;
+ DBusMessage *dbus_1_message;
+ GDBusMessage *message;
+ GDBusMessage *recovered_message;
+ GError *error;
+ DBusError dbus_error;
+ gchar *s;
+ gchar *s1;
+
+ message = g_dbus_message_new ();
+ g_dbus_message_set_body (message, value);
+ g_dbus_message_set_type (message, G_DBUS_MESSAGE_TYPE_METHOD_CALL);
+ g_dbus_message_set_serial (message, 0x41);
+ s = get_body_signature (value);
+ g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, g_variant_new_object_path ("/foo/bar"));
+ g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, g_variant_new_string ("Member"));
+ g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, g_variant_new_signature (s));
+ g_free (s);
+
+ /* First check that the serialization to the D-Bus wire format is correct */
+
+ error = NULL;
+ blob = g_dbus_message_to_blob (message,
+ &blob_size,
+ &error);
+ g_assert_no_error (error);
+ g_assert (blob != NULL);
+
+ dbus_error_init (&dbus_error);
+ dbus_1_message = dbus_message_demarshal ((char *) blob, blob_size, &dbus_error);
+ if (dbus_error_is_set (&dbus_error))
+ {
+ g_printerr ("Error calling dbus_message_demarshal() on this blob: %s: %s\n",
+ dbus_error.name,
+ dbus_error.message);
+ hexdump (blob, blob_size);
+ dbus_error_free (&dbus_error);
+
+ s = g_variant_print (value, TRUE);
+ g_printerr ("\nThe blob was generated from the following GVariant value:\n%s\n\n", s);
+ g_free (s);
+
+ g_printerr ("If the blob was encoded using DBusMessageIter, the payload would have been:\n");
+ print_gv_dbus_message (value);
+
+ g_assert_not_reached ();
+ }
+
+ s = dbus_1_message_print (dbus_1_message);
+ dbus_message_unref (dbus_1_message);
+
+ g_assert_cmpstr (s, ==, expected_dbus_1_output);
+ g_free (s);
+
+ /* Then serialize back and check that the body is identical */
+
+ error = NULL;
+ recovered_message = g_dbus_message_new_from_blob (blob, blob_size, &error);
+ g_assert_no_error (error);
+ g_assert (recovered_message != NULL);
+ g_assert (g_dbus_message_get_body (recovered_message) != NULL);
+
+ if (!g_variant_equal (g_dbus_message_get_body (recovered_message), value))
+ {
+ s = g_variant_print (g_dbus_message_get_body (recovered_message), TRUE);
+ s1 = g_variant_print (value, TRUE);
+ g_printerr ("Recovered value:\n%s\ndoes not match given value\n%s\n",
+ s,
+ s1);
+ g_free (s);
+ g_free (s1);
+ g_assert_not_reached ();
+ }
+ g_object_unref (message);
+ g_object_unref (recovered_message);
+}
+
+static void
+message_serialize_basic (void)
+{
+ check_serialization (g_variant_new ("(sogybnqiuxtd)",
+ "this is a string",
+ "/this/is/a/path",
+ "sad",
+ 42,
+ TRUE,
+ -42,
+ 60000,
+ -44,
+ 100000,
+ -G_GINT64_CONSTANT(2)<<34,
+ G_GUINT64_CONSTANT(0xffffffffffffffff),
+ 42.5),
+ "value 0: string: `this is a string'\n"
+ "value 1: object_path: `/this/is/a/path'\n"
+ "value 2: signature: `sad'\n"
+ "value 3: byte: 0x2a\n"
+ "value 4: bool: true\n"
+ "value 5: int16: -42\n"
+ "value 6: uint16: 60000\n"
+ "value 7: int32: -44\n"
+ "value 8: uint32: 100000\n"
+ "value 9: int64: -34359738368\n"
+ "value 10: uint64: 18446744073709551615\n"
+ "value 11: double: 42.500000\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+message_serialize_complex (void)
+{
+ GError *error;
+ GVariant *value;
+
+ error = NULL;
+ value = g_variant_parse (G_VARIANT_TYPE ("(aia{ss})"),
+ "([1, 2, 3], {'one': 'white', 'two': 'black'})",
+ NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (value != NULL);
+ check_serialization (value,
+ "value 0: array:\n"
+ " int32: 1\n"
+ " int32: 2\n"
+ " int32: 3\n"
+ "value 1: array:\n"
+ " dict_entry:\n"
+ " string: `one'\n"
+ " string: `white'\n"
+ " dict_entry:\n"
+ " string: `two'\n"
+ " string: `black'\n");
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/gdbus/message-serialize-basic", message_serialize_basic);
+ g_test_add_func ("/gdbus/message-serialize-complex", message_serialize_complex);
+ return g_test_run();
+}
+
diff --git a/gio/tests/gdbus-sessionbus.c b/gio/tests/gdbus-sessionbus.c
new file mode 100644
index 000000000..1e16eddc5
--- /dev/null
+++ b/gio/tests/gdbus-sessionbus.c
@@ -0,0 +1,342 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "gdbus-sessionbus.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Utilities for bringing up and tearing down session message bus instances */
+
+static void
+watch_parent (gint fd)
+{
+ GPollFD fds[1];
+ gint num_events;
+ gchar buf[512];
+ guint bytes_read;
+ GArray *buses_to_kill_array;
+
+ fds[0].fd = fd;
+ fds[0].events = G_IO_HUP | G_IO_IN;
+ fds[0].revents = 0;
+
+ buses_to_kill_array = g_array_new (FALSE, TRUE, sizeof (guint));
+
+ do
+ {
+ guint pid;
+ guint n;
+
+ num_events = g_poll (fds, 1, -1);
+ if (num_events == 0)
+ continue;
+
+ if (fds[0].revents == G_IO_HUP)
+ {
+ for (n = 0; n < buses_to_kill_array->len; n++)
+ {
+ pid = g_array_index (buses_to_kill_array, guint, n);
+ g_print ("cleaning up bus with pid %d\n", pid);
+ kill (pid, SIGTERM);
+ }
+ g_array_free (buses_to_kill_array, TRUE);
+ exit (0);
+ }
+
+ //g_debug ("data from parent");
+
+ memset (buf, '\0', sizeof buf);
+ again:
+ bytes_read = read (fds[0].fd, buf, sizeof buf);
+ if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+ goto again;
+
+ if (sscanf (buf, "add %d\n", &pid) == 1)
+ {
+ g_array_append_val (buses_to_kill_array, pid);
+ }
+ else if (sscanf (buf, "remove %d\n", &pid) == 1)
+ {
+ for (n = 0; n < buses_to_kill_array->len; n++)
+ {
+ if (g_array_index (buses_to_kill_array, guint, n) == pid)
+ {
+ g_array_remove_index (buses_to_kill_array, n);
+ pid = 0;
+ break;
+ }
+ }
+ if (pid != 0)
+ {
+ g_warning ("unknown pid %d to remove", pid);
+ }
+ }
+ else
+ {
+ g_warning ("unknown command from parent '%s'", buf);
+ }
+ }
+ while (TRUE);
+
+}
+
+static GHashTable *session_bus_address_to_pid = NULL;
+static gint pipe_fds[2];
+
+const gchar *
+session_bus_up_with_address (const gchar *given_address)
+{
+ gchar *address;
+ int stdout_fd;
+ GError *error;
+ gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
+ GPid pid;
+ gchar buf[512];
+ ssize_t bytes_read;
+ gchar *config_file_name;
+ gint config_file_fd;
+ GString *config_file_contents;
+
+ address = NULL;
+ error = NULL;
+ config_file_name = NULL;
+ config_file_fd = -1;
+ argv[2] = NULL;
+
+ config_file_fd = g_file_open_tmp ("g-dbus-tests-XXXXXX",
+ &config_file_name,
+ &error);
+ if (config_file_fd < 0)
+ {
+ g_warning ("Error creating temporary config file: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ config_file_contents = g_string_new (NULL);
+ g_string_append (config_file_contents, "<busconfig>\n");
+ g_string_append (config_file_contents, " <type>session</type>\n");
+ g_string_append_printf (config_file_contents, " <listen>%s</listen>\n", given_address);
+ g_string_append (config_file_contents,
+ " <policy context=\"default\">\n"
+ " <!-- Allow everything to be sent -->\n"
+ " <allow send_destination=\"*\" eavesdrop=\"true\"/>\n"
+ " <!-- Allow everything to be received -->\n"
+ " <allow eavesdrop=\"true\"/>\n"
+ " <!-- Allow anyone to own anything -->\n"
+ " <allow own=\"*\"/>\n"
+ " </policy>\n");
+ g_string_append (config_file_contents, "</busconfig>\n");
+
+ if (write (config_file_fd, config_file_contents->str, config_file_contents->len) != (gssize) config_file_contents->len)
+ {
+ g_warning ("Error writing %d bytes to config file: %m", (gint) config_file_contents->len);
+ g_string_free (config_file_contents, TRUE);
+ goto out;
+ }
+ g_string_free (config_file_contents, TRUE);
+
+ argv[2] = g_strdup_printf ("--config-file=%s", config_file_name);
+
+ if (session_bus_address_to_pid == NULL)
+ {
+ /* keep a mapping from session bus address to the pid */
+ session_bus_address_to_pid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ /* fork a child to clean up session buses when we are killed */
+ if (pipe (pipe_fds) != 0)
+ {
+ g_warning ("pipe() failed: %m");
+ g_assert_not_reached ();
+ }
+ switch (fork ())
+ {
+ case -1:
+ g_warning ("fork() failed: %m");
+ g_assert_not_reached ();
+ break;
+
+ case 0:
+ /* child */
+ close (pipe_fds[1]);
+ watch_parent (pipe_fds[0]);
+ break;
+
+ default:
+ /* parent */
+ close (pipe_fds[0]);
+ break;
+ }
+
+ //atexit (cleanup_session_buses);
+ /* TODO: need to handle the cases where we crash */
+ }
+ else
+ {
+ /* check if we already have a bus running for this address */
+ if (g_hash_table_lookup (session_bus_address_to_pid, given_address) != NULL)
+ {
+ g_warning ("Already have a bus instance for the given address %s", given_address);
+ goto out;
+ }
+ }
+
+ if (!g_spawn_async_with_pipes (NULL,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ &pid,
+ NULL,
+ &stdout_fd,
+ NULL,
+ &error))
+ {
+ g_warning ("Error spawning dbus-daemon: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ memset (buf, '\0', sizeof buf);
+ again:
+ bytes_read = read (stdout_fd, buf, sizeof buf);
+ if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+ goto again;
+ close (stdout_fd);
+
+ if (bytes_read == 0 || bytes_read == sizeof buf)
+ {
+ g_warning ("Error reading address from dbus daemon, %d bytes read", (gint) bytes_read);
+ kill (SIGTERM, pid);
+ goto out;
+ }
+
+ address = g_strdup (buf);
+ g_strstrip (address);
+
+ /* write the pid to the child so it can kill it when we die */
+ g_snprintf (buf, sizeof buf, "add %d\n", (guint) pid);
+ write (pipe_fds[1], buf, strlen (buf));
+
+ /* start dbus-monitor */
+ if (g_getenv ("G_DBUS_MONITOR") != NULL)
+ {
+ g_spawn_command_line_async ("dbus-monitor --session", NULL);
+ usleep (500 * 1000);
+ }
+
+ g_hash_table_insert (session_bus_address_to_pid, address, GUINT_TO_POINTER (pid));
+
+ out:
+ if (config_file_fd > 0)
+ {
+ if (close (config_file_fd) != 0)
+ {
+ g_warning ("Error closing fd for config file %s: %m", config_file_name);
+ }
+ g_assert (config_file_name != NULL);
+ if (unlink (config_file_name) != 0)
+ {
+ g_warning ("Error unlinking config file %s: %m", config_file_name);
+ }
+ }
+ g_free (argv[2]);
+ g_free (config_file_name);
+ return address;
+}
+
+void
+session_bus_down_with_address (const gchar *address)
+{
+ gpointer value;
+ GPid pid;
+ gchar buf[512];
+
+ g_assert (address != NULL);
+ g_assert (session_bus_address_to_pid != NULL);
+
+ value = g_hash_table_lookup (session_bus_address_to_pid, address);
+ g_assert (value != NULL);
+
+ pid = GPOINTER_TO_UINT (g_hash_table_lookup (session_bus_address_to_pid, address));
+
+ kill (pid, SIGTERM);
+
+ /* write the pid to the child so it won't kill it when we die */
+ g_snprintf (buf, sizeof buf, "remove %d\n", (guint) pid);
+ write (pipe_fds[1], buf, strlen (buf));
+
+ g_hash_table_remove (session_bus_address_to_pid, address);
+}
+
+static gchar *temporary_address = NULL;
+static gchar *temporary_address_used_by_bus = NULL;
+
+const gchar *
+session_bus_get_temporary_address (void)
+{
+ if (temporary_address == NULL)
+ {
+ /* TODO: maybe use a more random name etc etc */
+ temporary_address = g_strdup_printf ("unix:path=/tmp/g-dbus-tests-pid-%d", getpid ());
+ }
+
+ return temporary_address;
+}
+
+const gchar *
+session_bus_up (void)
+{
+ if (temporary_address_used_by_bus != NULL)
+ {
+ g_warning ("There is already a session bus up");
+ goto out;
+ }
+
+ temporary_address_used_by_bus = g_strdup (session_bus_up_with_address (session_bus_get_temporary_address ()));
+
+ out:
+ return temporary_address_used_by_bus;
+}
+
+void
+session_bus_down (void)
+{
+ if (temporary_address_used_by_bus == NULL)
+ {
+ g_warning ("There is not a session bus up");
+ }
+ else
+ {
+ session_bus_down_with_address (temporary_address_used_by_bus);
+ g_free (temporary_address_used_by_bus);
+ temporary_address_used_by_bus = NULL;
+ }
+}
diff --git a/gio/tests/gdbus-sessionbus.h b/gio/tests/gdbus-sessionbus.h
new file mode 100644
index 000000000..e107e0664
--- /dev/null
+++ b/gio/tests/gdbus-sessionbus.h
@@ -0,0 +1,38 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __SESSION_BUS_H__
+#define __SESSION_BUS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+const gchar *session_bus_up_with_address (const gchar *given_address);
+void session_bus_down_with_address (const gchar *address);
+const gchar *session_bus_get_temporary_address (void);
+const gchar *session_bus_up (void);
+void session_bus_down (void);
+
+G_END_DECLS
+
+#endif /* __SESSION_BUS_H__ */
diff --git a/gio/tests/gdbus-tests.c b/gio/tests/gdbus-tests.c
new file mode 100644
index 000000000..b8ac8490d
--- /dev/null
+++ b/gio/tests/gdbus-tests.c
@@ -0,0 +1,218 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+
+#include "gdbus-tests.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GMainLoop *loop;
+ gboolean timed_out;
+} PropertyNotifyData;
+
+static void
+on_property_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ PropertyNotifyData *data = user_data;
+ g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_property_notify_timeout (gpointer user_data)
+{
+ PropertyNotifyData *data = user_data;
+ data->timed_out = TRUE;
+ g_main_loop_quit (data->loop);
+ return TRUE;
+}
+
+gboolean
+_g_assert_property_notify_run (gpointer object,
+ const gchar *property_name)
+{
+ gchar *s;
+ gulong handler_id;
+ guint timeout_id;
+ PropertyNotifyData data;
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.timed_out = FALSE;
+ s = g_strdup_printf ("notify::%s", property_name);
+ handler_id = g_signal_connect (object,
+ s,
+ G_CALLBACK (on_property_notify),
+ &data);
+ g_free (s);
+ timeout_id = g_timeout_add (5 * 1000,
+ on_property_notify_timeout,
+ &data);
+ g_main_loop_run (data.loop);
+ g_signal_handler_disconnect (object, handler_id);
+ g_source_remove (timeout_id);
+ g_main_loop_unref (data.loop);
+
+ return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GMainLoop *loop;
+ gboolean timed_out;
+} SignalReceivedData;
+
+static void
+on_signal_received (gpointer user_data)
+{
+ SignalReceivedData *data = user_data;
+ g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_signal_received_timeout (gpointer user_data)
+{
+ SignalReceivedData *data = user_data;
+ data->timed_out = TRUE;
+ g_main_loop_quit (data->loop);
+ return TRUE;
+}
+
+gboolean
+_g_assert_signal_received_run (gpointer object,
+ const gchar *signal_name)
+{
+ gulong handler_id;
+ guint timeout_id;
+ SignalReceivedData data;
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.timed_out = FALSE;
+ handler_id = g_signal_connect_swapped (object,
+ signal_name,
+ G_CALLBACK (on_signal_received),
+ &data);
+ timeout_id = g_timeout_add (5 * 1000,
+ on_signal_received_timeout,
+ &data);
+ g_main_loop_run (data.loop);
+ g_signal_handler_disconnect (object, handler_id);
+ g_source_remove (timeout_id);
+ g_main_loop_unref (data.loop);
+
+ return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusConnection *
+_g_bus_get_priv (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *address;
+ GDBusConnection *ret;
+
+ ret = NULL;
+
+ address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error);
+ if (address == NULL)
+ goto out;
+
+ ret = g_dbus_connection_new_for_address_sync (address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ cancellable,
+ error);
+ g_free (address);
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GMainLoop *loop;
+ gboolean timed_out;
+} WaitSingleRefData;
+
+static gboolean
+on_wait_single_ref_timeout (gpointer user_data)
+{
+ WaitSingleRefData *data = user_data;
+ data->timed_out = TRUE;
+ g_main_loop_quit (data->loop);
+ return TRUE;
+}
+
+static void
+on_wait_for_single_ref_toggled (gpointer user_data,
+ GObject *object,
+ gboolean is_last_ref)
+{
+ WaitSingleRefData *data = user_data;
+ g_main_loop_quit (data->loop);
+}
+
+gboolean
+_g_object_wait_for_single_ref_do (gpointer object)
+{
+ WaitSingleRefData data;
+ guint timeout_id;
+
+ data.timed_out = FALSE;
+
+ if (G_OBJECT (object)->ref_count == 1)
+ goto out;
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ timeout_id = g_timeout_add (5 * 1000,
+ on_wait_single_ref_timeout,
+ &data);
+
+ g_object_add_toggle_ref (G_OBJECT (object),
+ on_wait_for_single_ref_toggled,
+ &data);
+ g_object_unref (object);
+
+ g_main_loop_run (data.loop);
+
+ g_object_ref (object);
+ g_object_remove_toggle_ref (object,
+ on_wait_for_single_ref_toggled,
+ &data);
+
+ g_source_remove (timeout_id);
+ g_main_loop_unref (data.loop);
+ out:
+ return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/tests/gdbus-tests.h b/gio/tests/gdbus-tests.h
new file mode 100644
index 000000000..ffcc57cfb
--- /dev/null
+++ b/gio/tests/gdbus-tests.h
@@ -0,0 +1,146 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __TESTS_H__
+#define __TESTS_H__
+
+#include <gio/gio.h>
+#include "gdbus-sessionbus.h"
+
+G_BEGIN_DECLS
+
+/* TODO: clean up and move to gtestutils.c
+ *
+ * This is needed because libdbus-1 does not give predictable error messages - e.g. you
+ * get a different error message on connecting to a bus if the socket file is there vs
+ * if the socket file is missing.
+ */
+
+#define _g_assert_error_domain(err, dom) do { if (!err || (err)->domain != dom) \
+ g_assertion_message_error (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #err, err, dom, -1); } while (0)
+
+#define _g_assert_property_notify(object, property_name) \
+ do \
+ { \
+ if (!G_IS_OBJECT (object)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Not a GObject instance"); \
+ } \
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), \
+ property_name) == NULL) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Property " property_name " does not " \
+ "exist on object"); \
+ } \
+ if (_g_assert_property_notify_run (object, property_name)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Timed out waiting for notification " \
+ "on property " property_name); \
+ } \
+ } \
+ while (FALSE)
+
+#define _g_assert_signal_received(object, signal_name) \
+ do \
+ { \
+ if (!G_IS_OBJECT (object)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Not a GObject instance"); \
+ } \
+ if (g_signal_lookup (signal_name, \
+ G_TYPE_FROM_INSTANCE (object)) == 0) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Signal `" signal_name "' does not " \
+ "exist on object"); \
+ } \
+ if (_g_assert_signal_received_run (object, signal_name)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Timed out waiting for signal `" \
+ signal_name "'"); \
+ } \
+ } \
+ while (FALSE)
+
+gboolean _g_assert_property_notify_run (gpointer object,
+ const gchar *property_name);
+
+
+gboolean _g_assert_signal_received_run (gpointer object,
+ const gchar *signal_name);
+
+GDBusConnection *_g_bus_get_priv (GBusType bus_type,
+ GCancellable *cancellable,
+ GError **error);
+
+
+#define _g_object_wait_for_single_ref(object) \
+ do \
+ { \
+ if (!G_IS_OBJECT (object)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Not a GObject instance"); \
+ } \
+ if (_g_object_wait_for_single_ref_do (object)) \
+ { \
+ g_assertion_message (G_LOG_DOMAIN, \
+ __FILE__, \
+ __LINE__, \
+ G_STRFUNC, \
+ "Timed out waiting for single ref"); \
+ } \
+ } \
+ while (FALSE)
+
+gboolean _g_object_wait_for_single_ref_do (gpointer object);
+
+G_END_DECLS
+
+#endif /* __TESTS_H__ */
diff --git a/gio/tests/gdbus-testserver.py b/gio/tests/gdbus-testserver.py
new file mode 100755
index 000000000..04d654e71
--- /dev/null
+++ b/gio/tests/gdbus-testserver.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+
+import gobject
+import time
+
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class TestException(dbus.DBusException):
+ _dbus_error_name = 'com.example.TestException'
+
+
+class TestService(dbus.service.Object):
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='s', out_signature='s')
+ def HelloWorld(self, hello_message):
+ if str(hello_message) == 'Yo':
+ raise TestException('Yo is not a proper greeting')
+ else:
+ return "You greeted me with '%s'. Thanks!"%(str(hello_message))
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='ss', out_signature='ss')
+ def DoubleHelloWorld(self, hello1, hello2):
+ return ("You greeted me with '%s'. Thanks!"%(str(hello1)), "Yo dawg, you uttered '%s'. Thanks!"%(str(hello2)))
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='', out_signature='su')
+ def PairReturn(self):
+ return ("foo", 42)
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='ybnqiuxtdsog', out_signature='ybnqiuxtdsog')
+ def TestPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double, val_string, val_objpath, val_signature):
+ return val_byte + 1, not val_boolean, val_int16 + 1, val_uint16 + 1, val_int32 + 1, val_uint32 + 1, val_int64 + 1, val_uint64 + 1, -val_double + 0.123, val_string * 2, val_objpath + "/modified", val_signature * 2
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='ayabanaqaiauaxatad', out_signature='ayabanaqaiauaxatad')
+ def TestArrayOfPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double):
+ return val_byte*2, val_boolean*2, val_int16*2, val_uint16*2, val_int32*2, val_uint32*2, val_int64*2, val_uint64*2, val_double*2
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='asaoag', out_signature='asaoag')
+ def TestArrayOfStringTypes(self, val_string, val_objpath, val_signature):
+ return val_string * 2, val_objpath * 2, val_signature * 2
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}',
+ out_signature = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}')
+ def TestHashTables(self, hyy, hbb, hnn, hqq, hii, huu, hxx, htt, hdd, hss, hoo, hgg):
+
+ ret_hyy = {}
+ for i in hyy:
+ ret_hyy[i*2] = (hyy[i]*3) & 255
+
+ ret_hbb = {}
+ for i in hbb:
+ ret_hbb[i] = True
+
+ ret_hnn = {}
+ for i in hnn:
+ ret_hnn[i*2] = hnn[i]*3
+
+ ret_hqq = {}
+ for i in hqq:
+ ret_hqq[i*2] = hqq[i]*3
+
+ ret_hii = {}
+ for i in hii:
+ ret_hii[i*2] = hii[i]*3
+
+ ret_huu = {}
+ for i in huu:
+ ret_huu[i*2] = huu[i]*3
+
+ ret_hxx = {}
+ for i in hxx:
+ ret_hxx[i + 2] = hxx[i] + 1
+
+ ret_htt = {}
+ for i in htt:
+ ret_htt[i + 2] = htt[i] + 1
+
+ ret_hdd = {}
+ for i in hdd:
+ ret_hdd[i + 2.5] = hdd[i] + 5.0
+
+ ret_hss = {}
+ for i in hss:
+ ret_hss[i + "mod"] = hss[i]*2
+
+ ret_hoo = {}
+ for i in hoo:
+ ret_hoo[i + "/mod"] = hoo[i] + "/mod2"
+
+ ret_hgg = {}
+ for i in hgg:
+ ret_hgg[i + "assgit"] = hgg[i]*2
+
+ return ret_hyy, ret_hbb, ret_hnn, ret_hqq, ret_hii, ret_huu, ret_hxx, ret_htt, ret_hdd, ret_hss, ret_hoo, ret_hgg
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='(ii)(s(ii)aya{ss})', out_signature='(ii)(s(ii)aya{ss})')
+ def TestStructureTypes(self, s1, s2):
+ (x, y) = s1;
+ (desc, (x1, y1), ay, hss) = s2;
+ ret_hss = {}
+ for i in hss:
+ ret_hss[i] = hss[i] + " ... in bed!"
+ return (x + 1, y + 1), (desc + " ... in bed!", (x1 + 2, y1 + 2), ay * 2, ret_hss)
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='vb', out_signature='v')
+ def TestVariant(self, v, modify):
+
+ if modify:
+ if type(v)==dbus.Boolean:
+ ret = False
+ elif type(v)==dbus.Dictionary:
+ ret = {}
+ for i in v:
+ ret[i] = v[i] * 2
+ elif type(v)==dbus.Struct:
+ ret = ["other struct", dbus.Int16(100)]
+ else:
+ ret = v * 2
+ else:
+ ret = v
+ return (type(v))(ret)
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='a(ii)aa(ii)aasaa{ss}aayavaav', out_signature='a(ii)aa(ii)aasaa{ss}aayavaav')
+ def TestComplexArrays(self, aii, aaii, aas, ahashes, aay, av, aav):
+ return aii * 2, aaii * 2, aas * 2, ahashes * 2, aay * 2, av *2, aav * 2
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}',
+ out_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}')
+ def TestComplexHashTables(self, h_str_to_pair, h_str_to_variant, h_str_to_av, h_str_to_aav,
+ h_str_to_array_of_pairs, hash_of_hashes):
+
+ ret_h_str_to_pair = {}
+ for i in h_str_to_pair:
+ ret_h_str_to_pair[i + "_baz"] = h_str_to_pair[i]
+
+ ret_h_str_to_variant = {}
+ for i in h_str_to_variant:
+ ret_h_str_to_variant[i + "_baz"] = h_str_to_variant[i]
+
+ return ret_h_str_to_pair, ret_h_str_to_variant, h_str_to_av, h_str_to_aav, h_str_to_array_of_pairs, hash_of_hashes
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='', out_signature='')
+ def Quit(self):
+ mainloop.quit()
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='sv', out_signature='')
+ def FrobSetProperty(self, prop_name, prop_value):
+ self.frob_props[prop_name] = prop_value
+ message = dbus.lowlevel.SignalMessage("/com/example/TestObject",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged")
+ message.append("com.example.Frob")
+ message.append({prop_name : prop_value})
+ session_bus.send_message(message)
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.signal("com.example.Frob",
+ signature="sov")
+ def TestSignal(self, str1, objpath1, variant1):
+ pass
+
+ @dbus.service.method("com.example.Frob",
+ in_signature='so', out_signature='')
+ def EmitSignal(self, str1, objpath1):
+ self.TestSignal (str1 + " .. in bed!", objpath1 + "/in/bed", "a variant")
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("com.example.Frob", in_signature='i', out_signature='',
+ async_callbacks=('return_cb', 'raise_cb'))
+ def Sleep(self, msec, return_cb, raise_cb):
+ def return_from_async_wait():
+ return_cb()
+ return False
+ gobject.timeout_add(msec, return_from_async_wait)
+
+ # ----------------------------------------------------------------------------------------------------
+
+ @dbus.service.method("org.freedesktop.DBus.Properties",
+ in_signature = 'ss',
+ out_signature = 'v')
+ def Get(self, interface_name, property_name):
+
+ if interface_name == "com.example.Frob":
+ return self.frob_props[property_name]
+ else:
+ raise TestException("No such interface " + interface_name)
+
+ @dbus.service.method("org.freedesktop.DBus.Properties",
+ in_signature = 's',
+ out_signature = 'a{sv}')
+ def GetAll(self, interface_name):
+ if interface_name == "com.example.Frob":
+ return self.frob_props
+ else:
+ raise TestException("No such interface " + interface_name)
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ session_bus = dbus.SessionBus()
+ name = dbus.service.BusName("com.example.TestService", session_bus)
+
+ obj = TestService(session_bus, '/com/example/TestObject')
+
+ #print "Our unique name is %s"%(session_bus.get_unique_name())
+
+ obj.frob_props = {}
+ obj.frob_props["y"] = dbus.Byte(1)
+ obj.frob_props["b"] = dbus.Boolean(True)
+ obj.frob_props["n"] = dbus.Int16(2)
+ obj.frob_props["q"] = dbus.UInt16(3)
+ obj.frob_props["i"] = dbus.Int32(4)
+ obj.frob_props["u"] = dbus.UInt32(5)
+ obj.frob_props["x"] = dbus.Int64(6)
+ obj.frob_props["t"] = dbus.UInt64(7)
+ obj.frob_props["d"] = dbus.Double(7.5)
+ obj.frob_props["s"] = dbus.String("a string")
+ obj.frob_props["o"] = dbus.ObjectPath("/some/path")
+ obj.frob_props["ay"] = [dbus.Byte(1), dbus.Byte(11)]
+ obj.frob_props["ab"] = [dbus.Boolean(True), dbus.Boolean(False)]
+ obj.frob_props["an"] = [dbus.Int16(2), dbus.Int16(12)]
+ obj.frob_props["aq"] = [dbus.UInt16(3), dbus.UInt16(13)]
+ obj.frob_props["ai"] = [dbus.Int32(4), dbus.Int32(14)]
+ obj.frob_props["au"] = [dbus.UInt32(5), dbus.UInt32(15)]
+ obj.frob_props["ax"] = [dbus.Int64(6), dbus.Int64(16)]
+ obj.frob_props["at"] = [dbus.UInt64(7), dbus.UInt64(17)]
+ obj.frob_props["ad"] = [dbus.Double(7.5), dbus.Double(17.5)]
+ obj.frob_props["as"] = [dbus.String("a string"), dbus.String("another string")]
+ obj.frob_props["ao"] = [dbus.ObjectPath("/some/path"), dbus.ObjectPath("/another/path")]
+ obj.frob_props["foo"] = "a frobbed string"
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
diff --git a/gio/tests/gdbus-threading.c b/gio/tests/gdbus-threading.c
new file mode 100644
index 000000000..0747907df
--- /dev/null
+++ b/gio/tests/gdbus-threading.c
@@ -0,0 +1,532 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a global connection */
+static GDBusConnection *c = NULL;
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Ensure that signal and method replies are delivered in the right thread */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+ GThread *thread;
+ GMainLoop *thread_loop;
+ guint signal_count;
+} DeliveryData;
+
+static void
+msg_cb_expect_success (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeliveryData *data = user_data;
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_variant_unref (result);
+
+ g_assert (g_thread_self () == data->thread);
+
+ g_main_loop_quit (data->thread_loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (GDBusConnection *connection,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeliveryData *data = user_data;
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_connection_invoke_method_finish (connection,
+ res,
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_assert (!g_dbus_error_is_remote_error (error));
+ g_error_free (error);
+ g_assert (result == NULL);
+
+ g_assert (g_thread_self () == data->thread);
+
+ g_main_loop_quit (data->thread_loop);
+}
+
+static void
+signal_handler (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ DeliveryData *data = user_data;
+
+ g_assert (g_thread_self () == data->thread);
+
+ data->signal_count++;
+
+ g_main_loop_quit (data->thread_loop);
+}
+
+static gpointer
+test_delivery_in_thread_func (gpointer _data)
+{
+ GMainLoop *thread_loop;
+ GMainContext *thread_context;
+ DeliveryData data;
+ GCancellable *ca;
+ guint subscription_id;
+ GDBusConnection *priv_c;
+ GError *error;
+
+ error = NULL;
+
+ thread_context = g_main_context_new ();
+ thread_loop = g_main_loop_new (thread_context, FALSE);
+ g_main_context_push_thread_default (thread_context);
+
+ data.thread = g_thread_self ();
+ data.thread_loop = thread_loop;
+ data.signal_count = 0;
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ /*
+ * Check that we get a reply to the GetId() method call.
+ */
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) msg_cb_expect_success,
+ &data);
+ g_main_loop_run (thread_loop);
+
+ /*
+ * Check that we never actually send a message if the GCancellable
+ * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED
+ * when the actual connection is not up.
+ */
+ ca = g_cancellable_new ();
+ g_cancellable_cancel (ca);
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ ca,
+ (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+ &data);
+ g_main_loop_run (thread_loop);
+ g_object_unref (ca);
+
+ /*
+ * Check that cancellation works when the message is already in flight.
+ */
+ ca = g_cancellable_new ();
+ g_dbus_connection_invoke_method (c,
+ "org.freedesktop.DBus", /* bus_name */
+ "/org/freedesktop/DBus", /* object path */
+ "org.freedesktop.DBus", /* interface name */
+ "GetId", /* method name */
+ NULL,
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ ca,
+ (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+ &data);
+ g_cancellable_cancel (ca);
+ g_main_loop_run (thread_loop);
+ g_object_unref (ca);
+
+ /*
+ * Check that signals are delivered to the correct thread.
+ *
+ * First we subscribe to the signal, then we create a a private
+ * connection. This should cause a NameOwnerChanged message from
+ * the message bus.
+ */
+ subscription_id = g_dbus_connection_signal_subscribe (c,
+ "org.freedesktop.DBus", /* sender */
+ "org.freedesktop.DBus", /* interface */
+ "NameOwnerChanged", /* member */
+ "/org/freedesktop/DBus", /* path */
+ NULL,
+ signal_handler,
+ &data,
+ NULL);
+ g_assert (subscription_id != 0);
+ g_assert (data.signal_count == 0);
+
+ priv_c = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (priv_c != NULL);
+
+ g_main_loop_run (thread_loop);
+ g_assert (data.signal_count == 1);
+
+ g_object_unref (priv_c);
+
+ g_dbus_connection_signal_unsubscribe (c, subscription_id);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ g_main_context_pop_thread_default (thread_context);
+ g_main_loop_unref (thread_loop);
+ g_main_context_unref (thread_context);
+
+ g_main_loop_quit (loop);
+
+ return NULL;
+}
+
+static void
+test_delivery_in_thread (void)
+{
+ GError *error;
+ GThread *thread;
+
+ error = NULL;
+ thread = g_thread_create (test_delivery_in_thread_func,
+ NULL,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+ g_assert (thread != NULL);
+
+ /* run the event loop - it is needed to dispatch D-Bus messages */
+ g_main_loop_run (loop);
+
+ g_thread_join (thread);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+ GDBusProxy *proxy;
+ gint msec;
+ guint num;
+ gboolean async;
+
+ GMainLoop *thread_loop;
+ GThread *thread;
+
+ gboolean done;
+} SyncThreadData;
+
+static void
+sleep_cb (GDBusProxy *proxy,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SyncThreadData *data = user_data;
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ result = g_dbus_proxy_invoke_method_finish (proxy,
+ res,
+ &error);
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+
+ g_assert (data->thread == g_thread_self ());
+
+ g_main_loop_quit (data->thread_loop);
+
+ //g_debug ("async cb (%p)", g_thread_self ());
+}
+
+static gpointer
+test_sleep_in_thread_func (gpointer _data)
+{
+ SyncThreadData *data = _data;
+ GMainContext *thread_context;
+ guint n;
+
+ thread_context = g_main_context_new ();
+ data->thread_loop = g_main_loop_new (thread_context, FALSE);
+ g_main_context_push_thread_default (thread_context);
+
+ data->thread = g_thread_self ();
+
+ for (n = 0; n < data->num; n++)
+ {
+ if (data->async)
+ {
+ //g_debug ("invoking async (%p)", g_thread_self ());
+ g_dbus_proxy_invoke_method (data->proxy,
+ "Sleep",
+ g_variant_new ("(i)", data->msec),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) sleep_cb,
+ data);
+ g_main_loop_run (data->thread_loop);
+ g_print ("A");
+ //g_debug ("done invoking async (%p)", g_thread_self ());
+ }
+ else
+ {
+ GError *error;
+ GVariant *result;
+
+ error = NULL;
+ //g_debug ("invoking sync (%p)", g_thread_self ());
+ result = g_dbus_proxy_invoke_method_sync (data->proxy,
+ "Sleep",
+ g_variant_new ("(i)", data->msec),
+ G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_print ("S");
+ //g_debug ("done invoking sync (%p)", g_thread_self ());
+ g_assert_no_error (error);
+ g_assert (result != NULL);
+ g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+ g_variant_unref (result);
+ }
+ }
+
+ g_main_context_pop_thread_default (thread_context);
+ g_main_loop_unref (data->thread_loop);
+ g_main_context_unref (thread_context);
+
+ data->done = TRUE;
+ g_main_loop_quit (loop);
+
+ return NULL;
+}
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ GDBusProxy *proxy,
+ gpointer user_data)
+{
+ guint n;
+
+ /*
+ * Check that multiple threads can do calls without interferring with
+ * each other. We do this by creating three threads that call the
+ * Sleep() method on the server (which handles it asynchronously, e.g.
+ * it won't block other requests) with different sleep durations and
+ * a number of times. We do this so each set of calls add up to 4000
+ * milliseconds.
+ *
+ * We run this test twice - first with async calls in each thread, then
+ * again with sync calls
+ */
+
+ for (n = 0; n < 2; n++)
+ {
+ gboolean do_async;
+ GThread *thread1;
+ GThread *thread2;
+ GThread *thread3;
+ SyncThreadData data1;
+ SyncThreadData data2;
+ SyncThreadData data3;
+ GError *error;
+ GTimeVal start_time;
+ GTimeVal end_time;
+ guint elapsed_msec;
+
+ error = NULL;
+ do_async = (n == 0);
+
+ g_get_current_time (&start_time);
+
+ data1.proxy = proxy;
+ data1.msec = 40;
+ data1.num = 100;
+ data1.async = do_async;
+ data1.done = FALSE;
+ thread1 = g_thread_create (test_sleep_in_thread_func,
+ &data1,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+ g_assert (thread1 != NULL);
+
+ data2.proxy = proxy;
+ data2.msec = 20;
+ data2.num = 200;
+ data2.async = do_async;
+ data2.done = FALSE;
+ thread2 = g_thread_create (test_sleep_in_thread_func,
+ &data2,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+ g_assert (thread2 != NULL);
+
+ data3.proxy = proxy;
+ data3.msec = 100;
+ data3.num = 40;
+ data3.async = do_async;
+ data3.done = FALSE;
+ thread3 = g_thread_create (test_sleep_in_thread_func,
+ &data3,
+ TRUE,
+ &error);
+ g_assert_no_error (error);
+ g_assert (thread3 != NULL);
+
+ /* we handle messages in the main loop - threads will quit it when they are done */
+ while (!(data1.done && data2.done && data3.done))
+ g_main_loop_run (loop);
+
+ g_thread_join (thread1);
+ g_thread_join (thread2);
+ g_thread_join (thread3);
+
+ g_get_current_time (&end_time);
+
+ elapsed_msec = ((end_time.tv_sec * G_USEC_PER_SEC + end_time.tv_usec) -
+ (start_time.tv_sec * G_USEC_PER_SEC + start_time.tv_usec)) / 1000;
+
+ //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec);
+
+ /* elapsed_msec should be 4000 msec + change for overhead */
+ g_assert_cmpint (elapsed_msec, >=, 4000);
+ g_assert_cmpint (elapsed_msec, <, 5000);
+
+ g_print (" ");
+ }
+
+ g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+test_method_calls_in_thread (void)
+{
+ guint watcher_id;
+
+ watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+ "com.example.TestService",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ "/com/example/TestObject",
+ "com.example.Frob",
+ G_TYPE_DBUS_PROXY,
+ G_DBUS_PROXY_FLAGS_NONE,
+ on_proxy_appeared,
+ on_proxy_vanished,
+ NULL,
+ NULL);
+
+ g_main_loop_run (loop);
+
+ g_bus_unwatch_proxy (watcher_id);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc,
+ char *argv[])
+{
+ GError *error;
+ gint ret;
+
+ g_type_init ();
+ g_thread_init (NULL);
+ g_test_init (&argc, &argv, NULL);
+
+ /* all the tests rely on a shared main loop */
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* all the tests use a session bus with a well-known address that we can bring up and down
+ * using session_bus_up() and session_bus_down().
+ */
+ g_unsetenv ("DISPLAY");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+ session_bus_up ();
+
+ /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+ * until one can connect to the bus but that's not how things work right now
+ */
+ usleep (500 * 1000);
+
+ /* this is safe; testserver will exit once the bus goes away */
+ g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+ /* wait for the service to come up */
+ usleep (500 * 1000);
+
+ /* Create the connection in the main thread */
+ error = NULL;
+ c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (c != NULL);
+
+ g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread);
+ g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread);
+
+ ret = g_test_run();
+
+ g_object_unref (c);
+
+ /* tear down bus */
+ session_bus_down ();
+
+ return ret;
+}