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 _MSC_VER
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 OF((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
152void *myalloc OF((void *, unsigned, unsigned));
153void myfree OF((void *, void *));
154
155void *myalloc(q, n, m)
156    void *q;
157    unsigned n, m;
158{
159    q = Z_NULL;
160    return calloc(n, m);
161}
162
163void myfree(q, p)
164    void *q, *p;
165{
166    q = Z_NULL;
167    free(p);
168}
169
170typedef struct gzFile_s {
171    FILE *file;
172    int write;
173    int err;
174    char *msg;
175    z_stream strm;
176} *gzFile;
177
178gzFile gzopen OF((const char *, const char *));
179gzFile gzdopen OF((int, const char *));
180gzFile gz_open OF((const char *, int, const char *));
181
182gzFile gzopen(path, mode)
183const char *path;
184const char *mode;
185{
186    return gz_open(path, -1, mode);
187}
188
189gzFile gzdopen(fd, mode)
190int fd;
191const char *mode;
192{
193    return gz_open(NULL, fd, mode);
194}
195
196gzFile gz_open(path, fd, mode)
197    const char *path;
198    int fd;
199    const char *mode;
200{
201    gzFile gz;
202    int ret;
203
204    gz = malloc(sizeof(struct gzFile_s));
205    if (gz == NULL)
206        return NULL;
207    gz->write = strchr(mode, 'w') != NULL;
208    gz->strm.zalloc = myalloc;
209    gz->strm.zfree = myfree;
210    gz->strm.opaque = Z_NULL;
211    if (gz->write)
212        ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0);
213    else {
214        gz->strm.next_in = 0;
215        gz->strm.avail_in = Z_NULL;
216        ret = inflateInit2(&(gz->strm), 15 + 16);
217    }
218    if (ret != Z_OK) {
219        free(gz);
220        return NULL;
221    }
222    gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") :
223                              fopen(path, gz->write ? "wb" : "rb");
224    if (gz->file == NULL) {
225        gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm));
226        free(gz);
227        return NULL;
228    }
229    gz->err = 0;
230    gz->msg = "";
231    return gz;
232}
233
234int gzwrite OF((gzFile, const void *, unsigned));
235
236int gzwrite(gz, buf, len)
237    gzFile gz;
238    const void *buf;
239    unsigned len;
240{
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 len;
256}
257
258int gzread OF((gzFile, void *, unsigned));
259
260int gzread(gz, buf, len)
261    gzFile gz;
262    void *buf;
263    unsigned len;
264{
265    int ret;
266    unsigned got;
267    unsigned char in[1];
268    z_stream *strm;
269
270    if (gz == NULL || gz->write)
271        return 0;
272    if (gz->err)
273        return 0;
274    strm = &(gz->strm);
275    strm->next_out = (void *)buf;
276    strm->avail_out = len;
277    do {
278        got = fread(in, 1, 1, gz->file);
279        if (got == 0)
280            break;
281        strm->next_in = in;
282        strm->avail_in = 1;
283        ret = inflate(strm, Z_NO_FLUSH);
284        if (ret == Z_DATA_ERROR) {
285            gz->err = Z_DATA_ERROR;
286            gz->msg = strm->msg;
287            return 0;
288        }
289        if (ret == Z_STREAM_END)
290            inflateReset(strm);
291    } while (strm->avail_out);
292    return len - strm->avail_out;
293}
294
295int gzclose OF((gzFile));
296
297int gzclose(gz)
298    gzFile gz;
299{
300    z_stream *strm;
301    unsigned char out[BUFLEN];
302
303    if (gz == NULL)
304        return Z_STREAM_ERROR;
305    strm = &(gz->strm);
306    if (gz->write) {
307        strm->next_in = Z_NULL;
308        strm->avail_in = 0;
309        do {
310            strm->next_out = out;
311            strm->avail_out = BUFLEN;
312            (void)deflate(strm, Z_FINISH);
313            fwrite(out, 1, BUFLEN - strm->avail_out, gz->file);
314        } while (strm->avail_out == 0);
315        deflateEnd(strm);
316    }
317    else
318        inflateEnd(strm);
319    fclose(gz->file);
320    free(gz);
321    return Z_OK;
322}
323
324const char *gzerror OF((gzFile, int *));
325
326const char *gzerror(gz, err)
327    gzFile gz;
328    int *err;
329{
330    *err = gz->err;
331    return gz->msg;
332}
333
334#endif
335
336char *prog;
337
338void error            OF((const char *msg));
339void gz_compress      OF((FILE   *in, gzFile out));
340#ifdef USE_MMAP
341int  gz_compress_mmap OF((FILE   *in, gzFile out));
342#endif
343void gz_uncompress    OF((gzFile in, FILE   *out));
344void file_compress    OF((char  *file, char *mode));
345void file_uncompress  OF((char  *file));
346int  main             OF((int argc, char *argv[]));
347
348/* ===========================================================================
349 * Display error message and exit
350 */
351void error(msg)
352    const char *msg;
353{
354    fprintf(stderr, "%s: %s\n", prog, msg);
355    exit(1);
356}
357
358/* ===========================================================================
359 * Compress input to output then close both files.
360 */
361
362void gz_compress(in, out)
363    FILE   *in;
364    gzFile out;
365{
366    local char buf[BUFLEN];
367    int len;
368    int err;
369
370#ifdef USE_MMAP
371    /* Try first compressing with mmap. If mmap fails (minigzip used in a
372     * pipe), use the normal fread loop.
373     */
374    if (gz_compress_mmap(in, out) == Z_OK) return;
375#endif
376    for (;;) {
377        len = (int)fread(buf, 1, sizeof(buf), in);
378        if (ferror(in)) {
379            perror("fread");
380            exit(1);
381        }
382        if (len == 0) break;
383
384        if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err));
385    }
386    fclose(in);
387    if (gzclose(out) != Z_OK) error("failed gzclose");
388}
389
390#ifdef USE_MMAP /* MMAP version, Miguel Albrecht <malbrech@eso.org> */
391
392/* Try compressing the input file at once using mmap. Return Z_OK if
393 * if success, Z_ERRNO otherwise.
394 */
395int gz_compress_mmap(in, out)
396    FILE   *in;
397    gzFile out;
398{
399    int len;
400    int err;
401    int ifd = fileno(in);
402    caddr_t buf;    /* mmap'ed buffer for the entire input file */
403    off_t buf_len;  /* length of the input file */
404    struct stat sb;
405
406    /* Determine the size of the file, needed for mmap: */
407    if (fstat(ifd, &sb) < 0) return Z_ERRNO;
408    buf_len = sb.st_size;
409    if (buf_len <= 0) return Z_ERRNO;
410
411    /* Now do the actual mmap: */
412    buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0);
413    if (buf == (caddr_t)(-1)) return Z_ERRNO;
414
415    /* Compress the whole file at once: */
416    len = gzwrite(out, (char *)buf, (unsigned)buf_len);
417
418    if (len != (int)buf_len) error(gzerror(out, &err));
419
420    munmap(buf, buf_len);
421    fclose(in);
422    if (gzclose(out) != Z_OK) error("failed gzclose");
423    return Z_OK;
424}
425#endif /* USE_MMAP */
426
427/* ===========================================================================
428 * Uncompress input to output then close both files.
429 */
430void gz_uncompress(in, out)
431    gzFile in;
432    FILE   *out;
433{
434    local char buf[BUFLEN];
435    int len;
436    int err;
437
438    for (;;) {
439        len = gzread(in, buf, sizeof(buf));
440        if (len < 0) error (gzerror(in, &err));
441        if (len == 0) break;
442
443        if ((int)fwrite(buf, 1, (unsigned)len, out) != len) {
444            error("failed fwrite");
445        }
446    }
447    if (fclose(out)) error("failed fclose");
448
449    if (gzclose(in) != Z_OK) error("failed gzclose");
450}
451
452
453/* ===========================================================================
454 * Compress the given file: create a corresponding .gz file and remove the
455 * original.
456 */
457void file_compress(file, mode)
458    char  *file;
459    char  *mode;
460{
461    local char outfile[MAX_NAME_LEN];
462    FILE  *in;
463    gzFile out;
464
465    if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) {
466        fprintf(stderr, "%s: filename too long\n", prog);
467        exit(1);
468    }
469
470#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
471    snprintf(outfile, sizeof(outfile), "%s%s", file, GZ_SUFFIX);
472#else
473    strcpy(outfile, file);
474    strcat(outfile, GZ_SUFFIX);
475#endif
476
477    in = fopen(file, "rb");
478    if (in == NULL) {
479        perror(file);
480        exit(1);
481    }
482    out = gzopen(outfile, mode);
483    if (out == NULL) {
484        fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile);
485        exit(1);
486    }
487    gz_compress(in, out);
488
489    unlink(file);
490}
491
492
493/* ===========================================================================
494 * Uncompress the given file and remove the original.
495 */
496void file_uncompress(file)
497    char  *file;
498{
499    local char buf[MAX_NAME_LEN];
500    char *infile, *outfile;
501    FILE  *out;
502    gzFile in;
503    size_t len = strlen(file);
504
505    if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) {
506        fprintf(stderr, "%s: filename too long\n", prog);
507        exit(1);
508    }
509
510#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
511    snprintf(buf, sizeof(buf), "%s", file);
512#else
513    strcpy(buf, file);
514#endif
515
516    if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) {
517        infile = file;
518        outfile = buf;
519        outfile[len-3] = '\0';
520    } else {
521        outfile = file;
522        infile = buf;
523#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
524        snprintf(buf + len, sizeof(buf) - len, "%s", GZ_SUFFIX);
525#else
526        strcat(infile, GZ_SUFFIX);
527#endif
528    }
529    in = gzopen(infile, "rb");
530    if (in == NULL) {
531        fprintf(stderr, "%s: can't gzopen %s\n", prog, infile);
532        exit(1);
533    }
534    out = fopen(outfile, "wb");
535    if (out == NULL) {
536        perror(file);
537        exit(1);
538    }
539
540    gz_uncompress(in, out);
541
542    unlink(infile);
543}
544
545
546/* ===========================================================================
547 * Usage:  minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...]
548 *   -c : write to standard output
549 *   -d : decompress
550 *   -f : compress with Z_FILTERED
551 *   -h : compress with Z_HUFFMAN_ONLY
552 *   -r : compress with Z_RLE
553 *   -1 to -9 : compression level
554 */
555
556int main(argc, argv)
557    int argc;
558    char *argv[];
559{
560    int copyout = 0;
561    int uncompr = 0;
562    gzFile file;
563    char *bname, outmode[20];
564
565#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
566    snprintf(outmode, sizeof(outmode), "%s", "wb6 ");
567#else
568    strcpy(outmode, "wb6 ");
569#endif
570
571    prog = argv[0];
572    bname = strrchr(argv[0], '/');
573    if (bname)
574      bname++;
575    else
576      bname = argv[0];
577    argc--, argv++;
578
579    if (!strcmp(bname, "gunzip"))
580      uncompr = 1;
581    else if (!strcmp(bname, "zcat"))
582      copyout = uncompr = 1;
583
584    while (argc > 0) {
585      if (strcmp(*argv, "-c") == 0)
586        copyout = 1;
587      else if (strcmp(*argv, "-d") == 0)
588        uncompr = 1;
589      else if (strcmp(*argv, "-f") == 0)
590        outmode[3] = 'f';
591      else if (strcmp(*argv, "-h") == 0)
592        outmode[3] = 'h';
593      else if (strcmp(*argv, "-r") == 0)
594        outmode[3] = 'R';
595      else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' &&
596               (*argv)[2] == 0)
597        outmode[2] = (*argv)[1];
598      else
599        break;
600      argc--, argv++;
601    }
602    if (outmode[3] == ' ')
603        outmode[3] = 0;
604    if (argc == 0) {
605        SET_BINARY_MODE(stdin);
606        SET_BINARY_MODE(stdout);
607        if (uncompr) {
608            file = gzdopen(fileno(stdin), "rb");
609            if (file == NULL) error("can't gzdopen stdin");
610            gz_uncompress(file, stdout);
611        } else {
612            file = gzdopen(fileno(stdout), outmode);
613            if (file == NULL) error("can't gzdopen stdout");
614            gz_compress(stdin, file);
615        }
616    } else {
617        if (copyout) {
618            SET_BINARY_MODE(stdout);
619        }
620        do {
621            if (uncompr) {
622                if (copyout) {
623                    file = gzopen(*argv, "rb");
624                    if (file == NULL)
625                        fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv);
626                    else
627                        gz_uncompress(file, stdout);
628                } else {
629                    file_uncompress(*argv);
630                }
631            } else {
632                if (copyout) {
633                    FILE * in = fopen(*argv, "rb");
634
635                    if (in == NULL) {
636                        perror(*argv);
637                    } else {
638                        file = gzdopen(fileno(stdout), outmode);
639                        if (file == NULL) error("can't gzdopen stdout");
640
641                        gz_compress(in, file);
642                    }
643
644                } else {
645                    file_compress(*argv, outmode);
646                }
647            }
648        } while (argv++, --argc);
649    }
650    return 0;
651}
652