bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "lib.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef HAVE_LZ4
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "ostream-private.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "ostream-zlib.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "iostream-lz4.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <lz4.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#define CHUNK_SIZE OSTREAM_LZ4_CHUNK_SIZE
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstruct lz4_ostream {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct ostream_private ostream;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned char compressbuf[CHUNK_SIZE];
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int compressbuf_offset;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* chunk size, followed by compressed data */
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila unsigned char outbuf[IOSTREAM_LZ4_CHUNK_PREFIX_LEN +
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila LZ4_COMPRESSBOUND(CHUNK_SIZE)];
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int outbuf_offset, outbuf_used;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen};
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void o_stream_lz4_close(struct iostream_private *stream,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen bool close_parent)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (close_parent)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen o_stream_close(zstream->ostream.parent);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic int o_stream_lz4_send_outbuf(struct lz4_ostream *zstream)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ssize_t ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen size_t size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (zstream->outbuf_used == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen size = zstream->outbuf_used - zstream->outbuf_offset;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(size > 0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ret = o_stream_send(zstream->ostream.parent,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf + zstream->outbuf_offset, size);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (ret < 0) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen o_stream_copy_error_from_parent(&zstream->ostream);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if ((size_t)ret != size) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf_offset += ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf_offset = 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf_used = 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic int o_stream_lz4_compress(struct lz4_ostream *zstream)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen uint32_t chunk_size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen int ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (zstream->compressbuf_offset == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if ((ret = o_stream_lz4_send_outbuf(zstream)) <= 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(zstream->outbuf_offset == 0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(zstream->outbuf_used == 0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi#if defined(HAVE_LZ4_COMPRESS_DEFAULT)
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi int max_dest_size = LZ4_compressBound(zstream->compressbuf_offset);
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi i_assert(max_dest_size >= 0);
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi if (max_dest_size == 0) {
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi io_stream_set_error(&zstream->ostream.iostream,
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi "lz4-compress: input size %u too large (> %u)",
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi zstream->compressbuf_offset, LZ4_MAX_INPUT_SIZE);
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi zstream->ostream.ostream.stream_errno = EINVAL;
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi return -1;
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi }
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi ret = LZ4_compress_default((void *)zstream->compressbuf,
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi (void *)(zstream->outbuf +
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi IOSTREAM_LZ4_CHUNK_PREFIX_LEN),
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi zstream->compressbuf_offset,
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi max_dest_size);
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi#else
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila ret = LZ4_compress((void *)zstream->compressbuf,
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila (void *)(zstream->outbuf +
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila IOSTREAM_LZ4_CHUNK_PREFIX_LEN),
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila zstream->compressbuf_offset);
d31a407872fb93f907fc2587daaa780ffa9a6f56Martti Rannanjärvi#endif /* defined(HAVE_LZ4_COMPRESS_DEFAULT) */
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila i_assert(ret > 0 && (unsigned int)ret <= sizeof(zstream->outbuf) -
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila IOSTREAM_LZ4_CHUNK_PREFIX_LEN);
efd203f312659a7ce82644fb2a39d791ac198b77Teemu Huovila zstream->outbuf_used = IOSTREAM_LZ4_CHUNK_PREFIX_LEN + ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen chunk_size = zstream->outbuf_used - IOSTREAM_LZ4_CHUNK_PREFIX_LEN;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf[0] = (chunk_size & 0xff000000) >> 24;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf[1] = (chunk_size & 0x00ff0000) >> 16;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf[2] = (chunk_size & 0x0000ff00) >> 8;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf[3] = (chunk_size & 0x000000ff);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->compressbuf_offset = 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic ssize_t
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Siraineno_stream_lz4_send_chunk(struct lz4_ostream *zstream,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const void *data, size_t size)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen size_t max_size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ssize_t added_bytes = 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen int ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(zstream->outbuf_used == 0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen do {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen max_size = I_MIN(size, sizeof(zstream->compressbuf) -
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->compressbuf_offset);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen memcpy(zstream->compressbuf + zstream->compressbuf_offset,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen data, max_size);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->compressbuf_offset += max_size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen data = CONST_PTR_OFFSET(data, max_size);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen size -= max_size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen added_bytes += max_size;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (zstream->compressbuf_offset == sizeof(zstream->compressbuf)) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ret = o_stream_lz4_compress(zstream);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (ret <= 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return added_bytes != 0 ? added_bytes : ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen } while (size > 0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return added_bytes;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic int o_stream_lz4_flush(struct ostream_private *stream)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (o_stream_lz4_compress(zstream) < 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (o_stream_lz4_send_outbuf(zstream) < 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
8109f3187f5ece5565de1813209af42dc7bb768bTimo Sirainen return o_stream_flush_parent(stream);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic ssize_t
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Siraineno_stream_lz4_sendv(struct ostream_private *stream,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const struct const_iovec *iov, unsigned int iov_count)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ssize_t ret, bytes = 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int i;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if ((ret = o_stream_lz4_send_outbuf(zstream)) <= 0) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* error / we still couldn't flush existing data to
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen parent stream. */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen for (i = 0; i < iov_count; i++) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen ret = o_stream_lz4_send_chunk(zstream, iov[i].iov_base,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen iov[i].iov_len);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (ret < 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen bytes += ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if ((size_t)ret != iov[i].iov_len)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen break;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen stream->ostream.offset += bytes;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return bytes;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstruct ostream *o_stream_create_lz4(struct ostream *output, int level)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct iostream_lz4_header *hdr;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct lz4_ostream *zstream;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(level >= 1 && level <= 9);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream = i_new(struct lz4_ostream, 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->ostream.sendv = o_stream_lz4_sendv;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->ostream.flush = o_stream_lz4_flush;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->ostream.iostream.close = o_stream_lz4_close;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(sizeof(zstream->outbuf) >= sizeof(*hdr));
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen hdr = (void *)zstream->outbuf;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen memcpy(hdr->magic, IOSTREAM_LZ4_MAGIC, sizeof(hdr->magic));
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen hdr->max_uncompressed_chunk_size[0] =
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen (OSTREAM_LZ4_CHUNK_SIZE & 0xff000000) >> 24;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen hdr->max_uncompressed_chunk_size[1] =
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen (OSTREAM_LZ4_CHUNK_SIZE & 0x00ff0000) >> 16;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen hdr->max_uncompressed_chunk_size[2] =
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen (OSTREAM_LZ4_CHUNK_SIZE & 0x0000ff00) >> 8;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen hdr->max_uncompressed_chunk_size[3] =
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen (OSTREAM_LZ4_CHUNK_SIZE & 0x000000ff);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen zstream->outbuf_used = sizeof(*hdr);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return o_stream_create(&zstream->ostream, output,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen o_stream_get_fd(output));
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif