Home | History | Annotate | Line # | Download | only in test
      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 */
     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 
     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 
    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 
    152 static void *myalloc(void *q, unsigned n, unsigned m) {
    153     (void)q;
    154     return calloc(n, m);
    155 }
    156 
    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 
    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 
    204 static gzFile gzopen(const char *path, const char *mode) {
    205     return gz_open(path, -1, mode);
    206 }
    207 
    208 static gzFile gzdopen(int fd, const char *mode) {
    209     return gz_open(NULL, fd, mode);
    210 }
    211 
    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 
    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 
    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 
    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  */
    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 (at) eso.org> */
    304 
    305 /* Try compressing the input file at once using mmap. Return Z_OK if
    306  * success, Z_ERRNO otherwise.
    307  */
    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 
    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  */
    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  */
    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  */
    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 
    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