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