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 
  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     q = Z_NULL;
 160     return calloc(n, m);
 161 }
 162 
 163 void myfree(q, p)
 164     void *q, *p;
 165 {
 166     q = Z_NULL;
 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 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     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 
 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 }