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