xref: /src/sys/contrib/zlib/test/minigzip.c (revision 7aa1dba6b00ccfb7d66627badc8a7aaa06b02946)
1 /* minigzip.c -- simulate gzip using the zlib compression library
2  * Copyright (C) 1995-2026 Jean-loup Gailly
3  * For conditions of distribution and use, see copyright notice in zlib.h
4  */
5 
6 /*
7  * minigzip is a minimal implementation of the gzip utility. This is
8  * only an example of using zlib and isn't meant to replace the
9  * full-featured gzip. No attempt is made to deal with file systems
10  * limiting names to 14 or 8+3 characters, etc... Error checking is
11  * very limited. So use minigzip only for testing; use gzip for the
12  * real thing. On MSDOS, use only on file names without extension
13  * or in pipe mode.
14  */
15 
16 /* @(#) $Id$ */
17 
18 #ifndef _POSIX_C_SOURCE
19 #  define _POSIX_C_SOURCE 200112L
20 #endif
21 
22 #if defined(_WIN32) && !defined(_CRT_SECURE_NO_WARNINGS)
23 #  define _CRT_SECURE_NO_WARNINGS
24 #endif
25 #if defined(_WIN32) && !defined(_CRT_NONSTDC_NO_DEPRECATE)
26 #  define _CRT_NONSTDC_NO_DEPRECATE
27 #endif
28 
29 #include "zlib.h"
30 #include <stdio.h>
31 
32 #ifdef STDC
33 #  include <string.h>
34 #  include <stdlib.h>
35 #endif
36 
37 #ifdef USE_MMAP
38 #  include <sys/types.h>
39 #  include <sys/mman.h>
40 #  include <sys/stat.h>
41 #endif
42 
43 #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
44 #  include <fcntl.h>
45 #  include <io.h>
46 #  ifdef UNDER_CE
47 #    include <stdlib.h>
48 #  endif
49 #  define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
50 #else
51 #  define SET_BINARY_MODE(file)
52 #endif
53 
54 #ifdef VMS
55 #  define unlink delete
56 #  define GZ_SUFFIX "-gz"
57 #endif
58 #if defined(__riscos) && !defined(__TARGET_UNIXLIB__)
59 #  define GZ_SUFFIX "/gz"
60 #  ifndef __GNUC__
61 #    define unlink remove
62 #    define fileno(file) file->__file
63 #  endif
64 #endif
65 #if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
66 #  include <unix.h> /* for fileno */
67 #endif
68 
69 #if !defined(Z_HAVE_UNISTD_H) && !defined(_LARGEFILE64_SOURCE)
70 #ifndef WIN32 /* unlink already in stdio.h for WIN32 */
71   extern int unlink(const char *);
72 #endif
73 #endif
74 
75 #if defined(UNDER_CE)
76 #  include <windows.h>
77 #  define perror(s) pwinerror(s)
78 
79 /* Map the Windows error number in ERROR to a locale-dependent error
80    message string and return a pointer to it.  Typically, the values
81    for ERROR come from GetLastError.
82 
83    The string pointed to shall not be modified by the application,
84    but may be overwritten by a subsequent call to strwinerror
85 
86    The strwinerror function does not change the current setting
87    of GetLastError.  */
88 
strwinerror(error)89 static char *strwinerror (error)
90      DWORD error;
91 {
92     static char buf[1024];
93 
94     wchar_t *msgbuf;
95     DWORD lasterr = GetLastError();
96     DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
97         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
98         NULL,
99         error,
100         0, /* Default language */
101         (LPVOID)&msgbuf,
102         0,
103         NULL);
104     if (chars != 0) {
105         /* If there is an \r\n appended, zap it.  */
106         if (chars >= 2
107             && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
108             chars -= 2;
109             msgbuf[chars] = 0;
110         }
111 
112         if (chars > sizeof (buf) - 1) {
113             chars = sizeof (buf) - 1;
114             msgbuf[chars] = 0;
115         }
116 
117         wcstombs(buf, msgbuf, chars + 1);
118         LocalFree(msgbuf);
119     }
120     else {
121         sprintf(buf, "unknown win32 error (%lu)", error);
122     }
123 
124     SetLastError(lasterr);
125     return buf;
126 }
127 
pwinerror(s)128 static void pwinerror (s)
129     const char *s;
130 {
131     if (s && *s)
132         fprintf(stderr, "%s: %s\n", s, strwinerror(GetLastError ()));
133     else
134         fprintf(stderr, "%s\n", strwinerror(GetLastError ()));
135 }
136 
137 #endif /* UNDER_CE */
138 
139 #ifndef GZ_SUFFIX
140 #  define GZ_SUFFIX ".gz"
141 #endif
142 #define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1)
143 
144 #define BUFLEN      16384
145 #define MAX_NAME_LEN 1024
146 
147 #ifdef MAXSEG_64K
148 #  define local static
149    /* Needed for systems with limitation on stack size. */
150 #else
151 #  define local
152 #endif
153 
154 /* ===========================================================================
155  * Safe string copy. Copy up to len bytes from src to dst, if src terminates
156  * with a null by then. If not, copy len-1 bytes from src, terminating it with
157  * a null in dst[len-1], cutting src short. Return a pointer to the terminating
158  * null. If len is zero, nothing is written to *dst and NULL is returned.
159  */
string_copy(char * dst,char const * src,z_size_t len)160 static char *string_copy(char *dst, char const *src, z_size_t len) {
161     if (len == 0)
162         return NULL;
163     while (--len) {
164         *dst = *src++;
165         if (*dst == 0)
166             return dst;
167         dst++;
168     }
169     *dst = 0;
170     return dst;
171 }
172 
173 #ifdef Z_SOLO
174 /* for Z_SOLO, create simplified gz* functions using deflate and inflate */
175 
176 #if defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE)
177 #  include <unistd.h>       /* for unlink() */
178 #endif
179 
myalloc(void * q,unsigned n,unsigned m)180 static void *myalloc(void *q, unsigned n, unsigned m) {
181     (void)q;
182     return calloc(n, m);
183 }
184 
myfree(void * q,void * p)185 static void myfree(void *q, void *p) {
186     (void)q;
187     free(p);
188 }
189 
190 typedef struct gzFile_s {
191     FILE *file;
192     int write;
193     int err;
194     char *msg;
195     z_stream strm;
196 } *gzFile;
197 
gz_open(const char * path,int fd,const char * mode)198 static gzFile gz_open(const char *path, int fd, const char *mode) {
199     gzFile gz;
200     int ret;
201 
202     gz = malloc(sizeof(struct gzFile_s));
203     if (gz == NULL)
204         return NULL;
205     gz->write = strchr(mode, 'w') != NULL;
206     gz->strm.zalloc = myalloc;
207     gz->strm.zfree = myfree;
208     gz->strm.opaque = Z_NULL;
209     if (gz->write)
210         ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0);
211     else {
212         gz->strm.next_in = 0;
213         gz->strm.avail_in = Z_NULL;
214         ret = inflateInit2(&(gz->strm), 15 + 16);
215     }
216     if (ret != Z_OK) {
217         free(gz);
218         return NULL;
219     }
220     gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") :
221                               fopen(path, gz->write ? "wb" : "rb");
222     if (gz->file == NULL) {
223         gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm));
224         free(gz);
225         return NULL;
226     }
227     gz->err = 0;
228     gz->msg = "";
229     return gz;
230 }
231 
gzopen(const char * path,const char * mode)232 static gzFile gzopen(const char *path, const char *mode) {
233     return gz_open(path, -1, mode);
234 }
235 
gzdopen(int fd,const char * mode)236 static gzFile gzdopen(int fd, const char *mode) {
237     return gz_open(NULL, fd, mode);
238 }
239 
gzwrite(gzFile gz,const void * buf,unsigned len)240 static int gzwrite(gzFile gz, const void *buf, unsigned len) {
241     z_stream *strm;
242     unsigned char out[BUFLEN];
243 
244     if (gz == NULL || !gz->write)
245         return 0;
246     strm = &(gz->strm);
247     strm->next_in = (void *)buf;
248     strm->avail_in = len;
249     do {
250         strm->next_out = out;
251         strm->avail_out = BUFLEN;
252         (void)deflate(strm, Z_NO_FLUSH);
253         fwrite(out, 1, BUFLEN - strm->avail_out, gz->file);
254     } while (strm->avail_out == 0);
255     return (int)len;
256 }
257 
gzread(gzFile gz,void * buf,unsigned len)258 static int gzread(gzFile gz, void *buf, unsigned len) {
259     int ret;
260     unsigned got;
261     unsigned char in[1];
262     z_stream *strm;
263 
264     if (gz == NULL || gz->write)
265         return 0;
266     if (gz->err)
267         return 0;
268     strm = &(gz->strm);
269     strm->next_out = (void *)buf;
270     strm->avail_out = len;
271     do {
272         got = (unsigned)fread(in, 1, 1, gz->file);
273         if (got == 0)
274             break;
275         strm->next_in = in;
276         strm->avail_in = 1;
277         ret = inflate(strm, Z_NO_FLUSH);
278         if (ret == Z_DATA_ERROR) {
279             gz->err = Z_DATA_ERROR;
280             gz->msg = strm->msg;
281             return 0;
282         }
283         if (ret == Z_STREAM_END)
284             inflateReset(strm);
285     } while (strm->avail_out);
286     return (int)(len - strm->avail_out);
287 }
288 
gzclose(gzFile gz)289 static int gzclose(gzFile gz) {
290     z_stream *strm;
291     unsigned char out[BUFLEN];
292 
293     if (gz == NULL)
294         return Z_STREAM_ERROR;
295     strm = &(gz->strm);
296     if (gz->write) {
297         strm->next_in = Z_NULL;
298         strm->avail_in = 0;
299         do {
300             strm->next_out = out;
301             strm->avail_out = BUFLEN;
302             (void)deflate(strm, Z_FINISH);
303             fwrite(out, 1, BUFLEN - strm->avail_out, gz->file);
304         } while (strm->avail_out == 0);
305         deflateEnd(strm);
306     }
307     else
308         inflateEnd(strm);
309     fclose(gz->file);
310     free(gz);
311     return Z_OK;
312 }
313 
gzerror(gzFile gz,int * err)314 static const char *gzerror(gzFile gz, int *err) {
315     *err = gz->err;
316     return gz->msg;
317 }
318 
319 #endif
320 
321 static char *prog;
322 
323 /* ===========================================================================
324  * Display error message and exit
325  */
error(const char * msg)326 static void error(const char *msg) {
327     fprintf(stderr, "%s: %s\n", prog, msg);
328     exit(1);
329 }
330 
331 #ifdef USE_MMAP /* MMAP version, Miguel Albrecht <malbrech@eso.org> */
332 
333 /* Try compressing the input file at once using mmap. Return Z_OK if
334  * success, Z_ERRNO otherwise.
335  */
gz_compress_mmap(FILE * in,gzFile out)336 static int gz_compress_mmap(FILE *in, gzFile out) {
337     int len;
338     int err;
339     int ifd = fileno(in);
340     caddr_t buf;    /* mmap'ed buffer for the entire input file */
341     off_t buf_len;  /* length of the input file */
342     struct stat sb;
343 
344     /* Determine the size of the file, needed for mmap: */
345     if (fstat(ifd, &sb) < 0) return Z_ERRNO;
346     buf_len = sb.st_size;
347     if (buf_len <= 0) return Z_ERRNO;
348 
349     /* Now do the actual mmap: */
350     buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0);
351     if (buf == (caddr_t)(-1)) return Z_ERRNO;
352 
353     /* Compress the whole file at once: */
354     len = gzwrite(out, (char *)buf, (unsigned)buf_len);
355 
356     if (len != (int)buf_len) error(gzerror(out, &err));
357 
358     munmap(buf, buf_len);
359     fclose(in);
360     if (gzclose(out) != Z_OK) error("failed gzclose");
361     return Z_OK;
362 }
363 #endif /* USE_MMAP */
364 
365 /* ===========================================================================
366  * Compress input to output then close both files.
367  */
368 
gz_compress(FILE * in,gzFile out)369 static void gz_compress(FILE *in, gzFile out) {
370     local char buf[BUFLEN];
371     int len;
372     int err;
373 
374 #ifdef USE_MMAP
375     /* Try first compressing with mmap. If mmap fails (minigzip used in a
376      * pipe), use the normal fread loop.
377      */
378     if (gz_compress_mmap(in, out) == Z_OK) return;
379 #endif
380     for (;;) {
381         len = (int)fread(buf, 1, sizeof(buf), in);
382         if (ferror(in)) {
383             perror("fread");
384             exit(1);
385         }
386         if (len == 0) break;
387 
388         if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err));
389     }
390     fclose(in);
391     if (gzclose(out) != Z_OK) error("failed gzclose");
392 }
393 
394 /* ===========================================================================
395  * Uncompress input to output then close both files.
396  */
gz_uncompress(gzFile in,FILE * out)397 static void gz_uncompress(gzFile in, FILE *out) {
398     local char buf[BUFLEN];
399     int len;
400     int err;
401 
402     for (;;) {
403         len = gzread(in, buf, sizeof(buf));
404         if (len < 0) error (gzerror(in, &err));
405         if (len == 0) break;
406 
407         if ((int)fwrite(buf, 1, (unsigned)len, out) != len) {
408             error("failed fwrite");
409         }
410     }
411     if (fclose(out)) error("failed fclose");
412 
413     if (gzclose(in) != Z_OK) error("failed gzclose");
414 }
415 
416 
417 /* ===========================================================================
418  * Compress the given file: create a corresponding .gz file and remove the
419  * original.
420  */
file_compress(char * file,char * mode)421 static void file_compress(char *file, char *mode) {
422     local char outfile[MAX_NAME_LEN+1], *end;
423     FILE  *in;
424     gzFile out;
425 
426     if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) {
427         fprintf(stderr, "%s: filename too long\n", prog);
428         exit(1);
429     }
430 
431     end = string_copy(outfile, file, sizeof(outfile));
432     string_copy(end, GZ_SUFFIX, sizeof(outfile) - (z_size_t)(end - outfile));
433 
434     in = fopen(file, "rb");
435     if (in == NULL) {
436         perror(file);
437         exit(1);
438     }
439     out = gzopen(outfile, mode);
440     if (out == NULL) {
441         fclose(in);
442         fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile);
443         exit(1);
444     }
445     gz_compress(in, out);
446 
447     unlink(file);
448 }
449 
450 
451 /* ===========================================================================
452  * Uncompress the given file and remove the original.
453  */
file_uncompress(char * file)454 static void file_uncompress(char *file) {
455     local char buf[MAX_NAME_LEN+1];
456     char *infile, *outfile;
457     FILE  *out;
458     gzFile in;
459     z_size_t len = strlen(file);
460 
461     if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) {
462         fprintf(stderr, "%s: filename too long\n", prog);
463         exit(1);
464     }
465 
466     string_copy(buf, file, sizeof(buf));
467 
468     if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) {
469         infile = file;
470         outfile = buf;
471         outfile[len-3] = '\0';
472     } else {
473         outfile = file;
474         infile = buf;
475         string_copy(buf + len, GZ_SUFFIX, sizeof(buf) - len);
476     }
477     in = gzopen(infile, "rb");
478     if (in == NULL) {
479         fprintf(stderr, "%s: can't gzopen %s\n", prog, infile);
480         exit(1);
481     }
482     out = fopen(outfile, "wb");
483     if (out == NULL) {
484         gzclose(in);
485         perror(file);
486         exit(1);
487     }
488 
489     gz_uncompress(in, out);
490 
491     unlink(infile);
492 }
493 
494 
495 /* ===========================================================================
496  * Usage:  minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...]
497  *   -c : write to standard output
498  *   -d : decompress
499  *   -f : compress with Z_FILTERED
500  *   -h : compress with Z_HUFFMAN_ONLY
501  *   -r : compress with Z_RLE
502  *   -1 to -9 : compression level
503  */
504 
main(int argc,char * argv[])505 int main(int argc, char *argv[]) {
506     int copyout = 0;
507     int uncompr = 0;
508     gzFile file;
509     char *bname, outmode[5];
510 
511     string_copy(outmode, "wb6 ", sizeof(outmode));
512     prog = argv[0];
513     bname = strrchr(argv[0], '/');
514     if (bname)
515       bname++;
516     else
517       bname = argv[0];
518     argc--, argv++;
519 
520     if (!strcmp(bname, "gunzip"))
521       uncompr = 1;
522     else if (!strcmp(bname, "zcat"))
523       copyout = uncompr = 1;
524 
525     while (argc > 0) {
526       if (strcmp(*argv, "-c") == 0)
527         copyout = 1;
528       else if (strcmp(*argv, "-d") == 0)
529         uncompr = 1;
530       else if (strcmp(*argv, "-f") == 0)
531         outmode[3] = 'f';
532       else if (strcmp(*argv, "-h") == 0)
533         outmode[3] = 'h';
534       else if (strcmp(*argv, "-r") == 0)
535         outmode[3] = 'R';
536       else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' &&
537                (*argv)[2] == 0)
538         outmode[2] = (*argv)[1];
539       else
540         break;
541       argc--, argv++;
542     }
543     if (outmode[3] == ' ')
544         outmode[3] = 0;
545     if (argc == 0) {
546         SET_BINARY_MODE(stdin);
547         SET_BINARY_MODE(stdout);
548         if (uncompr) {
549             file = gzdopen(fileno(stdin), "rb");
550             if (file == NULL) error("can't gzdopen stdin");
551             gz_uncompress(file, stdout);
552         } else {
553             file = gzdopen(fileno(stdout), outmode);
554             if (file == NULL) error("can't gzdopen stdout");
555             gz_compress(stdin, file);
556         }
557     } else {
558         if (copyout) {
559             SET_BINARY_MODE(stdout);
560         }
561         do {
562             if (uncompr) {
563                 if (copyout) {
564                     file = gzopen(*argv, "rb");
565                     if (file == NULL)
566                         fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv);
567                     else
568                         gz_uncompress(file, stdout);
569                 } else {
570                     file_uncompress(*argv);
571                 }
572             } else {
573                 if (copyout) {
574                     FILE * in = fopen(*argv, "rb");
575 
576                     if (in == NULL) {
577                         perror(*argv);
578                     } else {
579                         file = gzdopen(fileno(stdout), outmode);
580                         if (file == NULL) error("can't gzdopen stdout");
581 
582                         gz_compress(in, file);
583                     }
584 
585                 } else {
586                     file_compress(*argv, outmode);
587                 }
588             }
589         } while (argv++, --argc);
590     }
591     return 0;
592 }
593