diff options
Diffstat (limited to 'libtecla-1.6.1/chrqueue.c')
-rw-r--r-- | libtecla-1.6.1/chrqueue.c | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/libtecla-1.6.1/chrqueue.c b/libtecla-1.6.1/chrqueue.c new file mode 100644 index 0000000..346e8f6 --- /dev/null +++ b/libtecla-1.6.1/chrqueue.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL + * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder + * shall not be used in advertising or otherwise to promote the sale, use + * or other dealings in this Software without prior written authorization + * of the copyright holder. + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "ioutil.h" +#include "chrqueue.h" +#include "freelist.h" +#include "errmsg.h" + +/* + * Set the number of bytes allocated to each node of the list of + * character buffers. This facility is designed principally as + * an expandible I/O output buffer, so use the stdio buffer size + * where available. + */ +#ifdef BUFSIZ +#define GL_CQ_SIZE BUFSIZ +#else +#define GL_CQ_SIZE 512 +#endif + +/* + * The queue is contained in a list of fixed sized buffers. New nodes + * are appended to this list as needed to accomodate newly added bytes. + * Old nodes at the head of the list are removed as they are emptied. + */ +typedef struct CqCharBuff CqCharBuff; +struct CqCharBuff { + CqCharBuff *next; /* The next node in the list of buffers */ + char bytes[GL_CQ_SIZE]; /* The fixed size buffer of this node */ +}; + +/* + * Define the structure that is used to contain a list of character + * buffers. + */ +struct GlCharQueue { + ErrMsg *err; /* A buffer in which to record error messages */ + FreeList *bufmem; /* A free-list of CqCharBuff structures */ + struct { + CqCharBuff *head; /* The head of the list of output buffers */ + CqCharBuff *tail; /* The tail of the list of output buffers */ + } buffers; + int nflush; /* The total number of characters that have been */ + /* flushed from the start of the queue since */ + /* _glq_empty_queue() was last called. */ + int ntotal; /* The total number of characters that have been */ + /* appended to the queue since _glq_empty_queue() */ + /* was last called. */ +}; + +/*....................................................................... + * Create a new GlCharQueue object. + * + * Output: + * return GlCharQueue * The new object, or NULL on error. + */ +GlCharQueue *_new_GlCharQueue(void) +{ + GlCharQueue *cq; /* The object to be returned */ +/* + * Allocate the container. + */ + cq = malloc(sizeof(GlCharQueue)); + if(!cq) { + errno = ENOMEM; + return NULL; + }; +/* + * Before attempting any operation that might fail, initialize the + * container at least up to the point at which it can safely be passed + * to del_GlCharQueue(). + */ + cq->err = NULL; + cq->bufmem = NULL; + cq->buffers.head = NULL; + cq->buffers.tail = NULL; + cq->nflush = cq->ntotal = 0; +/* + * Allocate a place to record error messages. + */ + cq->err = _new_ErrMsg(); + if(!cq->err) + return _del_GlCharQueue(cq); +/* + * Allocate the freelist of CqCharBuff structures. + */ + cq->bufmem = _new_FreeList(sizeof(CqCharBuff), 1); + if(!cq->bufmem) + return _del_GlCharQueue(cq); + return cq; +} + +/*....................................................................... + * Delete a GlCharQueue object. + * + * Input: + * cq GlCharQueue * The object to be deleted. + * Output: + * return GlCharQueue * The deleted object (always NULL). + */ +GlCharQueue *_del_GlCharQueue(GlCharQueue *cq) +{ + if(cq) { + cq->err = _del_ErrMsg(cq->err); + cq->bufmem = _del_FreeList(cq->bufmem, 1); + free(cq); + }; + return NULL; +} + +/*....................................................................... + * Append an array of n characters to a character queue. + * + * Input: + * cq GlCharQueue * The queue to append to. + * chars const char * The array of n characters to be appended. + * n int The number of characters in chars[]. + * write_fn GL_WRITE_FN * The function to call to output characters, + * or 0 to simply discard the contents of the + * queue. This will be called whenever the + * buffer becomes full. If it fails to release + * any space, the buffer will be extended. + * data void * Anonymous data to pass to write_fn(). + * Output: + * return int The number of characters successfully + * appended. This will only be < n on error. + */ +int _glq_append_chars(GlCharQueue *cq, const char *chars, int n, + GlWriteFn *write_fn, void *data) +{ + int ndone = 0; /* The number of characters appended so far */ +/* + * Check the arguments. + */ + if(!cq || !chars) { + errno = EINVAL; + return 0; + }; +/* + * The appended characters may have to be split between multiple + * buffers, so loop for each buffer. + */ + while(ndone < n) { + int ntodo; /* The number of characters remaining to be appended */ + int nleft; /* The amount of space remaining in cq->buffers.tail */ + int nnew; /* The number of characters to append to cq->buffers.tail */ +/* + * Compute the offset at which the next character should be written + * into the tail buffer segment. + */ + int boff = cq->ntotal % GL_CQ_SIZE; +/* + * Since we don't allocate a new buffer until we have at least one + * character to write into it, if boff is 0 at this point, it means + * that we hit the end of the tail buffer segment on the last append, + * so we need to allocate a new one. + * + * If allocating this new node will require a call to malloc(), as + * opposed to using a currently unused node in the freelist, first try + * flushing the current contents of the buffer to the terminal. When + * write_fn() uses blocking I/O, this stops the buffer size ever getting + * bigger than a single buffer node. When it is non-blocking, it helps + * to keep the amount of memory, but it isn't gauranteed to do so. + */ + if(boff == 0 && _idle_FreeListNodes(cq->bufmem) == 0) { + switch(_glq_flush_queue(cq, write_fn, data)) { + case GLQ_FLUSH_DONE: + break; + case GLQ_FLUSH_AGAIN: + errno = 0; /* Don't confuse the caller */ + break; + default: + return ndone; /* Error */ + }; + boff = cq->ntotal % GL_CQ_SIZE; + }; +/* + * Since we don't allocate a new buffer until we have at least one + * character to write into it, if boff is 0 at this point, it means + * that we hit the end of the tail buffer segment on the last append, + * so we need to allocate a new one. + */ + if(boff == 0) { +/* + * Allocate the new node. + */ + CqCharBuff *node = (CqCharBuff *) _new_FreeListNode(cq->bufmem); + if(!node) { + _err_record_msg(cq->err, "Insufficient memory to buffer output.", + END_ERR_MSG); + return ndone; + }; +/* + * Initialize the node. + */ + node->next = NULL; +/* + * Append the new node to the tail of the list. + */ + if(cq->buffers.tail) + cq->buffers.tail->next = node; + else + cq->buffers.head = node; + cq->buffers.tail = node; + }; +/* + * How much room is there for new characters in the current tail node? + */ + nleft = GL_CQ_SIZE - boff; +/* + * How many characters remain to be appended? + */ + ntodo = n - ndone; +/* + * How many characters should we append to the current tail node? + */ + nnew = nleft < ntodo ? nleft : ntodo; +/* + * Append the latest prefix of nnew characters. + */ + memcpy(cq->buffers.tail->bytes + boff, chars + ndone, nnew); + cq->ntotal += nnew; + ndone += nnew; + }; +/* + * Return the count of the number of characters successfully appended. + */ + return ndone; +} + +/*....................................................................... + * Discard the contents of a queue of characters. + * + * Input: + * cq GlCharQueue * The queue to clear. + */ +void _glq_empty_queue(GlCharQueue *cq) +{ + if(cq) { +/* + * Return all list nodes to their respective free-lists. + */ + _rst_FreeList(cq->bufmem); +/* + * Mark the lists as empty. + */ + cq->buffers.head = cq->buffers.tail = NULL; + cq->nflush = cq->ntotal = 0; + }; +} + +/*....................................................................... + * Return a count of the number of characters currently in the queue. + * + * Input: + * cq GlCharQueue * The queue of interest. + * Output: + * return int The number of characters in the queue. + */ +int _glq_char_count(GlCharQueue *cq) +{ + return (cq && cq->buffers.head) ? (cq->ntotal - cq->nflush) : 0; +} + +/*....................................................................... + * Write as many characters as possible from the start of a character + * queue via a given output callback function, removing those written + * from the queue. + * + * Input: + * cq GlCharQueue * The queue to write characters from. + * write_fn GL_WRITE_FN * The function to call to output characters, + * or 0 to simply discard the contents of the + * queue. + * data void * Anonymous data to pass to write_fn(). + * Output: + * return GlFlushState The status of the flush operation: + * GLQ_FLUSH_DONE - The flush operation + * completed successfully. + * GLQ_FLUSH_AGAIN - The flush operation + * couldn't be completed + * on this call. Call this + * function again when the + * output channel can accept + * further output. + * GLQ_FLUSH_ERROR Unrecoverable error. + */ +GlqFlushState _glq_flush_queue(GlCharQueue *cq, GlWriteFn *write_fn, + void *data) +{ +/* + * Check the arguments. + */ + if(!cq) { + errno = EINVAL; + return GLQ_FLUSH_ERROR; + }; +/* + * If possible keep writing until all of the chained buffers have been + * emptied and removed from the list. + */ + while(cq->buffers.head) { +/* + * Are we looking at the only node in the list? + */ + int is_tail = cq->buffers.head == cq->buffers.tail; +/* + * How many characters more than an exact multiple of the buffer-segment + * size have been added to the buffer so far? + */ + int nmodulo = cq->ntotal % GL_CQ_SIZE; +/* + * How many characters of the buffer segment at the head of the list + * have been used? Note that this includes any characters that have + * already been flushed. Also note that if nmodulo==0, this means that + * the tail buffer segment is full. The reason for this is that we + * don't allocate new tail buffer segments until there is at least one + * character to be added to them. + */ + int nhead = (!is_tail || nmodulo == 0) ? GL_CQ_SIZE : nmodulo; +/* + * How many characters remain to be flushed from the buffer + * at the head of the list? + */ + int nbuff = nhead - (cq->nflush % GL_CQ_SIZE); +/* + * Attempt to write this number. + */ + int nnew = write_fn(data, cq->buffers.head->bytes + + cq->nflush % GL_CQ_SIZE, nbuff); +/* + * Was anything written? + */ + if(nnew > 0) { +/* + * Increment the count of the number of characters that have + * been flushed from the head of the queue. + */ + cq->nflush += nnew; +/* + * If we succeded in writing all of the contents of the current + * buffer segment, remove it from the queue. + */ + if(nnew == nbuff) { +/* + * If we just emptied the last node left in the list, then the queue is + * now empty and should be reset. + */ + if(is_tail) { + _glq_empty_queue(cq); + } else { +/* + * Get the node to be removed from the head of the list. + */ + CqCharBuff *node = cq->buffers.head; +/* + * Make the node that follows it the new head of the queue. + */ + cq->buffers.head = node->next; +/* + * Return it to the freelist. + */ + node = (CqCharBuff *) _del_FreeListNode(cq->bufmem, node); + }; + }; +/* + * If the write blocked, request that this function be called again + * when space to write next becomes available. + */ + } else if(nnew==0) { + return GLQ_FLUSH_AGAIN; +/* + * I/O error. + */ + } else { + _err_record_msg(cq->err, "Error writing to terminal", END_ERR_MSG); + return GLQ_FLUSH_ERROR; + }; + }; +/* + * To get here the queue must now be empty. + */ + return GLQ_FLUSH_DONE; +} + +/*....................................................................... + * Return extra information (ie. in addition to that provided by errno) + * about the last error to occur in any of the public functions of this + * module. + * + * Input: + * cq GlCharQueue * The container of the history list. + * Output: + * return const char * A pointer to the internal buffer in which + * the error message is temporarily stored. + */ +const char *_glq_last_error(GlCharQueue *cq) +{ + return cq ? _err_get_msg(cq->err) : "NULL GlCharQueue argument"; +} |