istream.c revision 51d379cbc50242a13462d0fded50e013eb00cc07
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen#include "lib.h"
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen#include "ioloop.h"
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen#include "array.h"
7a7d2aa11e46195e2d92d6c337d7e78052a5ce67Timo Sirainen#include "str.h"
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen#include "memarea.h"
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen#include "istream-private.h"
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenstatic bool i_stream_is_buffer_invalid(const struct istream_private *stream);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenvoid i_stream_set_name(struct istream *stream, const char *name)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen i_free(stream->real_stream->iostream.name);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen stream->real_stream->iostream.name = i_strdup(name);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen}
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainenconst char *i_stream_get_name(struct istream *stream)
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen{
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen while (stream->real_stream->iostream.name == NULL) {
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen stream = stream->real_stream->parent;
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen if (stream == NULL)
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen return "";
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen }
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen return stream->real_stream->iostream.name;
f7539a17ea306191b53b8f5e752e228937df9ec3Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenstatic void i_stream_close_full(struct istream *stream, bool close_parents)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen io_stream_close(&stream->real_stream->iostream, close_parents);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen stream->closed = TRUE;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen if (stream->stream_errno == 0)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen stream->stream_errno = EPIPE;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenvoid i_stream_destroy(struct istream **stream)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen{
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (*stream == NULL)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen return;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen i_stream_close_full(*stream, FALSE);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen i_stream_unref(stream);
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen}
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenvoid i_stream_ref(struct istream *stream)
3b32bc12710240f86465a00fbb2bd1ef030e6c40Timo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen io_stream_ref(&stream->real_stream->iostream);
d22301419109ed4a38351715e6760011421dadecTimo Sirainen}
d22301419109ed4a38351715e6760011421dadecTimo Sirainen
d22301419109ed4a38351715e6760011421dadecTimo Sirainenvoid i_stream_unref(struct istream **stream)
d22301419109ed4a38351715e6760011421dadecTimo Sirainen{
d22301419109ed4a38351715e6760011421dadecTimo Sirainen struct istream_private *_stream;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (*stream == NULL)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen return;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen _stream = (*stream)->real_stream;
d22301419109ed4a38351715e6760011421dadecTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (_stream->iostream.refcount == 1) {
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (_stream->line_str != NULL)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen str_free(&_stream->line_str);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen i_stream_snapshot_free(&_stream->prev_snapshot);
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen }
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (!io_stream_unref(&(*stream)->real_stream->iostream)) {
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen i_stream_unref(&(*stream)->real_stream->parent);
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen io_stream_free(&(*stream)->real_stream->iostream);
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen }
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen *stream = NULL;
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen}
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen#undef i_stream_add_destroy_callback
b42697a5749b85659a24316d97f1c208d469e4e8Timo Sirainenvoid i_stream_add_destroy_callback(struct istream *stream,
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen istream_callback_t *callback, void *context)
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen{
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen io_stream_add_destroy_callback(&stream->real_stream->iostream,
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen callback, context);
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen}
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainenvoid i_stream_remove_destroy_callback(struct istream *stream,
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen void (*callback)())
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen{
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen io_stream_remove_destroy_callback(&stream->real_stream->iostream,
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen callback);
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen}
724b7fcf28c2547eb9c837d0e99241c0501dccf3Timo Sirainen
724b7fcf28c2547eb9c837d0e99241c0501dccf3Timo Sirainenint i_stream_get_fd(struct istream *stream)
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen struct istream_private *_stream = stream->real_stream;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen return _stream->fd;
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen}
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenconst char *i_stream_get_error(struct istream *stream)
3d6fdafca17c073606b63745ca8638e035e871f4Timo Sirainen{
3d6fdafca17c073606b63745ca8638e035e871f4Timo Sirainen struct istream *s;
3d6fdafca17c073606b63745ca8638e035e871f4Timo Sirainen
3d6fdafca17c073606b63745ca8638e035e871f4Timo Sirainen /* we'll only return errors for streams that have stream_errno set or
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen that have reached EOF. we might be returning unintended error
3c493c276f599d9b9cd10764876d648003046954Timo Sirainen otherwise. */
9ffdc9d18870acef2e4dde99715d8528ff4b080dTimo Sirainen if (stream->stream_errno == 0)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen return stream->eof ? "EOF" : "<no error>";
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainen for (s = stream; s != NULL; s = s->real_stream->parent) {
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (s->stream_errno == 0)
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainen break;
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen if (s->real_stream->iostream.error != NULL)
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainen return s->real_stream->iostream.error;
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen }
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen return strerror(stream->stream_errno);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen}
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenconst char *i_stream_get_disconnect_reason(struct istream *stream)
e15b305e90c9834734ccf35ed78f0ad29d570ee9Timo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen return io_stream_get_disconnect_reason(stream, NULL);
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen}
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenvoid i_stream_close(struct istream *stream)
563273bdac80393af63b9520cbf4d24cc0efd028Timo Sirainen{
c6afd726060aae56b6622c6c52aec10231c4bf1cTimo Sirainen i_stream_close_full(stream, TRUE);
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
1108376e39a19912e8394e64e19b1bc6f6691cf6Timo Sirainenvoid i_stream_set_init_buffer_size(struct istream *stream, size_t size)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen{
3e564425db51f3921ce4de11859777135fdedd15Timo Sirainen stream->real_stream->init_buffer_size = size;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen}
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenvoid i_stream_set_max_buffer_size(struct istream *stream, size_t max_size)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen{
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen io_stream_set_max_buffer_size(&stream->real_stream->iostream, max_size);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen}
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen
306cfd77100131c08b243de10f6d40500f4c27c6Timo Sirainensize_t i_stream_get_max_buffer_size(struct istream *stream)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen{
09c08fad8e7cc694a6c8d1711e67839acd3a2f04Timo Sirainen size_t max_size = 0;
438f12d7a776da695019114884b48188d94613efTimo Sirainen
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen do {
9ffdc9d18870acef2e4dde99715d8528ff4b080dTimo Sirainen if (max_size < stream->real_stream->max_buffer_size)
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen max_size = stream->real_stream->max_buffer_size;
2649b237dd4690575e75a30b2bf3b39ebd37b835Timo Sirainen stream = stream->real_stream->parent;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen } while (stream != NULL);
d3442384ca53d4b18a493db7dd0b000f470419cfTimo Sirainen return max_size;
6469cf211a57433335641725dc236ebb2b9fdd3bTimo Sirainen}
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainenvoid i_stream_set_return_partial_line(struct istream *stream, bool set)
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen{
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen stream->real_stream->return_nolf_line = set;
62041dfb7d6ac6e9c633a557075999cdfcff7bd5Timo Sirainen}
c0d069950af1dbc6a4e5c3de3bf2e437796e3ae0Timo Sirainen
c0d069950af1dbc6a4e5c3de3bf2e437796e3ae0Timo Sirainenvoid i_stream_set_persistent_buffers(struct istream *stream, bool set)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen do {
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen stream->real_stream->nonpersistent_buffers = !set;
5137d2d80255938a0f5fb8f3c1a21b34cf11ada3Timo Sirainen stream = stream->real_stream->parent;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen } while (stream != NULL);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen}
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainenvoid i_stream_set_blocking(struct istream *stream, bool blocking)
ecdce39e5ef4b62eefa9f5818f17d153fd5d710aTimo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen int prev_fd = -1;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen do {
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen stream->blocking = blocking;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (stream->real_stream->fd != -1 &&
e3aeeb634245e80d4f643f8d2eea11d6b72336d8Timo Sirainen stream->real_stream->fd != prev_fd) {
e3aeeb634245e80d4f643f8d2eea11d6b72336d8Timo Sirainen fd_set_nonblock(stream->real_stream->fd, !blocking);
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen prev_fd = stream->real_stream->fd;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen }
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen stream = stream->real_stream->parent;
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen } while (stream != NULL);
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainen}
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainen
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainenstatic void i_stream_update(struct istream_private *stream)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen{
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen if (stream->parent == NULL)
c6a57378d3c54988f525f81e19c0c5d132a0770dTimo Sirainen stream->access_counter++;
else {
stream->access_counter =
stream->parent->real_stream->access_counter;
stream->parent_expected_offset = stream->parent->v_offset;
}
}
static bool snapshot_has_memarea(struct istream_snapshot *snapshot,
struct memarea *memarea)
{
if (snapshot->old_memarea == memarea)
return TRUE;
if (snapshot->prev_snapshot != NULL)
return snapshot_has_memarea(snapshot->prev_snapshot, memarea);
return FALSE;
}
struct istream_snapshot *
i_stream_default_snapshot(struct istream_private *stream,
struct istream_snapshot *prev_snapshot)
{
struct istream_snapshot *snapshot;
if (stream->memarea != NULL) {
if (prev_snapshot != NULL) {
if (snapshot_has_memarea(prev_snapshot, stream->memarea))
return prev_snapshot;
}
/* This stream has a memarea. Reference it, so we can later on
rollback if needed. */
snapshot = i_new(struct istream_snapshot, 1);
snapshot->old_memarea = stream->memarea;
snapshot->prev_snapshot = prev_snapshot;
memarea_ref(snapshot->old_memarea);
return snapshot;
}
if (stream->parent == NULL) {
i_panic("%s is missing istream.snapshot() implementation",
i_stream_get_name(&stream->istream));
}
struct istream_private *_parent_stream =
stream->parent->real_stream;
return _parent_stream->snapshot(_parent_stream, prev_snapshot);
}
void i_stream_snapshot_free(struct istream_snapshot **_snapshot)
{
struct istream_snapshot *snapshot = *_snapshot;
if (*_snapshot == NULL)
return;
*_snapshot = NULL;
i_stream_snapshot_free(&snapshot->prev_snapshot);
if (snapshot->old_memarea != NULL)
memarea_unref(&snapshot->old_memarea);
i_free(snapshot);
}
ssize_t i_stream_read(struct istream *stream)
{
struct istream_private *_stream = stream->real_stream;
size_t old_size;
ssize_t ret;
if (unlikely(stream->closed || stream->stream_errno != 0)) {
stream->eof = TRUE;
errno = stream->stream_errno;
return -1;
}
stream->eof = FALSE;
if (_stream->parent != NULL)
i_stream_seek(_stream->parent, _stream->parent_expected_offset);
old_size = _stream->pos - _stream->skip;
ret = _stream->read(_stream);
i_assert(old_size <= _stream->pos - _stream->skip);
switch (ret) {
case -2:
i_assert(_stream->skip != _stream->pos);
break;
case -1:
if (stream->stream_errno != 0) {
/* error handling should be easier if we now just
assume the stream is now at EOF */
stream->eof = TRUE;
errno = stream->stream_errno;
} else {
i_assert(stream->eof);
i_assert(old_size == _stream->pos - _stream->skip);
}
break;
case 0:
i_assert(!stream->blocking);
break;
default:
i_assert(ret > 0);
i_assert(_stream->skip < _stream->pos);
i_assert((size_t)ret+old_size == _stream->pos - _stream->skip);
break;
}
if (stream->stream_errno != 0) {
/* error handling should be easier if we now just
assume the stream is now at EOF. Note that we could get here
even if read() didn't return -1, although that's a little
bit sloppy istream implementation. */
stream->eof = TRUE;
}
i_stream_update(_stream);
/* verify that parents' access_counters are valid. the parent's
i_stream_read() should guarantee this. */
i_assert(!i_stream_is_buffer_invalid(_stream));
return ret;
}
ssize_t i_stream_read_copy_from_parent(struct istream *istream)
{
struct istream_private *stream = istream->real_stream;
size_t pos;
ssize_t ret;
stream->pos -= stream->skip;
stream->skip = 0;
stream->buffer = i_stream_get_data(stream->parent, &pos);
if (pos > stream->pos)
ret = 0;
else do {
if ((ret = i_stream_read(stream->parent)) == -2) {
i_stream_update(stream);
return -2;
}
stream->istream.stream_errno = stream->parent->stream_errno;
stream->istream.eof = stream->parent->eof;
stream->buffer = i_stream_get_data(stream->parent, &pos);
/* check again, in case the parent stream had been seeked
backwards and the previous read() didn't get us far
enough. */
} while (pos <= stream->pos && ret > 0);
ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) :
(ret == 0 ? 0 : -1);
stream->pos = pos;
i_assert(ret != -1 || stream->istream.eof ||
stream->istream.stream_errno != 0);
i_stream_update(stream);
return ret;
}
void i_stream_free_buffer(struct istream_private *stream)
{
if (stream->memarea != NULL) {
memarea_unref(&stream->memarea);
stream->w_buffer = NULL;
} else if (stream->w_buffer != NULL) {
i_free_and_null(stream->w_buffer);
} else {
/* don't know how to free it */
return;
}
stream->buffer_size = 0;
}
void i_stream_skip(struct istream *stream, uoff_t count)
{
struct istream_private *_stream = stream->real_stream;
size_t data_size;
data_size = _stream->pos - _stream->skip;
if (count <= data_size) {
/* within buffer */
stream->v_offset += count;
_stream->skip += count;
if (_stream->nonpersistent_buffers &&
_stream->skip == _stream->pos) {
_stream->skip = _stream->pos = 0;
i_stream_free_buffer(_stream);
}
return;
}
/* have to seek forward */
count -= data_size;
_stream->skip = _stream->pos;
stream->v_offset += data_size;
if (unlikely(stream->closed || stream->stream_errno != 0))
return;
_stream->seek(_stream, stream->v_offset + count, FALSE);
}
static bool i_stream_can_optimize_seek(struct istream_private *stream)
{
if (stream->parent == NULL)
return TRUE;
/* use the fast route only if the parent stream hasn't been changed */
if (stream->access_counter !=
stream->parent->real_stream->access_counter)
return FALSE;
return i_stream_can_optimize_seek(stream->parent->real_stream);
}
void i_stream_seek(struct istream *stream, uoff_t v_offset)
{
struct istream_private *_stream = stream->real_stream;
if (v_offset >= stream->v_offset &&
i_stream_can_optimize_seek(_stream))
i_stream_skip(stream, v_offset - stream->v_offset);
else {
if (unlikely(stream->closed || stream->stream_errno != 0)) {
stream->eof = TRUE;
return;
}
stream->eof = FALSE;
_stream->seek(_stream, v_offset, FALSE);
}
i_stream_update(_stream);
}
void i_stream_seek_mark(struct istream *stream, uoff_t v_offset)
{
struct istream_private *_stream = stream->real_stream;
if (unlikely(stream->closed || stream->stream_errno != 0))
return;
stream->eof = FALSE;
_stream->seek(_stream, v_offset, TRUE);
i_stream_update(_stream);
}
void i_stream_sync(struct istream *stream)
{
struct istream_private *_stream = stream->real_stream;
if (unlikely(stream->closed || stream->stream_errno != 0))
return;
if (_stream->sync != NULL) {
_stream->sync(_stream);
i_stream_update(_stream);
}
}
int i_stream_stat(struct istream *stream, bool exact, const struct stat **st_r)
{
struct istream_private *_stream = stream->real_stream;
if (unlikely(stream->closed || stream->stream_errno != 0))
return -1;
if (_stream->stat(_stream, exact) < 0) {
stream->eof = TRUE;
return -1;
}
*st_r = &_stream->statbuf;
return 0;
}
int i_stream_get_size(struct istream *stream, bool exact, uoff_t *size_r)
{
struct istream_private *_stream = stream->real_stream;
if (unlikely(stream->closed || stream->stream_errno != 0))
return -1;
int ret;
if ((ret = _stream->get_size(_stream, exact, size_r)) < 0)
stream->eof = TRUE;
return ret;
}
bool i_stream_have_bytes_left(struct istream *stream)
{
return i_stream_get_data_size(stream) > 0 || !stream->eof;
}
bool i_stream_read_eof(struct istream *stream)
{
if (i_stream_get_data_size(stream) == 0)
(void)i_stream_read(stream);
return !i_stream_have_bytes_left(stream);
}
uoff_t i_stream_get_absolute_offset(struct istream *stream)
{
uoff_t abs_offset = stream->v_offset;
while (stream != NULL) {
abs_offset += stream->real_stream->start_offset;
stream = stream->real_stream->parent;
}
return abs_offset;
}
static char *i_stream_next_line_finish(struct istream_private *stream, size_t i)
{
char *ret;
size_t end;
if (i > 0 && stream->buffer[i-1] == '\r') {
end = i - 1;
stream->line_crlf = TRUE;
} else {
end = i;
stream->line_crlf = FALSE;
}
if (stream->buffer == stream->w_buffer) {
/* modify the buffer directly */
stream->w_buffer[end] = '\0';
ret = (char *)stream->w_buffer + stream->skip;
} else {
/* use a temporary string to return it */
if (stream->line_str == NULL)
stream->line_str = str_new(default_pool, 256);
str_truncate(stream->line_str, 0);
str_append_n(stream->line_str, stream->buffer + stream->skip,
end - stream->skip);
ret = str_c_modifiable(stream->line_str);
}
if (i < stream->pos)
i++;
stream->istream.v_offset += i - stream->skip;
stream->skip = i;
return ret;
}
static char *i_stream_last_line(struct istream_private *_stream)
{
if (_stream->istream.eof && _stream->skip != _stream->pos &&
_stream->return_nolf_line) {
/* the last line is missing LF and we want to return it. */
return i_stream_next_line_finish(_stream, _stream->pos);
}
return NULL;
}
char *i_stream_next_line(struct istream *stream)
{
struct istream_private *_stream = stream->real_stream;
const unsigned char *pos;
if (_stream->skip >= _stream->pos)
return NULL;
pos = memchr(_stream->buffer + _stream->skip, '\n',
_stream->pos - _stream->skip);
if (pos != NULL) {
return i_stream_next_line_finish(_stream,
pos - _stream->buffer);
} else {
return i_stream_last_line(_stream);
}
}
char *i_stream_read_next_line(struct istream *stream)
{
char *line;
for (;;) {
line = i_stream_next_line(stream);
if (line != NULL)
break;
switch (i_stream_read(stream)) {
case -2:
io_stream_set_error(&stream->real_stream->iostream,
"Line is too long (over %"PRIuSIZE_T
" bytes at offset %"PRIuUOFF_T")",
i_stream_get_data_size(stream), stream->v_offset);
stream->stream_errno = errno = ENOBUFS;
stream->eof = TRUE;
return NULL;
case -1:
return i_stream_last_line(stream->real_stream);
case 0:
return NULL;
}
}
return line;
}
bool i_stream_last_line_crlf(struct istream *stream)
{
return stream->real_stream->line_crlf;
}
static bool i_stream_is_buffer_invalid(const struct istream_private *stream)
{
if (stream->parent == NULL) {
/* the buffer can't point to parent, because it doesn't exist */
return FALSE;
}
if (stream->w_buffer != NULL) {
/* we can pretty safely assume that the stream is using its
own private buffer, so it can never become invalid. */
return FALSE;
}
if (stream->access_counter !=
stream->parent->real_stream->access_counter) {
/* parent has been modified behind this stream, we can't trust
that our buffer is valid */
return TRUE;
}
return i_stream_is_buffer_invalid(stream->parent->real_stream);
}
const unsigned char *
i_stream_get_data(struct istream *stream, size_t *size_r)
{
struct istream_private *_stream = stream->real_stream;
if (_stream->skip >= _stream->pos) {
*size_r = 0;
return uchar_empty_ptr;
}
if (i_stream_is_buffer_invalid(_stream)) {
/* This stream may be using parent's buffer directly as
_stream->buffer, but the parent stream has already been
modified indirectly. This means that the buffer might no
longer point to where we assume it points to. So we'll
just return the stream as empty until it's read again.
It's a bit ugly to suddenly drop data from the stream that
was already read, but since this happens only with shared
parent istreams the caller is hopefully aware enough that
something like this might happen. The other solutions would
be to a) try to automatically read the data back (but we
can't handle errors..) or b) always copy data to stream's
own buffer instead of pointing to parent's buffer (but this
causes data copying that is nearly always unnecessary). */
*size_r = 0;
/* if we had already read until EOF, mark the stream again as
not being at the end of file. */
if (stream->stream_errno == 0) {
_stream->skip = _stream->pos = 0;
stream->eof = FALSE;
}
return uchar_empty_ptr;
}
*size_r = _stream->pos - _stream->skip;
return _stream->buffer + _stream->skip;
}
size_t i_stream_get_data_size(struct istream *stream)
{
size_t size;
(void)i_stream_get_data(stream, &size);
return size;
}
unsigned char *i_stream_get_modifiable_data(struct istream *stream,
size_t *size_r)
{
struct istream_private *_stream = stream->real_stream;
if (_stream->skip >= _stream->pos || _stream->w_buffer == NULL) {
*size_r = 0;
return NULL;
}
*size_r = _stream->pos - _stream->skip;
return _stream->w_buffer + _stream->skip;
}
int i_stream_read_data(struct istream *stream, const unsigned char **data_r,
size_t *size_r, size_t threshold)
{
ssize_t ret = 0;
bool read_more = FALSE;
do {
*data_r = i_stream_get_data(stream, size_r);
if (*size_r > threshold)
return 1;
/* we need more data */
ret = i_stream_read(stream);
if (ret > 0)
read_more = TRUE;
} while (ret > 0);
*data_r = i_stream_get_data(stream, size_r);
if (ret == -2)
return -2;
if (ret == 0) {
/* need to read more */
i_assert(!stream->blocking);
return 0;
}
if (stream->eof) {
if (read_more) {
/* we read at least some new data */
return 0;
}
} else {
i_assert(stream->stream_errno != 0);
}
return -1;
}
void i_stream_compress(struct istream_private *stream)
{
if (stream->skip != stream->pos) {
memmove(stream->w_buffer, stream->w_buffer + stream->skip,
stream->pos - stream->skip);
}
stream->pos -= stream->skip;
stream->skip = 0;
}
static void i_stream_w_buffer_free(void *buf)
{
i_free(buf);
}
static void
i_stream_w_buffer_realloc(struct istream_private *stream, size_t old_size)
{
void *new_buffer;
if (stream->memarea != NULL &&
memarea_get_refcount(stream->memarea) == 1) {
/* Nobody else is referencing the memarea.
We can just reallocate it. */
memarea_free_without_callback(&stream->memarea);
new_buffer = i_realloc(stream->w_buffer, old_size,
stream->buffer_size);
} else {
new_buffer = i_malloc(stream->buffer_size);
memcpy(new_buffer, stream->w_buffer, old_size);
if (stream->memarea != NULL)
memarea_unref(&stream->memarea);
}
stream->w_buffer = new_buffer;
stream->buffer = new_buffer;
stream->memarea = memarea_init(stream->w_buffer, stream->buffer_size,
i_stream_w_buffer_free, new_buffer);
}
void i_stream_grow_buffer(struct istream_private *stream, size_t bytes)
{
size_t old_size, max_size;
old_size = stream->buffer_size;
stream->buffer_size = stream->pos + bytes;
if (stream->buffer_size <= stream->init_buffer_size)
stream->buffer_size = stream->init_buffer_size;
else
stream->buffer_size = nearest_power(stream->buffer_size);
max_size = i_stream_get_max_buffer_size(&stream->istream);
i_assert(max_size > 0);
if (stream->buffer_size > max_size)
stream->buffer_size = max_size;
if (stream->buffer_size <= old_size)
stream->buffer_size = old_size;
else
i_stream_w_buffer_realloc(stream, old_size);
}
bool i_stream_try_alloc(struct istream_private *stream,
size_t wanted_size, size_t *size_r)
{
i_assert(wanted_size > 0);
if (wanted_size > stream->buffer_size - stream->pos) {
if (stream->skip > 0) {
/* remove the unused bytes from beginning of buffer */
if (stream->memarea != NULL &&
memarea_get_refcount(stream->memarea) > 1) {
/* The memarea is still referenced. We can't
overwrite data until extra references are
gone. */
i_stream_w_buffer_realloc(stream, stream->buffer_size);
}
i_stream_compress(stream);
} else if (stream->buffer_size < i_stream_get_max_buffer_size(&stream->istream)) {
/* buffer is full - grow it */
i_stream_grow_buffer(stream, I_STREAM_MIN_SIZE);
}
}
*size_r = stream->buffer_size - stream->pos;
if (stream->try_alloc_limit > 0 &&
*size_r > stream->try_alloc_limit)
*size_r = stream->try_alloc_limit;
return *size_r > 0;
}
bool ATTR_NOWARN_UNUSED_RESULT
i_stream_try_alloc_avoid_compress(struct istream_private *stream,
size_t wanted_size, size_t *size_r)
{
size_t old_skip = stream->skip;
/* try first with skip=0, so no compression is done */
stream->skip = 0;
bool ret = i_stream_try_alloc(stream, wanted_size, size_r);
stream->skip = old_skip;
if (ret || old_skip == 0)
return ret;
/* it's full. try with compression. */
return i_stream_try_alloc(stream, wanted_size, size_r);
}
void *i_stream_alloc(struct istream_private *stream, size_t size)
{
size_t old_size, avail_size;
i_stream_try_alloc(stream, size, &avail_size);
if (avail_size < size) {
old_size = stream->buffer_size;
stream->buffer_size = nearest_power(stream->pos + size);
i_stream_w_buffer_realloc(stream, old_size);
i_stream_try_alloc(stream, size, &avail_size);
i_assert(avail_size >= size);
}
return stream->w_buffer + stream->pos;
}
bool i_stream_add_data(struct istream *_stream, const unsigned char *data,
size_t size)
{
struct istream_private *stream = _stream->real_stream;
size_t size2;
i_stream_try_alloc(stream, size, &size2);
if (size > size2)
return FALSE;
memcpy(stream->w_buffer + stream->pos, data, size);
stream->pos += size;
return TRUE;
}
void i_stream_set_input_pending(struct istream *stream, bool pending)
{
if (!pending)
return;
while (stream->real_stream->parent != NULL) {
i_assert(stream->real_stream->io == NULL);
stream = stream->real_stream->parent;
}
if (stream->real_stream->io != NULL)
io_set_pending(stream->real_stream->io);
}
void i_stream_switch_ioloop(struct istream *stream)
{
do {
if (stream->real_stream->switch_ioloop != NULL)
stream->real_stream->switch_ioloop(stream->real_stream);
stream = stream->real_stream->parent;
} while (stream != NULL);
}
void i_stream_set_io(struct istream *stream, struct io *io)
{
while (stream->real_stream->parent != NULL) {
i_assert(stream->real_stream->io == NULL);
stream = stream->real_stream->parent;
}
i_assert(stream->real_stream->io == NULL);
stream->real_stream->io = io;
}
void i_stream_unset_io(struct istream *stream, struct io *io)
{
while (stream->real_stream->parent != NULL) {
i_assert(stream->real_stream->io == NULL);
stream = stream->real_stream->parent;
}
i_assert(stream->real_stream->io == io);
stream->real_stream->io = NULL;
}
static void
i_stream_default_set_max_buffer_size(struct iostream_private *stream,
size_t max_size)
{
struct istream_private *_stream = (struct istream_private *)stream;
_stream->max_buffer_size = max_size;
if (_stream->parent != NULL)
i_stream_set_max_buffer_size(_stream->parent, max_size);
}
static void i_stream_default_close(struct iostream_private *stream,
bool close_parent)
{
struct istream_private *_stream = (struct istream_private *)stream;
if (close_parent && _stream->parent != NULL)
i_stream_close(_stream->parent);
}
static void i_stream_default_destroy(struct iostream_private *stream)
{
struct istream_private *_stream = (struct istream_private *)stream;
i_stream_free_buffer(_stream);
i_stream_unref(&_stream->parent);
}
static void
i_stream_default_seek_seekable(struct istream_private *stream,
uoff_t v_offset, bool mark ATTR_UNUSED)
{
stream->istream.v_offset = v_offset;
stream->skip = stream->pos = 0;
}
void i_stream_default_seek_nonseekable(struct istream_private *stream,
uoff_t v_offset, bool mark ATTR_UNUSED)
{
size_t available;
if (stream->istream.v_offset > v_offset)
i_panic("stream %s doesn't support seeking backwards",
i_stream_get_name(&stream->istream));
while (stream->istream.v_offset < v_offset) {
(void)i_stream_read(&stream->istream);
available = stream->pos - stream->skip;
if (available == 0) {
if (stream->istream.stream_errno != 0) {
/* read failed */
return;
}
io_stream_set_error(&stream->iostream,
"Can't seek to offset %"PRIuUOFF_T
", because we have data only up to offset %"
PRIuUOFF_T" (eof=%d)", v_offset,
stream->istream.v_offset, stream->istream.eof ? 1 : 0);
stream->istream.stream_errno = ESPIPE;
return;
}
if (available <= v_offset - stream->istream.v_offset)
i_stream_skip(&stream->istream, available);
else {
i_stream_skip(&stream->istream,
v_offset - stream->istream.v_offset);
}
}
}
static int
i_stream_default_stat(struct istream_private *stream, bool exact)
{
const struct stat *st;
if (stream->parent == NULL)
return stream->istream.stream_errno == 0 ? 0 : -1;
if (i_stream_stat(stream->parent, exact, &st) < 0) {
stream->istream.stream_errno = stream->parent->stream_errno;
return -1;
}
stream->statbuf = *st;
if (exact && !stream->stream_size_passthrough) {
/* exact size is not known, even if parent returned something */
stream->statbuf.st_size = -1;
}
return 0;
}
static int
i_stream_default_get_size(struct istream_private *stream,
bool exact, uoff_t *size_r)
{
if (stream->stat(stream, exact) < 0)
return -1;
if (stream->statbuf.st_size == -1)
return 0;
*size_r = stream->statbuf.st_size;
return 1;
}
void i_stream_init_parent(struct istream_private *_stream,
struct istream *parent)
{
_stream->access_counter = parent->real_stream->access_counter;
_stream->parent = parent;
_stream->parent_start_offset = parent->v_offset;
_stream->parent_expected_offset = parent->v_offset;
_stream->start_offset = parent->v_offset;
/* if parent stream is an istream-error, copy the error */
_stream->istream.stream_errno = parent->stream_errno;
_stream->istream.eof = parent->eof;
i_stream_ref(parent);
}
struct istream *
i_stream_create(struct istream_private *_stream, struct istream *parent, int fd)
{
_stream->fd = fd;
if (parent != NULL)
i_stream_init_parent(_stream, parent);
else if (_stream->memarea == NULL) {
/* The stream has no parent and no memarea yet. We'll assume
that it wants to be using memareas for the reads. */
_stream->memarea = memarea_init_empty();
}
_stream->istream.real_stream = _stream;
if (_stream->iostream.close == NULL)
_stream->iostream.close = i_stream_default_close;
if (_stream->iostream.destroy == NULL)
_stream->iostream.destroy = i_stream_default_destroy;
if (_stream->seek == NULL) {
_stream->seek = _stream->istream.seekable ?
i_stream_default_seek_seekable :
i_stream_default_seek_nonseekable;
}
if (_stream->stat == NULL)
_stream->stat = i_stream_default_stat;
if (_stream->get_size == NULL)
_stream->get_size = i_stream_default_get_size;
if (_stream->snapshot == NULL)
_stream->snapshot = i_stream_default_snapshot;
if (_stream->iostream.set_max_buffer_size == NULL) {
_stream->iostream.set_max_buffer_size =
i_stream_default_set_max_buffer_size;
}
if (_stream->init_buffer_size == 0)
_stream->init_buffer_size = I_STREAM_MIN_SIZE;
i_zero(&_stream->statbuf);
_stream->statbuf.st_size = -1;
_stream->statbuf.st_atime =
_stream->statbuf.st_mtime =
_stream->statbuf.st_ctime = ioloop_time;
io_stream_init(&_stream->iostream);
if (_stream->istream.stream_errno != 0)
_stream->istream.eof = TRUE;
return &_stream->istream;
}
struct istream *i_stream_create_error(int stream_errno)
{
struct istream_private *stream;
stream = i_new(struct istream_private, 1);
stream->istream.closed = TRUE;
stream->istream.readable_fd = FALSE;
stream->istream.blocking = TRUE;
stream->istream.seekable = TRUE;
stream->istream.eof = TRUE;
stream->istream.stream_errno = stream_errno;
i_stream_create(stream, NULL, -1);
i_stream_set_name(&stream->istream, "(error)");
return &stream->istream;
}
struct istream *
i_stream_create_error_str(int stream_errno, const char *fmt, ...)
{
struct istream *input;
va_list args;
va_start(args, fmt);
input = i_stream_create_error(stream_errno);
io_stream_set_verror(&input->real_stream->iostream, fmt, args);
va_end(args);
return input;
}