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 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  
      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  void *myalloc OF((void *, unsigned, unsigned));
     153  void myfree OF((void *, void *));
     154  
     155  void *myalloc(q, n, m)
     156      void *q;
     157      unsigned n, m;
     158  {
     159      (void)q;
     160      return calloc(n, m);
     161  }
     162  
     163  void myfree(q, p)
     164      void *q, *p;
     165  {
     166      (void)q;
     167      free(p);
     168  }
     169  
     170  typedef struct gzFile_s {
     171      FILE *file;
     172      int write;
     173      int err;
     174      char *msg;
     175      z_stream strm;
     176  } *gzFile;
     177  
     178  gzFile gzopen OF((const char *, const char *));
     179  gzFile gzdopen OF((int, const char *));
     180  gzFile gz_open OF((const char *, int, const char *));
     181  
     182  gzFile gzopen(path, mode)
     183  const char *path;
     184  const char *mode;
     185  {
     186      return gz_open(path, -1, mode);
     187  }
     188  
     189  gzFile gzdopen(fd, mode)
     190  int fd;
     191  const char *mode;
     192  {
     193      return gz_open(NULL, fd, mode);
     194  }
     195  
     196  gzFile 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  
     234  int gzwrite OF((gzFile, const void *, unsigned));
     235  
     236  int 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  
     258  int gzread OF((gzFile, void *, unsigned));
     259  
     260  int 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  
     295  int gzclose OF((gzFile));
     296  
     297  int 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  
     324  const char *gzerror OF((gzFile, int *));
     325  
     326  const char *gzerror(gz, err)
     327      gzFile gz;
     328      int *err;
     329  {
     330      *err = gz->err;
     331      return gz->msg;
     332  }
     333  
     334  #endif
     335  
     336  static char *prog;
     337  
     338  void error            OF((const char *msg));
     339  void gz_compress      OF((FILE   *in, gzFile out));
     340  #ifdef USE_MMAP
     341  int  gz_compress_mmap OF((FILE   *in, gzFile out));
     342  #endif
     343  void gz_uncompress    OF((gzFile in, FILE   *out));
     344  void file_compress    OF((char  *file, char *mode));
     345  void file_uncompress  OF((char  *file));
     346  int  main             OF((int argc, char *argv[]));
     347  
     348  /* ===========================================================================
     349   * Display error message and exit
     350   */
     351  void 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  
     362  void 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   */
     395  int 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   */
     430  void 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   */
     457  void 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   */
     496  void 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      unsigned 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  
     556  int 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  }