From: Alan T. DeKok Date: Tue, 15 Mar 2022 17:50:21 +0000 (-0400) Subject: start of the tree list API X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7be2127244e31f05cb2c5c4b715496f10d988be8;p=thirdparty%2Ffreeradius-server.git start of the tree list API --- diff --git a/src/lib/util/tlist.h b/src/lib/util/tlist.h new file mode 100644 index 00000000000..3c5562715f7 --- /dev/null +++ b/src/lib/util/tlist.h @@ -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 + +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