From d97f081a3de44852197ced772e21560c108895a6 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Wed, 14 Sep 2016 14:19:39 +0300 Subject: [PATCH] iostream-temp: If write() to temp file fails at any time, move it back to memory. Similarly to if the write() to temp fails during the initial move attempt. This way even if write() fails due to out of disk space, it's not visible to caller. An error message is logged in any case. --- src/lib/iostream-temp.c | 44 ++++++++++++++++++++++++++++++++++-- src/lib/iostream-temp.h | 3 +++ src/lib/test-iostream-temp.c | 26 +++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/lib/iostream-temp.c b/src/lib/iostream-temp.c index 1b3583d5c0..bbdc764948 100644 --- a/src/lib/iostream-temp.c +++ b/src/lib/iostream-temp.c @@ -79,6 +79,36 @@ static int o_stream_temp_move_to_fd(struct temp_ostream *tstream) return 0; } +int o_stream_temp_move_to_memory(struct ostream *output) +{ + struct temp_ostream *tstream = + (struct temp_ostream *)output->real_stream; + unsigned char buf[IO_BLOCK_SIZE]; + uoff_t offset = 0; + ssize_t ret = 0; + + i_assert(tstream->buf == NULL); + tstream->buf = buffer_create_dynamic(default_pool, 8192); + while (offset < tstream->ostream.ostream.offset && + (ret = pread(tstream->fd, buf, sizeof(buf), offset)) > 0) { + if ((size_t)ret > tstream->ostream.ostream.offset - offset) + ret = tstream->ostream.ostream.offset - offset; + buffer_append(tstream->buf, buf, ret); + offset += ret; + } + if (ret < 0) { + /* not really expecting this to happen */ + i_error("iostream-temp %s: read(%s*) failed: %m", + o_stream_get_name(&tstream->ostream.ostream), + tstream->temp_path_prefix); + tstream->ostream.ostream.stream_errno = EIO; + return -1; + } + i_close_fd(&tstream->fd); + tstream->ostream.fd = -1; + return 0; +} + static ssize_t o_stream_temp_fd_sendv(struct temp_ostream *tstream, const struct const_iovec *iov, unsigned int iov_count) @@ -88,8 +118,18 @@ o_stream_temp_fd_sendv(struct temp_ostream *tstream, for (i = 0; i < iov_count; i++) { if (write_full(tstream->fd, iov[i].iov_base, iov[i].iov_len) < 0) { - tstream->ostream.ostream.stream_errno = errno; - return -1; + i_error("iostream-temp %s: write(%s*) failed: %m - moving to memory", + o_stream_get_name(&tstream->ostream.ostream), + tstream->temp_path_prefix); + if (o_stream_temp_move_to_memory(&tstream->ostream.ostream) < 0) + return -1; + for (; i < iov_count; i++) { + buffer_append(tstream->buf, iov[i].iov_base, iov[i].iov_len); + bytes += iov[i].iov_len; + tstream->ostream.ostream.offset += iov[i].iov_len; + } + i_assert(tstream->fd_tried); + return bytes; } bytes += iov[i].iov_len; tstream->ostream.ostream.offset += iov[i].iov_len; diff --git a/src/lib/iostream-temp.h b/src/lib/iostream-temp.h index 8f16ff0e56..bf5a45d15d 100644 --- a/src/lib/iostream-temp.h +++ b/src/lib/iostream-temp.h @@ -25,4 +25,7 @@ struct ostream *iostream_temp_create_sized(const char *temp_path_prefix, struct istream *iostream_temp_finish(struct ostream **output, size_t max_buffer_size); +/* For internal testing: */ +int o_stream_temp_move_to_memory(struct ostream *output); + #endif diff --git a/src/lib/test-iostream-temp.c b/src/lib/test-iostream-temp.c index 46b5e723bc..f1e0496493 100644 --- a/src/lib/test-iostream-temp.c +++ b/src/lib/test-iostream-temp.c @@ -48,6 +48,31 @@ static void test_iostream_temp_create_sized_disk(void) test_end(); } +static void test_iostream_temp_create_write_error(void) +{ + struct ostream *output; + + test_begin("iostream_temp_create_sized() write error"); + output = iostream_temp_create_sized(".", 0, "test", 1); + + test_assert(o_stream_send(output, "123", 3) == 3); + test_assert(o_stream_get_fd(output) != -1); + test_assert(output->offset == 3); + test_assert(o_stream_temp_move_to_memory(output) == 0); + test_assert(o_stream_get_fd(output) == -1); + test_assert(o_stream_send(output, "45", 2) == 2); + test_assert(output->offset == 5); + + const unsigned char *data; + size_t size; + struct istream *input = iostream_temp_finish(&output, 128); + test_assert(i_stream_read_bytes(input, &data, &size, 5) == 1 && + memcmp(data, "12345", 5) == 0); + i_stream_destroy(&input); + + test_end(); +} + static void test_iostream_temp_istream(void) { struct istream *input, *input2, *temp_input; @@ -120,5 +145,6 @@ void test_iostream_temp(void) { test_iostream_temp_create_sized_memory(); test_iostream_temp_create_sized_disk(); + test_iostream_temp_create_write_error(); test_iostream_temp_istream(); }