diff --git a/zlibWrapper/Makefile b/zlibWrapper/Makefile index 830b294bb6f..ca3d8e1abfd 100644 --- a/zlibWrapper/Makefile +++ b/zlibWrapper/Makefile @@ -46,9 +46,11 @@ release : STDFLAGS = release : DEBUGFLAGS = release : all -all: fitblk example zwrapbench minigzip +all: fitblk example zwrapbench minigzip gzfinishtest -test: example fitblk example_zstd fitblk_zstd zwrapbench minigzip minigzip_zstd +test: example fitblk example_zstd fitblk_zstd zwrapbench minigzip minigzip_zstd gzfinishtest gzfinishtest_zstd + ./gzfinishtest + ./gzfinishtest_zstd ./example ./example_zstd ./fitblk 10240 <$(TEST_FILE) @@ -83,6 +85,12 @@ test-valgrind: clean example fitblk example_zstd fitblk_zstd zwrapbench #.c.o: # $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ +gzfinishtest: gzfinishtest.o zstd_zlibwrapper.o $(GZFILES) $(ZSTDLIBRARY) + $(LINK.o) $^ $(LDLIBS) $(OUTPUT_OPTION) + +gzfinishtest_zstd: gzfinishtest.o zstdTurnedOn_zlibwrapper.o $(GZFILES) $(ZSTDLIBRARY) + $(LINK.o) $^ $(LDLIBS) $(OUTPUT_OPTION) + minigzip: minigzip.o zstd_zlibwrapper.o $(GZFILES) $(ZSTDLIBRARY) minigzip_zstd: minigzip.o zstdTurnedOn_zlibwrapper.o $(GZFILES) $(ZSTDLIBRARY) diff --git a/zlibWrapper/examples/gzfinishtest.c b/zlibWrapper/examples/gzfinishtest.c new file mode 100644 index 00000000000..8209e1dc0ff --- /dev/null +++ b/zlibWrapper/examples/gzfinishtest.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +/* gzfinishtest : check that gzflush(Z_FINISH) does not start an empty member. + * + * Writes one small buffer, finishes the member with gzflush(), flushes again + * with no data in between, then closes the file. The result must contain + * exactly one gzip member : the file size must equal the size obtained when + * writing the same data without the intermediate flushes. + */ + +#include +#include +#include +#define ZLIB_CONST +#define Z_PREFIX +#include "zlib.h" + +static int writeMode(const char* path, int mode) +{ + gzFile file = gzopen(path, "wb"); + if (file == NULL) { + fprintf(stderr, "gzfinishtest: gzopen failed\n"); + return 1; + } + if (gzwrite(file, "hello", 5) != 5) { + fprintf(stderr, "gzfinishtest: gzwrite failed\n"); + return 1; + } + if (mode >= 1) { + if (gzflush(file, Z_FINISH) != Z_OK) { + fprintf(stderr, "gzfinishtest: gzflush failed\n"); + return 1; + } + } + if (mode >= 2) { + /* second finish with no data in between : must not emit a member */ + if (gzflush(file, Z_FINISH) != Z_OK) { + fprintf(stderr, "gzfinishtest: second gzflush failed\n"); + return 1; + } + } + if (gzclose(file) != Z_OK) { + fprintf(stderr, "gzfinishtest: gzclose failed\n"); + return 1; + } + return 0; +} + +static long fileSize(const char* path) +{ + FILE* const f = fopen(path, "rb"); + long size; + if (f == NULL) return -1; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; } + size = ftell(f); + fclose(f); + return size; +} + +int main(int argc, char** argv) +{ + const char* name = (argc > 1) ? argv[1] : "gzfinishtest.tmp"; + long plain, flushed, twice; + + if (writeMode(name, 0)) return 1; + plain = fileSize(name); + if (writeMode(name, 1)) return 1; + flushed = fileSize(name); + if (writeMode(name, 2)) return 1; + twice = fileSize(name); + + if (plain < 0 || flushed != plain || twice != plain) { + fprintf(stderr, "gzfinishtest: file sizes differ : plain=%ld flushed=%ld twice=%ld " + "(spurious empty gzip member after Z_FINISH)\n", + plain, flushed, twice); + remove(name); + return 1; + } + + printf("gzfinishtest OK : %ld bytes in all cases\n", plain); + remove(name); + return 0; +} diff --git a/zlibWrapper/gzguts.h b/zlibWrapper/gzguts.h index 70a609d724c..cd64d01f2d7 100644 --- a/zlibWrapper/gzguts.h +++ b/zlibWrapper/gzguts.h @@ -197,6 +197,7 @@ typedef struct { /* just for writing */ int level; /* compression level */ int strategy; /* compression strategy */ + int reset; /* true if a reset is pending after a Z_FINISH */ /* seek request */ z_off64_t skip; /* amount to skip (already rewound if backwards) */ int seek; /* true if seek request pending */ diff --git a/zlibWrapper/gzlib.c b/zlibWrapper/gzlib.c index c726515113c..bbe7385b552 100644 --- a/zlibWrapper/gzlib.c +++ b/zlibWrapper/gzlib.c @@ -81,6 +81,7 @@ local void gz_reset(gz_statep state) { state.state->how = LOOK; /* look for gzip header */ } state.state->seek = 0; /* no seek request pending */ + state.state->reset = 0; /* no deflateReset pending */ gz_error(state, Z_OK, NULL); /* clear error */ state.state->x.pos = 0; /* no uncompressed data yet */ state.state->strm.avail_in = 0; /* no input data yet */ diff --git a/zlibWrapper/gzwrite.c b/zlibWrapper/gzwrite.c index 85b776a94f7..a007e469eb0 100644 --- a/zlibWrapper/gzwrite.c +++ b/zlibWrapper/gzwrite.c @@ -98,6 +98,15 @@ local int gz_comp(gz_statep state, int flush) { return 0; } + /* check for a pending reset */ + if (state.state->reset) { + /* don't start a new gzip member unless there is data to write */ + if (strm->avail_in == 0) + return 0; + deflateReset(strm); + state.state->reset = 0; + } + /* run deflate() on provided input until it produces no more output */ ret = Z_OK; do { @@ -135,7 +144,7 @@ local int gz_comp(gz_statep state, int flush) { /* if that completed a deflate stream, allow another to start */ if (flush == Z_FINISH) - deflateReset(strm); + state.state->reset = 1; /* all done, no errors */ return 0;