iostream-temp.c revision c076ad69e28e7d41af83ada84e12019793ffcfa2
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "lib.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "buffer.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "str.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "safe-mkstemp.h"
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include "write-full.h"
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen#include "istream-private.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "ostream-private.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "iostream-temp.h"
03f5c621d06d6b6d77a145196c9633a7aa64dc78Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen#include <unistd.h>
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
da2aa032ccfa8e7e4a4380ef738014549f4d2c2dTimo Sirainen#define IOSTREAM_TEMP_MAX_BUF_SIZE_DEFAULT (1024*128)
da2aa032ccfa8e7e4a4380ef738014549f4d2c2dTimo Sirainen
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainenstruct temp_ostream {
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen struct ostream_private ostream;
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen char *temp_path_prefix;
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen enum iostream_temp_flags flags;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen size_t max_mem_size;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen struct istream *dupstream;
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen uoff_t dupstream_offset, dupstream_start_offset;
2526d52441ef368215ab6bf04fd0356d3b09d235Timo Sirainen char *name;
2526d52441ef368215ab6bf04fd0356d3b09d235Timo Sirainen
43834f87bf431198f986e86052a4f6e558fdb07dTimo Sirainen buffer_t *buf;
43834f87bf431198f986e86052a4f6e558fdb07dTimo Sirainen int fd;
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen bool fd_tried;
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen uoff_t fd_size;
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen};
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstatic void
6ef7e31619edfaa17ed044b45861d106a86191efTimo Siraineno_stream_temp_close(struct iostream_private *stream,
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen bool close_parent ATTR_UNUSED)
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody{
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody struct temp_ostream *tstream = (struct temp_ostream *)stream;
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody if (tstream->fd != -1)
10c96a244935de4add8213ba0b894178dfb889a5Timo Sirainen i_close_fd(&tstream->fd);
bdcb00145ad87765e3fd22d4ebc4d2c029a326b9Timo Sirainen if (tstream->buf != NULL)
bdcb00145ad87765e3fd22d4ebc4d2c029a326b9Timo Sirainen buffer_free(&tstream->buf);
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen i_free(tstream->temp_path_prefix);
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen i_free(tstream->name);
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen}
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic int o_stream_temp_move_to_fd(struct temp_ostream *tstream)
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen{
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen string_t *path;
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen if (tstream->fd_tried)
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen return -1;
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen tstream->fd_tried = TRUE;
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen path = t_str_new(128);
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen str_append(path, tstream->temp_path_prefix);
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen tstream->fd = safe_mkstemp_hostpid(path, 0600, (uid_t)-1, (gid_t)-1);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (tstream->fd == -1) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_error("safe_mkstemp(%s) failed: %m", str_c(path));
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen return -1;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen }
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen if (i_unlink(str_c(path)) < 0) {
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen i_close_fd(&tstream->fd);
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen return -1;
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen }
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen if (write_full(tstream->fd, tstream->buf->data, tstream->buf->used) < 0) {
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen i_error("write(%s) failed: %m", str_c(path));
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen i_close_fd(&tstream->fd);
27a44fcfd8d19bffe0f267f20a2b5d3fe7600fddTimo Sirainen return -1;
27a44fcfd8d19bffe0f267f20a2b5d3fe7600fddTimo Sirainen }
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen /* make the fd available also to o_stream_get_fd(),
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen e.g. for unit tests */
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen tstream->ostream.fd = tstream->fd;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen tstream->fd_size = tstream->buf->used;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen buffer_free(&tstream->buf);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen return 0;
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen}
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic ssize_t
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Siraineno_stream_temp_fd_sendv(struct temp_ostream *tstream,
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen const struct const_iovec *iov, unsigned int iov_count)
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen{
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen size_t bytes = 0;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen unsigned int i;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen for (i = 0; i < iov_count; i++) {
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen if (write_full(tstream->fd, iov[i].iov_base, iov[i].iov_len) < 0) {
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen tstream->ostream.ostream.stream_errno = errno;
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen return -1;
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen bytes += iov[i].iov_len;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen tstream->ostream.ostream.offset += iov[i].iov_len;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen }
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen tstream->fd_size += bytes;
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen return bytes;
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen}
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainenstatic ssize_t
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Siraineno_stream_temp_sendv(struct ostream_private *stream,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen const struct const_iovec *iov, unsigned int iov_count)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct temp_ostream *tstream = (struct temp_ostream *)stream;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen ssize_t ret = 0;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int i;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen tstream->flags &= ~IOSTREAM_TEMP_FLAG_TRY_FD_DUP;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen if (tstream->fd != -1)
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen return o_stream_temp_fd_sendv(tstream, iov, iov_count);
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen for (i = 0; i < iov_count; i++) {
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen if (tstream->buf->used + iov[i].iov_len > tstream->max_mem_size) {
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen if (o_stream_temp_move_to_fd(tstream) == 0) {
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen return o_stream_temp_fd_sendv(tstream, iov+i,
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen iov_count-i);
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen }
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen /* failed to move to temp fd, just keep it in memory */
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen }
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen buffer_append(tstream->buf, iov[i].iov_base, iov[i].iov_len);
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen ret += iov[i].iov_len;
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen stream->ostream.offset += iov[i].iov_len;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen }
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen return ret;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen}
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainenstatic int o_stream_temp_dup_cancel(struct temp_ostream *tstream)
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen{
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen struct istream *input;
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen uoff_t size = tstream->dupstream_offset -
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen tstream->dupstream_start_offset;
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen int ret = -1;
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen i_stream_seek(tstream->dupstream, tstream->dupstream_start_offset);
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen tstream->ostream.ostream.offset = 0;
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen input = i_stream_create_limit(tstream->dupstream, size);
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen i_stream_unref(&tstream->dupstream);
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen if (io_stream_copy(&tstream->ostream.ostream, input) > 0) {
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen /* everything copied */
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen ret = 0;
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen } else if (tstream->ostream.ostream.stream_errno == 0) {
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen i_assert(input->stream_errno != 0);
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen tstream->ostream.ostream.stream_errno = input->stream_errno;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_stream_destroy(&input);
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen return ret;
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic int o_stream_temp_dup_istream(struct temp_ostream *outstream,
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen struct istream *instream)
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen{
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen uoff_t in_size;
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainen
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainen if (!instream->readable_fd || i_stream_get_fd(instream) == -1)
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainen return 0;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (i_stream_get_size(instream, TRUE, &in_size) <= 0) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (outstream->dupstream != NULL)
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen return o_stream_temp_dup_cancel(outstream);
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen return 0;
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen }
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen if (outstream->dupstream == NULL) {
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen outstream->dupstream = instream;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen outstream->dupstream_start_offset = instream->v_offset;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen i_stream_ref(outstream->dupstream);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen } else {
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen if (outstream->dupstream != instream ||
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen outstream->dupstream_offset != instream->v_offset ||
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen outstream->dupstream_offset > in_size)
return o_stream_temp_dup_cancel(outstream);
}
i_stream_seek(instream, in_size);
/* we should be at EOF now. o_stream_send_istream() asserts if
eof isn't set. */
instream->eof = TRUE;
outstream->dupstream_offset = instream->v_offset;
outstream->ostream.ostream.offset =
outstream->dupstream_offset - outstream->dupstream_start_offset;
return 1;
}
static int o_stream_temp_send_istream(struct ostream_private *_outstream,
struct istream *instream)
{
struct temp_ostream *outstream = (struct temp_ostream *)_outstream;
int ret;
if ((outstream->flags & IOSTREAM_TEMP_FLAG_TRY_FD_DUP) != 0) {
if ((ret = o_stream_temp_dup_istream(outstream, instream)) != 0)
return ret;
outstream->flags &= ~IOSTREAM_TEMP_FLAG_TRY_FD_DUP;
}
return io_stream_copy(&outstream->ostream.ostream, instream);
}
static int
o_stream_temp_write_at(struct ostream_private *stream,
const void *data, size_t size, uoff_t offset)
{
struct temp_ostream *tstream = (struct temp_ostream *)stream;
if (tstream->fd == -1) {
i_assert(stream->ostream.offset == tstream->buf->used);
buffer_write(tstream->buf, offset, data, size);
stream->ostream.offset = tstream->buf->used;
} else {
if (pwrite_full(tstream->fd, data, size, offset) < 0) {
stream->ostream.stream_errno = errno;
i_close_fd(&tstream->fd);
return -1;
}
if (tstream->fd_size < offset + size)
tstream->fd_size = offset + size;
}
return 0;
}
static int o_stream_temp_seek(struct ostream_private *_stream, uoff_t offset)
{
_stream->ostream.offset = offset;
return 0;
}
struct ostream *iostream_temp_create(const char *temp_path_prefix,
enum iostream_temp_flags flags)
{
return iostream_temp_create_named(temp_path_prefix, flags, "");
}
struct ostream *iostream_temp_create_named(const char *temp_path_prefix,
enum iostream_temp_flags flags,
const char *name)
{
return iostream_temp_create_sized(temp_path_prefix, flags, name,
IOSTREAM_TEMP_MAX_BUF_SIZE_DEFAULT);
}
struct ostream *iostream_temp_create_sized(const char *temp_path_prefix,
enum iostream_temp_flags flags,
const char *name,
size_t max_mem_size)
{
struct temp_ostream *tstream;
struct ostream *output;
tstream = i_new(struct temp_ostream, 1);
tstream->ostream.sendv = o_stream_temp_sendv;
tstream->ostream.send_istream = o_stream_temp_send_istream;
tstream->ostream.write_at = o_stream_temp_write_at;
tstream->ostream.seek = o_stream_temp_seek;
tstream->ostream.iostream.close = o_stream_temp_close;
tstream->temp_path_prefix = i_strdup(temp_path_prefix);
tstream->flags = flags;
tstream->max_mem_size = max_mem_size;
tstream->buf = buffer_create_dynamic(default_pool, 8192);
tstream->fd = -1;
output = o_stream_create(&tstream->ostream, NULL, -1);
tstream->name = i_strdup(name);
if (name[0] == '\0') {
o_stream_set_name(output, t_strdup_printf(
"(temp iostream in %s)", temp_path_prefix));
} else {
o_stream_set_name(output, t_strdup_printf(
"(temp iostream in %s for %s)", temp_path_prefix, name));
}
return output;
}
static void iostream_temp_buf_destroyed(buffer_t *buf)
{
buffer_free(&buf);
}
struct istream *iostream_temp_finish(struct ostream **output,
size_t max_buffer_size)
{
struct temp_ostream *tstream =
(struct temp_ostream *)(*output)->real_stream;
struct istream *input, *input2;
uoff_t abs_offset, size;
const char *for_path;
int fd;
if (tstream->name[0] == '\0')
for_path = "";
else
for_path = t_strdup_printf(" for %s", tstream->name);
if (tstream->dupstream != NULL && !tstream->dupstream->closed) {
abs_offset = tstream->dupstream->real_stream->abs_start_offset +
tstream->dupstream_start_offset;
size = tstream->dupstream_offset -
tstream->dupstream_start_offset;
fd = dup(i_stream_get_fd(tstream->dupstream));
if (fd == -1)
input = i_stream_create_error_str(errno, "dup() failed: %m");
else {
input2 = i_stream_create_fd_autoclose(&fd, max_buffer_size);
i_stream_seek(input2, abs_offset);
input = i_stream_create_limit(input2, size);
i_stream_unref(&input2);
}
i_stream_set_name(input, t_strdup_printf(
"(Temp file in %s%s, from %s)", tstream->temp_path_prefix,
for_path, i_stream_get_name(tstream->dupstream)));
i_stream_unref(&tstream->dupstream);
} else if (tstream->dupstream != NULL) {
/* return the original failed stream. */
input = tstream->dupstream;
} else if (tstream->fd != -1) {
int fd = tstream->fd;
input = i_stream_create_fd_autoclose(&tstream->fd, max_buffer_size);
i_stream_set_name(input, t_strdup_printf(
"(Temp file fd %d in %s%s, %"PRIuUOFF_T" bytes)",
fd, tstream->temp_path_prefix, for_path, tstream->fd_size));
} else {
input = i_stream_create_from_data(tstream->buf->data,
tstream->buf->used);
i_stream_set_name(input, t_strdup_printf(
"(Temp buffer in %s%s, %"PRIuSIZE_T" bytes)",
tstream->temp_path_prefix, for_path, tstream->buf->used));
i_stream_add_destroy_callback(input, iostream_temp_buf_destroyed,
tstream->buf);
tstream->buf = NULL;
}
o_stream_destroy(output);
return input;
}