/* * 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 #include #include #include #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"; }