diff options
Diffstat (limited to 'dbus/dbus-object-registry.c')
| -rw-r--r-- | dbus/dbus-object-registry.c | 617 | 
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 = ®istry->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 = ®istry->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 = ®istry->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)  | 
