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@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