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