/* $Cambridge: hermes/src/prayer/accountd/buffer.c,v 1.1.1.1 2003/04/15 13:00:02 dpc22 Exp $ */ /************************************************ * Prayer - a Webmail Interface * ************************************************/ /* Copyright (c) University of Cambridge 2000 - 2002 */ /* See the file NOTICE for conditions of use and distribution. */ #include "accountd.h" /* Class for processing arbitary length strings with append facility and * linear search/read access. The name "buffer" is historical, its possible * that this should be called something else today. However I can't really * face renaming several thousand references to "buffer" right now */ /* ====================================================================== */ /* buffer_create() ****************************************************** * * Create a new buffer structure. * pool: Target pool for storage * blocksize: Preferred size for individual allocation blocks. * Typically quite large for IO operations, small if we * have a good feel for like upper bound on size of object. * 0 => Picks default which would appropriate for IO objects * * Returns: New buffer object ***********************************************************************/ struct buffer *buffer_create(struct pool *pool, unsigned long blocksize) { struct buffer *b = pool_alloc(pool, sizeof(struct buffer)); b->size = 0; /* Buffer starts out empty */ b->pool = pool; /* Allocate from this pool */ b->blocksize = (blocksize > 0) ? blocksize : PREFERRED_BUFFER_BLOCK_SIZE; b->first = NIL; b->last = NIL; b->avail = 0; /* Forces allocation */ b->offset = 0; /* State used by read methods */ b->fetch = NIL; b->fetch_avail = 0; return (b); } /* buffer_free() ******************************************************** * * Free buffer including all allocated blocks. NOOP if pool defined. * b: buffer to free ***********************************************************************/ void buffer_free(struct buffer *b) { struct msgblock *current, *next; if (b->pool) /* Noop if data allocated from pool */ return; for (current = b->first; current; current = next) { next = current->next; free(current); } free(b); } /* buffer_size() ******************************************************** * * Returns number of characters currently stored in buffer ***********************************************************************/ unsigned long buffer_size(struct buffer *b) { return (b->size); } /* ====================================================================== */ /* put/extend methods */ /* buffer_add_msgblock() ************************************************ * * Adds a new msgblock (historical name for allocation space) to buffer: * gives us some room to expand. * b: Buffer to extend. ***********************************************************************/ static void buffer_add_msgblock(struct buffer *b) { struct msgblock *mb; mb = pool_alloc(b->pool, sizeof(struct msgblock) + (b->blocksize) - 1); mb->next = NIL; if (b->first) { b->last->next = mb; /* Add msgblock to end of chain */ b->last = mb; } else b->first = b->last = mb; /* First msgblock in chain */ } /* buffer_putchar() ***************************************************** * * Add a single character to the end of the buffer, extending buffer if * required. Typically access via macro bputc(). * b: Buffer * c: Character to add ***********************************************************************/ void buffer_putchar(struct buffer *b, unsigned char c) { /* Space available in current msgblock */ if (b->avail > 0) { b->last->data[b->blocksize - b->avail] = c; b->avail--; b->size++; return; } /* Need to allocate a fresh msgblock */ buffer_add_msgblock(b); b->avail = b->blocksize - 1; b->last->data[0] = c; b->size++; } /* buffer_print_ulong() ************************************************* * * Print number (as decimal represention) at end of buffer. * b: Buffer * value: value ***********************************************************************/ static void buffer_print_ulong(struct buffer *b, unsigned long value) { unsigned long tmp, weight; /* All numbers contain at least one digit. * Find weight of most significant digit. */ for (weight = 1, tmp = value / 10; tmp > 0; tmp /= 10) weight *= 10; for (tmp = value; weight > 0; weight /= 10) { if (value >= weight) { /* Strictly speaking redundant... */ bputc(b, '0' + (value / weight)); /* Digit other than zero */ value -= weight * (value / weight); /* Calculate remainder */ } else bputc(b, '0'); } } /* buffer_print_hex() *************************************************** * * Print number (as hex represention) at end of buffer. * b: Buffer * value: value ***********************************************************************/ static void buffer_print_hex(struct buffer *b, unsigned long value) { unsigned long tmp, weight; /* All numbers contain at least one digit. * Find weight of most significant digit. */ for (weight = 1, tmp = value / 16; tmp > 0; tmp /= 16) weight *= 16; for (tmp = value; weight > 0; weight /= 16) { unsigned long digit = value / weight; unsigned char c = (digit > 9) ? ('a' + (digit - 10)) : ('0' + digit); bputc(b, c); value -= weight * digit; } } /* buffer_vaprintf() **************************************************** * * vaprintf equivalent for buffer * b: Buffer * fmt: vaprintf format string, followed by arguments. ***********************************************************************/ void buffer_vaprintf(struct buffer *b, char *fmt, va_list ap) { unsigned char *s, c; while ((c = *fmt++)) { if (c != '%') { bputc(b, c); } else switch (*fmt++) { case 's': /* string */ if ((s = (unsigned char *) va_arg(ap, char *))) { while ((c = *s++)) bputc(b, c); } else bputs(b, "(nil)"); break; case 'l': if (*fmt == 'u') { buffer_print_ulong(b, va_arg(ap, unsigned long)); fmt++; } else if (*fmt == 'x') { buffer_print_hex(b, va_arg(ap, unsigned long)); fmt++; } else buffer_print_ulong(b, va_arg(ap, long)); break; case 'd': if (*fmt == 'u') { buffer_print_ulong(b, va_arg(ap, unsigned int)); fmt++; } else buffer_print_ulong(b, va_arg(ap, int)); break; case 'c': bputc(b, (unsigned char) va_arg(ap, int)); break; case 'x': buffer_print_hex(b, va_arg(ap, unsigned long)); break; case '%': bputc(b, '%'); break; default: log_fatal("Bad format string to buffer_printf"); } } } /* buffer_printf() ****************************************************** * * printf equivalent for buffer. Typically accessed via bprintf() macro * b: Buffer * fmt: vaprintf format string, followed by arguments. ***********************************************************************/ void buffer_printf(struct buffer *b, char *fmt, ...) { va_list ap; va_start(ap, fmt); buffer_vaprintf(b, fmt, ap); va_end(ap); } /* buffer_printf() ****************************************************** * * puts equivalent for buffer. Typically accessed via bputs() macro * b: Buffer * t: String to print ***********************************************************************/ void buffer_puts(struct buffer *b, char *t) { unsigned char *s = (unsigned char *) t; char c; if (!s) bputs(b, "(nil)"); else while ((c = *s++)) bputc(b, c); } /* ====================================================================== */ /* buffer_vaprintf_translate() ****************************************** * * Print string translating '/' characters with '@'. Used by short URL * translation stuff. * b: Buffer * fmt: vaprintf format string, followed by arguments. ***********************************************************************/ void buffer_vaprintf_translate(struct buffer *b, char *fmt, va_list ap) { unsigned char *s, c; while ((c = *fmt++)) { switch (c) { case '%': switch (*fmt++) { case 's': /* string */ if ((s = (unsigned char *) va_arg(ap, char *))) { while ((c = *s++)) bputc(b, (c == '/') ? '@' : c); } else bputs(b, "(nil)"); break; case 'l': if (*fmt == 'u') { buffer_print_ulong(b, va_arg(ap, unsigned long)); fmt++; } else buffer_print_ulong(b, va_arg(ap, long)); break; case 'd': if (*fmt == 'u') { buffer_print_ulong(b, va_arg(ap, unsigned int)); fmt++; } else buffer_print_ulong(b, va_arg(ap, int)); break; case 'c': bputc(b, (unsigned char) va_arg(ap, int)); break; case '%': bputc(b, '%'); break; default: log_fatal("Bad format string to buffer_printf"); } break; case '/': bputc(b, '@'); break; default: bputc(b, c); } } } /* buffer_printf_translate() ******************************************** * * Print string translating '/' characters with '@'. Used by short URL * translation stuff. * b: Buffer * fmt: vaprintf format string, followed by arguments. ***********************************************************************/ void buffer_printf_translate(struct buffer *b, char *fmt, ...) { va_list ap; va_start(ap, fmt); buffer_vaprintf_translate(b, fmt, ap); va_end(ap); } /* buffer_puts_translate() *********************************************** * * Print string translating '/' characters with '@'. Used by short URL * translation stuff. * b: Buffer * t: String to print/translate ***********************************************************************/ void buffer_puts_translate(struct buffer *b, char *t) { unsigned char *s = (unsigned char *) t; char c; if (!s) bputs(b, "(nil)"); else while ((c = *s++)) bputc(b, (c == '/') ? '@' : c); } /* ====================================================================== */ /* Fetch methods */ /* buffer_rewind() ****************************************************** * * Rewind read access ptrs to start of the buffer ***********************************************************************/ void buffer_rewind(struct buffer *b) { b->offset = 0; b->fetch = b->first; b->fetch_avail = b->blocksize; } /* buffer_seek_offset() ************************************************* * * Seek to given offset in buffer * b: Buffer * offset: Offset into buffer * * Returns: T on sucess. NIL if offset is out of range. ***********************************************************************/ BOOL buffer_seek_offset(struct buffer *b, unsigned long offset) { struct msgblock *mb = b->first; if ((b->offset = offset) > b->size) return (NIL); while (offset > b->blocksize) { mb = mb->next; offset -= b->blocksize; } b->fetch = mb; /* Correct block */ b->fetch_avail = b->blocksize - offset; /* Data left in this block */ return (T); } /* buffer_getchar() ***************************************************** * * Get character from current read location in buffer. Usually used via * bgetc() macro. * b: Buffer ***********************************************************************/ int buffer_getchar(struct buffer *b) { unsigned char result; if (b->offset >= b->size) /* Nothing more available */ return (EOF); if (b->fetch == NIL) /* Need to set up fetch ptrs */ buffer_rewind(b); if (b->fetch_avail == 0) { b->fetch = b->fetch->next; /* Next block in chain */ b->fetch_avail = b->blocksize; } /* Record current character */ result = b->fetch->data[b->blocksize - b->fetch_avail]; /* Then update pointers */ b->offset++; b->fetch_avail--; return ((int) result); } /* ====================================================================== */ /* buffer_getblock() ***************************************************** * * Get block of characters from buffer. Static support fn for buffer_fetch * b: Buffer * block: Target location * count: Number of characters. ***********************************************************************/ static unsigned long buffer_getblock(struct buffer *b, void *block, unsigned long count) { char *s = (char *) block; unsigned long result; if (b->offset >= b->size) /* No more bytes available */ return (0); if (count > (b->size - b->offset)) count = b->size - b->offset; /* Only this many bytes available */ result = count; /* Return (adjusted) count to caller */ if (b->fetch == NIL) /* Need to set up fetch ptrs */ buffer_rewind(b); b->offset += count; if (count < b->fetch_avail) { /* Can fetch block from current bucket */ memcpy(s, &(b->fetch->data[b->blocksize - b->fetch_avail]), count); b->fetch_avail -= count; return (result); } /* Otherwise block fetch will overflow into next bucket */ if (b->fetch_avail > 0) { /* Take partial chunk from current bucket */ /* NB: this deals with (count == b->fetch_avail) case too */ memcpy(s, &(b->fetch->data[b->blocksize - b->fetch_avail]), b->fetch_avail); s += b->fetch_avail; count -= b->fetch_avail; /* Set up next full bucket */ b->fetch = b->fetch->next; b->fetch_avail = b->blocksize; } while (count >= b->blocksize) { /* Copy in full b->blocksize chunks */ memcpy(s, b->fetch->data, b->blocksize); s += b->blocksize; count -= b->blocksize; /* Set up next full bucket */ b->fetch = b->fetch->next; b->fetch_avail = b->blocksize; } /* Possible final (partial) bucket will be < b->blocksize */ if (count > 0) { memcpy(s, b->fetch->data, count); /* More data to process in this bucket. b->fetch stays unchanged */ b->fetch_avail = b->blocksize - count; } return (result); } /* buffer_fetch() ******************************************************* * * Retrive block of data from buffer * b: Buffer * offset: Offset into buffer * count: Number of characters to retrieve * copy: Generate separate copy of data. * NIL => okay to return ptr to data in place if byte range * falls within a single msgblock object. ***********************************************************************/ void *buffer_fetch(struct buffer *b, unsigned long offset, unsigned long count, BOOL copy) { char *result; buffer_seek_offset(b, offset); if (count == 0) return (pool_strdup(b->pool, "")); if (copy || (b->fetch_avail < count + 1)) { result = pool_alloc(b->pool, count + 1); buffer_getblock(b, result, count); result[count] = '\0'; } else { unsigned long init_offset = b->blocksize - b->fetch_avail; b->fetch->data[init_offset + count] = '\0'; result = (char *) &(b->fetch->data[init_offset]); } return ((void *) result); } /* buffer_fetch_block() ************************************************* * * * Fetch single block from buffer from current seek position. Repeated * calls will step through the buffer one block at a time. * b: Buffer * ptrp: Used to return next block * sizep: Used to return size of next block * * Returns: T => data available. * NIL => no data available. ***********************************************************************/ BOOL buffer_fetch_block(struct buffer *b, unsigned char **ptrp, unsigned long *sizep) { if (b->fetch == NIL) return (NIL); if (b->fetch->next) { *ptrp = &b->fetch->data[0]; *sizep = b->blocksize; b->fetch = b->fetch->next; } else { *ptrp = &b->fetch->data[0]; *sizep = b->blocksize - b->avail; b->fetch = NIL; } return (T); }