summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup/stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup/stream.c')
-rw-r--r--usr.bin/csup/stream.c1303
1 files changed, 1303 insertions, 0 deletions
diff --git a/usr.bin/csup/stream.c b/usr.bin/csup/stream.c
new file mode 100644
index 0000000..aa229b4
--- /dev/null
+++ b/usr.bin/csup/stream.c
@@ -0,0 +1,1303 @@
+/*-
+ * 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$
+ */
+
+#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);
+}
+
OpenPOWER on IntegriCloud