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