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