1
/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
7
#include "istream-private.h"
8
#include "istream-zlib.h"
11
#define CHUNK_SIZE (1024*64)
13
struct bzlib_istream {
14
struct istream_private istream;
17
uoff_t eof_offset, stream_size;
18
size_t prev_size, high_pos;
19
struct stat last_parent_statbuf;
21
unsigned int log_errors:1;
22
unsigned int marked:1;
23
unsigned int zs_closed:1;
26
static void i_stream_bzlib_close(struct iostream_private *stream)
28
struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
30
if (!zstream->zs_closed) {
31
(void)BZ2_bzDecompressEnd(&zstream->zs);
32
zstream->zs_closed = TRUE;
36
static void bzlib_read_error(struct bzlib_istream *zstream, const char *error)
38
i_error("bzlib.read(%s): %s at %"PRIuUOFF_T,
39
i_stream_get_name(&zstream->istream.istream), error,
40
zstream->istream.abs_start_offset +
41
zstream->istream.istream.v_offset);
44
static ssize_t i_stream_bzlib_read(struct istream_private *stream)
46
struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
47
const unsigned char *data;
52
high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
53
if (zstream->eof_offset == high_offset) {
54
i_assert(zstream->high_pos == 0 ||
55
zstream->high_pos == stream->pos);
56
stream->istream.eof = TRUE;
60
if (stream->pos < zstream->high_pos) {
61
/* we're here because we seeked back within the read buffer. */
62
ret = zstream->high_pos - stream->pos;
63
stream->pos = zstream->high_pos;
64
zstream->high_pos = 0;
66
if (zstream->eof_offset != (uoff_t)-1) {
67
high_offset = stream->istream.v_offset +
68
(stream->pos - stream->skip);
69
i_assert(zstream->eof_offset == high_offset);
70
stream->istream.eof = TRUE;
74
zstream->high_pos = 0;
76
if (stream->pos + CHUNK_SIZE > stream->buffer_size) {
77
/* try to keep at least CHUNK_SIZE available */
78
if (!zstream->marked && stream->skip > 0) {
79
/* don't try to keep anything cached if we don't
81
i_stream_compress(stream);
83
if (stream->max_buffer_size == 0 ||
84
stream->buffer_size < stream->max_buffer_size)
85
i_stream_grow_buffer(stream, CHUNK_SIZE);
87
if (stream->pos == stream->buffer_size) {
88
if (stream->skip > 0) {
89
/* lose our buffer cache */
90
i_stream_compress(stream);
93
if (stream->pos == stream->buffer_size)
94
return -2; /* buffer full */
98
if (zstream->zs.avail_in == 0) {
99
/* need to read more data. try to read a full CHUNK_SIZE */
100
i_stream_skip(stream->parent, zstream->prev_size);
101
if (i_stream_read_data(stream->parent, &data, &size,
102
CHUNK_SIZE-1) == -1 && size == 0) {
103
if (stream->parent->stream_errno != 0) {
104
stream->istream.stream_errno =
105
stream->parent->stream_errno;
107
i_assert(stream->parent->eof);
108
if (zstream->log_errors) {
109
bzlib_read_error(zstream,
112
stream->istream.stream_errno = EINVAL;
116
zstream->prev_size = size;
119
i_assert(!stream->istream.blocking);
123
zstream->zs.next_in = (char *)data;
124
zstream->zs.avail_in = size;
127
size = stream->buffer_size - stream->pos;
128
zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
129
zstream->zs.avail_out = size;
130
ret = BZ2_bzDecompress(&zstream->zs);
132
size -= zstream->zs.avail_out;
141
if (zstream->log_errors)
142
bzlib_read_error(zstream, "corrupted data");
143
stream->istream.stream_errno = EINVAL;
145
case BZ_DATA_ERROR_MAGIC:
146
if (zstream->log_errors) {
147
bzlib_read_error(zstream,
148
"wrong magic in header (not bz2 file?)");
150
stream->istream.stream_errno = EINVAL;
153
i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory",
154
i_stream_get_name(&stream->istream));
156
zstream->eof_offset = stream->istream.v_offset +
157
(stream->pos - stream->skip);
158
zstream->stream_size = zstream->eof_offset;
160
stream->istream.eof = TRUE;
165
i_fatal("BZ2_bzDecompress() failed with %d", ret);
168
/* read more input */
169
return i_stream_bzlib_read(stream);
174
static void i_stream_bzlib_init(struct bzlib_istream *zstream)
178
ret = BZ2_bzDecompressInit(&zstream->zs, 0, 0);
183
i_fatal_status(FATAL_OUTOFMEM, "bzlib: Out of memory");
184
case BZ_CONFIG_ERROR:
185
i_fatal("Wrong bzlib library version (broken compilation)");
187
i_fatal("bzlib: Invalid parameters");
189
i_fatal("BZ2_bzDecompressInit() failed with %d", ret);
193
static void i_stream_bzlib_reset(struct bzlib_istream *zstream)
195
struct istream_private *stream = &zstream->istream;
197
i_stream_seek(stream->parent, stream->parent_start_offset);
198
zstream->eof_offset = (uoff_t)-1;
199
zstream->zs.next_in = NULL;
200
zstream->zs.avail_in = 0;
202
stream->parent_expected_offset = stream->parent_start_offset;
203
stream->skip = stream->pos = 0;
204
stream->istream.v_offset = 0;
205
zstream->high_pos = 0;
206
zstream->prev_size = 0;
208
(void)BZ2_bzDecompressEnd(&zstream->zs);
209
i_stream_bzlib_init(zstream);
213
i_stream_bzlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
215
struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
216
uoff_t start_offset = stream->istream.v_offset - stream->skip;
218
if (v_offset < start_offset) {
219
/* have to seek backwards */
220
i_stream_bzlib_reset(zstream);
222
} else if (zstream->high_pos != 0) {
223
stream->pos = zstream->high_pos;
224
zstream->high_pos = 0;
227
if (v_offset <= start_offset + stream->pos) {
228
/* seeking backwards within what's already cached */
229
stream->skip = v_offset - start_offset;
230
stream->istream.v_offset = v_offset;
231
zstream->high_pos = stream->pos;
232
stream->pos = stream->skip;
234
/* read and cache forward */
236
size_t avail = stream->pos - stream->skip;
238
if (stream->istream.v_offset + avail >= v_offset) {
239
i_stream_skip(&stream->istream,
241
stream->istream.v_offset);
245
i_stream_skip(&stream->istream, avail);
246
} while (i_stream_read(&stream->istream) >= 0);
248
if (stream->istream.v_offset != v_offset) {
249
/* some failure, we've broken it */
250
if (stream->istream.stream_errno != 0) {
251
i_error("bzlib_istream.seek(%s) failed: %s",
252
i_stream_get_name(&stream->istream),
253
strerror(stream->istream.stream_errno));
254
i_stream_close(&stream->istream);
256
/* unexpected EOF. allow it since we may just
257
want to check if there's anything.. */
258
i_assert(stream->istream.eof);
264
zstream->marked = TRUE;
267
static const struct stat *
268
i_stream_bzlib_stat(struct istream_private *stream, bool exact)
270
struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
271
const struct stat *st;
274
st = i_stream_stat(stream->parent, exact);
278
/* when exact=FALSE always return the parent stat's size, even if we
279
know the exact value. this is necessary because otherwise e.g. mbox
280
code can see two different values and think that a compressed mbox
281
file keeps changing. */
285
stream->statbuf = *st;
286
if (zstream->stream_size == (uoff_t)-1) {
287
uoff_t old_offset = stream->istream.v_offset;
290
(void)i_stream_get_data(&stream->istream, &size);
291
i_stream_skip(&stream->istream, size);
292
} while (i_stream_read(&stream->istream) > 0);
294
i_stream_seek(&stream->istream, old_offset);
295
if (zstream->stream_size == (uoff_t)-1)
298
stream->statbuf.st_size = zstream->stream_size;
299
return &stream->statbuf;
302
static void i_stream_bzlib_sync(struct istream_private *stream)
304
struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
305
const struct stat *st;
307
st = i_stream_stat(stream->parent, FALSE);
309
if (memcmp(&zstream->last_parent_statbuf,
310
st, sizeof(*st)) == 0) {
311
/* a compressed file doesn't change unexpectedly,
312
don't clear our caches unnecessarily */
315
zstream->last_parent_statbuf = *st;
317
i_stream_bzlib_reset(zstream);
320
struct istream *i_stream_create_bz2(struct istream *input, bool log_errors)
322
struct bzlib_istream *zstream;
324
zstream = i_new(struct bzlib_istream, 1);
325
zstream->eof_offset = (uoff_t)-1;
326
zstream->stream_size = (uoff_t)-1;
327
zstream->log_errors = log_errors;
329
i_stream_bzlib_init(zstream);
331
zstream->istream.iostream.close = i_stream_bzlib_close;
332
zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
333
zstream->istream.read = i_stream_bzlib_read;
334
zstream->istream.seek = i_stream_bzlib_seek;
335
zstream->istream.stat = i_stream_bzlib_stat;
336
zstream->istream.sync = i_stream_bzlib_sync;
338
zstream->istream.istream.readable_fd = FALSE;
339
zstream->istream.istream.blocking = input->blocking;
340
zstream->istream.istream.seekable = input->seekable;
342
return i_stream_create(&zstream->istream, input,
343
i_stream_get_fd(input));