chiark / gitweb /
bus: parse matches locally and allow registration of callbacks for them
[elogind.git] / src / libsystemd-bus / bus-match.c
diff --git a/src/libsystemd-bus/bus-match.c b/src/libsystemd-bus/bus-match.c
new file mode 100644 (file)
index 0000000..ff1edfa
--- /dev/null
@@ -0,0 +1,978 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-match.h"
+
+/* Example:
+ *
+ *  A: type=signal,sender=foo,interface=bar
+ *  B: type=signal,sender=quux,interface=fips
+ *  C: type=signal,sender=quux,interface=waldo
+ *  D: type=signal,member=test
+ *  E: sender=miau
+ *  F: type=signal
+ *  G: type=signal
+ *
+ *  results in this tree:
+ *
+ *  BUS_MATCH_ROOT
+ *  + BUS_MATCH_MESSAGE_TYPE
+ *  | ` BUS_MATCH_VALUE: value == signal
+ *  |   + DBUS_MATCH_SENDER
+ *  |   | + BUS_MATCH_VALUE: value == foo
+ *  |   | | ` DBUS_MATCH_INTERFACE
+ *  |   | |   ` BUS_MATCH_VALUE: value == bar
+ *  |   | |     ` BUS_MATCH_LEAF: A
+ *  |   | ` BUS_MATCH_VALUE: value == quux
+ *  |   |   ` DBUS_MATCH_INTERFACE
+ *  |   |     | BUS_MATCH_VALUE: value == fips
+ *  |   |     | ` BUS_MATCH_LEAF: B
+ *  |   |     ` BUS_MATCH_VALUE: value == waldo
+ *  |   |       ` BUS_MATCH_LEAF: C
+ *  |   + DBUS_MATCH_MEMBER
+ *  |   | ` BUS_MATCH_VALUE: value == test
+ *  |   |   ` BUS_MATCH_LEAF: D
+ *  |   + BUS_MATCH_LEAF: F
+ *  |   ` BUS_MATCH_LEAF: G
+ *  ` BUS_MATCH_SENDER
+ *    ` BUS_MATCH_VALUE: value == miau
+ *      ` BUS_MATCH_LEAF: E
+ */
+
+static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) {
+        return t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_ARG_NAMESPACE_LAST;
+}
+
+static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) {
+        return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) ||
+                (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST);
+}
+
+static void bus_match_node_free(struct bus_match_node *node) {
+        assert(node);
+        assert(node->parent);
+        assert(!node->child);
+        assert(node->type != BUS_MATCH_ROOT);
+        assert(node->type < _BUS_MATCH_NODE_TYPE_MAX);
+
+        if (node->parent->child) {
+                /* We are apparently linked into the parent's child
+                 * list. Let's remove us from there. */
+                if (node->prev) {
+                        assert(node->prev->next == node);
+                        node->prev->next = node->next;
+                } else {
+                        assert(node->parent->child == node);
+                        node->parent->child = node->next;
+                }
+
+                if (node->next)
+                        node->next->prev = node->prev;
+        }
+
+        if (node->type == BUS_MATCH_VALUE) {
+                /* We might be in the parent's hash table, so clean
+                 * this up */
+
+                if (node->parent->type == BUS_MATCH_MESSAGE_TYPE)
+                        hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8));
+                else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str)
+                        hashmap_remove(node->parent->compare.children, node->value.str);
+
+                free(node->value.str);
+        }
+
+        if (BUS_MATCH_IS_COMPARE(node->type)) {
+                assert(hashmap_isempty(node->compare.children));
+                hashmap_free(node->compare.children);
+        }
+
+        free(node);
+}
+
+static bool bus_match_node_maybe_free(struct bus_match_node *node) {
+        assert(node);
+
+        if (node->child)
+                return false;
+
+        if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children))
+                return true;
+
+        bus_match_node_free(node);
+        return true;
+}
+
+static bool value_node_test(
+                struct bus_match_node *node,
+                enum bus_match_node_type parent_type,
+                uint8_t value_u8,
+                const char *value_str) {
+
+        assert(node);
+        assert(node->type == BUS_MATCH_VALUE);
+
+        /* Tests parameters against this value node, doing prefix
+         * magic and stuff. */
+
+        switch (parent_type) {
+
+        case BUS_MATCH_MESSAGE_TYPE:
+                return node->value.u8 == value_u8;
+
+        case BUS_MATCH_SENDER:
+        case BUS_MATCH_DESTINATION:
+        case BUS_MATCH_INTERFACE:
+        case BUS_MATCH_MEMBER:
+        case BUS_MATCH_PATH:
+        case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+                return streq(node->value.str, value_str);
+
+        case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+                return namespace_simple_pattern(node->value.str, value_str);
+
+        case BUS_MATCH_PATH_NAMESPACE:
+                return path_simple_pattern(node->value.str, value_str);
+
+        case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+                return path_complex_pattern(node->value.str, value_str);
+
+        default:
+                assert_not_reached("Invalid node type");
+        }
+}
+
+static bool value_node_same(
+                struct bus_match_node *node,
+                enum bus_match_node_type parent_type,
+                uint8_t value_u8,
+                const char *value_str) {
+
+        /* Tests parameters against this value node, not doing prefix
+         * magic and stuff, i.e. this one actually compares the match
+         * itself.*/
+
+        assert(node);
+        assert(node->type == BUS_MATCH_VALUE);
+
+        switch (parent_type) {
+
+        case BUS_MATCH_MESSAGE_TYPE:
+                return node->value.u8 == value_u8;
+
+        case BUS_MATCH_SENDER:
+        case BUS_MATCH_DESTINATION:
+        case BUS_MATCH_INTERFACE:
+        case BUS_MATCH_MEMBER:
+        case BUS_MATCH_PATH:
+        case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+        case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+        case BUS_MATCH_PATH_NAMESPACE:
+        case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+                return streq(node->value.str, value_str);
+
+        default:
+                assert_not_reached("Invalid node type");
+        }
+}
+
+int bus_match_run(
+                sd_bus *bus,
+                struct bus_match_node *node,
+                int ret,
+                sd_bus_message *m) {
+
+
+        const char *test_str = NULL;
+        uint8_t test_u8 = 0;
+        int r;
+
+        assert(m);
+
+        if (!node)
+                return 0;
+
+        /* Not these special semantics: when traversing the tree we
+         * usually let bus_match_run() when called for a node
+         * recursively invoke bus_match_run(). There's are two
+         * exceptions here though, which are BUS_NODE_ROOT (which
+         * cannot have a sibling), and BUS_NODE_VALUE (whose siblings
+         * are invoked anyway by its parent. */
+
+        switch (node->type) {
+
+        case BUS_MATCH_ROOT:
+
+                /* Run all children. Since we cannot have any siblings
+                 * we won't call any. The children of the root node
+                 * are compares or leaves, they will automatically
+                 * call their siblings. */
+                return bus_match_run(bus, node->child, ret, m);
+
+        case BUS_MATCH_VALUE:
+
+                /* Run all children. We don't execute any siblings, we
+                 * assume our caller does that. The children of value
+                 * nodes are compares or leaves, they will
+                 * automatically call their siblings */
+
+                assert(node->child);
+                return bus_match_run(bus, node->child, ret, m);
+
+        case BUS_MATCH_LEAF:
+
+                /* Run the callback. And then invoke siblings. */
+                assert(node->leaf.callback);
+                r = node->leaf.callback(bus, ret, m, node->leaf.userdata);
+                if (r != 0)
+                        return r;
+
+                return bus_match_run(bus, node->next, ret, m);
+
+        case BUS_MATCH_MESSAGE_TYPE:
+                test_u8 = m->header->type;
+                break;
+
+        case BUS_MATCH_SENDER:
+                test_str = m->sender;
+                /* FIXME: resolve test_str from a well-known to a unique name first */
+                break;
+
+        case BUS_MATCH_DESTINATION:
+                test_str = m->destination;
+                break;
+
+        case BUS_MATCH_INTERFACE:
+                test_str = m->interface;
+                break;
+
+        case BUS_MATCH_MEMBER:
+                test_str = m->member;
+                break;
+
+        case BUS_MATCH_PATH:
+        case BUS_MATCH_PATH_NAMESPACE:
+                test_str = m->path;
+                break;
+
+        case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+                test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG);
+                break;
+
+        case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+                test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH);
+                break;
+
+        case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+                test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE);
+                break;
+
+        default:
+                assert_not_reached("Unknown match type.");
+        }
+
+        if (BUS_MATCH_CAN_HASH(node->type)) {
+                struct bus_match_node *found;
+
+                /* Lookup via hash table, nice! So let's jump directly. */
+
+                if (test_str)
+                        found = hashmap_get(node->compare.children, test_str);
+                else if (node->type == BUS_MATCH_MESSAGE_TYPE)
+                        found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8));
+                else
+                        found = NULL;
+
+                if (found) {
+                        r = bus_match_run(bus, found, ret, m);
+                        if (r != 0)
+                                return r;
+                }
+        } else {
+                struct bus_match_node *c;
+
+                /* No hash table, so let's iterate manually... */
+
+                for (c = node->child; c; c = c->next) {
+                        if (!value_node_test(c, node->type, test_u8, test_str))
+                                continue;
+
+                        r = bus_match_run(bus, c, ret, m);
+                        if (r != 0)
+                                return r;
+                }
+        }
+
+        /* And now, let's invoke our siblings */
+        return bus_match_run(bus, node->next, ret, m);
+}
+
+static int bus_match_add_compare_value(
+                struct bus_match_node *where,
+                enum bus_match_node_type t,
+                uint8_t value_u8,
+                const char *value_str,
+                struct bus_match_node **ret) {
+
+        struct bus_match_node *c = NULL, *n = NULL;
+        int r;
+
+        assert(where);
+        assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE);
+        assert(BUS_MATCH_IS_COMPARE(t));
+        assert(ret);
+
+        for (c = where->child; c && c->type != t; c = c->next)
+                ;
+
+        if (c) {
+                /* Comparison node already exists? Then let's see if
+                 * the value node exists too. */
+
+                if (t == BUS_MATCH_MESSAGE_TYPE)
+                        n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8));
+                else if (BUS_MATCH_CAN_HASH(t))
+                        n = hashmap_get(c->compare.children, value_str);
+                else {
+                        for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next)
+                                ;
+                }
+
+                if (n) {
+                        *ret = n;
+                        return 0;
+                }
+        } else {
+                /* Comparison node, doesn't exist yet? Then let's
+                 * create it. */
+
+                c = new0(struct bus_match_node, 1);
+                if (!c) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                c->type = t;
+                c->parent = where;
+                c->next = where->child;
+                if (c->next)
+                        c->next->prev = c;
+                where->child = c;
+
+                if (t == BUS_MATCH_MESSAGE_TYPE) {
+                        c->compare.children = hashmap_new(trivial_hash_func, trivial_compare_func);
+                        if (!c->compare.children) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+                } else if (BUS_MATCH_CAN_HASH(t)) {
+                        c->compare.children = hashmap_new(string_hash_func, string_compare_func);
+                        if (!c->compare.children) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+                }
+        }
+
+        n = new0(struct bus_match_node, 1);
+        if (!n) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        n->type = BUS_MATCH_VALUE;
+        n->value.u8 = value_u8;
+        if (value_str) {
+                n->value.str = strdup(value_str);
+                if (!n->value.str) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        n->parent = c;
+        if (c->compare.children) {
+
+                if (t == BUS_MATCH_MESSAGE_TYPE)
+                        r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n);
+                else
+                        r = hashmap_put(c->compare.children, n->value.str, n);
+
+                if (r < 0)
+                        goto fail;
+        } else {
+                n->next = c->child;
+                if (n->next)
+                        n->next->prev = n;
+                c->child = n;
+        }
+
+        *ret = n;
+        return 1;
+
+fail:
+        if (c)
+                bus_match_node_maybe_free(c);
+
+        if (n) {
+                free(n->value.str);
+                free(n);
+        }
+
+        return r;
+}
+
+static int bus_match_find_compare_value(
+                struct bus_match_node *where,
+                enum bus_match_node_type t,
+                uint8_t value_u8,
+                const char *value_str,
+                struct bus_match_node **ret) {
+
+        struct bus_match_node *c, *n;
+
+        assert(where);
+        assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE);
+        assert(BUS_MATCH_IS_COMPARE(t));
+        assert(ret);
+
+        for (c = where->child; c && c->type != t; c = c->next)
+                ;
+
+        if (!c)
+                return 0;
+
+        if (t == BUS_MATCH_MESSAGE_TYPE)
+                n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8));
+        else if (BUS_MATCH_CAN_HASH(t))
+                n = hashmap_get(c->compare.children, value_str);
+        else {
+                for (n = c->child; !value_node_same(n, t, value_u8, value_str); n = n->next)
+                        ;
+        }
+
+        if (n) {
+                *ret = n;
+                return 1;
+        }
+
+        return 0;
+}
+
+static int bus_match_add_leaf(
+                struct bus_match_node *where,
+                sd_message_handler_t callback,
+                void *userdata,
+                struct bus_match_node **ret) {
+
+        struct bus_match_node *n;
+
+        assert(where);
+        assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE);
+        assert(ret);
+
+        n = new0(struct bus_match_node, 1);
+        if (!n)
+                return -ENOMEM;
+
+        n->type = BUS_MATCH_LEAF;
+        n->parent = where;
+        n->next = where->child;
+        if (n->next)
+                n->next->prev = n;
+        n->leaf.callback = callback;
+        n->leaf.userdata = userdata;
+
+        where->child = n;
+
+        *ret = n;
+        return 1;
+}
+
+static int bus_match_find_leaf(
+                struct bus_match_node *where,
+                sd_message_handler_t callback,
+                void *userdata,
+                struct bus_match_node **ret) {
+
+        struct bus_match_node *c;
+
+        assert(where);
+        assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE);
+        assert(ret);
+
+        for (c = where->child; c; c = c->next) {
+                if (c->type == BUS_MATCH_LEAF &&
+                    c->leaf.callback == callback &&
+                    c->leaf.userdata == userdata) {
+                        *ret = c;
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
+enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) {
+        assert(k);
+
+        if (n == 4 && memcmp(k, "type", 4) == 0)
+                return BUS_MATCH_MESSAGE_TYPE;
+        if (n == 6 && memcmp(k, "sender", 6) == 0)
+                return BUS_MATCH_SENDER;
+        if (n == 11 && memcmp(k, "destination", 11) == 0)
+                return BUS_MATCH_DESTINATION;
+        if (n == 9 && memcmp(k, "interface", 9) == 0)
+                return BUS_MATCH_INTERFACE;
+        if (n == 6 && memcmp(k, "member", 6) == 0)
+                return BUS_MATCH_MEMBER;
+        if (n == 4 && memcmp(k, "path", 4) == 0)
+                return BUS_MATCH_PATH;
+        if (n == 14 && memcmp(k, "path_namespace", 14) == 0)
+                return BUS_MATCH_PATH_NAMESPACE;
+
+        if (n == 4 && memcmp(k, "arg", 3) == 0) {
+                int j;
+
+                j = undecchar(k[3]);
+                if (j < 0)
+                        return -EINVAL;
+
+                return BUS_MATCH_ARG + j;
+        }
+
+        if (n == 5 && memcmp(k, "arg", 3) == 0) {
+                int a, b;
+                enum bus_match_node_type t;
+
+                a = undecchar(k[3]);
+                b = undecchar(k[4]);
+                if (a <= 0 || b < 0)
+                        return -EINVAL;
+
+                t = BUS_MATCH_ARG + a * 10 + b;
+                if (t > BUS_MATCH_ARG_LAST)
+                        return -EINVAL;
+
+                return t;
+        }
+
+        if (n == 8 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "path", 4) == 0) {
+                int j;
+
+                j = undecchar(k[3]);
+                if (j < 0)
+                        return -EINVAL;
+
+                return BUS_MATCH_ARG_PATH + j;
+        }
+
+        if (n == 9 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "path", 4) == 0) {
+                enum bus_match_node_type t;
+                int a, b;
+
+                a = undecchar(k[3]);
+                b = undecchar(k[4]);
+                if (a <= 0 || b < 0)
+                        return -EINVAL;
+
+                t = BUS_MATCH_ARG_PATH + a * 10 + b;
+                if (t > BUS_MATCH_ARG_PATH_LAST)
+                        return -EINVAL;
+
+                return t;
+        }
+
+        if (n == 13 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "namespace", 9) == 0) {
+                int j;
+
+                j = undecchar(k[3]);
+                if (j < 0)
+                        return -EINVAL;
+
+                return BUS_MATCH_ARG_NAMESPACE + j;
+        }
+
+        if (n == 14 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "namespace", 9) == 0) {
+                enum bus_match_node_type t;
+                int a, b;
+
+                a = undecchar(k[3]);
+                b = undecchar(k[4]);
+                if (a <= 0 || b < 0)
+                        return -EINVAL;
+
+                t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b;
+                if (t > BUS_MATCH_ARG_NAMESPACE_LAST)
+                        return -EINVAL;
+
+                return t;
+        }
+
+        return -EINVAL;
+}
+
+struct match_component {
+        enum bus_match_node_type type;
+        uint8_t value_u8;
+        char *value_str;
+};
+
+static int match_component_compare(const void *a, const void *b) {
+        const struct match_component *x = a, *y = b;
+
+        if (x->type < y->type)
+                return -1;
+        if (x->type > y->type)
+                return 1;
+
+        return 0;
+}
+
+static void free_components(struct match_component *components, unsigned n_components) {
+        unsigned i;
+
+        for (i = 0; i < n_components; i++)
+                free(components[i].value_str);
+
+        free(components);
+}
+
+static int parse_match(
+                const char *match,
+                struct match_component **_components,
+                unsigned *_n_components) {
+
+        const char *p = match;
+        struct match_component *components = NULL;
+        size_t components_allocated = 0;
+        unsigned n_components = 0, i;
+        _cleanup_free_ char *value = NULL;
+        int r;
+
+        assert(match);
+        assert(_components);
+        assert(_n_components);
+
+        while (*p != 0) {
+                const char *eq, *q;
+                enum bus_match_node_type t;
+                unsigned j = 0;
+                size_t value_allocated = 0;
+                bool escaped = false;
+                uint8_t u;
+
+                eq = strchr(p, '=');
+                if (!eq)
+                        return -EINVAL;
+
+                if (eq[1] != '\'')
+                        return -EINVAL;
+
+                t = bus_match_node_type_from_string(p, eq - p);
+                if (t < 0)
+                        return -EINVAL;
+
+                for (q = eq + 2;; q++) {
+
+                        if (*q == 0) {
+                                r = -EINVAL;
+                                goto fail;
+                        }
+
+                        if (!escaped) {
+                                if (*q == '\\') {
+                                        escaped = true;
+                                        continue;
+                                }
+                                if (*q == '\'') {
+                                        if (value)
+                                                value[j] = 0;
+                                        break;
+                                }
+                        }
+
+                        if (!greedy_realloc((void**) &value, &value_allocated, j + 2)) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        value[j++] = *q;
+                        escaped = false;
+                }
+
+                if (t == BUS_MATCH_MESSAGE_TYPE) {
+                        r = bus_message_type_from_string(value, &u);
+                        if (r < 0)
+                                goto fail;
+
+                        free(value);
+                        value = NULL;
+                } else
+                        u = 0;
+
+                if (!greedy_realloc((void**) &components, &components_allocated,
+                                    (n_components + 1) * sizeof(struct match_component))) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                components[n_components].type = t;
+                components[n_components].value_str = value;
+                components[n_components].value_u8 = u;
+                n_components++;
+
+                value = NULL;
+
+                if (q[1] == 0)
+                        break;
+
+                if (q[1] != ',') {
+                        r = -EINVAL;
+                        goto fail;
+                }
+
+                p = q + 2;
+        }
+
+        /* Order the whole thing, so that we always generate the same tree */
+        qsort(components, n_components, sizeof(struct match_component), match_component_compare);
+
+        /* Check for duplicates */
+        for (i = 0; i+1 < n_components; i++)
+                if (components[i].type == components[i+1].type) {
+                        r = -EINVAL;
+                        goto fail;
+                }
+
+        *_components = components;
+        *_n_components = n_components;
+
+        return 0;
+
+fail:
+        free_components(components, n_components);
+        return r;
+}
+
+int bus_match_add(
+                struct bus_match_node *root,
+                const char *match,
+                sd_message_handler_t callback,
+                void *userdata,
+                struct bus_match_node **ret) {
+
+        struct match_component *components = NULL;
+        unsigned n_components = 0, i;
+        struct bus_match_node *n;
+        int r;
+
+        assert(root);
+        assert(match);
+        assert(callback);
+
+        r = parse_match(match, &components, &n_components);
+        if (r < 0)
+                return r;
+
+        n = root;
+        for (i = 0; i < n_components; i++) {
+                r = bus_match_add_compare_value(
+                                n, components[i].type,
+                                components[i].value_u8, components[i].value_str, &n);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = bus_match_add_leaf(n, callback, userdata, &n);
+        if (r < 0)
+                goto finish;
+
+        if (ret)
+                *ret = n;
+
+finish:
+        free_components(components, n_components);
+        return r;
+}
+
+int bus_match_remove(
+                struct bus_match_node *root,
+                const char *match,
+                sd_message_handler_t callback,
+                void *userdata) {
+
+        struct match_component *components = NULL;
+        unsigned n_components = 0, i;
+        struct bus_match_node *n, **gc;
+        int r;
+
+        assert(root);
+        assert(match);
+
+        r = parse_match(match, &components, &n_components);
+        if (r < 0)
+                return r;
+
+        gc = newa(struct bus_match_node*, n_components);
+
+        n = root;
+        for (i = 0; i < n_components; i++) {
+                r = bus_match_find_compare_value(
+                                n, components[i].type,
+                                components[i].value_u8, components[i].value_str,
+                                &n);
+                if (r <= 0)
+                        goto finish;
+
+                gc[i] = n;
+        }
+
+        r = bus_match_find_leaf(n, callback, userdata, &n);
+        if (r <= 0)
+                goto finish;
+
+        /* Free the leaf */
+        bus_match_node_free(n);
+
+        /* Prune the tree above */
+        for (i = n_components; i > 0; i --) {
+                struct bus_match_node *p = gc[i-1]->parent;
+
+                if (!bus_match_node_maybe_free(gc[i-1]))
+                        break;
+
+                if (!bus_match_node_maybe_free(p))
+                        break;
+        }
+
+finish:
+        free_components(components, n_components);
+        return r;
+}
+
+void bus_match_free(struct bus_match_node *node) {
+        struct bus_match_node *c;
+
+        if (!node)
+                return;
+
+        if (BUS_MATCH_CAN_HASH(node->type)) {
+                Iterator i;
+
+                HASHMAP_FOREACH(c, node->compare.children, i)
+                        bus_match_free(c);
+
+                assert(hashmap_isempty(node->compare.children));
+        }
+
+        while ((c = node->child))
+                bus_match_free(c);
+
+        if (node->type != BUS_MATCH_ROOT)
+                bus_match_node_free(node);
+}
+
+const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) {
+        switch (t) {
+
+        case BUS_MATCH_ROOT:
+                return "root";
+
+        case BUS_MATCH_VALUE:
+                return "value";
+
+        case BUS_MATCH_LEAF:
+                return "leaf";
+
+        case BUS_MATCH_MESSAGE_TYPE:
+                return "type";
+
+        case BUS_MATCH_SENDER:
+                return "sender";
+
+        case BUS_MATCH_DESTINATION:
+                return "destination";
+
+        case BUS_MATCH_INTERFACE:
+                return "interface";
+
+        case BUS_MATCH_MEMBER:
+                return "member";
+
+        case BUS_MATCH_PATH:
+                return "path";
+
+        case BUS_MATCH_PATH_NAMESPACE:
+                return "path_namespace";
+
+        case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+                snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG);
+                return buf;
+
+        case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+                snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH);
+                return buf;
+
+        case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+                snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE);
+                return buf;
+
+        default:
+                return NULL;
+        }
+}
+
+void bus_match_dump(struct bus_match_node *node, unsigned level) {
+        struct bus_match_node *c;
+        _cleanup_free_ char *pfx = NULL;
+        char buf[32];
+
+        if (!node)
+                return;
+
+        pfx = strrep("  ", level);
+        printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf)));
+
+        if (node->type == BUS_MATCH_VALUE) {
+                if (node->parent->type == BUS_MATCH_MESSAGE_TYPE)
+                        printf(" <%u>\n", node->value.u8);
+                else
+                        printf(" <%s>\n", node->value.str);
+        } else if (node->type == BUS_MATCH_ROOT)
+                puts(" root");
+        else if (node->type == BUS_MATCH_LEAF)
+                printf(" %p/%p\n", node->leaf.callback, node->leaf.userdata);
+        else
+                putchar('\n');
+
+        if (BUS_MATCH_CAN_HASH(node->type)) {
+                Iterator i;
+
+                HASHMAP_FOREACH(c, node->compare.children, i)
+                        bus_match_dump(c, level + 1);
+        }
+
+        for (c = node->child; c; c = c->next)
+                bus_match_dump(c, level + 1);
+}