xref: /illumos-gate/usr/src/lib/libtecla/common/chrqueue.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
1 /*
2  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3  *
4  * All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, and/or sell copies of the Software, and to permit persons
11  * to whom the Software is furnished to do so, provided that the above
12  * copyright notice(s) and this permission notice appear in all copies of
13  * the Software and that both the above copyright notice(s) and this
14  * permission notice appear in supporting documentation.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  *
26  * Except as contained in this notice, the name of a copyright holder
27  * shall not be used in advertising or otherwise to promote the sale, use
28  * or other dealings in this Software without prior written authorization
29  * of the copyright holder.
30  */
31 
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <errno.h>
36 
37 #include "ioutil.h"
38 #include "chrqueue.h"
39 #include "freelist.h"
40 #include "errmsg.h"
41 
42 /*
43  * Set the number of bytes allocated to each node of the list of
44  * character buffers. This facility is designed principally as
45  * an expandible I/O output buffer, so use the stdio buffer size
46  * where available.
47  */
48 #ifdef BUFSIZ
49 #define GL_CQ_SIZE BUFSIZ
50 #else
51 #define GL_CQ_SIZE 512
52 #endif
53 
54 /*
55  * The queue is contained in a list of fixed sized buffers. New nodes
56  * are appended to this list as needed to accomodate newly added bytes.
57  * Old nodes at the head of the list are removed as they are emptied.
58  */
59 typedef struct CqCharBuff CqCharBuff;
60 struct CqCharBuff {
61   CqCharBuff *next;          /* The next node in the list of buffers */
62   char bytes[GL_CQ_SIZE];    /* The fixed size buffer of this node */
63 };
64 
65 /*
66  * Define the structure that is used to contain a list of character
67  * buffers.
68  */
69 struct GlCharQueue {
70   ErrMsg *err;          /* A buffer in which to record error messages */
71   FreeList *bufmem;     /* A free-list of CqCharBuff structures */
72   struct {
73     CqCharBuff *head;   /* The head of the list of output buffers */
74     CqCharBuff *tail;   /* The tail of the list of output buffers */
75   } buffers;
76   int nflush;           /* The total number of characters that have been */
77                         /*  flushed from the start of the queue since */
78                         /*  _glq_empty_queue() was last called. */
79   int ntotal;           /* The total number of characters that have been */
80                         /*  appended to the queue since _glq_empty_queue() */
81                         /*  was last called. */
82 };
83 
84 /*.......................................................................
85  * Create a new GlCharQueue object.
86  *
87  * Output:
88  *  return  GlCharQueue *  The new object, or NULL on error.
89  */
90 GlCharQueue *_new_GlCharQueue(void)
91 {
92   GlCharQueue *cq;  /* The object to be returned */
93 /*
94  * Allocate the container.
95  */
96   cq = malloc(sizeof(GlCharQueue));
97   if(!cq) {
98     errno = ENOMEM;
99     return NULL;
100   };
101 /*
102  * Before attempting any operation that might fail, initialize the
103  * container at least up to the point at which it can safely be passed
104  * to del_GlCharQueue().
105  */
106   cq->err = NULL;
107   cq->bufmem = NULL;
108   cq->buffers.head = NULL;
109   cq->buffers.tail = NULL;
110   cq->nflush = cq->ntotal = 0;
111 /*
112  * Allocate a place to record error messages.
113  */
114   cq->err = _new_ErrMsg();
115   if(!cq->err)
116     return _del_GlCharQueue(cq);
117 /*
118  * Allocate the freelist of CqCharBuff structures.
119  */
120   cq->bufmem = _new_FreeList(sizeof(CqCharBuff), 1);
121   if(!cq->bufmem)
122     return _del_GlCharQueue(cq);
123   return cq;
124 }
125 
126 /*.......................................................................
127  * Delete a GlCharQueue object.
128  *
129  * Input:
130  *  cq     GlCharQueue *  The object to be deleted.
131  * Output:
132  *  return GlCharQueue *  The deleted object (always NULL).
133  */
134 GlCharQueue *_del_GlCharQueue(GlCharQueue *cq)
135 {
136   if(cq) {
137     cq->err = _del_ErrMsg(cq->err);
138     cq->bufmem = _del_FreeList(cq->bufmem, 1);
139     free(cq);
140   };
141   return NULL;
142 }
143 
144 /*.......................................................................
145  * Append an array of n characters to a character queue.
146  *
147  * Input:
148  *  cq        GlCharQueue *  The queue to append to.
149  *  chars      const char *  The array of n characters to be appended.
150  *  n                 int    The number of characters in chars[].
151  *  write_fn  GL_WRITE_FN *  The function to call to output characters,
152  *                           or 0 to simply discard the contents of the
153  *                           queue. This will be called whenever the
154  *                           buffer becomes full. If it fails to release
155  *                           any space, the buffer will be extended.
156  *  data             void *  Anonymous data to pass to write_fn().
157  * Output:
158  *  return        int    The number of characters successfully
159  *                       appended. This will only be < n on error.
160  */
161 int _glq_append_chars(GlCharQueue *cq, const char *chars, int n,
162 		      GlWriteFn *write_fn, void *data)
163 {
164   int ndone = 0;  /* The number of characters appended so far */
165 /*
166  * Check the arguments.
167  */
168   if(!cq || !chars) {
169     errno = EINVAL;
170     return 0;
171   };
172 /*
173  * The appended characters may have to be split between multiple
174  * buffers, so loop for each buffer.
175  */
176   while(ndone < n) {
177     int ntodo;     /* The number of characters remaining to be appended */
178     int nleft;     /* The amount of space remaining in cq->buffers.tail */
179     int nnew;      /* The number of characters to append to cq->buffers.tail */
180 /*
181  * Compute the offset at which the next character should be written
182  * into the tail buffer segment.
183  */
184     int boff = cq->ntotal % GL_CQ_SIZE;
185 /*
186  * Since we don't allocate a new buffer until we have at least one
187  * character to write into it, if boff is 0 at this point, it means
188  * that we hit the end of the tail buffer segment on the last append,
189  * so we need to allocate a new one.
190  *
191  * If allocating this new node will require a call to malloc(), as
192  * opposed to using a currently unused node in the freelist, first try
193  * flushing the current contents of the buffer to the terminal. When
194  * write_fn() uses blocking I/O, this stops the buffer size ever getting
195  * bigger than a single buffer node. When it is non-blocking, it helps
196  * to keep the amount of memory, but it isn't gauranteed to do so.
197  */
198     if(boff == 0 && _idle_FreeListNodes(cq->bufmem) == 0) {
199       switch(_glq_flush_queue(cq, write_fn, data)) {
200       case GLQ_FLUSH_DONE:
201 	break;
202       case GLQ_FLUSH_AGAIN:
203 	errno = 0;          /* Don't confuse the caller */
204 	break;
205       default:
206 	return ndone;       /* Error */
207       };
208       boff = cq->ntotal % GL_CQ_SIZE;
209     };
210 /*
211  * Since we don't allocate a new buffer until we have at least one
212  * character to write into it, if boff is 0 at this point, it means
213  * that we hit the end of the tail buffer segment on the last append,
214  * so we need to allocate a new one.
215  */
216     if(boff == 0) {
217 /*
218  * Allocate the new node.
219  */
220       CqCharBuff *node = (CqCharBuff *) _new_FreeListNode(cq->bufmem);
221       if(!node) {
222 	_err_record_msg(cq->err, "Insufficient memory to buffer output.",
223 			END_ERR_MSG);
224 	return ndone;
225       };
226 /*
227  * Initialize the node.
228  */
229       node->next = NULL;
230 /*
231  * Append the new node to the tail of the list.
232  */
233       if(cq->buffers.tail)
234 	cq->buffers.tail->next = node;
235       else
236 	cq->buffers.head = node;
237       cq->buffers.tail = node;
238     };
239 /*
240  * How much room is there for new characters in the current tail node?
241  */
242     nleft = GL_CQ_SIZE - boff;
243 /*
244  * How many characters remain to be appended?
245  */
246     ntodo = n - ndone;
247 /*
248  * How many characters should we append to the current tail node?
249  */
250     nnew = nleft < ntodo ? nleft : ntodo;
251 /*
252  * Append the latest prefix of nnew characters.
253  */
254     memcpy(cq->buffers.tail->bytes + boff, chars + ndone, nnew);
255     cq->ntotal += nnew;
256     ndone += nnew;
257   };
258 /*
259  * Return the count of the number of characters successfully appended.
260  */
261   return ndone;
262 }
263 
264 /*.......................................................................
265  * Discard the contents of a queue of characters.
266  *
267  * Input:
268  *  cq    GlCharQueue *  The queue to clear.
269  */
270 void _glq_empty_queue(GlCharQueue *cq)
271 {
272   if(cq) {
273 /*
274  * Return all list nodes to their respective free-lists.
275  */
276     _rst_FreeList(cq->bufmem);
277 /*
278  * Mark the lists as empty.
279  */
280     cq->buffers.head = cq->buffers.tail = NULL;
281     cq->nflush = cq->ntotal = 0;
282   };
283 }
284 
285 /*.......................................................................
286  * Return a count of the number of characters currently in the queue.
287  *
288  * Input:
289  *  cq    GlCharQueue *  The queue of interest.
290  * Output:
291  *  return        int    The number of characters in the queue.
292  */
293 int _glq_char_count(GlCharQueue *cq)
294 {
295   return (cq && cq->buffers.head) ? (cq->ntotal - cq->nflush) : 0;
296 }
297 
298 /*.......................................................................
299  * Write as many characters as possible from the start of a character
300  * queue via a given output callback function, removing those written
301  * from the queue.
302  *
303  * Input:
304  *  cq        GlCharQueue *  The queue to write characters from.
305  *  write_fn  GL_WRITE_FN *  The function to call to output characters,
306  *                           or 0 to simply discard the contents of the
307  *                           queue.
308  *  data             void *  Anonymous data to pass to write_fn().
309  * Output:
310  *  return   GlFlushState    The status of the flush operation:
311  *                             GLQ_FLUSH_DONE  -  The flush operation
312  *                                                completed successfully.
313  *                             GLQ_FLUSH_AGAIN -  The flush operation
314  *                                                couldn't be completed
315  *                                                on this call. Call this
316  *                                                function again when the
317  *                                                output channel can accept
318  *                                                further output.
319  *                             GLQ_FLUSH_ERROR    Unrecoverable error.
320  */
321 GlqFlushState _glq_flush_queue(GlCharQueue *cq, GlWriteFn *write_fn,
322 			       void *data)
323 {
324 /*
325  * Check the arguments.
326  */
327   if(!cq) {
328     errno = EINVAL;
329     return GLQ_FLUSH_ERROR;
330   };
331 /*
332  * If possible keep writing until all of the chained buffers have been
333  * emptied and removed from the list.
334  */
335   while(cq->buffers.head) {
336 /*
337  * Are we looking at the only node in the list?
338  */
339     int is_tail = cq->buffers.head == cq->buffers.tail;
340 /*
341  * How many characters more than an exact multiple of the buffer-segment
342  * size have been added to the buffer so far?
343  */
344     int nmodulo = cq->ntotal % GL_CQ_SIZE;
345 /*
346  * How many characters of the buffer segment at the head of the list
347  * have been used? Note that this includes any characters that have
348  * already been flushed. Also note that if nmodulo==0, this means that
349  * the tail buffer segment is full. The reason for this is that we
350  * don't allocate new tail buffer segments until there is at least one
351  * character to be added to them.
352  */
353     int nhead = (!is_tail || nmodulo == 0) ? GL_CQ_SIZE : nmodulo;
354 /*
355  * How many characters remain to be flushed from the buffer
356  * at the head of the list?
357  */
358     int nbuff = nhead - (cq->nflush % GL_CQ_SIZE);
359 /*
360  * Attempt to write this number.
361  */
362     int nnew = write_fn(data, cq->buffers.head->bytes +
363 			cq->nflush % GL_CQ_SIZE, nbuff);
364 /*
365  * Was anything written?
366  */
367     if(nnew > 0) {
368 /*
369  * Increment the count of the number of characters that have
370  * been flushed from the head of the queue.
371  */
372       cq->nflush += nnew;
373 /*
374  * If we succeded in writing all of the contents of the current
375  * buffer segment, remove it from the queue.
376  */
377       if(nnew == nbuff) {
378 /*
379  * If we just emptied the last node left in the list, then the queue is
380  * now empty and should be reset.
381  */
382 	if(is_tail) {
383 	  _glq_empty_queue(cq);
384 	} else {
385 /*
386  * Get the node to be removed from the head of the list.
387  */
388 	  CqCharBuff *node = cq->buffers.head;
389 /*
390  * Make the node that follows it the new head of the queue.
391  */
392 	  cq->buffers.head = node->next;
393 /*
394  * Return it to the freelist.
395  */
396 	  node = (CqCharBuff *) _del_FreeListNode(cq->bufmem, node);
397 	};
398       };
399 /*
400  * If the write blocked, request that this function be called again
401  * when space to write next becomes available.
402  */
403     } else if(nnew==0) {
404       return GLQ_FLUSH_AGAIN;
405 /*
406  * I/O error.
407  */
408     } else {
409       _err_record_msg(cq->err, "Error writing to terminal", END_ERR_MSG);
410       return GLQ_FLUSH_ERROR;
411     };
412   };
413 /*
414  * To get here the queue must now be empty.
415  */
416   return GLQ_FLUSH_DONE;
417 }
418 
419 /*.......................................................................
420  * Return extra information (ie. in addition to that provided by errno)
421  * about the last error to occur in any of the public functions of this
422  * module.
423  *
424  * Input:
425  *  cq     GlCharQueue *  The container of the history list.
426  * Output:
427  *  return  const char *  A pointer to the internal buffer in which
428  *                        the error message is temporarily stored.
429  */
430 const char *_glq_last_error(GlCharQueue *cq)
431 {
432   return cq ? _err_get_msg(cq->err) : "NULL GlCharQueue argument";
433 }
434