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