diff options
author | lulf <lulf@FreeBSD.org> | 2010-03-02 07:26:07 +0000 |
---|---|---|
committer | lulf <lulf@FreeBSD.org> | 2010-03-02 07:26:07 +0000 |
commit | c6aa3ac44645e36e9cc1e29aa598a706779c2c29 (patch) | |
tree | 34e14c6edd4db3c9e2addc2e9b8af07a970b71ed /usr.bin/csup/stream.c | |
parent | b86208843f144382d49d980b9908eb8618cfc5cd (diff) | |
download | FreeBSD-src-c6aa3ac44645e36e9cc1e29aa598a706779c2c29.zip FreeBSD-src-c6aa3ac44645e36e9cc1e29aa598a706779c2c29.tar.gz |
- Move csup away from contrib/ and into usr.bin/. Software is no longer
contributed, and main development is happening in the FreeBSD repo.
Suggested by: joel
Diffstat (limited to 'usr.bin/csup/stream.c')
-rw-r--r-- | usr.bin/csup/stream.c | 1303 |
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); +} + |