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 */
_new_GlCharQueue(void)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 */
_del_GlCharQueue(GlCharQueue * cq)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 */
_glq_append_chars(GlCharQueue * cq,const char * chars,int n,GlWriteFn * write_fn,void * data)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 */
_glq_empty_queue(GlCharQueue * cq)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 */
_glq_char_count(GlCharQueue * cq)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 */
_glq_flush_queue(GlCharQueue * cq,GlWriteFn * write_fn,void * data)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 */
_glq_last_error(GlCharQueue * cq)432 const char *_glq_last_error(GlCharQueue *cq)
433 {
434 return cq ? _err_get_msg(cq->err) : "NULL GlCharQueue argument";
435 }
436