summaryrefslogtreecommitdiff
path: root/dbus/dbus-object-registry.c
diff options
context:
space:
mode:
authorHavoc Pennington <hp@redhat.com>2003-08-07 02:18:54 +0000
committerHavoc Pennington <hp@redhat.com>2003-08-07 02:18:54 +0000
commitcefb84edc5f84011c5a171e5d052e37c56c55d27 (patch)
tree1857fc269db9094f012f819e20d02adcd267c059 /dbus/dbus-object-registry.c
parentff8908f1e98eda82b0a77abb449ecff36abf14aa (diff)
downloaddbus-cefb84edc5f84011c5a171e5d052e37c56c55d27.tar.gz
2003-08-06 Havoc Pennington <hp@pobox.com>
* dbus/dbus-object-registry.c: implement signal connection and dispatch * dbus/dbus-connection.c (_dbus_connection_unref_unlocked): new * dbus/dbus-internals.c (_dbus_memdup): new function
Diffstat (limited to 'dbus/dbus-object-registry.c')
-rw-r--r--dbus/dbus-object-registry.c617
1 files changed, 545 insertions, 72 deletions
diff --git a/dbus/dbus-object-registry.c b/dbus/dbus-object-registry.c
index a4d92216..55f8f749 100644
--- a/dbus/dbus-object-registry.c
+++ b/dbus/dbus-object-registry.c
@@ -35,11 +35,16 @@
* Types and functions related to DBusObjectRegistry. These
* are all internal.
*
+ * @todo interface entries and signal connections are handled pretty
+ * much identically, with lots of duplicate code. Once we're sure
+ * they will always be the same, we could merge this code.
+ *
* @{
*/
typedef struct DBusObjectEntry DBusObjectEntry;
typedef struct DBusInterfaceEntry DBusInterfaceEntry;
+typedef struct DBusSignalEntry DBusSignalEntry;
#define DBUS_MAX_OBJECTS_PER_INTERFACE 65535
struct DBusInterfaceEntry
@@ -50,6 +55,17 @@ struct DBusInterfaceEntry
char name[4]; /**< Name of interface (actually allocated larger) */
};
+#define DBUS_MAX_CONNECTIONS_PER_SIGNAL 65535
+struct DBusSignalEntry
+{
+ unsigned int n_connections : 16; /**< Number of connections to this signal */
+ unsigned int n_allocated : 16; /**< Allocated size of objects array */
+ dbus_uint16_t *connections; /**< Index of each object connected (can have dups for multiple
+ * connections)
+ */
+ char name[4]; /**< Name of signal (actually allocated larger) */
+};
+
/* 14 bits for object index, 32K objects */
#define DBUS_OBJECT_INDEX_BITS (14)
#define DBUS_OBJECT_INDEX_MASK (0x3fff)
@@ -62,6 +78,7 @@ struct DBusObjectEntry
void *object_impl; /**< Pointer to application-supplied implementation */
const DBusObjectVTable *vtable; /**< Virtual table for this object */
DBusInterfaceEntry **interfaces; /**< NULL-terminated list of interfaces */
+ DBusSignalEntry **signals; /**< Signal connections (contains dups, one each time we connect) */
};
struct DBusObjectRegistry
@@ -74,6 +91,8 @@ struct DBusObjectRegistry
int n_entries_used;
DBusHashTable *interface_table;
+
+ DBusHashTable *signal_table;
};
static void
@@ -88,11 +107,24 @@ free_interface_entry (void *entry)
dbus_free (iface);
}
+static void
+free_signal_entry (void *entry)
+{
+ DBusSignalEntry *signal = entry;
+
+ if (signal == NULL) /* DBusHashTable stupidity */
+ return;
+
+ dbus_free (signal->connections);
+ dbus_free (signal);
+}
+
DBusObjectRegistry*
_dbus_object_registry_new (DBusConnection *connection)
{
DBusObjectRegistry *registry;
DBusHashTable *interface_table;
+ DBusHashTable *signal_table;
/* the connection passed in here isn't fully constructed,
* so don't do anything more than store a pointer to
@@ -101,6 +133,7 @@ _dbus_object_registry_new (DBusConnection *connection)
registry = NULL;
interface_table = NULL;
+ signal_table = NULL;
registry = dbus_new0 (DBusObjectRegistry, 1);
if (registry == NULL)
@@ -110,10 +143,16 @@ _dbus_object_registry_new (DBusConnection *connection)
NULL, free_interface_entry);
if (interface_table == NULL)
goto oom;
+
+ signal_table = _dbus_hash_table_new (DBUS_HASH_STRING,
+ NULL, free_signal_entry);
+ if (signal_table == NULL)
+ goto oom;
registry->refcount = 1;
registry->connection = connection;
registry->interface_table = interface_table;
+ registry->signal_table = signal_table;
return registry;
@@ -122,7 +161,9 @@ _dbus_object_registry_new (DBusConnection *connection)
dbus_free (registry);
if (interface_table)
_dbus_hash_table_unref (interface_table);
-
+ if (signal_table)
+ _dbus_hash_table_unref (signal_table);
+
return NULL;
}
@@ -147,16 +188,20 @@ _dbus_object_registry_unref (DBusObjectRegistry *registry)
_dbus_assert (registry->n_entries_used == 0);
_dbus_assert (_dbus_hash_table_get_n_entries (registry->interface_table) == 0);
+ _dbus_assert (_dbus_hash_table_get_n_entries (registry->signal_table) == 0);
i = 0;
while (i < registry->n_entries_allocated)
{
if (registry->entries[i].interfaces)
dbus_free (registry->entries[i].interfaces);
+ if (registry->entries[i].signals)
+ dbus_free (registry->entries[i].signals);
++i;
}
_dbus_hash_table_unref (registry->interface_table);
+ _dbus_hash_table_unref (registry->signal_table);
dbus_free (registry->entries);
dbus_free (registry);
}
@@ -213,32 +258,41 @@ validate_id (DBusObjectRegistry *registry,
}
static void
-info_from_entry (DBusObjectRegistry *registry,
- DBusObjectInfo *info,
- DBusObjectEntry *entry)
+id_from_entry (DBusObjectRegistry *registry,
+ DBusObjectID *object_id,
+ DBusObjectEntry *entry)
{
- info->connection = registry->connection;
- info->object_impl = entry->object_impl;
#ifdef DBUS_BUILD_TESTS
if (registry->connection)
#endif
_dbus_connection_init_id (registry->connection,
- &info->object_id);
+ object_id);
#ifdef DBUS_BUILD_TESTS
else
{
- dbus_object_id_set_server_bits (&info->object_id, 1);
- dbus_object_id_set_client_bits (&info->object_id, 2);
+ dbus_object_id_set_server_bits (object_id, 1);
+ dbus_object_id_set_client_bits (object_id, 2);
}
#endif
- _dbus_assert (dbus_object_id_get_server_bits (&info->object_id) != 0);
- _dbus_assert (dbus_object_id_get_client_bits (&info->object_id) != 0);
+ _dbus_assert (dbus_object_id_get_server_bits (object_id) != 0);
+ _dbus_assert (dbus_object_id_get_client_bits (object_id) != 0);
- dbus_object_id_set_instance_bits (&info->object_id,
+ dbus_object_id_set_instance_bits (object_id,
ENTRY_TO_ID (entry));
- _dbus_assert (dbus_object_id_get_instance_bits (&info->object_id) != 0);
+ _dbus_assert (dbus_object_id_get_instance_bits (object_id) != 0);
+}
+
+static void
+info_from_entry (DBusObjectRegistry *registry,
+ DBusObjectInfo *info,
+ DBusObjectEntry *entry)
+{
+ info->connection = registry->connection;
+ info->object_impl = entry->object_impl;
+
+ id_from_entry (registry, &info->object_id, entry);
}
static DBusInterfaceEntry*
@@ -375,6 +429,483 @@ object_remove_from_interfaces (DBusObjectRegistry *registry,
}
}
+static DBusSignalEntry*
+lookup_signal (DBusObjectRegistry *registry,
+ const char *name,
+ dbus_bool_t create_if_not_found)
+{
+ DBusSignalEntry *entry;
+ int sz;
+ int len;
+
+ entry = _dbus_hash_table_lookup_string (registry->signal_table,
+ name);
+ if (entry != NULL || !create_if_not_found)
+ return entry;
+
+ _dbus_assert (create_if_not_found);
+
+ len = strlen (name);
+ sz = _DBUS_STRUCT_OFFSET (DBusSignalEntry, name) + len + 1;
+ entry = dbus_malloc (sz);
+ if (entry == NULL)
+ return NULL;
+ entry->n_connections = 0;
+ entry->n_allocated = 0;
+ entry->connections = NULL;
+ memcpy (entry->name, name, len + 1);
+
+ if (!_dbus_hash_table_insert_string (registry->signal_table,
+ entry->name, entry))
+ {
+ dbus_free (entry);
+ return NULL;
+ }
+
+ return entry;
+}
+
+static void
+delete_signal (DBusObjectRegistry *registry,
+ DBusSignalEntry *entry)
+{
+ _dbus_hash_table_remove_string (registry->signal_table,
+ entry->name);
+}
+
+static dbus_bool_t
+signal_entry_add_object (DBusSignalEntry *entry,
+ dbus_uint16_t object_index)
+{
+ if (entry->n_connections == entry->n_allocated)
+ {
+ unsigned int new_alloc;
+ dbus_uint16_t *new_objects;
+
+ if (entry->n_allocated == 0)
+ new_alloc = 2;
+ else
+ new_alloc = entry->n_allocated * 2;
+
+ /* Right now MAX_CONNECTIONS_PER_SIGNAL can't possibly be reached
+ * since the max number of objects _total_ is smaller, but the
+ * code is here for future robustness.
+ */
+
+ if (new_alloc > DBUS_MAX_CONNECTIONS_PER_SIGNAL)
+ new_alloc = DBUS_MAX_CONNECTIONS_PER_SIGNAL;
+ if (new_alloc == entry->n_allocated)
+ {
+ _dbus_warn ("Attempting to register another instance with signal %s, but max count %d reached\n",
+ entry->name, DBUS_MAX_CONNECTIONS_PER_SIGNAL);
+ return FALSE;
+ }
+
+ new_objects = dbus_realloc (entry->connections, new_alloc * sizeof (dbus_uint16_t));
+ if (new_objects == NULL)
+ return FALSE;
+ entry->connections = new_objects;
+ entry->n_allocated = new_alloc;
+ }
+
+ _dbus_assert (entry->n_connections < entry->n_allocated);
+
+ entry->connections[entry->n_connections] = object_index;
+ entry->n_connections += 1;
+
+ return TRUE;
+}
+
+static void
+signal_entry_remove_object (DBusSignalEntry *entry,
+ dbus_uint16_t object_index)
+{
+ unsigned int i;
+
+ i = 0;
+ while (i < entry->n_connections)
+ {
+ if (entry->connections[i] == object_index)
+ break;
+ ++i;
+ }
+
+ if (i == entry->n_connections)
+ {
+ _dbus_assert_not_reached ("Tried to remove object from an signal that didn't list that object\n");
+ return;
+ }
+
+ memmove (&entry->connections[i],
+ &entry->connections[i+1],
+ (entry->n_connections - i - 1) * sizeof (entry->connections[0]));
+ entry->n_connections -= 1;
+}
+
+static void
+object_remove_from_signals (DBusObjectRegistry *registry,
+ DBusObjectEntry *entry)
+{
+ if (entry->signals != NULL)
+ {
+ int i;
+
+ i = 0;
+ while (entry->signals[i] != NULL)
+ {
+ DBusSignalEntry *iface = entry->signals[i];
+
+ signal_entry_remove_object (iface, entry->id_index);
+ if (iface->n_connections == 0)
+ delete_signal (registry, iface);
+ ++i;
+ }
+ }
+}
+
+/**
+ * Connect this object to the given signal, such that if a
+ * signal emission message is received with the given
+ * signal name, the message will be routed to the
+ * given object.
+ *
+ * Must be called with #DBusConnection lock held.
+ *
+ * @param registry the object registry
+ * @param object_id object that would like to see the signal
+ * @param signal signal name
+ *
+ * @returns #FALSE if no memory
+ */
+dbus_bool_t
+_dbus_object_registry_connect_locked (DBusObjectRegistry *registry,
+ const DBusObjectID *object_id,
+ const char *signal_name)
+{
+ DBusSignalEntry **new_signals;
+ DBusSignalEntry *signal;
+ DBusObjectEntry *entry;
+ int i;
+
+ entry = validate_id (registry, object_id);
+ if (entry == NULL)
+ {
+ _dbus_warn ("Tried to connect a nonexistent D-BUS object ID to signal \"%s\"\n",
+ signal_name);
+
+ return FALSE;
+ }
+
+ /* O(n) in number of connections unfortunately, but in practice I
+ * don't think it will matter. It's marginally a space-time
+ * tradeoff (save an n_signals field) but the NULL termination is
+ * just as large as an n_signals once we have even a single
+ * connection.
+ */
+ i = 0;
+ if (entry->signals != NULL)
+ {
+ while (entry->signals[i] != NULL)
+ ++i;
+ }
+
+ new_signals = dbus_realloc (entry->signals,
+ (i + 2) * sizeof (DBusSignalEntry*));
+
+ if (new_signals == NULL)
+ return FALSE;
+
+ entry->signals = new_signals;
+
+ signal = lookup_signal (registry, signal_name, TRUE);
+ if (signal == NULL)
+ goto oom;
+
+ if (!signal_entry_add_object (signal, entry->id_index))
+ goto oom;
+
+ entry->signals[i] = signal;
+ ++i;
+ entry->signals[i] = NULL;
+
+ return TRUE;
+
+ oom:
+ if (signal && signal->n_connections == 0)
+ delete_signal (registry, signal);
+
+ return FALSE;
+}
+
+/**
+ * Reverses effects of _dbus_object_registry_disconnect_locked().
+ *
+ * @param registry the object registry
+ * @param object_id object that would like to see the signal
+ * @param signal signal name
+ */
+void
+_dbus_object_registry_disconnect_locked (DBusObjectRegistry *registry,
+ const DBusObjectID *object_id,
+ const char *signal_name)
+{
+ DBusObjectEntry *entry;
+ DBusSignalEntry *signal;
+
+ entry = validate_id (registry, object_id);
+ if (entry == NULL)
+ {
+ _dbus_warn ("Tried to disconnect signal \"%s\" from a nonexistent D-BUS object ID\n",
+ signal_name);
+
+ return;
+ }
+
+ signal = lookup_signal (registry, signal_name, FALSE);
+ if (signal == NULL)
+ {
+ _dbus_warn ("Tried to disconnect signal \"%s\" but no such signal is connected\n",
+ signal_name);
+ return;
+ }
+
+ signal_entry_remove_object (signal, entry->id_index);
+
+ if (signal->n_connections == 0)
+ delete_signal (registry, signal);
+}
+
+static DBusHandlerResult
+handle_method_call_and_unlock (DBusObjectRegistry *registry,
+ DBusMessage *message)
+{
+ DBusInterfaceEntry *iface_entry;
+ DBusObjectEntry *object_entry;
+ DBusObjectInfo info;
+ const DBusObjectVTable *vtable;
+
+ _dbus_assert (registry != NULL);
+ _dbus_assert (message != NULL);
+
+ /* FIXME handle calls to an object ID instead of just an
+ * interface name
+ */
+
+ /* If the message isn't to a specific object ID, we send
+ * it to the first object that supports the given interface.
+ */
+ iface_entry = lookup_interface (registry,
+ dbus_message_get_name (message),
+ FALSE);
+
+ if (iface_entry == NULL)
+ {
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+
+ _dbus_assert (iface_entry->n_objects > 0);
+ _dbus_assert (iface_entry->objects != NULL);
+
+ object_entry = &registry->entries[iface_entry->objects[0]];
+
+
+ /* Once we have an object entry, pass message to the object */
+
+ _dbus_assert (object_entry->vtable != NULL);
+
+ info_from_entry (registry, &info, object_entry);
+ vtable = object_entry->vtable;
+
+ /* Drop lock and invoke application code */
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ (* vtable->message) (&info, message);
+
+ return DBUS_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+typedef struct
+{
+ DBusObjectID id;
+} ObjectEmitData;
+
+static DBusHandlerResult
+handle_signal_and_unlock (DBusObjectRegistry *registry,
+ DBusMessage *message)
+{
+ DBusSignalEntry *signal_entry;
+ int i;
+ ObjectEmitData *objects;
+ int n_objects;
+
+ _dbus_assert (registry != NULL);
+ _dbus_assert (message != NULL);
+
+ signal_entry = lookup_signal (registry,
+ dbus_message_get_name (message),
+ FALSE);
+
+ if (signal_entry == NULL)
+ {
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+
+ _dbus_assert (signal_entry->n_connections > 0);
+ _dbus_assert (signal_entry->connections != NULL);
+
+ /* make a copy for safety vs. reentrancy */
+
+ /* FIXME (?) if you disconnect a signal during (vs. before)
+ * emission, you still receive that signal. To fix this uses more
+ * memory because we don't have a per-connection object at the
+ * moment. You would have to introduce a connection object and
+ * refcount it and have a "disconnected" flag. This is more like
+ * GObject semantics but also maybe not important at this level (the
+ * GObject/Qt wrappers can mop it up).
+ */
+
+ n_objects = signal_entry->n_connections;
+ objects = dbus_new (ObjectEmitData, n_objects);
+
+ if (objects == NULL)
+ {
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ i = 0;
+ while (i < signal_entry->n_connections)
+ {
+ DBusObjectEntry *object_entry;
+ int idx;
+
+ idx = signal_entry->connections[i];
+
+ object_entry = &registry->entries[idx];
+
+ _dbus_assert (object_entry->vtable != NULL);
+
+ id_from_entry (registry,
+ &objects[i].id,
+ object_entry);
+
+ ++i;
+ }
+
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_ref_unlocked (registry->connection);
+ _dbus_object_registry_ref (registry);
+ dbus_message_ref (message);
+
+ i = 0;
+ while (i < n_objects)
+ {
+ DBusObjectEntry *object_entry;
+
+ /* If an object ID no longer exists, don't send the
+ * signal
+ */
+ object_entry = validate_id (registry, &objects[i].id);
+ if (object_entry != NULL)
+ {
+ DBusObjectVTable *vtable;
+ DBusObjectInfo info;
+
+ info_from_entry (registry, &info, object_entry);
+ vtable = object_entry->vtable;
+
+ /* Drop lock and invoke application code */
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ (* vtable->message) (&info, message);
+
+ /* Reacquire lock */
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_lock (registry->connection);
+ }
+ ++i;
+ }
+
+ dbus_message_unref (message);
+ _dbus_object_registry_unref (registry);
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unref_unlocked (registry->connection);
+
+ dbus_free (objects);
+
+ /* Drop lock a final time */
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ return DBUS_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+/**
+ * Handle a message, passing it to any objects in the registry that
+ * should receive it.
+ *
+ * @todo handle messages to an object ID, not just those to
+ * an interface name.
+ *
+ * @param registry the object registry
+ * @param message the message to handle
+ * @returns what to do with the message next
+ */
+DBusHandlerResult
+_dbus_object_registry_handle_and_unlock (DBusObjectRegistry *registry,
+ DBusMessage *message)
+{
+ int type;
+
+ _dbus_assert (registry != NULL);
+ _dbus_assert (message != NULL);
+
+ type = dbus_message_get_type (message);
+
+ switch (type)
+ {
+ case DBUS_MESSAGE_TYPE_METHOD_CALL:
+ return handle_method_call_and_unlock (registry, message);
+ case DBUS_MESSAGE_TYPE_SIGNAL:
+ return handle_signal_and_unlock (registry, message);
+ default:
+#ifdef DBUS_BUILD_TESTS
+ if (registry->connection)
+#endif
+ _dbus_connection_unlock (registry->connection);
+
+ return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+}
+
dbus_bool_t
_dbus_object_registry_add_and_unlock (DBusObjectRegistry *registry,
const char **interfaces,
@@ -583,6 +1114,7 @@ _dbus_object_registry_remove_and_unlock (DBusObjectRegistry *registry,
return;
}
+ object_remove_from_signals (registry, entry);
object_remove_from_interfaces (registry, entry);
info_from_entry (registry, &info, entry);
@@ -600,65 +1132,6 @@ _dbus_object_registry_remove_and_unlock (DBusObjectRegistry *registry,
(* vtable->unregistered) (&info);
}
-/**
- * Handle a message, passing it to any objects in the registry that
- * should receive it.
- *
- * @todo handle messages to an object ID, not just those to
- * an interface name.
- *
- * @param registry the object registry
- * @param message the message to handle
- * @returns what to do with the message next
- */
-DBusHandlerResult
-_dbus_object_registry_handle_and_unlock (DBusObjectRegistry *registry,
- DBusMessage *message)
-{
- DBusInterfaceEntry *iface_entry;
- DBusObjectEntry *object_entry;
- DBusObjectInfo info;
- const DBusObjectVTable *vtable;
-
- _dbus_assert (registry != NULL);
- _dbus_assert (message != NULL);
-
- if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
- return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
- /* If the message isn't to a specific object ID, we send
- * it to the first object that supports the given interface.
- */
- iface_entry = lookup_interface (registry,
- dbus_message_get_name (message),
- FALSE);
-
- if (iface_entry == NULL)
- return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
- _dbus_assert (iface_entry->n_objects > 0);
- _dbus_assert (iface_entry->objects != NULL);
-
- object_entry = &registry->entries[iface_entry->objects[0]];
-
-
- /* Once we have an object entry, pass message to the object */
-
- _dbus_assert (object_entry->vtable != NULL);
-
- info_from_entry (registry, &info, object_entry);
- vtable = object_entry->vtable;
-
- /* Drop lock and invoke application code */
-#ifdef DBUS_BUILD_TESTS
- if (registry->connection)
-#endif
- _dbus_connection_unlock (registry->connection);
-
- (* vtable->message) (&info, message);
-
- return DBUS_HANDLER_RESULT_REMOVE_MESSAGE;
-}
void
_dbus_object_registry_free_all_unlocked (DBusObjectRegistry *registry)