diff options
Diffstat (limited to 'src/lib-fs/istream-metawrap.c')
-rw-r--r-- | src/lib-fs/istream-metawrap.c | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/lib-fs/istream-metawrap.c b/src/lib-fs/istream-metawrap.c new file mode 100644 index 0000000..ac2e8fb --- /dev/null +++ b/src/lib-fs/istream-metawrap.c @@ -0,0 +1,152 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream-private.h" +#include "istream-metawrap.h" + +#define METAWRAP_MAX_METADATA_LINE_LEN 8192 + +struct metawrap_istream { + struct istream_private istream; + metawrap_callback_t *callback; + void *context; + + uoff_t start_offset, pending_seek; + bool in_metadata; +}; + +static int metadata_header_read(struct metawrap_istream *mstream) +{ + char *line, *p; + + while ((line = i_stream_read_next_line(mstream->istream.parent)) != NULL) { + if (*line == '\0') { + mstream->callback(NULL, NULL, mstream->context); + return 1; + } + p = strchr(line, ':'); + if (p == NULL) { + io_stream_set_error(&mstream->istream.iostream, + "Metadata header line is missing ':' at offset %"PRIuUOFF_T, + mstream->istream.istream.v_offset); + mstream->istream.istream.stream_errno = EINVAL; + return -1; + } + *p++ = '\0'; + mstream->callback(line, p, mstream->context); + } + if (mstream->istream.parent->eof) { + if (mstream->istream.parent->stream_errno != 0) { + mstream->istream.istream.stream_errno = + mstream->istream.parent->stream_errno; + } else { + io_stream_set_error(&mstream->istream.iostream, + "Metadata header is missing ending line at offset %"PRIuUOFF_T, + mstream->istream.istream.v_offset); + mstream->istream.istream.stream_errno = EPIPE; + return -1; + } + mstream->istream.istream.eof = TRUE; + return -1; + } + i_assert(!mstream->istream.parent->blocking); + return 0; +} + +static ssize_t i_stream_metawrap_read(struct istream_private *stream) +{ + struct metawrap_istream *mstream = (struct metawrap_istream *)stream; + int ret; + + i_stream_seek(stream->parent, mstream->start_offset + + stream->istream.v_offset); + + if (mstream->in_metadata) { + size_t prev_max_size = i_stream_get_max_buffer_size(stream->parent); + + i_stream_set_max_buffer_size(stream->parent, METAWRAP_MAX_METADATA_LINE_LEN); + ret = metadata_header_read(mstream); + i_stream_set_max_buffer_size(stream->parent, prev_max_size); + + i_assert(stream->istream.v_offset == 0); + mstream->start_offset = stream->parent->v_offset; + if (ret <= 0) + return ret; + /* this stream is kind of silently skipping over the metadata */ + stream->start_offset += mstream->start_offset; + mstream->in_metadata = FALSE; + if (mstream->pending_seek != 0) { + i_stream_seek(&stream->istream, mstream->pending_seek); + return i_stream_read_memarea(&stream->istream); + } + } + /* after metadata header it's all just passthrough */ + return i_stream_read_copy_from_parent(&stream->istream); +} + +static void +i_stream_metawrap_seek(struct istream_private *stream, + uoff_t v_offset, bool mark ATTR_UNUSED) +{ + struct metawrap_istream *mstream = (struct metawrap_istream *)stream; + + if (!mstream->in_metadata) { + /* already read through metadata. we can skip directly. */ + stream->istream.v_offset = v_offset; + mstream->pending_seek = 0; + } else { + /* we need to read through the metadata first */ + mstream->pending_seek = v_offset; + stream->istream.v_offset = 0; + } + stream->skip = stream->pos = 0; +} + +static int i_stream_metawrap_stat(struct istream_private *stream, bool exact) +{ + struct metawrap_istream *mstream = (struct metawrap_istream *)stream; + const struct stat *st; + int ret; + + if (i_stream_stat(stream->parent, exact, &st) < 0) { + stream->istream.stream_errno = stream->parent->stream_errno; + return -1; + } + stream->statbuf = *st; + + if (mstream->in_metadata) { + ret = i_stream_read_memarea(&stream->istream); + if (ret < 0 && stream->istream.stream_errno != 0) + return -1; + if (ret == 0) { + stream->statbuf.st_size = -1; + return 0; + } + } + i_assert((uoff_t)stream->statbuf.st_size >= mstream->start_offset); + stream->statbuf.st_size -= mstream->start_offset; + return 0; +} + +struct istream * +i_stream_create_metawrap(struct istream *input, + metawrap_callback_t *callback, void *context) +{ + struct metawrap_istream *mstream; + + mstream = i_new(struct metawrap_istream, 1); + mstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + + mstream->istream.read = i_stream_metawrap_read; + mstream->istream.seek = i_stream_metawrap_seek; + mstream->istream.stat = input->seekable ? i_stream_metawrap_stat : NULL; + + mstream->istream.istream.readable_fd = input->readable_fd; + mstream->istream.istream.blocking = input->blocking; + mstream->istream.istream.seekable = input->seekable; + mstream->in_metadata = TRUE; + mstream->callback = callback; + mstream->context = context; + return i_stream_create(&mstream->istream, input, + i_stream_get_fd(input), 0); +} |