]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
start of the tree list API
authorAlan T. DeKok <aland@freeradius.org>
Tue, 15 Mar 2022 17:50:21 +0000 (13:50 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 15 Mar 2022 17:50:21 +0000 (13:50 -0400)
src/lib/util/tlist.h [new file with mode: 0644]

diff --git a/src/lib/util/tlist.h b/src/lib/util/tlist.h
new file mode 100644 (file)
index 0000000..3c55627
--- /dev/null
@@ -0,0 +1,770 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/** Tree list implementation
+ *
+ * @file src/lib/util/tlist.h
+ *
+ * @copyright 2022 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(dlist_h, "$Id$")
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <freeradius-devel/util/dlist.h>
+
+typedef struct fr_tlist_head_s fr_tlist_head_t;
+typedef struct fr_tlist_s fr_tlist_t;
+
+struct fr_tlist_head_s {
+       fr_tlist_t      *parent;        //!< the parent entry which holds this list.  May be NULL.
+
+       size_t          offset;         //!< Positive offset from start of structure to #fr_tlist_t.
+       char const      *type;          //!< of items contained within the list.  Used for talloc
+                                       ///< validation.
+
+       fr_dlist_head_t dlist_head;
+};
+
+struct fr_tlist_s {
+       fr_tlist_head_t *list_head;     //!< the list which holds this entry
+       fr_tlist_head_t *children;      //!< any child list
+       fr_dlist_t      dlist_entry;    //!< the doubly linked list of entries.
+};
+
+
+/** Find the tlist pointers within a list item
+ *
+ */
+static inline fr_tlist_t *fr_tlist_item_to_entry(void const *item)
+{
+       return (fr_tlist_t *)(((uintptr_t) item) + list_head->offset);
+}
+
+/** Get the item from a fr_tlist_t
+ *
+ */
+static inline void *fr_tlist_entry_to_item(fr_tlist_t const *entry)
+{
+       return (void *)(((uintptr_t) entry) - list_head->offset);
+}
+
+/** Initialise a linked list without metadata
+ *
+ */
+static inline void fr_tlist_entry_init(fr_tlist_t *entry)
+{
+       fr_dlist_entry_init(&entry->dlist_entry);
+       entry->list_head = NULL;
+}
+
+static inline CC_HINT(nonnull) void fr_tlist_entry_unlink(fr_tlist_t *entry)
+{
+       fr_dlist_entry_unlink(&entry->dlist_entry);
+       entry->list_head = NULL;
+}
+
+/** Check if a list entry is part of a list
+ *
+ * This works because the fr_tlist_head_t has an entry in the list.
+ * So if next and prev both point to the entry for the object being
+ * passed in, then it can't be part of a list with a fr_tlist_head_t.
+ *
+ * @return
+ *     - True if in a list.
+ *     - False otherwise.
+ */
+static inline CC_HINT(nonnull) bool fr_tlist_entry_in_list(fr_tlist_t const *entry)
+{
+       return fr_dlist_entry_in_list(&entry->dlist_entry);
+}
+
+/** Link in a single entry after the current entry
+ *
+ * @param[in] entry    to link in entry after.
+ * @param[in] to_link  entry to link in after.
+ */
+static inline CC_HINT(nonnull) void fr_dlist_entry_link_after(fr_tlist_t *entry, fr_tlist_t *to_link)
+{
+       fr_dlist_entry_link_after(&entry->dlist_entry, &to_link->dlist_entry);
+       to_link->list_head = entry->list_head;
+}
+
+/** Link in a single entry before the current entry
+ *
+ * @param[in] entry    to link in entry before.
+ * @param[in] to_link  entry to link in before.
+ */
+static inline CC_HINT(nonnull) void fr_dlist_entry_link_before(fr_tlist_t *entry, fr_tlist_t *to_link)
+{
+       fr_dlist_entry_link_before(&entry->dlist_entry, &to_link->dlist_entry);
+       to_link->list_head = entry->list_head;
+}
+
+// no fr_dlist_entry_move()
+
+/** Replace one entry with another
+ *
+ * @param[in] entry            to replace.
+ * @param[in] replacement      to link in.
+ */
+static inline CC_HINT(nonnull) void fr_tlist_entry_replace(fr_tlist_t *entry, fr_tlist_t *replacement)
+{
+       fr_dlist_entry_replace(&entry->dlist_entry, &replacement->dlist_entry);
+       replacement->list_head = entry->list_head;
+
+       /* Reset links on replaced item */
+       fr_tlist_entry_init(entry);
+}
+
+
+/** Initialise the head structure of a tlist
+ *
+ * @note This variant does not perform talloc validation.
+ *
+ * This function should only be called for the top level of the list.
+ * Please call fr_tlist_add_child() when adding a child list to an
+ * existing entry.
+ *
+ @code{.c}
+   typedef struct {
+       fr_tlist_t      tlist;
+       char const      *field_a;
+       int             *field_b;
+       ...
+   } my_struct_t;
+
+   int my_func(my_struct_t *a, my_struct_t *b)
+   {
+       fr_tlist_head_t head;
+
+       fr_tlist_init(&head, my_struct_t, tlist);
+       fr_tlist_insert_head(&head, a);
+       fr_tlist_insert_head(&head, b);
+   }
+ @endcode
+ *
+ * @param[in] _head    structure to initialise.
+ * @param[in] _type    of item being stored in the list, e.g. fr_value_box_t,
+ *                     fr_dict_attr_t etc...
+ * @param[in] _field   Containing the #fr_tlist_t within item being stored.
+ */
+#define fr_tlist_init(_head, _type, _field) \
+       _Generic((((_type *)0)->_field), fr_tlist_t: _fr_tlist_init(_head, offsetof(_type, _field), NULL))
+
+/** Initialise the head structure of a tlist
+ *
+ * @note This variant *DOES* perform talloc validation.  All items inserted
+ *      into the list must be allocated with talloc.
+ *
+ * @copybrief fr_tlist_init
+ *
+ * @param[in] _head    structure to initialise.
+ * @param[in] _type    of item being stored in the list, e.g. fr_value_box_t,
+ *                     fr_dict_attr_t etc...
+ * @param[in] _field   Containing the #fr_tlist_t within item being stored.
+ */
+#define fr_tlist_talloc_init(_head, _type, _field) \
+       _Generic((((_type *)0)->_field), fr_tlist_t: _fr_tlist_init(_head, offsetof(_type, _field), #_type))
+
+/** Initialise common fields in a tlist
+ *
+ *  The dlist entries point to the tlist structure, which then points to the real structure.
+ */
+static inline void _fr_tlist_init(fr_tlist_head_t *list_head, size_t offset, char const *type)
+{
+       fr_dlist_init(&list_head->dlist_head, fr_tlist_head_t, dlist_head);
+       list_head->offset = offset;
+       list_head->type = type;
+       list_head->parent = NULL;
+}
+
+/** Iterate over the contents of a list, only one level
+ *
+ * @param[in] _list_head       to iterate over.
+ * @param[in] _iter            Name of iteration variable.
+ *                             Will be declared in the scope of the loop.
+ */
+#define fr_tlist_foreach_entry(_list_head, _iter) \
+       for (fr_tlist_t *_iter = fr_dlist_head(&_list_head->dlist_head); _iter; _iter = fr_dlist_next(&_list_head->dlist_head, _iter))
+
+/** Remove all elements in a tlist
+ *
+ * @param[in] list_head        to clear.
+ */
+static inline void fr_tlist_clear(fr_tlist_head_t *list_head)
+{
+       fr_tlist_foreach_entry(&list_head, entry) {
+               entry->list_head = NULL;
+       }
+       fr_dlist_clear(&list_head->dlist_head);
+}
+
+/** Check if a list entry is part of a tlist
+ *
+ * This works because the fr_tlist_head_t has an entry in the list.
+ * So if next and prev both point to the entry for the object being
+ * passed in, then it can't be part of a list with a fr_tlist_head_t.
+ *
+ * @return
+ *     - True if in a list.
+ *     - False otherwise.
+ */
+static inline CC_HINT(nonnull) bool fr_tlist_in_list(fr_tlist_head_t *list_head, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(list_head->offset, ptr);
+
+       return (entry->list_head == list_head);
+}
+
+/** Insert an item into the head of a list
+ *
+ * @note If #fr_tlist_talloc_init was used to initialise #fr_tlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head        to insert ptr into.
+ * @param[in] ptr      to insert.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_insert_head(fr_tlist_head_t *list_head, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+
+       if (fr_dlist_insert_head(&list_head->dlist_head, entry) < 0) return -1;
+
+       entry->list_head = list_head;
+       return 0;
+}
+
+/** Insert an item into the tail of a list
+ *
+ * @note If #fr_tlist_talloc_init was used to initialise #fr_tlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head        to insert ptr into.
+ * @param[in] ptr      to insert.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_insert_tail(fr_tlist_head_t *list_head, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+
+       if (fr_dlist_insert_tail(&list_head->dlist_head, entry) < 0) return -1;
+
+       entry->list_head = list_head;
+       return 0;
+}
+
+/** Insert an item after an item already in the list
+ *
+ * @note If #fr_tlist_talloc_init was used to initialise #fr_tlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head        to insert ptr into.
+ * @param[in] pos      to insert ptr after.
+ * @param[in] ptr      to insert.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_insert_after(fr_tlist_head_t *list_head, void *pos, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+       fr_tlist_t *pos_entry = fr_tlist_item_to_entry(pos);
+
+       if (fr_dlist_insert_after(&list_head->dlist_head, pos, entry) < 0) return -1;
+
+       entry->list_head = list_head;
+       return 0;
+}
+
+/** Insert an item after an item already in the list
+ *
+ * @note If #fr_tlist_talloc_init was used to initialise #fr_tlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head        to insert ptr into.
+ * @param[in] pos      to insert ptr before.
+ * @param[in] ptr      to insert.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_insert_before(fr_tlist_head_t *list_head, void *pos, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+       fr_tlist_t *pos_entry = fr_tlist_item_to_entry(pos);
+
+       if (fr_dlist_insert_before(&list_head->dlist_head, pos, entry) < 0) return -1;
+
+       entry->list_head = list_head;
+       return 0;
+}
+
+/** Return the HEAD item of a list or NULL if the list is empty
+ *
+ * @param[in] list_head                to return the HEAD item from.
+ * @return
+ *     - The HEAD item.
+ *     - NULL if no items exist in the list.
+ */
+static inline CC_HINT(nonnull) void *fr_tlist_head(fr_tlist_head_t const *list_head)
+{
+       fr_tlist_t *entry;
+
+       entry = fr_dlist_head(&list_head->dlist_head);
+       if (!entry) return NULL;
+
+       return fr_tlist_entry_to_item(entry);
+}
+
+/** Check whether a list has any items.
+ *
+ * @return
+ *     - True if it does not.
+ *     - False if it does.
+ */
+static inline CC_HINT(nonnull) bool fr_tlist_empty(fr_tlist_head_t const *list_head)
+{
+       return fr_dlist_empty(&list_head->dlist_head);
+}
+
+/** Check if the list head is initialised
+ *
+ * Memory must be zeroed out or initialised.
+ *
+ * @return
+ *     - True if tlist initialised.
+ *     - False if tlist not initialised
+ */
+static inline CC_HINT(nonnull) bool fr_tlist_initialised(fr_tlist_head_t const *list_head)
+{
+       return fr_dlist_initialised(&list_head->dlist_head);
+}
+
+/** Get the next item in a list
+ *
+ * @note If #fr_dlist_talloc_init was used to initialise #fr_dlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head                containing ptr.
+ * @param[in] ptr              to retrieve the next item from.
+ *                             If ptr is NULL, the HEAD of the list will be returned.
+ * @return
+ *     - The next item in the list if ptr is not NULL.
+ *     - The head of the list if ptr is NULL.
+ *     - NULL if ptr is the tail of the list (no more items).
+ */
+static inline CC_HINT(nonnull(1)) void *fr_tlist_next(fr_tlist_head_t const *list_head, void const *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+       fr_tlist_t *next;
+
+       next = fr_dlist_next(&list_head->dlist_head, entry);
+       if (!next) return NULL;
+
+       return fr_tlist_entry_to_item(next);
+}
+
+/** Get the previous item in a list
+ *
+ * @note If #fr_dlist_talloc_init was used to initialise #fr_dlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * @param[in] list_head                containing ptr.
+ * @param[in] ptr              to retrieve the prev item from.
+ *                             If ptr is NULL, the HEAD of the list will be returned.
+ * @return
+ *     - The prev item in the list if ptr is not NULL.
+ *     - The head of the list if ptr is NULL.
+ *     - NULL if ptr is the tail of the list (no more items).
+ */
+static inline CC_HINT(nonnull(1)) void *fr_tlist_prev(fr_tlist_head_t const *list_head, void const *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+       fr_tlist_t *prev;
+
+       prev = fr_dlist_prev(&list_head->dlist_head, entry);
+       if (!prev) return NULL;
+
+       return fr_tlist_entry_to_item(prev);
+}
+
+/** Remove an item from the list
+ *
+ * @note If #fr_tlist_talloc_init was used to initialise #fr_tlist_head_t
+ *      ptr must be a talloced chunk of the type passed to #fr_tlist_talloc_init.
+ *
+ * When removing items in an iteration loop, the iterator variable must be
+ * assigned the item returned by this function.
+ *
+ * If the iterator variable is not updated, the item removed will be the last item
+ * iterated over, as its prev/prev pointers are set to point to itself.
+ @code{.c}
+       my_item_t *item = NULL;
+
+       while ((item = fr_tlist_prev(&head, item))) {
+               if (item->should_be_removed) {
+                       ...do things with item
+                       item = fr_tlist_remove(&head, item);
+                       continue;
+               }
+       }
+ @endcode
+ *
+ * @param[in] list_head        to remove ptr from.
+ * @param[in] ptr      to remove.
+ * @return
+ *     - The previous item in the list (makes iteration easier).
+ *     - NULL if we just removed the head of the list.
+ */
+static inline CC_HINT(nonnull(1)) void *fr_tlist_remove(fr_tlist_head_t *list_head, void *ptr)
+{
+       fr_tlist_t *entry = fr_tlist_item_to_entry(ptr);
+       fr_tlist_t *prev;
+
+       prev = fr_dlist_remove(&list_head->dlist_head, entry);
+       entry->list_head = NULL;
+
+       if (!prev) return NULL;
+       return fr_tlist_entry_to_item(prev);
+}
+
+/** Remove the head item in a list
+ *
+ * @param[in] list_head to remove head item from.
+ * @return
+ *     - The item removed.
+ *     - NULL if not items in tlist.
+ */
+static inline CC_HINT(nonnull(1)) void *fr_tlist_pop_head(fr_tlist_head_t *list_head)
+{
+       fr_tlist_t *entry;
+
+       entry = fr_dlist_head(&list_head->dlist_head);
+       if (!entry) return NULL;
+
+       (void) fr_tlist_remove(&list_head->dlist_head, entry);
+
+       return fr_tlist_entry_to_item(entry);
+}
+
+/** Remove the tail item in a list
+ *
+ * @param[in] list_head to remove tail item from.
+ * @return
+ *     - The item removed.
+ *     - NULL if not items in tlist.
+ */
+static inline CC_HINT(nonnull(1)) void *fr_tlist_pop_tail(fr_tlist_head_t *list_head)
+{
+       fr_tlist_t *entry;
+
+       entry = fr_dlist_tail(&list_head->dlist_head);
+       if (!entry) return NULL;
+
+       (void) fr_tlist_remove(&list_head->dlist_head, entry);
+
+       return fr_tlist_entry_to_item(entry);
+}
+
+/** Replace an item in a dlist
+ *
+ * @param list_head in which the original item is.
+ * @param item to replace.
+ * @param ptr replacement item.
+ * @return
+ *     - The item replaced
+ *     - NULL if nothing replaced
+ */
+static inline CC_HINT(nonnull) void *fr_tlist_replace(fr_dlist_head_t *list_head, void *item, void *ptr)
+{
+       fr_tlist_t *item_entry;
+       fr_tlist_t *ptr_entry;
+       
+       item_entry = fr_tlist_item_to_entry(item);
+       if (!fr_tlist_entry_in_list(item_entry)) return NULL;
+
+       ptr_entry = fr_tlist_item_to_entry(ptr);
+
+       fr_tlist_entry_replace(item_entry, ptr_entry);
+
+       return item;
+}
+
+
+/** Check all items in the list are valid
+ *
+ * Checks item talloc headers and types to ensure they're consistent
+ * with what we expect.
+ *
+ * Does nothing if the list was not initialised with #fr_tlist_talloc_init.
+ */
+#ifndef TALLOC_GET_TYPE_ABORT_NOOP
+static inline CC_HINT(nonnull) void fr_tlist_verify(char const *file, int line, fr_tlist_head_t const *list_head)
+{
+       fr_tlist_t *entry;
+
+       if (!list_head->type) return;
+
+       fr_assert_msg(fr_tlist_initialised(list_head), "CONSISTENCY CHECK FAILED %s[%i]: tlist not initialised",
+                     file, line);
+
+       for (entry = fr_dlist_head(&list_head->dlist_head);
+            entry;
+            entry = fr_tlist_next(&list_head->dlist_head, entry)) {
+               void *item = fr_tlist_entry_to_item(entry);
+
+               fr_assert_msg(entry->list_head == list_head, "CONSISTENCY CHECK FAILED %s[%i]: tlist entry %p has wrong parent",
+                             file, line, entry);
+
+               item = _talloc_get_type_abort(item, list_head->type, __location__);
+
+               if (entry->children) {
+                       fr_assert_msg(entry->children->type != NULL, "CONSISTENCY CHECK FAILED %s[%i]: tlist entry %p has non-talloc'd child list",
+                                     file, line, entry);
+                       
+                       fr_assert_msg(strcmp(entry->children->type, list_head->type) == 0,
+                                     "CONSISTENCY CHECK FAILED %s[%i]: tlist entry %p has different child type from parent",
+                                     file, line, entry);
+
+                       fr_tlist_verify(file, line, entry->children);
+               }
+       }
+}
+#  define FR_TLIST_VERIFY(_head) fr_tlist_verify(__FILE__, __LINE__, _head)
+#elif !defined(NDEBUG)
+#  define FR_TLIST_VERIFY(_head) fr_assert(_head)
+#else
+#  define FR_TLIST_VERIFY(_head)
+#endif
+
+
+/** Merge two lists, inserting the source at the tail of the destination
+ *
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_move(fr_tlist_head_t *list_dst, fr_tlist_head_t *list_src)
+{
+       fr_tlist_t *entry;
+
+#ifdef WITH_VERIFY_PTR
+       /*
+        *      Must be both talloced or both not
+        */
+       if (!fr_cond_assert((list_dst->type && list_src->type) || (!list_dst->type && !list_src->type))) return -1;
+
+       /*
+        *      Must be of the same type
+        */
+       if (!fr_cond_assert(!list_dst->type || (strcmp(list_dst->type, list_src->type) == 0))) return -1;
+#endif
+
+       entry = fr_dlist_head(&list_src->dlist_head);
+       if (!entry) return 0;
+
+       if (fr_dlist_move(&list_dst->dlist_head, &list_src->dlist_head) < 0) return -1;
+
+       /*
+        *      Update new parent from the middle of the list to the end.
+        */
+       do {
+               entry->list_head = list_dst;
+       } while ((entry = fr_tlist_next(&list_dst->dlist_head, entry)) != NULL);
+
+       return 0;
+}
+
+/** Merge two lists, inserting the source at the head of the destination
+ *
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static inline CC_HINT(nonnull) int fr_tlist_move_head(fr_tlist_head_t *list_dst, fr_tlist_head_t *list_src)
+{
+       fr_tlist_t *entry, *middle;
+
+#ifdef WITH_VERIFY_PTR
+       /*
+        *      Must be both talloced or both not
+        */
+       if (!fr_cond_assert((list_dst->type && list_src->type) || (!list_dst->type && !list_src->type))) return -1;
+
+       /*
+        *      Must be of the same type
+        */
+       if (!fr_cond_assert(!list_dst->type || (strcmp(list_dst->type, list_src->type) == 0))) return -1;
+#endif
+
+       middle = fr_dlist_head(&list_dst->dlist_head);
+
+       if (fr_dlist_move_head(&list_dst->dlist_head, &list_src->dlist_head) < 0) return -1;
+
+       /*
+        *      Update new parent from the start of the list to the middle.
+        */
+       for (entry = fr_dlist_head(&list_dst->dlist_head);
+            entry && (entry != middle);
+            entry = fr_tlist_next(&list_dst->dlist_head, entry)) {
+               entry->list_head = list_dst;
+       }
+
+       return 0;
+}
+
+/** Free the first item in the list
+ *
+ * @param[in] list_head                to free head item in.
+ */
+static inline void fr_tlist_talloc_free_head(fr_tlist_head_t *list_head)
+{
+       talloc_free(fr_tlist_pop_head(list_head));
+}
+
+/** Free the last item in the list
+ *
+ * @param[in] list_head                to free tail item in.
+ */
+static inline void fr_tlist_talloc_free_tail(fr_tlist_head_t *list_head)
+{
+       talloc_free(fr_tlist_pop_head(list_head));
+}
+
+/** Free the item specified
+ *
+ * @param[in] list_head                to free item in.
+ * @param[in] ptr              to remove and free.
+ * @return
+ *     - NULL if no more items in the list.
+ *     - Previous item in the list
+ */
+static inline void *fr_tlist_talloc_free_item(fr_tlist_head_t *list_head, void *ptr)
+{
+       void *prev;
+
+       prev = fr_tlist_remove(list_head, ptr);
+       talloc_free(ptr);
+       return prev;
+}
+
+/** Free all items in a doubly linked list (with talloc)
+ *
+ * @param[in] head of list to free.
+ */
+static inline void fr_tlist_talloc_free(fr_tlist_head_t *head)
+{
+       void *e;
+
+       while ((e == fr_tlist_pop_head(head)) != NULL) {
+               talloc_free(e);
+       }
+}
+
+/** Free all items in a doubly linked list from the tail backwards
+ *
+ * @param[in] list_head of list to free.
+ */
+static inline void fr_tlist_talloc_reverse_free(fr_tlist_head_t *list_head)
+{
+       void *e;
+
+       while ((e == fr_tlist_pop_tail(hlist_head)) != NULL) {
+               talloc_free(e);
+       }
+}
+
+/** Return the number of elements in the tlist
+ *
+ * @param[in] list_head of list to count elements for.
+ */
+static inline unsigned int fr_tlist_num_elements(fr_tlist_head_t const *list_head)
+{
+       return fr_dlist_num_elements(&list_head->dlist_head);
+}
+
+/* fr_dlist_sort_split() and following still need to be ported */
+
+/*
+ *     Functions which are specific to tlists
+ */
+
+
+/** Initialize a child tlist based on a parent entry
+ *
+ * @param[in] entry    the entry which will be the parent of the children
+ * @param[in] children structure to initialise.  Usually in the same parent structure as "entry"
+ */
+static inline void fr_tlist_init_children(fr_tlist_t *entry, fr_tlist_head_t *children)
+{
+       fr_tlist_head_t *list_head;
+
+       fr_assert(entry->children == NULL);     
+       fr_assert(entry->list_head != NULL);
+
+       list_head = entry->list_head;
+
+       fr_dlist_init(&child->dlist_head, fr_tlist_head_t, dlist_head);
+       children->offset = list_head->offset;
+       children->type = list_head->type;
+       children->parent = NULL;
+}
+
+/** Add a pre-initialized child tlist to a parent entry.
+ *
+ * @param[in] entry    the entry which will be the parent of the children
+ * @param[in] children structure to initialise.  Usually in the same parent structure as "entry"
+ */
+static inline int fr_tlist_add_children(fr_tlist_t *entry, fr_tlist_head_t *children)
+{
+       if (entry->children) return -1;
+
+       children->parent = entry;
+
+       entry->children = children;
+
+       return 0;
+}
+
+
+/** Remove a child tlist from a parent entry
+ *
+ * @param[in] entry    the entry which will have the children removed
+ */
+static inline fr_tlist_head_t *fr_tlist_remove_children(fr_tlist_t *entry)
+{
+       fr_tlist_head_t *children = entry->children;
+
+       if (!entry->children) return NULL;
+
+       entry->children->parent = NULL;
+       entry->children = NULL;
+
+       return children;
+}
+
+#ifdef __cplusplus
+}
+#endif