/*
 * Buffer handling functions
 *
 * Copyright (C) 2003-2007, Olaf Kirch <olaf.kirch@oracle.com>
 */

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>
#include <unistd.h>
#include <netinet/in.h> /* ntohl&htonl */
#include <libisns/buffer.h>
#include <libisns/util.h>	/* htonll */

static int	buf_drain(buf_t *bp);

buf_t *
buf_alloc(size_t size)
{
	buf_t	*bp;

	bp = isns_calloc(1, sizeof(*bp));
	buf_init_empty(bp, size);

	return bp;
}

buf_t *
buf_open(const char *filename, int flags)
{
	static const unsigned int buflen = 4096;
	buf_t		*bp;
	int		oerr;

	if (!(bp = isns_calloc(1, sizeof(*bp) + buflen)))
		return NULL;
	buf_init(bp, (bp + 1), buflen);

	switch (flags & O_ACCMODE) {
	case O_RDONLY:
		bp->write_mode = 0;
		break;

	case O_WRONLY:
		bp->write_mode = 1;
		break;

	default:
		errno = EINVAL;
		goto failed;
	}

	if (!filename || !strcmp(filename, "-")) {
		bp->fd = dup(bp->write_mode? 1 : 0);
	} else {
		bp->fd = open(filename, flags, 0666);
	}

	if (bp->fd < 0)
		goto failed;

	return bp;

failed:	oerr = errno;
	isns_free(bp);
	errno = oerr;
	return NULL;
}

buf_t *
buf_dup(const buf_t *src)
{
	buf_t	*bp;

	bp = buf_alloc(src->max_size);
	buf_put(bp, src->base + src->head, src->tail - src->head);

	bp->addr = src->addr;
	bp->addrlen = src->addrlen;
	return bp;
}

void
buf_close(buf_t *bp)
{
	if (bp->write_mode)
		buf_drain(bp);
	if (bp->fd >= 0)
		close(bp->fd);
	bp->fd = -1;
	isns_free(bp);
}

void
buf_free(buf_t *bp)
{
	if (!bp)
		return;
	if (bp->allocated)
		isns_free(bp->base);
	isns_free(bp);
}

void
buf_list_free(buf_t *bp)
{
	buf_t	*next;

	while (bp) {
		next = bp->next;
		buf_free(bp);
		bp = next;
	}
}

void
buf_init(buf_t *bp, void *mem, size_t len)
{
	memset(bp, 0, sizeof(*bp));
	bp->base = (unsigned char *) mem;
	bp->size = len;
	bp->max_size = len;
	bp->fd = -1;
}

void
buf_init_empty(buf_t *bp, size_t len)
{
	memset(bp, 0, sizeof(*bp));
	bp->max_size = len;
	bp->fd = -1;
}

void
buf_set(buf_t *bp, void *mem, size_t len)
{
	buf_init(bp, mem, len);
	bp->tail = len;
}

void
buf_clear(buf_t *bp)
{
	bp->head = bp->tail = 0;
}

static int
buf_fill(buf_t *bp)
{
	int	n;

	if (bp->head || bp->tail)
		buf_compact(bp);

	if (bp->write_mode || bp->fd < 0)
		return 0;

	n = read(bp->fd, bp->base + bp->tail, buf_tailroom(bp));
	if (n < 0) {
		warn("read error");
		return 0;
	}

	bp->tail += n;
	return n;
}

int
buf_drain(buf_t *bp)
{
	int	n;

	if (!bp->write_mode || bp->fd < 0)
		return 0;

	n = write(bp->fd, bp->base + bp->head, buf_avail(bp));
	if (n < 0) {
		warn("write error");
		return 0;
	}

	bp->head += n;
	return n;
}

int
__buf_resize(buf_t *bp, size_t new_size)
{
	void *new_base;

	if (new_size > bp->max_size)
		return 0;
	isns_assert(bp->allocated || bp->base == NULL);

	new_size = (new_size + 127) & ~127;
	if (new_size > bp->max_size)
		new_size = bp->max_size;

	new_base = isns_realloc(bp->base, new_size);
	if (new_base == NULL)
		return 0;

	bp->base = new_base;
	bp->size = new_size;
	bp->allocated = 1;
	return new_size;
}

buf_t *
buf_split(buf_t **to_split, size_t size)
{
	buf_t *old = *to_split, *new;
	size_t avail;

	avail = buf_avail(old);
	if (size > avail)
		return NULL;

	if (size == avail) {
		*to_split = NULL;
		return old;
	}

	new = buf_alloc(size);
	buf_put(new, buf_head(old), size);
	buf_pull(old, size);

	return new;
}

int
buf_seek(buf_t *bp, off_t offset)
{
	if (bp->write_mode && !buf_drain(bp))
		return 0;
	if (lseek(bp->fd, offset, SEEK_SET) < 0) {
		warn("cannot seek to offset %ld", (long) offset);
		return 0;
	}
	return 1;
}

int
buf_get(buf_t *bp, void *mem, size_t len)
{
	caddr_t		dst = (caddr_t) mem;
	unsigned int	total = len, copy;

	while (len) {
		if ((copy = buf_avail(bp)) > len)
			copy = len;
		if (copy == 0) {
			if (!buf_fill(bp))
				return 0;
			continue;
		}
		if (dst) {
			memcpy(dst, bp->base + bp->head, copy);
			dst += copy;
		}
		bp->head += copy;
		len -= copy;
	}
	return total;
}

int
buf_get32(buf_t *bp, uint32_t *vp)
{
	if (!buf_get(bp, vp, 4))
		return 0;
	*vp = ntohl(*vp);
	return 1;
}

int
buf_get64(buf_t *bp, uint64_t *vp)
{
	if (!buf_get(bp, vp, 8))
		return 0;
	*vp = ntohll(*vp);
	return 1;
}

int
buf_gets(buf_t *bp, char *stringbuf, size_t size)
{
	uint32_t	len, copy;

	if (size == 0)
		return 0;

	if (!buf_get32(bp, &len))
		return 0;

	if ((copy = len) >= size)
		copy = size - 1;

	if (!buf_get(bp, stringbuf, copy))
		return 0;
	stringbuf[copy] = '\0';

	/* Pull remaining bytes */
	if (copy != len && !buf_pull(bp, len - copy))
		return 0;

	return copy + 1;
}

int
buf_put(buf_t *bp, const void *mem, size_t len)
{
	caddr_t		src = (caddr_t) mem;
	unsigned int	total = len, copy;

	while (len) {
		if ((copy = bp->size - bp->tail) > len)
			copy = len;
		if (copy == 0) {
			if (buf_drain(bp)) {
				buf_compact(bp);
				continue;
			}
			if (__buf_resize(bp, bp->tail + len)) {
				buf_compact(bp);
				continue;
			}
			return 0;
		}
		if (src) {
			memcpy(bp->base + bp->tail, src, copy);
			src += copy;
		}
		bp->tail += copy;
		len -= copy;
	}
	return total;
}

int
buf_putc(buf_t *bp, int byte)
{
	unsigned char	c = byte;

	return buf_put(bp, &c, 1);
}

int
buf_put32(buf_t *bp, uint32_t val)
{
	val = htonl(val);
	if (!buf_put(bp, &val, 4))
		return 0;
	return 1;
}

int
buf_put64(buf_t *bp, uint64_t val)
{
	val = htonll(val);
	return buf_put(bp, &val, 8);
}

int
buf_puts(buf_t *bp, const char *sp)
{
	uint32_t	len = 0;

	if (sp)
		len = strlen(sp);
	return buf_put32(bp, len) && buf_put(bp, sp, len);
}

void
buf_compact(buf_t *bp)
{
	unsigned int	count;

	if (bp->head == 0)
		return;

	count = bp->tail - bp->head;
	memmove(bp->base, bp->base + bp->head, count);
	bp->tail -= bp->head;
	bp->head  = 0;
}

void
buf_list_append(buf_t **list, buf_t *bp)
{
	bp->next = NULL;
	while (*list)
		list = &(*list)->next;
	*list = bp;
}

int
buf_truncate(buf_t *bp, size_t len)
{
	if (bp->head + len > bp->tail)
		return 0;

	bp->tail = bp->head + len;
	return 1;
}
