mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-28 11:57:28 +00:00
4b6675a6f8
contributed, and main development is happening in the FreeBSD repo. Suggested by: joel
1304 lines
29 KiB
C
1304 lines
29 KiB
C
/*-
|
|
* 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);
|
|
}
|
|
|