compress: several fixlets for decompressor (#43506)

This commit is contained in:
Yu Watanabe
2026-08-27 07:58:38 +09:00
committed by GitHub
3 changed files with 299 additions and 94 deletions
+184 -60
View File
@@ -133,6 +133,7 @@ static DLSYM_PROTOTYPE(deflateEnd) = NULL;
static DLSYM_PROTOTYPE(inflateInit2_) = NULL;
static DLSYM_PROTOTYPE(inflate) = NULL;
static DLSYM_PROTOTYPE(inflateEnd) = NULL;
static DLSYM_PROTOTYPE(inflateReset) = NULL;
static inline void deflateEnd_wrapper(z_stream *s) {
sym_deflateEnd(s);
@@ -164,6 +165,7 @@ static inline void BZ2_bzDecompressEnd_wrapper(bz_stream *s) {
struct Compressor {
Compression type;
bool encoding;
bool finished;
union {
#if HAVE_XZ
lzma_stream xz;
@@ -396,7 +398,8 @@ int dlopen_zlib(int log_level) {
DLSYM_ARG(deflateEnd),
DLSYM_ARG(inflateInit2_),
DLSYM_ARG(inflate),
DLSYM_ARG(inflateEnd));
DLSYM_ARG(inflateEnd),
DLSYM_ARG(inflateReset));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"zlib support is not compiled in.");
@@ -482,9 +485,9 @@ static int compress_blob_xz(
return r;
if (level >= 0) {
r = sym_lzma_lzma_preset(&opt, (uint32_t) level);
if (r < 0)
return r;
/* This returns lzma_bool, and true means failure, i.e. the specified level is not supported. */
if (sym_lzma_lzma_preset(&opt, (uint32_t) level))
return -EINVAL;
}
/* Returns < 0 if we couldn't compress the data or the
@@ -493,8 +496,8 @@ static int compress_blob_xz(
if (src_size < 80)
return -ENOBUFS;
ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, NULL,
src, src_size, dst, &out_pos, dst_alloc_size);
ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, /* allocator= */ NULL,
src, src_size, dst, &out_pos, dst_alloc_size);
if (ret != LZMA_OK)
return -ENOBUFS;
@@ -1040,9 +1043,8 @@ static int decompress_blob_bzip2(
_cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {};
r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -ENOMEM;
if (sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0) != BZ_OK)
return -EIO;
size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX);
if (!greedy_realloc(dst, space, 1))
@@ -1477,9 +1479,8 @@ static int decompress_startswith_bzip2(
_cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {};
r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
return -EBADMSG;
if (sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0) != BZ_OK)
return -EIO;
if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
return -ENOMEM;
@@ -1634,21 +1635,34 @@ int compress_stream(
return 0;
}
/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on
* regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */
static int should_sparse(int fd) {
struct stat st;
assert(fd >= 0);
/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on regular
* files, without O_APPEND (which ignores the lseek position, collapsing any holes we try to create),
* and only when starting from a position at or beyond the current end of file (otherwise the skipped
* ranges would overlap pre-existing data instead of being genuinely unwritten). */
if (fstat(fd, &st) < 0)
return -errno;
if (!S_ISREG(st.st_mode))
return false;
int flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND);
if (FLAGS_SET(flags, O_APPEND))
return false;
off_t pos = lseek(fd, 0, SEEK_CUR);
if (pos < 0)
return -errno;
return pos >= st.st_size;
}
/* After sparse decompression, set the file size to the current position to account for
@@ -1702,10 +1716,6 @@ static int decompress_stream_write_callback(const void *data, size_t size, void
}
static int decompressor_new(Decompressor **ret, Compression type) {
#if HAVE_ZLIB || HAVE_BZIP2
int r;
#endif
assert(ret);
_cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1);
@@ -1745,16 +1755,14 @@ static int decompressor_new(Decompressor **ret, Compression type) {
#if HAVE_ZLIB
case COMPRESSION_GZIP:
r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip));
if (r != Z_OK)
if (sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip)) != Z_OK)
return -EIO;
break;
#endif
#if HAVE_BZIP2
case COMPRESSION_BZIP2:
r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0);
if (r != BZ_OK)
if (sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0) != BZ_OK)
return -EIO;
break;
#endif
@@ -1809,14 +1817,15 @@ int decompress_stream(
n = read(fdf, buf, COMPRESS_PIPE_BUFFER_SIZE);
if (n < 0)
return -errno;
if (n == 0)
break;
total_in += n;
r = decompressor_push(c, buf, n, decompress_stream_write_callback, &userdata);
if (r < 0)
return r;
if (n == 0)
break;
}
if (total_in == 0)
@@ -1953,15 +1962,20 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
int r;
assert(c);
assert(data || size == 0);
assert(callback);
/* This feeds `size` bytes of compressed data into `c`. Passing size == 0 has special meaning: it
* signals that no further input will be provided, and the function will attempt to finalize the
* stream, failing with -EBADMSG if the compressed data was truncated or has trailing garbage after
* the logical end of stream. */
if (c->encoding)
return -EINVAL;
if (size == 0)
return 1;
assert(data);
if (c->finished && size == 0)
/* The previous stream was correctly finished, and it was the last one. */
return 0;
#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2
_cleanup_free_ uint8_t *buffer = NULL;
@@ -1975,33 +1989,61 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
switch (c->type) {
case COMPRESSION_NONE:
if (c->finished)
return -EBADMSG;
if (size == 0) {
c->finished = true;
return 0;
}
r = callback(data, size, userdata);
if (r < 0)
return r;
break;
return 0;
#if HAVE_XZ
case COMPRESSION_XZ:
if (c->finished)
return -EBADMSG;
c->xz.next_in = data;
c->xz.avail_in = size;
while (c->xz.avail_in > 0) {
for (;;) {
c->xz.next_out = buffer;
c->xz.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
lzma_ret lzr = sym_lzma_code(&c->xz, LZMA_RUN);
lzma_ret lzr = sym_lzma_code(&c->xz, size == 0 ? LZMA_FINISH : LZMA_RUN);
if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END))
return -EBADMSG;
if (c->xz.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->xz.avail_out, userdata);
if (c->xz.avail_out > COMPRESS_PIPE_BUFFER_SIZE)
return -EIO;
size_t produced = COMPRESS_PIPE_BUFFER_SIZE - c->xz.avail_out;
if (produced > 0) {
r = callback(buffer, produced, userdata);
if (r < 0)
return r;
}
}
break;
if (lzr == LZMA_STREAM_END) {
if (c->xz.avail_in > 0)
return -EBADMSG; /* garbage data at the end */
c->finished = true;
return 0;
}
if (c->xz.avail_in == 0 && c->xz.avail_out > 0) {
if (size == 0)
return -EBADMSG; /* truncated */
return 0; /* No progress possible with the current input. */
}
}
#endif
#if HAVE_LZ4
@@ -2009,7 +2051,9 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
const uint8_t *src = data;
size_t src_remaining = size;
while (src_remaining > 0) {
c->finished = false;
for (;;) {
size_t produced = COMPRESS_PIPE_BUFFER_SIZE;
size_t consumed = src_remaining;
@@ -2017,8 +2061,9 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
if (sym_LZ4F_isError(rc))
return -EBADMSG;
if (consumed == 0 && produced == 0)
break; /* No progress possible with current input */
if (consumed > src_remaining ||
produced > COMPRESS_PIPE_BUFFER_SIZE)
return -EIO;
src += consumed;
src_remaining -= consumed;
@@ -2028,9 +2073,24 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
if (r < 0)
return r;
}
}
break;
if (src_remaining == 0) {
if (rc == 0) {
/* The current stream has finished. */
c->finished = true;
return 0;
}
if (produced == COMPRESS_PIPE_BUFFER_SIZE)
/* Remaining data exist in the library buffer. We need to read them. */
continue;
if (size == 0)
return -EBADMSG; /* truncated */
return 0; /* No progress possible with the current input. */
}
}
}
#endif
@@ -2041,7 +2101,9 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
.size = size,
};
while (input.pos < input.size) {
c->finished = false;
for (;;) {
ZSTD_outBuffer output = {
.dst = buffer,
.size = COMPRESS_PIPE_BUFFER_SIZE,
@@ -2051,14 +2113,33 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
if (sym_ZSTD_isError(res))
return -EBADMSG;
if (input.pos > input.size ||
output.pos > output.size)
return -EIO;
if (output.pos > 0) {
r = callback(output.dst, output.pos, userdata);
if (r < 0)
return r;
}
}
break;
if (input.pos == input.size) {
if (res == 0) {
/* The current stream has finished. */
c->finished = true;
return 0;
}
if (output.pos == COMPRESS_PIPE_BUFFER_SIZE)
/* Remaining data exist in the library buffer. We need to read them. */
continue;
if (size == 0)
return -EBADMSG; /* truncated */
return 0; /* No progress possible with the current input. */
}
}
}
#endif
@@ -2070,7 +2151,14 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
c->gzip.next_in = (void*) data;
c->gzip.avail_in = size;
while (c->gzip.avail_in > 0) {
for (;;) {
if (c->finished) {
if (sym_inflateReset(&c->gzip) != Z_OK)
return -EIO;
c->finished = false;
}
c->gzip.next_out = buffer;
c->gzip.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
@@ -2078,17 +2166,32 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
if (!IN_SET(zr, Z_OK, Z_STREAM_END))
return -EBADMSG;
if (c->gzip.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->gzip.avail_out, userdata);
if (c->gzip.avail_out > COMPRESS_PIPE_BUFFER_SIZE)
return -EIO;
size_t produced = COMPRESS_PIPE_BUFFER_SIZE - c->gzip.avail_out;
if (produced > 0) {
r = callback(buffer, produced, userdata);
if (r < 0)
return r;
}
if (zr == Z_STREAM_END)
break;
}
if (zr == Z_STREAM_END) {
c->finished = true;
break;
if (c->gzip.avail_in > 0)
continue; /* New stream may be pending. */
return 0;
}
if (c->gzip.avail_in == 0 && c->gzip.avail_out > 0) {
if (size == 0)
return -EBADMSG; /* truncated */
return 0; /* No progress possible with the current input. */
}
}
#endif
#if HAVE_BZIP2
@@ -2099,7 +2202,15 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
c->bzip2.next_in = (char*) data;
c->bzip2.avail_in = size;
while (c->bzip2.avail_in > 0) {
for (;;) {
if (c->finished) {
sym_BZ2_bzDecompressEnd(&c->bzip2);
if (sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0) != BZ_OK)
return -EIO;
c->finished = false;
}
c->bzip2.next_out = (char*) buffer;
c->bzip2.avail_out = COMPRESS_PIPE_BUFFER_SIZE;
@@ -2107,24 +2218,37 @@ int decompressor_push(Decompressor *c, const void *data, size_t size, Decompress
if (!IN_SET(bzr, BZ_OK, BZ_STREAM_END))
return -EBADMSG;
if (c->bzip2.avail_out < COMPRESS_PIPE_BUFFER_SIZE) {
r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->bzip2.avail_out, userdata);
if (c->bzip2.avail_out > COMPRESS_PIPE_BUFFER_SIZE)
return -EIO;
size_t produced = COMPRESS_PIPE_BUFFER_SIZE - c->bzip2.avail_out;
if (produced > 0) {
r = callback(buffer, produced, userdata);
if (r < 0)
return r;
}
if (bzr == BZ_STREAM_END)
break;
}
if (bzr == BZ_STREAM_END) {
c->finished = true;
break;
if (c->bzip2.avail_in > 0)
continue; /* New stream may be pending. */
return 0;
}
if (c->bzip2.avail_in == 0 && c->bzip2.avail_out > 0) {
if (size == 0)
return -EBADMSG; /* truncated */
return 0; /* No progress possible with the current input. */
}
}
#endif
default:
assert_not_reached();
}
return 1;
}
int compressor_new(Compressor **ret, Compression type) {