summaryrefslogtreecommitdiffstats
path: root/dbus/dbus-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'dbus/dbus-connection.c')
-rw-r--r--dbus/dbus-connection.c382
1 files changed, 351 insertions, 31 deletions
diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c
index 9c57edc9..92e0331b 100644
--- a/dbus/dbus-connection.c
+++ b/dbus/dbus-connection.c
@@ -233,6 +233,10 @@ struct DBusConnection
* for the global linked list mempool lock
*/
DBusObjectTree *objects; /**< Object path handlers registered with this connection */
+
+ char *server_guid; /**< GUID of server if we are in shared_connections, #NULL if server GUID is unknown or connection is private */
+
+ unsigned int shareable : 1; /**< #TRUE if connection can go in shared_connections once we know the GUID */
unsigned int dispatch_acquired : 1; /**< Someone has dispatch path (can drain incoming queue) */
unsigned int io_path_acquired : 1; /**< Someone has transport io path (can use the transport to read/write messages) */
@@ -1175,6 +1179,7 @@ _dbus_connection_new_for_transport (DBusTransport *transport)
connection->last_dispatch_status = DBUS_DISPATCH_COMPLETE; /* so we're notified first time there's data */
connection->objects = objects;
connection->exit_on_disconnect = FALSE;
+ connection->shareable = FALSE;
#ifndef DBUS_DISABLE_CHECKS
connection->generation = _dbus_current_generation;
#endif
@@ -1363,6 +1368,301 @@ _dbus_connection_handle_watch (DBusWatch *watch,
return retval;
}
+_DBUS_DEFINE_GLOBAL_LOCK (shared_connections);
+static DBusHashTable *shared_connections = NULL;
+
+static void
+shared_connections_shutdown (void *data)
+{
+ _DBUS_LOCK (shared_connections);
+
+ _dbus_assert (_dbus_hash_table_get_n_entries (shared_connections) == 0);
+ _dbus_hash_table_unref (shared_connections);
+ shared_connections = NULL;
+
+ _DBUS_UNLOCK (shared_connections);
+}
+
+static dbus_bool_t
+connection_lookup_shared (DBusAddressEntry *entry,
+ DBusConnection **result)
+{
+ _dbus_verbose ("checking for existing connection\n");
+
+ *result = NULL;
+
+ _DBUS_LOCK (shared_connections);
+
+ if (shared_connections == NULL)
+ {
+ _dbus_verbose ("creating shared_connections hash table\n");
+
+ shared_connections = _dbus_hash_table_new (DBUS_HASH_STRING,
+ dbus_free,
+ NULL);
+ if (shared_connections == NULL)
+ {
+ _DBUS_UNLOCK (shared_connections);
+ return FALSE;
+ }
+
+ if (!_dbus_register_shutdown_func (shared_connections_shutdown, NULL))
+ {
+ _dbus_hash_table_unref (shared_connections);
+ shared_connections = NULL;
+ _DBUS_UNLOCK (shared_connections);
+ return FALSE;
+ }
+
+ _dbus_verbose (" successfully created shared_connections\n");
+
+ _DBUS_UNLOCK (shared_connections);
+ return TRUE; /* no point looking up in the hash we just made */
+ }
+ else
+ {
+ const char *guid;
+
+ guid = dbus_address_entry_get_value (entry, "guid");
+
+ if (guid != NULL)
+ {
+ *result = _dbus_hash_table_lookup_string (shared_connections,
+ guid);
+
+ if (*result)
+ {
+ /* The DBusConnection can't have been disconnected
+ * between the lookup and this code, because the
+ * disconnection will take the shared_connections lock to
+ * remove the connection. It can't have been finalized
+ * since you have to disconnect prior to finalize.
+ *
+ * Thus it's safe to ref the connection.
+ */
+ dbus_connection_ref (*result);
+
+ _dbus_verbose ("looked up existing connection to server guid %s\n",
+ guid);
+ }
+ }
+
+ _DBUS_UNLOCK (shared_connections);
+ return TRUE;
+ }
+}
+
+static dbus_bool_t
+connection_record_shared_unlocked (DBusConnection *connection,
+ const char *guid)
+{
+ char *guid_key;
+ char *guid_in_connection;
+
+ /* A separate copy of the key is required in the hash table, because
+ * we don't have a lock on the connection when we are doing a hash
+ * lookup.
+ */
+
+ _dbus_assert (connection->server_guid == NULL);
+ _dbus_assert (connection->shareable);
+
+ guid_key = _dbus_strdup (guid);
+ if (guid_key == NULL)
+ return FALSE;
+
+ guid_in_connection = _dbus_strdup (guid);
+ if (guid_in_connection == NULL)
+ {
+ dbus_free (guid_key);
+ return FALSE;
+ }
+
+ _DBUS_LOCK (shared_connections);
+ _dbus_assert (shared_connections != NULL);
+
+ if (!_dbus_hash_table_insert_string (shared_connections,
+ guid_key, connection))
+ {
+ dbus_free (guid_key);
+ dbus_free (guid_in_connection);
+ _DBUS_UNLOCK (shared_connections);
+ return FALSE;
+ }
+
+ connection->server_guid = guid_in_connection;
+
+ _dbus_verbose ("stored connection to %s to be shared\n",
+ connection->server_guid);
+
+ _DBUS_UNLOCK (shared_connections);
+
+ _dbus_assert (connection->server_guid != NULL);
+
+ return TRUE;
+}
+
+static void
+connection_forget_shared_unlocked (DBusConnection *connection)
+{
+ HAVE_LOCK_CHECK (connection);
+
+ if (connection->server_guid == NULL)
+ return;
+
+ _dbus_verbose ("dropping connection to %s out of the shared table\n",
+ connection->server_guid);
+
+ _DBUS_LOCK (shared_connections);
+
+ if (!_dbus_hash_table_remove_string (shared_connections,
+ connection->server_guid))
+ _dbus_assert_not_reached ("connection was not in the shared table");
+
+ dbus_free (connection->server_guid);
+ connection->server_guid = NULL;
+
+ _DBUS_UNLOCK (shared_connections);
+}
+
+static DBusConnection*
+connection_try_from_address_entry (DBusAddressEntry *entry,
+ DBusError *error)
+{
+ DBusTransport *transport;
+ DBusConnection *connection;
+
+ transport = _dbus_transport_open (entry, error);
+
+ if (transport == NULL)
+ {
+ _DBUS_ASSERT_ERROR_IS_SET (error);
+ return NULL;
+ }
+
+ connection = _dbus_connection_new_for_transport (transport);
+
+ _dbus_transport_unref (transport);
+
+ if (connection == NULL)
+ {
+ _DBUS_SET_OOM (error);
+ return NULL;
+ }
+
+#ifndef DBUS_DISABLE_CHECKS
+ _dbus_assert (!connection->have_connection_lock);
+#endif
+ return connection;
+}
+
+/*
+ * If the shared parameter is true, then any existing connection will
+ * be used (and if a new connection is created, it will be available
+ * for use by others). If the shared parameter is false, a new
+ * connection will always be created, and the new connection will
+ * never be returned to other callers.
+ *
+ * @param address the address
+ * @param shared whether the connection is shared or private
+ * @param error error return
+ * @returns the connection or #NULL on error
+ */
+static DBusConnection*
+_dbus_connection_open_internal (const char *address,
+ dbus_bool_t shared,
+ DBusError *error)
+{
+ DBusConnection *connection;
+ DBusAddressEntry **entries;
+ DBusError tmp_error;
+ DBusError first_error;
+ int len, i;
+
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ _dbus_verbose ("opening %s connection to: %s\n",
+ shared ? "shared" : "private", address);
+
+ if (!dbus_parse_address (address, &entries, &len, error))
+ return NULL;
+
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ connection = NULL;
+
+ dbus_error_init (&tmp_error);
+ dbus_error_init (&first_error);
+ for (i = 0; i < len; i++)
+ {
+ if (shared)
+ {
+ if (!connection_lookup_shared (entries[i], &connection))
+ _DBUS_SET_OOM (&tmp_error);
+ }
+
+ if (connection == NULL)
+ {
+ connection = connection_try_from_address_entry (entries[i],
+ &tmp_error);
+
+ if (connection != NULL && shared)
+ {
+ const char *guid;
+
+ connection->shareable = TRUE;
+
+ guid = dbus_address_entry_get_value (entries[i], "guid");
+
+ /* we don't have a connection lock but we know nobody
+ * else has a handle to the connection
+ */
+
+ if (guid &&
+ !connection_record_shared_unlocked (connection, guid))
+ {
+ _DBUS_SET_OOM (&tmp_error);
+ dbus_connection_disconnect (connection);
+ dbus_connection_unref (connection);
+ connection = NULL;
+ }
+
+ /* but as of now the connection is possibly shared
+ * since another thread could have pulled it from the table
+ */
+ }
+ }
+
+ if (connection)
+ break;
+
+ _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
+
+ if (i == 0)
+ dbus_move_error (&tmp_error, &first_error);
+ else
+ dbus_error_free (&tmp_error);
+ }
+
+ /* NOTE we don't have a lock on a possibly-shared connection object */
+
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+ _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
+
+ if (connection == NULL)
+ {
+ _DBUS_ASSERT_ERROR_IS_SET (&first_error);
+ dbus_move_error (&first_error, error);
+ }
+ else
+ {
+ dbus_error_free (&first_error);
+ }
+
+ dbus_address_entries_free (entries);
+ return connection;
+}
+
/** @} */
/**
@@ -1372,16 +1672,19 @@ _dbus_connection_handle_watch (DBusWatch *watch,
*/
/**
- * Opens a new connection to a remote address.
+ * Gets a connection to a remote address. If a connection to the given
+ * address already exists, returns the existing connection with its
+ * reference count incremented. Otherwise, returns a new connection
+ * and saves the new connection for possible re-use if a future call
+ * to dbus_connection_open() asks to connect to the same server.
*
- * @todo specify what the address parameter is. Right now
- * it's just the name of a UNIX domain socket. It should be
- * something more complex that encodes which transport to use.
+ * Use dbus_connection_open_private() to get a dedicated connection
+ * not shared with other callers of dbus_connection_open().
*
- * If the open fails, the function returns #NULL, and provides
- * a reason for the failure in the result parameter. Pass
- * #NULL for the result parameter if you aren't interested
- * in the reason for failure.
+ * If the open fails, the function returns #NULL, and provides a
+ * reason for the failure in the error parameter. Pass #NULL for the
+ * error parameter if you aren't interested in the reason for
+ * failure.
*
* @param address the address.
* @param error address where an error can be returned.
@@ -1392,31 +1695,44 @@ dbus_connection_open (const char *address,
DBusError *error)
{
DBusConnection *connection;
- DBusTransport *transport;
_dbus_return_val_if_fail (address != NULL, NULL);
_dbus_return_val_if_error_is_set (error, NULL);
-
- transport = _dbus_transport_open (address, error);
- if (transport == NULL)
- {
- _DBUS_ASSERT_ERROR_IS_SET (error);
- return NULL;
- }
-
- connection = _dbus_connection_new_for_transport (transport);
- _dbus_transport_unref (transport);
-
- if (connection == NULL)
- {
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
- return NULL;
- }
+ connection = _dbus_connection_open_internal (address,
+ TRUE,
+ error);
+
+ return connection;
+}
+
+/**
+ * Opens a new, dedicated connection to a remote address. Unlike
+ * dbus_connection_open(), always creates a new connection.
+ * This connection will not be saved or recycled by libdbus.
+ *
+ * If the open fails, the function returns #NULL, and provides a
+ * reason for the failure in the error parameter. Pass #NULL for the
+ * error parameter if you aren't interested in the reason for
+ * failure.
+ *
+ * @param address the address.
+ * @param error address where an error can be returned.
+ * @returns new connection, or #NULL on failure.
+ */
+DBusConnection*
+dbus_connection_open_private (const char *address,
+ DBusError *error)
+{
+ DBusConnection *connection;
+
+ _dbus_return_val_if_fail (address != NULL, NULL);
+ _dbus_return_val_if_error_is_set (error, NULL);
+
+ connection = _dbus_connection_open_internal (address,
+ FALSE,
+ error);
-#ifndef DBUS_DISABLE_CHECKS
- _dbus_assert (!connection->have_connection_lock);
-#endif
return connection;
}
@@ -1479,7 +1795,8 @@ _dbus_connection_last_unref (DBusConnection *connection)
* you won't get the disconnected message.
*/
_dbus_assert (!_dbus_transport_get_is_connected (connection->transport));
-
+ _dbus_assert (connection->server_guid == NULL);
+
/* ---- We're going to call various application callbacks here, hope it doesn't break anything... */
_dbus_object_tree_free_all_unlocked (connection->objects);
@@ -1529,7 +1846,7 @@ _dbus_connection_last_unref (DBusConnection *connection)
_dbus_list_clear (&connection->incoming_messages);
_dbus_counter_unref (connection->outgoing_counter);
-
+
_dbus_transport_unref (connection->transport);
if (connection->disconnect_message_link)
@@ -1620,6 +1937,7 @@ dbus_connection_disconnect (DBusConnection *connection)
_dbus_verbose ("Disconnecting %p\n", connection);
CONNECTION_LOCK (connection);
+
_dbus_transport_disconnect (connection->transport);
_dbus_verbose ("%s middle\n", _DBUS_FUNCTION_NAME);
@@ -2838,7 +3156,9 @@ _dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection)
{
_dbus_verbose ("Sending disconnect message from %s\n",
_DBUS_FUNCTION_NAME);
-
+
+ connection_forget_shared_unlocked (connection);
+
/* We haven't sent the disconnect message already,
* and all real messages have been queued up.
*/