summaryrefslogtreecommitdiffstats
path: root/lib/vio_fifo.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vio_fifo.c')
-rw-r--r--lib/vio_fifo.c1165
1 files changed, 1165 insertions, 0 deletions
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c
new file mode 100644
index 00000000..685f33ec
--- /dev/null
+++ b/lib/vio_fifo.c
@@ -0,0 +1,1165 @@
+/* VTY I/O FIFO
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stddef.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.
+ *
+ * The FIFO is arranged as lumps of some given size. Lumps are allocated
+ * as and when necessary, and released once emptied.
+ *
+ * The last lump is never released. So, it may be that only one lump is
+ * 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.
+ *
+ * Once a lump has been allocated there is always one lump in the FIFO.
+ *
+ * The following are expected to be true:
+ *
+ * * put_ptr == get_ptr => FIFO empty
+ *
+ * * put_ptr == 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_ptr >= head->data ) otherwise something is broken
+ * get_ptr <= head->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
+ *
+ * Note that:
+ *
+ * * when the get_ptr reaches the put_ptr the pointers are reset to the
+ * start of the one and only lump.
+ *
+ * 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.
+ *
+ * * when advancing the put_ptr does not check for advancing the get_end.
+ *
+ * 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.
+ *
+ * Everywhere that the get_end is used, must check for there being one
+ * lump and the possibility that put_ptr has changed.
+ */
+
+/*==============================================================================
+ * Initialisation, allocation and freeing of FIFO and lumps thereof.
+ */
+
+/* Return default size, or given size rounded up to 16 byte boundary */
+static size_t
+vio_fifo_size(size_t size)
+{
+ if (size == 0)
+ return 4096 ;
+ else
+ return ((size + 16 - 1) / 16) * 16 ;
+} ;
+
+/*==============================================================================
+ * Initialise VTY I/O FIFO -- allocating if required.
+ */
+extern vio_fifo
+vio_fifo_init_new(vio_fifo vf, size_t size)
+{
+ 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 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Free contents of given FIFO, and free FIFO structure as well, if required.
+ *
+ * Does nothing if given a NULL pointer -- must already have been freed !
+ *
+ * If does not free the FIFO structure, resets it all empty.
+ *
+ * Frees *all* FIFO lumps.
+ *
+ * See also: vio_fifo_reset_keep(vio_fifo)
+ * vio_fifo_reset_free(vio_fifo)
+ */
+extern vio_fifo
+vio_fifo_reset(vio_fifo vf, int free_structure)
+{
+ vio_fifo_lump lump ;
+
+ if (vf == NULL)
+ return NULL ;
+
+ while (ddl_pop(&lump, vf->base, list) != NULL)
+ XFREE(MTYPE_VIO_FIFO_LUMP, lump) ;
+
+ if (vf->spare != NULL)
+ XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ;
+
+ if (free_structure)
+ XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */
+ else
+ vio_fifo_init_new(vf, vf->size) ;
+
+ return vf ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * The FIFO is empty, with one lump -- reset all pointers.
+ */
+inline static void
+vio_fifo_ptr_reset(vio_fifo vf, 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 ;
+ } ;
+
+ /* 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 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * The FIFO is utterly empty, with ZERO lumps -- unset all pointers.
+ */
+inline static void
+vio_fifo_ptr_unset(vio_fifo vf)
+{
+ 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 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear out contents of FIFO -- will continue to use the FIFO.
+ *
+ * Keeps one FIFO lump. (Frees everything else, including any spare.)
+ */
+extern void
+vio_fifo_clear(vio_fifo vf)
+{
+ 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) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * See how much room there is in the FIFO.
+ *
+ * If no lumps have been allocated, returns the size of the lump that would
+ * allocate.
+ *
+ * Otherwise, returns the amount of space available *without* allocating any
+ * further lumps.
+ *
+ * Returns: room available as described
+ */
+extern size_t
+vio_fifo_room(vio_fifo vf)
+{
+ if (vf->put_ptr != NULL)
+ return vf->put_end - vf->put_ptr ;
+ else
+ return vf->size ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Allocate another lump for putting into.
+ *
+ * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal.
+ *
+ * Set the put_ptr/put_end pointers to point at the new lump.
+ *
+ * If this is the first lump allocated, set the get_ptr/get_end pointers too.
+ *
+ * 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.
+ */
+extern void
+vio_fifo_lump_new(vio_fifo vf, size_t size)
+{
+ vio_fifo_lump lump ;
+ int first_alloc ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */
+
+ if (vf->one)
+ vf->get_end = vf->put_ptr ; /* update get_end */
+
+ lump = ddl_tail(vf->base) ;
+
+ first_alloc = (lump == NULL) ; /* extra initialisation needed */
+
+ 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 */
+
+ size = vio_fifo_size(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 ;
+ } ;
+
+ 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) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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)
+ {
+ vio_fifo_lump keep ;
+
+ /* Consistency checks */
+ if (release_head)
+ {
+ if (release_tail)
+ assert(vf->get_ptr == vf->put_ptr) ;
+ else
+ assert(vf->get_ptr == lump->end) ;
+
+ if (vf->rdr_lump == lump)
+ assert(vf->rdr_ptr == vf->get_ptr) ;
+ }
+ else if (release_tail)
+ {
+ assert(vf->put_ptr == lump->data) ;
+
+ if (vf->rdr_lump == lump)
+ assert(vf->rdr_ptr == vf->put_ptr) ;
+ } ;
+
+ /* 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 ((keep == NULL) || (keep->size < lump->size))
+ {
+ keep = lump ;
+ free = vf->spare ;
+ } ;
+
+ vf->spare = keep ;
+
+ head = ddl_head(vf->base) ; /* changed if released head */
+ tail = ddl_tail(vf->base) ; /* changed if released tail */
+ } ;
+
+ /* 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_ptr_unset(vf) ;
+ }
+ else
+ {
+ /* Have at least one lump left -- so must have had at least two ! */
+ assert(!vf->one) ;
+
+ vf->one = (head == tail) ; /* update */
+
+ 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 ;
+ } ;
+ } ;
+ } ;
+
+ /* Finally, free any lump that is actually to be freed */
+
+ if (free != NULL)
+ XFREE(MTYPE_VIO_FIFO_LUMP, free) ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Re-allocate lump for putting into.
+ *
+ * Call when vf->put_ptr == start of last lump, and that lump is not big
+ * enough !
+ *
+ * There must be at least one lump.
+ *
+ * Updates put_ptr/put_end pointers to point at the new lump.
+ *
+ * Updates get_ptr/get_end pointers if required.
+ *
+ * Updates rdr_ptr if required.
+ */
+static void
+vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size)
+{
+ 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) ;
+
+ /* Now allocate a new lump with the required size */
+ vio_fifo_lump_new(vf, size) ;
+
+ /* Restore the rdr_ptr, if required */
+ if (rdr_set)
+ {
+ vio_fifo_lump tail ;
+
+ tail = ddl_tail(vf->base) ;
+
+ vf->rdr_lump = tail ;
+ vf->rdr_ptr = tail->data ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+} ;
+
+/*==============================================================================
+ * Put data to the FIFO.
+ */
+
+/*------------------------------------------------------------------------------
+ * Store 'n' bytes -- allocate new lump if current is exhausted.
+ */
+extern void
+vio_fifo_put(vio_fifo vf, const char* src, size_t n)
+{
+ size_t take ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ while (n > 0)
+ {
+ if (vf->put_ptr >= vf->put_end)
+ vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */
+
+ take = (vf->put_end - vf->put_ptr) ;
+ if (take > n)
+ take = n ;
+
+ memcpy(vf->put_ptr, src, take) ;
+ vf->put_ptr += take ;
+
+ src += take ;
+ n -= take ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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, ...)
+{
+ va_list args;
+ int len ;
+
+ va_start (args, format);
+ len = vio_fifo_vprintf(vf, format, args);
+ va_end (args);
+
+ return len;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Formatted print to fifo -- cf vprintf()
+ *
+ * Returns: >= 0 -- number of bytes written
+ * < 0 -- failed (unlikely though that is)
+ */
+extern int
+vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args)
+{
+ va_list ac ;
+ int len ;
+ int have ;
+ size_t size ;
+ vio_fifo_lump lump ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ 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) ;
+
+ if (len < have)
+ {
+ if (len < 0)
+ break ; /* quit if failed */
+
+ vf->put_ptr += len ;
+ break ; /* done */
+ } ;
+
+ /* 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 */
+
+ lump = ddl_tail(vf->base) ;
+
+ 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 ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ return len ;
+} ;
+
+/*==============================================================================
+ * Get data from the FIFO.
+ */
+
+static bool vio_fifo_get_next_lump(vio_fifo vf) ;
+
+/*------------------------------------------------------------------------------
+ * Get ready to read something out of the FIFO.
+ *
+ * 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.
+ *
+ * Returns: true <=> there is something in the FIFO.
+ */
+static inline bool
+vio_fifo_get_ready(vio_fifo vf)
+{
+ assert(vf->rdr_lump == NULL) ;
+
+ if (vf->one)
+ vf->get_end = vf->put_ptr ; /* make sure have everything */
+
+ if (vf->get_ptr >= vf->get_end)
+ if (!vio_fifo_get_next_lump(vf))
+ return 0 ; /* quit now if nothing there */
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ return 1 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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)
+{
+ size_t have ;
+ void* dst_in ;
+
+ if (!vio_fifo_get_ready(vf))
+ return 0 ; /* quit now if nothing there */
+
+ dst_in = dst ;
+ while (n > 0)
+ {
+ have = vf->get_end - vf->get_ptr ;
+
+ if (have > n)
+ have = n ;
+
+ memcpy(dst, vf->get_ptr, have) ;
+ vf->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) ;
+
+ return (char*)dst - (char*)dst_in ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get byte -- the long winded way.
+ *
+ * See the inline vio_fifo_get_byte().
+ *
+ * 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.
+ *
+ * Returns: 0x00..0xFF -- byte value (as an int)
+ * -1 => FIFO is empty.
+ */
+
+extern int
+vio_fifo_get_next_byte(vio_fifo vf)
+{
+ 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) ;
+
+ return u ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get pointer to a lump 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.
+ *
+ * Returns number of bytes to the end of the current lump. There may be
+ * further lumps beyond the current one.
+ */
+extern void*
+vio_fifo_get_lump(vio_fifo vf, size_t* p_have)
+{
+ if (!vio_fifo_get_ready(vf))
+ {
+ *p_have = 0 ;
+ return NULL ;
+ } ;
+
+ *p_have = (vf->get_end - vf->get_ptr) ;
+ return vf->get_ptr ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Advance FIFO to position reached.
+ *
+ * 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.
+ *
+ * The "here" argument must the the address returned by vio_fifo_get_lump()
+ * plus the number of bytes taken.
+ */
+extern void
+vio_fifo_got_upto(vio_fifo vf, void* here)
+{
+ vf->get_ptr = here ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ if (vf->get_ptr >= vf->get_end)
+ vio_fifo_get_next_lump(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write contents of FIFO -- assuming non-blocking file
+ *
+ * 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
+ *
+ * Note: will work perfectly well for a non-blocking file -- which should
+ * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone".
+ */
+extern int
+vio_fifo_write_nb(vio_fifo vf, int fd, bool all)
+{
+ char* src ;
+ size_t have ;
+ int done ;
+
+ while ((src = vio_fifo_get_lump(vf, &have)) != NULL)
+ {
+ if (!all && vf->one)
+ break ; /* don't write last lump */
+
+ done = write_nb(fd, src, have) ;
+
+ if (done < 0)
+ return -1 ; /* failed */
+
+ vio_fifo_got_upto(vf, src + done) ;
+
+ if (done < (int)have)
+ 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 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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
+ *
+ * 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.
+ *
+ * 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.
+ */
+extern void*
+vio_fifo_get_rdr(vio_fifo vf, size_t* p_have)
+{
+ if (!vio_fifo_get_ready(vf))
+ {
+ *p_have = 0 ;
+ return NULL ;
+ } ;
+
+ if (vf->rdr_lump == NULL) /* set up new rdr if required */
+ {
+ vf->rdr_lump = ddl_head(vf->base) ;
+ vf->rdr_ptr = vf->get_ptr ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ;
+ return vf->rdr_ptr ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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.
+ *
+ * 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".
+ */
+extern void*
+vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step)
+{
+ char* rdr_end ;
+
+ assert(vf->rdr_lump != NULL) ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ rdr_end = vio_fifo_rdr_end(vf) ;
+ vf->rdr_ptr += step ;
+
+ if (vf->rdr_ptr >= rdr_end)
+ {
+ 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 ;
+
+ rdr_end = vio_fifo_rdr_end(vf) ;
+ } ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ *p_have = (rdr_end - vf->rdr_ptr) ;
+ return (*p_have > 0) ? vf->rdr_ptr : NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Move FIFO get position to the rdr position, if any.
+ *
+ * This clears the rdr position, and removes all data between the current and
+ * new get positions from the FIFO.
+ */
+extern void
+vio_fifo_sync_rdr(vio_fifo vf)
+{
+ vio_fifo_lump head ;
+
+ VIO_FIFO_DEBUG_VERIFY(vf) ;
+
+ if (vf->rdr_lump == NULL)
+ return ;
+
+ 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) ;
+ } ;
+
+ vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */
+
+ vf->rdr_lump = NULL ; /* clear the rdr */
+ vf->rdr_ptr = NULL ;
+
+ 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) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Drop the rdr position (if any).
+ *
+ * This clears the rdr position leaving the get position and FIFO unchanged.
+ */
+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_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)
+ {
+ assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ;
+
+ if (vf->get_ptr < vf->put_ptr)
+ {
+ /* Had an out of date vf->get_end */
+ vf->get_end = vf->put_ptr ;
+
+ 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 */
+ } ;
+
+ /* 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) ;
+} ;
+
+/*==============================================================================
+ * For debug purposes -- verify the state of the given FIFO
+ */
+Private void
+vio_fifo_verify(vio_fifo vf)
+{
+ vio_fifo_lump head ;
+ vio_fifo_lump lump ;
+ vio_fifo_lump tail ;
+
+ head = ddl_head(vf->base) ;
+ tail = ddl_tail(vf->base) ;
+
+ /* If nothing allocated, should all be NULL & !vf->one */
+ /* 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) )
+ zabort("nothing allocated, but not all NULL") ;
+ return ;
+ }
+ else
+ {
+ if (tail == NULL)
+ zabort("head pointer not NULL, but tail pointer is") ;
+ } ;
+
+ /* Check that all the pointers are within respective lumps
+ *
+ * Know that put_end is always tail->end, but get_end need not be.
+ */
+ 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 ( (head->data > vf->get_ptr)
+ || (vf->get_ptr > vf->get_end)
+ || (vf->get_end > head->end) )
+ zabort("get pointers outside the head lump") ;
+
+ /* If head == tail, should be vf->one, etc. */
+ if (head == tail)
+ {
+ if (!vf->one)
+ zabort("have one lump, but !vf->one") ;
+
+ if (vf->get_end > vf->put_ptr)
+ zabort("get_end is greater than put_ptr when vf->one") ;
+ }
+ 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 have an rdr_lump -- make sure everything else is valid */
+ if (vf->rdr_lump != NULL)
+ {
+ lump = head ;
+ while (lump != vf->rdr_lump)
+ {
+ if (lump == tail)
+ zabort("rdr_lump is not part of FIFO") ;
+ lump = ddl_next(lump, list) ;
+ } ;
+
+ 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
+ {
+ if (vf->rdr_ptr != NULL)
+ zabort("rdr_ptr not NULL when rdr_lump is") ;
+ }
+} ;