]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
driver: Implement Properties, with Features and Interfaces properties
authorSimon McVittie <smcv@collabora.com>
Wed, 31 May 2017 19:34:25 +0000 (20:34 +0100)
committerSimon McVittie <smcv@collabora.com>
Thu, 8 Jun 2017 15:59:50 +0000 (16:59 +0100)
We recommend using Properties for this sort of thing when designing
D-Bus APIs, so it's a bit hypocritical that the reference message bus
didn't. The Features and Interfaces properties can be used for
feature-discovery as we add new larger features, while the Properties
support can be used for finer-grained properties, for example in the
interface planned for #100344.

Signed-off-by: Simon McVittie <smcv@collabora.com>
Reviewed-by: Philip Withnall <withnall@endlessm.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=101257

bus/driver.c

index c3f344ad34e756ecde0476d792c42d54887b8324..373e53eb8de7debfae577cb2d9a40dbc41a05662 100644 (file)
@@ -2289,6 +2289,26 @@ out:
   return ret;
 }
 
+static dbus_bool_t bus_driver_handle_get (DBusConnection *connection,
+                                          BusTransaction *transaction,
+                                          DBusMessage *message,
+                                          DBusError *error);
+
+static dbus_bool_t bus_driver_handle_get_all (DBusConnection *connection,
+                                              BusTransaction *transaction,
+                                              DBusMessage *message,
+                                              DBusError *error);
+
+static dbus_bool_t bus_driver_handle_set (DBusConnection *connection,
+                                          BusTransaction *transaction,
+                                          DBusMessage *message,
+                                          DBusError *error);
+
+static dbus_bool_t features_getter (BusContext      *context,
+                                    DBusMessageIter *variant_iter);
+static dbus_bool_t interfaces_getter (BusContext      *context,
+                                      DBusMessageIter *variant_iter);
+
 typedef enum
 {
   /* Various older methods were available at every object path. We have to
@@ -2312,6 +2332,14 @@ typedef struct
   MethodFlags flags;
 } MessageHandler;
 
+typedef struct
+{
+  const char *name;
+  const char *type;
+  dbus_bool_t (* getter) (BusContext      *context,
+                          DBusMessageIter *variant_iter);
+} PropertyHandler;
+
 /* For speed it might be useful to sort this in order of
  * frequency of use (but doesn't matter with only a few items
  * anyhow)
@@ -2413,9 +2441,22 @@ static const MessageHandler dbus_message_handlers[] = {
   { NULL, NULL, NULL, NULL }
 };
 
+static const PropertyHandler dbus_property_handlers[] = {
+  { "Features", "as", features_getter },
+  { "Interfaces", "as", interfaces_getter },
+  { NULL, NULL, NULL }
+};
+
 static dbus_bool_t bus_driver_handle_introspect (DBusConnection *,
     BusTransaction *, DBusMessage *, DBusError *);
 
+static const MessageHandler properties_message_handlers[] = {
+  { "Get", "ss", "v", bus_driver_handle_get, METHOD_FLAG_NONE },
+  { "GetAll", "s", "a{sv}", bus_driver_handle_get_all, METHOD_FLAG_NONE },
+  { "Set", "ssv", "", bus_driver_handle_set, METHOD_FLAG_NONE },
+  { NULL, NULL, NULL, NULL }
+};
+
 static const MessageHandler introspectable_message_handlers[] = {
   { "Introspect", "", DBUS_TYPE_STRING_AS_STRING, bus_driver_handle_introspect,
     METHOD_FLAG_ANY_PATH },
@@ -2459,6 +2500,10 @@ typedef enum
    * Introspectable is also useful at all object paths. */
   INTERFACE_FLAG_ANY_PATH = (1 << 0),
 
+  /* Set this flag for interfaces that should not show up in the
+   * Interfaces property. */
+  INTERFACE_FLAG_UNINTERESTING = (1 << 1),
+
   INTERFACE_FLAG_NONE = 0
 } InterfaceFlags;
 
@@ -2467,6 +2512,7 @@ typedef struct {
   const MessageHandler *message_handlers;
   const char *extra_introspection;
   InterfaceFlags flags;
+  const PropertyHandler *property_handlers;
 } InterfaceHandler;
 
 /* These should ideally be sorted by frequency of use, although it
@@ -2484,9 +2530,26 @@ static InterfaceHandler interface_handlers[] = {
     "    <signal name=\"NameAcquired\">\n"
     "      <arg type=\"s\"/>\n"
     "    </signal>\n",
-    INTERFACE_FLAG_ANY_PATH },
+    /* Not in the Interfaces property because if you can get the properties
+     * of the o.fd.DBus interface, then you certainly have the o.fd.DBus
+     * interface, so there is little point in listing it explicitly.
+     * Partially available at all paths for backwards compatibility. */
+    INTERFACE_FLAG_ANY_PATH | INTERFACE_FLAG_UNINTERESTING,
+    dbus_property_handlers },
+  { DBUS_INTERFACE_PROPERTIES, properties_message_handlers,
+    "    <signal name=\"PropertiesChanged\">\n"
+    "      <arg type=\"s\" name=\"interface_name\"/>\n"
+    "      <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"
+    "      <arg type=\"as\" name=\"invalidated_properties\"/>\n"
+    "    </signal>\n",
+    /* Not in the Interfaces property because if you can get the properties
+     * of the o.fd.DBus interface, then you certainly have Properties. */
+    INTERFACE_FLAG_UNINTERESTING },
   { DBUS_INTERFACE_INTROSPECTABLE, introspectable_message_handlers, NULL,
-    INTERFACE_FLAG_ANY_PATH },
+    /* Not in the Interfaces property because introspection isn't really a
+     * feature in the same way as e.g. Monitoring.
+     * Available at all paths so tools like d-feet can start from "/". */
+    INTERFACE_FLAG_ANY_PATH | INTERFACE_FLAG_UNINTERESTING },
   { DBUS_INTERFACE_MONITORING, monitoring_message_handlers, NULL,
     INTERFACE_FLAG_NONE },
 #ifdef DBUS_ENABLE_VERBOSE_MODE
@@ -2542,6 +2605,7 @@ bus_driver_generate_introspect_string (DBusString *xml,
 {
   const InterfaceHandler *ih;
   const MessageHandler *mh;
+  const PropertyHandler *ph;
 
   if (!_dbus_string_append (xml, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE))
     return FALSE;
@@ -2573,6 +2637,20 @@ bus_driver_generate_introspect_string (DBusString *xml,
             return FALSE;
         }
 
+      for (ph = ih->property_handlers; ph != NULL && ph->name != NULL; ph++)
+        {
+          /* We only have constant properties so far, so hard-code that bit */
+          if (!_dbus_string_append_printf (xml,
+                                           "    <property name=\"%s\" type=\"%s\" access=\"read\">\n",
+                                           ph->name, ph->type))
+            return FALSE;
+
+          if (!_dbus_string_append (xml,
+                                    "      <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"const\"/>\n"
+                                    "    </property>\n"))
+            return FALSE;
+        }
+
       if (ih->extra_introspection != NULL &&
           !_dbus_string_append (xml, ih->extra_introspection))
         return FALSE;
@@ -2826,3 +2904,298 @@ bus_driver_remove_connection (DBusConnection *connection)
    * with the bus driver.
    */
 }
+
+static dbus_bool_t
+features_getter (BusContext      *context,
+                 DBusMessageIter *variant_iter)
+{
+  DBusMessageIter arr_iter;
+
+  if (!dbus_message_iter_open_container (variant_iter, DBUS_TYPE_ARRAY,
+                                         DBUS_TYPE_STRING_AS_STRING,
+                                         &arr_iter))
+    return FALSE;
+
+  if (bus_apparmor_enabled ())
+    {
+      const char *s = "AppArmor";
+
+      if (!dbus_message_iter_append_basic (&arr_iter, DBUS_TYPE_STRING, &s))
+        goto abandon;
+    }
+
+  if (bus_selinux_enabled ())
+    {
+      const char *s = "SELinux";
+
+      if (!dbus_message_iter_append_basic (&arr_iter, DBUS_TYPE_STRING, &s))
+        goto abandon;
+    }
+
+  if (bus_context_get_systemd_activation (context))
+    {
+      const char *s = "SystemdActivation";
+
+      if (!dbus_message_iter_append_basic (&arr_iter, DBUS_TYPE_STRING, &s))
+        goto abandon;
+    }
+
+  return dbus_message_iter_close_container (variant_iter, &arr_iter);
+
+abandon:
+  dbus_message_iter_abandon_container (variant_iter, &arr_iter);
+  return FALSE;
+}
+
+static dbus_bool_t
+interfaces_getter (BusContext      *context,
+                   DBusMessageIter *variant_iter)
+{
+  DBusMessageIter arr_iter;
+  const InterfaceHandler *ih;
+
+  if (!dbus_message_iter_open_container (variant_iter, DBUS_TYPE_ARRAY,
+                                         DBUS_TYPE_STRING_AS_STRING,
+                                         &arr_iter))
+    return FALSE;
+
+  for (ih = interface_handlers; ih->name != NULL; ih++)
+    {
+      if (ih->flags & INTERFACE_FLAG_UNINTERESTING)
+        continue;
+
+      if (!dbus_message_iter_append_basic (&arr_iter, DBUS_TYPE_STRING,
+                                           &ih->name))
+        goto abandon;
+    }
+
+  return dbus_message_iter_close_container (variant_iter, &arr_iter);
+
+abandon:
+  dbus_message_iter_abandon_container (variant_iter, &arr_iter);
+  return FALSE;
+}
+
+static const InterfaceHandler *
+bus_driver_find_interface (const char  *name,
+                           dbus_bool_t  canonical_path,
+                           DBusError   *error)
+{
+  const InterfaceHandler *ih;
+
+  for (ih = interface_handlers; ih->name != NULL; ih++)
+    {
+      if (!(canonical_path || (ih->flags & INTERFACE_FLAG_ANY_PATH)))
+        continue;
+
+      if (strcmp (name, ih->name) == 0)
+        return ih;
+    }
+
+  dbus_set_error (error, DBUS_ERROR_UNKNOWN_INTERFACE,
+                  "Interface \"%s\" not found", name);
+  return NULL;
+}
+
+static const PropertyHandler *
+interface_handler_find_property (const InterfaceHandler *ih,
+                                 const char             *name,
+                                 DBusError              *error)
+{
+  const PropertyHandler *ph;
+
+  for (ph = ih->property_handlers; ph != NULL && ph->name != NULL; ph++)
+    {
+      if (strcmp (name, ph->name) == 0)
+        return ph;
+    }
+
+  dbus_set_error (error, DBUS_ERROR_UNKNOWN_PROPERTY,
+                  "Property \"%s.%s\" not found", ih->name, name);
+  return NULL;
+}
+
+static dbus_bool_t
+bus_driver_handle_get (DBusConnection *connection,
+                       BusTransaction *transaction,
+                       DBusMessage    *message,
+                       DBusError      *error)
+{
+  const InterfaceHandler *ih;
+  const PropertyHandler *handler;
+  const char *iface;
+  const char *prop;
+  BusContext *context;
+  DBusMessage *reply = NULL;
+  DBusMessageIter iter;
+  DBusMessageIter var_iter;
+
+  /* The message signature has already been checked for us,
+   * so this should always succeed. */
+  if (!dbus_message_get_args (message, error,
+                              DBUS_TYPE_STRING, &iface,
+                              DBUS_TYPE_STRING, &prop,
+                              DBUS_TYPE_INVALID))
+    return FALSE;
+
+  /* We only implement Properties on /org/freedesktop/DBus so far. */
+  ih = bus_driver_find_interface (iface, TRUE, error);
+
+  if (ih == NULL)
+    return FALSE;
+
+  handler = interface_handler_find_property (ih, prop, error);
+
+  if (handler == NULL)
+    return FALSE;
+
+  context = bus_transaction_get_context (transaction);
+
+  reply = dbus_message_new_method_return (message);
+
+  if (reply == NULL)
+    goto oom;
+
+  dbus_message_iter_init_append (reply, &iter);
+
+  if (!dbus_message_iter_open_container (&iter, DBUS_TYPE_VARIANT,
+                                         handler->type, &var_iter))
+    goto oom;
+
+  if (!handler->getter (context, &var_iter))
+    {
+      dbus_message_iter_abandon_container (&iter, &var_iter);
+      goto oom;
+    }
+
+  if (!dbus_message_iter_close_container (&iter, &var_iter))
+    goto oom;
+
+  if (!bus_transaction_send_from_driver (transaction, connection, reply))
+    goto oom;
+
+  dbus_message_unref (reply);
+  return TRUE;
+
+oom:
+  if (reply != NULL)
+    dbus_message_unref (reply);
+
+  BUS_SET_OOM (error);
+  return FALSE;
+}
+
+static dbus_bool_t
+bus_driver_handle_get_all (DBusConnection *connection,
+                           BusTransaction *transaction,
+                           DBusMessage    *message,
+                           DBusError      *error)
+{
+  const InterfaceHandler *ih;
+  const char *iface;
+  const PropertyHandler *ph;
+  DBusMessageIter reply_iter;
+  DBusMessageIter array_iter;
+  BusContext *context;
+  DBusMessage *reply = NULL;
+
+  /* The message signature has already been checked for us,
+   * so this should always succeed. */
+  if (!dbus_message_get_args (message, error,
+                              DBUS_TYPE_STRING, &iface,
+                              DBUS_TYPE_INVALID))
+    return FALSE;
+
+  /* We only implement Properties on /org/freedesktop/DBus so far. */
+  ih = bus_driver_find_interface (iface, TRUE, error);
+
+  if (ih == NULL)
+    return FALSE;
+
+  context = bus_transaction_get_context (transaction);
+
+  reply = _dbus_asv_new_method_return (message, &reply_iter, &array_iter);
+
+  if (reply == NULL)
+    goto oom;
+
+  for (ph = ih->property_handlers; ph != NULL && ph->name != NULL; ph++)
+    {
+      DBusMessageIter entry_iter;
+      DBusMessageIter var_iter;
+
+      if (!_dbus_asv_open_entry (&array_iter, &entry_iter, ph->name,
+                                 ph->type, &var_iter))
+        goto oom_abandon_message;
+
+      if (!ph->getter (context, &var_iter))
+        {
+          _dbus_asv_abandon_entry (&array_iter, &entry_iter, &var_iter);
+          goto oom_abandon_message;
+        }
+
+      if (!_dbus_asv_close_entry (&array_iter, &entry_iter, &var_iter))
+        goto oom_abandon_message;
+    }
+
+  if (!_dbus_asv_close (&reply_iter, &array_iter))
+    goto oom;
+
+  if (!bus_transaction_send_from_driver (transaction, connection, reply))
+    goto oom;
+
+  dbus_message_unref (reply);
+  return TRUE;
+
+oom_abandon_message:
+  _dbus_asv_abandon (&reply_iter, &array_iter);
+  /* fall through */
+oom:
+  if (reply != NULL)
+    dbus_message_unref (reply);
+
+  BUS_SET_OOM (error);
+  return FALSE;
+}
+
+static dbus_bool_t
+bus_driver_handle_set (DBusConnection *connection,
+                       BusTransaction *transaction,
+                       DBusMessage    *message,
+                       DBusError      *error)
+{
+  const InterfaceHandler *ih;
+  const char *iface;
+  const char *prop;
+  const PropertyHandler *handler;
+  DBusMessageIter iter;
+
+  /* We already checked this in bus_driver_handle_message() */
+  _dbus_assert (dbus_message_has_signature (message, "ssv"));
+
+  if (!dbus_message_iter_init (message, &iter))
+    _dbus_assert_not_reached ("Message type was already checked to be 'ssv'");
+
+  dbus_message_iter_get_basic (&iter, &iface);
+
+  if (!dbus_message_iter_next (&iter))
+    _dbus_assert_not_reached ("Message type was already checked to be 'ssv'");
+
+  dbus_message_iter_get_basic (&iter, &prop);
+
+  /* We only implement Properties on /org/freedesktop/DBus so far. */
+  ih = bus_driver_find_interface (iface, TRUE, error);
+
+  if (ih == NULL)
+    return FALSE;
+
+  handler = interface_handler_find_property (ih, prop, error);
+
+  if (handler == NULL)
+    return FALSE;
+
+  /* We don't implement any properties that can be set yet. */
+  dbus_set_error (error, DBUS_ERROR_PROPERTY_READ_ONLY,
+                  "Property '%s.%s' cannot be set", iface, prop);
+  return FALSE;
+}