1 /* $OpenBSD: main.c,v 1.68 2007/01/18 20:53:36 millert Exp $ */ 2 3 #ifndef SMALL 4 static const char copyright[] = 5 "@(#) Copyright (c) 1992, 1993\n\ 6 The Regents of the University of California. All rights reserved.\n" 7 "Copyright (c) 1997-2002 Michael Shalayeff\n"; 8 #endif 9 10 #ifndef SMALL 11 static const char license[] = 12 "\n" 13 " Redistribution and use in source and binary forms, with or without\n" 14 " modification, are permitted provided that the following conditions\n" 15 " are met:\n" 16 " 1. Redistributions of source code must retain the above copyright\n" 17 " notice, this list of conditions and the following disclaimer.\n" 18 " 2. Redistributions in binary form must reproduce the above copyright\n" 19 " notice, this list of conditions and the following disclaimer in the\n" 20 " documentation and/or other materials provided with the distribution.\n" 21 " 3. Neither the name of the University nor the names of its contributors\n" 22 " may be used to endorse or promote products derived from this software\n" 23 " without specific prior written permission.\n" 24 "\n" 25 " THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" 26 " IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n" 27 " OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" 28 " IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT,\n" 29 " INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n" 30 " (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n" 31 " SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" 32 " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" 33 " STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" 34 " IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n" 35 " THE POSSIBILITY OF SUCH DAMAGE.\n"; 36 #endif /* SMALL */ 37 38 #ifndef SMALL 39 static const char main_rcsid[] = "$OpenBSD: main.c,v 1.68 2007/01/18 20:53:36 millert Exp $"; 40 #endif 41 42 #include <sys/param.h> 43 #include <sys/time.h> 44 #include <sys/stat.h> 45 46 #include <getopt.h> 47 #include <err.h> 48 #include <errno.h> 49 #include <fts.h> 50 #include <libgen.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <unistd.h> 55 #include <fcntl.h> 56 #include <paths.h> 57 #include "compress.h" 58 59 #define min(a,b) ((a) < (b)? (a) : (b)) 60 61 int pipin, force, verbose, testmode, list, nosave; 62 int savename, recurse; 63 int cat, decomp; 64 extern char *__progname; 65 66 const struct compressor { 67 char *name; 68 char *suffix; 69 u_char *magic; 70 void *(*open)(int, const char *, char *, int, u_int32_t, int); 71 int (*read)(void *, char *, int); 72 int (*write)(void *, const char *, int); 73 int (*close)(void *, struct z_info *, const char *, struct stat *); 74 } c_table[] = { 75 #define M_DEFLATE (&c_table[0]) 76 { "deflate", ".gz", "\037\213", gz_open, gz_read, gz_write, gz_close }, 77 #define M_COMPRESS (&c_table[1]) 78 #ifndef SMALL 79 { "compress", ".Z", "\037\235", z_open, zread, zwrite, z_close }, 80 #endif /* SMALL */ 81 #if 0 82 #define M_LZH (&c_table[2]) 83 { "lzh", ".lzh", "\037\240", lzh_open, lzh_read, lzh_write, lzh_close }, 84 #define M_ZIP (&c_table[3]) 85 { "zip", ".zip", "PK", zip_open, zip_read, zip_write, zip_close }, 86 #define M_PACK (&c_table[4]) 87 { "pack", ".pak", "\037\036", pak_open, pak_read, pak_write, pak_close }, 88 #endif 89 { NULL } 90 }; 91 92 #ifndef SMALL 93 const struct compressor null_method = 94 { "null", ".nul", "XX", null_open, null_read, null_write, null_close }; 95 #endif /* SMALL */ 96 97 int permission(const char *); 98 __dead void usage(int); 99 int docompress(const char *, char *, const struct compressor *, 100 int, struct stat *); 101 int dodecompress(const char *, char *, const struct compressor *, 102 int, struct stat *); 103 const struct compressor *check_method(int); 104 const char *check_suffix(const char *); 105 char *set_outfile(const char *, char *, size_t); 106 void list_stats(const char *, const struct compressor *, struct z_info *); 107 void verbose_info(const char *, off_t, off_t, u_int32_t); 108 109 #define OPTSTRING "123456789ab:cdfghlLnNOo:qrS:tvV" 110 const struct option longopts[] = { 111 #ifndef SMALL 112 { "ascii", no_argument, 0, 'a' }, 113 { "stdout", no_argument, 0, 'c' }, 114 { "to-stdout", no_argument, 0, 'c' }, 115 { "decompress", no_argument, 0, 'd' }, 116 { "uncompress", no_argument, 0, 'd' }, 117 { "force", no_argument, 0, 'f' }, 118 { "help", no_argument, 0, 'h' }, 119 { "list", no_argument, 0, 'l' }, 120 { "license", no_argument, 0, 'L' }, 121 { "no-name", no_argument, 0, 'n' }, 122 { "name", no_argument, 0, 'N' }, 123 { "quiet", no_argument, 0, 'q' }, 124 { "recursive", no_argument, 0, 'r' }, 125 { "suffix", required_argument, 0, 'S' }, 126 { "test", no_argument, 0, 't' }, 127 { "verbose", no_argument, 0, 'v' }, 128 { "version", no_argument, 0, 'V' }, 129 { "fast", no_argument, 0, '1' }, 130 { "best", no_argument, 0, '9' }, 131 #endif /* SMALL */ 132 { NULL } 133 }; 134 135 int 136 main(int argc, char *argv[]) 137 { 138 FTS *ftsp; 139 FTSENT *entry; 140 const struct compressor *method; 141 const char *s; 142 char *p, *infile; 143 char outfile[MAXPATHLEN], _infile[MAXPATHLEN], suffix[16]; 144 char *nargv[512]; /* some estimate based on ARG_MAX */ 145 int bits, ch, error, i, rc, cflag, oflag; 146 147 bits = cflag = oflag = 0; 148 nosave = -1; 149 p = __progname; 150 if (p[0] == 'g') { 151 method = M_DEFLATE; 152 bits = 6; 153 p++; 154 } else 155 #ifdef SMALL 156 method = M_DEFLATE; 157 #else 158 method = M_COMPRESS; 159 #endif /* SMALL */ 160 161 decomp = 0; 162 if (!strcmp(p, "zcat")) { 163 decomp++; 164 cflag = 1; 165 } else { 166 if (p[0] == 'u' && p[1] == 'n') { 167 p += 2; 168 decomp++; 169 } 170 171 if (strcmp(p, "zip") && 172 strcmp(p, "compress")) 173 errx(1, "unknown program name"); 174 } 175 176 strlcpy(suffix, method->suffix, sizeof(suffix)); 177 178 nargv[0] = NULL; 179 if (method == M_DEFLATE && (p = getenv("GZIP")) != NULL) { 180 char *last; 181 182 nargv[0] = *argv++; 183 for (i = 1, (p = strtok_r(p, " ", &last)); p != NULL; 184 (p = strtok_r(NULL, " ", &last)), i++) 185 if (i < sizeof(nargv)/sizeof(nargv[1]) - argc - 1) 186 nargv[i] = p; 187 else 188 errx(1, "GZIP is too long"); 189 argc += i - 1; 190 while ((nargv[i++] = *argv++)) 191 ; 192 argv = nargv; 193 } 194 195 while ((ch = getopt_long(argc, argv, OPTSTRING, longopts, NULL)) != -1) 196 switch(ch) { 197 case '1': 198 case '2': 199 case '3': 200 case '4': 201 case '5': 202 case '6': 203 case '7': 204 case '8': 205 case '9': 206 method = M_DEFLATE; 207 strlcpy(suffix, method->suffix, sizeof(suffix)); 208 bits = ch - '0'; 209 break; 210 case 'a': 211 warnx("option -a is ignored on this system"); 212 break; 213 case 'b': 214 bits = strtol(optarg, &p, 10); 215 /* 216 * POSIX 1002.3 says 9 <= bits <= 14 for portable 217 * apps, but says the implementation may allow 218 * greater. 219 */ 220 if (*p) 221 errx(1, "illegal bit count -- %s", optarg); 222 break; 223 case 'c': 224 cflag = 1; 225 break; 226 case 'd': /* Backward compatible. */ 227 decomp++; 228 break; 229 case 'f': 230 force++; 231 break; 232 case 'g': 233 method = M_DEFLATE; 234 strlcpy(suffix, method->suffix, sizeof(suffix)); 235 bits = 6; 236 break; 237 case 'l': 238 list++; 239 testmode++; 240 decomp++; 241 break; 242 case 'n': 243 nosave = 1; 244 break; 245 case 'N': 246 nosave = 0; 247 break; 248 #ifndef SMALL 249 case 'O': 250 method = M_COMPRESS; 251 strlcpy(suffix, method->suffix, sizeof(suffix)); 252 break; 253 #endif /* SMALL */ 254 case 'o': 255 if (strlcpy(outfile, optarg, 256 sizeof(outfile)) >= sizeof(outfile)) 257 errx(1, "-o argument is too long"); 258 oflag = 1; 259 break; 260 case 'q': 261 verbose = -1; 262 break; 263 case 'S': 264 p = suffix; 265 if (optarg[0] != '.') 266 *p++ = '.'; 267 strlcpy(p, optarg, sizeof(suffix) - (p - suffix)); 268 p = optarg; 269 break; 270 case 't': 271 testmode = 1; 272 decomp++; 273 break; 274 #ifndef SMALL 275 case 'V': 276 printf("%s\n%s\n", main_rcsid, gz_rcsid); 277 printf("%s\n%s\n", z_rcsid, null_rcsid); 278 #endif 279 exit (0); 280 case 'v': 281 verbose++; 282 break; 283 #ifndef SMALL 284 case 'L': 285 fputs(copyright, stderr); 286 fputs(license, stderr); 287 #endif 288 exit (0); 289 case 'r': 290 recurse++; 291 break; 292 293 case 'h': 294 usage(0); 295 break; 296 default: 297 usage(1); 298 } 299 argc -= optind; 300 argv += optind; 301 302 if (argc == 0) { 303 if (nargv[0] == NULL) 304 argv = nargv; 305 /* XXX - make sure we don't oflow nargv in $GZIP case (millert) */ 306 argv[0] = "-"; 307 argv[1] = NULL; 308 } 309 if (oflag && (recurse || argc > 1)) 310 errx(1, "-o option may only be used with a single input file"); 311 312 if ((cat && argc) + testmode + oflag > 1) 313 errx(1, "may not mix -o, -c, or -t options"); 314 if (nosave == -1) 315 nosave = decomp; 316 317 if ((ftsp = fts_open(argv, FTS_PHYSICAL|FTS_NOCHDIR, 0)) == NULL) 318 err(1, NULL); 319 for (rc = SUCCESS; (entry = fts_read(ftsp)) != NULL;) { 320 cat = cflag; 321 pipin = 0; 322 infile = entry->fts_path; 323 if (infile[0] == '-' && infile[1] == '\0') { 324 infile = "stdin"; 325 pipin++; 326 if (!oflag) 327 cat = 1; 328 } 329 else 330 switch (entry->fts_info) { 331 case FTS_D: 332 if (!recurse) { 333 warnx("%s is a directory: ignored", 334 infile); 335 fts_set(ftsp, entry, FTS_SKIP); 336 } 337 continue; 338 case FTS_DP: 339 continue; 340 case FTS_NS: 341 /* 342 * If file does not exist and has no suffix, 343 * tack on the default suffix and try that. 344 */ 345 if (entry->fts_errno == ENOENT) { 346 p = strrchr(entry->fts_accpath, '.'); 347 if ((p == NULL || 348 strcmp(p, suffix) != 0) && 349 snprintf(_infile, sizeof(_infile), 350 "%s%s", infile, suffix) < 351 sizeof(_infile) && 352 stat(_infile, entry->fts_statp) == 353 0 && 354 S_ISREG(entry->fts_statp->st_mode)) { 355 infile = _infile; 356 break; 357 } 358 } 359 case FTS_ERR: 360 case FTS_DNR: 361 warnx("%s: %s", infile, 362 strerror(entry->fts_errno)); 363 rc = rc ? rc : WARNING; 364 continue; 365 default: 366 if (!S_ISREG(entry->fts_statp->st_mode) && 367 !(S_ISLNK(entry->fts_statp->st_mode) && 368 cat)) { 369 warnx("%s not a regular file%s", 370 infile, cat ? "" : ": unchanged"); 371 rc = rc ? rc : WARNING; 372 continue; 373 } 374 break; 375 } 376 377 if (!decomp && !pipin && (s = check_suffix(infile)) != NULL) { 378 warnx("%s already has %s suffix -- unchanged", 379 infile, s); 380 rc = rc ? rc : WARNING; 381 continue; 382 } 383 384 if (!oflag) { 385 if (cat) 386 strlcpy(outfile, "stdout", sizeof(outfile)); 387 else if (decomp) { 388 if (set_outfile(infile, outfile, 389 sizeof outfile) == NULL) { 390 if (!recurse) { 391 warnx("%s: unknown suffix: " 392 "ignored", infile); 393 rc = rc ? rc : WARNING; 394 } 395 continue; 396 } 397 } else { 398 if (snprintf(outfile, sizeof(outfile), 399 "%s%s", infile, suffix) >= sizeof(outfile)) { 400 warnx("%s%s: name too long", 401 infile, suffix); 402 rc = rc ? rc : WARNING; 403 continue; 404 } 405 } 406 } 407 408 if (verbose > 0 && !pipin && !list) 409 fprintf(stderr, "%s:\t", infile); 410 411 error = (decomp ? dodecompress : docompress) 412 (infile, outfile, method, bits, entry->fts_statp); 413 414 switch (error) { 415 case SUCCESS: 416 if (!cat && !testmode) { 417 if (!pipin && unlink(infile) && verbose >= 0) 418 warn("input: %s", infile); 419 } 420 break; 421 case WARNING: 422 rc = rc ? rc : WARNING; 423 break; 424 default: 425 rc = FAILURE; 426 break; 427 } 428 } 429 if (list) 430 list_stats(NULL, NULL, NULL); 431 432 exit(rc); 433 } 434 435 int 436 docompress(const char *in, char *out, const struct compressor *method, 437 int bits, struct stat *sb) 438 { 439 #ifndef SMALL 440 u_char buf[Z_BUFSIZE]; 441 char *name; 442 int error, ifd, ofd, flags, oreg; 443 void *cookie; 444 ssize_t nr; 445 u_int32_t mtime; 446 struct z_info info; 447 struct stat osb; 448 449 mtime = 0; 450 flags = oreg = 0; 451 error = SUCCESS; 452 name = NULL; 453 cookie = NULL; 454 455 if (pipin) 456 ifd = dup(STDIN_FILENO); 457 else 458 ifd = open(in, O_RDONLY); 459 if (ifd < 0) { 460 if (verbose >= 0) 461 warn("%s", in); 462 return (FAILURE); 463 } 464 465 if (cat) 466 ofd = dup(STDOUT_FILENO); 467 else { 468 if (stat(out, &osb) == 0) { 469 oreg = S_ISREG(osb.st_mode); 470 if (!force && oreg && !permission(out)) { 471 (void) close(ifd); 472 return (WARNING); 473 } 474 } 475 ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR); 476 } 477 if (ofd < 0) { 478 if (verbose >= 0) 479 warn("%s", out); 480 (void) close(ifd); 481 return (FAILURE); 482 } 483 484 if (method != M_COMPRESS && !force && isatty(ofd)) { 485 if (verbose >= 0) 486 warnx("%s: won't write compressed data to terminal", 487 out); 488 (void) close(ofd); 489 (void) close(ifd); 490 return (FAILURE); 491 } 492 493 if (!pipin && !nosave) { 494 name = basename(in); 495 mtime = (u_int32_t)sb->st_mtime; 496 } 497 if ((cookie = (*method->open)(ofd, "w", name, bits, mtime, flags)) == NULL) { 498 if (verbose >= 0) 499 warn("%s", out); 500 if (oreg) 501 (void) unlink(out); 502 (void) close(ofd); 503 (void) close(ifd); 504 return (FAILURE); 505 } 506 507 while ((nr = read(ifd, buf, sizeof(buf))) > 0) 508 if ((method->write)(cookie, buf, nr) != nr) { 509 if (verbose >= 0) 510 warn("%s", out); 511 error = FAILURE; 512 break; 513 } 514 515 if (!error && nr < 0) { 516 if (verbose >= 0) 517 warn("%s", in); 518 error = FAILURE; 519 } 520 521 if ((method->close)(cookie, &info, out, sb)) { 522 if (!error && verbose >= 0) 523 warn("%s", out); 524 error = FAILURE; 525 } 526 527 if (close(ifd)) { 528 if (!error && verbose >= 0) 529 warn("%s", in); 530 error = FAILURE; 531 } 532 533 if (!force && info.total_out >= info.total_in) { 534 if (verbose > 0) 535 fprintf(stderr, "file would grow; left unmodified\n"); 536 (void) unlink(out); 537 error = WARNING; 538 } 539 540 if (error) { 541 if (oreg) 542 (void) unlink(out); 543 } else if (verbose > 0) 544 verbose_info(out, info.total_out, info.total_in, info.hlen); 545 546 return (error); 547 #else 548 warnx("compression not supported"); 549 return (FAILURE); 550 #endif 551 } 552 553 const struct compressor * 554 check_method(int fd) 555 { 556 const struct compressor *method; 557 u_char magic[2]; 558 559 if (read(fd, magic, sizeof(magic)) != 2) 560 return (NULL); 561 for (method = &c_table[0]; method->name != NULL; method++) { 562 if (magic[0] == method->magic[0] && 563 magic[1] == method->magic[1]) 564 return (method); 565 } 566 #ifndef SMALL 567 if (force && cat) { 568 null_magic[0] = magic[0]; 569 null_magic[1] = magic[1]; 570 return (&null_method); 571 } 572 #endif /* SMALL */ 573 return (NULL); 574 } 575 576 int 577 dodecompress(const char *in, char *out, const struct compressor *method, 578 int bits, struct stat *sb) 579 { 580 u_char buf[Z_BUFSIZE]; 581 char oldname[MAXPATHLEN]; 582 int error, oreg, ifd, ofd; 583 void *cookie; 584 ssize_t nr; 585 struct z_info info; 586 struct stat osb; 587 588 oreg = 0; 589 error = SUCCESS; 590 cookie = NULL; 591 592 if (pipin) 593 ifd = dup(STDIN_FILENO); 594 else 595 ifd = open(in, O_RDONLY); 596 if (ifd < 0) { 597 if (verbose >= 0) 598 warn("%s", in); 599 return -1; 600 } 601 602 if (!force && isatty(ifd)) { 603 if (verbose >= 0) 604 warnx("%s: won't read compressed data from terminal", 605 in); 606 close (ifd); 607 return -1; 608 } 609 610 if ((method = check_method(ifd)) == NULL) { 611 if (verbose >= 0) 612 warnx("%s: unrecognized file format", in); 613 close (ifd); 614 return -1; 615 } 616 617 /* XXX - open constrains outfile to MAXPATHLEN so this is safe */ 618 oldname[0] = '\0'; 619 if ((cookie = (*method->open)(ifd, "r", oldname, bits, 0, 1)) == NULL) { 620 if (verbose >= 0) 621 warn("%s", in); 622 close (ifd); 623 return (FAILURE); 624 } 625 if (!nosave && oldname[0] != '\0') { 626 strlcpy(out, oldname, MAXPATHLEN); 627 cat = 0; /* XXX should -c override? */ 628 } 629 630 if (testmode) 631 ofd = -1; 632 else { 633 if (cat) 634 ofd = dup(STDOUT_FILENO); 635 else { 636 if (stat(out, &osb) == 0) { 637 oreg = S_ISREG(osb.st_mode); 638 if (!force && oreg && !permission(out)) { 639 (void) close(ifd); 640 return (WARNING); 641 } 642 } 643 ofd = open(out, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR); 644 } 645 if (ofd < 0) { 646 if (verbose >= 0) 647 warn("%s", in); 648 (method->close)(cookie, NULL, NULL, NULL); 649 return (FAILURE); 650 } 651 } 652 653 while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0) { 654 if (ofd != -1 && write(ofd, buf, nr) != nr) { 655 if (verbose >= 0) 656 warn("%s", out); 657 error = FAILURE; 658 break; 659 } 660 } 661 662 if (!error && nr < 0) { 663 if (verbose >= 0) 664 warnx("%s: %s", in, 665 errno == EINVAL ? "crc error" : strerror(errno)); 666 error = errno == EINVAL ? WARNING : FAILURE; 667 } 668 669 if ((method->close)(cookie, &info, NULL, NULL)) { 670 if (!error && verbose >= 0) 671 warnx("%s", in); 672 error = FAILURE; 673 } 674 if (!nosave && !cat) { 675 if (info.mtime != 0) { 676 sb->st_mtimespec.tv_sec = 677 sb->st_atimespec.tv_sec = info.mtime; 678 sb->st_mtimespec.tv_nsec = 679 sb->st_atimespec.tv_nsec = 0; 680 } else 681 nosave = 1; /* no timestamp to restore */ 682 } 683 if (error == SUCCESS) 684 setfile(out, ofd, sb); 685 686 if (ofd != -1 && close(ofd)) { 687 if (!error && verbose >= 0) 688 warn("%s", out); 689 error = FAILURE; 690 } 691 692 if (!error) { 693 if (list) { 694 if (info.mtime == 0) 695 info.mtime = (u_int32_t)sb->st_mtime; 696 list_stats(out, method, &info); 697 } else if (verbose > 0) { 698 verbose_info(out, info.total_in, info.total_out, 699 info.hlen); 700 } 701 } 702 703 /* On error, clean up the file we created but preserve errno. */ 704 if (error && oreg) 705 unlink(out); 706 707 return (error); 708 } 709 710 void 711 setfile(const char *name, int fd, struct stat *fs) 712 { 713 struct timeval tv[2]; 714 715 if (name == NULL || cat || testmode) 716 return; 717 718 /* 719 * If input was a pipe we don't have any info to restore but we 720 * must set the mode since the current mode on the file is 0200. 721 */ 722 if (pipin) { 723 mode_t mask = umask(022); 724 fchmod(fd, DEFFILEMODE & ~mask); 725 umask(mask); 726 return; 727 } 728 729 /* 730 * Changing the ownership probably won't succeed, unless we're root 731 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting 732 * the mode; current BSD behavior is to remove all setuid bits on 733 * chown. If chown fails, lose setuid/setgid bits. 734 */ 735 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; 736 if (fchown(fd, fs->st_uid, fs->st_gid)) { 737 if (errno != EPERM) 738 warn("fchown: %s", name); 739 fs->st_mode &= ~(S_ISUID|S_ISGID); 740 } 741 if (fchmod(fd, fs->st_mode)) 742 warn("fchmod: %s", name); 743 744 if (fs->st_flags && fchflags(fd, fs->st_flags)) 745 warn("fchflags: %s", name); 746 747 if (!nosave) { 748 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); 749 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); 750 if (futimes(fd, tv)) 751 warn("futimes: %s", name); 752 } 753 } 754 755 int 756 permission(const char *fname) 757 { 758 int ch, first; 759 760 if (!isatty(fileno(stderr))) 761 return (0); 762 (void)fprintf(stderr, "overwrite %s? ", fname); 763 first = ch = getchar(); 764 while (ch != '\n' && ch != EOF) 765 ch = getchar(); 766 return (first == 'y'); 767 } 768 769 /* 770 * Check infile for a known suffix and return the suffix portion or NULL. 771 */ 772 const char * 773 check_suffix(const char *infile) 774 { 775 int i; 776 char *suf, *sep, *separators = ".-_"; 777 static char *suffixes[] = { "Z", "gz", "z", "tgz", "taz", NULL }; 778 779 for (sep = separators; *sep != '\0'; sep++) { 780 if ((suf = strrchr(infile, *sep)) == NULL) 781 continue; 782 suf++; 783 784 for (i = 0; suffixes[i] != NULL; i++) { 785 if (strcmp(suf, suffixes[i]) == 0) 786 return (suf - 1); 787 } 788 } 789 return (NULL); 790 } 791 792 /* 793 * Set outfile based on the suffix. In most cases we just strip 794 * off the suffix but things like .tgz and .taz are special. 795 */ 796 char * 797 set_outfile(const char *infile, char *outfile, size_t osize) 798 { 799 const char *s; 800 char *cp; 801 802 if ((s = check_suffix(infile)) == NULL) 803 return (NULL); 804 805 (void)strlcpy(outfile, infile, osize); 806 cp = outfile + (s - infile) + 1; 807 /* 808 * Convert tgz and taz -> tar, else drop the suffix. 809 */ 810 if (strcmp(cp, "tgz") == 0) { 811 cp[1] = 'a'; 812 cp[2] = 'r'; 813 } else if (strcmp(cp, "taz") == 0) 814 cp[2] = 'r'; 815 else 816 cp[-1] = '\0'; 817 return (outfile); 818 } 819 820 /* 821 * Print output for the -l option. 822 */ 823 void 824 list_stats(const char *name, const struct compressor *method, 825 struct z_info *info) 826 { 827 static off_t compressed_total, uncompressed_total, header_total; 828 static u_int nruns; 829 char *timestr; 830 831 if (nruns == 0) { 832 if (verbose >= 0) { 833 if (verbose > 0) 834 fputs("method crc date time ", stdout); 835 puts("compressed uncompressed ratio uncompressed_name"); 836 } 837 } 838 nruns++; 839 840 if (name != NULL) { 841 if (verbose > 0) { 842 timestr = ctime(&info->mtime) + 4; 843 timestr[12] = '\0'; 844 if (timestr[4] == ' ') 845 timestr[4] = '0'; 846 printf("%-7.7s %08x %s ", method->name, info->crc, 847 timestr); 848 } 849 printf("%10lld %10lld %4.1f%% %s\n", 850 (long long)(info->total_in + info->hlen), 851 (long long)info->total_out, 852 ((long long)info->total_out - (long long)info->total_in) * 853 100.0 / info->total_out, name); 854 compressed_total += info->total_in; 855 uncompressed_total += info->total_out; 856 header_total += info->hlen; 857 } else if (verbose >= 0) { 858 if (nruns < 3) /* only do totals for > 1 files */ 859 return; 860 if (verbose > 0) 861 fputs(" ", stdout); 862 printf("%10lld %10lld %4.1f%% (totals)\n", 863 (long long)(compressed_total + header_total), 864 (long long)uncompressed_total, 865 (uncompressed_total - compressed_total) * 866 100.0 / uncompressed_total); 867 } 868 } 869 870 void 871 verbose_info(const char *file, off_t compressed, off_t uncompressed, 872 u_int32_t hlen) 873 { 874 if (testmode) { 875 fputs("OK\n", stderr); 876 return; 877 } 878 if (!pipin) { 879 fprintf(stderr, "\t%4.1f%% -- replaced with %s\n", 880 (uncompressed - compressed) * 100.0 / uncompressed, file); 881 } 882 compressed += hlen; 883 fprintf(stderr, "%lld bytes in, %lld bytes out\n", 884 (long long)(decomp ? compressed : uncompressed), 885 (long long)(decomp ? uncompressed : compressed)); 886 } 887 888 __dead void 889 usage(int status) 890 { 891 fprintf(stderr, 892 "usage: %s [-123456789cdfghLlNnOqrtVv] [-b bits] [-o filename]\n" 893 "\t[-S suffix] [file ...]\n", 894 __progname); 895 exit(status); 896 } 897