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