1 /* $NetBSD: gzip.c,v 1.21 2004/01/29 13:18:58 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 1997, 1998, 2003 Matthew R. Green 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 #ifndef lint 33 __COPYRIGHT("@(#) Copyright (c) 1997, 1998, 2003 Matthew R. Green\n\ 34 All rights reserved.\n"); 35 __RCSID("$NetBSD: gzip.c,v 1.21 2004/01/29 13:18:58 mrg Exp $"); 36 #endif /* not lint */ 37 38 /* 39 * gzip.c -- GPL free gzip using zlib. 40 * 41 * very minor portions of this code are (very loosely) derived from 42 * the minigzip.c in the zlib distribution. 43 * 44 * TODO: 45 * - handle .taz/.tgz files? 46 * - use mmap where possible 47 * - handle some signals better (remove outfile?) 48 */ 49 50 #include <sys/param.h> 51 #include <sys/stat.h> 52 #include <sys/time.h> 53 54 #include <unistd.h> 55 #include <stdio.h> 56 #include <string.h> 57 #include <stdlib.h> 58 #include <err.h> 59 #include <errno.h> 60 #include <fcntl.h> 61 #include <zlib.h> 62 #include <fts.h> 63 #include <libgen.h> 64 #include <stdarg.h> 65 #include <getopt.h> 66 67 /* what type of file are we dealing with */ 68 enum filetype { 69 FT_GZIP, 70 FT_BZIP2, 71 FT_LAST, 72 FT_UNKNOWN 73 }; 74 75 #define BZ_NO_STDIO 76 #include <bzlib.h> 77 78 #define BZ2_SUFFIX ".bz2" 79 #define BZIP2_MAGIC "\102\132\150" 80 81 #define GZ_SUFFIX ".gz" 82 83 #define BUFLEN (32 * 1024) 84 85 #define GZIP_MAGIC0 0x1F 86 #define GZIP_MAGIC1 0x8B 87 #define GZIP_OMAGIC1 0x9E 88 89 #define ORIG_NAME 0x08 90 91 /* Define this if you have the NetBSD gzopenfull(3) extension to zlib(3) */ 92 #ifndef HAVE_ZLIB_GZOPENFULL 93 #define HAVE_ZLIB_GZOPENFULL 1 94 #endif 95 96 static const char gzip_version[] = "NetBSD gzip 2.1"; 97 98 static char gzipflags[3]; /* `w' or `r', possible with [1-9] */ 99 static int cflag; /* stdout mode */ 100 static int dflag; /* decompress mode */ 101 static int fflag; /* force mode */ 102 static int lflag; /* list mode */ 103 static int nflag; /* don't save name/timestamp */ 104 static int Nflag; /* don't restore name/timestamp */ 105 static int qflag; /* quiet mode */ 106 static int rflag; /* recursive mode */ 107 static int tflag; /* test */ 108 static int vflag; /* verbose mode */ 109 static char *Sflag; 110 static char *suffix; 111 112 #define suffix_len (strlen(suffix) + 1) /* len + nul */ 113 static char *newfile; /* name of newly created file */ 114 static char *infile; /* name of file coming in */ 115 116 static void maybe_err(int rv, const char *fmt, ...); 117 static void maybe_errx(int rv, const char *fmt, ...); 118 static void maybe_warn(const char *fmt, ...); 119 static void maybe_warnx(const char *fmt, ...); 120 static void prepend_gzip(char *, int *, char ***); 121 static void gz_compress(FILE *, gzFile); 122 static off_t gz_uncompress(gzFile, FILE *); 123 static void copymodes(const char *, struct stat *); 124 static ssize_t file_compress(char *); 125 static ssize_t file_uncompress(char *); 126 static void handle_pathname(char *); 127 static void handle_file(char *, struct stat *); 128 static void handle_dir(char *, struct stat *); 129 static void handle_stdin(void); 130 static void handle_stdout(void); 131 static void print_ratio(off_t, off_t, FILE *); 132 static void print_verbage(char *, char *, ssize_t, ssize_t); 133 static void print_test(char *, int); 134 static void print_list(int fd, off_t, const char *, time_t); 135 static void usage(void); 136 static void display_version(void); 137 static off_t unbzip2(int, int); 138 139 int main(int, char *p[]); 140 141 #ifdef SMALL 142 #define getopt_long(a,b,c,d,e) getopt(a,b,c) 143 #else 144 static const struct option longopts[] = { 145 { "stdout", no_argument, 0, 'c' }, 146 { "to-stdout", no_argument, 0, 'c' }, 147 { "decompress", no_argument, 0, 'd' }, 148 { "uncompress", no_argument, 0, 'd' }, 149 { "force", no_argument, 0, 'f' }, 150 { "help", no_argument, 0, 'h' }, 151 { "list", no_argument, 0, 'l' }, 152 { "no-name", no_argument, 0, 'n' }, 153 { "name", no_argument, 0, 'N' }, 154 { "quiet", no_argument, 0, 'q' }, 155 { "recursive", no_argument, 0, 'r' }, 156 { "suffix", required_argument, 0, 'S' }, 157 { "test", no_argument, 0, 't' }, 158 { "verbose", no_argument, 0, 'v' }, 159 { "version", no_argument, 0, 'V' }, 160 { "fast", no_argument, 0, '1' }, 161 { "best", no_argument, 0, '9' }, 162 #if 0 163 /* 164 * This is what else GNU gzip implements. --ascii isn't useful 165 * on NetBSD, and I don't care to have a --license. 166 */ 167 { "ascii", no_argument, 0, 'a' }, 168 { "license", no_argument, 0, 'L' }, 169 #endif 170 { NULL, no_argument, 0, 0 }, 171 }; 172 #endif 173 174 int 175 main(int argc, char **argv) 176 { 177 const char *progname = getprogname(); 178 char *gzip; 179 int ch; 180 181 /* XXX set up signals */ 182 183 gzipflags[0] = 'w'; 184 gzipflags[1] = '\0'; 185 186 suffix = GZ_SUFFIX;; 187 188 if ((gzip = getenv("GZIP")) != NULL) 189 prepend_gzip(gzip, &argc, &argv); 190 191 /* 192 * XXX 193 * handle being called `gunzip', `zcat' and `gzcat' 194 */ 195 if (strcmp(progname, "gunzip") == 0) 196 dflag = 1; 197 else if (strcmp(progname, "zcat") == 0 || 198 strcmp(progname, "gzcat") == 0) 199 dflag = cflag = 1; 200 201 while ((ch = getopt_long(argc, argv, "cdfhHlnNqrS:tvV123456789", 202 longopts, NULL)) != -1) 203 switch (ch) { 204 case 'c': 205 cflag = 1; 206 break; 207 case 'd': 208 dflag = 1; 209 break; 210 case 'f': 211 fflag = 1; 212 break; 213 case 'l': 214 lflag = 1; 215 tflag = 1; 216 dflag = 1; 217 break; 218 case 'n': 219 nflag = 1; 220 Nflag = 0; 221 break; 222 case 'N': 223 nflag = 0; 224 Nflag = 1; 225 break; 226 case 'q': 227 qflag = 1; 228 break; 229 case 'r': 230 rflag = 1; 231 break; 232 case 'S': 233 Sflag = optarg; 234 break; 235 case 't': 236 cflag = 1; 237 tflag = 1; 238 dflag = 1; 239 break; 240 case 'v': 241 vflag = 1; 242 break; 243 case 'V': 244 display_version(); 245 /* NOTREACHED */ 246 case '1': case '2': case '3': 247 case '4': case '5': case '6': 248 case '7': case '8': case '9': 249 gzipflags[1] = (char)ch; 250 gzipflags[2] = '\0'; 251 break; 252 default: 253 usage(); 254 /* NOTREACHED */ 255 } 256 argv += optind; 257 argc -= optind; 258 if (dflag) 259 gzipflags[0] = 'r'; 260 261 if (argc == 0) { 262 if (dflag) /* stdin mode */ 263 handle_stdin(); 264 else /* stdout mode */ 265 handle_stdout(); 266 } else { 267 do { 268 handle_pathname(argv[0]); 269 } while (*++argv); 270 } 271 if (qflag == 0 && lflag && argc > 1) 272 print_list(-1, 0, "(totals)", 0); 273 exit(0); 274 } 275 276 /* maybe print a warning */ 277 void 278 maybe_warn(const char *fmt, ...) 279 { 280 va_list ap; 281 282 if (qflag == 0) { 283 va_start(ap, fmt); 284 vwarn(fmt, ap); 285 va_end(ap); 286 } 287 } 288 289 void 290 maybe_warnx(const char *fmt, ...) 291 { 292 va_list ap; 293 294 if (qflag == 0) { 295 va_start(ap, fmt); 296 vwarnx(fmt, ap); 297 va_end(ap); 298 } 299 } 300 301 /* maybe print a warning */ 302 void 303 maybe_err(int rv, const char *fmt, ...) 304 { 305 va_list ap; 306 307 if (qflag == 0) { 308 va_start(ap, fmt); 309 vwarn(fmt, ap); 310 va_end(ap); 311 } 312 exit(rv); 313 } 314 315 /* maybe print a warning */ 316 void 317 maybe_errx(int rv, const char *fmt, ...) 318 { 319 va_list ap; 320 321 if (qflag == 0) { 322 va_start(ap, fmt); 323 vwarnx(fmt, ap); 324 va_end(ap); 325 } 326 exit(rv); 327 } 328 329 /* split up $GZIP and prepend it to the argument list */ 330 static void 331 prepend_gzip(char *gzip, int *argc, char ***argv) 332 { 333 char *s, **nargv, **ac; 334 int nenvarg = 0, i; 335 336 /* scan how many arguments there are */ 337 for (s = gzip; *s; s++) { 338 if (*s == ' ' || *s == '\t') 339 continue; 340 nenvarg++; 341 for (; *s; s++) 342 if (*s == ' ' || *s == '\t') 343 break; 344 if (*s == 0) 345 break; 346 } 347 /* punt early */ 348 if (nenvarg == 0) 349 return; 350 351 *argc += nenvarg; 352 ac = *argv; 353 354 nargv = (char **)malloc((*argc + 1) * sizeof(char *)); 355 if (nargv == NULL) 356 maybe_err(1, "malloc"); 357 358 /* stash this away */ 359 *argv = nargv; 360 361 /* copy the program name first */ 362 i = 0; 363 nargv[i++] = *(ac++); 364 365 /* take a copy of $GZIP and add it to the array */ 366 s = strdup(gzip); 367 if (s == NULL) 368 maybe_err(1, "strdup"); 369 for (; *s; s++) { 370 if (*s == ' ' || *s == '\t') 371 continue; 372 nargv[i++] = s; 373 for (; *s; s++) 374 if (*s == ' ' || *s == '\t') { 375 *s = 0; 376 break; 377 } 378 } 379 380 /* copy the original arguments and a NULL */ 381 while (*ac) 382 nargv[i++] = *(ac++); 383 nargv[i] = NULL; 384 } 385 386 /* compress input to output then close both files */ 387 static void 388 gz_compress(FILE *in, gzFile out) 389 { 390 char buf[BUFLEN]; 391 ssize_t len; 392 int i; 393 394 for (;;) { 395 len = fread(buf, 1, sizeof(buf), in); 396 if (ferror(in)) 397 maybe_err(1, "fread"); 398 if (len == 0) 399 break; 400 401 if ((ssize_t)gzwrite(out, buf, len) != len) 402 maybe_err(1, gzerror(out, &i)); 403 } 404 if (fclose(in) < 0) 405 maybe_err(1, "failed fclose"); 406 if (gzclose(out) != Z_OK) 407 maybe_err(1, "failed gzclose"); 408 } 409 410 /* uncompress input to output then close the input */ 411 static off_t 412 gz_uncompress(gzFile in, FILE *out) 413 { 414 char buf[BUFLEN]; 415 off_t size; 416 ssize_t len; 417 int i; 418 419 for (size = 0;;) { 420 len = gzread(in, buf, sizeof(buf)); 421 422 if (len < 0) { 423 if (tflag) { 424 print_test(infile, 0); 425 return (0); 426 } else 427 maybe_errx(1, gzerror(in, &i)); 428 } else if (len == 0) { 429 if (tflag) 430 print_test(infile, 1); 431 break; 432 } 433 434 size += len; 435 436 /* don't write anything with -t */ 437 if (tflag) 438 continue; 439 440 if (fwrite(buf, 1, (unsigned)len, out) != (ssize_t)len) 441 maybe_err(1, "failed fwrite"); 442 } 443 if (gzclose(in) != Z_OK) 444 maybe_errx(1, "failed gzclose"); 445 446 return (size); 447 } 448 449 /* 450 * set the owner, mode, flags & utimes for a file 451 */ 452 static void 453 copymodes(const char *file, struct stat *sbp) 454 { 455 struct timeval times[2]; 456 457 /* 458 * If we have no info on the input, give this file some 459 * default values and return.. 460 */ 461 if (sbp == NULL) { 462 mode_t mask = umask(022); 463 464 (void)chmod(file, DEFFILEMODE & ~mask); 465 (void)umask(mask); 466 return; 467 } 468 469 /* if the chown fails, remove set-id bits as-per compress(1) */ 470 if (chown(file, sbp->st_uid, sbp->st_gid) < 0) { 471 if (errno != EPERM) 472 maybe_warn("couldn't chown: %s", file); 473 sbp->st_mode &= ~(S_ISUID|S_ISGID); 474 } 475 476 /* we only allow set-id and the 9 normal permission bits */ 477 sbp->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; 478 if (chmod(file, sbp->st_mode) < 0) 479 maybe_warn("couldn't chmod: %s", file); 480 481 /* only try flags if they exist already */ 482 if (sbp->st_flags != 0 && chflags(file, sbp->st_flags) < 0) 483 maybe_warn("couldn't chflags: %s", file); 484 485 TIMESPEC_TO_TIMEVAL(×[0], &sbp->st_atimespec); 486 TIMESPEC_TO_TIMEVAL(×[1], &sbp->st_mtimespec); 487 if (utimes(file, times) < 0) 488 maybe_warn("couldn't utimes: %s", file); 489 } 490 491 /* 492 * compress the given file: create a corresponding .gz file and remove the 493 * original. 494 */ 495 static ssize_t 496 file_compress(char *file) 497 { 498 FILE *in; 499 gzFile out; 500 struct stat isb, osb; 501 char outfile[MAXPATHLEN]; 502 ssize_t size; 503 u_int32_t mtime = 0; 504 505 if (cflag == 0) { 506 (void)strncpy(outfile, file, MAXPATHLEN - suffix_len); 507 outfile[MAXPATHLEN - suffix_len] = '\0'; 508 (void)strlcat(outfile, suffix, sizeof(outfile)); 509 510 if (fflag == 0) { 511 if (stat(outfile, &osb) == 0) { 512 maybe_warnx("%s already exists -- skipping", 513 outfile); 514 goto lose; 515 } 516 } 517 if (stat(file, &isb) == 0) { 518 if (isb.st_nlink > 1) { 519 maybe_warnx("%s has %d other link%s -- " 520 "skipping", file, isb.st_nlink-1, 521 isb.st_nlink == 1 ? "" : "s"); 522 goto lose; 523 } 524 if (nflag == 0) 525 mtime = (u_int32_t)isb.st_mtime; 526 } 527 } 528 in = fopen(file, "r"); 529 if (in == 0) 530 maybe_err(1, "can't fopen %s", file); 531 532 if (cflag == 0) { 533 #if HAVE_ZLIB_GZOPENFULL 534 char *savename; 535 536 if (nflag == 0) 537 savename = basename(file); 538 else 539 savename = NULL; 540 out = gzopenfull(outfile, gzipflags, savename, mtime); 541 #else 542 out = gzopen(outfile, gzipflags); 543 #endif 544 } else 545 out = gzdopen(STDOUT_FILENO, gzipflags); 546 547 if (out == 0) 548 maybe_err(1, "can't gz%sopen %s", 549 cflag ? "d" : "", 550 cflag ? "stdout" : outfile); 551 552 gz_compress(in, out); 553 554 /* 555 * if we compressed to stdout, we don't know the size and 556 * we don't know the new file name, punt. if we can't stat 557 * the file, whine, otherwise set the size from the stat 558 * buffer. we only blow away the file if we can stat the 559 * output, just in case. 560 */ 561 if (cflag == 0) { 562 if (stat(outfile, &osb) < 0) { 563 maybe_warn("couldn't stat: %s", outfile); 564 maybe_warnx("leaving original %s", file); 565 size = 0; 566 } else { 567 unlink(file); 568 size = osb.st_size; 569 } 570 newfile = outfile; 571 copymodes(outfile, &isb); 572 } else { 573 lose: 574 size = 0; 575 newfile = 0; 576 } 577 578 return (size); 579 } 580 581 /* uncompress the given file and remove the original */ 582 static ssize_t 583 file_uncompress(char *file) 584 { 585 struct stat isb, osb; 586 char buf[PATH_MAX]; 587 char *outfile = buf, *s; 588 FILE *out; 589 gzFile in; 590 off_t size; 591 ssize_t len = strlen(file); 592 int fd; 593 unsigned char header1[10], name[PATH_MAX + 1]; 594 enum filetype method; 595 596 /* gather the old name info */ 597 598 fd = open(file, O_RDONLY); 599 if (fd < 0) 600 maybe_err(1, "can't open %s", file); 601 if (read(fd, header1, 10) != 10) { 602 /* we don't want to fail here. */ 603 if (fflag) 604 goto close_it; 605 maybe_err(1, "can't read %s", file); 606 } 607 608 if (header1[0] == GZIP_MAGIC0 && 609 (header1[1] == GZIP_MAGIC1 || header1[1] == GZIP_OMAGIC1)) 610 method = FT_GZIP; 611 else if (memcmp(header1, BZIP2_MAGIC, 3) == 0 && 612 header1[3] >= '0' && header1[3] <= '9') { 613 if (Sflag == NULL) 614 suffix = BZ2_SUFFIX; 615 method = FT_BZIP2; 616 } else 617 method = FT_UNKNOWN; 618 619 if (fflag == 0 && method == FT_UNKNOWN) 620 maybe_errx(1, "%s: not in gzip format", file); 621 622 if (cflag == 0 || lflag) { 623 s = &file[len - suffix_len + 1]; 624 if (strncmp(s, suffix, suffix_len) == 0) { 625 (void)strncpy(outfile, file, len - suffix_len + 1); 626 outfile[len - suffix_len + 1] = '\0'; 627 } else if (lflag == 0) 628 maybe_errx(1, "unknown suffix %s", s); 629 } 630 631 if (method == FT_GZIP && (Nflag || lflag)) { 632 if (header1[3] & ORIG_NAME) { 633 size_t rbytes; 634 int i; 635 636 rbytes = read(fd, name, PATH_MAX + 1); 637 if (rbytes < 0) 638 maybe_err(1, "can't read %s", file); 639 for (i = 0; i < rbytes && name[i]; i++) 640 ; 641 if (i < rbytes) { 642 name[i] = 0; 643 /* now maybe merge old dirname */ 644 if (strchr(outfile, '/') == 0) 645 outfile = name; 646 else { 647 char *dir = dirname(outfile); 648 if (asprintf(&outfile, "%s/%s", dir, 649 name) == -1) 650 maybe_err(1, "malloc"); 651 } 652 } 653 } 654 } 655 close_it: 656 close(fd); 657 658 if ((cflag == 0 || lflag) && fflag == 0) { 659 if (lflag == 0 && stat(outfile, &osb) == 0) { 660 maybe_warnx("%s already exists -- skipping", outfile); 661 goto lose; 662 } 663 if (stat(file, &isb) == 0) { 664 if (isb.st_nlink > 1 && lflag == 0) { 665 maybe_warnx("%s has %d other links -- skipping", 666 file, isb.st_nlink - 1); 667 goto lose; 668 } 669 } else 670 goto lose; 671 } 672 673 if (method == FT_BZIP2) { 674 int in, out; 675 676 if ((in = open(file, O_RDONLY)) == -1) 677 maybe_err(1, "open for read: %s", file); 678 if (cflag == 1) 679 out = STDOUT_FILENO; 680 else 681 out = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600); 682 if (out == -1) 683 maybe_err(1, "open for write: %s", outfile); 684 685 if ((size = unbzip2(in, out)) == 0) { 686 unlink(outfile); 687 goto lose; 688 } 689 } else { 690 if (lflag) { 691 int fd; 692 693 if ((fd = open(file, O_RDONLY)) == -1) 694 maybe_err(1, "open"); 695 print_list(fd, isb.st_size, outfile, isb.st_mtime); 696 return 0; /* XXX */ 697 } 698 699 in = gzopen(file, gzipflags); 700 if (in == NULL) 701 maybe_err(1, "can't gzopen %s", file); 702 703 if (cflag == 0) { 704 int fd; 705 706 /* Use open(2) directly to get a safe file. */ 707 fd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600); 708 if (fd < 0) 709 maybe_err(1, "can't open %s", outfile); 710 out = fdopen(fd, "w"); 711 if (out == NULL) 712 maybe_err(1, "can't fdopen %s", outfile); 713 } else 714 out = stdout; 715 716 if ((size = gz_uncompress(in, out)) == 0) { 717 unlink(outfile); 718 goto lose; 719 } 720 721 /* close the file */ 722 if (fclose(out)) 723 maybe_err(1, "failed fclose"); 724 } 725 726 /* if testing, or we uncompressed to stdout, this is all we need */ 727 if (tflag || cflag) 728 return (size); 729 730 /* 731 * if we create a file... 732 */ 733 if (cflag == 0) { 734 /* 735 * if we can't stat the file, or we are uncompressing to 736 * stdin, don't remove the file. 737 */ 738 if (stat(outfile, &osb) < 0) { 739 maybe_warn("couldn't stat (leaving original): %s", 740 outfile); 741 goto lose; 742 } 743 if (osb.st_size != size) { 744 maybe_warn("stat gave different size: %llu != %llu " 745 "(leaving original)", 746 (unsigned long long)size, 747 (unsigned long long)osb.st_size); 748 goto lose; 749 } 750 newfile = outfile; 751 unlink(file); 752 size = osb.st_size; 753 copymodes(outfile, &isb); 754 } 755 return (size); 756 757 lose: 758 newfile = 0; 759 return (0); 760 } 761 762 static void 763 handle_stdin(void) 764 { 765 gzFile *file; 766 767 if (fflag == 0 && lflag == 0 && isatty(STDIN_FILENO)) { 768 maybe_warnx("standard input is a terminal -- ignoring"); 769 return; 770 } 771 772 if (lflag) { 773 struct stat isb; 774 775 if (fstat(STDIN_FILENO, &isb) < 0) 776 maybe_err(1, "fstat"); 777 print_list(STDIN_FILENO, isb.st_size, "stdout", isb.st_mtime); 778 return; 779 } 780 781 file = gzdopen(STDIN_FILENO, gzipflags); 782 if (file == NULL) 783 maybe_err(1, "can't gzdopen stdin"); 784 gz_uncompress(file, stdout); 785 } 786 787 static void 788 handle_stdout(void) 789 { 790 gzFile *file; 791 792 if (fflag == 0 && isatty(STDOUT_FILENO)) { 793 maybe_warnx("standard output is a terminal -- ignoring"); 794 return; 795 } 796 file = gzdopen(STDOUT_FILENO, gzipflags); 797 if (file == NULL) 798 maybe_err(1, "can't gzdopen stdout"); 799 gz_compress(stdin, file); 800 } 801 802 /* do what is asked for, for the path name */ 803 static void 804 handle_pathname(char *path) 805 { 806 char *opath = path, *s = 0; 807 ssize_t len; 808 struct stat sb; 809 810 /* check for stdout/stdin */ 811 if (path[0] == '-' && path[1] == '\0') { 812 if (dflag) 813 handle_stdin(); 814 else 815 handle_stdout(); 816 } 817 818 retry: 819 if (stat(path, &sb) < 0) { 820 /* lets try <path>.gz if we're decompressing */ 821 if (dflag && s == 0 && errno == ENOENT) { 822 len = strlen(path); 823 s = malloc(len + suffix_len); 824 if (s == 0) 825 maybe_err(1, "malloc"); 826 memmove(s, path, len); 827 memmove(&s[len], suffix, suffix_len); 828 path = s; 829 goto retry; 830 } 831 maybe_warn("can't stat: %s", opath); 832 goto out; 833 } 834 835 if (S_ISDIR(sb.st_mode)) { 836 if (rflag) 837 handle_dir(path, &sb); 838 else 839 maybe_warn("%s is a directory", path); 840 goto out; 841 } 842 843 if (S_ISREG(sb.st_mode)) 844 handle_file(path, &sb); 845 846 out: 847 if (s) 848 free(s); 849 } 850 851 /* compress/decompress a file */ 852 static void 853 handle_file(char *file, struct stat *sbp) 854 { 855 ssize_t usize, gsize; 856 857 infile = file; 858 if (dflag) { 859 usize = file_uncompress(file); 860 if (usize == 0) 861 return; 862 gsize = sbp->st_size; 863 } else { 864 gsize = file_compress(file); 865 if (gsize == 0) 866 return; 867 usize = sbp->st_size; 868 } 869 870 if (vflag && !tflag) 871 print_verbage(file, cflag == 0 ? newfile : 0, usize, gsize); 872 } 873 874 /* this is used with -r to recursively decend directories */ 875 static void 876 handle_dir(char *dir, struct stat *sbp) 877 { 878 char *path_argv[2]; 879 FTS *fts; 880 FTSENT *entry; 881 882 path_argv[0] = dir; 883 path_argv[1] = 0; 884 fts = fts_open(path_argv, FTS_PHYSICAL, NULL); 885 if (fts == NULL) { 886 warn("couldn't fts_open %s", dir); 887 return; 888 } 889 890 while ((entry = fts_read(fts))) { 891 switch(entry->fts_info) { 892 case FTS_D: 893 case FTS_DP: 894 continue; 895 896 case FTS_DNR: 897 case FTS_ERR: 898 case FTS_NS: 899 maybe_warn("%s", entry->fts_path); 900 continue; 901 case FTS_F: 902 handle_file(entry->fts_name, entry->fts_statp); 903 } 904 } 905 (void)fts_close(fts); 906 } 907 908 /* print a ratio */ 909 static void 910 print_ratio(off_t in, off_t out, FILE *where) 911 { 912 u_int64_t percent; 913 914 if (out == 0) 915 percent = 0; 916 else 917 percent = 1000 - ((1000 * out) / in); 918 fprintf(where, "%3lu.%1lu%%", (unsigned long)percent / 10UL, 919 (unsigned long)percent % 10); 920 } 921 922 /* print compression statistics, and the new name (if there is one!) */ 923 static void 924 print_verbage(char *file, char *nfile, ssize_t usize, ssize_t gsize) 925 { 926 fprintf(stderr, "%s:%s ", file, 927 strlen(file) < 7 ? "\t\t" : "\t"); 928 print_ratio(usize, gsize, stderr); 929 if (nfile) 930 fprintf(stderr, " -- replaced with %s", nfile); 931 fprintf(stderr, "\n"); 932 fflush(stderr); 933 } 934 935 /* print test results */ 936 static void 937 print_test(char *file, int ok) 938 { 939 940 fprintf(stderr, "%s:%s %s\n", file, 941 strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK"); 942 fflush(stderr); 943 } 944 945 /* print a file's info ala --list */ 946 /* eg: 947 compressed uncompressed ratio uncompressed_name 948 354841 1679360 78.8% /usr/pkgsrc/distfiles/libglade-2.0.1.tar 949 */ 950 static void 951 print_list(int fd, off_t in, const char *outfile, time_t ts) 952 { 953 static int first = 1; 954 static off_t in_tot, out_tot; 955 off_t out; 956 u_int32_t crc; 957 int rv; 958 959 if (first) { 960 if (vflag) 961 printf("method crc date time "); 962 if (qflag == 0) 963 printf(" compressed uncompressed " 964 "ratio uncompressed_name\n"); 965 } 966 first = 0; 967 968 /* print totals? */ 969 if (fd == -1) { 970 in = in_tot; 971 out = out_tot; 972 } else { 973 /* read the last 4 bytes - this is the uncompressed size */ 974 rv = lseek(fd, (off_t)(-8), SEEK_END); 975 if (rv != -1) { 976 unsigned char buf[8]; 977 u_int32_t usize; 978 979 if (read(fd, (char *)buf, sizeof(buf)) != sizeof(buf)) 980 maybe_err(1, "read of uncompressed size"); 981 crc = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; 982 usize = buf[4] | buf[5] << 8 | buf[6] << 16 | buf[7] << 24; 983 out = (off_t)usize; 984 } 985 } 986 987 if (vflag && fd == -1) 988 printf(" "); 989 else if (vflag) { 990 char *date = ctime(&ts); 991 992 /* skip the day, 1/100th second, and year */ 993 date += 4; 994 date[12] = 0; 995 printf("%5s %08x %11s ", "defla"/*XXX*/, crc, date); 996 } 997 printf("%12llu %12llu ", (unsigned long long)in, (unsigned long long)out); 998 print_ratio(in, out, stdout); 999 printf(" %s\n", outfile); 1000 in_tot += in; 1001 out_tot += out; 1002 } 1003 1004 /* display the usage of NetBSD gzip */ 1005 static void 1006 usage(void) 1007 { 1008 1009 fprintf(stderr, "%s\n", gzip_version); 1010 fprintf(stderr, 1011 "usage: %s [-cdfhnNqrStvV123456789] [<file> [<file> ...]]\n" 1012 #ifndef SMALL 1013 " -c --stdout write to stdout, keep original files\n" 1014 " --to-stdout\n" 1015 " -d --decompress uncompress files\n" 1016 " --uncompress\n" 1017 " -f --force force overwriting & compress links\n" 1018 " -h --help display this help\n" 1019 " -n --no-name don't save original file name or time stamp\n" 1020 " -N --name save or restore original file name and time stamp\n" 1021 " -q --quiet output no warnings\n" 1022 " -r --recursive recursively compress files in directories\n" 1023 " -S .suf use suffix .suf instead of .gz\n" 1024 " --suffix .suf\n" 1025 " -t --test test compressed file\n" 1026 " -v --verbose print extra statistics\n" 1027 " -V --version display program version\n" 1028 " -1 --fast fastest (worst) compression\n" 1029 " -2 .. -8 set compression level\n" 1030 " -9 --best best (slowest) compression\n", 1031 #else 1032 , 1033 #endif 1034 getprogname()); 1035 fflush(stderr); 1036 exit(0); 1037 } 1038 1039 /* display the version of NetBSD gzip */ 1040 static void 1041 display_version(void) 1042 { 1043 1044 fprintf(stderr, "%s\n", gzip_version); 1045 fflush(stderr); 1046 exit(0); 1047 } 1048 1049 #include "unbzip2.c" 1050