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