summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2018-09-03 10:44:33 +0200
committerMilan Crha <mcrha@redhat.com>2018-09-03 10:44:33 +0200
commit4565d18a8d812c65d0a630fb99ee13b39144317d (patch)
tree64b04757330bbb539d4040fb28572f548343c4c8
parent6022b2b7816fea84919eef5993363f636bf734dc (diff)
downloadevolution-data-server-4565d18a8d812c65d0a630fb99ee13b39144317d.tar.gz
Add an optionally built evolution-dbus-session tool
The tool runs an isolated D-Bus session, but it also passes D-Bus messages between the "parent" D-Bus session and the isolated D-Bus session. It can be used for example by Flatpak applications to run an isolated D-Bus session for recent evolution-data-server D-Bus services, while still being able to talk to requested D-Bus interfaces from the "parent" D-Bus session. Its build can be enabled with -DENABLE_DBUS_SESSION_TOOL=ON CMake command argument.
-rw-r--r--CMakeLists.txt7
-rw-r--r--src/tools/CMakeLists.txt4
-rw-r--r--src/tools/evolution-dbus-session/CMakeLists.txt31
-rw-r--r--src/tools/evolution-dbus-session/evolution-dbus-session.c757
4 files changed, 799 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88cb6e4de..f52674317 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,7 @@ set(PROJECT_DISTCONFIGURE_PARAMS
-DENABLE_VALA_BINDINGS=ON
-DENABLE_INSTALLED_TESTS=ON
-DENABLE_GTK_DOC=ON
+ -DENABLE_DBUS_SESSION_TOOL=ON
-DWITH_PRIVATE_DOCS=ON
)
@@ -992,6 +993,12 @@ if(ENABLE_VALA_BINDINGS)
endif(ENABLE_VALA_BINDINGS)
+# ******************************
+# D-Bus session tool, a Flatpak helper
+# ******************************
+
+add_printable_option(ENABLE_DBUS_SESSION_TOOL "Build evolution-dbus-session tool" OFF)
+
# Generate the ${PROJECT_NAME}-config.h file
CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-config.h)
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index e13c25f4f..796e02b6f 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -1,2 +1,6 @@
add_subdirectory(addressbook-export)
add_subdirectory(list-sources)
+
+if(ENABLE_DBUS_SESSION_TOOL)
+ add_subdirectory(evolution-dbus-session)
+endif(ENABLE_DBUS_SESSION_TOOL)
diff --git a/src/tools/evolution-dbus-session/CMakeLists.txt b/src/tools/evolution-dbus-session/CMakeLists.txt
new file mode 100644
index 000000000..2f0635893
--- /dev/null
+++ b/src/tools/evolution-dbus-session/CMakeLists.txt
@@ -0,0 +1,31 @@
+set(SOURCES
+ evolution-dbus-session.c
+)
+
+add_executable(evolution-dbus-session
+ ${SOURCES}
+)
+
+target_compile_definitions(evolution-dbus-session PRIVATE
+ -DG_LOG_DOMAIN=\"evolution-dbus-session\"
+)
+
+target_compile_options(edataserver PUBLIC
+ ${GNOME_PLATFORM_CFLAGS}
+)
+
+target_include_directories(evolution-dbus-session PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${GNOME_PLATFORM_INCLUDE_DIRS}
+)
+
+target_link_libraries(evolution-dbus-session
+ ${GNOME_PLATFORM_LDFLAGS}
+)
+
+install(TARGETS evolution-dbus-session
+ DESTINATION ${privlibexecdir}
+)
diff --git a/src/tools/evolution-dbus-session/evolution-dbus-session.c b/src/tools/evolution-dbus-session/evolution-dbus-session.c
new file mode 100644
index 000000000..6c23594d1
--- /dev/null
+++ b/src/tools/evolution-dbus-session/evolution-dbus-session.c
@@ -0,0 +1,757 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
+ *
+ * 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.
+ *
+ * This program 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 program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define DBUS_CALL_TIMEOUT -1 /* as set by D-Bus settings */
+
+static void
+object_manager_object_added_cb (GDBusObjectManager *manager,
+ GDBusObject *object,
+ gpointer user_data);
+static void
+object_manager_object_removed_cb (GDBusObjectManager *manager,
+ GDBusObject *object,
+ gpointer user_data);
+
+typedef struct _ProxyData {
+ gchar *iface_name;
+ gchar *path;
+
+ /* The original D-Bus session part */
+ GDBusProxy *proxy;
+ GDBusObjectManager *object_manager;
+ GSList *sigids; /* GUINT_TO_POINTER(sigid) */
+
+ /* The new D-Bus session part */
+ GDBusConnection *regs_connection;
+ guint ownid;
+ GSList *regids; /* GUINT_TO_POINTER(regid) */
+ GSList *object_manager_pds; /* ProxyData *, when object_manager is not NULL */
+} ProxyData;
+
+static void
+proxy_data_free (gpointer ptr)
+{
+ ProxyData *pd = ptr;
+
+ if (pd) {
+ if (pd->object_manager)
+ g_signal_handlers_disconnect_by_data (pd->object_manager, pd);
+
+ g_slist_free_full (pd->object_manager_pds, proxy_data_free);
+
+ if (pd->regs_connection && !g_dbus_connection_is_closed (pd->regs_connection)) {
+ GSList *link;
+
+ for (link = pd->regids; link; link = g_slist_next (link)) {
+ guint regid = GPOINTER_TO_UINT (link->data);
+
+ g_dbus_connection_unregister_object (pd->regs_connection, regid);
+ }
+
+ if (pd->object_manager) {
+ GList *objects, *llink;
+
+ objects = g_dbus_object_manager_get_objects (pd->object_manager);
+
+ for (llink = objects; llink; llink = g_list_next (llink)) {
+ GDBusObject *object = llink->data;
+
+ object_manager_object_removed_cb (pd->object_manager, object, pd);
+ }
+
+ g_list_free_full (objects, g_object_unref);
+ }
+ }
+
+ if (pd->ownid) {
+ g_bus_unown_name (pd->ownid);
+ pd->ownid = 0;
+ }
+
+ if (pd->proxy && pd->sigids) {
+ GDBusConnection *connection;
+
+ connection = g_dbus_proxy_get_connection (pd->proxy);
+
+ if (connection && !g_dbus_connection_is_closed (connection)) {
+ GSList *link;
+
+ for (link = pd->sigids; link; link = g_slist_next (link)) {
+ guint sigid = GPOINTER_TO_UINT (link->data);
+
+ g_dbus_connection_signal_unsubscribe (connection, sigid);
+ }
+ }
+ }
+
+ g_clear_object (&pd->proxy);
+ g_clear_object (&pd->object_manager);
+ g_clear_object (&pd->regs_connection);
+ g_slist_free (pd->regids);
+ g_slist_free (pd->sigids);
+ g_free (pd->iface_name);
+ g_free (pd->path);
+ g_free (pd);
+ }
+}
+
+/* encoded_string :== iface_name + ":" + path */
+static ProxyData *
+proxy_data_new (const gchar *encoded_string)
+{
+ ProxyData *pd;
+ gchar **strv;
+ GError *error = NULL;
+
+ strv = g_strsplit (encoded_string, ":", -1);
+ g_return_val_if_fail (strv != NULL, NULL);
+ if (!strv[0] || !strv[1] || strv[2]) {
+ g_warning ("Unexpected proxy data string format: '%s'", encoded_string);
+ g_strfreev (strv);
+ return NULL;
+ }
+
+ pd = g_new0 (ProxyData, 1);
+ pd->iface_name = g_strdup (strv[0]); /* like "org.freedesktop.portal.Desktop" */
+ pd->path = g_strdup (strv[1]); /* like "/org/freedesktop/portal/desktop" */
+ pd->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ pd->iface_name, pd->path, pd->iface_name, NULL, &error);
+
+ if (!pd->proxy) {
+ g_warning ("Failed to open proxy for interface '%s' at path '%s': %s", pd->iface_name, pd->path, error ? error->message : "Unknown error");
+ proxy_data_free (pd);
+ pd = NULL;
+ }
+
+ g_clear_error (&error);
+ g_strfreev (strv);
+
+ return pd;
+}
+
+static void
+handle_method_call_cb (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+ GVariant *result;
+ GError *error = NULL;
+
+ g_return_if_fail (pd != NULL);
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (pd->proxy),
+ g_dbus_proxy_get_name (pd->proxy),
+ g_dbus_proxy_get_object_path (pd->proxy),
+ interface_name,
+ method_name,
+ parameters,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_CALL_TIMEOUT,
+ NULL, &error);
+
+ if (result) {
+ g_dbus_method_invocation_return_value (invocation, result);
+ g_variant_unref (result);
+ } else {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ }
+
+ g_clear_error (&error);
+}
+
+static GVariant *
+handle_get_property_cb (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+
+ g_return_val_if_fail (pd != NULL, NULL);
+
+ return g_dbus_connection_call_sync (g_dbus_proxy_get_connection (pd->proxy),
+ g_dbus_proxy_get_name (pd->proxy),
+ g_dbus_proxy_get_object_path (pd->proxy),
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", interface_name, property_name),
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_CALL_TIMEOUT,
+ NULL, error);
+}
+
+static gboolean
+handle_set_property_cb (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+ GVariant *result;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (pd != NULL, FALSE);
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (pd->proxy),
+ g_dbus_proxy_get_name (pd->proxy),
+ g_dbus_proxy_get_object_path (pd->proxy),
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new ("(ssv)", interface_name, property_name, value),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_CALL_TIMEOUT,
+ NULL, &local_error);
+
+ if (result)
+ g_variant_unref (result);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return local_error != NULL;
+}
+
+static void
+handle_signal_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+ GError *error = NULL;
+
+ g_return_if_fail (pd != NULL);
+
+ if (!g_dbus_connection_emit_signal (pd->regs_connection, NULL, g_dbus_proxy_get_object_path (pd->proxy),
+ interface_name, signal_name, parameters, &error)) {
+ g_clear_error (&error);
+ }
+}
+
+static void
+proxy_data_reg_interfaces (ProxyData *pd,
+ GDBusConnection *connection)
+{
+ const GDBusInterfaceVTable interface_vtable = {
+ handle_method_call_cb,
+ handle_get_property_cb,
+ handle_set_property_cb
+ };
+ GDBusNodeInfo *introspection_data;
+ GVariant *result;
+ const gchar *xml_data;
+ guint regid;
+ gint ii;
+ GError *error = NULL;
+
+ g_return_if_fail (pd != NULL);
+ g_return_if_fail (pd->proxy != NULL);
+ g_return_if_fail (pd->regs_connection == NULL);
+ g_return_if_fail (connection != NULL);
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (pd->proxy),
+ g_dbus_proxy_get_name (pd->proxy),
+ g_dbus_proxy_get_object_path (pd->proxy),
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_CALL_TIMEOUT,
+ NULL, &error);
+
+ if (!result) {
+ g_warning ("Failed to get introspected data for '%s': %s", g_dbus_proxy_get_name (pd->proxy), error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ return;
+ }
+
+ g_variant_get (result, "(&s)", &xml_data);
+ introspection_data = g_dbus_node_info_new_for_xml (xml_data, NULL);
+ g_variant_unref (result);
+
+ if (!introspection_data) {
+ g_warning ("Failed to parse introspected data for '%s': %s", g_dbus_proxy_get_name (pd->proxy), error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ return;
+ }
+
+ pd->regs_connection = g_object_ref (connection);
+
+ for (ii = 0; introspection_data->interfaces && introspection_data->interfaces[ii]; ii++) {
+ GDBusInterfaceInfo *iface_info = introspection_data->interfaces[ii];
+
+ if (!iface_info)
+ continue;
+
+ regid = g_dbus_connection_register_object (pd->regs_connection, g_dbus_proxy_get_object_path (pd->proxy),
+ iface_info,
+ &interface_vtable,
+ pd,
+ NULL,
+ &error);
+
+ if (regid) {
+ pd->regids = g_slist_prepend (pd->regids, GUINT_TO_POINTER (regid));
+
+ if (iface_info->signals) {
+ gint jj;
+
+ for (jj = 0; iface_info->signals[jj]; jj++) {
+ guint sigid;
+
+ sigid = g_dbus_connection_signal_subscribe (g_dbus_proxy_get_connection (pd->proxy), NULL,
+ iface_info->name,
+ iface_info->signals[jj]->name,
+ g_dbus_proxy_get_object_path (pd->proxy),
+ NULL, G_DBUS_SIGNAL_FLAGS_NONE,
+ handle_signal_cb, pd, NULL);
+
+ if (sigid)
+ pd->sigids = g_slist_prepend (pd->sigids, GUINT_TO_POINTER (sigid));
+ }
+ }
+
+ if (g_strcmp0 (iface_info->name, "org.freedesktop.DBus.ObjectManager") == 0) {
+ pd->object_manager = g_dbus_object_manager_client_new_sync (g_dbus_proxy_get_connection (pd->proxy),
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ g_dbus_proxy_get_name (pd->proxy),
+ g_dbus_proxy_get_object_path (pd->proxy),
+ NULL, NULL, NULL, NULL, &error);
+
+ if (pd->object_manager) {
+ GList *objects, *link;
+
+ g_signal_connect (pd->object_manager, "object-added",
+ G_CALLBACK (object_manager_object_added_cb), pd);
+ g_signal_connect (pd->object_manager, "object-removed",
+ G_CALLBACK (object_manager_object_removed_cb), pd);
+
+ objects = g_dbus_object_manager_get_objects (pd->object_manager);
+ for (link = objects; link; link = g_list_next (link)) {
+ GDBusObject *object = link->data;
+
+ object_manager_object_added_cb (pd->object_manager, object, pd);
+ }
+
+ g_list_free_full (objects, g_object_unref);
+ } else {
+ g_clear_error (&error);
+ }
+ }
+ } else {
+ g_clear_error (&error);
+ }
+ }
+
+ g_dbus_node_info_unref (introspection_data);
+}
+
+static void
+bus_name_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+
+ g_return_if_fail (pd != NULL);
+
+ proxy_data_reg_interfaces (pd, connection);
+}
+
+static void
+bus_name_lost_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ ProxyData *pd = user_data;
+
+ g_return_if_fail (pd != NULL);
+}
+
+static void
+object_manager_object_added_cb (GDBusObjectManager *manager,
+ GDBusObject *object,
+ gpointer user_data)
+{
+ ProxyData *parent_pd = user_data, *child_pd;
+ gchar *encoded_string;
+
+ encoded_string = g_strconcat (parent_pd->iface_name, ":", g_dbus_object_get_object_path (object), NULL);
+ child_pd = proxy_data_new (encoded_string);
+ g_free (encoded_string);
+
+ if (child_pd) {
+ proxy_data_reg_interfaces (child_pd, parent_pd->regs_connection);
+ parent_pd->object_manager_pds = g_slist_prepend (parent_pd->object_manager_pds, child_pd);
+ }
+}
+
+static void
+object_manager_object_removed_cb (GDBusObjectManager *manager,
+ GDBusObject *object,
+ gpointer user_data)
+{
+ ProxyData *parent_pd = user_data;
+ GSList *link;
+ const gchar *obj_path;
+
+ obj_path = g_dbus_object_get_object_path (object);
+ for (link = parent_pd->object_manager_pds; link; link = g_slist_next (link)) {
+ ProxyData *child_pd = link->data;
+
+ if (child_pd && g_strcmp0 (child_pd->path, obj_path) == 0) {
+ proxy_data_free (child_pd);
+ parent_pd->object_manager_pds = g_slist_remove (parent_pd->object_manager_pds, child_pd);
+ break;
+ }
+ }
+}
+
+static gpointer
+main_loop_thread (gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+
+ g_main_loop_run (main_loop);
+
+ return NULL;
+}
+
+/* Adapted from glib's gtestdbus.c */
+static gchar *
+write_config_file (const GSList *service_dirs)
+{
+ GString *contents;
+ gint fd;
+ GSList *link;
+ gchar *path = NULL;
+ GError *error = NULL;
+
+ fd = g_file_open_tmp ("evolution-bus-tunnel-XXXXXX", &path, &error);
+ if (fd <= 0) {
+ g_warning ("Failed to create tmp file for D-Bus configuration: %s", error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ contents = g_string_new (NULL);
+ g_string_append (contents,
+ "<busconfig>\n"
+ " <type>session</type>\n"
+ " <listen>unix:tmpdir=/tmp</listen>\n");
+
+ for (link = (GSList *) service_dirs; link; link = g_slist_next (link)) {
+ const gchar *dir_path = link->data;
+
+ g_string_append_printf (contents," <servicedir>%s</servicedir>\n", dir_path);
+ }
+
+ g_string_append (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"
+ "</busconfig>\n");
+
+ close (fd);
+ if (!g_file_set_contents (path, contents->str, contents->len, &error)) {
+ g_warning ("Failed to write file '%s': %s", path, error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ g_free (path);
+ return NULL;
+ }
+
+ g_string_free (contents, TRUE);
+
+ return path;
+}
+
+/* Adapted from glib's gtestdbus.c */
+static gboolean
+start_daemon (const GSList *service_dirs,
+ GPid *pbus_pid,
+ gint *pbus_stdout_fd,
+ gchar **pbus_address)
+{
+ const gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
+ gchar *config_path;
+ gchar *config_arg;
+ GIOChannel *channel;
+ gint stdout_fd2;
+ gsize termpos;
+ GError *error = NULL;
+
+ /* Write config file and set its path in argv */
+ config_path = write_config_file (service_dirs);
+ config_arg = g_strdup_printf ("--config-file=%s", config_path);
+ argv[2] = config_arg;
+
+ /* Spawn dbus-daemon */
+ g_spawn_async_with_pipes (NULL, (gchar **) argv, NULL,
+ G_SPAWN_SEARCH_PATH, NULL, NULL, pbus_pid,
+ NULL, pbus_stdout_fd, NULL, &error);
+ if (error) {
+ g_warning ("Failed to start D-Bus daemon: %s", error->message);
+ g_clear_error (&error);
+ g_free (config_path);
+ g_free (config_arg);
+ return FALSE;
+ }
+
+ /* Read bus address from daemon' stdout. We have to be careful to avoid
+ * closing the FD, as it is passed to any D-Bus service activated processes,
+ * and if we close it, they will get a SIGPIPE and die when they try to write
+ * to their stdout. */
+ stdout_fd2 = dup (*pbus_stdout_fd);
+ g_assert_cmpint (stdout_fd2, >=, 0);
+ channel = g_io_channel_unix_new (stdout_fd2);
+
+ g_io_channel_read_line (channel, pbus_address, NULL, &termpos, &error);
+ if (error) {
+ g_warning ("Failed to read new D-Bus daemon address: %s", error->message);
+ g_io_channel_shutdown (channel, FALSE, NULL);
+ g_io_channel_unref (channel);
+ g_clear_error (&error);
+ g_free (config_path);
+ g_free (config_arg);
+ return FALSE;
+ }
+
+ (*pbus_address)[termpos] = '\0';
+
+ /* Cleanup */
+ g_io_channel_shutdown (channel, FALSE, &error);
+ if (error) {
+ g_debug ("Failed to shutdown io channel: %s", error->message);
+ g_clear_error (&error);
+ }
+ g_io_channel_unref (channel);
+
+ /* Don't use g_file_delete since it calls into gvfs */
+ if (g_unlink (config_path) != 0)
+ g_warning ("Failed to remove config file '%s'", config_path);
+
+ g_free (config_path);
+ g_free (config_arg);
+
+ g_unsetenv ("DBUS_STARTER_ADDRESS");
+ g_unsetenv ("DBUS_STARTER_BUS_TYPE");
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", *pbus_address, TRUE);
+
+ return TRUE;
+}
+
+/* Adapted from glib's gtestdbus.c */
+static void
+stop_daemon (GPid *pbus_pid,
+ gint *pbus_stdout_fd,
+ gchar **pbus_address)
+{
+ kill (*pbus_pid, SIGTERM);
+ g_spawn_close_pid (*pbus_pid);
+ *pbus_pid = 0;
+
+ close (*pbus_stdout_fd);
+ *pbus_stdout_fd = -1;
+
+ g_free (*pbus_address);
+ *pbus_address = NULL;
+}
+
+static void
+print_help (void)
+{
+ printf ("Usage: evolution-dbus-session --exec exec [ --service-dir dir ] [ --iface name:path ]\n");
+ printf (" --help ... shows this help and exits\n");
+ printf (" --exec exec ... a program to run in a dedicated D-Bus session\n");
+ printf (" --service-dir dir ... adds a dir as a service directory for the new D-Bus session\n");
+ printf (" --iface name:path ... adds an interface name and its D-Bus path to proxy into the new D-Bus session\n");
+ printf ("\n");
+ printf (" Both --service-dir and --iface arguments are optional and can be specified multiple times.\n");
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ GPid bus_pid = 0;
+ gint bus_stdout_fd = -1, ii, res = 0;
+ gchar *bus_address = NULL;
+ GSList *service_dirs = NULL;
+ GSList *pds = NULL; /* ProxyData * */
+ const gchar *to_exec = NULL;
+
+ if (argc == 1) {
+ print_help ();
+ return 0;
+ }
+
+ for (ii = 1; ii < argc; ii++) {
+ if (g_str_equal (argv[ii], "--help")) {
+ print_help ();
+ g_slist_free_full (pds, proxy_data_free);
+ g_slist_free (service_dirs);
+ return 0;
+ } else if (g_str_equal (argv[ii], "--exec")) {
+ if (ii + 1 >= argc) {
+ g_printerr ("Missing value for %s\n", argv[ii]);
+ return 1;
+ }
+
+ ii++;
+ to_exec = argv[ii];
+ } else if (g_str_equal (argv[ii], "--service-dir")) {
+ if (ii + 1 >= argc) {
+ g_printerr ("Missing value for %s\n", argv[ii]);
+ return 2;
+ }
+
+ ii++;
+ service_dirs = g_slist_prepend (service_dirs, argv[ii]);
+ } else if (g_str_equal (argv[ii], "--iface")) {
+ ProxyData *pd;
+
+ if (ii + 1 >= argc) {
+ g_printerr ("Missing value for %s\n", argv[ii]);
+ return 3;
+ }
+
+ ii++;
+ pd = proxy_data_new (argv[ii]);
+ if (pd) {
+ pds = g_slist_prepend (pds, pd);
+ }
+ } else {
+ g_printerr ("Unknown argument '%s'\n", argv[ii]);
+ return 4;
+ }
+ }
+
+ if (!pds) {
+ g_printerr ("No --iface defined or cannot connect to any, exiting\n");
+ g_slist_free_full (pds, proxy_data_free);
+ g_slist_free (service_dirs);
+ return 5;
+ }
+
+ if (!to_exec) {
+ g_printerr ("No --exec defined, exiting\n");
+ g_slist_free_full (pds, proxy_data_free);
+ g_slist_free (service_dirs);
+ return 6;
+ }
+
+ /* Preserve order as specified on the command line */
+ pds = g_slist_reverse (pds);
+ service_dirs = g_slist_reverse (service_dirs);
+
+ if (start_daemon (service_dirs, &bus_pid, &bus_stdout_fd, &bus_address)) {
+ GThread *thread;
+ GMainLoop *main_loop;
+ GDBusConnection *connection;
+ GError *error = NULL;
+
+ connection = g_dbus_connection_new_for_address_sync (bus_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, NULL, NULL, &error);
+ if (connection) {
+ GSList *link;
+
+ for (link = pds; link; link = g_slist_next (link)) {
+ ProxyData *pd = link->data;
+
+ if (!pd)
+ continue;
+
+ pd->ownid = g_bus_own_name_on_connection (connection,
+ pd->iface_name,
+ G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
+ bus_name_acquired_cb,
+ bus_name_lost_cb,
+ pd, NULL);
+ }
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+
+ thread = g_thread_new (NULL, main_loop_thread, main_loop);
+
+ if (system (to_exec) == -1)
+ g_warning ("Failed to execute '%s'", to_exec);
+
+ if (!g_dbus_connection_is_closed (connection) &&
+ !g_dbus_connection_close_sync (connection, NULL, &error)) {
+ g_warning ("Failed to close connection: %s", error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ }
+
+ g_slist_free_full (pds, proxy_data_free);
+ pds = NULL;
+
+ stop_daemon (&bus_pid, &bus_stdout_fd, &bus_address);
+ g_main_loop_quit (main_loop);
+ g_thread_join (thread);
+
+ g_main_loop_unref (main_loop);
+ g_clear_object (&connection);
+ } else {
+ g_warning ("Failed to create connection for dedicated server address '%s': %s",
+ bus_address, error ? error->message : "Unknown error");
+ g_clear_error (&error);
+ }
+ } else {
+ res = 7;
+ }
+
+ g_slist_free_full (pds, proxy_data_free);
+ g_slist_free (service_dirs);
+
+ return res;
+}