chiark / gitweb /
bus: parse matches locally and allow registration of callbacks for them
authorLennart Poettering <lennart@poettering.net>
Sun, 31 Mar 2013 14:16:37 +0000 (16:16 +0200)
committerLennart Poettering <lennart@poettering.net>
Sun, 31 Mar 2013 14:16:37 +0000 (16:16 +0200)
This includes code to parse and split up match strings which will also
be useful to calculate bloom filter masks when the time comes.

17 files changed:
.gitignore
Makefile.am
src/libsystemd-bus/bus-control.c
src/libsystemd-bus/bus-control.h [new file with mode: 0644]
src/libsystemd-bus/bus-internal.c
src/libsystemd-bus/bus-internal.h
src/libsystemd-bus/bus-match.c [new file with mode: 0644]
src/libsystemd-bus/bus-match.h [new file with mode: 0644]
src/libsystemd-bus/bus-message.c
src/libsystemd-bus/bus-message.h
src/libsystemd-bus/sd-bus.c
src/libsystemd-bus/sd-bus.h
src/libsystemd-bus/test-bus-chat.c
src/libsystemd-bus/test-bus-match.c [new file with mode: 0644]
src/libsystemd-bus/test-bus-signature.c
src/shared/util.c
src/shared/util.h

index 353449a..d38a8a1 100644 (file)
 /systemd-user-sessions
 /systemd-vconsole-setup
 /tags
+/test-bus-chat
 /test-bus-marshal
+/test-bus-match
 /test-bus-signature
-/test-bus-chat
 /test-bus-server
 /test-calendarspec
 /test-catalog
index e599da9..07f4362 100644 (file)
@@ -1670,6 +1670,7 @@ libsystemd_bus_la_SOURCES = \
        src/libsystemd-bus/sd-bus.h \
        src/libsystemd-bus/sd-bus-protocol.h \
        src/libsystemd-bus/bus-control.c \
+       src/libsystemd-bus/bus-control.h \
        src/libsystemd-bus/bus-error.c \
        src/libsystemd-bus/bus-error.h \
        src/libsystemd-bus/bus-internal.c \
@@ -1681,7 +1682,9 @@ libsystemd_bus_la_SOURCES = \
        src/libsystemd-bus/bus-signature.c \
        src/libsystemd-bus/bus-signature.h \
        src/libsystemd-bus/bus-type.c \
-       src/libsystemd-bus/bus-type.h
+       src/libsystemd-bus/bus-type.h \
+       src/libsystemd-bus/bus-match.c \
+       src/libsystemd-bus/bus-match.h
 
 libsystemd_bus_la_LIBADD =  \
        libsystemd-id128-internal.la \
@@ -1694,7 +1697,8 @@ noinst_tests += \
        test-bus-marshal \
        test-bus-signature \
        test-bus-chat \
-       test-bus-server
+       test-bus-server \
+       test-bus-match
 
 noinst_PROGRAMS += \
        busctl
@@ -1744,6 +1748,17 @@ test_bus_server_LDADD = \
        libsystemd-bus.la \
        libsystemd-id128-internal.la
 
+test_bus_match_SOURCES = \
+       src/libsystemd-bus/test-bus-match.c
+
+test_bus_match_CFLAGS = \
+       $(AM_CFLAGS)
+
+test_bus_match_LDADD = \
+       libsystemd-shared.la \
+       libsystemd-bus.la \
+       libsystemd-id128-internal.la
+
 busctl_SOURCES = \
        src/libsystemd-bus/busctl.c
 
index 06afaf3..dd40444 100644 (file)
@@ -27,6 +27,7 @@
 #include "sd-bus.h"
 #include "bus-internal.h"
 #include "bus-message.h"
+#include "bus-control.h"
 
 int sd_bus_get_unique_name(sd_bus *bus, const char **unique) {
         int r;
@@ -292,14 +293,12 @@ int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid) {
         return 0;
 }
 
-int sd_bus_add_match(sd_bus *bus, const char *match) {
+int bus_add_match_internal(sd_bus *bus, const char *match) {
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
         int r;
 
-        if (!bus)
-                return -EINVAL;
-        if (!match)
-                return -EINVAL;
+        assert(bus);
+        assert(match);
 
         r = sd_bus_message_new_method_call(
                         bus,
@@ -318,14 +317,12 @@ int sd_bus_add_match(sd_bus *bus, const char *match) {
         return sd_bus_send_with_reply_and_block(bus, m, 0, NULL, &reply);
 }
 
-int sd_bus_remove_match(sd_bus *bus, const char *match) {
+int bus_remove_match_internal(sd_bus *bus, const char *match) {
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
         int r;
 
-        if (!bus)
-                return -EINVAL;
-        if (!match)
-                return -EINVAL;
+        assert(bus);
+        assert(match);
 
         r = sd_bus_message_new_method_call(
                         bus,
diff --git a/src/libsystemd-bus/bus-control.h b/src/libsystemd-bus/bus-control.h
new file mode 100644 (file)
index 0000000..34ecb26
--- /dev/null
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  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 "sd-bus.h"
+
+int bus_add_match_internal(sd_bus *bus, const char *match);
+int bus_remove_match_internal(sd_bus *bus, const char *match);
index d27b3f4..317f6a8 100644 (file)
@@ -168,3 +168,64 @@ bool member_name_is_valid(const char *p) {
 
         return true;
 }
+
+static bool complex_pattern_check(char c, const char *a, const char *b) {
+        bool separator = false;
+
+        for (;;) {
+                if (*a != *b)
+                        return (separator && (*a == 0 || *b == 0)) ||
+                                (*a == 0 && *b == c && b[1] == 0) ||
+                                (*b == 0 && *a == c && a[1] == 0);
+
+                if (*a == 0)
+                        return true;
+
+                separator = *a == c;
+
+                a++, b++;
+        }
+}
+
+bool namespace_complex_pattern(const char *pattern, const char *value) {
+        return complex_pattern_check('.', pattern, value);
+}
+
+bool path_complex_pattern(const char *pattern, const char *value) {
+        return complex_pattern_check('/', pattern, value);
+}
+
+static bool simple_pattern_check(char c, const char *a, const char *b) {
+        for (;;) {
+                if (*a != *b)
+                        return *a == 0 && *b == c;
+
+                if (*a == 0)
+                        return true;
+
+                a++, b++;
+        }
+}
+
+bool namespace_simple_pattern(const char *pattern, const char *value) {
+        return simple_pattern_check('.', pattern, value);
+}
+
+bool path_simple_pattern(const char *pattern, const char *value) {
+        return simple_pattern_check('/', pattern, value);
+}
+
+int bus_message_type_from_string(const char *s, uint8_t *u) {
+        if (streq(s, "signal"))
+                *u = SD_BUS_MESSAGE_TYPE_SIGNAL;
+        else if (streq(s, "method_call"))
+                *u = SD_BUS_MESSAGE_TYPE_METHOD_CALL;
+        else if (streq(s, "error"))
+                *u = SD_BUS_MESSAGE_TYPE_METHOD_ERROR;
+        else if (streq(s, "method_return"))
+                *u = SD_BUS_MESSAGE_TYPE_METHOD_RETURN;
+        else
+                return -EINVAL;
+
+        return 0;
+}
index 76b90ea..1dcb383 100644 (file)
@@ -32,6 +32,7 @@
 
 #include "sd-bus.h"
 #include "bus-error.h"
+#include "bus-match.h"
 
 struct reply_callback {
         sd_message_handler_t callback;
@@ -97,6 +98,7 @@ struct sd_bus {
 
         char *unique_name;
 
+        struct bus_match_node match_callbacks;
         Prioq *reply_callbacks_prioq;
         Hashmap *reply_callbacks;
         LIST_HEAD(struct filter_callback, filter_callbacks);
@@ -166,6 +168,14 @@ bool interface_name_is_valid(const char *p);
 bool service_name_is_valid(const char *p);
 bool member_name_is_valid(const char *p);
 
+bool namespace_complex_pattern(const char *pattern, const char *value);
+bool path_complex_pattern(const char *pattern, const char *value);
+
+bool namespace_simple_pattern(const char *pattern, const char *value);
+bool path_simple_pattern(const char *pattern, const char *value);
+
+int bus_message_type_from_string(const char *s, uint8_t *u);
+
 #define error_name_is_valid interface_name_is_valid
 
 int bus_ensure_running(sd_bus *bus);
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);
+}
diff --git a/src/libsystemd-bus/bus-match.h b/src/libsystemd-bus/bus-match.h
new file mode 100644 (file)
index 0000000..0a63b55
--- /dev/null
@@ -0,0 +1,81 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  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 "hashmap.h"
+
+#include "sd-bus.h"
+
+enum bus_match_node_type {
+        BUS_MATCH_ROOT,
+        BUS_MATCH_VALUE,
+        BUS_MATCH_LEAF,
+
+        /* The following are all different kinds of compare nodes */
+        BUS_MATCH_MESSAGE_TYPE,
+        BUS_MATCH_SENDER,
+        BUS_MATCH_DESTINATION,
+        BUS_MATCH_INTERFACE,
+        BUS_MATCH_MEMBER,
+        BUS_MATCH_PATH,
+        BUS_MATCH_PATH_NAMESPACE,
+        BUS_MATCH_ARG,
+        BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63,
+        BUS_MATCH_ARG_PATH,
+        BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63,
+        BUS_MATCH_ARG_NAMESPACE,
+        BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63,
+        _BUS_MATCH_NODE_TYPE_MAX,
+        _BUS_MATCH_NODE_TYPE_INVALID = -1
+};
+
+struct bus_match_node {
+        enum bus_match_node_type type;
+        struct bus_match_node *parent, *next, *prev, *child;
+
+        union {
+                struct {
+                        uint8_t u8;
+                        char *str;
+                } value;
+                struct {
+                        sd_message_handler_t callback;
+                        void *userdata;
+                } leaf;
+                struct {
+                        /* If this is set, then the child is NULL */
+                        Hashmap *children;
+                } compare;
+        };
+};
+
+int bus_match_run(sd_bus *bus, struct bus_match_node *root, int ret, sd_bus_message *m);
+
+int bus_match_add(struct bus_match_node *root, const char *match, sd_message_handler_t callback, void *userdata, struct bus_match_node **ret);
+int bus_match_remove(struct bus_match_node *root, const char *match, sd_message_handler_t callback, void *userdata);
+
+void bus_match_free(struct bus_match_node *node);
+
+void bus_match_dump(struct bus_match_node *node, unsigned level);
+
+const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l);
+enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n);
index 845e2c0..85b9cba 100644 (file)
@@ -3002,3 +3002,38 @@ int bus_message_read_strv_extend(sd_bus_message *m, char ***l) {
 
         return 0;
 }
+
+const char* bus_message_get_arg(sd_bus_message *m, unsigned i) {
+        int r;
+        const char *t;
+        char type;
+
+        assert(m);
+
+        r = sd_bus_message_rewind(m, true);
+        if (r < 0)
+                return NULL;
+
+        while (i > 0) {
+                r = sd_bus_message_peek_type(m, &type, NULL);
+                if (r < 0)
+                        return NULL;
+
+                if (type != SD_BUS_TYPE_STRING &&
+                    type != SD_BUS_TYPE_OBJECT_PATH &&
+                    type != SD_BUS_TYPE_SIGNATURE)
+                        return NULL;
+
+                r = sd_bus_message_read_basic(m, type, &t);
+                if (r < 0)
+                        return NULL;
+
+                i--;
+        }
+
+        r = sd_bus_message_rewind(m, true);
+        if (r < 0)
+                return NULL;
+
+        return t;
+}
index 3289b37..91b1668 100644 (file)
@@ -142,3 +142,5 @@ int bus_message_from_malloc(
                 const struct ucred *ucred,
                 const char *label,
                 sd_bus_message **ret);
+
+const char* bus_message_get_arg(sd_bus_message *m, unsigned i);
index 047f286..0a263ea 100644 (file)
@@ -37,6 +37,7 @@
 #include "bus-message.h"
 #include "bus-type.h"
 #include "bus-socket.h"
+#include "bus-control.h"
 
 static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec);
 
@@ -84,6 +85,8 @@ static void bus_free(sd_bus *b) {
 
         hashmap_free(b->object_callbacks);
 
+        bus_match_free(&b->match_callbacks);
+
         free(b);
 }
 
@@ -1493,6 +1496,9 @@ static int process_filter(sd_bus *bus, sd_bus_message *m) {
         struct filter_callback *l;
         int r;
 
+        assert(bus);
+        assert(m);
+
         LIST_FOREACH(callbacks, l, bus->filter_callbacks) {
                 r = l->callback(bus, 0, m, l->userdata);
                 if (r != 0)
@@ -1502,6 +1508,13 @@ static int process_filter(sd_bus *bus, sd_bus_message *m) {
         return 0;
 }
 
+static int process_match(sd_bus *bus, sd_bus_message *m) {
+        assert(bus);
+        assert(m);
+
+        return bus_match_run(bus, &bus->match_callbacks, 0, m);
+}
+
 static int process_builtin(sd_bus *bus, sd_bus_message *m) {
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
         int r;
@@ -1731,6 +1744,10 @@ static int process_message(sd_bus *bus, sd_bus_message *m) {
         if (r != 0)
                 return r;
 
+        r = process_match(bus, m);
+        if (r != 0)
+                return r;
+
         r = process_builtin(bus, m);
         if (r != 0)
                 return r;
@@ -2057,3 +2074,48 @@ int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t ca
 int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata) {
         return bus_remove_object(bus, true, prefix, callback, userdata);
 }
+
+int sd_bus_add_match(sd_bus *bus, const char *match, sd_message_handler_t callback, void *userdata) {
+        int r = 0;
+
+        if (!bus)
+                return -EINVAL;
+        if (!match)
+                return -EINVAL;
+
+        if (bus->bus_client) {
+                r = bus_add_match_internal(bus, match);
+                if (r < 0)
+                        return r;
+        }
+
+        if (callback) {
+                r = bus_match_add(&bus->match_callbacks, match, callback, userdata, NULL);
+                if (r < 0) {
+
+                        if (bus->bus_client)
+                                bus_remove_match_internal(bus, match);
+                }
+        }
+
+        return r;
+}
+
+int sd_bus_remove_match(sd_bus *bus, const char *match, sd_message_handler_t callback, void *userdata) {
+        int r = 0, q = 0;
+
+        if (!bus)
+                return -EINVAL;
+        if (!match)
+                return -EINVAL;
+
+        if (bus->bus_client)
+                r = bus_remove_match_internal(bus, match);
+
+        if (callback)
+                q = bus_match_remove(&bus->match_callbacks, match, callback, userdata);
+
+        if (r < 0)
+                return r;
+        return q;
+}
index db3b227..e0970cd 100644 (file)
@@ -33,7 +33,7 @@ extern "C" {
 #endif
 
 /* TODO:
- * - allow installing match callbacks
+ * - sd_message_handler_t needs to be renamed to sd_bus_message_handler_t
  *
  * Later:
  * - add page donation logic
@@ -98,6 +98,9 @@ int sd_bus_remove_object(sd_bus *bus, const char *path, sd_message_handler_t cal
 int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata);
 int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata);
 
+int sd_bus_add_match(sd_bus *bus, const char *match, sd_message_handler_t callback, void *userdata);
+int sd_bus_remove_match(sd_bus *bus, const char *match, sd_message_handler_t callback, void *userdata);
+
 /* Message object */
 
 int sd_bus_message_new_signal(sd_bus *bus, const char *path, const char *interface, const char *member, sd_bus_message **m);
@@ -154,8 +157,6 @@ int sd_bus_list_names(sd_bus *bus, char ***l);
 int sd_bus_get_owner(sd_bus *bus, const char *name, char **owner);
 int sd_bus_get_owner_uid(sd_bus *bus, const char *name, uid_t *uid);
 int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid);
-int sd_bus_add_match(sd_bus *bus, const char *match);
-int sd_bus_remove_match(sd_bus *bus, const char *match);
 
 /* Error structures */
 
index 906a7e6..2dd3091 100644 (file)
 #include "sd-bus.h"
 #include "bus-message.h"
 #include "bus-error.h"
+#include "bus-match.h"
+#include "bus-internal.h"
+
+static int match_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) {
+        log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m)));
+        return 0;
+}
 
 static int object_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) {
         int r;
@@ -106,6 +113,20 @@ static int server_init(sd_bus **_bus) {
                 goto fail;
         }
 
+        r = sd_bus_add_match(bus, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL);
+        if (r < 0) {
+                log_error("Failed to add match: %s", strerror(-r));
+                goto fail;
+        }
+
+        r = sd_bus_add_match(bus, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL);
+        if (r < 0) {
+                log_error("Failed to add match: %s", strerror(-r));
+                goto fail;
+        }
+
+        bus_match_dump(&bus->match_callbacks, 0);
+
         *_bus = bus;
         return 0;
 
@@ -424,6 +445,26 @@ static void* client2(void*p) {
         sd_bus_message_unref(m);
         m = NULL;
 
+        r = sd_bus_message_new_signal(
+                        bus,
+                        "/foobar",
+                        "foo.bar",
+                        "Notify",
+                        &m);
+        if (r < 0) {
+                log_error("Failed to allocate signal: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = sd_bus_send(bus, m, NULL);
+        if (r < 0) {
+                log_error("Failed to issue signal: %s", bus_error_message(&error, -r));
+                goto finish;
+        }
+
+        sd_bus_message_unref(m);
+        m = NULL;
+
         r = sd_bus_message_new_method_call(
                         bus,
                         "org.freedesktop.systemd.test",
diff --git a/src/libsystemd-bus/test-bus-match.c b/src/libsystemd-bus/test-bus-match.c
new file mode 100644 (file)
index 0000000..ccbea81
--- /dev/null
@@ -0,0 +1,114 @@
+/*-*- 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 <assert.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+
+#include "bus-match.h"
+#include "bus-message.h"
+
+static bool mask[32];
+
+static int filter(sd_bus *b, int ret, sd_bus_message *m, void *userdata) {
+        log_info("Ran %i", PTR_TO_INT(userdata));
+        mask[PTR_TO_INT(userdata)] = true;
+        return 0;
+}
+
+static bool mask_contains(unsigned a[], unsigned n) {
+        unsigned i, j;
+
+        for (i = 0; i < ELEMENTSOF(mask); i++) {
+                bool found = false;
+
+                for (j = 0; j < n; j++)
+                        if (a[j] == i) {
+                                found = true;
+                                break;
+                        }
+
+                if (found != mask[i])
+                        return false;
+        }
+
+        return true;
+}
+
+int main(int argc, char *argv[]) {
+        struct bus_match_node root;
+        _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+        enum bus_match_node_type i;
+
+        zero(root);
+        root.type = BUS_MATCH_ROOT;
+
+        assert_se(bus_match_add(&root, "arg3='wal\\'do',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(1), NULL) >= 0);
+        assert_se(bus_match_add(&root, "arg3='wal\\'do2',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(2), NULL) >= 0);
+        assert_se(bus_match_add(&root, "arg4='test',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(3), NULL) >= 0);
+        assert_se(bus_match_add(&root, "arg4='test',sender='foo',type='method_call',interface='bar',", filter, INT_TO_PTR(4), NULL) >= 0);
+        assert_se(bus_match_add(&root, "", filter, INT_TO_PTR(5), NULL) >= 0);
+        assert_se(bus_match_add(&root, "interface='quux'", filter, INT_TO_PTR(6), NULL) >= 0);
+        assert_se(bus_match_add(&root, "interface='bar'", filter, INT_TO_PTR(7), NULL) >= 0);
+        assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8), NULL) >= 0);
+        assert_se(bus_match_add(&root, "path='/foo/bar'", filter, INT_TO_PTR(9), NULL) >= 0);
+        assert_se(bus_match_add(&root, "path_namespace='/foo'", filter, INT_TO_PTR(10), NULL) >= 0);
+        assert_se(bus_match_add(&root, "path_namespace='/foo/quux'", filter, INT_TO_PTR(11), NULL) >= 0);
+        assert_se(bus_match_add(&root, "arg2='two'", filter, INT_TO_PTR(12), NULL) >= 0);
+        assert_se(bus_match_add(&root, "member='waldo',arg3path='/prefix/'", filter, INT_TO_PTR(13), NULL) >= 0);
+        assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar',arg4namespace='prefix'", filter, INT_TO_PTR(14), NULL) >= 0);
+
+        bus_match_dump(&root, 0);
+
+        assert_se(sd_bus_message_new_signal(NULL, "/foo/bar", "bar", "waldo", &m) >= 0);
+        assert_se(sd_bus_message_append(m, "ssss", "one", "two", "/prefix/three", "prefix.four") >= 0);
+        assert_se(bus_message_seal(m, 1) >= 0);
+
+        zero(mask);
+        assert_se(bus_match_run(NULL, &root, 0, m) == 0);
+        assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14 }, 8));
+
+        assert_se(bus_match_remove(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8)) > 0);
+        assert_se(bus_match_remove(&root, "arg3path='/prefix/',member='waldo'", filter, INT_TO_PTR(13)) > 0);
+        assert_se(bus_match_remove(&root, "interface='barxx'", filter, INT_TO_PTR(7)) == 0);
+
+        bus_match_dump(&root, 0);
+
+        zero(mask);
+        assert_se(bus_match_run(NULL, &root, 0, m) == 0);
+        assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7 }, 6));
+
+        for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) {
+                char buf[32];
+                const char *x;
+
+                assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf)));
+
+                if (i >= BUS_MATCH_MESSAGE_TYPE)
+                        assert_se(bus_match_node_type_from_string(x, strlen(x)) == i);
+        }
+
+        bus_match_free(&root);
+
+        return 0;
+}
index 5bc4310..cab0daa 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "log.h"
 #include "bus-signature.h"
+#include "bus-internal.h"
 
 int main(int argc, char *argv[]) {
 
@@ -81,5 +82,35 @@ int main(int argc, char *argv[]) {
         assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false));
         assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false));
 
+        assert_se(namespace_complex_pattern("", ""));
+        assert_se(namespace_complex_pattern("foobar", "foobar"));
+        assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo"));
+        assert_se(namespace_complex_pattern("foobar.", "foobar.waldo"));
+        assert_se(namespace_complex_pattern("foobar.waldo", "foobar."));
+        assert_se(!namespace_complex_pattern("foobar.waldo", "foobar"));
+        assert_se(!namespace_complex_pattern("foobar", "foobar.waldo"));
+        assert_se(!namespace_complex_pattern("", "foo"));
+        assert_se(!namespace_complex_pattern("foo", ""));
+        assert_se(!namespace_complex_pattern("foo.", ""));
+
+        assert_se(path_complex_pattern("", ""));
+        assert_se(path_complex_pattern("", "/"));
+        assert_se(path_complex_pattern("/", ""));
+        assert_se(path_complex_pattern("/", "/"));
+        assert_se(path_complex_pattern("/foobar/", "/"));
+        assert_se(path_complex_pattern("/foobar/", "/foobar"));
+        assert_se(path_complex_pattern("/foobar", "/foobar"));
+        assert_se(path_complex_pattern("/foobar", "/foobar/"));
+        assert_se(!path_complex_pattern("/foobar", "/foobar/waldo"));
+        assert_se(path_complex_pattern("/foobar/", "/foobar/waldo"));
+
+        assert_se(namespace_simple_pattern("", ""));
+        assert_se(namespace_simple_pattern("foobar", "foobar"));
+        assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo"));
+        assert_se(namespace_simple_pattern("foobar", "foobar.waldo"));
+        assert_se(!namespace_simple_pattern("foobar.waldo", "foobar"));
+        assert_se(!namespace_simple_pattern("", "foo"));
+        assert_se(!namespace_simple_pattern("foo", ""));
+
         return 0;
 }
index 873c958..760013c 100644 (file)
@@ -5855,3 +5855,20 @@ char *strrep(const char *s, unsigned n) {
         *p = 0;
         return r;
 }
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need) {
+        size_t a;
+        void *q;
+
+        if (*allocated >= need)
+                return *p;
+
+        a = MAX(64, need * 2);
+        q = realloc(*p, a);
+        if (!q)
+                return NULL;
+
+        *p = q;
+        *allocated = a;
+        return q;
+}
index 0d05cd6..d1cdd90 100644 (file)
@@ -613,3 +613,5 @@ void *unhexmem(const char *p, size_t l);
 
 char *strextend(char **x, ...);
 char *strrep(const char *s, unsigned n);
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need);