summaryrefslogtreecommitdiffstats
path: root/src/lib-fs/istream-metawrap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-fs/istream-metawrap.c')
-rw-r--r--src/lib-fs/istream-metawrap.c152
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);
+}