3

Is there a way to check for listening clients in DBus?

Is it even possible to do? I'm using gdbus.

Background

I'm creating service which interfaces with serial ports and I want to implicitly open serial ports if someone is listening and automatically close it if last client disconnects. I could do it with open/close methods but there is risk that one client closes connection when other is still listening.

Another solution to my problem would be connection counting, but there is also risk that client forgets to close port or crashes.

Do you have any other idea how to implement this?

My code (shortened)

Based on: https://github.com/bratsche/glib/blob/master/gio/tests/gdbus-example-server.c

#include <gio/gio.h>
#include <stdlib.h>

#ifdef G_OS_UNIX
#include <unistd.h>
#endif

/* ---------------------------------------------------------------------------------------------------- */

static GDBusNodeInfo *introspection_data = NULL;

/* Introspection data for the service we are exporting */
static const gchar introspection_xml[] =
  "<node>"
  "  <interface name='info.skorepa.serial.port'>"
  "    <signal name='DataRecieved'>"
  "      <arg type='ay' name='data'/>"
  "    </signal>"
  "  </interface>"
  "</node>";

/* ---------------------------------------------------------------------------------------------------- */

static void
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)
{
  // nothing - signal only
}

static GVariant *
handle_get_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GError          **error,
                     gpointer          user_data)
{
  // nothing - signal only
}

static gboolean
handle_set_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GVariant         *value,
                     GError          **error,
                     gpointer          user_data)
{
  // nothing - no properties
}


/* for now */
static const GDBusInterfaceVTable interface_vtable =
{
  handle_method_call,
  handle_get_property,
  handle_set_property
};

/* ---------------------------------------------------------------------------------------------------- */
// Here I emit signal - for now I just emit every 2 seconds
static gboolean
on_timeout_cb (gpointer user_data)
{
  GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
  GVariantBuilder *builder;
  GVariantBuilder *invalidated_builder;
  GError *error;

  error = NULL;
  printf("Constructing array\n");
  builder = g_variant_builder_new (G_VARIANT_TYPE ("ay"));
  printf("Adding 65\n");
  g_variant_builder_add (builder,
                         "y",
                         65);
  printf("Adding 66\n");
  g_variant_builder_add (builder,
                         "y",
                         66);
  printf("Emitting signal\n");
  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 "/info/skorepa/TestObject",
                                 "info.skorepa.serial.port",
                                 "DataRecieved",
                                 g_variant_new ("(ay)",
                                                builder),
                                 &error);
  printf("Checking for errors\n");
  g_assert_no_error (error);


  return TRUE;
}

/* ---------------------------------------------------------------------------------------------------- */

static void
on_bus_acquired (GDBusConnection *connection,
                 const gchar     *name,
                 gpointer         user_data)
{
  guint registration_id;

  registration_id = g_dbus_connection_register_object (connection,
                                                       "/info/skorepa/TestObject",
                                                       introspection_data->interfaces[0],
                                                       &interface_vtable,
                                                       NULL,  /* user_data */
                                                       NULL,  /* user_data_free_func */
                                                       NULL); /* GError** */
  g_assert (registration_id > 0);

  /* swap value of properties Foo and Bar every two seconds */
  g_timeout_add_seconds (2,
                         on_timeout_cb,
                         connection);
}

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
}

static void
on_name_lost (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
  exit (1);
}

int
main (int argc, char *argv[])
{
  guint owner_id;
  GMainLoop *loop;

  g_type_init ();

  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
  g_assert (introspection_data != NULL);

  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
                             "info.skorepa.serial",
                             G_BUS_NAME_OWNER_FLAGS_NONE,
                             on_bus_acquired,
                             on_name_acquired,
                             on_name_lost,
                             NULL,
                             NULL);

  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);

  g_bus_unown_name (owner_id);

  g_dbus_node_info_unref (introspection_data);

  return 0;
}

Compiled using:

gcc signal-sample.c `pkg-config --cflags --libs glib-2.0 gio-2.0` -o test

Thank you

  • Why downvote? I would like to know how to improve my question. – jakub.skorepa May 12 '15 at 16:37
  • I would recommend adding your code to your question. That way you will have a better chance of getting a helpful response. – Ted May 12 '15 at 16:51
  • I'm not sure why you got a downvote either. I disagree with Ted in that I don't think code would help. You're obviously very early in your exploration of this idea. However, it would be a good idea to show some research you've done to try to solve the problem. Have you consulted any DBus resources that didn't have your answer? – skrrgwasme May 12 '15 at 17:19
  • Try giving [this DBus tutorial](http://linoxide.com/how-tos/d-bus-ipc-mechanism-linux/) a read. – skrrgwasme May 12 '15 at 17:25

1 Answers1

1

Is there a way to check for listening clients in DBus?

No, it is not possible, due to the way D-Bus is designed.

When a client wants to subscribe to a signal, they send an AddMatch method call to the D-Bus daemon, which registers that state internally. When your service emits a signal, it sends the signal to the D-Bus daemon, which then forwards it to the clients who have subscribed to that signal (subject to various policy rules about broadcasts and permissions). Your service can’t know about the internal subscription state in the D-Bus daemon.

The pattern for handling this kind of thing is for your service to explicitly expose a subscribe or open method which clients must call in order to open a serial port. You can have a second method for closing the serial port when the client is done with it; and you can also listen for client disconnections to close the ports automatically. (In GDBus, this is done using g_bus_watch_name() and passing it the unique name of the client, which looks something like :1.5.)

Philip Withnall
  • 5,293
  • 14
  • 28