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