diff options
Diffstat (limited to 'lib/vio_fifo.c')
-rw-r--r-- | lib/vio_fifo.c | 1823 |
1 files changed, 1162 insertions, 661 deletions
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index 10c4a343..e6365861 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -20,14 +20,13 @@ */ #include "misc.h" +#include <stdio.h> #include <string.h> #include "vio_fifo.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,7 +38,6 @@ * ever needed. * * When releasing lumps, keeps one lump "spare", to be reused as necessary. - * This is used in ... TODO <<<< And is released... * *------------------------------------------------------------------------------ * Implementation notes: @@ -48,97 +46,218 @@ * * Once a lump has been allocated there is always one lump in the FIFO. * + * The hold_mark allows the get_ptr to move forward, but retaining the data in + * the FIFO until the hold_mark is cleared. Can move the get_ptr back to the + * hold_mark to reread the data. + * + * The end_mark allows put_ptr to move forward, but the new data cannot be got + * from the FIFO until the end_mark is cleared. Can discard the new data + * and move the put_ptr back to the end_mark. + * + * 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 + * * set <=> at least one lump in the FIFO + * + * * as_one <=> set && get_lump == tail && !end_mark + * => get_end moves with put_ptr * - * * put_ptr == tail->end -- at all times (NULL when no lumps) + * * hold_mark => there is a hold mark & hold_ptr is valid + * & head lump contains mark + * !hold_mark => get_lump == head & hold_ptr == NULL + * + * * end_mark => there is an end_mark & end_end is valid + * & end_lump contains mark + * !end_mark => end_lump == tail & end_ptr == NULL + * + * * put_ptr == get_ptr => FIFO empty -- unless hold_mark and + * hold_ptr != get_ptr. + * + * * put_end == tail->end -- at all times (NULL when no lumps) * * 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 + * * get_end == get_lump->end -- when get_lump != end_lump + * == end_end -- when get_lump == end_lump & end_mark set + * <= put_ptr -- when get_lump == end_lump & no end_mark + * + * See note below on get_end and as_one flag. * - * 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 => nothing to get from current get_lump + * BUT if as_one, make sure that get_end == put_ptr + * get_ptr < get_end => data exists in the current get_lump * get_ptr > get_end => broken * * 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 <= get_end can get stuff without worrying about other + * pointers or moving between lumps etc. It is permissible to leave + * get_ptr == get_end -- this will be tidied up on the next get operation, + * or any other time vio_fifo_sync_get() is called. Leaving get_ptr in + * that state delays the discard of the now empty lump, but has no other + * real downside. + * + * Similarly, while put_ptr <= put_end, can put stuff without worrying + * about other pointers or moving between lumps etc. + * + * When getting, if get_ptr == get_end, or require more data than is + * immediately available, need to use vio_fifo_sync_get(), to do that. + * + * * the value of get_end depends on whether get_lump == end_lump, and then + * whether there is an end_mark. + * + * When get_lump == end_lump && !end_mark, then get_end may be out of date + * because get_ptr has been advanced. This is dealt with by + * vio_fifo_sync_get(), which uses the as_one flag to signal that it should + * set get_end = put_ptr and then (re)check for anything to get. * - * 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. + * The as_one flag is there to save a little work. Making sure that the + * get_end is up to date can be done often when getting from the FIFO. So + * the maintenance of the flag should be worth the effort. * - * * when advancing the put_ptr does not check for advancing the get_end. + * * some care must be taken to ensure that are not fooled by the + * ambiguity of a pointer to the end of one lump and a pointer to the + * start of the next -- these are really equal, but they don't look as if + * they are ! * - * 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 -- if at the end of the last lump there is no next lump ! * - * Everywhere that the get_end is used, must check for there being one - * lump and the possibility that put_ptr has changed. + * When a new lump is added, the put_ptr advances to the + * start of the new last lump. + * + * - end_end -- when set from the put_ptr, this can be at the end of + * the last lump, but as above, there is no next lump. + * + * When a new lump is added, if the end_end is at the + * end of the last lump, it is moved to the start of the + * new last lump (along with the out_ptr). + * + * So... end_end will never be ambiguous. + * + * - get_ptr -- this can be ambiguous. + * + * When getting bytes, if the segment get_ptr..get_end + * is sufficient, then nothing else is required. + * + * Otherwise, vio_fifo_sync_get() will sort things out, + * including resetting all pointers if the FIFO has been + * emptied. + * + * - hold_ptr -- when set from get_ptr this could be at the end of the + * first lump -- but when vio_fifo_sync_get() is called, + * that will be spotted and sorted out. + * + * If get_ptr is set from an ambiguous hold_ptr, that is + * also taken care of by the next vio_fifo_sync_get(). + * + * When doing things with hold_ptr, does a number of + * vio_fifo_sync_get() operations, so the hold_ptr should + * not, in practice, be ambiguous. + * + * * Before the first lump is allocated the FIFO appears empty (of course) + * but may have hold_mark and/or end_mark set, and these work as expected. */ /*============================================================================== * Initialisation, allocation and freeing of FIFO and lumps thereof. */ -/* Return default size, or given size rounded up to 16 byte boundary */ +/*------------------------------------------------------------------------------ + * Return default size, or given size rounded up to 128 byte boundary + */ static size_t vio_fifo_size(size_t size) { +#if VIO_FIFO_DEBUG +#warning VIO_FIFO_DEBUG and 29 byte lumps ! + return 29 ; +#else if (size == 0) return 4096 ; else - return ((size + 16 - 1) / 16) * 16 ; + return ((size + 128 - 1) / 128) * 128 ; +#endif +} ; + +/*------------------------------------------------------------------------------ + * Set and return true end of lump. + * + * End of lump can be set short of true end when putting stuff, if wish to + * move to next lump early (eg if not enough room left in current lump). + * + * When reusing a lump, need to restore the true lump end. + */ +static inline char* +vio_fifo_true_lump_size(vio_fifo_lump lump) +{ + return lump->end = lump->data + lump->size ; } ; /*============================================================================== * Initialise VTY I/O FIFO -- allocating if required. */ extern vio_fifo -vio_fifo_init_new(vio_fifo vf, size_t size) +vio_fifo_init_new(vio_fifo vff, size_t size) { - if (vf == NULL) - vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; + if (vff == NULL) + vff = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; else - memset(vf, 0, sizeof(vio_fifo_t)) ; + memset(vff, 0, sizeof(vio_fifo_t)) ; /* Zeroising the the vio_fifo_t has set: * - * lump -- base pair, both pointers NULL => list is empty + * base -- 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 + * set -- false -- nothing set, yet. + * as_one -- false -- get_lump != tail or end_mark + * hold_mark -- false -- no hold mark + * end_mark -- false -- no end mark + * + * hold_ptr -- NULL no hold_mark * + * get_lump -- NULL ) * 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 + * end_lump -- NULL no lump at end of what can get + * end_end -- NULL no end_mark * - * spare -- NULL no spare lump + * put_ptr -- NULL ) no lump to put anything into + * put_end -- NULL ) put_ptr == put_end => no room in current lump * - * ALSO put_ptr == get_ptr => FIFO is empty ! + * size -- 0 no size set for lumps (yet) + * + * spare -- NULL no spare lump */ + confirm(VIO_FIFO_INIT_ALL_ZEROS) ; - vf->size = vio_fifo_size(size) ; + if (size != 0) + vff->size = vio_fifo_size(size) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - return vf ; + return vff ; } /*------------------------------------------------------------------------------ @@ -146,7 +265,8 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * * Does nothing if given a NULL pointer -- must already have been freed ! * - * If does not free the FIFO structure, resets it all empty. + * If does not free the FIFO structure, resets it all empty, keeping the + * current size setting. * * Frees *all* FIFO lumps. * @@ -154,104 +274,289 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * vio_fifo_reset_free(vio_fifo) */ extern vio_fifo -vio_fifo_reset(vio_fifo vf, int free_structure) +vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) { vio_fifo_lump lump ; - if (vf == NULL) + if (vff == NULL) return NULL ; - while (ddl_pop(&lump, vf->base, list) != NULL) + while (ddl_pop(&lump, vff->base, list) != NULL) XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; + + confirm(free_it == true) ; if (free_structure) - XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + XFREE(MTYPE_VIO_FIFO, vff) ; /* sets vff = NULL */ else - vio_fifo_init_new(vf, vf->size) ; + vio_fifo_init_new(vff, vff->size) ; - return vf ; + return vff ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO has one lump -- set all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. + */ +inline static void +vio_fifo_ptr_set(vio_fifo vff, vio_fifo_lump lump) +{ + vff->as_one = !vff->end_mark ; + + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; + + vff->hold_ptr = vff->hold_mark ? vff->put_ptr : NULL ; + + vff->get_lump = lump ; + vff->get_ptr = vff->put_ptr ; + vff->get_end = vff->put_ptr ; + + vff->end_lump = lump ; + vff->end_end = vff->end_mark ? vff->put_ptr : NULL ; } ; /*------------------------------------------------------------------------------ * The FIFO is empty, with one lump -- reset all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. */ inline static void -vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +vio_fifo_ptr_reset(vio_fifo vff, vio_fifo_lump lump) { - if (vf->rdr_lump != NULL) - { - assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; - vf->rdr_ptr = lump->data ; - } ; + assert(vff->set) ; + assert(lump == ddl_tail(vff->base)) ; /* must be tail */ + assert(lump == ddl_head(vff->base)) ; /* and must be head */ + + vio_fifo_ptr_set(vff, lump) ; +} ; + +/*------------------------------------------------------------------------------ + * Synchronise get_end and put_ptr (if required) + * + * When in the same lump, the get_end and the put_ptr should be the same. But + * maintaining that every time something is put into the buffer is a pain, so + * we allow the put_ptr to get ahead, and re-synchronise when get_ptr hits + * get_end, or any other time we need the pointers to be straight. + * + * The as_one flag takes a little maintenance... it means: + * + * (get_lump == tail) && set && !end_mark + * + * If get_ptr is at the end of what can be got from the current lump, advances + * to the next lump if possible, releasing anything that can be released. + * + * If the FIFO is empty (with one lump), will crash all pointers down to start + * of current lump. The FIFO is empty if get_ptr == put_ptr, and there are no + * bytes held before the get_ptr. + * + * NB: object of the hold_mark is to allow users of the FIFO to store pointers + * to sections of the FIFO returned by vio_fifo_get_lump() and stepped + * over by vio_fifo_got_upto(). Note that where there is nothing to + * read vio_fifo_get_lump() returns NULL -- so do not have to preserve + * the hold_ptr when hold_ptr == get_ptr (== end_end) == put_ptr -- that + * is, if hold_ptr == get_ptr, we can move the hold_ptr around with the + * get_ptr. + * + * Can reach empty state without realising it, because the "get" stuff is not + * required to check every time a byte is read -- in fact, it is not necessary + * to check until cannot get anything because get_ptr == get_end. + * + * NB: with an end_mark there is the ambiguous position at the end of a + * lump -- which is the same as the start of the next lump, if any. + * + * Elsewhere we advance the end_end if it was at the very end of the FIFO + * and we advance the put_ptr to a new lump. But we cope here in any + * case. + * + * Returns: true <=> there is something in the (now) current get_lump. + * false <=> there is nothing else to be got (though if there is an + * end_mark, there may be stuff beyond that). + */ +static bool vio_fifo_sync_get_next(vio_fifo vff) ; +static void vio_fifo_release_head(vio_fifo vff, vio_fifo_lump lump) ; + +inline static bool +vio_fifo_sync_get(vio_fifo vff) +{ + if (vff->as_one) /* false if !vff->set */ + vff->get_end = vff->put_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 ; + if (vff->get_ptr < vff->get_end) /* both NULL if !vff->set */ + return true ; /* have at least one byte */ + + return vio_fifo_sync_get_next(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * See if fifo really is or is not empty (get_ptr == end_end or == put_ptr). + * + * Used by vio_fifo_empty(). Called iff get_ptr >= get_end ! + * + * So need to vio_fifo_sync_get() and test again against end_end & put_ptr. + */ +Private bool +vio_fifo_do_empty(vio_fifo vff) +{ + return ! vio_fifo_sync_get(vff) ; } ; /*------------------------------------------------------------------------------ - * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + * Set a new get_lump/get_ptr/get_end set. And set as_one to suit. */ inline static void -vio_fifo_ptr_unset(vio_fifo vf) +vio_fifo_set_get_ptr(vio_fifo vff, void* ptr, vio_fifo_lump lump) { - assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; + vff->get_lump = lump ; + vff->get_ptr = ptr ; - vf->one = false ; + if (lump != vff->end_lump) + { + vff->get_end = lump->end ; + vff->as_one = false ; + } + else if (vff->end_mark) + { + vff->get_end = vff->end_end ; + vff->as_one = false ; + } + else + { + vff->get_end = vff->put_ptr ; + vff->as_one = true ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Move on to next lump to get stuff from. + * + * For use by vio_fifo_sync_get() *ONLY*. + * + * Asserts that (vff->get_ptr == vff->get_end) and assumes that if as_one, then + * get_end == put_ptr ! + * + * Returns: true <=> at least one byte in FIFO available to get. + */ +static bool +vio_fifo_sync_get_next(vio_fifo vff) +{ + bool hold_empty ; - vf->put_ptr = NULL ; - vf->put_end = NULL ; - vf->get_ptr = NULL ; - vf->get_end = NULL ; + assert(vff->get_ptr == vff->get_end) ; - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + + if (!vff->set) + return false ; /* quit before get into any trouble */ + + /* Worry about whether there is anything held. + * + * NB: we know that at this point the get_ptr == get_end, so is at the end + * of the current get_lump. The hold_ptr cannot be ahead of get_ptr, + * so the test hold_ptr == get_ptr is sufficient to detect that there + * is nothing, in fact, held. + */ + hold_empty = !vff->hold_mark || (vff->hold_ptr == vff->get_ptr) ; + + /* If we are not at the end_lump, step forward, discarding current + * lump unless hold_mark. + */ + if (vff->get_lump != vff->end_lump) + { + vio_fifo_lump next ; + next = ddl_next(vff->get_lump, list) ; + + vio_fifo_set_get_ptr(vff, next->data, next) ; + + if (hold_empty) + { + vio_fifo_release_head(vff, next) ; + + /* If there is a hold_mark, then had hold_ptr == get_ptr + * and this is where we keep hold_ptr & get_ptr in sync. + */ + if (vff->hold_mark) + vff->hold_ptr = vff->get_ptr ; + } ; + + /* Return the get state now */ + if (vff->get_ptr < vff->get_end) + return true ; + } ; + + /* Still have get_ptr == get_end => nothing more to get. + * + * Check now for empty FIFO, and reset all pointers if that is the case. + */ + if ((vff->get_ptr == vff->put_ptr) && hold_empty) + { + if (vff->hold_mark) + assert(vff->hold_ptr == vff->get_ptr) ; + + if (vff->end_mark) + assert(vff->end_end == vff->put_ptr) ; + + vio_fifo_ptr_reset(vff, vff->end_lump) ; + } + + return false ; /* nothing to get */ } ; /*------------------------------------------------------------------------------ * Clear out contents of FIFO -- will continue to use the FIFO. * + * If required, clears any hold mark and/or end mark. + * * Keeps one FIFO lump. (Frees everything else, including any spare.) * * Does nothing if there is no FIFO ! */ extern void -vio_fifo_clear(vio_fifo vf) +vio_fifo_clear(vio_fifo vff, bool clear_marks) { - vio_fifo_lump tail ; - - if (vf == NULL) + if (vff == NULL) return ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - tail = ddl_tail(vf->base) ; + vff->as_one = vff->set ; + if (clear_marks) + { + vff->hold_mark = false ; /* Discard marks */ + vff->end_mark = false ; + } ; - if (tail != NULL) + if (vff->set) { - while (ddl_head(vf->base) != tail) + vio_fifo_lump lump ; + + while (1) { - vio_fifo_lump lump ; - ddl_pop(&lump, vf->base, list) ; + lump = ddl_head(vff->base) ; + if (lump == ddl_tail(vff->base)) + break ; + + ddl_pop(&lump, vff->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) ; + vio_fifo_ptr_reset(vff, lump) ; + } ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; /* sets vff->spare = NULL */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ @@ -266,279 +571,226 @@ vio_fifo_clear(vio_fifo vf) * Returns: room available as described */ extern size_t -vio_fifo_room(vio_fifo vf) +vio_fifo_room(vio_fifo vff) { - if (vf->put_ptr != NULL) - return vf->put_end - vf->put_ptr ; + if (vff->set) + return vff->put_end - vff->put_ptr ; else - return vf->size ; + return vff->size ; } ; /*------------------------------------------------------------------------------ - * Allocate another lump for putting into. + * Need a new lump to put stuff into. * - * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. + * Call when (vff->put_ptr >= vff->put_end) -- asserts that they are equal. * - * Set the put_ptr/put_end pointers to point at the new lump. + * If the FIFO is, in fact, empty but with at least one lump, then does not + * allocate anything more, but releases all lumps but the last lump and then + * resets all pointers to the start of that lump. * - * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * Otherwise, allocates a new lump (or reuses the spare) to the requested size, + * and updates all pointers as required. (Allocates to vff->size if + * requested size is zero.) * - * 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. + * NB: if there is an end_mark, makes sure that it advances with the put_ptr + * if currently end_end == the put_ptr. */ -extern void -vio_fifo_lump_new(vio_fifo vf, size_t size) +Private void +vio_fifo_lump_new(vio_fifo vff, size_t size) { vio_fifo_lump lump ; - int first_alloc ; + size_t std_size ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + passert(vff->put_ptr == vff->put_end) ; /* must be end of tail lump + (or both NULL) */ + VIO_FIFO_DEBUG_VERIFY(vff) ; - passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + /* First, make sure that the get side is synchronised, which may advance + * various pointers, release lumps and possibly reset now empty FIFO. + * + * If synchronising the get side does not yield space, then the FIFO is + * either not set or it has something in it (including held stuff). + */ + vio_fifo_sync_get(vff) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* update get_end */ + if (vff->put_ptr < vff->put_end) + return ; /* Done if have created space */ - lump = ddl_tail(vf->base) ; + /* If we can use the spare, do so, otherwise make it to size */ + lump = vff->spare ; + vff->spare = NULL ; - first_alloc = (lump == NULL) ; /* extra initialisation needed */ + std_size = vio_fifo_size(vff->size) ; /* normalised standard */ - if (first_alloc) - assert(vf->put_ptr == NULL) ; /* must all be NULL together */ + if (size <= std_size) + size = std_size ; /* use standard as a minimum */ else - assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ +#if VIO_FIFO_DEBUG + size |= 3 ; /* most of the time a little bigger */ +#else + size = vio_fifo_size(size) ; /* normalise requested size */ +#endif - size = vio_fifo_size(size) ; - - if ((vf->spare != NULL) && (vf->spare->size >= size)) - { - lump = vf->spare ; - vf->spare = NULL ; - } - else + if ((lump == NULL) || (lump->size < size)) { - lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, - offsetof(vio_fifo_lump_t, data[size])) ; + /* If there was no spare, lump == NULL and XREALLOC == XMALLOC. + * If there was a spare that was too small, better extend that than + * keep a sub-standard spare. + */ + lump = XREALLOC(MTYPE_VIO_FIFO_LUMP, lump, + offsetof(vio_fifo_lump_t, data[size])) ; lump->size = size ; + lump->end = lump->data + size ; } ; - lump->end = lump->data + lump->size ; - - ddl_append(vf->base, lump, list) ; - - vf->one = first_alloc ; - - vf->put_ptr = lump->data ; - vf->put_end = lump->end ; - - if (first_alloc) - { - vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ - vf->get_end = vf->put_ptr ; - } ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; -} ; + ddl_append(vff->base, lump, list) ; -/*------------------------------------------------------------------------------ - * Release lump, head or tail (or both) and update pointers. - * - * 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. - * - * * 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 ! - */ -static void -vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) -{ - 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 not just allocated the first lump, set the put_ptr and normalise any + * end_mark or update end_lump. + * + * If is first block to be allocated, set all pointers, taking into account + * any end_mark and/or hold_mark. + */ + if (vff->set) { - vio_fifo_lump keep ; + /* Allocated new lump on the end of a not-empty fifo. + * + * Have to watch out for cases where the get_ptr and/or end_end are + * equal to the put_ptr, which is about to move to the start of a new + * lump, so as to avoid ambiguity ! + */ - /* Consistency checks */ - if (release_head) + if (vff->get_ptr == vff->put_ptr) { - if (release_tail) - assert(vf->get_ptr == vf->put_ptr) ; - else - assert(vf->get_ptr == lump->end) ; + /* If the fifo is empty, the vio_fifo_sync_get() above will have + * spotted it. So can only get here iff there is something held in + * the fifo behind the get_ptr. + * + * If was as_one, is still as_one. + * + * If was not as_one then must have end_mark with end_end == put_ptr, + * which will be dealt with below. + */ + assert(vff->hold_mark && (vff->get_ptr != vff->hold_ptr)) ; - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->get_ptr) ; + vff->get_ptr = lump->data ; + vff->get_end = lump->data ; + vff->get_lump = lump ; } - else if (release_tail) + else { - assert(vf->put_ptr == lump->data) ; - - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->put_ptr) ; + /* If were as_one, then will no longer be, because put_ptr is about + * to advance to the start of the new lump. + */ + vff->as_one = false ; } ; - /* 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) ; - - keep = vf->spare ; /* expect to keep current spare */ + if (vff->end_mark) + { + assert(!vff->as_one) ; - if ((keep == NULL) || (keep->size < lump->size)) + /* The end_end follows the put_ptr iff they are equal + * + * If the get_ptr also equals the put_ptr, it has already advanced. + */ + if (vff->end_end == vff->put_ptr) + { + vff->end_end = lump->data ; + vff->end_lump = lump ; + } ; + } + else { - keep = lump ; - free = vf->spare ; + /* No end_mark => end_lump simply follows the put_ptr. */ + vff->end_lump = lump ; } ; - vf->spare = keep ; - - head = ddl_head(vf->base) ; /* changed if released head */ - tail = ddl_tail(vf->base) ; /* changed if released tail */ + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; + } + else + { + /* Allocated lump for previously empty fifo -- set all pointers to the + * start of the lump, except for put_end. + */ + vff->set = true ; + vio_fifo_ptr_set(vff, lump) ; } ; - /* 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) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; - vio_fifo_ptr_unset(vf) ; - } - else +/*------------------------------------------------------------------------------ + * Release the given lump, provided it is neither get_lump nor end_lump. + * + * If don't have a spare lump, keep this one. Otherwise, keep larger of + * this and current spare. + */ +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) { - /* Have at least one lump left -- so must have had at least two ! */ - assert(!vf->one) ; + vio_fifo_lump free ; - vf->one = (head == tail) ; /* update */ + free = vff->spare ; - if (release_head) + if (free->size > lump->size) { - /* 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 ; - } ; + free = lump ; + lump = vff->spare ; } ; - } ; - - /* Finally, free any lump that is actually to be freed */ - if (free != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->spare = lump ; } ; /*------------------------------------------------------------------------------ - * Re-allocate lump for putting into. - * - * Call when vf->put_ptr == start of last lump, and that lump is not big - * enough ! + * Release lumps from head up to, but not including, given lump. * - * There must be at least one lump. + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. * - * Updates put_ptr/put_end pointers to point at the new lump. + * So MUST advance at least the get_lump before calling this. * - * Updates get_ptr/get_end pointers if required. - * - * Updates rdr_ptr if required. + * It is the caller's responsibility to update get and/or hold pointers. */ static void -vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +vio_fifo_release_head(vio_fifo vff, vio_fifo_lump upto) { - bool rdr_set ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - /* FIFO may not be completely empty. - * This must be the last lump. - * The last lump must be empty. - */ - assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; - - /* Remove the last, *empty* lump, and update all pointers to suit. */ - rdr_set = (vf->rdr_lump == lump) ; - - vio_fifo_lump_release(vf, lump) ; + assert(vff->set) ; - /* Now allocate a new lump with the required size */ - vio_fifo_lump_new(vf, size) ; - - /* Restore the rdr_ptr, if required */ - if (rdr_set) + while (upto != ddl_head(vff->base)) { - vio_fifo_lump tail ; + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ; + } ; +} ; - tail = ddl_tail(vf->base) ; +/*------------------------------------------------------------------------------ + * Release lumps from tail back to, but not including, given lump. + * + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. + * + * It is the caller's responsibility to update end and/or put pointers. + */ +static void +vio_fifo_release_tail(vio_fifo vff, vio_fifo_lump backto) +{ + assert(vff->set) ; - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->data ; + while (backto != ddl_tail(vff->base)) + { + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ; } ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; } ; /*============================================================================== @@ -546,196 +798,461 @@ 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, size_t 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 */ + size_t take ; - take = (vf->put_end - vf->put_ptr) ; + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + 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: does not extend an existing lump in order to make things fit, but + * splits the result across two lumps. This ensures that at all times + * pointers into existing lumps are stable -- so pointer returned by + * vio_fifo_get_lump(), for example, cannot be upset ! */ 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 args) { va_list ac ; int len ; int have ; - size_t size ; - vio_fifo_lump lump ; + int had ; + int need ; + char* last ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff == NULL) + return 0 ; - size = vf->size ; /* standard allocation size */ - while (1) + VIO_FIFO_DEBUG_VERIFY(vff) ; + + /* First the simple way. + * + * 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'. In the meantime, it has output as much as it + * can, with a trailing '\0'. + */ + assert(vff->put_ptr <= vff->put_end) ; + + have = vff->put_end - vff->put_ptr ; /* what can do in current lump + if any. */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; + + if ((len < have) || (len == 0)) /* OK, or failed ! */ { - /* 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 */ + if (len > 0) + vff->put_ptr += len ; /* advance put_ptr as required */ - have = vf->put_end - vf->put_ptr ; - assert(have > 0) ; + return len ; + } ; - /* 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) ; + /* Now know that we need len + 1 bytes in all to complete the task, and + * that it has written have - 1 bytes to the existing lump (if any). + * + * Also, len > 0. + * + * Allocate a new lump in which we can write the entire result, even if + * that is a non-standard size. + */ + need = len + 1 ; /* need includes the '\0' */ + had = have ; - if (len < have) - { - if (len < 0) - break ; /* quit if failed */ + if (had > 0) + vff->put_ptr += had ; /* step to the end */ + last = vff->put_ptr ; /* point at end of lump (NULL if none) */ - vf->put_ptr += len ; - break ; /* done */ - } ; + vio_fifo_lump_new(vff, need) ;/* new lump to do it all */ - /* 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 */ + have = vff->put_end - vff->put_ptr ; + assert(have >= need) ; /* have >= 2 */ - lump = ddl_tail(vf->base) ; + /* We really expect to get the same result a second time ! */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; - 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 ; + /* Since have >= what previously said it needed, things have gone + * badly wrong if the new len is >= have. + * + * Also, things have gone badly wrong if new len is < what previously + * had, which was then not enough ! + * + * Also, things have gone badly wrong if new len == 0, because previously + * it was > 0 ! + */ + if ((len >= have) || (len < had) || (len == 0)) + return (len < 0) ? len : -1 ; + + /* Move result around if required -- len >= had */ + have = len ; + if (had > 0) + { + char* frag ; + frag = vff->put_ptr + had ; /* first character to keep */ + *(last - 1) = *(frag - 1) ; /* replace the '\0' */ + + have -= had ; /* amount to keep */ + if (have > 0) + memmove(vff->put_ptr, frag, have) ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + /* Advance the put_ptr past what we have in the new lump. */ + vff->put_ptr += have ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; return len ; } ; +/*------------------------------------------------------------------------------ + * Read part of file into FIFO -- assuming non-blocking file + * + * Will read up to the end of the lump which meets, or exceeds the number of + * bytes requested, or until would block. + * + * 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. + */ +extern int +vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) +{ + size_t total ; + + total = 0 ; + + do + { + int got ; + + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ; + + 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 ; + + } while (total < request) ; + + return total ; +} ; + /*============================================================================== - * Get data from the FIFO. + * Copy operations -- from one fifo to another. */ -static bool vio_fifo_get_next_lump(vio_fifo vf) ; +/*------------------------------------------------------------------------------ + * Copy src fifo (everything from get_ptr to end_mark or put_ptr) to dst fifo. + * + * Create a dst fifo if there isn't one. + * + * Appends to the dst fifo. + * + * Does not change the src fifo in any way. + */ +extern vio_fifo +vio_fifo_copy(vio_fifo dst, vio_fifo src) +{ + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; + + if ((src != 0) && (src->set)) + { + vio_fifo_lump src_lump ; + char* src_ptr ; + + if (src->get_ptr >= src->get_end) + vio_fifo_sync_get(src) ; + + src_lump = src->get_lump ; + src_ptr = src->get_ptr ; + + while (1) + { + char* src_end ; + + if (src_lump != src->end_lump) + src_end = src_lump->end ; + else + src_end = (src->end_mark) ? src->end_end : src->put_ptr ; + + 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 ; + } ; + } ; + + return dst ; +} ; /*------------------------------------------------------------------------------ - * Get ready to read something out of the FIFO. + * Copy tail of src fifo (everything from end_mark to put_ptr) to dst fifo. + * + * Create a dst fifo if there isn't one. * - * 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. + * Appends to the dst fifo. * - * Returns: true <=> there is something in the FIFO. + * Does not change the src fifo in any way. */ -static inline bool -vio_fifo_get_ready(vio_fifo vf) +extern vio_fifo +vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) { - assert(vf->rdr_lump == NULL) ; + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* make sure have everything */ + if ((src != 0) && (src->end_mark) && (src->set)) + { + vio_fifo_lump src_lump ; + char* src_ptr ; + vio_fifo_lump tail ; - if (vf->get_ptr >= vf->get_end) - if (!vio_fifo_get_next_lump(vf)) - return 0 ; /* quit now if nothing there */ + src_lump = src->end_lump ; + src_ptr = src->end_end ; + tail = ddl_tail(src->base) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + while (1) + { + char* src_end ; - return 1 ; + 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 ; + } ; + } ; + + return dst ; } ; +/*============================================================================== + * End Mark Operations. + */ + +/*------------------------------------------------------------------------------ + * 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. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_set_end_mark(vio_fifo vff) +{ + if (vff->set) + { + vio_fifo_sync_get(vff) ; /* in case is currently empty */ + + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = vff->put_ptr ; + + vff->get_end = (vff->get_lump == vff->end_lump) ? vff->end_end + : vff->get_lump->end ; + } ; + + vff->as_one = false ; /* not as_one with end_mark */ + vff->end_mark = true ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is an end mark, advance it to the put_ptr. + * + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. + * + * If there was no end_mark before, do nothing. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_step_end_mark(vio_fifo vff) +{ + if (vff->end_mark) + vio_fifo_set_end_mark(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is an end_mark, clear it -- everything between end mark and + * current put_ptr is kept in the FIFO. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_clear_end_mark(vio_fifo vff) +{ + if (vff->end_mark) + { + vff->end_mark = false ; + + if (vff->set) + { + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = NULL ; + + vff->as_one = (vff->get_lump == vff->end_lump) ; + /* since now no end_mark */ + if (!vff->as_one) + vff->get_end = vff->get_lump->end ; + /* would have been end_end */ + + vio_fifo_sync_get(vff) ; /* sets get_end if as_one and + tidies up if now empty (!) */ + } ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Move put_ptr back to the end mark, if any, and discard data. + * + * If there is an end_mark, keep it if required. + * + * If there is no end mark, do nothing. + */ +extern void +vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) +{ + if (vff->end_mark) + { + if (vff->set) + { + vio_fifo_release_tail(vff, vff->end_lump) ; + + vff->put_ptr = vff->end_end ; + vff->put_end = vio_fifo_true_lump_size(vff->end_lump) ; + + /* If retaining the existing end_mark, we retain the end_end and + * the current as_one (false). + * + * Otherwise... + */ + if (!keep) + { + vff->end_mark = false ; + vff->end_end = NULL ; + vff->as_one = (vff->get_lump == vff->end_lump) ; + } ; + + vio_fifo_sync_get(vff) ; /* in case now empty */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else + vff->end_mark = keep ; + } ; +} ; + +/*============================================================================== + * Get data from the FIFO. + */ + /*------------------------------------------------------------------------------ * Get upto 'n' bytes. * * Returns: number of bytes got -- may be zero. */ extern size_t -vio_fifo_get(vio_fifo vf, void* dst, size_t n) +vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) { size_t have ; void* dst_in ; - if (!vio_fifo_get_ready(vf)) - return 0 ; /* quit now if nothing there */ + VIO_FIFO_DEBUG_VERIFY(vff) ; dst_in = dst ; - while (n > 0) + while (vio_fifo_sync_get(vff) && (n > 0)) { - have = vf->get_end - vf->get_ptr ; + have = vff->get_end - vff->get_ptr ; if (have > n) have = n ; - memcpy(dst, vf->get_ptr, have) ; - vf->get_ptr += have ; + memcpy(dst, vff->get_ptr, have) ; + vff->get_ptr += have ; dst = (char*)dst + have ; - 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 */ - n -= have ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; return (char*)dst - (char*)dst_in ; } ; @@ -752,76 +1269,84 @@ vio_fifo_get(vio_fifo vf, void* dst, size_t n) * Returns: 0x00..0xFF -- byte value (as an int) * -1 => FIFO is empty. */ - -extern int -vio_fifo_get_next_byte(vio_fifo vf) +Private int +vio_fifo_get_next_byte(vio_fifo vff) { - unsigned char u ; - - if (!vio_fifo_get_ready(vf)) - return -1 ; /* quit now if nothing there */ - - u = *vf->get_ptr++ ; - - /* 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) ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vio_fifo_sync_get(vff)) + return (uchar)*vff->get_ptr++ ; - return u ; + return -1 ; } ; /*------------------------------------------------------------------------------ - * Get pointer to a lump of bytes. + * Get pointer to as many bytes as are available in the current lump (or next + * lump if nothing available in the current). * * Returns: address of next byte to get, *p_have = number of bytes available * or: NULL => FIFO is empty, *p_have = 0 * - * If the FIFO is not empty, will return pointer to at least one byte. - * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * If the FIFO is not empty and not at the end_mark, will return pointer to at + * least one byte. There may be more bytes to get in further lumps. */ extern void* -vio_fifo_get_lump(vio_fifo vf, size_t* p_have) +vio_fifo_get(vio_fifo vff, size_t* p_have) { - if (!vio_fifo_get_ready(vf)) + if (vio_fifo_sync_get(vff)) { - *p_have = 0 ; - return NULL ; + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; } ; - *p_have = (vf->get_end - vf->get_ptr) ; - return vf->get_ptr ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ - * Advance FIFO to position reached. + * Step FIFO past bytes used. * - * 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. + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). * - * The "here" argument must the the address returned by vio_fifo_get_lump() - * plus the number of bytes taken. + * NB: the "step" argument MUST not exceed the "have" previously returned. */ extern void -vio_fifo_got_upto(vio_fifo vf, void* here) +vio_fifo_step(vio_fifo vff, size_t step) +{ + vff->get_ptr += step ; + vio_fifo_sync_get(vff) ; /* ensure up to date with that */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Step FIFO past bytes used, the get pointer to as many bytes as are available + * in the current lump (or next lump if nothing available in the current). + * + * Same as vio_fifo_step() followed by vio_fifo_get(). + * + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). + * + * NB: the "step" argument MUST not exceed the "have" previously returned. + */ +extern void* +vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) { - vf->get_ptr = here ; + vff->get_ptr += step ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vio_fifo_sync_get(vff)) + { + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; + } ; - if (vf->get_ptr >= vf->get_end) - vio_fifo_get_next_lump(vf) ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ * Write contents of FIFO -- assuming non-blocking file * - * Will write all of FIFO, or upto but excluding the last lump. + * Will write all of FIFO up to end mark or put_ptr, or upto but excluding + * the last lump. * * Returns: > 0 => blocked * 0 => all gone (up to last lump if !all) @@ -831,338 +1356,314 @@ vio_fifo_got_upto(vio_fifo vf, void* here) * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ extern int -vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +vio_fifo_write_nb(vio_fifo vff, int fd, bool all) { char* src ; size_t have ; - int done ; - while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + while ((src = vio_fifo_get(vff, &have)) != NULL) { - if (!all && vf->one) - break ; /* don't write last lump */ + int done ; + + if ((vff->get_lump == vff->end_lump) && !all) + break ; /* don't write last lump */ done = write_nb(fd, src, have) ; if (done < 0) - return -1 ; /* failed */ + return -1 ; /* failed */ - vio_fifo_got_upto(vf, src + done) ; + vio_fifo_step(vff, done) ; if (done < (int)have) - return 1 ; /* blocked */ + return 1 ; /* blocked */ } ; - return 0 ; /* all gone */ -} ; - -/*------------------------------------------------------------------------------ - * Get the current rdr_end value. - * - * Unlike get_end, do not have a field for this, but find it each time. - */ -inline static char* -vio_fifo_rdr_end(vio_fifo vf) -{ - if (vf->rdr_lump == ddl_tail(vf->base)) - return vf->put_ptr ; - else - return vf->rdr_lump->end ; + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Get the current rdr position -- sets it up if not currently set. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * Write contents of FIFO -- assuming blocking file * - * If the FIFO is not empty, will return pointer to at least one byte. + * Will write all of FIFO up to end mark or put_ptr. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * Returns: 0 => all gone + * < 0 => failed -- see errno * - * 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: 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_get_rdr(vio_fifo vf, size_t* p_have) +extern int +vio_fifo_fwrite(vio_fifo vff, FILE* file) { - if (!vio_fifo_get_ready(vf)) - { - *p_have = 0 ; - return NULL ; - } ; + char* src ; + size_t have ; - if (vf->rdr_lump == NULL) /* set up new rdr if required */ + while ((src = vio_fifo_get(vff, &have)) != NULL) { - vf->rdr_lump = ddl_head(vf->base) ; - vf->rdr_ptr = vf->get_ptr ; - } ; + size_t done ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + done = fwrite(src, have, 1, file) ; - *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; - return vf->rdr_ptr ; + if (done < 1) + return -1 ; /* failed */ + + vio_fifo_step(vff, have) ; + } ; + + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Step the rdr forward by the given number of bytes. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 - * - * If the FIFO is not empty, will return pointer to at least one byte. + * Skip get_ptr to the current end -- which may be the current end_mark. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. - * - * 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(). - * - * NB: the step may NOT exceed the last reported "have". + * Does not clear any hold_mark or end_mark. */ -extern void* -vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +extern void +vio_fifo_skip_to_end(vio_fifo vff) { - char* rdr_end ; + vio_fifo_sync_get(vff) ; /* ensure all straight */ - assert(vf->rdr_lump != NULL) ; + /* Setting the get_ptr to the start of the end_lump does the bulk + * of the work -- then just skip to the get_end which that sets. + */ + vio_fifo_set_get_ptr(vff, vff->end_lump->data, vff->end_lump) ; + vff->get_ptr = vff->get_end ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vio_fifo_sync_get(vff) ; /* crunch */ +} ; - rdr_end = vio_fifo_rdr_end(vf) ; - vf->rdr_ptr += step ; +/*============================================================================== + * Hold Mark Operations. + */ - if (vf->rdr_ptr >= rdr_end) +/*------------------------------------------------------------------------------ + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. + * + * Set hold_mark at the current get_ptr. + */ +extern void +vio_fifo_set_hold_mark(vio_fifo vff) +{ + if (vff->set) { - assert(vf->rdr_ptr == rdr_end) ; - - if (vf->rdr_lump != ddl_tail(vf->base)) - { - vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; - vf->rdr_ptr = vf->rdr_lump->data ; + if (vff->hold_mark) + vio_fifo_clear_hold_mark(vff) ; /* clear existing mark & sync */ + else + vio_fifo_sync_get(vff) ; /* ensure all straight */ - rdr_end = vio_fifo_rdr_end(vf) ; - } ; + vff->hold_ptr = vff->get_ptr ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->hold_mark = true ; - *p_have = (rdr_end - vf->rdr_ptr) ; - return (*p_have > 0) ? vf->rdr_ptr : NULL ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Move FIFO get position to the rdr position, if any. + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. * - * This clears the rdr position, and removes all data between the current and - * new get positions from the FIFO. + * The get_ptr is synchronised. */ extern void -vio_fifo_sync_rdr(vio_fifo vf) +vio_fifo_clear_hold_mark(vio_fifo vff) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - if (vf->rdr_lump == NULL) - return ; + /* Make sure all is up to date, and in particular that the get_ptr + * is not sitting at the end of a lump when there is a following lump. + */ + vio_fifo_sync_get(vff) ; - while ((head = ddl_head(vf->base)) != vf->rdr_lump) + if ((vff->hold_mark) && (vff->set)) { - vf->get_ptr = vf->get_end ; /* jump to end of lump */ - vio_fifo_lump_release(vf, head) ; - } ; + /* Release everything upto but not including the current get_lump. + * + * This has no effect on the get_ptr etc. so they remain straight. + */ + vio_fifo_release_head(vff, vff->get_lump) ; - vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + vff->hold_ptr = NULL ; + } ; - vf->rdr_lump = NULL ; /* clear the rdr */ - vf->rdr_ptr = NULL ; + vff->hold_mark = false ; - 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(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Drop the rdr position (if any). + * If there is an hold_mark, reset get_ptr *back* to it. * - * This clears the rdr position leaving the get position and FIFO unchanged. + * Leave hold mark set or clear. */ extern void -vio_fifo_drop_rdr(vio_fifo vf) -{ - VIO_FIFO_DEBUG_VERIFY(vf) ; - - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; -} ; - -/*------------------------------------------------------------------------------ - * 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 ! - * - * 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. - * - * That is done here, but may be worth updating get_end before testing - * against get_ptr. - * - * Returns: true <=> at least one byte in FIFO. - * - * NB: if finds that the FIFO is empty, resets the pointers to the start - * of the last lump -- does not release the last lump. - */ -static bool -vio_fifo_get_next_lump(vio_fifo vf) +vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - assert(vf->get_ptr == vf->get_end) ; - - head = ddl_head(vf->base) ; /* current lump for get */ - - /* 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) + if (vff->hold_mark) { - assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; - - if (vf->get_ptr < vf->put_ptr) + if (vff->set) { - /* Had an out of date vf->get_end */ - vf->get_end = vf->put_ptr ; + vio_fifo_set_get_ptr(vff, vff->hold_ptr, ddl_head(vff->base)) ; + /* Set back to hold position */ - return true ; /* FIFO not empty */ - } ; - - assert(vf->get_ptr == vf->put_ptr) ; - - /* FIFO is empty -- reset pointers and exit */ - vio_fifo_ptr_reset(vf, head) ; - - return false ; /* FIFO empty */ - } ; + vff->end_mark = mark ; /* new state */ + if (!mark) + vff->hold_ptr = NULL ; /* clear if required */ - /* Release the head and update pointers - * - * Deals with possibility that nothing has yet been allocated - */ - vio_fifo_lump_release(vf, head) ; + vio_fifo_sync_get(vff) ; /* to be absolutely sure ! */ + } ; - return (vf->get_ptr < vf->get_end) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else if (mark) + vio_fifo_set_end_mark(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 ; - 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 nothing allocated, should all be NULL & !vff->set */ /* If something allocated, tail must not be NULL */ if (head == NULL) { 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) ) + || (vff->set) + || (vff->as_one) + || (vff->hold_ptr != NULL) + || (vff->get_lump != NULL) + || (vff->get_ptr != NULL) + || (vff->get_end != NULL) + || (vff->end_lump != NULL) + || (vff->end_end != NULL) + || (vff->put_ptr != NULL) + || (vff->put_end != NULL) ) zabort("nothing allocated, but not all NULL") ; return ; } else { if (tail == NULL) - zabort("head pointer not NULL, but tail pointer is") ; + zabort("head not NULL, but tail is") ; } ; - /* Check that all the pointers are within respective lumps + /* Must now be set ! */ + if (!vff->set) + zabort("head not NULL, but set is false") ; + + /* Make sure that the lump pointers all work * - * Know that put_end is always tail->end, but get_end need not be. + * When finished, know that head <= get_lump <= end_lump <= tail. */ - if ( (tail->data > vf->put_ptr) - || (vf->put_ptr > vf->put_end) - || (vf->put_end != tail->end) ) - zabort("put pointers outside the tail lump") ; + lump = head ; + while (lump != vff->get_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for get_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") ; + while (lump != vff->end_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for end_lump") ; + } ; - /* If head == tail, should be vf->one, etc. */ - if (head == tail) + while (lump != tail) { - if (!vf->one) - zabort("have one lump, but !vf->one") ; + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for tail") ; + } ; - if (vf->get_end > vf->put_ptr) - zabort("get_end is greater than put_ptr when vf->one") ; + /* 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_end <= put_ptr or tail != end_lump + */ + if (vff->hold_mark) + { + if ( (head->data > vff->hold_ptr) + || (vff->hold_ptr > head->end) ) + zabort("hold pointer outside the head lump") ; + + if ((vff->get_lump == head) && (vff->hold_ptr > vff->get_ptr)) + zabort("hold pointer greater than get 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->hold_ptr != NULL) + zabort("no hold_mark, but hold pointer not NULL") ; + if (vff->get_lump != head) + zabort("no hold_mark, but get_lump is not head") ; } ; - /* If have an rdr_lump -- make sure everything else is valid */ - if (vf->rdr_lump != NULL) + if ( (vff->get_lump->data > vff->get_ptr) + || (vff->get_ptr > vff->get_end) + || (vff->get_end > vff->get_lump->end)) + zabort("get pointers outside the get lump") ; + + if (vff->end_mark) { - lump = head ; - while (lump != vf->rdr_lump) - { - if (lump == tail) - zabort("rdr_lump is not part of FIFO") ; - lump = ddl_next(lump, list) ; - } ; + if ( (vff->end_lump->data > vff->end_end) + || (vff->end_end > vff->end_lump->end) ) + zabort("end pointer outside the end lump") ; - if ( (lump->data > vf->rdr_ptr) - || (vf->rdr_ptr > lump->end) ) - zabort("rdr_ptr outside its lump") ; + if ((vff->end_lump == tail) && (vff->end_end > vff->put_ptr)) + zabort("end pointer greater than put pointer") ; + } + else + { + if (vff->end_end != NULL) + zabort("no end_mark, but end end not NULL") ; + if (vff->end_lump != tail) + zabort("no end_mark, but end_lump is not tail") ; + } ; - if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) - zabort("rdr_ptr is less than get_ptr in first lump") ; + if ( (tail->data > vff->put_ptr) + || (vff->put_ptr > vff->put_end) + || (vff->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; - if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) - zabort("rdr_ptr is greater than put_ptr in last lump") ; + /* The as_one state & get_end + */ + if (vff->get_lump != vff->end_lump) + { + if (vff->as_one) + zabort("get_lump != end_lump, but as_one true") ; + if (vff->get_end != vff->get_lump->end) + zabort("get_lump != end_lump, but get_end != get_lump->end") ; } - else + else if (vff->end_mark) { - if (vf->rdr_ptr != NULL) - zabort("rdr_ptr not NULL when rdr_lump is") ; + if (vff->as_one) + zabort("end_mark true, but as_one also true") ; + if (vff->get_end != vff->end_end) + zabort("get_lump == end_lump and end_mark, but get_end != end_end") ; } + else + { + if (!vff->as_one) + zabort("get_lump == end_lump and !end_mark, but as_one not true") ; + if (vff->get_end > vff->put_ptr) + zabort("is as_one, but get_end > put_ptr") ; + } ; } ; |