/* $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);
}
syntax highlighted by Code2HTML, v. 0.9.1