summaryrefslogtreecommitdiffstats
path: root/bus/connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'bus/connection.c')
-rw-r--r--bus/connection.c422
1 files changed, 414 insertions, 8 deletions
diff --git a/bus/connection.c b/bus/connection.c
index 40bbc325..ff671c58 100644
--- a/bus/connection.c
+++ b/bus/connection.c
@@ -24,34 +24,79 @@
#include "dispatch.h"
#include "loop.h"
#include "services.h"
+#include "utils.h"
#include <dbus/dbus-list.h>
+static void bus_connection_remove_transactions (DBusConnection *connection);
+
static int connection_data_slot;
static DBusList *connections = NULL;
typedef struct
{
+ DBusConnection *connection;
DBusList *services_owned;
-
char *name;
+ DBusList *transaction_messages; /**< Stuff we need to send as part of a transaction */
+ DBusMessage *oom_message;
+ DBusPreallocatedSend *oom_preallocated;
} BusConnectionData;
#define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot))
void
-bus_connection_disconnect (DBusConnection *connection)
+bus_connection_disconnected (DBusConnection *connection)
{
BusConnectionData *d;
BusService *service;
-
+
_dbus_warn ("Disconnected\n");
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
- /* Drop any service ownership */
- while ((service = _dbus_list_get_last (&d->services_owned)))
- bus_service_remove_owner (service, connection);
+ /* Drop any service ownership. FIXME Unfortunately, this requires
+ * memory allocation and there doesn't seem to be a good way to
+ * handle it other than sleeping; we can't "fail" the operation of
+ * disconnecting a client, and preallocating a broadcast "service is
+ * now gone" message for every client-service pair seems kind of
+ * involved. Probably we need to do that though, and also
+ * extend BusTransaction to be able to revert generic
+ * stuff, not just sending a message (so we can e.g. revert
+ * removal of service owners).
+ */
+ {
+ BusTransaction *transaction;
+ DBusError error;
+
+ dbus_error_init (&error);
+
+ transaction = NULL;
+ while (transaction == NULL)
+ {
+ transaction = bus_transaction_new ();
+ bus_wait_for_memory ();
+ }
+
+ while ((service = _dbus_list_get_last (&d->services_owned)))
+ {
+ retry:
+ if (!bus_service_remove_owner (service, connection,
+ transaction, &error))
+ {
+ if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+ {
+ dbus_error_free (&error);
+ bus_wait_for_memory ();
+ goto retry;
+ }
+ else
+ _dbus_assert_not_reached ("Removing service owner failed for non-memory-related reason");
+ }
+ }
+
+ bus_transaction_execute_and_free (transaction);
+ }
bus_dispatch_remove_connection (connection);
@@ -60,12 +105,14 @@ bus_connection_disconnect (DBusConnection *connection)
NULL, NULL,
connection,
NULL);
+
+ bus_connection_remove_transactions (connection);
dbus_connection_set_data (connection,
connection_data_slot,
NULL, NULL);
- _dbus_list_remove (&connections, connection);
+ _dbus_list_remove (&connections, connection);
dbus_connection_unref (connection);
}
@@ -106,7 +153,14 @@ free_connection_data (void *data)
/* services_owned should be NULL since we should be disconnected */
_dbus_assert (d->services_owned == NULL);
+ /* similarly */
+ _dbus_assert (d->transaction_messages == NULL);
+ if (d->oom_preallocated)
+ dbus_connection_free_preallocated_send (d->connection, d->oom_preallocated);
+ if (d->oom_message)
+ dbus_message_unref (d->oom_message);
+
dbus_free (d->name);
dbus_free (d);
@@ -132,6 +186,8 @@ bus_connection_setup (DBusConnection *connection)
if (d == NULL)
return FALSE;
+
+ d->connection = connection;
if (!dbus_connection_set_data (connection,
connection_data_slot,
@@ -163,6 +219,88 @@ bus_connection_setup (DBusConnection *connection)
return TRUE;
}
+/**
+ * Checks whether the connection is registered with the message bus.
+ *
+ * @param connection the connection
+ * @returns #TRUE if we're an active message bus participant
+ */
+dbus_bool_t
+bus_connection_is_active (DBusConnection *connection)
+{
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+
+ return d != NULL && d->name != NULL;
+}
+
+dbus_bool_t
+bus_connection_preallocate_oom_error (DBusConnection *connection)
+{
+ DBusMessage *message;
+ DBusPreallocatedSend *preallocated;
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+
+ _dbus_assert (d != NULL);
+
+ if (d->oom_preallocated != NULL)
+ return TRUE;
+
+ preallocated = dbus_connection_preallocate_send (connection);
+ if (preallocated == NULL)
+ return FALSE;
+
+ message = dbus_message_new (DBUS_SERVICE_DBUS,
+ DBUS_ERROR_NO_MEMORY);
+ if (message == NULL)
+ {
+ dbus_connection_free_preallocated_send (connection, preallocated);
+ return FALSE;
+ }
+
+ /* set reply serial to placeholder value just so space is already allocated
+ * for it.
+ */
+ if (!dbus_message_set_reply_serial (message, 14))
+ {
+ dbus_connection_free_preallocated_send (connection, preallocated);
+ dbus_message_unref (message);
+ return FALSE;
+ }
+
+ d->oom_message = message;
+ d->oom_preallocated = preallocated;
+
+ return TRUE;
+}
+
+void
+bus_connection_send_oom_error (DBusConnection *connection,
+ DBusMessage *in_reply_to)
+{
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+
+ _dbus_assert (d != NULL);
+ _dbus_assert (d->oom_message != NULL);
+
+ /* should always succeed since we set it to a placeholder earlier */
+ if (!dbus_message_set_reply_serial (d->oom_message,
+ dbus_message_get_serial (in_reply_to)))
+ _dbus_assert_not_reached ("Failed to set reply serial for preallocated oom message");
+
+ dbus_connection_send_preallocated (connection, d->oom_preallocated,
+ d->oom_message, NULL);
+
+ dbus_message_unref (d->oom_message);
+ d->oom_message = NULL;
+ d->oom_preallocated = NULL;
+}
+
dbus_bool_t
bus_connection_add_owned_service (DBusConnection *connection,
BusService *service)
@@ -223,9 +361,277 @@ bus_connection_get_name (DBusConnection *connection)
return d->name;
}
+/**
+ * Calls function on each connection; if the function returns
+ * #FALSE, stops iterating.
+ *
+ * @param function the function
+ * @param data data to pass to it as a second arg
+ */
void
bus_connection_foreach (BusConnectionForeachFunction function,
void *data)
{
- _dbus_list_foreach (&connections, (DBusForeachFunction)function, data);
+ DBusList *link;
+
+ link = _dbus_list_get_first_link (&connections);
+ while (link != NULL)
+ {
+ DBusConnection *connection = link->data;
+ DBusList *next = _dbus_list_get_next_link (&connections, link);
+
+ if (!(* function) (connection, data))
+ break;
+
+ link = next;
+ }
+}
+
+typedef struct
+{
+ BusTransaction *transaction;
+ DBusMessage *message;
+ DBusPreallocatedSend *preallocated;
+} MessageToSend;
+
+struct BusTransaction
+{
+ DBusList *connections;
+
+};
+
+static void
+message_to_send_free (DBusConnection *connection,
+ MessageToSend *to_send)
+{
+ if (to_send->message)
+ dbus_message_unref (to_send->message);
+
+ if (to_send->preallocated)
+ dbus_connection_free_preallocated_send (connection, to_send->preallocated);
+
+ dbus_free (to_send);
+}
+
+BusTransaction*
+bus_transaction_new (void)
+{
+ BusTransaction *transaction;
+
+ transaction = dbus_new0 (BusTransaction, 1);
+ if (transaction == NULL)
+ return NULL;
+
+ return transaction;
+}
+
+dbus_bool_t
+bus_transaction_send_message (BusTransaction *transaction,
+ DBusConnection *connection,
+ DBusMessage *message)
+{
+ MessageToSend *to_send;
+ BusConnectionData *d;
+ DBusList *link;
+
+ if (!dbus_connection_get_is_connected (connection))
+ return TRUE; /* silently ignore disconnected connections */
+
+ d = BUS_CONNECTION_DATA (connection);
+ _dbus_assert (d != NULL);
+
+ to_send = dbus_new (MessageToSend, 1);
+ if (to_send == NULL)
+ {
+ return FALSE;
+ }
+
+ to_send->preallocated = dbus_connection_preallocate_send (connection);
+ if (to_send->preallocated == NULL)
+ {
+ dbus_free (to_send);
+ return FALSE;
+ }
+
+ dbus_message_ref (message);
+ to_send->message = message;
+ to_send->transaction = transaction;
+
+ if (!_dbus_list_prepend (&d->transaction_messages, to_send))
+ {
+ message_to_send_free (connection, to_send);
+ return FALSE;
+ }
+
+ /* See if we already had this connection in the list
+ * for this transaction. If we have a pending message,
+ * then we should already be in transaction->connections
+ */
+ link = _dbus_list_get_first_link (&d->transaction_messages);
+ _dbus_assert (link->data == to_send);
+ link = _dbus_list_get_next_link (&d->transaction_messages, link);
+ while (link != NULL)
+ {
+ MessageToSend *m = link->data;
+ DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link);
+
+ if (m->transaction == transaction)
+ break;
+
+ link = next;
+ }
+
+ if (link == NULL)
+ {
+ if (!_dbus_list_prepend (&transaction->connections, connection))
+ {
+ _dbus_list_remove (&d->transaction_messages, to_send);
+ message_to_send_free (connection, to_send);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+connection_cancel_transaction (DBusConnection *connection,
+ BusTransaction *transaction)
+{
+ DBusList *link;
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+ _dbus_assert (d != NULL);
+
+ link = _dbus_list_get_first_link (&d->transaction_messages);
+ while (link != NULL)
+ {
+ MessageToSend *m = link->data;
+ DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link);
+
+ if (m->transaction == transaction)
+ {
+ _dbus_list_remove_link (&d->transaction_messages,
+ link);
+
+ message_to_send_free (connection, m);
+ }
+
+ link = next;
+ }
+}
+
+void
+bus_transaction_cancel_and_free (BusTransaction *transaction)
+{
+ DBusConnection *connection;
+
+ while ((connection = _dbus_list_pop_first (&transaction->connections)))
+ connection_cancel_transaction (connection, transaction);
+
+ _dbus_assert (transaction->connections == NULL);
+
+ dbus_free (transaction);
+}
+
+static void
+connection_execute_transaction (DBusConnection *connection,
+ BusTransaction *transaction)
+{
+ DBusList *link;
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+ _dbus_assert (d != NULL);
+
+ /* Send the queue in order (FIFO) */
+ link = _dbus_list_get_last_link (&d->transaction_messages);
+ while (link != NULL)
+ {
+ MessageToSend *m = link->data;
+ DBusList *prev = _dbus_list_get_prev_link (&d->transaction_messages, link);
+
+ if (m->transaction == transaction)
+ {
+ _dbus_list_remove_link (&d->transaction_messages,
+ link);
+
+ dbus_connection_send_preallocated (connection,
+ m->preallocated,
+ m->message,
+ NULL);
+
+ m->preallocated = NULL; /* so we don't double-free it */
+
+ message_to_send_free (connection, m);
+ }
+
+ link = prev;
+ }
+}
+
+void
+bus_transaction_execute_and_free (BusTransaction *transaction)
+{
+ /* For each connection in transaction->connections
+ * send the messages
+ */
+ DBusConnection *connection;
+
+ while ((connection = _dbus_list_pop_first (&transaction->connections)))
+ connection_execute_transaction (connection, transaction);
+
+ _dbus_assert (transaction->connections == NULL);
+
+ dbus_free (transaction);
+}
+
+static void
+bus_connection_remove_transactions (DBusConnection *connection)
+{
+ MessageToSend *to_send;
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+ _dbus_assert (d != NULL);
+
+ while ((to_send = _dbus_list_get_first (&d->transaction_messages)))
+ {
+ /* only has an effect for the first MessageToSend listing this transaction */
+ _dbus_list_remove (&to_send->transaction->connections,
+ connection);
+
+ _dbus_list_remove (&d->transaction_messages, to_send);
+ message_to_send_free (connection, to_send);
+ }
+}
+
+/**
+ * Converts the DBusError to a message reply
+ */
+dbus_bool_t
+bus_transaction_send_error_reply (BusTransaction *transaction,
+ DBusConnection *connection,
+ const DBusError *error,
+ DBusMessage *in_reply_to)
+{
+ DBusMessage *reply;
+
+ _dbus_assert (error != NULL);
+ _DBUS_ASSERT_ERROR_IS_SET (error);
+
+ reply = dbus_message_new_error_reply (in_reply_to,
+ error->name,
+ error->message);
+ if (reply == NULL)
+ return FALSE;
+
+ if (!bus_transaction_send_message (transaction, connection, reply))
+ {
+ dbus_message_unref (reply);
+ return FALSE;
+ }
+
+ return TRUE;
}