]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic: add RB-Tree implementation 2115/head
authorDavid Herrmann <dh.herrmann@gmail.com>
Mon, 7 Dec 2015 17:34:05 +0000 (18:34 +0100)
committerDavid Herrmann <dh.herrmann@gmail.com>
Mon, 7 Dec 2015 17:34:05 +0000 (18:34 +0100)
This adds an self-standing RB-Tree implementation to src/basic/. This
will be needed for NSEC RR lookups, since we need "close lookups", which
hashmaps (not even ordered-hashmaps) can give us in reasonable time.

.gitignore
Makefile.am
src/basic/c-rbtree.c [new file with mode: 0644]
src/basic/c-rbtree.h [new file with mode: 0644]
src/test/test-rbtree.c [new file with mode: 0644]

index e495f23e3a5b9ffb4c5b4fba65bb6752be2c7d1d..5acf5f819adc47e61d78b3b8011f14776c6b0731 100644 (file)
 /test-pty
 /test-qcow2
 /test-ratelimit
+/test-rbtree
 /test-replace-var
 /test-resolve
 /test-ring
index 48fb8208a4895444733bb0d00c132ec5d45656b4..31e311dba1a46bb086c7cfb1e188123dc9eafdc1 100644 (file)
@@ -766,6 +766,8 @@ libbasic_la_SOURCES = \
        src/basic/missing.h \
        src/basic/capability-util.c \
        src/basic/capability-util.h \
+       src/basic/c-rbtree.c \
+       src/basic/c-rbtree.h \
        src/basic/conf-files.c \
        src/basic/conf-files.h \
        src/basic/stdio-util.h \
@@ -1493,6 +1495,7 @@ tests += \
        test-copy \
        test-cap-list \
        test-sigbus \
+       test-rbtree \
        test-verbs \
        test-af-list \
        test-arphrd-list \
@@ -1728,6 +1731,12 @@ test_sigbus_SOURCES = \
 test_sigbus_LDADD = \
        libshared.la
 
+test_rbtree_SOURCES = \
+       src/test/test-rbtree.c
+
+test_rbtree_LDADD = \
+       libshared.la
+
 test_condition_SOURCES = \
        src/test/test-condition.c
 
diff --git a/src/basic/c-rbtree.c b/src/basic/c-rbtree.c
new file mode 100644 (file)
index 0000000..914d7e5
--- /dev/null
@@ -0,0 +1,679 @@
+/***
+  This file is part of systemd. See COPYING for details.
+
+  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/>.
+***/
+
+/*
+ * RB-Tree Implementation
+ * This implements the insertion/removal of elements in RB-Trees. You're highly
+ * recommended to have an RB-Tree documentation at hand when reading this. Both
+ * insertion and removal can be split into a handful of situations that can
+ * occur. Those situations are enumerated as "Case 1" to "Case n" here, and
+ * follow closely the cases described in most RB-Tree documentations. This file
+ * does not explain why it is enough to handle just those cases, nor does it
+ * provide a proof of correctness. Dig out your algorithm 101 handbook if
+ * you're interested.
+ *
+ * This implementation is *not* straightforward. Usually, a handful of
+ * rotation, reparent, swap and link helpers can be used to implement the
+ * rebalance operations. However, those often perform unnecessary writes.
+ * Therefore, this implementation hard-codes all the operations. You're highly
+ * recommended to look at the two basic helpers before reading the code:
+ *     c_rbtree_swap_child()
+ *     c_rbtree_set_parent_and_color()
+ * Those are the only helpers used, hence, you should really know what they do
+ * before digging into the code.
+ *
+ * For a highlevel documentation of the API, see the header file and docbook
+ * comments.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include "c-rbtree.h"
+
+enum {
+        C_RBNODE_RED   = 0,
+        C_RBNODE_BLACK = 1,
+};
+
+static inline unsigned long c_rbnode_color(CRBNode *n) {
+        return (unsigned long)n->__parent_and_color & 1UL;
+}
+
+static inline _Bool c_rbnode_is_red(CRBNode *n) {
+        return c_rbnode_color(n) == C_RBNODE_RED;
+}
+
+static inline _Bool c_rbnode_is_black(CRBNode *n) {
+        return c_rbnode_color(n) == C_RBNODE_BLACK;
+}
+
+/**
+ * c_rbnode_leftmost() - return leftmost child
+ * @n:          current node, or NULL
+ *
+ * This returns the leftmost child of @n. If @n is NULL, this will return NULL.
+ * In all other cases, this function returns a valid pointer. That is, if @n
+ * does not have any left children, this returns @n.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to leftmost child, or NULL.
+ */
+CRBNode *c_rbnode_leftmost(CRBNode *n) {
+        if (n)
+                while (n->left)
+                        n = n->left;
+        return n;
+}
+
+/**
+ * c_rbnode_rightmost() - return rightmost child
+ * @n:          current node, or NULL
+ *
+ * This returns the rightmost child of @n. If @n is NULL, this will return
+ * NULL. In all other cases, this function returns a valid pointer. That is, if
+ * @n does not have any right children, this returns @n.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to rightmost child, or NULL.
+ */
+CRBNode *c_rbnode_rightmost(CRBNode *n) {
+        if (n)
+                while (n->right)
+                        n = n->right;
+        return n;
+}
+
+/**
+ * c_rbnode_next() - return next node
+ * @n:          current node, or NULL
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically next node to @n. If @n is NULL, the last node or
+ * unlinked, this returns NULL.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to next node, or NULL.
+ */
+CRBNode *c_rbnode_next(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+        if (n->right)
+                return c_rbnode_leftmost(n->right);
+
+        while ((p = c_rbnode_parent(n)) && n == p->right)
+                n = p;
+
+        return p;
+}
+
+/**
+ * c_rbnode_prev() - return previous node
+ * @n:          current node, or NULL
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically previous node to @n. If @n is NULL, the first node or
+ * unlinked, this returns NULL.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to previous node, or NULL.
+ */
+CRBNode *c_rbnode_prev(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+        if (n->left)
+                return c_rbnode_rightmost(n->left);
+
+        while ((p = c_rbnode_parent(n)) && n == p->left)
+                n = p;
+
+        return p;
+}
+
+/**
+ * c_rbtree_first() - return first node
+ * @t:          tree to operate on
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically first node in @t. If @t is empty, NULL is returned.
+ *
+ * Fixed runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to first node, or NULL.
+ */
+CRBNode *c_rbtree_first(CRBTree *t) {
+        assert(t);
+        return c_rbnode_leftmost(t->root);
+}
+
+/**
+ * c_rbtree_last() - return last node
+ * @t:          tree to operate on
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically last node in @t. If @t is empty, NULL is returned.
+ *
+ * Fixed runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to last node, or NULL.
+ */
+CRBNode *c_rbtree_last(CRBTree *t) {
+        assert(t);
+        return c_rbnode_rightmost(t->root);
+}
+
+/*
+ * Set the color and parent of a node. This should be treated as a simple
+ * assignment of the 'color' and 'parent' fields of the node. No other magic is
+ * applied. But since both fields share its backing memory, this helper
+ * function is provided.
+ */
+static inline void c_rbnode_set_parent_and_color(CRBNode *n, CRBNode *p, unsigned long c) {
+        assert(!((unsigned long)p & 1));
+        assert(c < 2);
+        n->__parent_and_color = (CRBNode*)((unsigned long)p | c);
+}
+
+/* same as c_rbnode_set_parent_and_color(), but keeps the current parent */
+static inline void c_rbnode_set_color(CRBNode *n, unsigned long c) {
+        c_rbnode_set_parent_and_color(n, c_rbnode_parent(n), c);
+}
+
+/* same as c_rbnode_set_parent_and_color(), but keeps the current color */
+static inline void c_rbnode_set_parent(CRBNode *n, CRBNode *p) {
+        c_rbnode_set_parent_and_color(n, p, c_rbnode_color(n));
+}
+
+/*
+ * This function partially replaces an existing child pointer to a new one. The
+ * existing child must be given as @old, the new child as @new. @p must be the
+ * parent of @old (or NULL if it has no parent).
+ * This function ensures that the parent of @old now points to @new. However,
+ * it does *NOT* change the parent pointer of @new. The caller must ensure
+ * this.
+ * If @p is NULL, this function ensures that the root-pointer is adjusted
+ * instead (given as @t).
+ */
+static inline void c_rbtree_swap_child(CRBTree *t, CRBNode *p, CRBNode *old, CRBNode *new) {
+        if (p) {
+                if (p->left == old)
+                        p->left = new;
+                else
+                        p->right = new;
+        } else {
+                t->root = new;
+        }
+}
+
+static inline CRBNode *c_rbtree_paint_one(CRBTree *t, CRBNode *n) {
+        CRBNode *p, *g, *gg, *u, *x;
+
+        /*
+         * Paint a single node according to RB-Tree rules. The node must
+         * already be linked into the tree and painted red.
+         * We repaint the node or rotate the tree, if required. In case a
+         * recursive repaint is required, the next node to be re-painted
+         * is returned.
+         *      p: parent
+         *      g: grandparent
+         *      gg: grandgrandparent
+         *      u: uncle
+         *      x: temporary
+         */
+
+        /* node is red, so we can access the parent directly */
+        p = n->__parent_and_color;
+
+        if (!p) {
+                /* Case 1:
+                 * We reached the root. Mark it black and be done. As all
+                 * leaf-paths share the root, the ratio of black nodes on each
+                 * path stays the same. */
+                c_rbnode_set_parent_and_color(n, p, C_RBNODE_BLACK);
+                n = NULL;
+        } else if (c_rbnode_is_black(p)) {
+                /* Case 2:
+                 * The parent is already black. As our node is red, we did not
+                 * change the number of black nodes on any path, nor do we have
+                 * multiple consecutive red nodes. */
+                n = NULL;
+        } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */
+                g = p->__parent_and_color;
+                gg = c_rbnode_parent(g);
+                u = g->right;
+
+                if (u && c_rbnode_is_red(u)) {
+                        /* Case 3:
+                         * Parent and uncle are both red. We know the
+                         * grandparent must be black then. Repaint parent and
+                         * uncle black, the grandparent red and recurse into
+                         * the grandparent. */
+                        c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED);
+                        n = g;
+                } else {
+                        /* parent is red, uncle is black */
+
+                        if (n == p->right) {
+                                /* Case 4:
+                                 * We're the right child. Rotate on parent to
+                                 * become left child, so we can handle it the
+                                 * same as case 5. */
+                                x = n->left;
+                                p->right = n->left;
+                                n->left = p;
+                                if (x)
+                                        c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK);
+                                c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED);
+                                p = n;
+                        }
+
+                        /* 'n' is invalid from here on! */
+                        n = NULL;
+
+                        /* Case 5:
+                         * We're the red left child or a red parent, black
+                         * grandparent and uncle. Rotate on grandparent and
+                         * switch color with parent. Number of black nodes on
+                         * each path stays the same, but we got rid of the
+                         * double red path. As the grandparent is still black,
+                         * we're done. */
+                        x = p->right;
+                        g->left = x;
+                        p->right = g;
+                        if (x)
+                                c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED);
+                        c_rbtree_swap_child(t, gg, g, p);
+                }
+        } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */
+                g = p->__parent_and_color;
+                gg = c_rbnode_parent(g);
+                u = g->left;
+
+                if (u && c_rbnode_is_red(u)) {
+                        c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED);
+                        n = g;
+                } else {
+                        if (n == p->left) {
+                                x = n->right;
+                                p->left = n->right;
+                                n->right = p;
+                                if (x)
+                                        c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK);
+                                c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED);
+                                p = n;
+                        }
+
+                        n = NULL;
+
+                        x = p->left;
+                        g->right = x;
+                        p->left = g;
+                        if (x)
+                                c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED);
+                        c_rbtree_swap_child(t, gg, g, p);
+                }
+        }
+
+        return n;
+}
+
+static inline void c_rbtree_paint(CRBTree *t, CRBNode *n) {
+        assert(t);
+        assert(n);
+
+        while (n)
+                n = c_rbtree_paint_one(t, n);
+}
+
+/**
+ * c_rbtree_add() - add node to tree
+ * @t:          tree to operate one
+ * @p:          parent node to link under, or NULL
+ * @l:          left/right slot of @p (or root) to link at
+ * @n:          node to add
+ *
+ * This links @n into the tree given as @t. The caller must provide the exact
+ * spot where to link the node. That is, the caller must traverse the tree
+ * based on their search order. Once they hit a leaf where to insert the node,
+ * call this function to link it and rebalance the tree.
+ *
+ * A typical insertion would look like this (@t is your tree, @n is your node):
+ *
+ *        CRBNode **i, *p;
+ *
+ *        i = &t->root;
+ *        p = NULL;
+ *        while (*i) {
+ *                p = *i;
+ *                if (compare(n, *i) < 0)
+ *                        i = &(*i)->left;
+ *                else
+ *                        i = &(*i)->right;
+ *        }
+ *
+ *        c_rbtree_add(t, p, i, n);
+ *
+ * Once the node is linked into the tree, a simple lookup on the same tree can
+ * be coded like this:
+ *
+ *        CRBNode *i;
+ *
+ *        i = t->root;
+ *        while (i) {
+ *                int v = compare(n, i);
+ *                if (v < 0)
+ *                        i = (*i)->left;
+ *                else if (v > 0)
+ *                        i = (*i)->right;
+ *                else
+ *                        break;
+ *        }
+ *
+ * When you add nodes to a tree, the memory contents of the node do not matter.
+ * That is, there is no need to initialize the node via c_rbnode_init().
+ * However, if you relink nodes multiple times during their lifetime, it is
+ * usually very convenient to use c_rbnode_init() and c_rbtree_remove_init().
+ * In those cases, you should validate that a node is unlinked before you call
+ * c_rbtree_add().
+ */
+void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) {
+        assert(t);
+        assert(l);
+        assert(n);
+        assert(!p || l == &p->left || l == &p->right);
+        assert(p || l == &t->root);
+
+        c_rbnode_set_parent_and_color(n, p, C_RBNODE_RED);
+        n->left = n->right = NULL;
+        *l = n;
+
+        c_rbtree_paint(t, n);
+}
+
+static inline CRBNode *c_rbtree_rebalance_one(CRBTree *t, CRBNode *p, CRBNode *n) {
+        CRBNode *s, *x, *y, *g;
+
+        /*
+         * Rebalance tree after a node was removed. This happens only if you
+         * remove a black node and one path is now left with an unbalanced
+         * number or black nodes.
+         * This function assumes all paths through p and n have one black node
+         * less than all other paths. If recursive fixup is required, the
+         * current node is returned.
+         */
+
+        if (n == p->left) {
+                s = p->right;
+                if (c_rbnode_is_red(s)) {
+                        /* Case 3:
+                         * We have a red node as sibling. Rotate it onto our
+                         * side so we can later on turn it black. This way, we
+                         * gain the additional black node in our path. */
+                        g = c_rbnode_parent(p);
+                        x = s->left;
+                        p->right = x;
+                        s->left = p;
+                        c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p));
+                        c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED);
+                        c_rbtree_swap_child(t, g, p, s);
+                        s = x;
+                }
+
+                x = s->right;
+                if (!x || c_rbnode_is_black(x)) {
+                        y = s->left;
+                        if (!y || c_rbnode_is_black(y)) {
+                                /* Case 4:
+                                 * Our sibling is black and has only black
+                                 * children. Flip it red and turn parent black.
+                                 * This way we gained a black node in our path,
+                                 * or we fix it recursively one layer up, which
+                                 * will rotate the red sibling as parent. */
+                                c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED);
+                                if (c_rbnode_is_black(p))
+                                        return p;
+
+                                c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK);
+                                return NULL;
+                        }
+
+                        /* Case 5:
+                         * Left child of our sibling is red, right one is black.
+                         * Rotate on parent so the right child of our sibling is
+                         * now red, and we can fall through to case 6. */
+                        x = y->right;
+                        s->left = y->right;
+                        y->right = s;
+                        p->right = y;
+                        if (x)
+                                c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK);
+                        x = s;
+                        s = y;
+                }
+
+                /* Case 6:
+                 * The right child of our sibling is red. Rotate left and flip
+                 * colors, which gains us an additional black node in our path,
+                 * that was previously on our sibling. */
+                g = c_rbnode_parent(p);
+                y = s->left;
+                p->right = y;
+                s->left = p;
+                c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK);
+                if (y)
+                        c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y));
+                c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p));
+                c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK);
+                c_rbtree_swap_child(t, g, p, s);
+        } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */
+                s = p->left;
+                if (c_rbnode_is_red(s)) {
+                        g = c_rbnode_parent(p);
+                        x = s->right;
+                        p->left = x;
+                        s->right = p;
+                        c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(s, g, C_RBNODE_BLACK);
+                        c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED);
+                        c_rbtree_swap_child(t, g, p, s);
+                        s = x;
+                }
+
+                x = s->left;
+                if (!x || c_rbnode_is_black(x)) {
+                        y = s->right;
+                        if (!y || c_rbnode_is_black(y)) {
+                                c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED);
+                                if (c_rbnode_is_black(p))
+                                        return p;
+
+                                c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK);
+                                return NULL;
+                        }
+
+                        x = y->left;
+                        s->right = y->left;
+                        y->left = s;
+                        p->left = y;
+                        if (x)
+                                c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK);
+                        x = s;
+                        s = y;
+                }
+
+                g = c_rbnode_parent(p);
+                y = s->right;
+                p->left = y;
+                s->right = p;
+                c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK);
+                if (y)
+                        c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y));
+                c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p));
+                c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK);
+                c_rbtree_swap_child(t, g, p, s);
+        }
+
+        return NULL;
+}
+
+static inline void c_rbtree_rebalance(CRBTree *t, CRBNode *p) {
+        CRBNode *n = NULL;
+
+        assert(t);
+        assert(p);
+
+        do {
+                n = c_rbtree_rebalance_one(t, p, n);
+                p = n ? c_rbnode_parent(n) : NULL;
+        } while (p);
+}
+
+/**
+ * c_rbtree_remove() - remove node from tree
+ * @t:          tree to operate one
+ * @n:          node to remove
+ *
+ * This removes the given node from its tree. Once unlinked, the tree is
+ * rebalanced.
+ * The caller *must* ensure that the given tree is actually the tree it is
+ * linked on. Otherwise, behavior is undefined.
+ *
+ * This does *NOT* reset @n to being unlinked (for performance reason, this
+ * function *never* modifies @n at all). If you need this, use
+ * c_rbtree_remove_init().
+ */
+void c_rbtree_remove(CRBTree *t, CRBNode *n) {
+        CRBNode *p, *s, *gc, *x, *next = NULL;
+        unsigned long c;
+
+        assert(t);
+        assert(n);
+        assert(c_rbnode_is_linked(n));
+
+        /*
+         * There are three distinct cases during node removal of a tree:
+         *  * The node has no children, in which case it can simply be removed.
+         *  * The node has exactly one child, in which case the child displaces
+         *    its parent.
+         *  * The node has two children, in which case there is guaranteed to
+         *    be a successor to the node (successor being the node ordered
+         *    directly after it). This successor cannot have two children by
+         *    itself (two interior nodes can never be successive). Therefore,
+         *    we can simply swap the node with its successor (including color)
+         *    and have reduced this case to either of the first two.
+         *
+         * Whenever the node we removed was black, we have to rebalance the
+         * tree. Note that this affects the actual node we _remove_, not @n (in
+         * case we swap it).
+         *
+         *      p: parent
+         *      s: successor
+         *      gc: grand-...-child
+         *      x: temporary
+         *      next: next node to rebalance on
+         */
+
+        if (!n->left) {
+                /*
+                 * Case 1:
+                 * The node has no left child. If it neither has a right child,
+                 * it is a leaf-node and we can simply unlink it. If it also
+                 * was black, we have to rebalance, as always if we remove a
+                 * black node.
+                 * But if the node has a right child, the child *must* be red
+                 * (otherwise, the right path has more black nodes as the
+                 * non-existing left path), and the node to be removed must
+                 * hence be black. We simply replace the node with its child,
+                 * turning the red child black, and thus no rebalancing is
+                 * required.
+                 */
+                p = c_rbnode_parent(n);
+                c = c_rbnode_color(n);
+                c_rbtree_swap_child(t, p, n, n->right);
+                if (n->right)
+                        c_rbnode_set_parent_and_color(n->right, p, c);
+                else
+                        next = (c == C_RBNODE_BLACK) ? p : NULL;
+        } else if (!n->right) {
+                /*
+                 * Case 1.1:
+                 * The node has exactly one child, and it is on the left. Treat
+                 * it as mirrored case of Case 1 (i.e., replace the node by its
+                 * child).
+                 */
+                p = c_rbnode_parent(n);
+                c = c_rbnode_color(n);
+                c_rbtree_swap_child(t, p, n, n->left);
+                c_rbnode_set_parent_and_color(n->left, p, c);
+        } else {
+                /*
+                 * Case 2:
+                 * We are dealing with a full interior node with a child not on
+                 * both sides. Find its successor and swap it. Then remove the
+                 * node similar to Case 1. For performance reasons we don't
+                 * perform the full swap, but skip links that are about to be
+                 * removed, anyway.
+                 */
+                s = n->right;
+                if (!s->left) {
+                        /* right child is next, no need to touch grandchild */
+                        p = s;
+                        gc = s->right;
+                } else {
+                        /* find successor and swap partially */
+                        s = c_rbnode_leftmost(s);
+                        p = c_rbnode_parent(s);
+
+                        gc = s->right;
+                        p->left = s->right;
+                        s->right = n->right;
+                        c_rbnode_set_parent(n->right, s);
+                }
+
+                /* node is partially swapped, now remove as in Case 1 */
+                s->left = n->left;
+                c_rbnode_set_parent(n->left, s);
+
+                x = c_rbnode_parent(n);
+                c = c_rbnode_color(n);
+                c_rbtree_swap_child(t, x, n, s);
+                if (gc)
+                        c_rbnode_set_parent_and_color(gc, p, C_RBNODE_BLACK);
+                else
+                        next = c_rbnode_is_black(s) ? p : NULL;
+                c_rbnode_set_parent_and_color(s, x, c);
+        }
+
+        if (next)
+                c_rbtree_rebalance(t, next);
+}
diff --git a/src/basic/c-rbtree.h b/src/basic/c-rbtree.h
new file mode 100644 (file)
index 0000000..20c5515
--- /dev/null
@@ -0,0 +1,297 @@
+#pragma once
+
+/***
+  This file is part of systemd. See COPYING for details.
+
+  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/>.
+***/
+
+/*
+ * Standalone Red-Black-Tree Implementation in Standard ISO-C11
+ *
+ * This header provides an RB-Tree API, that is fully implemented in ISO-C11
+ * and has no external dependencies. Furthermore, tree traversal, memory
+ * allocations, and key comparisons a fully in control of the API user. The
+ * implementation only provides the RB-Tree specific rebalancing and coloring.
+ *
+ * A tree is represented by the "CRBTree" structure. It contains a *singly*
+ * field, which is a pointer to the root node. If NULL, the tree is empty. If
+ * non-NULL, there is at least a single element in the tree.
+ *
+ * Each node of the tree is represented by the "CRBNode" structure. It has
+ * three fields. The @left and @right members can be accessed by the API user
+ * directly to traverse the tree. The third member is an implementation detail
+ * and encodes the parent pointer and color of the node.
+ * API users are required to embed the CRBNode object into their own objects
+ * and then use offsetof() (i.e., container_of() and friends) to turn CRBNode
+ * pointers into pointers to their own structure.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct CRBNode CRBNode;
+typedef struct CRBTree CRBTree;
+
+/**
+ * struct CRBNode - Node of a Red-Black Tree
+ * @__parent_and_color:         internal state
+ * @left:                       left child, or NULL
+ * @right:                      right child, or NULL
+ *
+ * Each node in an RB-Tree must embed an CRBNode object. This object contains
+ * pointers to its left and right child, which can be freely accessed by the
+ * API user at any time. They are NULL, if the node does not have a left/right
+ * child.
+ *
+ * The @__parent_and_color field must never be accessed directly. It encodes
+ * the pointer to the parent node, and the color of the node. Use the accessor
+ * functions instead.
+ *
+ * There is no reason to initialize a CRBNode object before linking it.
+ * However, if you need a boolean state that tells you whether the node is
+ * linked or not, you should initialize the node via c_rbnode_init() or
+ * C_RBNODE_INIT.
+ */
+struct CRBNode {
+        CRBNode *__parent_and_color;
+        CRBNode *left;
+        CRBNode *right;
+};
+
+#define C_RBNODE_INIT(_var) { .__parent_and_color = &(_var) }
+
+CRBNode *c_rbnode_leftmost(CRBNode *n);
+CRBNode *c_rbnode_rightmost(CRBNode *n);
+CRBNode *c_rbnode_next(CRBNode *n);
+CRBNode *c_rbnode_prev(CRBNode *n);
+
+/**
+ * struct CRBTree - Red-Black Tree
+ * @root:       pointer to the root node, or NULL
+ *
+ * Each Red-Black Tree is rooted in an CRBTree object. This object contains a
+ * pointer to the root node of the tree. The API user is free to access the
+ * @root member at any time, and use it to traverse the tree.
+ *
+ * To initialize an RB-Tree, set it to NULL / all zero.
+ */
+struct CRBTree {
+        CRBNode *root;
+};
+
+CRBNode *c_rbtree_first(CRBTree *t);
+CRBNode *c_rbtree_last(CRBTree *t);
+
+void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n);
+void c_rbtree_remove(CRBTree *t, CRBNode *n);
+
+/**
+ * c_rbnode_init() - mark a node as unlinked
+ * @n:          node to operate on
+ *
+ * This marks the node @n as unlinked. The node will be set to a valid state
+ * that can never happen if the node is linked in a tree. Furthermore, this
+ * state is fully known to the implementation, and as such handled gracefully
+ * in all cases.
+ *
+ * You are *NOT* required to call this on your node. c_rbtree_add() can handle
+ * uninitialized nodes just fine. However, calling this allows to use
+ * c_rbnode_is_linked() to check for the state of a node. Furthermore,
+ * iterators and accessors can be called on initialized (yet unlinked) nodes.
+ *
+ * Use the C_RBNODE_INIT macro if you want to initialize static variables.
+ */
+static inline void c_rbnode_init(CRBNode *n) {
+        *n = (CRBNode)C_RBNODE_INIT(*n);
+}
+
+/**
+ * c_rbnode_is_linked() - check whether a node is linked
+ * @n:          node to check, or NULL
+ *
+ * This checks whether the passed node is linked. If you pass NULL, or if the
+ * node is not linked into a tree, this will return false. Otherwise, this
+ * returns true.
+ *
+ * Note that you must have either linked the node or initialized it, before
+ * calling this function. Never call this function on uninitialized nodes.
+ * Furthermore, removing a node via c_rbtree_remove() does *NOT* mark the node
+ * as unlinked. You have to call c_rbnode_init() yourself after removal, or use
+ * the c_rbtree_remove_init() helper.
+ *
+ * Return: true if the node is linked, false if not.
+ */
+static inline _Bool c_rbnode_is_linked(CRBNode *n) {
+        return n && n->__parent_and_color != n;
+}
+
+/**
+ * c_rbnode_parent() - return parent pointer
+ * @n           node to access
+ *
+ * This returns a pointer to the parent of the given node @n. If @n does not
+ * have a parent, NULL is returned. If @n is not linked, @n itself is returned.
+ *
+ * You should not call this on unlinked or uninitialized nodes! If you do, you
+ * better know how its semantics.
+ *
+ * Return: Pointer to parent.
+ */
+static inline CRBNode *c_rbnode_parent(CRBNode *n) {
+        return (CRBNode*)((unsigned long)n->__parent_and_color & ~1UL);
+}
+
+/**
+ * c_rbtree_remove_init() - safely remove node from tree and reinitialize it
+ * @t:          tree to operate on
+ * @n:          node to remove, or NULL
+ *
+ * This is almost the same as c_rbtree_remove(), but extends it slightly, to be
+ * more convenient to use in many cases:
+ *  - if @n is unlinked or NULL, this is a no-op
+ *  - @n is reinitialized after being removed
+ */
+static inline void c_rbtree_remove_init(CRBTree *t, CRBNode *n) {
+        if (c_rbnode_is_linked(n)) {
+                c_rbtree_remove(t, n);
+                c_rbnode_init(n);
+        }
+}
+
+/**
+ * CRBCompareFunc - compare a node to a key
+ * @t:          tree where the node is linked to
+ * @k:          key to compare
+ * @n:          node to compare
+ *
+ * If you use the tree-traversal helpers (which are optional), you need to
+ * provide this callback so they can compare nodes in a tree to the key you
+ * look for.
+ *
+ * The tree @t is provided as optional context to this callback. The key you
+ * look for is provided as @k, the current node that should be compared to is
+ * provided as @n. This function should work like strcmp(), that is, return -1
+ * if @key orders before @n, 0 if both compare equal, and 1 if it orders after
+ * @n.
+ */
+typedef int (*CRBCompareFunc) (CRBTree *t, void *k, CRBNode *n);
+
+/**
+ * c_rbtree_find_node() - find node
+ * @t:          tree to search through
+ * @f:          comparison function
+ * @k:          key to search for
+ *
+ * This searches through @t for a node that compares equal to @k. The function
+ * @f must be provided by the caller, which is used to compare nodes to @k. See
+ * the documentation of CRBCompareFunc for details.
+ *
+ * If there are multiple entries that compare equal to @k, this will return a
+ * pseudo-randomly picked node. If you need stable lookup functions for trees
+ * where duplicate entries are allowed, you better code your own lookup.
+ *
+ * Return: Pointer to matching node, or NULL.
+ */
+static inline CRBNode *c_rbtree_find_node(CRBTree *t, CRBCompareFunc f, const void *k) {
+        CRBNode *i;
+
+        assert(t);
+        assert(f);
+
+        i = t->root;
+        while (i) {
+                int v = f(t, (void *)k, i);
+                if (v < 0)
+                        i = i->left;
+                else if (v > 0)
+                        i = i->right;
+                else
+                        return i;
+        }
+
+        return NULL;
+}
+
+/**
+ * c_rbtree_find_entry() - find entry
+ * @_t:         tree to search through
+ * @_f:         comparison function
+ * @_k:         key to search for
+ * @_t:         type of the structure that embeds the nodes
+ * @_o:         name of the node-member in type @_t
+ *
+ * This is very similar to c_rbtree_find_node(), but instead of returning a
+ * pointer to the CRBNode, it returns a pointer to the surrounding object. This
+ * object must embed the CRBNode object. The type of the surrounding object
+ * must be given as @_t, and the name of the embedded CRBNode member as @_o.
+ *
+ * See c_rbtree_find_node() for more details.
+ *
+ * Return: Pointer to found entry, NULL if not found.
+ */
+#define c_rbtree_find_entry(_m, _f, _k, _t, _o) \
+        ((_t *)(((char *)c_rbtree_find_node((_m), (_f), (_k)) ?: \
+                (char *)NULL + offsetof(_t, _o)) - offsetof(_t, _o)))
+
+/**
+ * c_rbtree_find_slot() - find slot to insert new node
+ * @t:          tree to search through
+ * @f:          comparison function
+ * @k:          key to search for
+ * @p:          output storage for parent pointer
+ *
+ * This searches through @t just like c_rbtree_find_node() does. However,
+ * instead of returning a pointer to a node that compares equal to @k, this
+ * searches for a slot to insert a node with key @k. A pointer to the slot is
+ * returned, and a pointer to the parent of the slot is stored in @p. Both
+ * can be passed directly to c_rbtree_add(), together with your node to insert.
+ *
+ * If there already is a node in the tree, that compares equal to @k, this will
+ * return NULL and store the conflicting node in @p. In all other cases,
+ * this will return a pointer (non-NULL) to the empty slot to insert the node
+ * at. @p will point to the parent node of that slot.
+ *
+ * If you want trees that allow duplicate nodes, you better code your own
+ * insertion function.
+ *
+ * Return: Pointer to slot to insert node, or NULL on conflicts.
+ */
+static inline CRBNode **c_rbtree_find_slot(CRBTree *t, CRBCompareFunc f, const void *k, CRBNode **p) {
+        CRBNode **i;
+
+        assert(t);
+        assert(f);
+        assert(p);
+
+        i = &t->root;
+        *p = NULL;
+        while (*i) {
+                int v = f(t, (void *)k, *i);
+                *p = *i;
+                if (v < 0)
+                        i = &(*i)->left;
+                else if (v > 0)
+                        i = &(*i)->right;
+                else
+                        return NULL;
+        }
+
+        return i;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/test/test-rbtree.c b/src/test/test-rbtree.c
new file mode 100644 (file)
index 0000000..8ae416c
--- /dev/null
@@ -0,0 +1,362 @@
+/***
+  This file is part of systemd. See COPYING for details.
+
+  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/>.
+***/
+
+/*
+ * Tests for RB-Tree
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "c-rbtree.h"
+
+/* verify that all API calls are exported */
+static void test_api(void) {
+        CRBTree t = {};
+        CRBNode n = C_RBNODE_INIT(n);
+
+        assert(!c_rbnode_is_linked(&n));
+
+        /* init, is_linked, add, remove, remove_init */
+
+        c_rbtree_add(&t, NULL, &t.root, &n);
+        assert(c_rbnode_is_linked(&n));
+
+        c_rbtree_remove_init(&t, &n);
+        assert(!c_rbnode_is_linked(&n));
+
+        c_rbtree_add(&t, NULL, &t.root, &n);
+        assert(c_rbnode_is_linked(&n));
+
+        c_rbtree_remove(&t, &n);
+        assert(c_rbnode_is_linked(&n)); /* @n wasn't touched */
+
+        c_rbnode_init(&n);
+        assert(!c_rbnode_is_linked(&n));
+
+        /* first, last, leftmost, rightmost, next, prev */
+
+        assert(!c_rbtree_first(&t));
+        assert(!c_rbtree_last(&t));
+        assert(&n == c_rbnode_leftmost(&n));
+        assert(&n == c_rbnode_rightmost(&n));
+        assert(!c_rbnode_next(&n));
+        assert(!c_rbnode_prev(&n));
+}
+
+/* copied from c-rbtree.c, relies on internal representation */
+static inline _Bool c_rbnode_is_red(CRBNode *n) {
+        return !((unsigned long)n->__parent_and_color & 1UL);
+}
+
+/* copied from c-rbtree.c, relies on internal representation */
+static inline _Bool c_rbnode_is_black(CRBNode *n) {
+        return !!((unsigned long)n->__parent_and_color & 1UL);
+}
+
+static size_t validate(CRBTree *t) {
+        unsigned int i_black, n_black;
+        CRBNode *n, *p, *o;
+        size_t count = 0;
+
+        assert(t);
+        assert(!t->root || c_rbnode_is_black(t->root));
+
+        /* traverse to left-most child, count black nodes */
+        i_black = 0;
+        n = t->root;
+        while (n && n->left) {
+                if (c_rbnode_is_black(n))
+                        ++i_black;
+                n = n->left;
+        }
+        n_black = i_black;
+
+        /*
+         * Traverse tree and verify correctness:
+         *  1) A node is either red or black
+         *  2) The root is black
+         *  3) All leaves are black
+         *  4) Every red node must have two black child nodes
+         *  5) Every path to a leaf contains the same number of black nodes
+         *
+         * Note that NULL nodes are considered black, which is why we don't
+         * check for 3).
+         */
+        o = NULL;
+        while (n) {
+                ++count;
+
+                /* verify natural order */
+                assert(n > o);
+                o = n;
+
+                /* verify consistency */
+                assert(!n->right || c_rbnode_parent(n->right) == n);
+                assert(!n->left || c_rbnode_parent(n->left) == n);
+
+                /* verify 2) */
+                if (!c_rbnode_parent(n))
+                        assert(c_rbnode_is_black(n));
+
+                if (c_rbnode_is_red(n)) {
+                        /* verify 4) */
+                        assert(!n->left || c_rbnode_is_black(n->left));
+                        assert(!n->right || c_rbnode_is_black(n->right));
+                } else {
+                        /* verify 1) */
+                        assert(c_rbnode_is_black(n));
+                }
+
+                /* verify 5) */
+                if (!n->left && !n->right)
+                        assert(i_black == n_black);
+
+                /* get next node */
+                if (n->right) {
+                        n = n->right;
+                        if (c_rbnode_is_black(n))
+                                ++i_black;
+
+                        while (n->left) {
+                                n = n->left;
+                                if (c_rbnode_is_black(n))
+                                        ++i_black;
+                        }
+                } else {
+                        while ((p = c_rbnode_parent(n)) && n == p->right) {
+                                n = p;
+                                if (c_rbnode_is_black(p->right))
+                                        --i_black;
+                        }
+
+                        n = p;
+                        if (p && c_rbnode_is_black(p->left))
+                                --i_black;
+                }
+        }
+
+        return count;
+}
+
+static void insert(CRBTree *t, CRBNode *n) {
+        CRBNode **i, *p;
+
+        assert(t);
+        assert(n);
+        assert(!c_rbnode_is_linked(n));
+
+        i = &t->root;
+        p = NULL;
+        while (*i) {
+                p = *i;
+                if (n < *i) {
+                        i = &(*i)->left;
+                } else {
+                        assert(n > *i);
+                        i = &(*i)->right;
+                }
+        }
+
+        c_rbtree_add(t, p, i, n);
+}
+
+static void shuffle(void **nodes, size_t n_memb) {
+        unsigned int i, j;
+        void *t;
+
+        for (i = 0; i < n_memb; ++i) {
+                j = rand() % n_memb;
+                t = nodes[j];
+                nodes[j] = nodes[i];
+                nodes[i] = t;
+        }
+}
+
+/* run some pseudo-random tests on the tree */
+static void test_shuffle(void) {
+        CRBNode *nodes[256];
+        CRBTree t = {};
+        unsigned int i, j;
+        size_t n;
+
+        /* allocate and initialize all nodes */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                nodes[i] = malloc(sizeof(*nodes[i]));
+                assert(nodes[i]);
+                c_rbnode_init(nodes[i]);
+        }
+
+        /* shuffle nodes and validate *empty* tree */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+        n = validate(&t);
+        assert(n == 0);
+
+        /* add all nodes and validate after each insertion */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                insert(&t, nodes[i]);
+                n = validate(&t);
+                assert(n == i + 1);
+        }
+
+        /* shuffle nodes again */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+
+        /* remove all nodes (in different order) and validate on each round */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                c_rbtree_remove(&t, nodes[i]);
+                n = validate(&t);
+                assert(n == sizeof(nodes) / sizeof(*nodes) - i - 1);
+                c_rbnode_init(nodes[i]);
+        }
+
+        /* shuffle nodes and validate *empty* tree again */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+        n = validate(&t);
+        assert(n == 0);
+
+        /* add all nodes again */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                insert(&t, nodes[i]);
+                n = validate(&t);
+                assert(n == i + 1);
+        }
+
+        /* 4 times, remove half of the nodes and add them again */
+        for (j = 0; j < 4; ++j) {
+                /* shuffle nodes again */
+                shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+
+                /* remove half of the nodes */
+                for (i = 0; i < sizeof(nodes) / sizeof(*nodes) / 2; ++i) {
+                        c_rbtree_remove(&t, nodes[i]);
+                        n = validate(&t);
+                        assert(n == sizeof(nodes) / sizeof(*nodes) - i - 1);
+                        c_rbnode_init(nodes[i]);
+                }
+
+                /* shuffle the removed half */
+                shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes) / 2);
+
+                /* add the removed half again */
+                for (i = 0; i < sizeof(nodes) / sizeof(*nodes) / 2; ++i) {
+                        insert(&t, nodes[i]);
+                        n = validate(&t);
+                        assert(n == sizeof(nodes) / sizeof(*nodes) / 2 + i + 1);
+                }
+        }
+
+        /* shuffle nodes again */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+
+        /* remove all */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                c_rbtree_remove(&t, nodes[i]);
+                n = validate(&t);
+                assert(n == sizeof(nodes) / sizeof(*nodes) - i - 1);
+                c_rbnode_init(nodes[i]);
+        }
+
+        /* free nodes again */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i)
+                free(nodes[i]);
+}
+
+typedef struct {
+        unsigned long key;
+        CRBNode rb;
+} Node;
+
+#define node_from_rb(_rb) ((Node *)((char *)(_rb) - offsetof(Node, rb)))
+
+static int compare(CRBTree *t, void *k, CRBNode *n) {
+        unsigned long key = (unsigned long)k;
+        Node *node = node_from_rb(n);
+
+        return (key < node->key) ? -1 : (key > node->key) ? 1 : 0;
+}
+
+/* run tests against the c_rbtree_find*() helpers */
+static void test_map(void) {
+        CRBNode **slot, *p;
+        CRBTree t = {};
+        Node *nodes[2048];
+        unsigned long i;
+
+        /* allocate and initialize all nodes */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                nodes[i] = malloc(sizeof(*nodes[i]));
+                assert(nodes[i]);
+                nodes[i]->key = i;
+                c_rbnode_init(&nodes[i]->rb);
+        }
+
+        /* shuffle nodes */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+
+        /* add all nodes, and verify that each node is linked */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                assert(!c_rbnode_is_linked(&nodes[i]->rb));
+                assert(!c_rbtree_find_entry(&t, compare, (void *)nodes[i]->key, Node, rb));
+
+                slot = c_rbtree_find_slot(&t, compare, (void *)nodes[i]->key, &p);
+                assert(slot);
+                c_rbtree_add(&t, p, slot, &nodes[i]->rb);
+
+                assert(c_rbnode_is_linked(&nodes[i]->rb));
+                assert(nodes[i] == c_rbtree_find_entry(&t, compare, (void *)nodes[i]->key, Node, rb));
+        }
+
+        /* shuffle nodes again */
+        shuffle((void **)nodes, sizeof(nodes) / sizeof(*nodes));
+
+        /* remove all nodes (in different order) */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) {
+                assert(c_rbnode_is_linked(&nodes[i]->rb));
+                assert(nodes[i] == c_rbtree_find_entry(&t, compare, (void *)nodes[i]->key, Node, rb));
+
+                c_rbtree_remove_init(&t, &nodes[i]->rb);
+
+                assert(!c_rbnode_is_linked(&nodes[i]->rb));
+                assert(!c_rbtree_find_entry(&t, compare, (void *)nodes[i]->key, Node, rb));
+        }
+
+        /* free nodes again */
+        for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i)
+                free(nodes[i]);
+}
+
+int main(int argc, char **argv) {
+        unsigned int i;
+
+        /* we want stable tests, so use fixed seed */
+        srand(0xdeadbeef);
+
+        test_api();
+
+        /*
+         * The tests are pseudo random; run them multiple times, each run will
+         * have different orders and thus different results.
+         */
+        for (i = 0; i < 4; ++i) {
+                test_shuffle();
+                test_map();
+        }
+
+        return 0;
+}