diff options
Diffstat (limited to 'src/lib-compression/istream-lzma.c')
-rw-r--r-- | src/lib-compression/istream-lzma.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/lib-compression/istream-lzma.c b/src/lib-compression/istream-lzma.c new file mode 100644 index 0000000..7b0c2a6 --- /dev/null +++ b/src/lib-compression/istream-lzma.c @@ -0,0 +1,264 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#ifdef HAVE_LZMA + +#include "istream-private.h" +#include "istream-zlib.h" +#include <lzma.h> + +#define CHUNK_SIZE (1024*64) + +#define LZMA_MEMORY_LIMIT (1024*1024*80) + +struct lzma_istream { + struct istream_private istream; + + lzma_stream strm; + uoff_t eof_offset; + struct stat last_parent_statbuf; + + bool hdr_read:1; + bool marked:1; + bool strm_closed:1; +}; + +static void i_stream_lzma_close(struct iostream_private *stream, + bool close_parent) +{ + struct lzma_istream *zstream = (struct lzma_istream *)stream; + + if (!zstream->strm_closed) { + lzma_end(&zstream->strm); + zstream->strm_closed = TRUE; + } + if (close_parent) + i_stream_close(zstream->istream.parent); +} + +static void lzma_read_error(struct lzma_istream *zstream, const char *error) +{ + io_stream_set_error(&zstream->istream.iostream, + "lzma.read(%s): %s at %"PRIuUOFF_T, + i_stream_get_name(&zstream->istream.istream), error, + i_stream_get_absolute_offset(&zstream->istream.istream)); +} + +static int lzma_handle_error(struct lzma_istream *zstream, lzma_ret lzma_err) +{ + struct istream_private *stream = &zstream->istream; + switch (lzma_err) { + case LZMA_OK: + break; + case LZMA_DATA_ERROR: + case LZMA_BUF_ERROR: + lzma_read_error(zstream, "corrupted data"); + stream->istream.stream_errno = EINVAL; + return -1; + case LZMA_FORMAT_ERROR: + lzma_read_error(zstream, "wrong magic in header (not xz file?)"); + stream->istream.stream_errno = EINVAL; + return -1; + case LZMA_OPTIONS_ERROR: + lzma_read_error(zstream, "Unsupported xz options"); + stream->istream.stream_errno = EIO; + return -1; + case LZMA_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "lzma.read(%s): Out of memory", + i_stream_get_name(&stream->istream)); + case LZMA_STREAM_END: + break; + default: + lzma_read_error(zstream, t_strdup_printf( + "lzma_code() failed with %d", lzma_err)); + stream->istream.stream_errno = EIO; + return -1; + } + return 0; +} + +static void lzma_stream_end(struct lzma_istream *zstream) +{ + zstream->eof_offset = zstream->istream.istream.v_offset + + (zstream->istream.pos - zstream->istream.skip); + zstream->istream.cached_stream_size = zstream->eof_offset; +} + +static ssize_t i_stream_lzma_read(struct istream_private *stream) +{ + struct lzma_istream *zstream = (struct lzma_istream *)stream; + const unsigned char *data; + uoff_t high_offset; + size_t size, out_size; + lzma_ret ret; + + high_offset = stream->istream.v_offset + (stream->pos - stream->skip); + if (zstream->eof_offset == high_offset) { + stream->istream.eof = TRUE; + return -1; + } + + if (!zstream->marked) { + if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } else { + /* try to avoid compressing, so we can quickly seek backwards */ + if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size)) + return -2; /* buffer full */ + } + + if (i_stream_read_more(stream->parent, &data, &size) < 0) { + if (stream->parent->stream_errno != 0) { + stream->istream.stream_errno = + stream->parent->stream_errno; + } else { + i_assert(stream->parent->eof); + lzma_stream_end(zstream); + ret = lzma_code(&zstream->strm, LZMA_FINISH); + if (lzma_handle_error(zstream, ret) < 0) + ; + else if (!zstream->hdr_read) { + lzma_read_error(zstream, "file too small (not xz file?)"); + stream->istream.stream_errno = EINVAL; + } else if (ret != LZMA_STREAM_END) { + lzma_read_error(zstream, "unexpected EOF"); + stream->istream.stream_errno = EPIPE; + } + stream->istream.eof = TRUE; + } + return -1; + } + if (size == 0) { + /* no more input */ + i_assert(!stream->istream.blocking); + return 0; + } + + zstream->strm.next_in = data; + zstream->strm.avail_in = size; + + zstream->strm.next_out = stream->w_buffer + stream->pos; + zstream->strm.avail_out = out_size; + if (!zstream->hdr_read && size > LZMA_STREAM_HEADER_SIZE) + zstream->hdr_read = TRUE; + ret = lzma_code(&zstream->strm, LZMA_RUN); + + out_size -= zstream->strm.avail_out; + stream->pos += out_size; + + size_t bytes_consumed = size - zstream->strm.avail_in; + i_stream_skip(stream->parent, bytes_consumed); + if (i_stream_get_data_size(stream->parent) > 0 && + (bytes_consumed > 0 || out_size > 0)) { + /* Parent stream was only partially consumed. Set the stream's + IO as pending to avoid hangs. */ + i_stream_set_input_pending(&stream->istream, TRUE); + } + + if (lzma_handle_error(zstream, ret) < 0) { + return -1; + } else if (ret == LZMA_STREAM_END) { + lzma_stream_end(zstream); + if (out_size == 0) { + stream->istream.eof = TRUE; + return -1; + } + } + if (out_size == 0) { + /* read more input */ + return i_stream_lzma_read(stream); + } + return out_size; +} + +static void i_stream_lzma_init(struct lzma_istream *zstream) +{ + lzma_ret ret; + + ret = lzma_stream_decoder(&zstream->strm, LZMA_MEMORY_LIMIT, + LZMA_CONCATENATED); + switch (ret) { + case LZMA_OK: + break; + case LZMA_MEM_ERROR: + i_fatal_status(FATAL_OUTOFMEM, "lzma: Out of memory"); + default: + i_fatal("lzma_stream_decoder() failed with ret=%d", ret); + } +} + +static void i_stream_lzma_reset(struct lzma_istream *zstream) +{ + struct istream_private *stream = &zstream->istream; + + i_stream_seek(stream->parent, stream->parent_start_offset); + zstream->eof_offset = UOFF_T_MAX; + zstream->strm.next_in = NULL; + zstream->strm.avail_in = 0; + + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + + lzma_end(&zstream->strm); + i_stream_lzma_init(zstream); +} + +static void +i_stream_lzma_seek(struct istream_private *stream, uoff_t v_offset, bool mark) +{ + struct lzma_istream *zstream = (struct lzma_istream *) stream; + + if (i_stream_nonseekable_try_seek(stream, v_offset)) + return; + + /* have to seek backwards - reset state and retry */ + i_stream_lzma_reset(zstream); + if (!i_stream_nonseekable_try_seek(stream, v_offset)) + i_unreached(); + + if (mark) + zstream->marked = TRUE; +} + +static void i_stream_lzma_sync(struct istream_private *stream) +{ + struct lzma_istream *zstream = (struct lzma_istream *) stream; + const struct stat *st; + + if (i_stream_stat(stream->parent, FALSE, &st) == 0) { + if (memcmp(&zstream->last_parent_statbuf, + st, sizeof(*st)) == 0) { + /* a compressed file doesn't change unexpectedly, + don't clear our caches unnecessarily */ + return; + } + zstream->last_parent_statbuf = *st; + } + i_stream_lzma_reset(zstream); +} + +struct istream *i_stream_create_lzma(struct istream *input) +{ + struct lzma_istream *zstream; + + zstream = i_new(struct lzma_istream, 1); + zstream->eof_offset = UOFF_T_MAX; + + i_stream_lzma_init(zstream); + + zstream->istream.iostream.close = i_stream_lzma_close; + zstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + zstream->istream.read = i_stream_lzma_read; + zstream->istream.seek = i_stream_lzma_seek; + zstream->istream.sync = i_stream_lzma_sync; + + zstream->istream.istream.readable_fd = FALSE; + zstream->istream.istream.blocking = input->blocking; + zstream->istream.istream.seekable = input->seekable; + + return i_stream_create(&zstream->istream, input, + i_stream_get_fd(input), 0); +} +#endif |