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