From b78378a2ba194084d85810dd5721217bb4337edc Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Fri, 10 Apr 2015 17:44:30 +0200 Subject: [PATCH] bus: implement bus_path_{en,de}code_unique() 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 | 128 +++++++++++++++++++++++ src/libelogind/sd-bus/bus-util.h | 3 + src/libelogind/sd-bus/test-bus-marshal.c | 11 ++ 3 files changed, 142 insertions(+) diff --git a/src/libelogind/sd-bus/bus-util.c b/src/libelogind/sd-bus/bus-util.c index c4e44b1fa..ee002a55f 100644 --- a/src/libelogind/sd-bus/bus-util.c +++ b/src/libelogind/sd-bus/bus-util.c @@ -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; +} diff --git a/src/libelogind/sd-bus/bus-util.h b/src/libelogind/sd-bus/bus-util.h index cc16a9d69..b97729e2a 100644 --- a/src/libelogind/sd-bus/bus-util.h +++ b/src/libelogind/sd-bus/bus-util.h @@ -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); diff --git a/src/libelogind/sd-bus/test-bus-marshal.c b/src/libelogind/sd-bus/test-bus-marshal.c index 7569ff98b..f8ecadf49 100644 --- a/src/libelogind/sd-bus/test-bus-marshal.c +++ b/src/libelogind/sd-bus/test-bus-marshal.c @@ -39,6 +39,16 @@ #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; } -- 2.30.2