/*
 * Utility functions and classes
 *
 * Two classes:  Buffer and Heap.
 * Buffer is a simple buffered input class, like a very lightweight FILE*
 * but with additional functionality to flush internal buffers, push back
 * large masses of text onto the input stream, and wads of record keeping
 * so we can debug screwed up transfers.
 * Heap is what it sounds like; it's a heap we can stored data on, which
 * we can then write out at a later date.
 *
 * Copyright (c) 1997 David L Parsons 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */
static const char rcsid[] = "$Id: s.util.cc 1.5 98/06/23 21:53:37-00:00 orc $";

extern "C" {
#include <stdio.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <malloc.h>
#include <fcntl.h>
#include <errno.h>
}

#include <string.h>

#include "util.H"

#define CHUNK 4000

/*
 * Buffer::Buffer() create a buffer
 */
Buffer::Buffer(int filedesc, int timeout)
{
    curr = size = _bytes_read = 0;
    alarm(timeout);
    bfr = (char*)malloc(bfrlen = CHUNK);
    if (bfr == (char*)0) {
	syslog(LOG_ERR, "Buffer::Buffer: %m");
	exit(1);
    }
    fd = filedesc;
} /* Buffer */


/*
 * Buffer::alarm() set or clear the read timeout
*/
void
Buffer::alarm(int to)
{
    int flags = fcntl(fd, F_GETFL);
    if ((_timeout = to) > 0) {
	/* if we are using timed reads, we use nonblocking i/o and
	 * select to pick up input
	 */
	fcntl(fd, F_SETFL,flags | O_NONBLOCK);
    }
    else {
	/* If we aren't using timed reads, we'll just block forever
	 * waiting for something interesting to happen.
	 */
	fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
    }
} /* Buffer::alarm */


/*
 * Buffer::readchar() reads a character from the input stream
 */
unsigned int
Buffer::readchar()
{
    while (1) {
	if (curr < size) return 0xff & (int)(bfr[curr++]);

	curr = 0;
	if (_timeout > 0) {
	    /* We are doing timed i/o, so we'll set up a select and
	     * block waiting for something interesting to happen.
	     */
	    fd_set readfds;
	    fd_set exceptfds;
	    struct timeval clock;
	    int rc;

	    FD_ZERO(&readfds);
	    FD_ZERO(&exceptfds);
	    FD_SET(fd, &readfds);
	    FD_SET(fd, &exceptfds);
	    clock.tv_sec = _timeout;
	    clock.tv_usec = 0;

	    rc = ::select(fd+1, &readfds, 0, &exceptfds, &clock);
	    if (rc < 1 || !FD_ISSET(fd, &readfds))
		return (unsigned)(int)EOF;
	}
	size = ::read(fd, bfr, bfrlen);
#if WWWDEBUG
	syslog(LOG_DEBUG, "Buffer::readchar(); %d bytes from %d", size, fd);
#endif
	if (size < 0 && (errno == EAGAIN || errno == EINTR))
	    continue;
	if (size <= 0) return (unsigned)(int)EOF;
	_bytes_read += size;
    }
} /* Buffer::readchar */


/*
 * Buffer::readline() reads a line of text from the buffer.
 */
void
Buffer::readline(char *line, int len)
{
    int idx;			/* running count of characters read into dest */
    int c;			/* latest character we read */

    memset(line, 0, len);	/* preclear so we don't have to null-term */
    --len;			/* back down the len so there will */
				/* always be a null byte */

    idx = 0;
    do {
	if ((c = readchar()) == EOF) {
#if WWWDEBUG
	    syslog(LOG_DEBUG, "%s EOF", (fd==CLIENT) ? "Client" : "Server");
#endif
	    exit(1);
	}
	if (idx < len)
	    line[idx++] = c;
#if WWWDEBUG
	else syslog(LOG_DEBUG, "LINE OVERFLOW");
#endif
    } while (c != '\n');
#if WWWDEBUG
    syslog(LOG_DEBUG, "Buffer::readline(%d,line,%d) -> %s", fd, len+1, line);
#endif
} /* GetLine */


/*
 * Buffer::read() reads a chunk of data
 */
int
Buffer::read(char *data, int len)
{
    int c, sz;

    for (sz=0; sz<len; sz++) {
	c = readchar();
	if (c == EOF)
	    return sz;
	data[sz] = c;
    }
    return len;
} /* Buffer::read */


/*
 * Buffer::pushback() pushes previously read data back onto the
 * unread data pile.
 */
void
Buffer::pushback(char *data, int len)
{
    char *tmp;
    int tmplen;

    tmplen = bfrlen+len;
    if ((tmp = (char*)malloc(tmplen)) == (char*)0) {
	syslog(LOG_ERR, "Buffer::pushback: %m");
	exit(1);
    }
    memset(tmp, 0, tmplen);
    memcpy(tmp, data, len);
    memcpy(tmp+len, bfr+curr, size-curr);
    free(bfr);
    bfr = tmp;
    curr = 0;
    size = len + (size-curr);
    bfrlen = tmplen;
#if WWWDEBUG
    syslog(LOG_DEBUG, "Buffer::pushback: buffer now contains [%.*s]",
	    size, bfr);
#endif
} /* Buffer::pushback */


/*
 * Buffer::write() writes something to the file, keeping
 * detailed records of how many bytes we wrote.
 */
int
Buffer::write(char *data, int len)
{
    int sz;

    sz = ::write(fd, data, len);
    if (sz > 0)
	_bytes_written += sz;

    return sz;
} /* Buffer::write */


/*
 * Buffer::flush(Buffer) flushes unread data out to another Buffer
 */
int
Buffer::flush(Buffer &to)
{
    int sz;
    char *p;

    sz = size-curr;
    p = bfr+curr;
    curr = size = 0;

    return (sz > 0) ? to.write(p, sz) : 0;
} /* Buffer::flush(Buffer) */


/*
 * Buffer::flush(int) flushes unread data out to a file descriptor
 */
int
Buffer::flush(int to)
{
    int sz;
    char *p;

    sz = size-curr;
    p = bfr+curr;
    curr = size = 0;

    return (sz > 0) ? (::write(to,p,sz)) : 0;
} /* Buffer::flush(int) */


/*
 * Heap::Heap() create a heap object.
 */
Heap::Heap()
{
    alloc = CHUNK;
    used = 0;
    bfr = (char*)malloc(alloc);

    if (bfr == 0) {
	syslog(LOG_ERR, "Heap::Heap(): %m");
	exit(1);
    }
} /* Heap::Heap */


/*
 * Heap::Heap(char*) create a heap object containing a null-terminated
 *                   string.
 */
Heap::Heap(char *string)
{
    used = strlen(string);
    alloc = used+CHUNK;

    bfr = (char*)malloc(alloc);
    if (bfr == 0) {
	syslog(LOG_ERR, "Heap::Heap(char*): %m");
	exit(1);
    }
    strcpy(bfr, string);
} /* Heap::Heap(char*) */


/*
 * Heap::add() a null-terminated string to the heap
 */
void
Heap::add(char *string)
{
    int strsize = strlen(string);

    if (used+strsize > alloc) {
	alloc = used+strsize+CHUNK;
	if ((bfr = (char*)realloc(bfr, alloc)) == (char*)0) {
	    syslog(LOG_ERR, "Heap::operator +=: %m");
	    exit(1);
	}
    }
    strcpy(bfr+used, string);
    used += strsize;
} /* Heap::operator += */


/*
 * Heap::add() adds arbitrary (not necessarily null-terminated) data to
 *             the heap
 */
void
Heap::add(char *data, int size)
{
    if (used + size > alloc) {
	alloc = used+size+CHUNK;
	if ((bfr = (char*)realloc(bfr, alloc)) == (char*)0) {
	    syslog(LOG_ERR, "Heap::add: %m");
	    exit(1);
	}
    }
    memcpy(bfr+used, data, size);
    used += size;
} /* Heap::add */


/*
 * Heap::~Heap() kills off a heap
 */
Heap::~Heap()
{
    free(bfr);
} /* Heap::~Heap */


extern "C" char *
haddrtostring(struct hostent *p, int whichone)
{
    int i;
    static char bfr[80];

    bfr[0] = 0;
    for (i=0; i<p->h_length; i++) {
	if (bfr[0])
	    strcat(bfr, ".");
	sprintf(bfr+strlen(bfr), "%d", (unsigned char)(p->h_addr_list[whichone][i]));
    }
    return bfr;
}
