istream-zlib.c revision a94936bafd127680184da114c6a177b37ff656e5
/* Copyright (C) 2005 Timo Sirainen */
#include "lib.h"
#include "istream-internal.h"
#include "istream-zlib.h"
#include <zlib.h>
/* Default maximum buffer size. Seeking backwards is very expensive, so keep
this pretty large */
#define DEFAULT_MAX_BUFFER_SIZE (1024*1024)
struct zlib_istream {
struct istream_private istream;
int fd;
gzFile *file;
uoff_t cached_size;
uoff_t seek_offset;
unsigned int marked:1;
};
static void i_stream_zlib_close(struct iostream_private *stream)
{
struct zlib_istream *zstream = (struct zlib_istream *)stream;
if (zstream->file != NULL) {
gzclose(zstream->file);
zstream->file = NULL;
}
}
static void i_stream_zlib_destroy(struct iostream_private *stream ATTR_UNUSED)
{
struct istream_private *_stream = (struct istream_private *)stream;
i_free(_stream->w_buffer);
}
static ssize_t i_stream_zlib_read(struct istream_private *stream)
{
struct zlib_istream *zstream = (struct zlib_istream *)stream;
size_t size;
int ret;
if (stream->istream.closed)
return -1;
stream->istream.stream_errno = 0;
if (stream->pos == stream->buffer_size) {
if (!zstream->marked && stream->skip > 0) {
/* don't try to keep anything cached if we don't
have a seek mark. */
i_stream_compress(stream);
}
if (stream->max_buffer_size == 0 ||
stream->buffer_size < stream->max_buffer_size) {
/* buffer is full - grow it */
i_stream_grow_buffer(stream, I_STREAM_MIN_SIZE);
}
if (stream->pos == stream->buffer_size) {
if (stream->skip > 0) {
/* lose our buffer cache */
i_stream_compress(stream);
}
if (stream->pos == stream->buffer_size)
return -2; /* buffer full */
}
}
size = stream->buffer_size - stream->pos;
ret = -1;
i_assert(zstream->seek_offset == stream->istream.v_offset +
(stream->pos - stream->skip));
do {
ret = gzread(zstream->file, stream->w_buffer + stream->pos,
size);
} while (ret < 0 && errno == EINTR && stream->istream.blocking);
if (ret == 0) {
/* EOF */
stream->istream.eof = TRUE;
return -1;
}
if (ret < 0) {
if (errno == EAGAIN) {
i_assert(!stream->istream.blocking);
ret = 0;
} else {
stream->istream.eof = TRUE;
stream->istream.stream_errno = errno;
return -1;
}
}
zstream->seek_offset += ret;
stream->pos += ret;
i_assert(ret != 0);
return ret;
}
static void
i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
{
struct zlib_istream *zstream = (struct zlib_istream *) stream;
uoff_t start_offset = stream->istream.v_offset - stream->skip;
stream->istream.stream_errno = 0;
if (v_offset < start_offset) {
/* have to seek backwards */
gzseek(zstream->file, v_offset, SEEK_SET);
zstream->seek_offset = v_offset;
stream->skip = stream->pos = 0;
stream->istream.v_offset = v_offset;
} else if (v_offset <= start_offset + stream->pos) {
/* seeking backwards within what's already cached */
stream->skip = v_offset - start_offset;
stream->istream.v_offset = v_offset;
} else {
/* read and cache forward */
do {
size_t avail = stream->pos - stream->skip;
if (stream->istream.v_offset + avail >= v_offset) {
i_stream_skip(&stream->istream,
v_offset -
stream->istream.v_offset);
break;
}
i_stream_skip(&stream->istream, avail);
} while (i_stream_zlib_read(stream) >= 0);
if (stream->istream.v_offset != v_offset) {
/* some failure, we've broken it */
if (stream->istream.stream_errno != 0) {
i_error("zlib_istream.seek() failed: %s",
strerror(stream->istream.stream_errno));
i_stream_close(&stream->istream);
} else {
/* unexpected EOF. allow it since we may just
want to check if there's anything.. */
i_assert(stream->istream.eof);
}
}
}
if (mark) {
i_stream_compress(stream);
zstream->marked = TRUE;
}
}
static const struct stat *
i_stream_zlib_stat(struct istream_private *stream, bool exact)
{
struct zlib_istream *zstream = (struct zlib_istream *) stream;
size_t size;
if (fstat(zstream->fd, &stream->statbuf) < 0) {
i_error("zlib_istream.fstat() failed: %m");
return NULL;
}
if (!exact)
return &stream->statbuf;
if (zstream->cached_size == (uoff_t)-1) {
uoff_t old_offset = stream->istream.v_offset;
do {
(void)i_stream_get_data(&stream->istream, &size);
i_stream_skip(&stream->istream, size);
} while (i_stream_zlib_read(stream) > 0);
zstream->cached_size = stream->istream.v_offset;
i_stream_seek(&stream->istream, old_offset);
}
stream->statbuf.st_size = zstream->cached_size;
return &stream->statbuf;
}
static void i_stream_zlib_sync(struct istream_private *stream)
{
struct zlib_istream *zstream = (struct zlib_istream *) stream;
zstream->cached_size = (uoff_t)-1;
}
struct istream *i_stream_create_zlib(int fd)
{
struct zlib_istream *zstream;
struct stat st;
zstream = i_new(struct zlib_istream, 1);
zstream->fd = fd;
zstream->file = gzdopen(fd, "r");
zstream->cached_size = (uoff_t)-1;
zstream->istream.iostream.close = i_stream_zlib_close;
zstream->istream.iostream.destroy = i_stream_zlib_destroy;
zstream->istream.max_buffer_size = DEFAULT_MAX_BUFFER_SIZE;
zstream->istream.read = i_stream_zlib_read;
zstream->istream.seek = i_stream_zlib_seek;
zstream->istream.stat = i_stream_zlib_stat;
zstream->istream.sync = i_stream_zlib_sync;
/* if it's a file, set the flags properly */
if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
zstream->istream.istream.blocking = TRUE;
zstream->istream.istream.seekable = TRUE;
}
return i_stream_create(&zstream->istream, fd, 0);
}