From 8b190118f570361f9d252efba86e8f77817e5fe2 Mon Sep 17 00:00:00 2001 From: Darren Carreras <283775510+carrerasdarren-cell@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:06:24 -0400 Subject: [PATCH] Preserve clean EOF after non-blocking gzip member --- gzread.c | 7 ++++ test/CMakeLists.txt | 6 ++++ test/gzread_nonblock.c | 79 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 test/gzread_nonblock.c diff --git a/gzread.c b/gzread.c index 8b3e369dac..3cc7003f0d 100644 --- a/gzread.c +++ b/gzread.c @@ -125,6 +125,13 @@ local int gz_look(gz_statep state) { if we're looking for a gzip member after the first one, which is not at the start, then proceed directly to look for a gzip member next */ if (state->direct == -1 || state->junk == 0) { + /* wait for input before committing to another gzip member */ + if (state->junk == 0 && strm->avail_in == 0) { + if (gz_avail(state) == -1) + return -1; + if (strm->avail_in == 0) + return 0; + } inflateReset(strm); state->how = GZIP; state->junk = state->junk != -1; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ee08695c4..a836fdbd9c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,6 +69,12 @@ if(ZLIB_BUILD_STATIC) $<$:HAVE_HIDDEN>) add_test(NAME zlib_examplestatic COMMAND zlib_examplestatic) + if(UNIX) + add_executable(zlib_gzread_nonblock gzread_nonblock.c) + target_link_libraries(zlib_gzread_nonblock ZLIB::ZLIBSTATIC) + add_test(NAME zlib_gzread_nonblock COMMAND zlib_gzread_nonblock) + endif(UNIX) + add_executable(zlib_minigzipstatic minigzip.c) target_link_libraries(zlib_minigzipstatic ZLIB::ZLIBSTATIC) set_target_properties(zlib_minigzipstatic diff --git a/test/gzread_nonblock.c b/test/gzread_nonblock.c new file mode 100644 index 0000000000..b9517d0c51 --- /dev/null +++ b/test/gzread_nonblock.c @@ -0,0 +1,79 @@ +/* gzread_nonblock.c -- test non-blocking gzip reads + * Copyright (C) 2026 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zlib.h" +#include +#include +#include +#include +#include + +int main(void) { + static const unsigned char compressed[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, + 0xcb, 0xcf, 0x4b, 0xca, 0xc9, 0x4f, 0xce, 0xce, 0xcc, 0x4b, 0x57, + 0x48, 0xaf, 0xca, 0x2c, 0x50, 0x28, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, + 0xe5, 0x02, 0x00, 0xd5, 0x80, 0x6a, 0xb7, 0x18, 0x00, 0x00, 0x00 + }; + static const unsigned char expected[] = "nonblocking gzip stream\n"; + unsigned char output[sizeof(expected)]; + int pipefd[2]; + int got; + int ret; + size_t have; + size_t i; + gzFile file; + + if (pipe(pipefd) == -1 || + fcntl(pipefd[0], F_SETFL, + fcntl(pipefd[0], F_GETFL) | O_NONBLOCK) == -1) { + perror("pipe"); + return 1; + } + file = gzdopen(pipefd[0], "rbN"); + if (file == NULL) { + fprintf(stderr, "gzdopen failed\n"); + return 1; + } + + have = 0; + for (i = 0; i < sizeof(compressed); i++) { + if (write(pipefd[1], compressed + i, 1) != 1) { + perror("write"); + return 1; + } + do { + errno = 0; + got = gzread(file, output + have, + (unsigned)(sizeof(output) - have)); + if (got > 0) + have += (unsigned)got; + } while (got > 0); + if (got != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) { + fprintf(stderr, "gzread did not report a non-blocking stall\n"); + return 1; + } + } + + close(pipefd[1]); + do { + got = gzread(file, output + have, + (unsigned)(sizeof(output) - have)); + if (got > 0) + have += (unsigned)got; + } while (got > 0); + if (got != 0 || have != sizeof(expected) - 1 || + memcmp(output, expected, sizeof(expected) - 1) != 0) { + fprintf(stderr, "non-blocking gzip output mismatch\n"); + return 1; + } + + ret = gzclose(file); + if (ret != Z_OK) { + fprintf(stderr, "gzclose returned %d after a complete stream\n", ret); + return 1; + } + return 0; +}