diff options
Diffstat (limited to 'src/lib/cursor.c')
-rw-r--r-- | src/lib/cursor.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/lib/cursor.c b/src/lib/cursor.c new file mode 100644 index 0000000..ae88ebe --- /dev/null +++ b/src/lib/cursor.c @@ -0,0 +1,461 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 of the + * License as published by the Free Software Foundation. + * + * 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 + */ + +/** + * $Id$ + * + * @file cursor.c + * @brief Functions to iterate over collections of VALUE_PAIRs + * + * @note Do not modify collections of VALUE_PAIRs pointed to be a cursor + * with none fr_cursor_* functions, during the lifetime of that cursor. + * + * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2013-2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2013-2015 The FreeRADIUS Server Project. + */ + +#include <freeradius-devel/libradius.h> + +/** Internal function to update cursor state + * + * @param cursor to operate on. + * @param vp to set current and found positions to. + * @return value passed in as vp. + */ +inline static VALUE_PAIR *fr_cursor_update(vp_cursor_t *cursor, VALUE_PAIR *vp) +{ + if (!vp) { + cursor->next = NULL; + cursor->current = NULL; + + return NULL; + } + + cursor->next = vp->next; + cursor->current = vp; + cursor->found = vp; + + return vp; +} + +/** Setup a cursor to iterate over attribute pairs + * + * @param cursor Where to initialise the cursor (uses existing structure). + * @param const_vp to start from. + * @return the attribute pointed to by vp. + */ +VALUE_PAIR *fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR * const *const_vp) +{ + VALUE_PAIR **vp; + + if (!const_vp || !cursor) { + return NULL; + } + + memset(cursor, 0, sizeof(*cursor)); + + memcpy(&vp, &const_vp, sizeof(vp)); /* stupid const hacks */ + + /* + * Useful check to see if uninitialised memory is pointed + * to by vp + */ +#ifndef NDEBUG + if (*vp) VERIFY_VP(*vp); +#endif + memcpy(&cursor->first, &vp, sizeof(cursor->first)); + cursor->current = *cursor->first; + + if (cursor->current) { + VERIFY_VP(cursor->current); + cursor->next = cursor->current->next; + } + + return cursor->current; +} + +/** Copy a cursor + * + * @param in Cursor to copy. + * @param out Where to copy the cursor to. + */ +void fr_cursor_copy(vp_cursor_t *out, vp_cursor_t *in) +{ + memcpy(out, in, sizeof(*out)); +} + +/** Rewind cursor to the start of the list + * + * @param cursor to operate on. + * @return the VALUE_PAIR at the start of the list. + */ +VALUE_PAIR *fr_cursor_first(vp_cursor_t *cursor) +{ + if (!cursor->first) return NULL; + + cursor->current = *cursor->first; + + if (cursor->current) { + VERIFY_VP(cursor->current); + cursor->next = cursor->current->next; + if (cursor->next) VERIFY_VP(cursor->next); + cursor->found = NULL; + } + + return cursor->current; +} + +/** Wind cursor to the last pair in the list + * + * @param cursor to operate on. + * @return the VALUE_PAIR at the end of the list. + */ +VALUE_PAIR *fr_cursor_last(vp_cursor_t *cursor) +{ + if (!cursor->first || !*cursor->first) return NULL; + + /* Need to start at the start */ + if (!cursor->current) fr_cursor_first(cursor); + + /* Wind to the end */ + while (cursor->next) fr_cursor_next(cursor); + + return cursor->current; +} + +/** Iterate over a collection of VALUE_PAIRs of a given type in the pairlist + * + * Find the next attribute of a given type. If no fr_cursor_next_by_* function + * has been called on a cursor before, or the previous call returned + * NULL, the search will start with the current attribute. Subsequent calls to + * fr_cursor_next_by_* functions will start the search from the previously + * matched attribute. + * + * @param cursor to operate on. + * @param attr number to match. + * @param vendor number to match (0 for none vendor attribute). + * @param tag to match. Either a tag number or TAG_ANY to match any tagged or + * untagged attribute, TAG_NONE to match attributes without tags. + * @return the next matching VALUE_PAIR, or NULL if no VALUE_PAIRs match. + */ +VALUE_PAIR *fr_cursor_next_by_num(vp_cursor_t *cursor, unsigned int attr, unsigned int vendor, int8_t tag) +{ + VALUE_PAIR *i; + + if (!cursor->first) return NULL; + + for (i = !cursor->found ? cursor->current : cursor->found->next; + i != NULL; + i = i->next) { + VERIFY_VP(i); + if ((i->da->attr == attr) && (i->da->vendor == vendor) && + (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) { + break; + } + } + + return fr_cursor_update(cursor, i); +} + +/** Iterate over attributes of a given DA in the pairlist + * + * Find the next attribute of a given type. If no fr_cursor_next_by_* function + * has been called on a cursor before, or the previous call returned + * NULL, the search will start with the current attribute. Subsequent calls to + * fr_cursor_next_by_* functions will start the search from the previously + * matched attribute. + * + * @note DICT_ATTR pointers are compared, not the attribute numbers and vendors. + * + * @param cursor to operate on. + * @param da to match. + * @param tag to match. Either a tag number or TAG_ANY to match any tagged or + * untagged attribute, TAG_NONE to match attributes without tags. + * @return the next matching VALUE_PAIR, or NULL if no VALUE_PAIRs match. + */ +VALUE_PAIR *fr_cursor_next_by_da(vp_cursor_t *cursor, DICT_ATTR const *da, int8_t tag) +{ + VALUE_PAIR *i; + + if (!cursor->first) return NULL; + + for (i = !cursor->found ? cursor->current : cursor->found->next; + i != NULL; + i = i->next) { + VERIFY_VP(i); + if ((i->da == da) && + (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) { + break; + } + } + + return fr_cursor_update(cursor, i); +} + +/** Advanced the cursor to the next VALUE_PAIR + * + * @param cursor to operate on. + * @return the next VALUE_PAIR, or NULL if no more VALUE_PAIRS in the collection. + */ +VALUE_PAIR *fr_cursor_next(vp_cursor_t *cursor) +{ + if (!cursor->first) return NULL; + + cursor->current = cursor->next; + if (cursor->current) { + VERIFY_VP(cursor->current); + + /* + * Set this now in case 'current' gets freed before + * fr_cursor_next is called again. + */ + cursor->next = cursor->current->next; + + /* + * Next call to fr_cursor_next_by_num will start from the current + * position in the list, not the last found instance. + */ + cursor->found = NULL; + } + + return cursor->current; +} + +/** Return the next VALUE_PAIR without advancing the cursor + * + * @param cursor to operate on. + * @return the next VALUE_PAIR, or NULL if no more VALUE_PAIRS in the collection. + */ +VALUE_PAIR *fr_cursor_next_peek(vp_cursor_t *cursor) +{ + return cursor->next; +} + +/** Return the VALUE_PAIR the cursor current points to + * + * @param cursor to operate on. + * @return the VALUE_PAIR the cursor currently points to. + */ +VALUE_PAIR *fr_cursor_current(vp_cursor_t *cursor) +{ + if (cursor->current) VERIFY_VP(cursor->current); + + return cursor->current; +} + +/** Insert a single VALUE_PAIR at the end of the list + * + * @note Will not advance cursor position to new attribute, but will set cursor + * to this attribute, if it's the first one in the list. + * + * Insert a VALUE_PAIR at the end of the list. + * + * @param cursor to operate on. + * @param vp to insert. + */ +void fr_cursor_insert(vp_cursor_t *cursor, VALUE_PAIR *vp) +{ + VALUE_PAIR *i; + + if (!fr_assert(cursor->first)) return; /* cursor must have been initialised */ + + if (!vp) return; + + VERIFY_VP(vp); + + /* + * Only allow one VP to by inserted at a time + */ + vp->next = NULL; + + /* + * Cursor was initialised with a pointer to a NULL value_pair + */ + if (!*cursor->first) { + *cursor->first = vp; + cursor->current = vp; + + return; + } + + /* + * We don't yet know where the last VALUE_PAIR is + * + * Assume current is closer to the end of the list and + * use that if available. + */ + if (!cursor->last) cursor->last = cursor->current ? cursor->current : *cursor->first; + + VERIFY_VP(cursor->last); + + /* + * Wind last to the end of the list. + */ + if (cursor->last->next) { + for (i = cursor->last; i; i = i->next) { + VERIFY_VP(i); + cursor->last = i; + } + } + + /* + * Either current was never set, or something iterated to the + * end of the attribute list. In both cases the newly inserted + * VALUE_PAIR should be set as the current VALUE_PAIR. + */ + if (!cursor->current) cursor->current = vp; + + /* + * Add the VALUE_PAIR to the end of the list + */ + cursor->last->next = vp; + cursor->last = vp; /* Wind it forward a little more */ + + /* + * If the next pointer was NULL, and the VALUE_PAIR + * just added has a next pointer value, set the cursor's next + * pointer to the VALUE_PAIR's next pointer. + */ + if (!cursor->next) cursor->next = cursor->current->next; +} + +/** Merges multiple VALUE_PAIR into the cursor + * + * Add multiple VALUE_PAIR from add to cursor. + * + * @param cursor to insert VALUE_PAIRs with + * @param add one or more VALUE_PAIRs (may be NULL, which results in noop). + */ +void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *add) +{ + vp_cursor_t from; + VALUE_PAIR *vp; + + if (!add) return; + + if (!fr_assert(cursor->first)) return; /* cursor must have been initialised */ + + for (vp = fr_cursor_init(&from, &add); + vp; + vp = fr_cursor_next(&from)) { + fr_cursor_insert(cursor, vp); + } +} + +/** Remove the current pair + * + * @todo this is really inefficient and should be fixed... + * + * The current VP will be set to the one before the VP being removed, + * this is so the commonly used check and remove loop (below) works + * as expected. + @code {.c} + for (vp = fr_cursor_init(&cursor, head); + vp; + vp = fr_cursor_next(&cursor) { + if (<condition>) { + vp = fr_cursor_remove(&cursor); + talloc_free(vp); + } + } + @endcode + * + * @param cursor to remove the current pair from. + * @return NULL on error, else the VALUE_PAIR that was just removed. + */ +VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor) +{ + VALUE_PAIR *vp, *before; + + if (!fr_assert(cursor->first)) return NULL; /* cursor must have been initialised */ + + vp = cursor->current; + if (!vp) return NULL; + + /* + * Where VP is head of the list + */ + if (*(cursor->first) == vp) { + *(cursor->first) = vp->next; + cursor->current = vp->next; + cursor->next = vp->next ? vp->next->next : NULL; + before = NULL; + goto fixup; + } + + /* + * Where VP is not head of the list + */ + before = *(cursor->first); + if (!before) return NULL; + + /* + * Find the VP immediately preceding the one being removed + */ + while (before->next != vp) before = before->next; + + cursor->next = before->next = vp->next; /* close the gap */ + cursor->current = before; /* current jumps back one, but this is usually desirable */ + +fixup: + vp->next = NULL; /* limit scope of fr_pair_list_free() */ + + /* + * Fixup cursor->found if we removed the VP it was referring to, + * and point to the previous one. + */ + if (vp == cursor->found) cursor->found = before; + + /* + * Fixup cursor->last if we removed the VP it was referring to + */ + if (vp == cursor->last) cursor->last = cursor->current; + return vp; +} + +/** Replace the current pair + * + * @todo this is really inefficient and should be fixed... + * + * @param cursor to replace the current pair in. + * @param new VALUE_PAIR to insert. + * @return NULL on error, else the VALUE_PAIR we just replaced. + */ +VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new) +{ + VALUE_PAIR *vp, **last; + + if (!fr_assert(cursor->first)) return NULL; /* cursor must have been initialised */ + + vp = cursor->current; + if (!vp) { + *cursor->first = new; + return NULL; + } + + last = cursor->first; + while (*last != vp) { + last = &(*last)->next; + } + + fr_cursor_next(cursor); /* Advance the cursor past the one were about to replace */ + + *last = new; + new->next = vp->next; + vp->next = NULL; + + return vp; +} |