diff options
Diffstat (limited to 'lib/vio_fifo.c')
-rw-r--r-- | lib/vio_fifo.c | 1825 |
1 files changed, 1084 insertions, 741 deletions
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index 685f33ec..0612c465 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -19,15 +19,14 @@ * Boston, MA 02111-1307, USA. */ -#include <stddef.h> -#include <string.h> +#include "misc.h" +#include <stdio.h> #include "vio_fifo.h" +#include "qfstring.h" #include "network.h" -#include "list_util.h" #include "memory.h" -#include "zassert.h" /*============================================================================== * VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer. @@ -39,503 +38,558 @@ * ever needed. * * When releasing lumps, keeps one lump "spare", to be reused as necessary. - * This is used in ... TODO <<<< And is released... * *------------------------------------------------------------------------------ * Implementation notes: * - * The FIFO is initialised with all pointers NULL -- so with no lumps at all. + * The FIFO is initialised with one lump in it (the "own_lump", which is + * embedded in the FIFO structure). There is always at least one lump in + * the FIFO. * - * Once a lump has been allocated there is always one lump in the FIFO. + * The hold_ptr allows the get_ptr to move forward, but retaining the data in + * the FIFO until the hold_ptr is cleared. Can move the get_ptr back to the + * hold_ptr to reread the data. + * + * The end_ptr allows put_ptr to move forward, but the new data cannot be got + * from the FIFO until the end_ptr is cleared. Can discard the new data + * and move the put_ptr back to the end_ptr. + * + * There are four lumps of interest: + * + * * head -- where the hold_mark is, if there is one. + * + * * get_lump -- where the get_ptr is. + * Same as head when no hold_mark. + * + * * end_lump -- where the end_mark is, if there is one. + * Same as tail when no end mark. + * + * * tail -- where the put_ptr is. + * + * Some or all of those may be the same, depending on how big the FIFO is. * * The following are expected to be true: * - * * put_ptr == get_ptr => FIFO empty + * * p_start == &get_ptr => no hold mark + * &hold_ptr => hold mark is set + * + * * put_ptr == get_ptr => FIFO empty -- unless *p_start != get_ptr. * - * * put_ptr == tail->end -- at all times (NULL when no lumps) + * * put_end == tail->end -- at all times * * put_ptr >= tail->data ) otherwise something is broken * put_ptr <= tail->end ) * - * * get_ptr == head->end -- when there is more than one lump - * get_ptr <= put_ptr -- when there is only one lump + * * p_get_end == &get_lump->end -- when get_lump != end_lump + * == &end_ptr -- when get_lump == end_lump & end mark set + * == &put_ptr -- when get_lump == end_lump & no end mark * - * get_ptr >= head->data ) otherwise something is broken - * get_ptr <= head->end ) + * get_ptr >= get_lump->data ) otherwise something is broken + * get_ptr <= get_lump->end ) * * * put_ptr == put_end => tail lump is full * put_ptr < put_end => space exists in the tail lump * put_ptr > put_end => broken * - * * get_ptr == get_end => head lump is empty - * BUT if there is only one lump, make sure that - * get_end == put_ptr. - * get_ptr < get_end => data exists in the head lump - * get_ptr > get_end => broken + * * get_ptr == *p_get_end => nothing to get -- get_lump == end_lump + * get_ptr < *p_get_end => data exists in the current get_lump + * get_ptr > *p_get_end => broken + * + * * p_end == &put_ptr => no end mark + * == &end_ptr => end mark is set * * Note that: * - * * when the get_ptr reaches the put_ptr the pointers are reset to the - * start of the one and only lump. + * * while get_ptr < *p_get_end can get stuff without worrying about other + * pointers or moving between lumps etc. + * + * When get_ptr reaches *p_get_end, however, must move to the next lump, + * if possible, or collapse the pointers if have hit the put_ptr. Keeping + * the get_ptr honest in this way: (a) ensures that will always get from + * the beginning of a lump if possible; (b) simplifies the handling of + * hold_ptr et al (because get_ptr is never in the ambiguous position + * at the end of one lump, which is the same as the start of the next). + * + * Similarly, while put_ptr < put_end, can put stuff without worrying + * about other pointers or moving between lumps etc. Will leave the + * put_ptr at the very end of the current lump if just fills it. + * + * * the value of p_get_end depends on whether get_lump == end_lump, and + * then whether there is an end_ptr. But at any time, points to the end + * of what can be read by get_ptr without stepping between lumps etc. + * + * Note that get_ptr == p_get_end <=> is at the current end of the FIFO, + * because after any get operation, will advance to the next lump. If + * FIFO is empty after advancing the get_ptr, will reset the pointers + * back to the start of the then current (and only) lump. + * + * * some care must be taken to ensure that if the fifo is empty, the + * pointers will be at the start of one empty lump. + * + * In this context, empty means nothing between *p_start and put_ptr. + * Noting that *p_start is &hold_ptr or &get_ptr, depending on whether + * there is a hold mark or not. (If *p_start == put_ptr, there may be + * an end mark, but it must be end_ptr == put_ptr !) + * + * - get_ptr -- as above, if this hits *p_get_end, must: * - * Everywhere that the get_ptr is moved, must check for meeting the - * put_ptr and reset pointers. At the same time, when reaches the end of - * a lump, gets rid of it. + * * step to the next lump, if there is one, and, + * unless something is held behind the get_ptr, + * release the lump just stepped from. * - * * when advancing the put_ptr does not check for advancing the get_end. + * * if has hit put_ptr, reset pointers -- unless there + * is something held behind the get_ptr. * - * The one exception to this, is that when the put_ptr advances to a new - * block, if there was one lump, sets the get_end to the end of that block. + * - put_ptr -- is always somewhere in the tail lump. * - * Everywhere that the get_end is used, must check for there being one - * lump and the possibility that put_ptr has changed. + * - end_ptr -- when a new lump is added, if the end_ptr is at the + * end of the last lump, it is moved to the start of the + * new last lump (along with the put_ptr). + * + * If the end_ptr is equal to the put_ptr and the get_ptr + * hits it, if the pointers are reset, the end_ptr will be + * reset along with the put_ptr. + * + * If the put_ptr is reset back to the end_ptr, need to + * see if the FIFO is empty, and reset pointers if it is. + * + * - hold_ptr -- because the pointers are reset whenever the FIFO + * becomes empty, when hold_ptr is set, it will be at + * the start of an empty FIFO, or somewhere in a not- + * empty one. When a hold_ptr is cleared, the get_ptr + * may be equal to the put_ptr, and pointers must be + * reset. */ +inline static void vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump, + char* ptr) ; +inline static void vio_fifo_set_get_end(vio_fifo vff) ; +inline static void vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump upto) ; +static void vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) ; -/*============================================================================== - * Initialisation, allocation and freeing of FIFO and lumps thereof. +/*------------------------------------------------------------------------------ + * Test whether there is a hold mark. */ - -/* Return default size, or given size rounded up to 16 byte boundary */ -static size_t -vio_fifo_size(size_t size) +inline static bool +vio_fifo_have_hold_mark(vio_fifo vff) { - if (size == 0) - return 4096 ; - else - return ((size + 16 - 1) / 16) * 16 ; + return vff->p_start == &vff->hold_ptr ; } ; -/*============================================================================== - * Initialise VTY I/O FIFO -- allocating if required. +/*------------------------------------------------------------------------------ + * Test whether there is an end mark. */ -extern vio_fifo -vio_fifo_init_new(vio_fifo vf, size_t size) +inline static bool +vio_fifo_have_end_mark(vio_fifo vff) { - if (vf == NULL) - vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; - else - memset(vf, 0, sizeof(vio_fifo_t)) ; - - /* Zeroising the the vio_fifo_t has set: - * - * lump -- base pair, both pointers NULL => list is empty - * - * put_ptr -- NULL ) no lump to put anything into - * put_end -- NULL ) put_ptr == put_end => no room in current lump - * - * get_ptr -- NULL ) no lump to get anything from - * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump - * - * rdr_lump -- NULL ) no rdr_lump - * rdr_ptr -- NULL - * - * spare -- NULL no spare lump - * - * ALSO put_ptr == get_ptr => FIFO is empty ! - */ - - vf->size = vio_fifo_size(size) ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - return vf ; -} + return vff->p_end == &vff->end_ptr ; +} ; /*------------------------------------------------------------------------------ - * Free contents of given FIFO, and free FIFO structure as well, if required. + * The FIFO is empty, with one lump -- reset all pointers. * - * Does nothing if given a NULL pointer -- must already have been freed ! + * Preserves and hold mark or end mark -- so no need to change p_start or p_end. * - * If does not free the FIFO structure, resets it all empty. + * Resets the hold_ptr and the end_ptr whether there is a hold and/or end mark + * or not -- saves testing for whether to do it or not. These values are + * only significant if p_start or p_end/p_get_end point at them. * - * Frees *all* FIFO lumps. + * HOWEVER: does not set p_get_end -- so if get_lump or end_lump or p_end have + * changed, then must also call vio_fifo_set_get_end(). * - * See also: vio_fifo_reset_keep(vio_fifo) - * vio_fifo_reset_free(vio_fifo) + * ALSO: does not set put_end -- so if the tail lumps has changed, that must + * be updated. */ -extern vio_fifo -vio_fifo_reset(vio_fifo vf, int free_structure) +inline static void +vio_fifo_reset_ptrs(vio_fifo vff) { - vio_fifo_lump lump ; + char* ptr = ddl_tail(vff->base)->data ; - if (vf == NULL) - return NULL ; + if (vio_fifo_debug) + assert(ddl_head(vff->base) == ddl_tail(vff->base)) ; - while (ddl_pop(&lump, vf->base, list) != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + vff->hold_ptr = ptr ; + vff->get_ptr = ptr ; + vff->end_ptr = ptr ; + vff->put_ptr = ptr ; +} ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; +/*------------------------------------------------------------------------------ + * This is called *iff* get_ptr >= *p_get_end -- and preferably only after + * it has been adjusted forwards by at least 1 (but that is not required). + * + * If there is anything available to be got, adjust get_ptr and/or get_end + * in order to be able to get it -- discarding lumps as required. + */ +Private void +vio_fifo_sync_get(vio_fifo vff) +{ + if (vio_fifo_debug) + assert(vff->get_ptr == *vff->p_get_end) ; + + if (vff->get_lump == vff->end_lump) + { + if (vio_fifo_debug) + assert(vff->get_ptr == *vff->p_end) ; - if (free_structure) - XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + /* We are in the end_lump, and there is nothing more to be read. + * + * If have reached the put_ptr, then unless there is something held + * behind the get_ptr, the fifo is completely empty, and pointers can + * be reset to the start of the end_lump (which is the only lump). + * + * p_start == &hold_ptr or &get_ptr, so can check for empty in one test. + */ + if (*vff->p_start == vff->put_ptr) + vio_fifo_reset_ptrs(vff) ; + } else - vio_fifo_init_new(vf, vf->size) ; + { + /* Good news, can advance the get_ptr + * + * Step the get_ptr to the start of the next lump, and if no hold mark, + * discard any lumps which precede the new get_lump. + */ + vio_fifo_lump get_lump ; + + if (vio_fifo_debug) + { + assert(vff->get_lump != vff->end_lump) ; + assert(vff->get_ptr == vff->get_lump->end) ; + } ; - return vf ; + get_lump = ddl_next(vff->get_lump, list) ; + vio_fifo_set_get_ptr(vff, get_lump, get_lump->data) ; + + if (!vio_fifo_have_hold_mark(vff)) + vio_fifo_release_up_to(vff, get_lump) ; + } ; } ; /*------------------------------------------------------------------------------ - * The FIFO is empty, with one lump -- reset all pointers. + * Set get_lump/get_ptr and p_get_end to suit. */ inline static void -vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump, char* ptr) { - if (vf->rdr_lump != NULL) - { - assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; - vf->rdr_ptr = lump->data ; - } ; + vff->get_lump = lump ; + vff->get_ptr = ptr ; - /* Note that sets the lump->end to the true lump->end */ - vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; - vf->put_end = lump->end = lump->data + lump->size ; + vio_fifo_set_get_end(vff) ; } ; /*------------------------------------------------------------------------------ - * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + * Set the p_get_end depending on whether the get_lump == end_lump, or not. + * + * This must be called if the get_lump or the end_lump are changed, or if + * p_end changes. */ inline static void -vio_fifo_ptr_unset(vio_fifo vf) +vio_fifo_set_get_end(vio_fifo vff) { - assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; - - vf->one = false ; - - vf->put_ptr = NULL ; - vf->put_end = NULL ; - vf->get_ptr = NULL ; - vf->get_end = NULL ; - - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; + vff->p_get_end = (vff->get_lump == vff->end_lump) ? vff->p_end + : &vff->get_lump->end ; } ; /*------------------------------------------------------------------------------ - * Clear out contents of FIFO -- will continue to use the FIFO. + * Release all lumps up to (but excluding) the given lump. * - * Keeps one FIFO lump. (Frees everything else, including any spare.) + * NB: takes no notice of hold_ptr or anything else. */ -extern void -vio_fifo_clear(vio_fifo vf) +inline static void +vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump to) { - vio_fifo_lump tail ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - assert(vf != NULL) ; - - tail = ddl_tail(vf->base) ; - - if (tail != NULL) - { - while (ddl_head(vf->base) != tail) - { - vio_fifo_lump lump ; - ddl_pop(&lump, vf->base, list) ; - XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; - } ; - - vf->rdr_lump = NULL ; /* clear rdr */ - vf->rdr_ptr = NULL ; - - vf->one = true ; - vio_fifo_ptr_reset(vf, tail) ; - } - else - vio_fifo_ptr_unset(vf) ; - - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ - - VIO_FIFO_DEBUG_VERIFY(vf) ; + vio_fifo_lump lump ; + while (ddl_head(vff->base) != to) + vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ; } ; /*------------------------------------------------------------------------------ - * See how much room there is in the FIFO. - * - * If no lumps have been allocated, returns the size of the lump that would - * allocate. + * Release all lumps back to (but excluding) the given lump. * - * Otherwise, returns the amount of space available *without* allocating any - * further lumps. + * Reset vff->put_end to be the end of the to->lump. * - * Returns: room available as described + * NB: takes no notice of hold_ptr or anything else. */ -extern size_t -vio_fifo_room(vio_fifo vf) +inline static void +vio_fifo_release_back_to(vio_fifo vff, vio_fifo_lump to) { - if (vf->put_ptr != NULL) - return vf->put_end - vf->put_ptr ; - else - return vf->size ; + vio_fifo_lump lump ; + do + vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ; + while (to != ddl_tail(vff->base)) ; + + vff->put_end = to->end ; } ; +/*============================================================================== + * Initialisation, allocation and freeing of FIFO and lumps thereof. + */ + /*------------------------------------------------------------------------------ - * Allocate another lump for putting into. + * Allocate and initialise a new FIFO. * - * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. + * The size given is the size for all lumps in the FIFO. 0 => default size. * - * Set the put_ptr/put_end pointers to point at the new lump. + * Size is rounded up to a 128 byte boundary. * - * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * Once allocated and initialised, the FIFO contains one lump, and if it + * grows to more than one, will retain a spare lump once it shrinks again. * - * If have just filled the first lump on the list, update the get_end pointer - * to reflect the fact that the out lump is now full. + * Keeping a pair of lumps allows the get_ptr to lag behind the put_ptr by + * about a lump full, without requiring repeated memory allocation. Also, + * vio_fifo_write_nb() can be asked to write only lumps -- so if called + * regularly while putting stuff to a FIFO, will write entire lumps at once. */ -extern void -vio_fifo_lump_new(vio_fifo vf, size_t size) +extern vio_fifo +vio_fifo_new(ulen size) { - vio_fifo_lump lump ; - int first_alloc ; + vio_fifo vff ; + ulen total_size ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (size == 0) + size = VIO_FIFO_DEFAULT_LUMP_SIZE ; - passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + size = ((size + 128 - 1) / 128) * 128 ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* update get_end */ + if (vio_fifo_debug) + size = 29 ; - lump = ddl_tail(vf->base) ; + total_size = offsetof(struct vio_fifo, own_lump[0].data[size]) ; - first_alloc = (lump == NULL) ; /* extra initialisation needed */ + vff = XCALLOC(MTYPE_VIO_FIFO, total_size) ; - if (first_alloc) - assert(vf->put_ptr == NULL) ; /* must all be NULL together */ - else - assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ + /* Zeroising the the vio_fifo_t has set: + * + * base -- base pair, both pointers NULL => list is empty + * + * p_start -- X -- see vio_fifo_ptr_set() + * + * hold_ptr -- NULL -- not relevant until hold mark is set + * + * get_lump -- X ) + * get_ptr -- X ) -- see vio_fifo_ptr_set() + * p_get_end -- X ) + * + * end_lump -- X -- see vio_fifo_ptr_set() + * + * end_ptr -- NULL -- not relevant until hold mark is set + * + * put_ptr -- X ) + * put_end -- X ) -- see vio_fifo_ptr_set() + * p_end -- X ) + * + * size -- X -- set below + * + * spare -- NULL -- no spare lump + * + * own_lump -- all zeros: list -- pointers NULL, set below + * end -- set below + */ + vff->size = size ; + vff->own_lump->end = vff->own_lump->data + vff->size ; - size = vio_fifo_size(size) ; + if (vio_fifo_debug) + assert(vff->own_lump->end == ((char*)vff + total_size)) ; - if ((vf->spare != NULL) && (vf->spare->size >= size)) - { - lump = vf->spare ; - vf->spare = NULL ; - } - else - { - lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, - offsetof(vio_fifo_lump_t, data[size])) ; - lump->size = size ; - } ; + ddl_append(vff->base, vff->own_lump, list) ; - lump->end = lump->data + lump->size ; + vff->p_start = &vff->get_ptr ; - ddl_append(vf->base, lump, list) ; + vff->get_lump = vff->own_lump ; + vff->get_ptr = vff->own_lump->data ; + vff->p_get_end = &vff->put_ptr ; - vf->one = first_alloc ; + vff->end_lump = vff->own_lump ; - vf->put_ptr = lump->data ; - vf->put_end = lump->end ; + vff->put_ptr = vff->own_lump->data ; + vff->put_end = vff->own_lump->end ; - if (first_alloc) - { - vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ - vf->get_end = vf->put_ptr ; - } ; + vff->p_end = &vff->put_ptr ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + return vff ; } ; /*------------------------------------------------------------------------------ - * Release lump, head or tail (or both) and update pointers. + * Free contents of given FIFO and the FIFO structure as well. * - * Note that this does release the lump if it is the only lump. - * - * Do nothing if nothing is yet allocated. - * - * If releasing the only lump in the FIFO, resets all pointers to NULL. - * - * If releasing the head lump: - * - * * the lump MUST be finished with -- so vf->get_ptr must be at the end - * - * If releasing the only lump, the FIFO MUST be empty. - * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! - * - * If releasing the tail lump: - * - * * the lump MUST be empty - * - * If releasing the only lump, the FIFO MUST be empty. + * Does nothing if given a NULL pointer -- must already have been freed ! * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! + * Returns: NULL */ -static void -vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) +extern vio_fifo +vio_fifo_free(vio_fifo vff) { - vio_fifo_lump head ; - vio_fifo_lump tail ; - vio_fifo_lump free ; - bool release_head ; - bool release_tail ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - /* Prepare and check whether removing head or tail (or both) */ - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; - - release_head = (lump == head) ; - release_tail = (lump == tail) ; - - assert(release_head || release_tail) ; - - /* Unless nothing ever allocated -- release the lump. */ - free = lump ; /* expect to free the lump */ - if (lump != NULL) + if (vff != NULL) { - vio_fifo_lump keep ; + vio_fifo_lump lump ; + + lump = vff->spare ; + vff->spare = NULL ; - /* Consistency checks */ - if (release_head) + do { - if (release_tail) - assert(vf->get_ptr == vf->put_ptr) ; - else - assert(vf->get_ptr == lump->end) ; + if (lump != vff->own_lump) + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; /* accepts lump == NULL */ - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->get_ptr) ; + ddl_pop(&lump, vff->base, list) ; } - else if (release_tail) - { - assert(vf->put_ptr == lump->data) ; + while (lump != NULL) ; - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->put_ptr) ; - } ; + XFREE(MTYPE_VIO_FIFO, vff) ; + } ; - /* Remove lump from FIFO and decide whether to keep as spare, or - * which of spare and this to free. - */ - ddl_del(vf->base, lump, list) ; + return NULL ; +} ; - keep = vf->spare ; /* expect to keep current spare */ +/*------------------------------------------------------------------------------ + * Clear out contents of FIFO -- will continue to use the FIFO. + * + * If required, clears any hold mark and/or end mark. + * + * Keeps one spare lump. + * + * Does nothing if there is no FIFO ! + */ +extern void +vio_fifo_clear(vio_fifo vff, bool clear_marks) +{ + vio_fifo_lump lump ; - if ((keep == NULL) || (keep->size < lump->size)) - { - keep = lump ; - free = vf->spare ; - } ; + if (vff == NULL) + return ; - vf->spare = keep ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - head = ddl_head(vf->base) ; /* changed if released head */ - tail = ddl_tail(vf->base) ; /* changed if released tail */ - } ; + lump = ddl_tail(vff->base) ; - /* Now update pointers... depending on what was released and what have - * left. - */ - if (head == NULL) - { - /* Deal with FIFO that now has no lumps or had none to start with */ - if (lump != NULL) - assert(vf->one) ; + vff->get_lump = lump ; /* before releasing */ + vff->end_lump = lump ; - vio_fifo_ptr_unset(vf) ; - } - else - { - /* Have at least one lump left -- so must have had at least two ! */ - assert(!vf->one) ; + vio_fifo_release_up_to(vff, lump) ; - vf->one = (head == tail) ; /* update */ + vio_fifo_reset_ptrs(vff) ; - if (release_head) - { - /* Released the head. - * - * Update the vf->get_ptr and the vf->get_end. - */ - vf->get_ptr = head->data ; - if (vf->one) - vf->get_end = vf->put_ptr ; - else - vf->get_end = head->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = head ; - vf->rdr_ptr = head->data ; - } ; - } - else - { - /* Released the tail. - * Update the vf->put_ptr and vf->put_end - */ - vf->put_ptr = vf->put_end = tail->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->end ; - } ; - } ; + if (clear_marks) + { + vff->p_start = &vff->get_ptr ; + vff->p_end = &vff->put_ptr ; } ; - /* Finally, free any lump that is actually to be freed */ - - if (free != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + vff->p_get_end = vff->p_end ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Re-allocate lump for putting into. + * Add a new lump to put stuff into -- work-horse for putting to the FIFO. * - * Call when vf->put_ptr == start of last lump, and that lump is not big - * enough ! + * Call when (vff->put_ptr >= vff->put_end) -- asserts that they are equal. * - * There must be at least one lump. + * The FIFO cannot be empty -- if it were, the pointers would have been reset, + * and could not be vff->put_ptr >= vff->put_end !! * - * Updates put_ptr/put_end pointers to point at the new lump. + * Allocates a new lump (or reuses the spare) and updates the put_ptr. * - * Updates get_ptr/get_end pointers if required. - * - * Updates rdr_ptr if required. + * If the end_ptr and the put_ptr were equal, then advances that too, which + * ensures that the end_ptr is not ambiguous. + + * If the get_ptr and the put_ptr were equal, then advances that too, which + * ensures that the get_ptr is not ambiguous. This can be the case if there + * is a hold_ptr. */ -static void -vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +Private void +vio_fifo_add_lump(vio_fifo vff) { - bool rdr_set ; + vio_fifo_lump lump ; + + assert(vff->put_ptr == vff->put_end) ; /* must be end of tail lump */ + assert(vff->put_ptr != *vff->p_start) ; /* cannot be empty ! */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - /* FIFO may not be completely empty. - * This must be the last lump. - * The last lump must be empty. + /* If we can use the spare, do so, otherwise make a new one and + * add to the end of the FIFO. */ - assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; + lump = vff->spare ; + vff->spare = NULL ; + + if (lump == NULL) + { + ulen lump_size = offsetof(vio_fifo_lump_t, data[vff->size]) ; - /* Remove the last, *empty* lump, and update all pointers to suit. */ - rdr_set = (vf->rdr_lump == lump) ; + lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, lump_size) ; + lump->end = (char*)lump + lump_size ; - vio_fifo_lump_release(vf, lump) ; + if (vio_fifo_debug) + assert(lump->end == (lump->data + vff->size)) ; + } ; - /* Now allocate a new lump with the required size */ - vio_fifo_lump_new(vf, size) ; + ddl_append(vff->base, lump, list) ; - /* Restore the rdr_ptr, if required */ - if (rdr_set) + /* Allocated new lump on the end of FIFO. + * + * If the get_ptr == put_ptr, advance the get_ptr. If there is an end_ptr, + * it must be == put_ptr, and is about to advance too. + * + * If put_ptr == *p_end, advance the end_lump and the end_ptr. If there is + * no end_mark, then p_end == &put_ptr, and the end_lump must follow the + * put_ptr. If there is an end_mark, then p_end == &end_ptr, and that must + * follow the put_ptr if they are equal. + * + * The get_lump may or may not have been the end_lump, and that may or may + * not have changed. Simplest thing is to set p_get_end to what it should + * be now. + */ + if (vff->get_ptr == vff->put_ptr) { - vio_fifo_lump tail ; + if (vio_fifo_debug) + assert(vio_fifo_have_hold_mark(vff)) ; - tail = ddl_tail(vf->base) ; + vff->get_lump = lump ; + vff->get_ptr = lump->data ; + } ; - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->data ; + if (vff->put_ptr == *vff->p_end) + { + vff->end_lump = lump ; + vff->end_ptr = lump->data ; /* no effect if no end_mark */ } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->put_ptr = lump->data ; + vff->put_end = lump->end ; + + vio_fifo_set_get_end(vff) ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Release the given lump, provided it is neither get_lump nor end_lump. + * + * If don't have a spare lump, keep this one. + * If do have a spare lump, discard this one, unless it is "own_lump". + */ +static void +vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) +{ + assert(lump != NULL) ; + assert(lump != vff->get_lump) ; + assert(lump != vff->end_lump) ; + + if (vff->spare == NULL) + vff->spare = lump ; + else + { + if (lump == vff->own_lump) + { + lump = vff->spare ; /* free the spare instead */ + vff->spare = vff->own_lump ; + } ; + + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + } ; } ; /*============================================================================== @@ -543,623 +597,912 @@ vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) */ /*------------------------------------------------------------------------------ - * Store 'n' bytes -- allocate new lump if current is exhausted. + * Put 'n' bytes -- allocating as required. */ extern void -vio_fifo_put(vio_fifo vf, const char* src, size_t n) +vio_fifo_put_bytes(vio_fifo vff, const char* src, ulen n) { - size_t take ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; while (n > 0) { - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + ulen take ; + + if (vff->put_ptr >= vff->put_end) + vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */ - take = (vf->put_end - vf->put_ptr) ; + take = (vff->put_end - vff->put_ptr) ; if (take > n) take = n ; - memcpy(vf->put_ptr, src, take) ; - vf->put_ptr += take ; + memcpy(vff->put_ptr, src, take) ; + vff->put_ptr += take ; src += take ; n -= take ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf printf() + * Formatted print to FIFO -- cf printf() * * Returns: >= 0 -- number of bytes written * < 0 -- failed (unlikely though that is) */ extern int -vio_fifo_printf(vio_fifo vf, const char* format, ...) +vio_fifo_printf(vio_fifo vff, const char* format, ...) { va_list args; int len ; va_start (args, format); - len = vio_fifo_vprintf(vf, format, args); + len = vio_fifo_vprintf(vff, format, args); va_end (args); return len; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf vprintf() + * Formatted print to FIFO -- cf vprintf() + * + * Does nothing if vff is NULL ! * * Returns: >= 0 -- number of bytes written * < 0 -- failed (unlikely though that is) + * + * NB: uses qfs_vprintf(qfs, format, va), which allows the result to be + * collected a section at a time, if required. With reasonable size + * lumps, expect to need no more than two sections, and then only + * occasionally. */ extern int -vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) +vio_fifo_vprintf(vio_fifo vff, const char *format, va_list va) { - va_list ac ; - int len ; - int have ; - size_t size ; - vio_fifo_lump lump ; + qf_str_t qfs ; + ulen done ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff == NULL) + return 0 ; - size = vf->size ; /* standard allocation size */ - while (1) - { - /* Find what space is left in the tail lump, and allocate a new, - * empty lump if required. - */ - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, size) ; /* traps put_ptr > put_end */ - - have = vf->put_end - vf->put_ptr ; - assert(have > 0) ; - - /* Note that vsnprintf() returns the length of what it would like to - * have produced, if it had the space. That length does not include - * the trailing '\0'. - */ - va_copy(ac, args) ; - len = vsnprintf(vf->put_ptr, have, format, ac) ; - va_end(ac) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - if (len < have) - { - if (len < 0) - break ; /* quit if failed */ + done = 0 ; + do + { + ulen did ; - vf->put_ptr += len ; - break ; /* done */ - } ; + if (vff->put_ptr >= vff->put_end) + vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */ - /* Not able to complete the operation in the current buffer. - * - * If the required space (len + 1) is greater than the standard - * allocation, then need to increase the allocation for the next lump. - * - * If the current lump is empty, need to renew it with a fresh lump of - * the now known required size. - * - * If the current lump is not empty, need to cut the end off and then - * allocate a fresh lump (of the standard or now known required size). - */ - if (len >= (int)size) - size = len + 1 ; /* need a non-standard size */ + qfs_init_offset(qfs, vff->put_ptr, vff->put_end - vff->put_ptr, done) ; - lump = ddl_tail(vf->base) ; + did = qfs_vprintf(qfs, format, va) ; - if (vf->put_ptr == lump->data) - /* Need to replace the last, empty, lump with another empty lump, but - * big enough. - */ - vio_fifo_lump_renew(vf, lump, size) ; - else - /* Need to cut this lump short, and allocate new lump at top of loop. - */ - lump->end = vf->put_end = vf->put_ptr ; - } ; + done += did ; + vff->put_ptr += did ; + } + while (qfs_overflow(qfs) != 0) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - return len ; + return done ; } ; -/*============================================================================== - * Get data from the FIFO. - */ - -static bool vio_fifo_get_next_lump(vio_fifo vf) ; - /*------------------------------------------------------------------------------ - * Get ready to read something out of the FIFO. + * Read part of file into FIFO -- assuming non-blocking file + * + * Will read up to the end of the current lump, then will read as may whole + * lumps as are requested -- request of 0 reads up to the end of the current + * lump (at least 1 byte). Will stop if would block. * - * Makes sure vf->get_end is up to date (if required) and if the FIFO is not - * empty, makes sure vf->get_ptr points at the next byte to be read. + * Except where blocking intervenes, this reads in units of the lump size. * - * Returns: true <=> there is something in the FIFO. + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately + * + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here with + * something, error or EOF. */ -static inline bool -vio_fifo_get_ready(vio_fifo vf) +extern int +vio_fifo_read_nb(vio_fifo vff, int fd, ulen request) { - assert(vf->rdr_lump == NULL) ; + ulen total ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; + + total = 0 ; + + do + { + int got ; + + if (vff->put_ptr >= vff->put_end) + { + vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */ + + if (request > 0) + --request ; + } ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* make sure have everything */ + got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ; - if (vf->get_ptr >= vf->get_end) - if (!vio_fifo_get_next_lump(vf)) - return 0 ; /* quit now if nothing there */ + if (got <= 0) + { + if (got == -2) /* EOF met */ + return (total > 0) ? (int)total : got ; + else + return (got == 0) ? (int)total : got ; + } ; + + vff->put_ptr += got ; + total += got ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + } while (request > 0) ; - return 1 ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + + return total ; } ; /*------------------------------------------------------------------------------ - * Get upto 'n' bytes. + * Strip trailing whitespace and, if required, insert '\n' if result is not + * empty and does not now end in '\n' * - * Returns: number of bytes got -- may be zero. + * Strips anything 0x00..0x20 except for '\n'. + * + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately */ -extern size_t -vio_fifo_get(vio_fifo vf, void* dst, size_t n) +extern void +vio_fifo_trim(vio_fifo vff, bool term) { - size_t have ; - void* dst_in ; + vio_fifo_lump lump ; + char* p ; + char* s ; + char* end_ptr ; + bool end_ptr_passed ; + char ch ; + + /* Position at end of fifo, and establish how far back in current lump + * can discard whitespace. + */ + lump = ddl_tail(vff->base) ; + p = vff->put_ptr ; + s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ; - if (!vio_fifo_get_ready(vf)) - return 0 ; /* quit now if nothing there */ + if (vio_fifo_have_end_mark(vff)) + end_ptr = vff->end_ptr ; + else + end_ptr = NULL ; + end_ptr_passed = false ; - dst_in = dst ; - while (n > 0) + /* Track backwards, until reach get_ptr or hit '\n' or something which is + * not 0x00..0x20. + */ + ch = '\0' ; + while (1) { - have = vf->get_end - vf->get_ptr ; + if (s == p) + { + /* At the start of the current lump and/or at the get_ptr. + * + * If at the get_ptr, then cannot track any further back, but if + * there is a hold mark, there may be something before the get_ptr. + */ + if (lump == vff->get_lump) + { + qassert(p == vff->get_ptr) ; /* hit get_ptr */ + + ch = '\0' ; + if (p == *vff->p_start) + break ; /* hit start of fifo */ + + if (p != lump->data) + qassert(p > lump->data) ; + else + { + qassert(lump != ddl_head(vff->base)) ; + s = ddl_prev(lump, list)->end ; + } ; + + ch = *(s - 1) ; + + if (ch != '\n') + ch = '\xFF' ; /* anything but '\0' */ + + break ; + } ; + + lump = ddl_prev(lump, list) ; + + p = lump->end ; + s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ; - if (have > n) - have = n ; + continue ; + } ; + + qassert(p > s) ; - memcpy(dst, vf->get_ptr, have) ; - vf->get_ptr += have ; - dst = (char*)dst + have ; + ch = *(p-1) ; - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - if (!vio_fifo_get_next_lump(vf)) - break ; /* quit if nothing more to come */ + if ((ch > 0x20) || (ch == '\n')) + break ; - n -= have ; + if (p == end_ptr) + end_ptr_passed = true ; /* stepped past the end mark */ + + --p ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + /* Decide what to do now. + * + * ch == '\0' <=> p points at end of empty fifo ! + * ch == '\n' <=> p points after a '\n'. + * otherwise <=> p points after non-whitespace, or is at get_ptr and + * something other than '\n' precedes it. + * + * lump points at the lump that 'p' is in. + * + * If stepped back past the end_ptr, now is the time to set the end_ptr + * to the current position. + */ + if (end_ptr_passed) + { + qassert(end_ptr != NULL) ; - return (char*)dst - (char*)dst_in ; + vff->end_lump = lump ; + vff->end_ptr = p ; + } ; + + /* If have stepped back across one or more lumps, trim now excess lumps off + * the end. + * + * Note, it is (just) possible that in inserting a '\n', that this will + * trim off a lump that needs to be re-instated -- but it is more + * straightforward to do this, and the odds are pretty slim. + */ + if (lump != ddl_tail(vff->base)) + vio_fifo_release_back_to(vff, lump) ; + + /* set the (new) put_ptr. + */ + vff->put_ptr = p ; + + /* If we need to add a terminator, do that now. + */ + if (term && (ch != '\n') && (ch != '\0')) + vio_fifo_put_byte(vff, '\n') ; } ; +/*============================================================================== + * Copy operations -- from one FIFO to another. + */ + /*------------------------------------------------------------------------------ - * Get byte -- the long winded way. + * Copy src FIFO (everything from get_ptr to end mark or put_ptr) to dst FIFO. * - * See the inline vio_fifo_get_byte(). + * Create a dst FIFO if there isn't one. There must be a src FIFO. * - * The version is used when the get_ptr is at or just before the end of the - * current lump. Looks after all the necessary pointer updates associated with - * hitting end of lump, or hitting end of FIFO. + * Appends to the dst FIFO. * - * Returns: 0x00..0xFF -- byte value (as an int) - * -1 => FIFO is empty. + * Does not change the src FIFO in any way. */ - -extern int -vio_fifo_get_next_byte(vio_fifo vf) +extern vio_fifo +vio_fifo_copy(vio_fifo dst, vio_fifo src) { - unsigned char u ; + vio_fifo_lump src_lump ; + char* src_ptr ; - if (!vio_fifo_get_ready(vf)) - return -1 ; /* quit now if nothing there */ + if (dst == NULL) + dst = vio_fifo_new(src->size) ; - u = *vf->get_ptr++ ; + VIO_FIFO_DEBUG_VERIFY(src) ; + VIO_FIFO_DEBUG_VERIFY(dst) ; - /* As soon as reach the end want either to discard empty lump, or reset - * the pointers. - */ - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - vio_fifo_get_next_lump(vf) ; + src_lump = src->get_lump ; + src_ptr = src->get_ptr ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + while (1) + { + char* src_end ; + + if (src_lump != src->end_lump) + src_end = src_lump->end ; /* end of not end_lump */ + else + src_end = *src->p_end ; /* end of end_lump */ + + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; + + if (src_lump == src->end_lump) + break ; + + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; + } ; + + VIO_FIFO_DEBUG_VERIFY(dst) ; - return u ; + return dst ; } ; /*------------------------------------------------------------------------------ - * Get pointer to a lump of bytes. + * Copy tail of src FIFO (everything from end mark to put_ptr) to dst FIFO. * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * Create a dst FIFO if there isn't one. There must be a src FIFO. * - * If the FIFO is not empty, will return pointer to at least one byte. + * Appends to the dst FIFO. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * Does not change the src FIFO in any way. */ -extern void* -vio_fifo_get_lump(vio_fifo vf, size_t* p_have) +extern vio_fifo +vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) { - if (!vio_fifo_get_ready(vf)) + vio_fifo_lump src_lump ; + char* src_ptr ; + vio_fifo_lump tail ; + + if (dst == NULL) + dst = vio_fifo_new(src->size) ; + + VIO_FIFO_DEBUG_VERIFY(src) ; + VIO_FIFO_DEBUG_VERIFY(dst) ; + + if (!vio_fifo_have_end_mark(src)) + return dst ; + + src_lump = src->end_lump ; + src_ptr = src->end_ptr ; + tail = ddl_tail(src->base) ; + + while (1) { - *p_have = 0 ; - return NULL ; + char* src_end ; + + if (src_lump != tail) + src_end = src_lump->end ; + else + src_end = src->put_ptr ; + + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; + + if (src_lump == tail) + break ; + + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; } ; - *p_have = (vf->get_end - vf->get_ptr) ; - return vf->get_ptr ; + VIO_FIFO_DEBUG_VERIFY(dst) ; + + return dst ; } ; +/*============================================================================== + * End Mark Operations. + * + * Set/Clear end mark is pretty straightforward: + * + * * if there was an end_ptr before and the put_ptr is ahead of it: + * + * this adds one or more bytes between the get_ptr and the (new) end. + * + * * if there was no end_ptr, or it is the same as the put_ptr: + * + * setting/clearing the end_ptr makes no difference, because whenever + * the get_ptr reaches the put_ptr, the pointers are reset if they can + * be -- so need not worry about that here. + * + * Set the p_end and p_get_end to the new reality. + * + * The put_ptr stays in its current lump, so no need to worry about put_end. + */ + /*------------------------------------------------------------------------------ - * Advance FIFO to position reached. + * Set end_mark at the current put position. + * + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. * - * Having done vio_fifo_get_lump(), can take any number of bytes (up to the - * number that "have"), then call this function to advance the pointers. + * If the put_ptr is at the end of the last lump, then the end_ptr will follow + * it if another lump is added to the FIFO. * - * The "here" argument must the the address returned by vio_fifo_get_lump() - * plus the number of bytes taken. + * Set the p_end and p_get_end to the new reality. */ extern void -vio_fifo_got_upto(vio_fifo vf, void* here) +vio_fifo_set_end_mark(vio_fifo vff) { - vf->get_ptr = here ; + vff->p_end = &vff->end_ptr ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->end_ptr = vff->put_ptr ; + vff->end_lump = ddl_tail(vff->base) ; - if (vf->get_ptr >= vf->get_end) - vio_fifo_get_next_lump(vf) ; + vio_fifo_set_get_end(vff) ; /* in case end_lump or p_end changed */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Write contents of FIFO -- assuming non-blocking file + * If there is an end mark, advance it to the put_ptr. * - * Will write all of FIFO, or upto but excluding the last lump. - * - * Returns: > 0 => blocked - * 0 => all gone (up to last lump if !all) - * < 0 => failed -- see errno + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. * - * Note: will work perfectly well for a non-blocking file -- which should - * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". + * If there was no end mark before, do nothing. */ -extern int -vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +extern void +vio_fifo_step_end_mark(vio_fifo vff) { - char* src ; - size_t have ; - int done ; - - while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + if (vio_fifo_have_end_mark(vff)) { - if (!all && vf->one) - break ; /* don't write last lump */ - - done = write_nb(fd, src, have) ; - - if (done < 0) - return -1 ; /* failed */ + vff->end_ptr = vff->put_ptr ; + vff->end_lump = ddl_tail(vff->base) ; - vio_fifo_got_upto(vf, src + done) ; + vio_fifo_set_get_end(vff) ; /* in case end_lump changed */ - if (done < (int)have) - return 1 ; /* blocked */ - } ; - - return 0 ; /* all gone */ + VIO_FIFO_DEBUG_VERIFY(vff) ; + } } ; /*------------------------------------------------------------------------------ - * Get the current rdr_end value. + * If there is an end_ptr, clear it -- everything between end_ptr and the + * current put_ptr is kept in the FIFO. * - * Unlike get_end, do not have a field for this, but find it each time. + * Set the p_end and p_get_end to the new reality. */ -inline static char* -vio_fifo_rdr_end(vio_fifo vf) +extern void +vio_fifo_clear_end_mark(vio_fifo vff) { - if (vf->rdr_lump == ddl_tail(vf->base)) - return vf->put_ptr ; - else - return vf->rdr_lump->end ; + vff->p_end = &vff->put_ptr ; + + vff->end_lump = ddl_tail(vff->base) ; + + vio_fifo_set_get_end(vff) ; /* in case end_lump or p_end changed */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Get the current rdr position -- sets it up if not currently set. + * Move put_ptr back to the end mark, if any, and discard data. * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * If there is an end_mark, keep it if required. * - * If the FIFO is not empty, will return pointer to at least one byte. + * If there is no end mark, do nothing. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * If there is an end mark but it is the same as the put_ptr, then need do + * nothing at all. * - * NB: unless returns FIFO is empty, it is a mistake to now do any "get" - * operation other than vio_fifo_step_rdr(), until do vio_fifo_sync_rdr() - * or vio_fifo_drop_rdr. + * Note that: if there is an end mark, then p_end == &end_ptr, so if + * *p_end == put_ptr, then end_ptr == put_ptr ; if there is no end mark, + * then p_end == &put_ptr, so *p_end == put_ptr !! */ -extern void* -vio_fifo_get_rdr(vio_fifo vf, size_t* p_have) +extern void +vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) { - if (!vio_fifo_get_ready(vf)) + if (*vff->p_end != vff->put_ptr) /* test for not-empty end mark */ { - *p_have = 0 ; - return NULL ; + if (vio_fifo_debug) + assert(vio_fifo_have_end_mark(vff)) ; + + if (vff->end_lump != ddl_tail(vff->base)) + vio_fifo_release_back_to(vff, vff->end_lump) ; + + if (*vff->p_start == vff->end_ptr) + vio_fifo_reset_ptrs(vff) ; + else + vff->put_ptr = vff->end_ptr ; } ; - if (vf->rdr_lump == NULL) /* set up new rdr if required */ + if (!keep) { - vf->rdr_lump = ddl_head(vf->base) ; - vf->rdr_ptr = vf->get_ptr ; + vff->p_end = &vff->put_ptr ; + vio_fifo_set_get_end(vff) ; /* in case get_lump == end_lump */ } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; - - *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; - return vf->rdr_ptr ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; +/*============================================================================== + * Get data from the FIFO. + */ + /*------------------------------------------------------------------------------ - * Step the rdr forward by the given number of bytes. + * Get upto 'n' bytes -- steps past the bytes fetched. * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * Stops at current end of FIFO (and not before). * - * If the FIFO is not empty, will return pointer to at least one byte. + * Returns: number of bytes got -- may be zero. + */ +extern ulen +vio_fifo_get_bytes(vio_fifo vff, void* dst, ulen n) +{ + void* dst_start ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; + + dst_start = dst ; + while (n > 0) + { + ulen take ; + + take = *vff->p_get_end - vff->get_ptr ; + + if (take > n) + take = n ; + else if (take == 0) + break ; + + memcpy(dst, vff->get_ptr, take) ; + dst = (char*)dst + take ; + + n -= take ; + + vio_fifo_step(vff, take) ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; + + return (char*)dst - (char*)dst_start ; +} ; + +/*------------------------------------------------------------------------------ + * Write contents of FIFO -- assuming non-blocking file * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * Will write all of FIFO up to end mark or put_ptr, or upto but excluding + * the end_lump. * - * NB: this does not change the get pointers, so all the data being stepped - * over is preserved in the FIFO, until vio_fifo_sync_rdr(). + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see errno * - * NB: the step may NOT exceed the last reported "have". + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ -extern void* -vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +extern int +vio_fifo_write_nb(vio_fifo vff, int fd, bool all) { - char* rdr_end ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - assert(vf->rdr_lump != NULL) ; + while (1) + { + ulen have ; + int done ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if ((vff->get_lump == vff->end_lump) && !all) + break ; /* don't write last lump */ - rdr_end = vio_fifo_rdr_end(vf) ; - vf->rdr_ptr += step ; + have = vio_fifo_get(vff) ; - if (vf->rdr_ptr >= rdr_end) - { - assert(vf->rdr_ptr == rdr_end) ; + if (have == 0) + break ; - if (vf->rdr_lump != ddl_tail(vf->base)) - { - vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; - vf->rdr_ptr = vf->rdr_lump->data ; + done = write_nb(fd, vio_fifo_get_ptr(vff), have) ; - rdr_end = vio_fifo_rdr_end(vf) ; - } ; + if (done < 0) + return -1 ; /* failed */ + + vio_fifo_step(vff, done) ; + + if (done < (int)have) + return 1 ; /* blocked */ } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - *p_have = (rdr_end - vf->rdr_ptr) ; - return (*p_have > 0) ? vf->rdr_ptr : NULL ; + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Move FIFO get position to the rdr position, if any. + * Write contents of FIFO -- assuming blocking file + * + * Will write all of FIFO up to end mark or put_ptr. + * + * Returns: 0 => all gone + * < 0 => failed -- see errno * - * This clears the rdr position, and removes all data between the current and - * new get positions from the FIFO. + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ -extern void -vio_fifo_sync_rdr(vio_fifo vf) +extern int +vio_fifo_fwrite(vio_fifo vff, FILE* file) { - vio_fifo_lump head ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + while (1) + { + int done ; + ulen have ; - if (vf->rdr_lump == NULL) - return ; + have = vio_fifo_get(vff) ; + if (have == 0) + break ; - while ((head = ddl_head(vf->base)) != vf->rdr_lump) - { - vf->get_ptr = vf->get_end ; /* jump to end of lump */ - vio_fifo_lump_release(vf, head) ; - } ; + done = fwrite(vio_fifo_get_ptr(vff), have, 1, file) ; - vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + if (done < 1) + return -1 ; /* failed */ - vf->rdr_lump = NULL ; /* clear the rdr */ - vf->rdr_ptr = NULL ; + vio_fifo_step(vff, have) ; + } ; - if (vf->one) - { - if (vf->put_ptr == vf->get_ptr) /* reset pointers if FIFO empty */ - vio_fifo_ptr_reset(vf, head) ; - else - vf->get_end = vf->put_ptr ; - } - else - vf->get_end = head->end ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Drop the rdr position (if any). + * Skip get_ptr to the current end -- which may be the current end mark. * - * This clears the rdr position leaving the get position and FIFO unchanged. + * Does not clear any hold_ptr or end_ptr. */ extern void -vio_fifo_drop_rdr(vio_fifo vf) +vio_fifo_skip_to_end(vio_fifo vff) { - VIO_FIFO_DEBUG_VERIFY(vf) ; + /* Advance directly to the current end and then synchronise get_ptr + */ + vff->get_lump = vff->end_lump ; + vff->get_ptr = *vff->p_end ; + vff->p_get_end = vff->p_end ; - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; + vio_fifo_sync_get(vff) ; } ; -/*------------------------------------------------------------------------------ - * Move on to next lump to get stuff from. - * - * Advance pointers etc. so that have at least one byte available, unless - * the FIFO is entirely empty. - * - * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that - * these are equal ! +/*============================================================================== + * Hold Mark Operations. * - * NB: when there is only one block, it may be that get_end is out of date, - * and should be advanced to the current put_ptr position. + * Set or clear hold_ptr. * - * That is done here, but may be worth updating get_end before testing - * against get_ptr. + * The get_ptr is unambiguous -- so the hold_ptr is, because it is only ever + * set equal to the get_ptr ! * - * Returns: true <=> at least one byte in FIFO. + * The put_ptr stays in its current lump, so no need to worry about put_end. + */ + +/*------------------------------------------------------------------------------ + * Set hold mark -- clearing existing one, if any. * - * NB: if finds that the FIFO is empty, resets the pointers to the start - * of the last lump -- does not release the last lump. + * Discard all contents up to the current get_ptr (easy if no hold mark), then + * set hold mark at get_ptr. */ -static bool -vio_fifo_get_next_lump(vio_fifo vf) +extern void +vio_fifo_set_hold_mark(vio_fifo vff) { - vio_fifo_lump head ; + vio_fifo_release_up_to(vff, vff->get_lump) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; - assert(vf->get_ptr == vf->get_end) ; + if (vff->get_ptr == vff->put_ptr) + vio_fifo_reset_ptrs(vff) ; + else + vff->hold_ptr = vff->get_ptr ; - head = ddl_head(vf->base) ; /* current lump for get */ + vff->p_start = &vff->hold_ptr ; - /* Deal with the simple case of one lump, first. - * - * To save work when putting data into the FIFO (particularly when putting - * a byte at a time) does not keep the vf->get_end up to date (when there is - * only one lump). - * - * If the FIFO is empty, reset pointers and return empty. - */ - if (vf->one) - { - assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; - if (vf->get_ptr < vf->put_ptr) - { - /* Had an out of date vf->get_end */ - vf->get_end = vf->put_ptr ; +/*------------------------------------------------------------------------------ + * Clear hold mark -- if any. + * + * Discard all contents up to the current get_ptr (easy if no hold mark), then + * clear hold mark (no effect if not set). + * + * Note that clearing a hold_ptr in an empty FIFO resets all the pointers. To + * avoid that could test for an empty hold mark (*p_start == get_ptr), but the + * extra step in the majority case seems worse than the extra work in the + * minority one. + */ +extern void +vio_fifo_clear_hold_mark(vio_fifo vff) +{ + vio_fifo_release_up_to(vff, vff->get_lump) ; - return true ; /* FIFO not empty */ - } ; + if (vff->get_ptr == vff->put_ptr) + vio_fifo_reset_ptrs(vff) ; - assert(vf->get_ptr == vf->put_ptr) ; + vff->p_start = &vff->get_ptr ; - /* FIFO is empty -- reset pointers and exit */ - vio_fifo_ptr_reset(vf, head) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; - return false ; /* FIFO empty */ - } ; +/*------------------------------------------------------------------------------ + * If there is an hold_mark, reset get_ptr *back* to it, and leave the mark + * set or clear. + * + * If there is no hold mark, set one at the current position, if required. + * + * Setting the get_ptr back to the hold_ptr sets it to an unambiguous + * position. If the get_ptr == hold_ptr then if the FIFO is empty, the + * pointers will have been reset. + */ +extern void +vio_fifo_back_to_hold_mark(vio_fifo vff, on_off_b set) +{ + if (vio_fifo_have_hold_mark(vff)) + vio_fifo_set_get_ptr(vff, ddl_head(vff->base), vff->hold_ptr) ; + + vff->p_start = (set) ? &vff->hold_ptr : &vff->get_ptr ; - /* Release the head and update pointers - * - * Deals with possibility that nothing has yet been allocated - */ - vio_fifo_lump_release(vf, head) ; - return (vf->get_ptr < vf->get_end) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*============================================================================== * For debug purposes -- verify the state of the given FIFO */ Private void -vio_fifo_verify(vio_fifo vf) +vio_fifo_verify(vio_fifo vff) { vio_fifo_lump head ; vio_fifo_lump lump ; vio_fifo_lump tail ; + bool own_seen ; - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; + head = ddl_head(vff->base) ; + tail = ddl_tail(vff->base) ; - /* If nothing allocated, should all be NULL & !vf->one */ - /* If something allocated, tail must not be NULL */ + /* FIFO always has at least one lump. */ if (head == NULL) + zabort("head is NULL") ; + if (tail == NULL) + zabort("tail is NULL") ; + + /* Make sure that the lump pointers all work + * + * When finished, know that head <= get_lump <= end_lump <= tail. + */ + own_seen = false ; + + lump = head ; + while (1) + { + if (lump == vff->own_lump) + own_seen = true ; + + if (lump == vff->get_lump) + break ; + + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for get_lump") ; + } ; + + while (lump != vff->end_lump) + { + lump = ddl_next(lump, list) ; + + if (lump == NULL) + zabort("ran out of lumps looking for end_lump") ; + + if (lump == vff->own_lump) + own_seen = true ; + } ; + + while (lump != tail) + { + lump = ddl_next(lump, list) ; + + if (lump == NULL) + zabort("ran out of lumps looking for tail") ; + + if (lump == vff->own_lump) + own_seen = true ; + } ; + + if (vff->spare == vff->own_lump) { - if ( (tail != NULL) - || (vf->put_ptr != NULL) - || (vf->put_end != NULL) - || (vf->get_ptr != NULL) - || (vf->rdr_lump != NULL) - || (vf->rdr_ptr != NULL) - || (vf->one) ) - zabort("nothing allocated, but not all NULL") ; - return ; + if (own_seen) + zabort("own seen in FIFO, but is also spare") ; } else { - if (tail == NULL) - zabort("head pointer not NULL, but tail pointer is") ; + if (!own_seen) + zabort("not found own lump in the FIFO") ; + } ; + + /* Check that the p_start, p_get_end and p_end are valid + */ + if ((vff->p_start != &vff->hold_ptr) && (vff->p_start != &vff->get_ptr)) + zabort("p_start is neither &get_ptr nor &hold_ptr") ; + + if ((vff->p_end != &vff->end_ptr) && (vff->p_end != &vff->put_ptr)) + zabort("p_end is neither &put_ptr nor &end_ptr") ; + + if (vff->get_lump == vff->end_lump) + { + if (vff->p_get_end != vff->p_end) + zabort("p_get_end not equal to p_end and is in end_lump") ; + } + else + { + if (vff->p_get_end != &vff->get_lump->end) + zabort("p_get_end not equal to get_lump->end and is not in end_lump") ; } ; /* Check that all the pointers are within respective lumps * * Know that put_end is always tail->end, but get_end need not be. + * + * When finished, know that: + * + * - get_lump == head if !hold_mark + * - end_lump == tail if !end_mark + * - that all pointers are within their respective lumps + * - all ptr are <= their respective ends + * - if hold_mark: hold_ptr <= get_ptr or head != get_lump + * - if end_mark: end_ptr <= put_ptr or tail != end_lump */ - if ( (tail->data > vf->put_ptr) - || (vf->put_ptr > vf->put_end) - || (vf->put_end != tail->end) ) - zabort("put pointers outside the tail lump") ; + if (vio_fifo_have_hold_mark(vff)) + { + if ( (head->data > vff->hold_ptr) + || (vff->hold_ptr > head->end) ) + zabort("hold_ptr outside the head lump") ; - if ( (head->data > vf->get_ptr) - || (vf->get_ptr > vf->get_end) - || (vf->get_end > head->end) ) - zabort("get pointers outside the head lump") ; + if ((vff->get_lump == head) && (vff->hold_ptr > vff->get_ptr)) + zabort("hold_ptr greater than get_ptr") ; + } + else + { + if (vff->get_lump != head) + zabort("no hold_ptr, but get_lump is not head") ; + } ; + + if ( (vff->get_lump->data > vff->get_ptr) + || (vff->get_ptr > *vff->p_get_end) + || (*vff->p_get_end > vff->get_lump->end)) + zabort("get pointers outside the get lump") ; - /* If head == tail, should be vf->one, etc. */ - if (head == tail) + if (vio_fifo_have_end_mark(vff)) { - if (!vf->one) - zabort("have one lump, but !vf->one") ; + if ( (vff->end_lump->data > vff->end_ptr) + || (vff->end_ptr > vff->end_lump->end) ) + zabort("end pointer outside the end lump") ; - if (vf->get_end > vf->put_ptr) - zabort("get_end is greater than put_ptr when vf->one") ; + if ((vff->end_lump == tail) && (vff->end_ptr > vff->put_ptr)) + zabort("end pointer greater than put pointer") ; } else { - if (vf->one) - zabort("have two or more lumps, but vf->one is true") ; - - if (vf->get_end != head->end) - zabort("get_end is not head->end when !vf->one") ; + if (vff->end_lump != tail) + zabort("no end_ptr, but end_lump is not tail") ; } ; - /* If have an rdr_lump -- make sure everything else is valid */ - if (vf->rdr_lump != NULL) + if ( (tail->data > vff->put_ptr) + || (vff->put_ptr > vff->put_end) + || (vff->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; + + /* Check that if get_ptr == p_get_end, that it is empty, or there is some + * not-empty hold or end mark. + * + * The point is to trap any failure to reset pointers or advance the get_ptr + * when it hits *p_get_end. + */ + if (vff->get_ptr == *vff->p_get_end) { - lump = head ; - while (lump != vf->rdr_lump) + if (*vff->p_start != vff->put_ptr) { - if (lump == tail) - zabort("rdr_lump is not part of FIFO") ; - lump = ddl_next(lump, list) ; + /* Not empty -- so must have a hold and/or end */ + if (!(vio_fifo_have_hold_mark(vff) || vio_fifo_have_end_mark(vff))) + zabort("get_ptr is at get_end, is not empty by no marks set") ; } ; + } ; - if ( (lump->data > vf->rdr_ptr) - || (vf->rdr_ptr > lump->end) ) - zabort("rdr_ptr outside its lump") ; - - if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) - zabort("rdr_ptr is less than get_ptr in first lump") ; - - if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) - zabort("rdr_ptr is greater than put_ptr in last lump") ; - } - else + /* Check that if is empty, the pointers are reset. + */ + if (*vff->p_start == vff->put_ptr) { - if (vf->rdr_ptr != NULL) - zabort("rdr_ptr not NULL when rdr_lump is") ; - } + if ( (tail != head) + || (vff->get_lump != head) + || (vff->end_lump != head) + || (vff->get_ptr != head->data) + || (vff->put_ptr != head->data) + || (vff->put_end != head->end) + || !( (vff->p_start != &vff->hold_ptr) || (vff->hold_ptr == head->data) ) + || !( (vff->p_end != &vff->end_ptr) || (vff->end_ptr == head->data) ) + ) + zabort("pointers not valid for empty fifo") ; + } ; } ; |