chiark / gitweb /
bus: implement bus_path_{en,de}code_unique()
authorDavid Herrmann <dh.herrmann@gmail.com>
Fri, 10 Apr 2015 15:44:30 +0000 (17:44 +0200)
committerSven Eden <yamakuzure@gmx.net>
Tue, 14 Mar 2017 07:01:25 +0000 (08:01 +0100)
Whenever we provide a bus API that allows clients to create and manage
server-side objects, we need to provide a unique name for these objects.
There are two ways to provide them:
  1) Let the server choose a name and return it as method reply.
  2) Let the client pass its name of choice in the method arguments.

The first method is the easiest one to implement. However, it suffers from
a race condition: If a client creates an object asynchronously, it cannot
destroy that object until it received the method reply. It cannot know the
name of the new object, thus, it cannot destroy it. Furthermore, this
method enforces a round-trip. If the client _depends_ on the method call
to succeed (eg., it would close() the connection if it failed), the client
usually has no reason to wait for the method reply. Instead, the client
can immediately schedule further method calls on the newly created object
(in case the API guarantees in-order method-call handling).

The second method fixes both problems: The client passes an object name
with the method-call. The server uses it to create the object. Therefore,
the client can schedule object destruction even if the object-creation
hasn't finished, yet (again, requiring in-order method-call handling).
Furthermore, the client can schedule further method calls on the newly
created object, before the constructor returned.

There're two problems to solve, though:
  1) Object names are usually defined via dbus object paths, which are
     usually globally namespaced. Therefore, multiple clients must be able
     to choose unique object names without interference.
  2) If multiple libraries share the same bus connection, they must be
     able to choose unique object names without interference.

The first problem is solved easily by prefixing a name with the
unique-bus-name of a connection. The server side must enforce this and
reject any other name.
The second problem is solved by providing unique suffixes from within
sd-bus. As long as sd-bus always returns a fresh new ID, if requested,
multiple libraries will never interfere. This implementation re-uses
bus->cookie as ID generator, which already provides unique IDs for each
bus connection.

This patch introduces two new helpers:
  bus_path_encode_unique(sd_bus *bus,
                         const char *prefix,
                         const char *sender_id,
                         const char *external_id,
                         char **ret_path);
    This creates a new object-path via the template
    '/prefix/sender_id/external_id'. That is, it appends two new labels to
    the given prefix. If 'sender_id' is NULL, it will use
    bus->unique_name, if 'external_id' is NULL, it will allocate a fresh,
    unique cookie from bus->cookie.

  bus_path_decode_unique(const char *path,
                         const char *prefix,
                         char **ret_sender,
                         char **ret_external);
    This reverses what bus_path_encode_unique() did. It parses 'path' from
    the template '/prefix/sender/external' and returns both suffix-labels
    in 'ret_sender' and 'ret_external'. In case the template does not
    match, 0 is returned and both output arguments are set to NULL.
    Otherwise, 1 is returned and the output arguments contain the decoded
    labels.

Note: Client-side allocated IDs are inspired by the Wayland protocol
      (which itself was inspired by X11). Wayland uses those IDs heavily
      to avoid round-trips. Clients can create server-side objects and
      send method calls without any round-trip and waiting for any object
      IDs to be returned. But unlike Wayland, DBus uses gobally namespaced
      object names. Therefore, we have to add the extra step by adding the
      unique-name of the bus connection.

src/libelogind/sd-bus/bus-util.c
src/libelogind/sd-bus/bus-util.h
src/libelogind/sd-bus/test-bus-marshal.c

index c4e44b1..ee002a5 100644 (file)
@@ -34,6 +34,7 @@
 
 #include "sd-bus.h"
 #include "bus-error.h"
+#include "bus-label.h"
 #include "bus-message.h"
 #include "bus-util.h"
 #include "bus-internal.h"
@@ -1903,3 +1904,130 @@ int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet) {
 
         return 0;
 }
+
+/**
+ * bus_path_encode_unique() - encode unique object path
+ * @b: bus connection or NULL
+ * @prefix: object path prefix
+ * @sender_id: unique-name of client, or NULL
+ * @external_id: external ID to be chosen by client, or NULL
+ * @ret_path: storage for encoded object path pointer
+ *
+ * Whenever we provide a bus API that allows clients to create and manage
+ * server-side objects, we need to provide a unique name for these objects. If
+ * we let the server choose the name, we suffer from a race condition: If a
+ * client creates an object asynchronously, it cannot destroy that object until
+ * it received the method reply. It cannot know the name of the new object,
+ * thus, it cannot destroy it. Furthermore, it enforces a round-trip.
+ *
+ * Therefore, many APIs allow the client to choose the unique name for newly
+ * created objects. There're two problems to solve, though:
+ *    1) Object names are usually defined via dbus object paths, which are
+ *       usually globally namespaced. Therefore, multiple clients must be able
+ *       to choose unique object names without interference.
+ *    2) If multiple libraries share the same bus connection, they must be
+ *       able to choose unique object names without interference.
+ * The first problem is solved easily by prefixing a name with the
+ * unique-bus-name of a connection. The server side must enforce this and
+ * reject any other name. The second problem is solved by providing unique
+ * suffixes from within sd-bus.
+ *
+ * This helper allows clients to create unique object-paths. It uses the
+ * template '/prefix/sender_id/external_id' and returns the new path in
+ * @ret_path (must be freed by the caller).
+ * If @sender_id is NULL, the unique-name of @b is used. If @external_id is
+ * NULL, this function allocates a unique suffix via @b (by requesting a new
+ * cookie). If both @sender_id and @external_id are given, @b can be passed as
+ * NULL.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) {
+        _cleanup_free_ char *sender_label = NULL, *external_label = NULL;
+        char external_buf[DECIMAL_STR_MAX(uint64_t)], *p;
+        int r;
+
+        assert_return(b || (sender_id && external_id), -EINVAL);
+        assert_return(object_path_is_valid(prefix), -EINVAL);
+        assert_return(ret_path, -EINVAL);
+
+        if (!sender_id) {
+                r = sd_bus_get_unique_name(b, &sender_id);
+                if (r < 0)
+                        return r;
+        }
+
+        if (!external_id) {
+                xsprintf(external_buf, "%"PRIu64, ++b->cookie);
+                external_id = external_buf;
+        }
+
+        sender_label = bus_label_escape(sender_id);
+        if (!sender_label)
+                return -ENOMEM;
+
+        external_label = bus_label_escape(external_id);
+        if (!external_label)
+                return -ENOMEM;
+
+        p = strjoin(prefix, "/", sender_label, "/", external_label, NULL);
+        if (!p)
+                return -ENOMEM;
+
+        *ret_path = p;
+        return 0;
+}
+
+/**
+ * bus_path_decode_unique() - decode unique object path
+ * @path: object path to decode
+ * @prefix: object path prefix
+ * @ret_sender: output parameter for sender-id label
+ * @ret_external: output parameter for external-id label
+ *
+ * This does the reverse of bus_path_encode_unique() (see its description for
+ * details). Both trailing labels, sender-id and external-id, are unescaped and
+ * returned in the given output parameters (the caller must free them).
+ *
+ * Note that this function returns 0 if the path does not match the template
+ * (see bus_path_encode_unique()), 1 if it matched.
+ *
+ * Returns: Negative error code on failure, 0 if the given object path does not
+ *          match the template (return parameters are set to NULL), 1 if it was
+ *          parsed successfully (return parameters contain allocated labels).
+ */
+int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) {
+        const char *p, *q;
+        char *sender, *external;
+
+        assert(object_path_is_valid(path));
+        assert(object_path_is_valid(prefix));
+        assert(ret_sender);
+        assert(ret_external);
+
+        p = object_path_startswith(path, prefix);
+        if (!p) {
+                *ret_sender = NULL;
+                *ret_external = NULL;
+                return 0;
+        }
+
+        q = strchr(p, '/');
+        if (!q) {
+                *ret_sender = NULL;
+                *ret_external = NULL;
+                return 0;
+        }
+
+        sender = bus_label_unescape_n(p, q - p);
+        external = bus_label_unescape(q + 1);
+        if (!sender || !external) {
+                free(sender);
+                free(external);
+                return -ENOMEM;
+        }
+
+        *ret_sender = sender;
+        *ret_external = external;
+        return 1;
+}
index cc16a9d..b97729e 100644 (file)
@@ -211,3 +211,6 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet);
 DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free);
 
 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
+
+int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path);
+int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external);
index 7569ff9..f8ecadf 100644 (file)
 #include "bus-dump.h"
 #include "bus-label.h"
 
+static void test_bus_path_encode_unique(void) {
+        _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL;
+
+        assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix"));
+        assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix"));
+        assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d);
+        assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d);
+        assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, ""));
+}
+
 static void test_bus_path_encode(void) {
         _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL;
 
@@ -357,6 +367,7 @@ int main(int argc, char *argv[]) {
 
         test_bus_label_escape();
         test_bus_path_encode();
+        test_bus_path_encode_unique();
 
         return 0;
 }