view src/lib/istream-file.c @ 1741:9df02b1533b3 HEAD

Removed most of the license comments from src/lib/*.c. It's just fine to keep them in a single COPYING.MIT file. Changed a few other comments as well.
author Timo Sirainen <tss@iki.fi>
date Wed, 27 Aug 2003 00:18:16 +0300
parents 19d58921378f
children 920307c42b17
line wrap: on
line source

/* Copyright (c) 2002-2003 Timo Sirainen */

/* @UNSAFE: whole file */

#include "lib.h"
#include "alarm-hup.h"
#include "istream-internal.h"
#include "network.h"

#include <time.h>
#include <unistd.h>
#include <sys/stat.h>

#define I_STREAM_MIN_SIZE 4096

#define STREAM_IS_BLOCKING(fstream) \
	((fstream)->timeout_msecs != 0)

struct file_istream {
	struct _istream istream;

	size_t max_buffer_size;
	uoff_t skip_left;

	int timeout_msecs;
	void (*timeout_cb)(void *);
	void *timeout_context;

	unsigned int file:1;
	unsigned int autoclose_fd:1;
};

static void _close(struct _iostream *stream)
{
	struct file_istream *fstream = (struct file_istream *) stream;
	struct _istream *_stream = (struct _istream *) stream;

	if (fstream->autoclose_fd && _stream->fd != -1) {
		if (close(_stream->fd) < 0)
			i_error("file_istream.close() failed: %m");
		_stream->fd = -1;
	}
}

static void _destroy(struct _iostream *stream)
{
	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 file_istream *fstream = (struct file_istream *) stream;

	fstream->max_buffer_size = max_size;
}

static void _set_blocking(struct _iostream *stream, int timeout_msecs,
			  void (*timeout_cb)(void *), void *context)
{
	struct file_istream *fstream = (struct file_istream *) stream;

	fstream->timeout_msecs = timeout_msecs;
	fstream->timeout_cb = timeout_cb;
	fstream->timeout_context = context;

	net_set_nonblock(fstream->istream.fd, timeout_msecs == 0);

	if (timeout_msecs != 0)
		alarm_hup_init();
}

static void i_stream_grow_buffer(struct _istream *stream, size_t bytes)
{
	struct file_istream *fstream = (struct file_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 = nearest_power(stream->buffer_size);

	if (fstream->max_buffer_size > 0 &&
	    stream->buffer_size > fstream->max_buffer_size)
		stream->buffer_size = fstream->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 file_istream *fstream = (struct file_istream *) stream;
	time_t timeout_time;
	uoff_t read_limit;
	size_t size;
	ssize_t ret;

	if (stream->istream.closed)
		return -1;

	if (fstream->skip_left > 0) {
		i_assert(stream->skip == stream->pos);

		if (fstream->file) {
			/* we're a file, so we can lseek() */
			i_stream_seek(&stream->istream,
				      stream->istream.v_offset);
			if (stream->istream.closed)
				return -1;
		}
	}

	stream->istream.stream_errno = 0;

	if (stream->pos == stream->buffer_size) {
		if (stream->skip > 0) {
			/* remove the unused bytes from beginning of buffer */
                        i_stream_compress(stream);
		} else if (fstream->max_buffer_size == 0 ||
			   stream->buffer_size < fstream->max_buffer_size) {
			/* buffer is full - grow it */
			i_stream_grow_buffer(stream, I_STREAM_MIN_SIZE);
		}

		if (stream->pos == stream->buffer_size)
			return -2; /* buffer full */
	}

	size = stream->buffer_size - stream->pos;
	if (stream->istream.v_limit > 0) {
		i_assert(stream->istream.v_limit >= stream->istream.v_offset);

		read_limit = stream->istream.v_limit -
			stream->istream.v_offset + fstream->skip_left;
		if (read_limit <= stream->pos - stream->skip) {
			/* virtual limit reached == EOF */
			return -1;
		}

		read_limit -= stream->pos - stream->skip;
		if (size > read_limit)
			size = read_limit;
	}

	timeout_time = GET_TIMEOUT_TIME(fstream);

	ret = -1;
	do {
		if (ret == 0 && timeout_time > 0 && time(NULL) > timeout_time) {
			/* timeouted */
			if (fstream->timeout_cb != NULL)
				fstream->timeout_cb(fstream->timeout_context);
			stream->istream.stream_errno = EAGAIN;
			return -1;
		}

		ret = read(stream->fd, stream->w_buffer + stream->pos, size);
		if (ret == 0) {
			/* EOF */
			stream->istream.stream_errno = 0;
			return -1;
		}

		if (ret < 0) {
			if (errno == ECONNRESET || errno == ETIMEDOUT) {
				/* treat as disconnection */
				stream->istream.stream_errno = 0;
				return -1;
			}

			if (errno == EINTR || errno == EAGAIN)
				ret = 0;
			else {
				stream->istream.stream_errno = errno;
				return -1;
			}
		}

		if (ret > 0 && fstream->skip_left > 0) {
			if (fstream->skip_left >= (size_t)ret) {
				fstream->skip_left -= ret;
				ret = 0;
			} else {
				ret -= fstream->skip_left;
				stream->pos += fstream->skip_left;
				stream->skip += fstream->skip_left;
				fstream->skip_left = 0;
			}
		}
	} while (ret == 0 && STREAM_IS_BLOCKING(fstream));

	stream->pos += ret;
	return ret;
}

static void _skip(struct _istream *stream, uoff_t count)
{
	struct file_istream *fstream = (struct file_istream *) stream;

	fstream->skip_left += count - (stream->pos - stream->skip);
	stream->skip = stream->pos = 0;
	stream->istream.v_offset += count;
}

static void _seek(struct _istream *stream, uoff_t v_offset)
{
	struct file_istream *fstream = (struct file_istream *) stream;
	uoff_t real_offset;
	off_t ret;

	real_offset = stream->istream.start_offset + v_offset;
	if (real_offset > OFF_T_MAX) {
		stream->istream.stream_errno = EOVERFLOW;
		ret = -1;
	} else {
		ret = lseek(stream->fd, (off_t)real_offset, SEEK_SET);
		if (ret < 0)
			stream->istream.stream_errno = errno;
		else if (ret != (off_t)real_offset) {
			stream->istream.stream_errno = EINVAL;
			ret = -1;
		} else {
			stream->skip = stream->pos = 0;
			fstream->skip_left = 0;
		}
	}

	if (ret < 0)
                i_stream_close(&stream->istream);
	else {
		stream->istream.stream_errno = 0;
		stream->istream.v_offset = v_offset;
	}
}

struct istream *i_stream_create_file(int fd, pool_t pool,
				     size_t max_buffer_size, int autoclose_fd)
{
	struct file_istream *fstream;
	struct stat st;

	fstream = p_new(pool, struct file_istream, 1);
	fstream->max_buffer_size = max_buffer_size;
	fstream->autoclose_fd = autoclose_fd;

	fstream->istream.iostream.close = _close;
	fstream->istream.iostream.destroy = _destroy;
	fstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
	fstream->istream.iostream.set_blocking = _set_blocking;

	fstream->istream.read = _read;
	fstream->istream.skip_count = _skip;
	fstream->istream.seek = _seek;

	/* get size of fd if it's a file */
	if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode))
		st.st_size = 0;
	else
		fstream->file = TRUE;

	return _i_stream_create(&fstream->istream, pool, fd, 0,
				(uoff_t)st.st_size);
}