1/* minigzip.c -- simulate gzip using the zlib compression library
2 * Copyright (C) 1995-2006, 2010 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
141char *prog;
142
143void error            OF((const char *msg));
144void gz_compress      OF((FILE   *in, gzFile out));
145#ifdef USE_MMAP
146int  gz_compress_mmap OF((FILE   *in, gzFile out));
147#endif
148void gz_uncompress    OF((gzFile in, FILE   *out));
149void file_compress    OF((char  *file, char *mode));
150void file_uncompress  OF((char  *file));
151int  main             OF((int argc, char *argv[]));
152
153/* ===========================================================================
154 * Display error message and exit
155 */
156void error(msg)
157    const char *msg;
158{
159    fprintf(stderr, "%s: %s\n", prog, msg);
160    exit(1);
161}
162
163/* ===========================================================================
164 * Compress input to output then close both files.
165 */
166
167void gz_compress(in, out)
168    FILE   *in;
169    gzFile out;
170{
171    local char buf[BUFLEN];
172    int len;
173    int err;
174
175#ifdef USE_MMAP
176    /* Try first compressing with mmap. If mmap fails (minigzip used in a
177     * pipe), use the normal fread loop.
178     */
179    if (gz_compress_mmap(in, out) == Z_OK) return;
180#endif
181    for (;;) {
182        len = (int)fread(buf, 1, sizeof(buf), in);
183        if (ferror(in)) {
184            perror("fread");
185            exit(1);
186        }
187        if (len == 0) break;
188
189        if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err));
190    }
191    fclose(in);
192    if (gzclose(out) != Z_OK) error("failed gzclose");
193}
194
195#ifdef USE_MMAP /* MMAP version, Miguel Albrecht <malbrech@eso.org> */
196
197/* Try compressing the input file at once using mmap. Return Z_OK if
198 * if success, Z_ERRNO otherwise.
199 */
200int gz_compress_mmap(in, out)
201    FILE   *in;
202    gzFile out;
203{
204    int len;
205    int err;
206    int ifd = fileno(in);
207    caddr_t buf;    /* mmap'ed buffer for the entire input file */
208    off_t buf_len;  /* length of the input file */
209    struct stat sb;
210
211    /* Determine the size of the file, needed for mmap: */
212    if (fstat(ifd, &sb) < 0) return Z_ERRNO;
213    buf_len = sb.st_size;
214    if (buf_len <= 0) return Z_ERRNO;
215
216    /* Now do the actual mmap: */
217    buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0);
218    if (buf == (caddr_t)(-1)) return Z_ERRNO;
219
220    /* Compress the whole file at once: */
221    len = gzwrite(out, (char *)buf, (unsigned)buf_len);
222
223    if (len != (int)buf_len) error(gzerror(out, &err));
224
225    munmap(buf, buf_len);
226    fclose(in);
227    if (gzclose(out) != Z_OK) error("failed gzclose");
228    return Z_OK;
229}
230#endif /* USE_MMAP */
231
232/* ===========================================================================
233 * Uncompress input to output then close both files.
234 */
235void gz_uncompress(in, out)
236    gzFile in;
237    FILE   *out;
238{
239    local char buf[BUFLEN];
240    int len;
241    int err;
242
243    for (;;) {
244        len = gzread(in, buf, sizeof(buf));
245        if (len < 0) error (gzerror(in, &err));
246        if (len == 0) break;
247
248        if ((int)fwrite(buf, 1, (unsigned)len, out) != len) {
249            error("failed fwrite");
250        }
251    }
252    if (fclose(out)) error("failed fclose");
253
254    if (gzclose(in) != Z_OK) error("failed gzclose");
255}
256
257
258/* ===========================================================================
259 * Compress the given file: create a corresponding .gz file and remove the
260 * original.
261 */
262void file_compress(file, mode)
263    char  *file;
264    char  *mode;
265{
266    local char outfile[MAX_NAME_LEN];
267    FILE  *in;
268    gzFile out;
269
270    if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) {
271        fprintf(stderr, "%s: filename too long\n", prog);
272        exit(1);
273    }
274
275    strcpy(outfile, file);
276    strcat(outfile, GZ_SUFFIX);
277
278    in = fopen(file, "rb");
279    if (in == NULL) {
280        perror(file);
281        exit(1);
282    }
283    out = gzopen(outfile, mode);
284    if (out == NULL) {
285        fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile);
286        exit(1);
287    }
288    gz_compress(in, out);
289
290    unlink(file);
291}
292
293
294/* ===========================================================================
295 * Uncompress the given file and remove the original.
296 */
297void file_uncompress(file)
298    char  *file;
299{
300    local char buf[MAX_NAME_LEN];
301    char *infile, *outfile;
302    FILE  *out;
303    gzFile in;
304    size_t len = strlen(file);
305
306    if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) {
307        fprintf(stderr, "%s: filename too long\n", prog);
308        exit(1);
309    }
310
311    strcpy(buf, file);
312
313    if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) {
314        infile = file;
315        outfile = buf;
316        outfile[len-3] = '\0';
317    } else {
318        outfile = file;
319        infile = buf;
320        strcat(infile, GZ_SUFFIX);
321    }
322    in = gzopen(infile, "rb");
323    if (in == NULL) {
324        fprintf(stderr, "%s: can't gzopen %s\n", prog, infile);
325        exit(1);
326    }
327    out = fopen(outfile, "wb");
328    if (out == NULL) {
329        perror(file);
330        exit(1);
331    }
332
333    gz_uncompress(in, out);
334
335    unlink(infile);
336}
337
338
339/* ===========================================================================
340 * Usage:  minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...]
341 *   -c : write to standard output
342 *   -d : decompress
343 *   -f : compress with Z_FILTERED
344 *   -h : compress with Z_HUFFMAN_ONLY
345 *   -r : compress with Z_RLE
346 *   -1 to -9 : compression level
347 */
348
349int main(argc, argv)
350    int argc;
351    char *argv[];
352{
353    int copyout = 0;
354    int uncompr = 0;
355    gzFile file;
356    char *bname, outmode[20];
357
358    strcpy(outmode, "wb6 ");
359
360    prog = argv[0];
361    bname = strrchr(argv[0], '/');
362    if (bname)
363      bname++;
364    else
365      bname = argv[0];
366    argc--, argv++;
367
368    if (!strcmp(bname, "gunzip"))
369      uncompr = 1;
370    else if (!strcmp(bname, "zcat"))
371      copyout = uncompr = 1;
372
373    while (argc > 0) {
374      if (strcmp(*argv, "-c") == 0)
375        copyout = 1;
376      else if (strcmp(*argv, "-d") == 0)
377        uncompr = 1;
378      else if (strcmp(*argv, "-f") == 0)
379        outmode[3] = 'f';
380      else if (strcmp(*argv, "-h") == 0)
381        outmode[3] = 'h';
382      else if (strcmp(*argv, "-r") == 0)
383        outmode[3] = 'R';
384      else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' &&
385               (*argv)[2] == 0)
386        outmode[2] = (*argv)[1];
387      else
388        break;
389      argc--, argv++;
390    }
391    if (outmode[3] == ' ')
392        outmode[3] = 0;
393    if (argc == 0) {
394        SET_BINARY_MODE(stdin);
395        SET_BINARY_MODE(stdout);
396        if (uncompr) {
397            file = gzdopen(fileno(stdin), "rb");
398            if (file == NULL) error("can't gzdopen stdin");
399            gz_uncompress(file, stdout);
400        } else {
401            file = gzdopen(fileno(stdout), outmode);
402            if (file == NULL) error("can't gzdopen stdout");
403            gz_compress(stdin, file);
404        }
405    } else {
406        if (copyout) {
407            SET_BINARY_MODE(stdout);
408        }
409        do {
410            if (uncompr) {
411                if (copyout) {
412                    file = gzopen(*argv, "rb");
413                    if (file == NULL)
414                        fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv);
415                    else
416                        gz_uncompress(file, stdout);
417                } else {
418                    file_uncompress(*argv);
419                }
420            } else {
421                if (copyout) {
422                    FILE * in = fopen(*argv, "rb");
423
424                    if (in == NULL) {
425                        perror(*argv);
426                    } else {
427                        file = gzdopen(fileno(stdout), outmode);
428                        if (file == NULL) error("can't gzdopen stdout");
429
430                        gz_compress(in, file);
431                    }
432
433                } else {
434                    file_compress(*argv, outmode);
435                }
436            }
437        } while (argv++, --argc);
438    }
439    return 0;
440}
441