[kdbus] Rework kdbus core files
[platform/upstream/glib.git] / gio / gdbusinterfaceskeleton.c
index 89616ae..0070e8b 100644 (file)
@@ -13,9 +13,7 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: David Zeuthen <davidz@redhat.com>
  */
 #include "gdbusinterface.h"
 #include "gdbusinterfaceskeleton.h"
 #include "gdbusobjectskeleton.h"
-#include "gio-marshal.h"
 #include "gioenumtypes.h"
 #include "gdbusprivate.h"
 #include "gdbusmethodinvocation.h"
 #include "gdbusconnection.h"
-#include "gioscheduler.h"
+#include "gtask.h"
 #include "gioerror.h"
 
 #include "glibintl.h"
 
 struct _GDBusInterfaceSkeletonPrivate
 {
-  GDBusObject *object;
+  GMutex                      lock;
+
+  GDBusObject                *object;
   GDBusInterfaceSkeletonFlags flags;
-  guint registration_id;
 
-  GDBusConnection *connection;
-  gchar *object_path;
-  GDBusInterfaceVTable *hooked_vtable;
+  GSList                     *connections;   /* List of ConnectionData */
+  gchar                      *object_path;   /* The object path for this skeleton */
+  GDBusInterfaceVTable       *hooked_vtable;
 };
 
+typedef struct
+{
+  GDBusConnection *connection;
+  guint            registration_id;
+} ConnectionData;
+
 enum
 {
   G_AUTHORIZE_METHOD_SIGNAL,
@@ -68,25 +72,52 @@ enum
 
 static guint signals[LAST_SIGNAL] = {0};
 
-static void dbus_interface_interface_init (GDBusInterfaceIface *iface);
+static void     dbus_interface_interface_init                      (GDBusInterfaceIface    *iface);
+
+static void     set_object_path_locked                             (GDBusInterfaceSkeleton *interface_,
+                                                                    const gchar            *object_path);
+static void     remove_connection_locked                           (GDBusInterfaceSkeleton *interface_,
+                                                                    GDBusConnection        *connection);
+static void     skeleton_intercept_handle_method_call              (GDBusConnection        *connection,
+                                                                    const gchar            *sender,
+                                                                    const gchar            *object_path,
+                                                                    const gchar            *interface_name,
+                                                                    const gchar            *method_name,
+                                                                    GVariant               *parameters,
+                                                                    GDBusMethodInvocation  *invocation,
+                                                                    gpointer                user_data);
+
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GDBusInterfaceSkeleton, g_dbus_interface_skeleton, G_TYPE_OBJECT,
-                                  G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_INTERFACE, dbus_interface_interface_init));
+                                  G_ADD_PRIVATE (GDBusInterfaceSkeleton)
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_INTERFACE, dbus_interface_interface_init))
 
 static void
 g_dbus_interface_skeleton_finalize (GObject *object)
 {
   GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON (object);
-  /* unexport if already exported */
-  if (interface->priv->registration_id > 0)
-    g_dbus_interface_skeleton_unexport (interface);
 
-  g_assert (interface->priv->connection == NULL);
-  g_assert (interface->priv->object_path == NULL);
-  g_assert (interface->priv->hooked_vtable == NULL);
+  /* Hold the lock just incase any code we call verifies that the lock is held */
+  g_mutex_lock (&interface->priv->lock);
+
+  /* unexport from all connections if we're exported anywhere */
+  while (interface->priv->connections != NULL)
+    {
+      ConnectionData *data = interface->priv->connections->data;
+      remove_connection_locked (interface, data->connection);
+    }
+
+  set_object_path_locked (interface, NULL);
+
+  g_mutex_unlock (&interface->priv->lock);
+
+  g_free (interface->priv->hooked_vtable);
 
   if (interface->priv->object != NULL)
     g_object_remove_weak_pointer (G_OBJECT (interface->priv->object), (gpointer *) &interface->priv->object);
+
+  g_mutex_clear (&interface->priv->lock);
+
   G_OBJECT_CLASS (g_dbus_interface_skeleton_parent_class)->finalize (object);
 }
 
@@ -177,11 +208,11 @@ g_dbus_interface_skeleton_class_init (GDBusInterfaceSkeletonClass *klass)
    *
    * Note that this signal is emitted in a thread dedicated to
    * handling the method call so handlers are allowed to perform
-   * blocking IO. This means that it is appropriate to call
-   * e.g. <ulink
-   * url="http://hal.freedesktop.org/docs/polkit/PolkitAuthority.html#polkit-authority-check-authorization-sync">polkit_authority_check_authorization_sync()</ulink>
-   * with the <ulink
-   * url="http://hal.freedesktop.org/docs/polkit/PolkitAuthority.html#POLKIT-CHECK-AUTHORIZATION-FLAGS-ALLOW-USER-INTERACTION:CAPS">POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION</ulink> flag set.
+   * blocking IO. This means that it is appropriate to call e.g.
+   * [polkit_authority_check_authorization_sync()](http://hal.freedesktop.org/docs/polkit/PolkitAuthority.html#polkit-authority-check-authorization-sync)
+   * with the
+   * [POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION](http://hal.freedesktop.org/docs/polkit/PolkitAuthority.html#POLKIT-CHECK-AUTHORIZATION-FLAGS-ALLOW-USER-INTERACTION:CAPS)
+   * flag set.
    *
    * If %FALSE is returned then no further handlers are run and the
    * signal handler must take a reference to @invocation and finish
@@ -217,18 +248,17 @@ g_dbus_interface_skeleton_class_init (GDBusInterfaceSkeletonClass *klass)
                   G_STRUCT_OFFSET (GDBusInterfaceSkeletonClass, g_authorize_method),
                   _g_signal_accumulator_false_handled,
                   NULL,
-                  _gio_marshal_BOOLEAN__OBJECT,
+                  NULL,
                   G_TYPE_BOOLEAN,
                   1,
                   G_TYPE_DBUS_METHOD_INVOCATION);
-
-  g_type_class_add_private (klass, sizeof (GDBusInterfaceSkeletonPrivate));
 }
 
 static void
 g_dbus_interface_skeleton_init (GDBusInterfaceSkeleton *interface)
 {
-  interface->priv = G_TYPE_INSTANCE_GET_PRIVATE (interface, G_TYPE_DBUS_INTERFACE_SKELETON, GDBusInterfaceSkeletonPrivate);
+  interface->priv = g_dbus_interface_skeleton_get_instance_private (interface);
+  g_mutex_init (&interface->priv->lock);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -265,11 +295,17 @@ g_dbus_interface_skeleton_set_flags (GDBusInterfaceSkeleton      *interface_,
                                      GDBusInterfaceSkeletonFlags  flags)
 {
   g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_));
+  g_mutex_lock (&interface_->priv->lock);
   if (interface_->priv->flags != flags)
     {
       interface_->priv->flags = flags;
+      g_mutex_unlock (&interface_->priv->lock);
       g_object_notify (G_OBJECT (interface_), "g-flags");
     }
+  else
+    {
+      g_mutex_unlock (&interface_->priv->lock);
+    }
 }
 
 /**
@@ -321,7 +357,9 @@ g_dbus_interface_skeleton_get_vtable (GDBusInterfaceSkeleton *interface_)
  *
  * Gets all D-Bus properties for @interface_.
  *
- * Returns: A new, floating, #GVariant of type <link linkend="G-VARIANT-TYPE-VARDICT:CAPS">'a{sv}'</link>. Free with g_variant_unref().
+ * Returns: (transfer full): A #GVariant of type
+ * ['a{sv}'][G-VARIANT-TYPE-VARDICT:CAPS].
+ * Free with g_variant_unref().
  *
  * Since: 2.30
  */
@@ -331,8 +369,7 @@ g_dbus_interface_skeleton_get_properties (GDBusInterfaceSkeleton *interface_)
   GVariant *ret;
   g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
   ret = G_DBUS_INTERFACE_SKELETON_GET_CLASS (interface_)->get_properties (interface_);
-  g_warn_if_fail (g_variant_is_floating (ret));
-  return ret;
+  return g_variant_take_ref (ret);
 }
 
 /**
@@ -344,7 +381,7 @@ g_dbus_interface_skeleton_get_properties (GDBusInterfaceSkeleton *interface_)
  *
  * For example, an exported D-Bus interface may queue up property
  * changes and emit the
- * <literal>org.freedesktop.DBus.Properties::PropertiesChanged</literal>
+ * `org.freedesktop.DBus.Properties::Propert``
  * signal later (e.g. in an idle handler). This technique is useful
  * for collapsing multiple property changes into one.
  *
@@ -370,7 +407,24 @@ static GDBusObject *
 g_dbus_interface_skeleton_get_object (GDBusInterface *interface_)
 {
   GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON (interface_);
-  return interface->priv->object;
+  GDBusObject *ret;
+  g_mutex_lock (&interface->priv->lock);
+  ret = interface->priv->object;
+  g_mutex_unlock (&interface->priv->lock);
+  return ret;
+}
+
+static GDBusObject *
+g_dbus_interface_skeleton_dup_object (GDBusInterface *interface_)
+{
+  GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON (interface_);
+  GDBusObject *ret;
+  g_mutex_lock (&interface->priv->lock);
+  ret = interface->priv->object;
+  if (ret != NULL)
+    g_object_ref (ret);
+  g_mutex_unlock (&interface->priv->lock);
+  return ret;
 }
 
 static void
@@ -378,11 +432,13 @@ g_dbus_interface_skeleton_set_object (GDBusInterface *interface_,
                                       GDBusObject    *object)
 {
   GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON (interface_);
+  g_mutex_lock (&interface->priv->lock);
   if (interface->priv->object != NULL)
     g_object_remove_weak_pointer (G_OBJECT (interface->priv->object), (gpointer *) &interface->priv->object);
   interface->priv->object = object;
   if (object != NULL)
     g_object_add_weak_pointer (G_OBJECT (interface->priv->object), (gpointer *) &interface->priv->object);
+  g_mutex_unlock (&interface->priv->lock);
 }
 
 static void
@@ -390,6 +446,7 @@ dbus_interface_interface_init (GDBusInterfaceIface *iface)
 {
   iface->get_info    = _g_dbus_interface_skeleton_get_info;
   iface->get_object  = g_dbus_interface_skeleton_get_object;
+  iface->dup_object  = g_dbus_interface_skeleton_dup_object;
   iface->set_object  = g_dbus_interface_skeleton_set_object;
 }
 
@@ -401,18 +458,13 @@ typedef struct
   GDBusInterfaceSkeleton       *interface;
   GDBusInterfaceMethodCallFunc  method_call_func;
   GDBusMethodInvocation        *invocation;
-  GMainContext                 *context;
 } DispatchData;
 
 static void
 dispatch_data_unref (DispatchData *data)
 {
   if (g_atomic_int_dec_and_test (&data->ref_count))
-    {
-      if (data->context != NULL)
-        g_main_context_unref (data->context);
-      g_free (data);
-    }
+    g_slice_free (DispatchData, data);
 }
 
 static DispatchData *
@@ -437,19 +489,29 @@ dispatch_invoke_in_context_func (gpointer user_data)
   return FALSE;
 }
 
-static gboolean
-dispatch_in_thread_func (GIOSchedulerJob *job,
-                         GCancellable    *cancellable,
-                         gpointer         user_data)
+static void
+dispatch_in_thread_func (GTask        *task,
+                         gpointer      source_object,
+                         gpointer      task_data,
+                         GCancellable *cancellable)
 {
-  DispatchData *data = user_data;
+  DispatchData *data = task_data;
+  GDBusInterfaceSkeletonFlags flags;
+  GDBusObject *object;
   gboolean authorized;
 
+  g_mutex_lock (&data->interface->priv->lock);
+  flags = data->interface->priv->flags;
+  object = data->interface->priv->object;
+  if (object != NULL)
+    g_object_ref (object);
+  g_mutex_unlock (&data->interface->priv->lock);
+
   /* first check on the enclosing object (if any), then the interface */
   authorized = TRUE;
-  if (data->interface->priv->object != NULL)
+  if (object != NULL)
     {
-      g_signal_emit_by_name (data->interface->priv->object,
+      g_signal_emit_by_name (object,
                              "authorize-method",
                              data->interface,
                              data->invocation,
@@ -467,7 +529,7 @@ dispatch_in_thread_func (GIOSchedulerJob *job,
   if (authorized)
     {
       gboolean run_in_thread;
-      run_in_thread = (data->interface->priv->flags & G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+      run_in_thread = (flags & G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
       if (run_in_thread)
         {
           /* might as well just re-use the existing thread */
@@ -483,8 +545,8 @@ dispatch_in_thread_func (GIOSchedulerJob *job,
       else
         {
           /* bah, back to original context */
-          g_main_context_invoke_full (data->context,
-                                      G_PRIORITY_DEFAULT,
+          g_main_context_invoke_full (g_task_get_context (task),
+                                      g_task_get_priority (task),
                                       dispatch_invoke_in_context_func,
                                       dispatch_data_ref (data),
                                       (GDestroyNotify) dispatch_data_unref);
@@ -495,7 +557,8 @@ dispatch_in_thread_func (GIOSchedulerJob *job,
       /* do nothing */
     }
 
-  return FALSE;
+  if (object != NULL)
+    g_object_unref (object);
 }
 
 static void
@@ -507,11 +570,20 @@ g_dbus_interface_method_dispatch_helper (GDBusInterfaceSkeleton       *interface
   gboolean has_default_class_handler;
   gboolean emit_authorized_signal;
   gboolean run_in_thread;
+  GDBusInterfaceSkeletonFlags flags;
+  GDBusObject *object;
 
   g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface));
   g_return_if_fail (method_call_func != NULL);
   g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
 
+  g_mutex_lock (&interface->priv->lock);
+  flags = interface->priv->flags;
+  object = interface->priv->object;
+  if (object != NULL)
+    g_object_ref (object);
+  g_mutex_unlock (&interface->priv->lock);
+
   /* optimization for the common case where
    *
    *  a) no handler is connected and class handler is not overridden (both interface and object); and
@@ -527,11 +599,11 @@ g_dbus_interface_method_dispatch_helper (GDBusInterfaceSkeleton       *interface
   emit_authorized_signal = (has_handlers || !has_default_class_handler);
   if (!emit_authorized_signal)
     {
-      if (interface->priv->object != NULL)
-        emit_authorized_signal = _g_dbus_object_skeleton_has_authorize_method_handlers (G_DBUS_OBJECT_SKELETON (interface->priv->object));
+      if (object != NULL)
+        emit_authorized_signal = _g_dbus_object_skeleton_has_authorize_method_handlers (G_DBUS_OBJECT_SKELETON (object));
     }
 
-  run_in_thread = (interface->priv->flags & G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+  run_in_thread = (flags & G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
   if (!emit_authorized_signal && !run_in_thread)
     {
       method_call_func (g_dbus_method_invocation_get_connection (invocation),
@@ -545,21 +617,23 @@ g_dbus_interface_method_dispatch_helper (GDBusInterfaceSkeleton       *interface
     }
   else
     {
+      GTask *task;
       DispatchData *data;
-      data = g_new0 (DispatchData, 1);
+
+      data = g_slice_new0 (DispatchData);
       data->interface = interface;
       data->method_call_func = method_call_func;
       data->invocation = invocation;
-      data->context = g_main_context_get_thread_default ();
       data->ref_count = 1;
-      if (data->context != NULL)
-        g_main_context_ref (data->context);
-      g_io_scheduler_push_job (dispatch_in_thread_func,
-                               data,
-                               (GDestroyNotify) dispatch_data_unref,
-                               G_PRIORITY_DEFAULT,
-                               NULL); /* GCancellable* */
+
+      task = g_task_new (interface, NULL, NULL, NULL);
+      g_task_set_task_data (task, data, (GDestroyNotify) dispatch_data_unref);
+      g_task_run_in_thread (task, dispatch_in_thread_func);
+      g_object_unref (task);
     }
+
+  if (object != NULL)
+    g_object_unref (object);
 }
 
 static void
@@ -580,11 +654,112 @@ skeleton_intercept_handle_method_call (GDBusConnection       *connection,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static ConnectionData *
+new_connection (GDBusConnection *connection,
+                guint            registration_id)
+{
+  ConnectionData *data;
+
+  data = g_slice_new0 (ConnectionData);
+  data->connection      = g_object_ref (connection);
+  data->registration_id = registration_id;
+
+  return data;
+}
+
+static void
+free_connection (ConnectionData *data)
+{
+  if (data != NULL)
+    {
+      g_object_unref (data->connection);
+      g_slice_free (ConnectionData, data);
+    }
+}
+
+static gboolean
+add_connection_locked (GDBusInterfaceSkeleton *interface_,
+                       GDBusConnection        *connection,
+                       GError                **error)
+{
+  ConnectionData *data;
+  guint registration_id;
+  gboolean ret = FALSE;
+
+  if (interface_->priv->hooked_vtable == NULL)
+    {
+      /* Hook the vtable since we need to intercept method calls for
+       * ::g-authorize-method and for dispatching in thread vs
+       * context
+       *
+       * We need to wait until subclasses have had time to initialize
+       * properly before building the hooked_vtable, so we create it
+       * once at the last minute.
+       */
+      interface_->priv->hooked_vtable = g_memdup (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable));
+      interface_->priv->hooked_vtable->method_call = skeleton_intercept_handle_method_call;
+    }
+
+  registration_id = g_dbus_connection_register_object (connection,
+                                                       interface_->priv->object_path,
+                                                       g_dbus_interface_skeleton_get_info (interface_),
+                                                       interface_->priv->hooked_vtable,
+                                                       interface_,
+                                                       NULL, /* user_data_free_func */
+                                                       error);
+
+  if (registration_id > 0)
+    {
+      data = new_connection (connection, registration_id);
+      interface_->priv->connections = g_slist_append (interface_->priv->connections, data);
+      ret = TRUE;
+    }
+
+  return ret;
+}
+
+static void
+remove_connection_locked (GDBusInterfaceSkeleton *interface_,
+                          GDBusConnection        *connection)
+{
+  ConnectionData *data;
+  GSList *l;
+
+  /* Get the connection in the list and unregister ... */
+  for (l = interface_->priv->connections; l != NULL; l = l->next)
+    {
+      data = l->data;
+      if (data->connection == connection)
+        {
+          g_warn_if_fail (g_dbus_connection_unregister_object (data->connection, data->registration_id));
+          free_connection (data);
+          interface_->priv->connections = g_slist_delete_link (interface_->priv->connections, l);
+          /* we are guaranteed that the connection is only added once, so bail out early */
+          goto out;
+        }
+    }
+ out:
+  ;
+}
+
+static void
+set_object_path_locked (GDBusInterfaceSkeleton *interface_,
+                        const gchar            *object_path)
+{
+  if (g_strcmp0 (interface_->priv->object_path, object_path) != 0)
+    {
+      g_free (interface_->priv->object_path);
+      interface_->priv->object_path = g_strdup (object_path);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 /**
  * g_dbus_interface_skeleton_get_connection:
  * @interface_: A #GDBusInterfaceSkeleton.
  *
- * Gets the connection that @interface_ is exported on, if any.
+ * Gets the first connection that @interface_ is exported on, if any.
  *
  * Returns: (transfer none): A #GDBusConnection or %NULL if @interface_ is
  * not exported anywhere. Do not free, the object belongs to @interface_.
@@ -594,8 +769,99 @@ skeleton_intercept_handle_method_call (GDBusConnection       *connection,
 GDBusConnection *
 g_dbus_interface_skeleton_get_connection (GDBusInterfaceSkeleton *interface_)
 {
+  ConnectionData  *data;
+  GDBusConnection *ret;
+
   g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
-  return interface_->priv->connection;
+  g_mutex_lock (&interface_->priv->lock);
+
+  ret = NULL;
+  if (interface_->priv->connections != NULL)
+    {
+      data = interface_->priv->connections->data;
+      if (data != NULL)
+        ret = data->connection;
+    }
+
+  g_mutex_unlock (&interface_->priv->lock);
+
+  return ret;
+}
+
+/**
+ * g_dbus_interface_skeleton_get_connections:
+ * @interface_: A #GDBusInterfaceSkeleton.
+ *
+ * Gets a list of the connections that @interface_ is exported on.
+ *
+ * Returns: (element-type GDBusConnection) (transfer full): A list of
+ *   all the connections that @interface_ is exported on. The returned
+ *   list should be freed with g_list_free() after each element has
+ *   been freed with g_object_unref().
+ *
+ * Since: 2.32
+ */
+GList *
+g_dbus_interface_skeleton_get_connections (GDBusInterfaceSkeleton *interface_)
+{
+  GList           *connections;
+  GSList          *l;
+  ConnectionData  *data;
+
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
+
+  g_mutex_lock (&interface_->priv->lock);
+  connections = NULL;
+
+  for (l = interface_->priv->connections; l != NULL; l = l->next)
+    {
+      data        = l->data;
+      connections = g_list_prepend (connections,
+                                    /* Return a reference to each connection */
+                                    g_object_ref (data->connection));
+    }
+
+  g_mutex_unlock (&interface_->priv->lock);
+
+  return g_list_reverse (connections);
+}
+
+/**
+ * g_dbus_interface_skeleton_has_connection:
+ * @interface_: A #GDBusInterfaceSkeleton.
+ * @connection: A #GDBusConnection.
+ *
+ * Checks if @interface_ is exported on @connection.
+ *
+ * Returns: %TRUE if @interface_ is exported on @connection, %FALSE otherwise.
+ *
+ * Since: 2.32
+ */
+gboolean
+g_dbus_interface_skeleton_has_connection (GDBusInterfaceSkeleton     *interface_,
+                                          GDBusConnection            *connection)
+{
+  GSList *l;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  g_mutex_lock (&interface_->priv->lock);
+
+  for (l = interface_->priv->connections; l != NULL; l = l->next)
+    {
+      ConnectionData *data = l->data;
+      if (data->connection == connection)
+        {
+          ret = TRUE;
+          goto out;
+        }
+    }
+
+ out:
+  g_mutex_unlock (&interface_->priv->lock);
+  return ret;
 }
 
 /**
@@ -612,8 +878,12 @@ g_dbus_interface_skeleton_get_connection (GDBusInterfaceSkeleton *interface_)
 const gchar *
 g_dbus_interface_skeleton_get_object_path (GDBusInterfaceSkeleton *interface_)
 {
+  const gchar *ret;
   g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
-  return interface_->priv->object_path;
+  g_mutex_lock (&interface_->priv->lock);
+  ret = interface_->priv->object_path;
+  g_mutex_unlock (&interface_->priv->lock);
+  return ret;
 }
 
 /**
@@ -625,9 +895,13 @@ g_dbus_interface_skeleton_get_object_path (GDBusInterfaceSkeleton *interface_)
  *
  * Exports @interface_ at @object_path on @connection.
  *
+ * This can be called multiple times to export the same @interface_
+ * onto multiple connections however the @object_path provided must be
+ * the same for all connections.
+ *
  * Use g_dbus_interface_skeleton_unexport() to unexport the object.
  *
- * Returns: %TRUE if the interface was exported, other %FALSE with
+ * Returns: %TRUE if the interface was exported on @connection, otherwise %FALSE with
  * @error set.
  *
  * Since: 2.30
@@ -638,49 +912,26 @@ g_dbus_interface_skeleton_export (GDBusInterfaceSkeleton  *interface_,
                                   const gchar             *object_path,
                                   GError                 **error)
 {
-  gboolean ret;
+  gboolean ret = FALSE;
 
-  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), 0);
-  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
-  g_return_val_if_fail (g_variant_is_object_path (object_path), 0);
-  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (g_variant_is_object_path (object_path), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-  ret = FALSE;
-  if (interface_->priv->registration_id > 0)
-    {
-      g_set_error_literal (error,
-                           G_IO_ERROR,
-                           G_IO_ERROR_FAILED, /* TODO: new error code */
-                           "The object is already exported");
-      goto out;
-    }
+  /* Assert that the object path is the same for multiple connections here */
+  g_return_val_if_fail (interface_->priv->object_path == NULL ||
+                        g_strcmp0 (interface_->priv->object_path, object_path) == 0, FALSE);
 
-  g_assert (interface_->priv->connection == NULL);
-  g_assert (interface_->priv->object_path == NULL);
-  g_assert (interface_->priv->hooked_vtable == NULL);
+  g_mutex_lock (&interface_->priv->lock);
 
-  /* Hook the vtable since we need to intercept method calls for
-   * ::g-authorize-method and for dispatching in thread vs
-   * context
-   */
-  interface_->priv->hooked_vtable = g_memdup (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable));
-  interface_->priv->hooked_vtable->method_call = skeleton_intercept_handle_method_call;
-
-  interface_->priv->connection = g_object_ref (connection);
-  interface_->priv->object_path = g_strdup (object_path);
-  interface_->priv->registration_id = g_dbus_connection_register_object (connection,
-                                                                         object_path,
-                                                                         g_dbus_interface_skeleton_get_info (interface_),
-                                                                         interface_->priv->hooked_vtable,
-                                                                         interface_,
-                                                                         NULL, /* user_data_free_func */
-                                                                         error);
-  if (interface_->priv->registration_id == 0)
-    goto out;
-
-  ret = TRUE;
+  /* Set the object path */
+  set_object_path_locked (interface_, object_path);
 
- out:
+  /* Add the connection */
+  ret = add_connection_locked (interface_, connection, error);
+
+  g_mutex_unlock (&interface_->priv->lock);
   return ret;
 }
 
@@ -688,8 +939,10 @@ g_dbus_interface_skeleton_export (GDBusInterfaceSkeleton  *interface_,
  * g_dbus_interface_skeleton_unexport:
  * @interface_: A #GDBusInterfaceSkeleton.
  *
- * Stops exporting an interface previously exported with
- * g_dbus_interface_skeleton_export().
+ * Stops exporting @interface_ on all connections it is exported on.
+ *
+ * To unexport @interface_ from only a single connection, use
+ * g_dbus_interface_skeleton_unexport_from_connection()
  *
  * Since: 2.30
  */
@@ -697,21 +950,59 @@ void
 g_dbus_interface_skeleton_unexport (GDBusInterfaceSkeleton *interface_)
 {
   g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_));
-  g_return_if_fail (interface_->priv->registration_id > 0);
+  g_return_if_fail (interface_->priv->connections != NULL);
+
+  g_mutex_lock (&interface_->priv->lock);
+
+  g_assert (interface_->priv->object_path != NULL);
+  g_assert (interface_->priv->hooked_vtable != NULL);
+
+  /* Remove all connections */
+  while (interface_->priv->connections != NULL)
+    {
+      ConnectionData *data = interface_->priv->connections->data;
+      remove_connection_locked (interface_, data->connection);
+    }
+
+  /* Unset the object path since there are no connections left */
+  set_object_path_locked (interface_, NULL);
+
+  g_mutex_unlock (&interface_->priv->lock);
+}
+
+
+/**
+ * g_dbus_interface_skeleton_unexport_from_connection:
+ * @interface_: A #GDBusInterfaceSkeleton.
+ * @connection: A #GDBusConnection.
+ *
+ * Stops exporting @interface_ on @connection.
+ *
+ * To stop exporting on all connections the interface is exported on,
+ * use g_dbus_interface_skeleton_unexport().
+ *
+ * Since: 2.32
+ */
+void
+g_dbus_interface_skeleton_unexport_from_connection (GDBusInterfaceSkeleton *interface_,
+                                                    GDBusConnection        *connection)
+{
+  g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_));
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (interface_->priv->connections != NULL);
+
+  g_mutex_lock (&interface_->priv->lock);
 
-  g_assert (interface_->priv->connection != NULL);
   g_assert (interface_->priv->object_path != NULL);
   g_assert (interface_->priv->hooked_vtable != NULL);
 
-  g_warn_if_fail (g_dbus_connection_unregister_object (interface_->priv->connection,
-                                                       interface_->priv->registration_id));
+  remove_connection_locked (interface_, connection);
+
+  /* Reset the object path if we removed the last connection */
+  if (interface_->priv->connections == NULL)
+    set_object_path_locked (interface_, NULL);
 
-  g_object_unref (interface_->priv->connection);
-  g_free (interface_->priv->object_path);
-  interface_->priv->connection = NULL;
-  interface_->priv->object_path = NULL;
-  interface_->priv->hooked_vtable = NULL;
-  interface_->priv->registration_id = 0;
+  g_mutex_unlock (&interface_->priv->lock);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */