summaryrefslogtreecommitdiffstats
path: root/src/lib/istream-try.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/istream-try.c')
-rw-r--r--src/lib/istream-try.c165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/lib/istream-try.c b/src/lib/istream-try.c
new file mode 100644
index 0000000..3b83a7f
--- /dev/null
+++ b/src/lib/istream-try.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-private.h"
+#include "istream-try.h"
+
+struct try_istream {
+ struct istream_private istream;
+
+ size_t min_buffer_full_size;
+ unsigned int try_input_count;
+ struct istream **try_input;
+ unsigned int try_idx;
+
+ struct istream *final_input;
+};
+
+static void i_stream_unref_try_inputs(struct try_istream *tstream)
+{
+ for (unsigned int i = 0; i < tstream->try_input_count; i++) {
+ if (tstream->try_input[i] != NULL)
+ i_stream_unref(&tstream->try_input[i]);
+ }
+ tstream->try_input_count = 0;
+ i_free(tstream->try_input);
+}
+
+static void i_stream_try_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct try_istream *tstream =
+ container_of(stream, struct try_istream, istream.iostream);
+
+ if (close_parent) {
+ if (tstream->istream.parent != NULL)
+ i_stream_close(tstream->istream.parent);
+ for (unsigned int i = 0; i < tstream->try_input_count; i++) {
+ if (tstream->try_input[i] != NULL)
+ i_stream_close(tstream->try_input[i]);
+ }
+ }
+ i_stream_unref_try_inputs(tstream);
+}
+
+static bool
+i_stream_try_is_buffer_full(struct try_istream *tstream,
+ struct istream *try_input)
+{
+ /* See if one of the parent istreams have their buffer full.
+ This is mainly intended to check with istream-tee whether its
+ parent is full. That means that the try_input has already seen
+ a full buffer of input, but it hasn't decided to return anything
+ yet. But it also hasn't failed, so we'll assume that the input is
+ correct for it and it simply needs a lot more input before it can
+ return anything (e.g. istream-bzlib).
+
+ Note that it's common for buffer_size to be 0 for all parents. This
+ could be e.g. because the root is istream-concat, which breaks the
+ parent hierarchy since it has multiple parents. So the buffer_size
+ check can be thought of just as an optional extra check that
+ sometimes works and sometimes doesn't.
+
+ Note that we don't check whether skip==pos. An istream could be
+ reading its buffer full without skipping over anything. */
+ while (try_input->real_stream->parent != NULL) {
+ try_input = try_input->real_stream->parent;
+ if (try_input->real_stream->pos >= try_input->real_stream->buffer_size &&
+ try_input->real_stream->pos >= tstream->min_buffer_full_size)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int i_stream_try_detect(struct try_istream *tstream)
+{
+ int ret;
+
+ for (; tstream->try_idx < tstream->try_input_count; tstream->try_idx++) {
+ struct istream *try_input =
+ tstream->try_input[tstream->try_idx];
+
+ ret = i_stream_read(try_input);
+ if (ret == 0 && i_stream_try_is_buffer_full(tstream, try_input))
+ ret = 1;
+ if (ret > 0) {
+ i_stream_init_parent(&tstream->istream, try_input);
+ i_stream_unref_try_inputs(tstream);
+ return 1;
+ }
+ if (ret == 0)
+ return 0;
+ if (try_input->stream_errno == 0) {
+ /* empty file */
+ tstream->istream.istream.eof = TRUE;
+ return -1;
+ }
+ if (try_input->stream_errno != EINVAL) {
+ tstream->istream.istream.stream_errno =
+ try_input->stream_errno;
+ io_stream_set_error(&tstream->istream.iostream,
+ "Unexpected error while detecting stream format: %s",
+ i_stream_get_error(try_input));
+ return -1;
+ }
+ }
+
+ /* All streams failed with EINVAL. */
+ io_stream_set_error(&tstream->istream.iostream,
+ "Failed to detect stream format");
+ tstream->istream.istream.stream_errno = EINVAL;
+ return -1;
+}
+
+static ssize_t
+i_stream_try_read(struct istream_private *stream)
+{
+ struct try_istream *tstream =
+ container_of(stream, struct try_istream, istream);
+ int ret;
+
+ if (stream->parent == NULL) {
+ if ((ret = i_stream_try_detect(tstream)) <= 0)
+ return ret;
+ }
+
+ i_stream_seek(stream->parent, stream->parent_start_offset +
+ stream->istream.v_offset);
+ return i_stream_read_copy_from_parent(&stream->istream);
+}
+
+struct istream *istream_try_create(struct istream *const input[],
+ size_t min_buffer_full_size)
+{
+ struct try_istream *tstream;
+ unsigned int count;
+ size_t max_buffer_size = I_STREAM_MIN_SIZE;
+ bool blocking = TRUE, seekable = TRUE;
+
+ for (count = 0; input[count] != NULL; count++) {
+ max_buffer_size = I_MAX(max_buffer_size,
+ i_stream_get_max_buffer_size(input[count]));
+ if (!input[count]->blocking)
+ blocking = FALSE;
+ if (!input[count]->seekable)
+ seekable = FALSE;
+ i_stream_ref(input[count]);
+ }
+ i_assert(count != 0);
+
+ tstream = i_new(struct try_istream, 1);
+ tstream->min_buffer_full_size = min_buffer_full_size;
+ tstream->try_input_count = count;
+ tstream->try_input = p_memdup(default_pool, input,
+ sizeof(*input) * count);
+
+ tstream->istream.iostream.close = i_stream_try_close;
+
+ tstream->istream.max_buffer_size = max_buffer_size;
+ tstream->istream.read = i_stream_try_read;
+
+ tstream->istream.istream.readable_fd = FALSE;
+ tstream->istream.istream.blocking = blocking;
+ tstream->istream.istream.seekable = seekable;
+ return i_stream_create(&tstream->istream, NULL, -1, 0);
+}