/*-
 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: releng/9.2/usr.bin/csup/stream.c 204556 2010-03-02 07:26:07Z lulf $
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <assert.h>
#include <zlib.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "misc.h"
#include "stream.h"

/*
 * Simple stream API to make my life easier.  If the fgetln() and
 * funopen() functions were standard and if funopen() wasn't using
 * wrong types for the function pointers, I could have just used
 * stdio, but life sucks.
 *
 * For now, streams are always block-buffered.
 */

/*
 * Try to quiet warnings as much as possible with GCC while staying
 * compatible with other compilers.
 */
#ifndef __unused
#if defined(__GNUC__) && (__GNUC__ > 2 || __GNUC__ == 2 && __GNUC_MINOR__ >= 7)
#define	__unused	__attribute__((__unused__))
#else
#define	__unused
#endif
#endif

/*
 * Flags passed to the flush methods.
 *
 * STREAM_FLUSH_CLOSING is passed during the last flush call before
 * closing a stream.  This allows the zlib filter to emit the EOF
 * marker as appropriate.  In all other cases, STREAM_FLUSH_NORMAL
 * should be passed.
 *
 * These flags are completely unused in the default flush method,
 * but they are very important for the flush method of the zlib
 * filter.
 */
typedef enum {
	STREAM_FLUSH_NORMAL,
	STREAM_FLUSH_CLOSING
} stream_flush_t;

/*
 * This is because buf_new() will always allocate size + 1 bytes,
 * so our buffer sizes will still be power of 2 values.
 */
#define	STREAM_BUFSIZ	1023

struct buf {
	char *buf;
	size_t size;
	size_t in;
	size_t off;
};

struct stream {
	void *cookie;
	int fd;
	int buf;
	struct buf *rdbuf;
	struct buf *wrbuf;
	stream_readfn_t *readfn;
	stream_writefn_t *writefn;
	stream_closefn_t *closefn;
	int eof;
	struct stream_filter *filter;
	void *fdata;
};

typedef int	stream_filter_initfn_t(struct stream *, void *);
typedef void	stream_filter_finifn_t(struct stream *);
typedef int	stream_filter_flushfn_t(struct stream *, struct buf *,
		    stream_flush_t);
typedef ssize_t	stream_filter_fillfn_t(struct stream *, struct buf *);

struct stream_filter {
	stream_filter_t id;
	stream_filter_initfn_t *initfn;
	stream_filter_finifn_t *finifn;
	stream_filter_fillfn_t *fillfn;
	stream_filter_flushfn_t *flushfn;
};

/* Low-level buffer API. */
#define	buf_avail(buf)		((buf)->size - (buf)->off - (buf)->in)
#define	buf_count(buf)		((buf)->in)
#define	buf_size(buf)		((buf)->size)

static void		 buf_more(struct buf *, size_t);
static void		 buf_less(struct buf *, size_t);
static void		 buf_grow(struct buf *, size_t);

/* Internal stream functions. */
static ssize_t		 stream_fill(struct stream *);
static ssize_t		 stream_fill_default(struct stream *, struct buf *);
static int		 stream_flush_int(struct stream *, stream_flush_t);
static int		 stream_flush_default(struct stream *, struct buf *,
			     stream_flush_t);

/* Filters specific functions. */
static struct stream_filter *stream_filter_lookup(stream_filter_t);
static int		 stream_filter_init(struct stream *, void *);
static void		 stream_filter_fini(struct stream *);

/* The zlib stream filter declarations. */
#define	ZFILTER_EOF	1				/* Got Z_STREAM_END. */

struct zfilter {
	int flags;
	struct buf *rdbuf;
	struct buf *wrbuf;
	z_stream *rdstate;
	z_stream *wrstate;
};

static int		 zfilter_init(struct stream *, void *);
static void		 zfilter_fini(struct stream *);
static ssize_t		 zfilter_fill(struct stream *, struct buf *);
static int		 zfilter_flush(struct stream *, struct buf *,
			     stream_flush_t);

/* The MD5 stream filter. */
struct md5filter {
	MD5_CTX ctx;
	char *md5;
	char lastc;
#define PRINT	1
#define WS	2
#define STRING	3
#define SEEN	4
	int state;
};

static int		 md5filter_init(struct stream *, void *);
static void		 md5filter_fini(struct stream *);
static ssize_t		 md5filter_fill(struct stream *, struct buf *);
static int		 md5filter_flush(struct stream *, struct buf *,
			     stream_flush_t);
static int		 md5rcsfilter_flush(struct stream *, struct buf *,
			     stream_flush_t);

/* The available stream filters. */
struct stream_filter stream_filters[] = {
	{
		STREAM_FILTER_NULL,
		NULL,
		NULL,
		stream_fill_default,
		stream_flush_default
	},
	{
	       	STREAM_FILTER_ZLIB,
		zfilter_init,
		zfilter_fini,
		zfilter_fill,
		zfilter_flush
	},
	{
		STREAM_FILTER_MD5,
		md5filter_init,
		md5filter_fini,
		md5filter_fill,
		md5filter_flush
	},
	{
		STREAM_FILTER_MD5RCS,
		md5filter_init,
		md5filter_fini,
		md5filter_fill,
		md5rcsfilter_flush
	}

};


/* Create a new buffer. */
struct buf *
buf_new(size_t size)
{
	struct buf *buf;

	buf = xmalloc(sizeof(struct buf));
	/*
	 * We keep one spare byte so that stream_getln() can put a '\0'
	 * there in case the stream doesn't have an ending newline.
	 */
	buf->buf = xmalloc(size + 1);
	memset(buf->buf, 0, size + 1);
	buf->size = size;
	buf->in = 0;
	buf->off = 0;
	return (buf);
}

/*
 * Grow the size of the buffer.  If "need" is 0, bump its size to the
 * next power of 2 value.  Otherwise, bump it to the next power of 2
 * value bigger than "need".
 */
static void
buf_grow(struct buf *buf, size_t need)
{

	if (need == 0)
		buf->size = buf->size * 2 + 1; /* Account for the spare byte. */
	else {
		assert(need > buf->size);
		while (buf->size < need)
			buf->size = buf->size * 2 + 1;
	}
	buf->buf = xrealloc(buf->buf, buf->size + 1);
}

/* Make more room in the buffer if needed. */
static void
buf_prewrite(struct buf *buf)
{

	if (buf_count(buf) == buf_size(buf))
		buf_grow(buf, 0);
	if (buf_count(buf) > 0 && buf_avail(buf) == 0) {
		memmove(buf->buf, buf->buf + buf->off, buf_count(buf));
		buf->off = 0;
	}
}

/* Account for "n" bytes being added in the buffer. */
static void
buf_more(struct buf *buf, size_t n)
{

	assert(n <= buf_avail(buf));
	buf->in += n;
}

/* Account for "n" bytes having been read in the buffer. */
static void
buf_less(struct buf *buf, size_t n)
{

	assert(n <= buf_count(buf));
	buf->in -= n;
	if (buf->in == 0)
		buf->off = 0;
	else
		buf->off += n;
}

/* Free a buffer. */
void
buf_free(struct buf *buf)
{

	free(buf->buf);
	free(buf);
}

static struct stream *
stream_new(stream_readfn_t *readfn, stream_writefn_t *writefn,
    stream_closefn_t *closefn)
{
	struct stream *stream;

	stream = xmalloc(sizeof(struct stream));
	if (readfn == NULL && writefn == NULL) {
		errno = EINVAL;
		return (NULL);
	}
	if (readfn != NULL)
		stream->rdbuf = buf_new(STREAM_BUFSIZ);
	else
		stream->rdbuf = NULL;
	if (writefn != NULL)
		stream->wrbuf = buf_new(STREAM_BUFSIZ);
	else
		stream->wrbuf = NULL;
	stream->cookie = NULL;
	stream->fd = -1;
	stream->buf = 0;
	stream->readfn = readfn;
	stream->writefn = writefn;
	stream->closefn = closefn;
	stream->filter = stream_filter_lookup(STREAM_FILTER_NULL);
	stream->fdata = NULL;
	stream->eof = 0;
	return (stream);
}

/* Create a new stream associated with a void *. */
struct stream *
stream_open(void *cookie, stream_readfn_t *readfn, stream_writefn_t *writefn,
    stream_closefn_t *closefn)
{
	struct stream *stream;

	stream = stream_new(readfn, writefn, closefn);
	stream->cookie = cookie;
	return (stream);
}

/* Associate a file descriptor with a stream. */
struct stream *
stream_open_fd(int fd, stream_readfn_t *readfn, stream_writefn_t *writefn,
    stream_closefn_t *closefn)
{
	struct stream *stream;

	stream = stream_new(readfn, writefn, closefn);
	stream->cookie = &stream->fd;
	stream->fd = fd;
	return (stream);
}

/* Associate a buf with a stream. */
struct stream *
stream_open_buf(struct buf *b)
{
	struct stream *stream;

	stream = stream_new(stream_read_buf, stream_append_buf, stream_close_buf);
	stream->cookie = b;
	stream->buf = 1;
	b->in = 0;
	return (stream);
}

/*
 * Truncate a buffer, just decrease offset pointer.
 * XXX: this can be dangerous if not used correctly.
 */
void
stream_truncate_buf(struct buf *b, off_t off)
{
	b->off += off;
}

/* Like open() but returns a stream. */
struct stream *
stream_open_file(const char *path, int flags, ...)
{
	struct stream *stream;
	stream_readfn_t *readfn;
	stream_writefn_t *writefn;
	va_list ap;
	mode_t mode;
	int fd;

	va_start(ap, flags);
	if (flags & O_CREAT) {
		/*
		 * GCC says I should not be using mode_t here since it's
		 * promoted to an int when passed through `...'.
		 */
		mode = va_arg(ap, int);
		fd = open(path, flags, mode);
	} else
		fd = open(path, flags);
	va_end(ap);
	if (fd == -1)
		return (NULL);

	flags &= O_ACCMODE;
	if (flags == O_RDONLY) {
		readfn = stream_read_fd;
		writefn = NULL;
	} else if (flags == O_WRONLY) {
		readfn = NULL;
		writefn = stream_write_fd;
	} else if (flags == O_RDWR) {
		assert(flags == O_RDWR);
		readfn = stream_read_fd;
		writefn = stream_write_fd;
	} else {
		errno = EINVAL;
		close(fd);
		return (NULL);
	}

	stream = stream_open_fd(fd, readfn, writefn, stream_close_fd);
	if (stream == NULL)
		close(fd);
	return (stream);
}

/* Return the file descriptor associated with this stream, or -1. */
int
stream_fileno(struct stream *stream)
{

	return (stream->fd);
}

/* Convenience read function for character buffers. */
ssize_t
stream_read_buf(void *cookie, void *buf, size_t size)
{
	struct buf *b;
	size_t avail;

	/* Use in to be read offset. */
	b = (struct buf *)cookie;
	/* Just return what we have if the request is to large. */
	avail = b->off - b->in;
	if (avail < size) {
		memcpy(buf, (b->buf + b->in), avail);
		b->in += avail;
		return (avail);
	}
	memcpy(buf, (b->buf + b->in), size);
	b->in += size;
	return (size);
}

/* Convenience write function for appending character buffers. */
ssize_t
stream_append_buf(void *cookie, const void *buf, size_t size)
{
	struct buf *b;
	size_t avail;

	/* Use off to be write offset. */
	b = (struct buf *)cookie;

	avail = b->size - b->off;
	if (size > avail)
		buf_grow(b, b->size + size);
	memcpy((b->buf + b->off), buf, size);
	b->off += size;
	b->buf[b->off] = '\0';
	return (size);
}

/* Convenience close function for freeing character buffers. */
int
stream_close_buf(void *cookie)
{
	void *data;

	data = cookie;
	/* Basically a NOP. */
	return (0);
}

/* Convenience read function for file descriptors. */
ssize_t
stream_read_fd(void *cookie, void *buf, size_t size)
{
	ssize_t nbytes;
	int fd;

	fd = *(int *)cookie;
	nbytes = read(fd, buf, size);
	return (nbytes);
}

/* Convenience write function for file descriptors. */
ssize_t
stream_write_fd(void *cookie, const void *buf, size_t size)
{
	ssize_t nbytes;
	int fd;

	fd = *(int *)cookie;
	nbytes = write(fd, buf, size);
	return (nbytes);
}

/* Convenience close function for file descriptors. */
int
stream_close_fd(void *cookie)
{
	int fd, ret;

	fd = *(int *)cookie;
	ret = close(fd);
	return (ret);
}

/* Read some bytes from the stream. */
ssize_t
stream_read(struct stream *stream, void *buf, size_t size)
{
	struct buf *rdbuf;
	ssize_t ret;
	size_t n;

	rdbuf = stream->rdbuf;
	if (buf_count(rdbuf) == 0) {
		ret = stream_fill(stream);
		if (ret <= 0)
			return (-1);
	}
	n = min(size, buf_count(rdbuf));
	memcpy(buf, rdbuf->buf + rdbuf->off, n);
	buf_less(rdbuf, n);
	return (n);
}

/* A blocking stream_read call. */
ssize_t
stream_read_blocking(struct stream *stream, void *buf, size_t size)
{
	struct buf *rdbuf;
	ssize_t ret;
	size_t n;

	rdbuf = stream->rdbuf;
	while (buf_count(rdbuf) <= size) {
		ret = stream_fill(stream);
		if (ret <= 0)
			return (-1);
	}
	/* XXX: Should be at least size bytes in the buffer, right? */
	/* Just do this to make sure. */
	n = min(size, buf_count(rdbuf));
	memcpy(buf, rdbuf->buf + rdbuf->off, n);
	buf_less(rdbuf, n);
	return (n);
}

/*
 * Read a line from the stream and return a pointer to it.
 *
 * If "len" is non-NULL, the length of the string will be put into it.
 * The pointer is only valid until the next stream API call.  The line
 * can be modified by the caller, provided he doesn't write before or
 * after it.
 *
 * This is somewhat similar to the BSD fgetln() function, except that
 * "len" can be NULL here.  In that case the string is terminated by
 * overwriting the '\n' character with a NUL character.  If it's the
 * last line in the stream and it has no ending newline, we can still
 * add '\0' after it, because we keep one spare byte in the buffers.
 *
 * However, be warned that one can't handle binary lines properly
 * without knowing the size of the string since those can contain
 * NUL characters.
 */
char *
stream_getln(struct stream *stream, size_t *len)
{
	struct buf *buf;
	char *cp, *line;
	ssize_t n;
	size_t done, size;

	buf = stream->rdbuf;
	if (buf_count(buf) == 0) {
		n = stream_fill(stream);
		if (n <= 0)
			return (NULL);
	}
	cp = memchr(buf->buf + buf->off, '\n', buf_count(buf));
	for (done = buf_count(buf); cp == NULL; done += n) {
		n = stream_fill(stream);
		if (n < 0)
			return (NULL);
		if (n == 0)
			/* Last line of the stream. */
			cp = buf->buf + buf->off + buf->in - 1;
		else
			cp = memchr(buf->buf + buf->off + done, '\n',
			    buf_count(buf) - done);
	}
	line = buf->buf + buf->off;
	assert(cp >= line);
	size = cp - line + 1;
	buf_less(buf, size);
	if (len != NULL) {
		*len = size;
	} else {
		/* Terminate the string when len == NULL. */
		if (line[size - 1] == '\n')
			line[size - 1] = '\0';
		else
			line[size] = '\0';
	}
	return (line);
}

/* Write some bytes to a stream. */
ssize_t
stream_write(struct stream *stream, const void *src, size_t nbytes)
{
	struct buf *buf;
	int error;

	buf = stream->wrbuf;
	if (nbytes > buf_size(buf))
		buf_grow(buf, nbytes);
	if (nbytes > buf_avail(buf)) {
		error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
		if (error)
			return (-1);
	}
	memcpy(buf->buf + buf->off + buf->in, src, nbytes);
	buf_more(buf, nbytes);
	return (nbytes);
}

/* Formatted output to a stream. */
int
stream_printf(struct stream *stream, const char *fmt, ...)
{
	struct buf *buf;
	va_list ap;
	int error, ret;

	buf = stream->wrbuf;
again:
	va_start(ap, fmt);
	ret = vsnprintf(buf->buf + buf->off + buf->in, buf_avail(buf), fmt, ap);
	va_end(ap);
	if (ret < 0)
		return (ret);
	if ((unsigned)ret >= buf_avail(buf)) {
		if ((unsigned)ret >= buf_size(buf))
			buf_grow(buf, ret + 1);
		if ((unsigned)ret >= buf_avail(buf)) {
			error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
			if (error)
				return (-1);
		}
		goto again;
	}
	buf_more(buf, ret);
	return (ret);
}

/* Flush the entire write buffer of the stream. */
int
stream_flush(struct stream *stream)
{
	int error;

	error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
	return (error);
}

/* Internal flush API. */
static int
stream_flush_int(struct stream *stream, stream_flush_t how)
{
	struct buf *buf;
	int error;

	buf = stream->wrbuf;
	error = (*stream->filter->flushfn)(stream, buf, how);
	assert(buf_count(buf) == 0);
	return (error);
}

/* The default flush method. */
static int
stream_flush_default(struct stream *stream, struct buf *buf,
    stream_flush_t __unused how)
{
	ssize_t n;

	while (buf_count(buf) > 0) {
		do {
			n = (*stream->writefn)(stream->cookie,
			    buf->buf + buf->off, buf_count(buf));
		} while (n == -1 && errno == EINTR);
		if (n <= 0)
			return (-1);
		buf_less(buf, n);
	}
	return (0);
}

/* Flush the write buffer and call fsync() on the file descriptor. */
int
stream_sync(struct stream *stream)
{
	int error;

	if (stream->fd == -1) {
		errno = EINVAL;
		return (-1);
	}
	error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
	if (error)
		return (-1);
	error = fsync(stream->fd);
	return (error);
}

/* Like truncate() but on a stream. */
int
stream_truncate(struct stream *stream, off_t size)
{
	int error;

	if (stream->fd == -1) {
		errno = EINVAL;
		return (-1);
	}
	error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
	if (error)
		return (-1);
	error = ftruncate(stream->fd, size);
	return (error);
}

/* Like stream_truncate() except the off_t parameter is an offset. */
int
stream_truncate_rel(struct stream *stream, off_t off)
{
	struct stat sb;
	int error;

	if (stream->buf) {
		stream_truncate_buf(stream->cookie, off);
		return (0);
	}
	if (stream->fd == -1) {
		errno = EINVAL;
		return (-1);
	}
	error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
	if (error)
		return (-1);
	error = fstat(stream->fd, &sb);
	if (error)
		return (-1);
	error = stream_truncate(stream, sb.st_size + off);
	return (error);
}

/* Rewind the stream. */
int
stream_rewind(struct stream *stream)
{
	int error;

	if (stream->fd == -1) {
		errno = EINVAL;
		return (-1);
	}
	if (stream->rdbuf != NULL)
		buf_less(stream->rdbuf, buf_count(stream->rdbuf));
	if (stream->wrbuf != NULL) {
		error = stream_flush_int(stream, STREAM_FLUSH_NORMAL);
		if (error)
			return (error);
	}
	error = lseek(stream->fd, 0, SEEK_SET);
	return (error);
}

/* Return EOF status. */
int
stream_eof(struct stream *stream)
{

	return (stream->eof);
}

/* Close a stream and free any resources held by it. */
int
stream_close(struct stream *stream)
{
	int error;

	if (stream == NULL)
		return (0);

	error = 0;
	if (stream->wrbuf != NULL)
		error = stream_flush_int(stream, STREAM_FLUSH_CLOSING);
	stream_filter_fini(stream);
	if (stream->closefn != NULL)
		/*
		 * We might overwrite a previous error from stream_flush(),
		 * but we have no choice, because wether it had worked or
		 * not, we need to close the file descriptor.
		 */
		error = (*stream->closefn)(stream->cookie);
	if (stream->rdbuf != NULL)
		buf_free(stream->rdbuf);
	if (stream->wrbuf != NULL)
		buf_free(stream->wrbuf);
	free(stream);
	return (error);
}

/* The default fill method. */
static ssize_t
stream_fill_default(struct stream *stream, struct buf *buf)
{
	ssize_t n;

	if (stream->eof)
		return (0);
	assert(buf_avail(buf) > 0);
	n = (*stream->readfn)(stream->cookie, buf->buf + buf->off + buf->in,
	    buf_avail(buf));
	if (n < 0)
		return (-1);
	if (n == 0) {
		stream->eof = 1;
		return (0);
	}
	buf_more(buf, n);
	return (n);
}

/*
 * Refill the read buffer.  This function is not permitted to return
 * without having made more bytes available, unless there was an error.
 * Moreover, stream_fill() returns the number of bytes added.
 */
static ssize_t
stream_fill(struct stream *stream)
{
	struct stream_filter *filter;
	struct buf *buf;
#ifndef NDEBUG
	size_t oldcount;
#endif
	ssize_t n;

	filter = stream->filter;
	buf = stream->rdbuf;
	buf_prewrite(buf);
#ifndef NDEBUG
	oldcount = buf_count(buf);
#endif
	n = (*filter->fillfn)(stream, buf);
	assert((n > 0 && n == (signed)(buf_count(buf) - oldcount)) ||
	    (n <= 0 && buf_count(buf) == oldcount));
	return (n);
}

/*
 * Lookup a stream filter.
 *
 * We are not supposed to get passed an invalid filter id, since
 * filter ids are an enum type and we don't have invalid filter
 * ids in the enum :-).  Thus, we are not checking for out of
 * bounds access here.  If it happens, it's the caller's fault
 * anyway.
 */
static struct stream_filter *
stream_filter_lookup(stream_filter_t id)
{
	struct stream_filter *filter;

	filter = stream_filters;
	while (filter->id != id)
		filter++;
	return (filter);
}

static int
stream_filter_init(struct stream *stream, void *data)
{
	struct stream_filter *filter;
	int error;

	filter = stream->filter;
	if (filter->initfn == NULL)
		return (0);
	error = (*filter->initfn)(stream, data);
	return (error);
}

static void
stream_filter_fini(struct stream *stream)
{
	struct stream_filter *filter;

	filter = stream->filter;
	if (filter->finifn != NULL)
		(*filter->finifn)(stream);
}

/*
 * Start a filter on a stream.
 */
int
stream_filter_start(struct stream *stream, stream_filter_t id, void *data)
{
	struct stream_filter *filter;
	int error;

	filter = stream->filter;
	if (id == filter->id)
		return (0);
	stream_filter_fini(stream);
	stream->filter = stream_filter_lookup(id);
	stream->fdata = NULL;
	error = stream_filter_init(stream, data);
	return (error);
}


/* Stop a filter, this is equivalent to setting the null filter. */
void
stream_filter_stop(struct stream *stream)
{

	stream_filter_start(stream, STREAM_FILTER_NULL, NULL);
}

/* The zlib stream filter implementation. */

/* Take no chances with zlib... */
static void *
zfilter_alloc(void __unused *opaque, unsigned int items, unsigned int size)
{

	return (xmalloc(items * size));
}

static void
zfilter_free(void __unused *opaque, void *ptr)
{

	free(ptr);
}

static int
zfilter_init(struct stream *stream, void __unused *data)
{
	struct zfilter *zf;
	struct buf *buf;
	z_stream *state;
	int rv;

	zf = xmalloc(sizeof(struct zfilter));
	memset(zf, 0, sizeof(struct zfilter));
	if (stream->rdbuf != NULL) {
		state = xmalloc(sizeof(z_stream));
		state->zalloc = zfilter_alloc;
		state->zfree = zfilter_free;
		state->opaque = Z_NULL;
		rv = inflateInit(state);
		if (rv != Z_OK)
			errx(1, "inflateInit: %s", state->msg);
		buf = buf_new(buf_size(stream->rdbuf));
		zf->rdbuf = stream->rdbuf;
		stream->rdbuf = buf;
		zf->rdstate = state;
	}
	if (stream->wrbuf != NULL) {
		state = xmalloc(sizeof(z_stream));
		state->zalloc = zfilter_alloc;
		state->zfree = zfilter_free;
		state->opaque = Z_NULL;
		rv = deflateInit(state, Z_DEFAULT_COMPRESSION);
		if (rv != Z_OK)
			errx(1, "deflateInit: %s", state->msg);
		buf = buf_new(buf_size(stream->wrbuf));
		zf->wrbuf = stream->wrbuf;
		stream->wrbuf = buf;
		zf->wrstate = state;
	}
	stream->fdata = zf;
	return (0);
}

static void
zfilter_fini(struct stream *stream)
{
	struct zfilter *zf;
	struct buf *zbuf;
	z_stream *state;
	ssize_t n;

	zf = stream->fdata;
	if (zf->rdbuf != NULL) {
		state = zf->rdstate;
		zbuf = zf->rdbuf;
		/*
		 * Even if it has produced all the bytes, zlib sometimes
		 * hasn't seen the EOF marker, so we need to call inflate()
		 * again to make sure we have eaten all the zlib'ed bytes.
		 */
		if ((zf->flags & ZFILTER_EOF) == 0) {
			n = zfilter_fill(stream, stream->rdbuf);
			assert(n == 0 && zf->flags & ZFILTER_EOF);
		}
		inflateEnd(state);
		free(state);
		buf_free(stream->rdbuf);
		stream->rdbuf = zbuf;
	}
	if (zf->wrbuf != NULL) {
		state = zf->wrstate;
		zbuf = zf->wrbuf;
		/*
		 * Compress the remaining bytes in the buffer, if any,
		 * and emit an EOF marker as appropriate.  We ignore
		 * the error because we can't do anything about it at
		 * this point, and it can happen if we're getting
		 * disconnected.
		 */
		(void)zfilter_flush(stream, stream->wrbuf,
		    STREAM_FLUSH_CLOSING);
		deflateEnd(state);
		free(state);
		buf_free(stream->wrbuf);
		stream->wrbuf = zbuf;
	}
	free(zf);
}

static int
zfilter_flush(struct stream *stream, struct buf *buf, stream_flush_t how)
{
	struct zfilter *zf;
	struct buf *zbuf;
	z_stream *state;
	size_t lastin, lastout, ate, prod;
	int done, error, flags, rv;

	zf = stream->fdata;
	state = zf->wrstate;
	zbuf = zf->wrbuf;

	if (how == STREAM_FLUSH_NORMAL)
		flags = Z_SYNC_FLUSH;
	else
		flags = Z_FINISH;

	done = 0;
	rv = Z_OK;

again:
	/*
	 * According to zlib.h, we should have at least 6 bytes
	 * available when using deflate() with Z_SYNC_FLUSH.
	 */
	if ((buf_avail(zbuf) < 6 && flags == Z_SYNC_FLUSH) ||
	    rv == Z_BUF_ERROR || buf_avail(buf) == 0) {
		error = stream_flush_default(stream, zbuf, how);
		if (error)
			return (error);
	}

	state->next_in = (Bytef *)(buf->buf + buf->off);
	state->avail_in = buf_count(buf);
	state->next_out = (Bytef *)(zbuf->buf + zbuf->off + zbuf->in);
	state->avail_out = buf_avail(zbuf);
	lastin = state->avail_in;
	lastout = state->avail_out;
	rv = deflate(state, flags);
	if (rv != Z_BUF_ERROR && rv != Z_OK && rv != Z_STREAM_END)
		errx(1, "deflate: %s", state->msg);
	ate = lastin - state->avail_in;
	prod = lastout - state->avail_out;
	buf_less(buf, ate);
	buf_more(zbuf, prod);
	if ((flags == Z_SYNC_FLUSH && buf_count(buf) > 0) ||
	    (flags == Z_FINISH && rv != Z_STREAM_END) ||
	    (rv == Z_BUF_ERROR))
		goto again;

	assert(rv == Z_OK || (rv == Z_STREAM_END && flags == Z_FINISH));
	error = stream_flush_default(stream, zbuf, how);
	return (error);
}

static ssize_t
zfilter_fill(struct stream *stream, struct buf *buf)
{
	struct zfilter *zf;
	struct buf *zbuf;
	z_stream *state;
	size_t lastin, lastout, new;
	ssize_t n;
	int rv;

	zf = stream->fdata;
	state = zf->rdstate;
	zbuf = zf->rdbuf;

	assert(buf_avail(buf) > 0);
	if (buf_count(zbuf) == 0) {
		n = stream_fill_default(stream, zbuf);
		if (n <= 0)
			return (n);
	}
again:
	assert(buf_count(zbuf) > 0);
	state->next_in = (Bytef *)(zbuf->buf + zbuf->off);
	state->avail_in = buf_count(zbuf);
	state->next_out = (Bytef *)(buf->buf + buf->off + buf->in);
	state->avail_out = buf_avail(buf);
	lastin = state->avail_in;
	lastout = state->avail_out;
	rv = inflate(state, Z_SYNC_FLUSH);
	buf_less(zbuf, lastin - state->avail_in);
	new = lastout - state->avail_out;
	if (new == 0 && rv != Z_STREAM_END) {
		n = stream_fill_default(stream, zbuf);
		if (n == -1)
			return (-1);
		if (n == 0)
			return (0);
		goto again;
	}
	if (rv != Z_STREAM_END && rv != Z_OK)
		errx(1, "inflate: %s", state->msg);
	if (rv == Z_STREAM_END)
		zf->flags |= ZFILTER_EOF;
	buf_more(buf, new);
	return (new);
}

/* The MD5 stream filter implementation. */
static int
md5filter_init(struct stream *stream, void *data)
{
	struct md5filter *mf;

	mf = xmalloc(sizeof(struct md5filter));
	MD5_Init(&mf->ctx);
	mf->md5 = data;
	mf->lastc = ';';
	mf->state = PRINT;
	stream->fdata = mf;
	return (0);
}

static void
md5filter_fini(struct stream *stream)
{
	struct md5filter *mf;

	mf = stream->fdata;
	MD5_End(mf->md5, &mf->ctx);
	free(stream->fdata);
}

static ssize_t
md5filter_fill(struct stream *stream, struct buf *buf)
{
	ssize_t n;

	assert(buf_avail(buf) > 0);
	n = stream_fill_default(stream, buf);
	return (n);
}

static int
md5filter_flush(struct stream *stream, struct buf *buf, stream_flush_t how)
{
	struct md5filter *mf;
	int error;

	mf = stream->fdata;
	MD5_Update(&mf->ctx, buf->buf + buf->off, buf->in);
	error = stream_flush_default(stream, buf, how);
	return (error);
}

/* MD5 flush for RCS, where whitespaces are omitted. */
static int
md5rcsfilter_flush(struct stream *stream, struct buf *buf, stream_flush_t how)
{
	struct md5filter *mf;
	char *ptr, *end;
	char *start;
	char space[2];
	int error;

	mf = stream->fdata;
	space[0] = ' ';
	space[1] = '\0';
	ptr = buf->buf + buf->off;
	end = buf->buf + buf->off + buf->in;

#define IS_WS(var) ((var) == ' ' || (var) == '\n' || (var) == '\t' || \
                    (var) == '\010' || (var) == '\013' || (var) == '\f' || \
                    (var) == '\r')

#define IS_SPECIAL(var) ((var) == '$' || (var) == ',' || (var) == ':' || \
			 (var) == ';' || (var) == '@')

#define IS_PRINT(var) (!IS_WS(var) && (var) != '@')

	/* XXX: We can do better than this state machine. */
	while (ptr < end) {
		switch (mf->state) {
			/* Outside RCS statements. */
			case PRINT:
				start = ptr;
				while (ptr < end && IS_PRINT(*ptr)) {
					mf->lastc = *ptr;
					ptr++;
				}
				MD5_Update(&mf->ctx, start, (ptr - start));
				if (ptr < end) {
					if (*ptr == '@') {
						MD5_Update(&mf->ctx, ptr, 1);
						ptr++;
						mf->state = STRING;
					} else {
						mf->state = WS;
					}
				}
				break;
			case WS:
				while (ptr < end && IS_WS(*ptr)) {
					ptr++;
				}
				if (ptr < end) {
					if (*ptr == '@') {
						if (mf->lastc == '@') {
							MD5_Update(&mf->ctx,
							    space, 1);
						}
						MD5_Update(&mf->ctx, ptr, 1);
						ptr++;
						mf->state = STRING;
					} else {
						if (!IS_SPECIAL(*ptr) &&
						    !IS_SPECIAL(mf->lastc)) {
							MD5_Update(&mf->ctx,
							    space, 1);
						}
						mf->state = PRINT;
					}
				}
				break;
			case STRING:
				start = ptr;
				while (ptr < end && *ptr != '@') {
					ptr++;
				}
				MD5_Update(&mf->ctx, start, (ptr - start));
				if (ptr < end) {
					MD5_Update(&mf->ctx, ptr, 1);
					ptr++;
					mf->state = SEEN;
				}
				break;
			case SEEN:
				if (*ptr == '@') {
					MD5_Update(&mf->ctx, ptr, 1);
					ptr++;
					mf->state = STRING;
				} else if(IS_WS(*ptr)) {
					mf->lastc = '@';
					mf->state = WS;
				} else {
					mf->state = PRINT;
				}
				break;
			default:
				err(1, "Invalid state");
				break;
		}
	}

	error = stream_flush_default(stream, buf, how);
	return (error);
}

