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
80static 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
119static 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
152static void *myalloc(void *q, unsigned n, unsigned m) {
153    (void)q;
154    return calloc(n, m);
155}
156
157static void myfree(void *q, void *p) {
158    (void)q;
159    free(p);
160}
161
162typedef struct gzFile_s {
163    FILE *file;
164    int write;
165    int err;
166    char *msg;
167    z_stream strm;
168} *gzFile;
169
170static 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
204static gzFile gzopen(const char *path, const char *mode) {
205    return gz_open(path, -1, mode);
206}
207
208static gzFile gzdopen(int fd, const char *mode) {
209    return gz_open(NULL, fd, mode);
210}
211
212static 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
230static 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
261static 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
286static const char *gzerror(gzFile gz, int *err) {
287    *err = gz->err;
288    return gz->msg;
289}
290
291#endif
292
293static char *prog;
294
295/* ===========================================================================
296 * Display error message and exit
297 */
298static 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 */
308static 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
341static 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 */
369static 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 */
393static 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 */
429static 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
487int 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