summaryrefslogtreecommitdiff
path: root/trunk/client/gvfsdaemondbus.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/client/gvfsdaemondbus.c')
-rw-r--r--trunk/client/gvfsdaemondbus.c1023
1 files changed, 1023 insertions, 0 deletions
diff --git a/trunk/client/gvfsdaemondbus.c b/trunk/client/gvfsdaemondbus.c
new file mode 100644
index 00000000..c3df2cf6
--- /dev/null
+++ b/trunk/client/gvfsdaemondbus.c
@@ -0,0 +1,1023 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2006-2007 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: Alexander Larsson <alexl@redhat.com>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <gio/gio.h>
+#include "gvfsdaemondbus.h"
+#include <gvfsdaemonprotocol.h>
+#include <gdaemonvfs.h>
+#include "gdbusutils.h"
+#include "gsysutils.h"
+
+/* Extra vfs-specific data for DBusConnections */
+typedef struct {
+ int extra_fd;
+ int extra_fd_count;
+ char *async_dbus_id;
+
+ /* Only used for async connections */
+ GHashTable *outstanding_fds;
+ GSource *extra_fd_source;
+} VfsConnectionData;
+
+static gint32 vfs_data_slot = -1;
+static GOnce once_init_dbus = G_ONCE_INIT;
+
+static GStaticPrivate local_connections = G_STATIC_PRIVATE_INIT;
+
+/* dbus id -> async connection */
+static GHashTable *async_map = NULL;
+G_LOCK_DEFINE_STATIC(async_map);
+
+/* dbus object path -> dbus message filter */
+static GHashTable *obj_path_map = NULL;
+G_LOCK_DEFINE_STATIC(obj_path_map);
+
+static void setup_async_fd_receive (VfsConnectionData *connection_data);
+static void invalidate_local_connection (const char *dbus_id,
+ GError **error);
+
+
+GQuark
+_g_vfs_error_quark (void)
+{
+ return g_quark_from_static_string ("g-vfs-error-quark");
+}
+
+static gpointer
+vfs_dbus_init (gpointer arg)
+{
+ if (!dbus_connection_allocate_data_slot (&vfs_data_slot))
+ g_error ("Unable to allocate data slot");
+
+ return NULL;
+}
+
+/**************************************************************************
+ * message filters for vfs dbus connections *
+ *************************************************************************/
+
+typedef struct {
+ DBusHandleMessageFunction callback;
+ GObject *data;
+} PathMapEntry;
+
+void
+_g_dbus_register_vfs_filter (const char *obj_path,
+ DBusHandleMessageFunction callback,
+ GObject *data)
+{
+ PathMapEntry * entry;
+
+ G_LOCK (obj_path_map);
+
+ if (obj_path_map == NULL)
+ obj_path_map = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ entry = g_new (PathMapEntry,1 );
+ entry->callback = callback;
+ entry->data = data;
+
+ g_hash_table_insert (obj_path_map, g_strdup (obj_path), entry);
+
+ G_UNLOCK (obj_path_map);
+}
+
+void
+_g_dbus_unregister_vfs_filter (const char *obj_path)
+{
+ G_LOCK (obj_path_map);
+
+ if (obj_path_map)
+ g_hash_table_remove (obj_path_map, obj_path);
+
+ G_UNLOCK (obj_path_map);
+}
+
+static DBusHandlerResult
+vfs_connection_filter (DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ PathMapEntry *entry;
+ DBusHandlerResult res;
+ DBusHandleMessageFunction callback;
+ GObject *data;
+ VfsConnectionData *connection_data;
+ const char *path;
+
+ callback = NULL;
+ data = NULL;
+
+ if (dbus_message_is_signal (message,
+ DBUS_INTERFACE_LOCAL,
+ "Disconnected"))
+ {
+ connection_data = dbus_connection_get_data (connection, vfs_data_slot);
+ if (connection_data->async_dbus_id)
+ {
+ _g_daemon_vfs_invalidate_dbus_id (connection_data->async_dbus_id);
+ G_LOCK (async_map);
+ g_hash_table_remove (async_map, connection_data->async_dbus_id);
+ G_UNLOCK (async_map);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ path = dbus_message_get_path (message);
+ G_LOCK (obj_path_map);
+ if (obj_path_map && path)
+ {
+ entry = g_hash_table_lookup (obj_path_map,
+ path);
+
+ if (entry)
+ {
+ callback = entry->callback;
+ data = g_object_ref (entry->data);
+ }
+ }
+ G_UNLOCK (obj_path_map);
+
+ res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (callback)
+ {
+ res = callback (connection, message, data);
+ g_object_unref (data);
+ }
+
+ return res;
+}
+
+static void
+connection_data_free (gpointer p)
+{
+ VfsConnectionData *data = p;
+
+ if (data->extra_fd != -1)
+ close (data->extra_fd);
+
+ if (data->extra_fd_source)
+ {
+ g_source_destroy (data->extra_fd_source);
+ g_source_unref (data->extra_fd_source);
+ }
+
+ if (data->outstanding_fds)
+ g_hash_table_destroy (data->outstanding_fds);
+
+ g_free (data->async_dbus_id);
+
+ g_free (data);
+}
+
+static void
+vfs_connection_setup (DBusConnection *connection,
+ int extra_fd,
+ gboolean async)
+{
+ VfsConnectionData *connection_data;
+
+ connection_data = g_new0 (VfsConnectionData, 1);
+ connection_data->extra_fd = extra_fd;
+ connection_data->extra_fd_count = 0;
+
+ if (async)
+ setup_async_fd_receive (connection_data);
+
+ if (!dbus_connection_set_data (connection, vfs_data_slot, connection_data, connection_data_free))
+ _g_dbus_oom ();
+
+ if (!dbus_connection_add_filter (connection, vfs_connection_filter, NULL, NULL))
+ _g_dbus_oom ();
+}
+
+/**************************************************************************
+ * Functions to get fds from vfs dbus connections *
+ *************************************************************************/
+
+typedef struct {
+ int fd;
+ GetFdAsyncCallback callback;
+ gpointer callback_data;
+} OutstandingFD;
+
+static void
+outstanding_fd_free (OutstandingFD *outstanding)
+{
+ if (outstanding->fd != -1)
+ close (outstanding->fd);
+
+ g_free (outstanding);
+}
+
+static void
+async_connection_accept_new_fd (VfsConnectionData *data,
+ GIOCondition condition,
+ int fd)
+{
+ int new_fd;
+ int fd_id;
+ OutstandingFD *outstanding_fd;
+
+ if (condition & G_IO_HUP)
+ {
+ close (data->extra_fd);
+ data->extra_fd = -1;
+ g_source_destroy (data->extra_fd_source);
+ g_source_unref (data->extra_fd_source);
+ data->extra_fd_source = NULL;
+ return;
+ }
+
+ fd_id = data->extra_fd_count;
+ new_fd = _g_socket_receive_fd (data->extra_fd);
+ if (new_fd != -1)
+ {
+ data->extra_fd_count++;
+
+ outstanding_fd = g_hash_table_lookup (data->outstanding_fds, GINT_TO_POINTER (fd_id));
+
+ if (outstanding_fd)
+ {
+ outstanding_fd->callback (new_fd, outstanding_fd->callback_data);
+ g_hash_table_remove (data->outstanding_fds, GINT_TO_POINTER (fd_id));
+ }
+ else
+ {
+ outstanding_fd = g_new0 (OutstandingFD, 1);
+ outstanding_fd->fd = new_fd;
+ outstanding_fd->callback = NULL;
+ outstanding_fd->callback_data = NULL;
+ g_hash_table_insert (data->outstanding_fds,
+ GINT_TO_POINTER (fd_id),
+ outstanding_fd);
+ }
+ }
+}
+
+static void
+setup_async_fd_receive (VfsConnectionData *connection_data)
+{
+ connection_data->outstanding_fds =
+ g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)outstanding_fd_free);
+
+
+ connection_data->extra_fd_source =
+ __g_fd_source_new (connection_data->extra_fd, G_IO_IN|G_IO_ERR, NULL);
+ g_source_set_callback (connection_data->extra_fd_source,
+ (GSourceFunc)async_connection_accept_new_fd,
+ connection_data, NULL);
+ g_source_attach (connection_data->extra_fd_source, NULL);
+}
+
+int
+_g_dbus_connection_get_fd_sync (DBusConnection *connection,
+ int fd_id)
+{
+ VfsConnectionData *data;
+ int fd;
+
+ data = dbus_connection_get_data (connection, vfs_data_slot);
+ g_assert (data != NULL);
+
+ /* I don't think we can get reorders here, can we?
+ * Its a sync per-thread connection after all
+ */
+ g_assert (fd_id == data->extra_fd_count);
+
+ fd = _g_socket_receive_fd (data->extra_fd);
+ if (fd != -1)
+ data->extra_fd_count++;
+
+ return fd;
+}
+
+void
+_g_dbus_connection_get_fd_async (DBusConnection *connection,
+ int fd_id,
+ GetFdAsyncCallback callback,
+ gpointer callback_data)
+{
+ VfsConnectionData *data;
+ OutstandingFD *outstanding_fd;
+ int fd;
+
+ data = dbus_connection_get_data (connection, vfs_data_slot);
+ g_assert (data != NULL);
+
+ outstanding_fd = g_hash_table_lookup (data->outstanding_fds, GINT_TO_POINTER (fd_id));
+
+ if (outstanding_fd)
+ {
+ fd = outstanding_fd->fd;
+ outstanding_fd->fd = -1;
+ g_hash_table_remove (data->outstanding_fds, GINT_TO_POINTER (fd_id));
+ callback (fd, callback_data);
+ }
+ else
+ {
+ outstanding_fd = g_new0 (OutstandingFD, 1);
+ outstanding_fd->fd = -1;
+ outstanding_fd->callback = callback;
+ outstanding_fd->callback_data = callback_data;
+ g_hash_table_insert (data->outstanding_fds,
+ GINT_TO_POINTER (fd_id),
+ outstanding_fd);
+ }
+}
+
+/*******************************************************************
+ * Caching of async connections *
+ *******************************************************************/
+
+
+static DBusConnection *
+get_connection_for_async (const char *dbus_id)
+{
+ DBusConnection *connection;
+
+ connection = NULL;
+ G_LOCK (async_map);
+ if (async_map != NULL)
+ connection = g_hash_table_lookup (async_map, dbus_id);
+ if (connection)
+ dbus_connection_ref (connection);
+ G_UNLOCK (async_map);
+
+ return connection;
+}
+
+static void
+close_and_unref_connection (void *data)
+{
+ DBusConnection *connection = data;
+
+ dbus_connection_close (connection);
+ dbus_connection_unref (connection);
+}
+
+static void
+set_connection_for_async (DBusConnection *connection, const char *dbus_id)
+{
+ VfsConnectionData *data;
+
+ G_LOCK (async_map);
+ data = dbus_connection_get_data (connection, vfs_data_slot);
+ data->async_dbus_id = g_strdup (dbus_id);
+
+ if (async_map == NULL)
+ async_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, close_and_unref_connection);
+
+ g_hash_table_insert (async_map, g_strdup (dbus_id), connection);
+ dbus_connection_ref (connection);
+ G_UNLOCK (async_map);
+}
+
+/**************************************************************************
+ * Asynchronous daemon calls *
+ *************************************************************************/
+
+typedef struct {
+ const char *dbus_id;
+
+ DBusMessage *message;
+ DBusConnection *connection;
+ GCancellable *cancellable;
+
+ GVfsAsyncDBusCallback callback;
+ gpointer callback_data;
+
+ GError *io_error;
+ gulong cancelled_tag;
+} AsyncDBusCall;
+
+static void
+async_call_finish (AsyncDBusCall *async_call,
+ DBusMessage *reply)
+{
+ if (async_call->callback)
+ async_call->callback (reply, async_call->connection,
+ async_call->io_error,
+ async_call->callback_data);
+
+ if (async_call->connection)
+ dbus_connection_unref (async_call->connection);
+ dbus_message_unref (async_call->message);
+ if (async_call->cancellable)
+ g_object_unref (async_call->cancellable);
+ if (async_call->io_error)
+ g_error_free (async_call->io_error);
+ g_free (async_call);
+}
+
+static void
+async_dbus_response (DBusMessage *reply,
+ GError *error,
+ gpointer data)
+{
+ AsyncDBusCall *async_call = data;
+
+ if (async_call->cancelled_tag)
+ g_signal_handler_disconnect (async_call->cancellable,
+ async_call->cancelled_tag);
+
+ if (reply == NULL)
+ async_call->io_error = g_error_copy (error);
+
+ async_call_finish (async_call, reply);
+}
+
+typedef struct {
+ DBusConnection *connection;
+ dbus_uint32_t serial;
+} AsyncCallCancelData;
+
+static void
+async_call_cancel_data_free (gpointer _data)
+{
+ AsyncCallCancelData *data = _data;
+
+ dbus_connection_unref (data->connection);
+ g_free (data);
+}
+
+/* Might be called on another thread */
+static void
+async_call_cancelled_cb (GCancellable *cancellable,
+ gpointer _data)
+{
+ AsyncCallCancelData *data = _data;
+ DBusMessage *cancel_message;
+
+ /* Send cancellation message, this just queues it, sending
+ * will happen in mainloop */
+ cancel_message = dbus_message_new_method_call (NULL,
+ G_VFS_DBUS_DAEMON_PATH,
+ G_VFS_DBUS_DAEMON_INTERFACE,
+ G_VFS_DBUS_OP_CANCEL);
+ if (cancel_message != NULL)
+ {
+ if (dbus_message_append_args (cancel_message,
+ DBUS_TYPE_UINT32, &data->serial,
+ DBUS_TYPE_INVALID))
+ dbus_connection_send (data->connection,
+ cancel_message, NULL);
+ dbus_message_unref (cancel_message);
+ }
+}
+
+static void
+async_call_send (AsyncDBusCall *async_call)
+{
+ AsyncCallCancelData *cancel_data;
+
+ if (async_call->cancellable)
+ {
+ cancel_data = g_new0 (AsyncCallCancelData, 1);
+ cancel_data->connection = dbus_connection_ref (async_call->connection);
+ cancel_data->serial = dbus_message_get_serial (async_call->message);
+ async_call->cancelled_tag =
+ g_signal_connect_data (async_call->cancellable, "cancelled",
+ (GCallback)async_call_cancelled_cb,
+ cancel_data,
+ (GClosureNotify)async_call_cancel_data_free,
+ 0);
+ }
+
+ _g_dbus_connection_call_async (async_call->connection,
+ async_call->message,
+ G_VFS_DBUS_TIMEOUT_MSECS,
+ async_dbus_response,
+ async_call);
+}
+
+static void
+async_get_connection_response (DBusMessage *reply,
+ GError *error,
+ void *data)
+{
+ AsyncDBusCall *async_call = data;
+ DBusError derror;
+ char *address1, *address2;
+ int extra_fd;
+ DBusConnection *connection, *existing_connection;
+
+ if (reply == NULL)
+ {
+ async_call->io_error = g_error_copy (error);
+ async_call_finish (async_call, NULL);
+ return;
+ }
+
+ dbus_error_init (&derror);
+ if (!dbus_message_get_args (reply, &derror,
+ DBUS_TYPE_STRING, &address1,
+ DBUS_TYPE_STRING, &address2,
+ DBUS_TYPE_INVALID))
+ {
+ _g_error_from_dbus (&derror, &async_call->io_error);
+ dbus_error_free (&derror);
+ async_call_finish (async_call, NULL);
+ return;
+ }
+
+ /* I don't know of any way to do an async connect */
+ error = NULL;
+ extra_fd = _g_socket_connect (address2, &error);
+ if (extra_fd == -1)
+ {
+ g_set_error (&async_call->io_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Error connecting to daemon: %s"), error->message);
+ g_error_free (error);
+ async_call_finish (async_call, NULL);
+ return;
+ }
+
+ /* Unfortunately dbus doesn't have an async open */
+ dbus_error_init (&derror);
+ connection = dbus_connection_open_private (address1, &derror);
+ if (!connection)
+ {
+ close (extra_fd);
+ dbus_message_unref (reply);
+
+ g_set_error (&async_call->io_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error while getting peer-to-peer dbus connection: %s",
+ derror.message);
+ dbus_error_free (&derror);
+ async_call_finish (async_call, NULL);
+ return;
+ }
+
+ vfs_connection_setup (connection, extra_fd, TRUE);
+
+ /* Maybe we already had a connection? This happens if we requested
+ * the same owner several times in parallel.
+ * If so, just drop this connection and use that.
+ */
+
+ existing_connection = get_connection_for_async (async_call->dbus_id);
+ if (existing_connection != NULL)
+ {
+ async_call->connection = existing_connection;
+ dbus_connection_close (connection);
+ dbus_connection_unref (connection);
+ }
+ else
+ {
+ _g_dbus_connection_integrate_with_main (connection);
+ set_connection_for_async (connection, async_call->dbus_id);
+ async_call->connection = connection;
+ }
+
+ /* Maybe we were canceled while setting up connection, then
+ * avoid doing the operation */
+ if (g_cancellable_set_error_if_cancelled (async_call->cancellable, &async_call->io_error))
+ {
+ async_call_finish (async_call, NULL);
+ return;
+ }
+
+ async_call_send (async_call);
+}
+
+static void
+open_connection_async (AsyncDBusCall *async_call)
+{
+ DBusMessage *get_connection_message;
+
+ get_connection_message = dbus_message_new_method_call (async_call->dbus_id,
+ G_VFS_DBUS_DAEMON_PATH,
+ G_VFS_DBUS_DAEMON_INTERFACE,
+ G_VFS_DBUS_OP_GET_CONNECTION);
+
+ if (get_connection_message == NULL)
+ _g_dbus_oom ();
+
+
+ _g_dbus_connection_call_async (_g_daemon_vfs_get_async_bus (),
+ get_connection_message,
+ G_VFS_DBUS_TIMEOUT_MSECS,
+ async_get_connection_response,
+ async_call);
+
+ dbus_message_unref (get_connection_message);
+}
+
+void
+_g_vfs_daemon_call_async (DBusMessage *message,
+ GVfsAsyncDBusCallback callback,
+ gpointer callback_data,
+ GCancellable *cancellable)
+{
+ AsyncDBusCall *async_call;
+
+ g_once (&once_init_dbus, vfs_dbus_init, NULL);
+
+ async_call = g_new0 (AsyncDBusCall, 1);
+ async_call->dbus_id = dbus_message_get_destination (message);
+ async_call->message = dbus_message_ref (message);
+ if (cancellable)
+ async_call->cancellable = g_object_ref (cancellable);
+ async_call->callback = callback;
+ async_call->callback_data = callback_data;
+
+ async_call->connection = get_connection_for_async (async_call->dbus_id);
+ if (async_call->connection == NULL)
+ open_connection_async (async_call);
+ else
+ async_call_send (async_call);
+}
+
+/**************************************************************************
+ * Synchronous daemon calls *
+ *************************************************************************/
+
+DBusMessage *
+_g_vfs_daemon_call_sync (DBusMessage *message,
+ DBusConnection **connection_out,
+ const char *callback_obj_path,
+ DBusObjectPathMessageFunction callback,
+ gpointer callback_user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DBusConnection *connection;
+ DBusError derror;
+ DBusMessage *reply;
+ DBusPendingCall *pending;
+ int dbus_fd;
+ int cancel_fd;
+ gboolean sent_cancel;
+ DBusMessage *cancel_message;
+ dbus_uint32_t serial;
+ gboolean handle_callbacks;
+ const char *dbus_id = dbus_message_get_destination (message);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ connection = _g_dbus_connection_get_sync (dbus_id, error);
+ if (connection == NULL)
+ return NULL;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ handle_callbacks = FALSE;
+ if (callback_obj_path != NULL && callback != NULL)
+ {
+ struct DBusObjectPathVTable vtable = { NULL, callback };
+ handle_callbacks = dbus_connection_register_object_path (connection,
+ callback_obj_path,
+ &vtable,
+ callback_user_data);
+ }
+
+ reply = NULL;
+ cancel_fd = g_cancellable_get_fd (cancellable);
+ if (cancel_fd != -1 || handle_callbacks)
+ {
+ if (!dbus_connection_send_with_reply (connection, message,
+ &pending,
+ G_VFS_DBUS_TIMEOUT_MSECS))
+ _g_dbus_oom ();
+
+ if (pending == NULL ||
+ !dbus_connection_get_is_connected (connection))
+ {
+ if (pending)
+ dbus_pending_call_unref (pending);
+ invalidate_local_connection (dbus_id, error);
+ goto out;
+ }
+
+ /* Make sure the message is sent */
+ dbus_connection_flush (connection);
+
+ if (!dbus_connection_get_unix_fd (connection, &dbus_fd))
+ {
+ dbus_pending_call_unref (pending);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error while getting peer-to-peer dbus connection: %s",
+ "No fd");
+ goto out;
+ }
+
+ sent_cancel = (cancel_fd == -1);
+ while (!dbus_pending_call_get_completed (pending))
+ {
+ GPollFD poll_fds[2];
+ int poll_ret;
+
+ do
+ {
+ poll_fds[0].events = G_IO_IN;
+ poll_fds[0].fd = dbus_fd;
+ poll_fds[1].events = G_IO_IN;
+ poll_fds[1].fd = cancel_fd;
+ poll_ret = g_poll (poll_fds, sent_cancel?1:2, -1);
+ }
+ while (poll_ret == -1 && errno == EINTR);
+
+ if (poll_ret == -1)
+ {
+ dbus_pending_call_unref (pending);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error while getting peer-to-peer dbus connection: %s",
+ "poll error");
+ goto out;
+ }
+
+ if (!sent_cancel && g_cancellable_is_cancelled (cancellable))
+ {
+ sent_cancel = TRUE;
+ serial = dbus_message_get_serial (message);
+ cancel_message =
+ dbus_message_new_method_call (NULL,
+ G_VFS_DBUS_DAEMON_PATH,
+ G_VFS_DBUS_DAEMON_INTERFACE,
+ G_VFS_DBUS_OP_CANCEL);
+ if (cancel_message != NULL)
+ {
+ if (dbus_message_append_args (cancel_message,
+ DBUS_TYPE_UINT32, &serial,
+ DBUS_TYPE_INVALID))
+ {
+ dbus_connection_send (connection, cancel_message, NULL);
+ dbus_connection_flush (connection);
+ }
+
+ dbus_message_unref (cancel_message);
+ }
+ }
+
+ if (poll_fds[0].revents != 0)
+ {
+ dbus_connection_read_write (connection,
+ G_VFS_DBUS_TIMEOUT_MSECS);
+
+ while (dbus_connection_dispatch (connection) == DBUS_DISPATCH_DATA_REMAINS)
+ ;
+ }
+ }
+
+ reply = dbus_pending_call_steal_reply (pending);
+ dbus_pending_call_unref (pending);
+ }
+ else
+ {
+ dbus_error_init (&derror);
+ reply = dbus_connection_send_with_reply_and_block (connection, message,
+ G_VFS_DBUS_TIMEOUT_MSECS,
+ &derror);
+ if (!reply)
+ {
+ if (dbus_error_has_name (&derror, DBUS_ERROR_NO_REPLY) &&
+ !dbus_connection_get_is_connected (connection))
+ {
+ /* The mount for this connection died, we invalidate
+ * the caches, and then caller needs to retry.
+ */
+
+ invalidate_local_connection (dbus_id, error);
+ }
+ else
+ _g_error_from_dbus (&derror, error);
+ dbus_error_free (&derror);
+ goto out;
+ }
+ }
+
+ if (connection_out)
+ *connection_out = connection;
+
+ out:
+
+ if (handle_callbacks)
+ dbus_connection_unregister_object_path (connection, callback_obj_path);
+
+ if (reply != NULL && _g_error_from_message (reply, error))
+ {
+ dbus_message_unref (reply);
+ return NULL;
+ }
+
+ return reply;
+}
+
+/*************************************************************************
+ * get per-thread synchronous dbus connections *
+ *************************************************************************/
+
+typedef struct {
+ GHashTable *connections;
+ DBusConnection *session_bus;
+} ThreadLocalConnections;
+
+static void
+free_mount_connection (DBusConnection *conn)
+{
+ dbus_connection_close (conn);
+ dbus_connection_unref (conn);
+}
+
+static void
+free_local_connections (ThreadLocalConnections *local)
+{
+ g_hash_table_destroy (local->connections);
+ if (local->session_bus)
+ free_mount_connection (local->session_bus);
+ g_free (local);
+}
+
+static void
+invalidate_local_connection (const char *dbus_id,
+ GError **error)
+{
+ ThreadLocalConnections *local;
+
+ _g_daemon_vfs_invalidate_dbus_id (dbus_id);
+
+ local = g_static_private_get (&local_connections);
+ if (local)
+ g_hash_table_remove (local->connections, dbus_id);
+
+ g_set_error_literal (error,
+ G_VFS_ERROR,
+ G_VFS_ERROR_RETRY,
+ "Cache invalid, retry (internally handled)");
+}
+
+DBusConnection *
+_g_dbus_connection_get_sync (const char *dbus_id,
+ GError **error)
+{
+ DBusConnection *bus;
+ ThreadLocalConnections *local;
+ GError *local_error;
+ DBusConnection *connection;
+ DBusMessage *message, *reply;
+ DBusError derror;
+ char *address1, *address2;
+ int extra_fd;
+
+ g_once (&once_init_dbus, vfs_dbus_init, NULL);
+
+ local = g_static_private_get (&local_connections);
+ if (local == NULL)
+ {
+ local = g_new0 (ThreadLocalConnections, 1);
+ local->connections = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify)free_mount_connection);
+ g_static_private_set (&local_connections, local, (GDestroyNotify)free_local_connections);
+ }
+
+ if (dbus_id == NULL)
+ {
+ /* Session bus */
+
+ if (local->session_bus)
+ {
+ if (dbus_connection_get_is_connected (local->session_bus))
+ return local->session_bus;
+
+ /* Session bus was disconnected, re-connect */
+ local->session_bus = NULL;
+ dbus_connection_unref (local->session_bus);
+ }
+ }
+ else
+ {
+ /* Mount daemon connection */
+
+ connection = g_hash_table_lookup (local->connections, dbus_id);
+ if (connection != NULL)
+ {
+ if (!dbus_connection_get_is_connected (connection))
+ {
+ /* The mount for this connection died, we invalidate
+ * the caches, and then caller needs to retry.
+ */
+
+ invalidate_local_connection (dbus_id, error);
+ return NULL;
+ }
+
+ return connection;
+ }
+ }
+
+ dbus_error_init (&derror);
+
+ if (local->session_bus == NULL)
+ {
+ bus = dbus_bus_get_private (DBUS_BUS_SESSION, &derror);
+ if (bus == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Couldn't get main dbus connection: %s",
+ derror.message);
+ dbus_error_free (&derror);
+ return NULL;
+ }
+
+ local->session_bus = bus;
+
+ if (dbus_id == NULL)
+ return bus; /* We actually wanted the session bus, so done */
+ }
+
+ message = dbus_message_new_method_call (dbus_id,
+ G_VFS_DBUS_DAEMON_PATH,
+ G_VFS_DBUS_DAEMON_INTERFACE,
+ G_VFS_DBUS_OP_GET_CONNECTION);
+ reply = dbus_connection_send_with_reply_and_block (local->session_bus, message, -1,
+ &derror);
+ dbus_message_unref (message);
+
+ if (!reply)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error while getting peer-to-peer dbus connection: %s",
+ derror.message);
+ dbus_error_free (&derror);
+ return NULL;
+ }
+
+ if (_g_error_from_message (reply, error))
+ return NULL;
+
+ dbus_message_get_args (reply, NULL,
+ DBUS_TYPE_STRING, &address1,
+ DBUS_TYPE_STRING, &address2,
+ DBUS_TYPE_INVALID);
+
+ local_error = NULL;
+ extra_fd = _g_socket_connect (address2, &local_error);
+ if (extra_fd == -1)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Error connecting to daemon: %s"), local_error->message);
+ g_error_free (local_error);
+ dbus_message_unref (reply);
+ return NULL;
+ }
+
+ dbus_error_init (&derror);
+ connection = dbus_connection_open_private (address1, &derror);
+ if (!connection)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error while getting peer-to-peer dbus connection: %s",
+ derror.message);
+ close (extra_fd);
+ dbus_message_unref (reply);
+ dbus_error_free (&derror);
+ return NULL;
+ }
+ dbus_message_unref (reply);
+
+ vfs_connection_setup (connection, extra_fd, FALSE);
+
+ g_hash_table_insert (local->connections, g_strdup (dbus_id), connection);
+
+ return connection;
+}