2004-07-24 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-connection.c
index b8e67c1..91a2100 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2002, 2003  Red Hat Inc.
  *
- * Licensed under the Academic Free License version 1.2
+ * Licensed under the Academic Free License version 2.0
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@
  */
 
 #include <config.h>
+#include "dbus-shared.h"
 #include "dbus-connection.h"
 #include "dbus-list.h"
 #include "dbus-timeout.h"
@@ -37,6 +38,7 @@
 #include "dbus-string.h"
 #include "dbus-pending-call.h"
 #include "dbus-object-tree.h"
+#include "dbus-marshal.h"
 
 #if 0
 #define CONNECTION_LOCK(connection)   do {                      \
  * handle the details here for you by setting up watch functions.
  *
  * When a connection is disconnected, you are guaranteed to get a
- * message with the name #DBUS_MESSAGE_LOCAL_DISCONNECT.
+ * signal "Disconnected" from the interface
+ * #DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, path
+ * #DBUS_PATH_ORG_FREEDESKTOP_LOCAL.
  *
  * You may not drop the last reference to a #DBusConnection
  * until that connection has been disconnected.
  *
  * You may dispatch the unprocessed incoming message queue even if the
- * connection is disconnected. However, #DBUS_MESSAGE_LOCAL_DISCONNECT
- * will always be the last message in the queue (obviously no messages
- * are received after disconnection).
+ * connection is disconnected. However, "Disconnected" will always be
+ * the last message in the queue (obviously no messages are received
+ * after disconnection).
  *
  * #DBusConnection has thread locks and drops them when invoking user
  * callbacks, so in general is transparently threadsafe. However,
  * @{
  */
 
+/**
+ * Internal struct representing a message filter function 
+ */
 typedef struct DBusMessageFilter DBusMessageFilter;
 
+/**
+ * Internal struct representing a message filter function 
+ */
 struct DBusMessageFilter
 {
-  DBusAtomic refcount;
-  DBusHandleMessageFunction function;
-  void *user_data;
-  DBusFreeFunction free_user_data_function;
+  DBusAtomic refcount; /**< Reference count */
+  DBusHandleMessageFunction function; /**< Function to call to filter */
+  void *user_data; /**< User data for the function */
+  DBusFreeFunction free_user_data_function; /**< Function to free the user data */
+};
+
+
+/**
+ * Internals of DBusPreallocatedSend
+ */
+struct DBusPreallocatedSend
+{
+  DBusConnection *connection; /**< Connection we'd send the message to */
+  DBusList *queue_link;       /**< Preallocated link in the queue */
+  DBusList *counter_link;     /**< Preallocated link in the resource counter */
 };
 
 static dbus_bool_t _dbus_modify_sigpipe = TRUE;
@@ -189,6 +210,8 @@ struct DBusConnection
                          *   for the global linked list mempool lock
                          */
   DBusObjectTree *objects; /**< Object path handlers registered with this connection */
+
+  unsigned int exit_on_disconnect : 1; /**< If #TRUE, exit after handling disconnect signal */
 };
 
 static void               _dbus_connection_remove_timeout_locked             (DBusConnection     *connection,
@@ -198,11 +221,13 @@ static void               _dbus_connection_update_dispatch_status_and_unlock (DB
                                                                               DBusDispatchStatus  new_status);
 static void               _dbus_connection_last_unref                        (DBusConnection     *connection);
 
-static void
+static DBusMessageFilter *
 _dbus_message_filter_ref (DBusMessageFilter *filter)
 {
   _dbus_assert (filter->refcount.value > 0);
   _dbus_atomic_inc (&filter->refcount);
+
+  return filter;
 }
 
 static void
@@ -325,11 +350,13 @@ _dbus_connection_queue_received_message_link (DBusConnection  *connection,
 
   _dbus_connection_wakeup_mainloop (connection);
   
-  _dbus_verbose ("Message %p (%s) added to incoming queue %p, %d incoming\n",
+  _dbus_verbose ("Message %p (%d %s '%s') added to incoming queue %p, %d incoming\n",
                  message,
+                 dbus_message_get_type (message),
                  dbus_message_get_interface (message) ?
                  dbus_message_get_interface (message) :
                  "no interface",
+                 dbus_message_get_signature (message),
                  connection,
                  connection->n_incoming);
 }
@@ -411,11 +438,13 @@ _dbus_connection_message_sent (DBusConnection *connection,
   
   connection->n_outgoing -= 1;
 
-  _dbus_verbose ("Message %p (%s) removed from outgoing queue %p, %d left to send\n",
+  _dbus_verbose ("Message %p (%d %s '%s') removed from outgoing queue %p, %d left to send\n",
                  message,
+                 dbus_message_get_type (message),
                  dbus_message_get_interface (message) ?
                  dbus_message_get_interface (message) :
                  "no interface",
+                 dbus_message_get_signature (message),
                  connection, connection->n_outgoing);
 
   /* Save this link in the link cache also */
@@ -554,25 +583,6 @@ _dbus_connection_toggle_timeout (DBusConnection *connection,
                                        timeout, enabled);
 }
 
-/**
- * Tells the connection that the transport has been disconnected.
- * Results in posting a disconnect message on the incoming message
- * queue.  Only has an effect the first time it's called.
- *
- * @param connection the connection
- */
-void
-_dbus_connection_notify_disconnected (DBusConnection *connection)
-{
-  if (connection->disconnect_message_link)
-    {
-      /* We haven't sent the disconnect message already */
-      _dbus_connection_queue_synthesized_message_link (connection,
-                                                      connection->disconnect_message_link);
-      connection->disconnect_message_link = NULL;
-    }
-}
-
 static dbus_bool_t
 _dbus_connection_attach_pending_call_unlocked (DBusConnection  *connection,
                                                DBusPendingCall *pending)
@@ -671,12 +681,20 @@ _dbus_pending_call_complete_and_unlock (DBusPendingCall *pending,
       message = pending->timeout_link->data;
       _dbus_list_clear (&pending->timeout_link);
     }
+  else
+    dbus_message_ref (message);
 
-  _dbus_verbose ("  handing message %p to pending call\n", message);
+  _dbus_verbose ("  handing message %p (%s) to pending call serial %u\n",
+                 message,
+                 dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN ?
+                 "method return" :
+                 dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR ?
+                 "error" : "other type",
+                 pending->reply_serial);
   
   _dbus_assert (pending->reply == NULL);
+  _dbus_assert (pending->reply_serial == dbus_message_get_reply_serial (message));
   pending->reply = message;
-  dbus_message_ref (pending->reply);
   
   dbus_pending_call_ref (pending); /* in case there's no app with a ref held */
   _dbus_connection_detach_pending_call_and_unlock (pending->connection, pending);
@@ -889,6 +907,7 @@ _dbus_connection_new_for_transport (DBusTransport *transport)
   connection->filter_list = NULL;
   connection->last_dispatch_status = DBUS_DISPATCH_COMPLETE; /* so we're notified first time there's data */
   connection->objects = objects;
+  connection->exit_on_disconnect = FALSE;
   
   _dbus_data_slot_list_init (&connection->slot_list);
 
@@ -948,8 +967,9 @@ _dbus_connection_new_for_transport (DBusTransport *transport)
  * Requires that the caller already holds the connection lock.
  *
  * @param connection the connection.
+ * @returns the connection.
  */
-void
+DBusConnection *
 _dbus_connection_ref_unlocked (DBusConnection *connection)
 {
 #ifdef DBUS_HAVE_ATOMIC_INT
@@ -958,6 +978,8 @@ _dbus_connection_ref_unlocked (DBusConnection *connection)
   _dbus_assert (connection->refcount.value > 0);
   connection->refcount.value += 1;
 #endif
+
+  return connection;
 }
 
 /**
@@ -1102,11 +1124,12 @@ dbus_connection_open (const char     *address,
  * Increments the reference count of a DBusConnection.
  *
  * @param connection the connection.
+ * @returns the connection.
  */
-void
+DBusConnection *
 dbus_connection_ref (DBusConnection *connection)
 {
-  _dbus_return_if_fail (connection != NULL);
+  _dbus_return_val_if_fail (connection != NULL, NULL);
 
   /* The connection lock is better than the global
    * lock in the atomic increment fallback
@@ -1121,6 +1144,8 @@ dbus_connection_ref (DBusConnection *connection)
   connection->refcount.value += 1;
   CONNECTION_UNLOCK (connection);
 #endif
+
+  return connection;
 }
 
 static void
@@ -1273,18 +1298,27 @@ dbus_connection_unref (DBusConnection *connection)
  * function does not affect the connection's reference count.  It's
  * safe to disconnect a connection more than once; all calls after the
  * first do nothing. It's impossible to "reconnect" a connection, a
- * new connection must be created.
+ * new connection must be created. This function may result in a call
+ * to the DBusDispatchStatusFunction set with
+ * dbus_connection_set_dispatch_status_function(), as the disconnect
+ * message it generates needs to be dispatched.
  *
  * @param connection the connection.
  */
 void
 dbus_connection_disconnect (DBusConnection *connection)
 {
+  DBusDispatchStatus status;
+  
   _dbus_return_if_fail (connection != NULL);
   
   CONNECTION_LOCK (connection);
   _dbus_transport_disconnect (connection->transport);
-  CONNECTION_UNLOCK (connection);
+  
+  status = _dbus_connection_get_dispatch_status_unlocked (connection);
+
+  /* this calls out to user code */
+  _dbus_connection_update_dispatch_status_and_unlock (connection, status);
 }
 
 static dbus_bool_t
@@ -1339,12 +1373,29 @@ dbus_connection_get_is_authenticated (DBusConnection *connection)
   return res;
 }
 
-struct DBusPreallocatedSend
+/**
+ * Set whether _exit() should be called when the connection receives a
+ * disconnect signal. The call to _exit() comes after any handlers for
+ * the disconnect signal run; handlers can cancel the exit by calling
+ * this function.
+ *
+ * By default, exit_on_disconnect is #FALSE; but for message bus
+ * connections returned from dbus_bus_get() it will be toggled on
+ * by default.
+ *
+ * @param connection the connection
+ * @param exit_on_disconnect #TRUE if _exit() should be called after a disconnect signal
+ */
+void
+dbus_connection_set_exit_on_disconnect (DBusConnection *connection,
+                                        dbus_bool_t     exit_on_disconnect)
 {
-  DBusConnection *connection;
-  DBusList *queue_link;
-  DBusList *counter_link;
-};
+  _dbus_return_if_fail (connection != NULL);
+
+  CONNECTION_LOCK (connection);
+  connection->exit_on_disconnect = exit_on_disconnect != FALSE;
+  CONNECTION_UNLOCK (connection);
+}
 
 static DBusPreallocatedSend*
 _dbus_connection_preallocate_send_unlocked (DBusConnection *connection)
@@ -1468,11 +1519,13 @@ _dbus_connection_send_preallocated_unlocked (DBusConnection       *connection,
   
   connection->n_outgoing += 1;
 
-  _dbus_verbose ("Message %p (%s) added to outgoing queue %p, %d pending to send\n",
+  _dbus_verbose ("Message %p (%d %s '%s') added to outgoing queue %p, %d pending to send\n",
                  message,
+                 dbus_message_get_type (message),
                  dbus_message_get_interface (message) ?
                  dbus_message_get_interface (message) :
                  "no interface",
+                 dbus_message_get_signature (message),
                  connection,
                  connection->n_outgoing);
 
@@ -1698,20 +1751,15 @@ dbus_connection_send_with_reply (DBusConnection     *connection,
 
   reply = dbus_message_new_error (message, DBUS_ERROR_NO_REPLY,
                                   "No reply within specified time");
-  if (!reply)
-    {
-      CONNECTION_UNLOCK (connection);
-      dbus_pending_call_unref (pending);
-      return FALSE;
-    }
+  if (reply == NULL)
+    goto error;
 
   reply_link = _dbus_list_alloc_link (reply);
-  if (!reply)
+  if (reply_link == NULL)
     {
       CONNECTION_UNLOCK (connection);
       dbus_message_unref (reply);
-      dbus_pending_call_unref (pending);
-      return FALSE;
+      goto error_unlocked;
     }
 
   pending->timeout_link = reply_link;
@@ -1721,29 +1769,30 @@ dbus_connection_send_with_reply (DBusConnection     *connection,
    * Also, add the timeout.
    */
   if (!_dbus_connection_attach_pending_call_unlocked (connection,
-                                                      pending))
-    {
-      CONNECTION_UNLOCK (connection);
-      dbus_pending_call_unref (pending);
-      return FALSE;
-    }
+                                                     pending))
+    goto error;
   
   if (!_dbus_connection_send_unlocked (connection, message, NULL))
     {
       _dbus_connection_detach_pending_call_and_unlock (connection,
-                                                       pending);
-      return FALSE;
+                                                      pending);
+      goto error_unlocked;
     }
 
   if (pending_return)
-    {
-      dbus_pending_call_ref (pending);
-      *pending_return = pending;
-    }
+    *pending_return = pending;
+  else
+    dbus_pending_call_unref (pending);
 
   CONNECTION_UNLOCK (connection);
   
   return TRUE;
+
+ error:
+  CONNECTION_UNLOCK (connection);
+ error_unlocked:
+  dbus_pending_call_unref (pending);
+  return FALSE;
 }
 
 static DBusMessage*
@@ -1762,7 +1811,6 @@ check_for_reply_unlocked (DBusConnection *connection,
        {
          _dbus_list_remove_link (&connection->incoming_messages, link);
          connection->n_incoming  -= 1;
-         dbus_message_ref (reply);
          return reply;
        }
       link = _dbus_list_get_next_link (&connection->incoming_messages, link);
@@ -1779,6 +1827,10 @@ check_for_reply_unlocked (DBusConnection *connection,
  * the whole message queue for example) and has thread issues,
  * see comments in source
  *
+ * Does not re-enter the main loop or run filter/path-registered
+ * callbacks. The reply to the message will not be seen by
+ * filter callbacks.
+ *
  * @param connection the connection
  * @param client_serial the reply serial to wait for
  * @param timeout_milliseconds timeout in milliseconds or -1 for default
@@ -1860,7 +1912,9 @@ _dbus_connection_block_for_reply (DBusConnection     *connection,
   
   _dbus_get_current_time (&tv_sec, &tv_usec);
   
-  if (tv_sec < start_tv_sec)
+  if (!_dbus_connection_get_is_connected_unlocked (connection))
+    return NULL;
+  else if (tv_sec < start_tv_sec)
     _dbus_verbose ("dbus_connection_send_with_reply_and_block(): clock set backward\n");
   else if (connection->disconnect_message_link == NULL)
     _dbus_verbose ("dbus_connection_send_with_reply_and_block(): disconnected\n");
@@ -2141,11 +2195,13 @@ _dbus_connection_pop_message_link_unlocked (DBusConnection *connection)
       link = _dbus_list_pop_first_link (&connection->incoming_messages);
       connection->n_incoming -= 1;
 
-      _dbus_verbose ("Message %p (%s) removed from incoming queue %p, %d incoming\n",
+      _dbus_verbose ("Message %p (%d %s '%s') removed from incoming queue %p, %d incoming\n",
                      link->data,
+                     dbus_message_get_type (link->data),
                      dbus_message_get_interface (link->data) ?
                      dbus_message_get_interface (link->data) :
                      "no interface",
+                     dbus_message_get_signature (link->data),
                      connection, connection->n_incoming);
 
       return link;
@@ -2190,11 +2246,13 @@ _dbus_connection_putback_message_link_unlocked (DBusConnection *connection,
                            message_link);
   connection->n_incoming += 1;
 
-  _dbus_verbose ("Message %p (%s) put back into queue %p, %d incoming\n",
+  _dbus_verbose ("Message %p (%d %s '%s') put back into queue %p, %d incoming\n",
                  message_link->data,
+                 dbus_message_get_type (message_link->data),
                  dbus_message_get_interface (message_link->data) ?
                  dbus_message_get_interface (message_link->data) :
                  "no interface",
+                 dbus_message_get_signature (message_link->data),
                  connection, connection->n_incoming);
 }
 
@@ -2292,6 +2350,18 @@ _dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection)
       
       status = _dbus_transport_get_dispatch_status (connection->transport);
 
+      if (status == DBUS_DISPATCH_COMPLETE &&
+          connection->disconnect_message_link &&
+          !_dbus_transport_get_is_connected (connection->transport))
+        {
+          /* We haven't sent the disconnect message already,
+           * and all real messages have been queued up.
+           */
+          _dbus_connection_queue_synthesized_message_link (connection,
+                                                           connection->disconnect_message_link);
+          connection->disconnect_message_link = NULL;
+        }
+      
       if (status != DBUS_DISPATCH_COMPLETE)
         return status;
       else if (connection->n_incoming > 0)
@@ -2375,8 +2445,15 @@ dbus_connection_get_dispatch_status (DBusConnection *connection)
  * @todo some FIXME in here about handling DBUS_HANDLER_RESULT_NEED_MEMORY
  *
  * @todo right now a message filter gets run on replies to a pending
- * call in here, but not in the case where we block without
- * entering the main loop.
+ * call in here, but not in the case where we block without entering
+ * the main loop. Simple solution might be to just have the pending
+ * call stuff run before the filters.
+ *
+ * @todo FIXME what if we call out to application code to handle a
+ * message, holding the dispatch lock, and the application code runs
+ * the main loop and dispatches again? Probably deadlocks at the
+ * moment. Maybe we want a dispatch status of DBUS_DISPATCH_IN_PROGRESS,
+ * and then the GSource etc. could handle the situation?
  * 
  * @param connection the connection
  * @returns dispatch status
@@ -2521,11 +2598,13 @@ dbus_connection_dispatch (DBusConnection *connection)
   /* We're still protected from dispatch() reentrancy here
    * since we acquired the dispatcher
    */
-  _dbus_verbose ("  running object path dispatch on message %p (%s)\n",
+  _dbus_verbose ("  running object path dispatch on message %p (%d %s '%s')\n",
                  message,
+                 dbus_message_get_type (message),
                  dbus_message_get_interface (message) ?
                  dbus_message_get_interface (message) :
-                 "no interface");
+                 "no interface",
+                 dbus_message_get_signature (message));
   
   result = _dbus_object_tree_dispatch_and_unlock (connection->objects,
                                                   message);
@@ -2588,15 +2667,19 @@ dbus_connection_dispatch (DBusConnection *connection)
       result = DBUS_HANDLER_RESULT_HANDLED;
     }
   
-  _dbus_verbose ("  done dispatching %p (%s) on connection %p\n", message,
+  _dbus_verbose ("  done dispatching %p (%d %s '%s') on connection %p\n", message,
+                 dbus_message_get_type (message),
                  dbus_message_get_interface (message) ?
                  dbus_message_get_interface (message) :
                  "no interface",
+                 dbus_message_get_signature (message),
                  connection);
   
  out:
   if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
     {
+      _dbus_verbose ("out of memory in %s\n", _DBUS_FUNCTION_NAME);
+      
       /* Put message back, and we'll start over.
        * Yes this means handlers must be idempotent if they
        * don't return HANDLED; c'est la vie.
@@ -2606,6 +2689,19 @@ dbus_connection_dispatch (DBusConnection *connection)
     }
   else
     {
+      _dbus_verbose ("Done with message in %s\n", _DBUS_FUNCTION_NAME);
+      
+      if (connection->exit_on_disconnect &&
+          dbus_message_is_signal (message,
+                                  DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL,
+                                  "Disconnected"))
+        {
+          _dbus_verbose ("Exiting on Disconnected signal\n");
+          CONNECTION_UNLOCK (connection);
+          _dbus_exit (1);
+          _dbus_assert_not_reached ("Call to exit() returned");
+        }
+      
       _dbus_list_free_link (message_link);
       dbus_message_unref (message); /* don't want the message to count in max message limits
                                      * in computing dispatch status below
@@ -2857,6 +2953,37 @@ dbus_connection_set_dispatch_status_function (DBusConnection             *connec
 }
 
 /**
+ * Get the UNIX file descriptor of the connection, if any.  This can
+ * be used for SELinux access control checks with getpeercon() for
+ * example. DO NOT read or write to the file descriptor, or try to
+ * select() on it; use DBusWatch for main loop integration. Not all
+ * connections will have a file descriptor. So for adding descriptors
+ * to the main loop, use dbus_watch_get_fd() and so forth.
+ *
+ * @param connection the connection
+ * @param fd return location for the file descriptor.
+ * @returns #TRUE if fd is successfully obtained.
+ */
+dbus_bool_t
+dbus_connection_get_unix_fd (DBusConnection *connection,
+                             int            *fd)
+{
+  dbus_bool_t retval;
+
+  _dbus_return_val_if_fail (connection != NULL, FALSE);
+  _dbus_return_val_if_fail (connection->transport != NULL, FALSE);
+  
+  CONNECTION_LOCK (connection);
+  
+  retval = _dbus_transport_get_unix_fd (connection->transport,
+                                        fd);
+
+  CONNECTION_UNLOCK (connection);
+
+  return retval;
+}
+
+/**
  * Gets the UNIX user ID of the connection if any.
  * Returns #TRUE if the uid is filled in.
  * Always returns #FALSE on non-UNIX platforms.
@@ -2889,6 +3016,37 @@ dbus_connection_get_unix_user (DBusConnection *connection,
 }
 
 /**
+ * Gets the process ID of the connection if any.
+ * Returns #TRUE if the uid is filled in.
+ * Always returns #FALSE prior to authenticating the
+ * connection.
+ *
+ * @param connection the connection
+ * @param pid return location for the process ID
+ * @returns #TRUE if uid is filled in with a valid process ID
+ */
+dbus_bool_t
+dbus_connection_get_unix_process_id (DBusConnection *connection,
+                                    unsigned long  *pid)
+{
+  dbus_bool_t result;
+
+  _dbus_return_val_if_fail (connection != NULL, FALSE);
+  _dbus_return_val_if_fail (pid != NULL, FALSE);
+  
+  CONNECTION_LOCK (connection);
+
+  if (!_dbus_transport_get_is_authenticated (connection->transport))
+    result = FALSE;
+  else
+    result = _dbus_transport_get_unix_process_id (connection->transport,
+                                                 pid);
+  CONNECTION_UNLOCK (connection);
+
+  return result;
+}
+
+/**
  * Sets a predicate function used to determine whether a given user ID
  * is allowed to connect. When an incoming connection has
  * authenticated with a particular user ID, this function is called;
@@ -2936,7 +3094,9 @@ dbus_connection_set_unix_user_function (DBusConnection             *connection,
  *
  * @todo we don't run filters on messages while blocking without
  * entering the main loop, since filters are run as part of
- * dbus_connection_dispatch().
+ * dbus_connection_dispatch(). This is probably a feature, as filters
+ * could create arbitrary reentrancy. But kind of sucks if you're
+ * trying to filter METHOD_RETURN for some reason.
  *
  * @param connection the connection
  * @param function function to handle messages
@@ -2992,7 +3152,8 @@ dbus_connection_add_filter (DBusConnection            *connection,
  * instance).
  *
  * @param connection the connection
- * @param handler the handler to remove
+ * @param function the handler to remove
+ * @param user_data user data for the handler to remove
  *
  */
 void
@@ -3054,33 +3215,39 @@ dbus_connection_remove_filter (DBusConnection            *connection,
  *
  *
  * @param connection the connection
- * @param path #NULL-terminated array of path elements
+ * @param path a '/' delimited string of path elements
  * @param vtable the virtual table
  * @param user_data data to pass to functions in the vtable
  * @returns #FALSE if not enough memory
  */
 dbus_bool_t
 dbus_connection_register_object_path (DBusConnection              *connection,
-                                      const char                 **path,
+                                      const char                  *path,
                                       const DBusObjectPathVTable  *vtable,
                                       void                        *user_data)
 {
+  char **decomposed_path;
   dbus_bool_t retval;
   
   _dbus_return_val_if_fail (connection != NULL, FALSE);
   _dbus_return_val_if_fail (path != NULL, FALSE);
-  _dbus_return_val_if_fail (path[0] != NULL, FALSE);
+  _dbus_return_val_if_fail (path[0] == '/', FALSE);
   _dbus_return_val_if_fail (vtable != NULL, FALSE);
 
+  if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL))
+    return FALSE;
+
   CONNECTION_LOCK (connection);
 
   retval = _dbus_object_tree_register (connection->objects,
                                        FALSE,
-                                       path, vtable,
+                                       (const char **) decomposed_path, vtable,
                                        user_data);
 
   CONNECTION_UNLOCK (connection);
 
+  dbus_free_string_array (decomposed_path);
+
   return retval;
 }
 
@@ -3090,57 +3257,107 @@ dbus_connection_register_object_path (DBusConnection              *connection,
  * path. You can use this to establish a default message handling
  * policy for a whole "subdirectory."
  *
- *
  * @param connection the connection
- * @param path #NULL-terminated array of path elements
+ * @param path a '/' delimited string of path elements
  * @param vtable the virtual table
  * @param user_data data to pass to functions in the vtable
  * @returns #FALSE if not enough memory
  */
 dbus_bool_t
 dbus_connection_register_fallback (DBusConnection              *connection,
-                                   const char                 **path,
+                                   const char                  *path,
                                    const DBusObjectPathVTable  *vtable,
                                    void                        *user_data)
 {
+  char **decomposed_path;
   dbus_bool_t retval;
   
   _dbus_return_val_if_fail (connection != NULL, FALSE);
   _dbus_return_val_if_fail (path != NULL, FALSE);
-  _dbus_return_val_if_fail (path[0] != NULL, FALSE);
+  _dbus_return_val_if_fail (path[0] == '/', FALSE);
   _dbus_return_val_if_fail (vtable != NULL, FALSE);
 
+  if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL))
+    return FALSE;
+
   CONNECTION_LOCK (connection);
 
   retval = _dbus_object_tree_register (connection->objects,
                                        TRUE,
-                                       path, vtable,
+                                      (const char **) decomposed_path, vtable,
                                        user_data);
 
   CONNECTION_UNLOCK (connection);
 
+  dbus_free_string_array (decomposed_path);
+
   return retval;
 }
 
 /**
  * Unregisters the handler registered with exactly the given path.
  * It's a bug to call this function for a path that isn't registered.
+ * Can unregister both fallback paths and object paths.
  *
  * @param connection the connection
- * @param path the #NULL-terminated array of path elements
+ * @param path a '/' delimited string of path elements
+ * @returns #FALSE if not enough memory
  */
-void
+dbus_bool_t
 dbus_connection_unregister_object_path (DBusConnection              *connection,
-                                        const char                 **path)
+                                        const char                  *path)
 {
-  _dbus_return_if_fail (connection != NULL);
-  _dbus_return_if_fail (path != NULL);
-  _dbus_return_if_fail (path[0] != NULL);
+  char **decomposed_path;
+
+  _dbus_return_val_if_fail (connection != NULL, FALSE);
+  _dbus_return_val_if_fail (path != NULL, FALSE);
+  _dbus_return_val_if_fail (path[0] == '/', FALSE);
+
+  if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL))
+      return FALSE;
 
   CONNECTION_LOCK (connection);
 
-  return _dbus_object_tree_unregister_and_unlock (connection->objects,
-                                                  path);
+  _dbus_object_tree_unregister_and_unlock (connection->objects, (const char **) decomposed_path);
+
+  dbus_free_string_array (decomposed_path);
+
+  return TRUE;
+}
+
+/**
+ * Lists the registered fallback handlers and object path handlers at
+ * the given parent_path. The returned array should be freed with
+ * dbus_free_string_array().
+ *
+ * @param connection the connection
+ * @param parent_path the path to list the child handlers of
+ * @param child_entries returns #NULL-terminated array of children
+ * @returns #FALSE if no memory to allocate the child entries
+ */
+dbus_bool_t
+dbus_connection_list_registered (DBusConnection              *connection,
+                                 const char                  *parent_path,
+                                 char                      ***child_entries)
+{
+  char **decomposed_path;
+  dbus_bool_t retval;
+  _dbus_return_val_if_fail (connection != NULL, FALSE);
+  _dbus_return_val_if_fail (parent_path != NULL, FALSE);
+  _dbus_return_val_if_fail (parent_path[0] == '/', FALSE);
+  _dbus_return_val_if_fail (child_entries != NULL, FALSE);
+
+  if (!_dbus_decompose_path (parent_path, strlen (parent_path), &decomposed_path, NULL))
+    return FALSE;
+
+  CONNECTION_LOCK (connection);
+
+  retval = _dbus_object_tree_list_registered_and_unlock (connection->objects,
+                                                        (const char **) decomposed_path,
+                                                        child_entries);
+  dbus_free_string_array (decomposed_path);
+
+  return retval;
 }
 
 static DBusDataSlotAllocator slot_allocator;