summaryrefslogtreecommitdiffstats
path: root/libtecla-1.6.3/chrqueue.c
diff options
context:
space:
mode:
Diffstat (limited to 'libtecla-1.6.3/chrqueue.c')
-rw-r--r--libtecla-1.6.3/chrqueue.c432
1 files changed, 432 insertions, 0 deletions
diff --git a/libtecla-1.6.3/chrqueue.c b/libtecla-1.6.3/chrqueue.c
new file mode 100644
index 0000000..a1d1d80
--- /dev/null
+++ b/libtecla-1.6.3/chrqueue.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 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";
+}