view src/plugins/zlib/istream-zlib.c @ 4891:6ab2712f1a93 HEAD

Only imap binary was actually working.
author Timo Sirainen <tss@iki.fi>
date Sun, 10 Dec 2006 14:35:02 +0200
parents 55df57c028d4
children 81394e71f92a
line wrap: on
line source

/* 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)

#define I_STREAM_MIN_SIZE 4096

struct zlib_istream {
	struct _istream istream;

	size_t max_buffer_size;

	int fd;
	gzFile *file;
	uoff_t cached_size;
	uoff_t seek_offset;

	unsigned int marked:1;
};

static void _close(struct _iostream *stream)
{
	struct zlib_istream *zstream = (struct zlib_istream *)stream;

	if (zstream->file != NULL) {
		gzclose(zstream->file);
		zstream->file = NULL;
	}
}

static void _destroy(struct _iostream *stream __attr_unused__)
{
	struct _istream *_stream = (struct _istream *) stream;

	p_free(_stream->iostream.pool, _stream->w_buffer);
}

static void _set_max_buffer_size(struct _iostream *stream, size_t max_size)
{
	struct zlib_istream *zstream = (struct zlib_istream *)stream;

	zstream->max_buffer_size = max_size;
}

static void i_stream_grow_buffer(struct _istream *stream, size_t bytes)
{
	struct zlib_istream *zstream = (struct zlib_istream *)stream;
	size_t old_size;

	old_size = stream->buffer_size;

	stream->buffer_size = stream->pos + bytes;
	if (stream->buffer_size <= I_STREAM_MIN_SIZE)
		stream->buffer_size = I_STREAM_MIN_SIZE;
	else {
		stream->buffer_size =
			pool_get_exp_grown_size(stream->iostream.pool,
						old_size, stream->buffer_size);
	}

	if (zstream->max_buffer_size > 0 &&
	    stream->buffer_size > zstream->max_buffer_size)
		stream->buffer_size = zstream->max_buffer_size;

	stream->buffer = stream->w_buffer =
		p_realloc(stream->iostream.pool, stream->w_buffer,
			  old_size, stream->buffer_size);
}

static void i_stream_compress(struct _istream *stream)
{
	memmove(stream->w_buffer, stream->w_buffer + stream->skip,
		stream->pos - stream->skip);
	stream->pos -= stream->skip;

	stream->skip = 0;
}

static ssize_t _read(struct _istream *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 (zstream->max_buffer_size == 0 ||
		    stream->buffer_size < zstream->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));
	ret = gzread(zstream->file, stream->w_buffer + stream->pos, size);
	if (ret == 0) {
		/* EOF */
		stream->istream.eof = TRUE;
		return -1;
	}

	if (ret < 0) {
		if (errno == EINTR || errno == EAGAIN)
			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 _seek(struct _istream *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 (_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 *_stat(struct _istream *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 (_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 _sync(struct _istream *stream)
{
	struct zlib_istream *zstream = (struct zlib_istream *) stream;

	zstream->cached_size = (uoff_t)-1;
}

struct istream *i_stream_create_zlib(int fd, pool_t pool)
{
	struct zlib_istream *zstream;

	zstream = p_new(pool, struct zlib_istream, 1);
	zstream->fd = fd;
	zstream->file = gzdopen(fd, "r");
	zstream->cached_size = (uoff_t)-1;
	zstream->max_buffer_size = DEFAULT_MAX_BUFFER_SIZE;

	zstream->istream.iostream.close = _close;
	zstream->istream.iostream.destroy = _destroy;
	zstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;

	zstream->istream.read = _read;
	zstream->istream.seek = _seek;
	zstream->istream.stat = _stat;
	zstream->istream.sync = _sync;

	zstream->istream.istream.seekable = TRUE;
	return _i_stream_create(&zstream->istream, pool, fd, 0);
}