Annotation of src/usr.bin/compress/main.c, Revision 1.24
1.24 ! deraadt 1: /* $OpenBSD: main.c,v 1.23 2003/06/04 04:55:58 deraadt Exp $ */
1.1 mickey 2:
1.18 mickey 3: static const char copyright[] =
4: "@(#) Copyright (c) 1992, 1993\n\
5: The Regents of the University of California. All rights reserved.\n"
6: "Copyright (c) 1997-2002 Michael Shalayeff\n";
1.1 mickey 7:
1.18 mickey 8: static const char license[] =
9: "\n"
10: " Redistribution and use in source and binary forms, with or without\n"
11: " modification, are permitted provided that the following conditions\n"
12: " are met:\n"
13: " 1. Redistributions of source code must retain the above copyright\n"
14: " notice, this list of conditions and the following disclaimer.\n"
15: " 2. Redistributions in binary form must reproduce the above copyright\n"
16: " notice, this list of conditions and the following disclaimer in the\n"
17: " documentation and/or other materials provided with the distribution.\n"
1.23 deraadt 18: " 3. Neither the name of the University nor the names of its contributors\n"
19: " may be used to endorse or promote products derived from this software\n"
20: " without specific prior written permission.\n"
1.18 mickey 21: "\n"
22: " THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
23: " IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
24: " OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
25: " IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT,\n"
26: " INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n"
27: " (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n"
28: " SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
29: " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
30: " STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
31: " IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n"
32: " THE POSSIBILITY OF SUCH DAMAGE.\n";
1.1 mickey 33:
34: #ifndef lint
35: #if 0
36: static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94";
37: #else
1.24 ! deraadt 38: static const char main_rcsid[] = "$OpenBSD: main.c,v 1.23 2003/06/04 04:55:58 deraadt Exp $";
1.1 mickey 39: #endif
40: #endif /* not lint */
41:
42: #include <sys/param.h>
43: #include <sys/time.h>
44: #include <sys/stat.h>
45:
1.18 mickey 46: #include <getopt.h>
1.1 mickey 47: #include <err.h>
48: #include <errno.h>
1.19 millert 49: #include <fts.h>
1.1 mickey 50: #include <stdio.h>
51: #include <stdlib.h>
52: #include <string.h>
53: #include <unistd.h>
54: #include <fcntl.h>
55: #include <paths.h>
56: #include "compress.h"
57:
58: #define min(a,b) ((a) < (b)? (a) : (b))
59:
1.18 mickey 60: int pipin, force, verbose, testmode, list, nosave;
61: int savename, recurse;
62: int bits, cat, decomp;
1.1 mickey 63: extern char *__progname;
64:
1.18 mickey 65: const struct compressor {
1.1 mickey 66: char *name;
67: char *suffix;
1.17 millert 68: int (*check_header)(int, struct stat *, const char *);
69: void *(*open)(int, const char *, int);
70: int (*read)(void *, char *, int);
71: int (*write)(void *, const char *, int);
72: int (*close)(void *);
1.1 mickey 73: } c_table[] = {
74: #define M_COMPRESS (&c_table[0])
75: { "compress", ".Z", z_check_header, z_open, zread, zwrite, zclose },
76: #define M_DEFLATE (&c_table[1])
77: { "deflate", ".gz", gz_check_header, gz_open, gz_read, gz_write, gz_close },
1.18 mickey 78: #if 0
79: #define M_LZH (&c_table[2])
80: { "lzh", ".lzh", lzh_check_header, lzh_open, lzh_read, lzh_write, lzh_close },
81: #define M_ZIP (&c_table[3])
82: { "zip", ".zip", zip_check_header, zip_open, zip_read, zip_write, zip_close },
83: #define M_PACK (&c_table[4])
84: { "pack", ".pak",pak_check_header, pak_open, pak_read, pak_write, pak_close },
85: #endif
1.1 mickey 86: { NULL }
87: };
88:
1.18 mickey 89: int permission(const char *);
90: void setfile(const char *, struct stat *);
1.17 millert 91: void usage(void);
1.18 mickey 92: int compress(const char *, const char *, const struct compressor *, int, struct stat *);
93: int decompress(const char *, const char *, const struct compressor *, int, struct stat *);
94: const struct compressor *check_method(int, struct stat *, const char *);
95:
1.19 millert 96: #define OPTSTRING "123456789ab:cdfghlLnNOo:qrS:tvV"
1.18 mickey 97: const struct option longopts[] = {
98: { "ascii", no_argument, 0, 'a' },
99: { "stdout", no_argument, 0, 'c' },
100: { "to-stdout", no_argument, 0, 'c' },
101: { "decompress", no_argument, 0, 'd' },
102: { "uncompress", no_argument, 0, 'd' },
103: { "force", no_argument, 0, 'f' },
104: { "help", no_argument, 0, 'h' },
105: { "list", no_argument, 0, 'l' },
106: { "license", no_argument, 0, 'L' },
107: { "no-name", no_argument, 0, 'n' },
108: { "name", no_argument, 0, 'N' },
109: { "quiet", no_argument, 0, 'q' },
110: { "recursive", no_argument, 0, 'r' },
111: { "suffix", required_argument, 0, 'S' },
112: { "test", no_argument, 0, 't' },
113: { "verbose", no_argument, 0, 'v' },
114: { "version", no_argument, 0, 'V' },
115: { "fast", no_argument, 0, '1' },
116: { "best", no_argument, 0, '9' },
117: { NULL }
118: };
1.1 mickey 119:
120: int
1.24 ! deraadt 121: main(int argc, char *argv[])
1.1 mickey 122: {
1.19 millert 123: FTS *ftsp;
124: FTSENT *entry;
125: struct stat osb;
1.18 mickey 126: const struct compressor *method;
1.19 millert 127: char *p, *s, *infile;
128: char outfile[MAXPATHLEN], _infile[MAXPATHLEN], suffix[16];
1.18 mickey 129: char *nargv[512]; /* some estimate based on ARG_MAX */
1.19 millert 130: int exists, oreg, ch, error, i, rc, oflag;
1.1 mickey 131:
1.19 millert 132: bits = cat = oflag = decomp = 0;
1.1 mickey 133: p = __progname;
134: if (p[0] == 'g') {
135: method = M_DEFLATE;
1.18 mickey 136: bits = 6;
1.1 mickey 137: p++;
138: } else
139: method = M_COMPRESS;
140:
141: decomp = 0;
142: if (!strcmp(p, "zcat")) {
143: decomp++;
1.20 mickey 144: cat = 1;
1.1 mickey 145: } else {
146: if (p[0] == 'u' && p[1] == 'n') {
147: p += 2;
148: decomp++;
149: }
150:
1.2 mickey 151: if (strcmp(p, "zip") &&
152: strcmp(p, "compress"))
1.1 mickey 153: errx(1, "unknown program name");
154: }
155:
1.18 mickey 156: strlcpy(suffix, method->suffix, sizeof(suffix));
157:
1.19 millert 158: nargv[0] = NULL;
1.18 mickey 159: if ((s = getenv("GZIP")) != NULL) {
160: char *last;
161:
162: nargv[0] = *argv++;
163: for (i = 1, (p = strtok_r(s, " ", &last)); p;
164: (p = strtok_r(NULL, " ", &last)), i++)
165: if (i < sizeof(nargv)/sizeof(nargv[1]) - argc - 1)
166: nargv[i] = p;
167: else {
168: errx(1, "GZIP is too long");
169: }
170: argc += i - 1;
171: while ((nargv[i++] = *argv++))
172: ;
173: argv = nargv;
174: }
175:
176: while ((ch = getopt_long(argc, argv, OPTSTRING, longopts, NULL)) != -1)
1.1 mickey 177: switch(ch) {
178: case '1':
179: case '2':
180: case '3':
181: case '4':
182: case '5':
183: case '6':
184: case '7':
185: case '8':
186: case '9':
187: method = M_DEFLATE;
1.18 mickey 188: strlcpy(suffix, method->suffix, sizeof(suffix));
1.1 mickey 189: bits = ch - '0';
190: break;
1.18 mickey 191: case 'a':
192: warnx("option -a is ignored on this system");
193: break;
1.1 mickey 194: case 'b':
195: bits = strtol(optarg, &p, 10);
1.7 denny 196: /*
197: * POSIX 1002.3 says 9 <= bits <= 14 for portable
198: * apps, but says the implementation may allow
199: * greater.
200: */
1.1 mickey 201: if (*p)
202: errx(1, "illegal bit count -- %s", optarg);
203: break;
204: case 'c':
1.20 mickey 205: cat = 1;
1.1 mickey 206: break;
207: case 'd': /* Backward compatible. */
208: decomp++;
209: break;
210: case 'f':
211: force++;
212: break;
213: case 'g':
214: method = M_DEFLATE;
1.18 mickey 215: strlcpy(suffix, method->suffix, sizeof(suffix));
216: bits = 6;
1.1 mickey 217: break;
218: case 'l':
219: list++;
220: break;
221: case 'n':
222: nosave++;
223: break;
224: case 'N':
1.18 mickey 225: nosave = 0; /* XXX not yet */
1.1 mickey 226: break;
227: case 'O':
228: method = M_COMPRESS;
1.18 mickey 229: strlcpy(suffix, method->suffix, sizeof(suffix));
1.1 mickey 230: break;
231: case 'o':
1.18 mickey 232: if (strlcpy(outfile, optarg,
233: sizeof(outfile)) >= sizeof(outfile))
234: errx(1, "-o argument is too long");
1.20 mickey 235: oflag = 1;
1.1 mickey 236: break;
237: case 'q':
238: verbose = -1;
239: break;
240: case 'S':
241: p = suffix;
242: if (optarg[0] != '.')
243: *p++ = '.';
1.18 mickey 244: strlcpy(p, optarg, sizeof(suffix) - (p - suffix));
245: p = optarg;
1.1 mickey 246: break;
247: case 't':
1.20 mickey 248: testmode = 1;
1.1 mickey 249: break;
1.18 mickey 250: case 'V':
251: printf("%s\n%s\n%s\n", main_rcsid,
252: z_rcsid, gz_rcsid);
1.19 millert 253: exit (0);
1.1 mickey 254: case 'v':
255: verbose++;
256: break;
1.18 mickey 257: case 'L':
258: fputs(copyright, stderr);
259: fputs(license, stderr);
1.19 millert 260: exit (0);
1.18 mickey 261: case 'r':
1.19 millert 262: recurse++;
1.18 mickey 263: break;
264:
1.1 mickey 265: case 'h':
266: case '?':
267: default:
268: usage();
269: }
270: argc -= optind;
271: argv += optind;
272:
1.19 millert 273: if (argc == 0) {
274: if (nargv[0] == NULL)
275: argv = nargv;
276: /* XXX - make sure we don't oflow nargv in $GZIP case (millert) */
277: argv[0] = "/dev/stdin";
278: argv[1] = NULL;
279: pipin++;
1.20 mickey 280: cat = 1;
1.19 millert 281: }
282: if (oflag && (recurse || argc > 1))
283: errx(1, "-o option may only be used with a single input file");
1.20 mickey 284:
1.19 millert 285: if (cat + testmode + oflag > 1)
286: errx(1, "may not mix -o, -c, or -t options");
287:
288: if ((ftsp = fts_open(argv, FTS_PHYSICAL|FTS_NOCHDIR, 0)) == NULL)
289: err(1, NULL);
290: /* XXX - set rc in cases where we "continue" below? */
291: for (rc = 0; (entry = fts_read(ftsp)) != NULL;) {
292: infile = entry->fts_path;
293: switch (entry->fts_info) {
294: case FTS_D:
295: if (!recurse) {
296: warnx("%s is a directory: ignored",
297: infile);
298: fts_set(ftsp, entry, FTS_SKIP);
299: }
300: continue;
301: case FTS_DP:
302: continue;
303: case FTS_NS:
304: /*
305: * If file does not exist and has no suffix,
306: * tack on the default suffix and try that.
307: */
308: /* XXX - is overwriting fts_statp legal? (millert) */
309: if (entry->fts_errno == ENOENT &&
310: strchr(entry->fts_accpath, '.') == NULL &&
311: snprintf(_infile, sizeof(_infile), "%s%s", infile,
312: suffix) < sizeof(_infile) &&
313: stat(_infile, entry->fts_statp) == 0 &&
314: S_ISREG(entry->fts_statp->st_mode)) {
315: infile = _infile;
316: break;
317: }
318: case FTS_ERR:
319: case FTS_DNR:
320: warnx("%s: %s", infile, strerror(entry->fts_errno));
321: error = 1;
322: continue;
323: default:
1.20 mickey 324: if (!S_ISREG(entry->fts_statp->st_mode) && !pipin) {
1.19 millert 325: warnx("%s not a regular file: unchanged",
326: infile);
327: continue;
1.18 mickey 328: }
1.19 millert 329: break;
1.9 mickey 330: }
1.1 mickey 331:
332: if (testmode)
1.22 deraadt 333: strlcpy(outfile, _PATH_DEVNULL, sizeof outfile);
1.19 millert 334: else if (cat)
1.22 deraadt 335: strlcpy(outfile, "/dev/stdout", sizeof outfile);
1.19 millert 336: else if (!oflag) {
337: if (decomp) {
338: const struct compressor *m = method;
339:
340: if ((s = strrchr(infile, '.')) != NULL &&
341: strcmp(s, suffix) != 0) {
342: for (m = &c_table[0];
343: m->name && strcmp(s, m->suffix);
344: m++)
345: ;
346: }
347: if (s == NULL || m->name == NULL) {
348: if (!recurse)
349: warnx("%s: unknown suffix: "
350: "ignored", infile);
351: continue;
352: }
353: method = m;
354: strlcpy(outfile, infile,
355: min(sizeof(outfile), (s - infile) + 1));
356: } else {
357: if (snprintf(outfile, sizeof(outfile),
358: "%s%s", infile, suffix) >= sizeof(outfile)) {
359: warnx("%s%s: name too long",
360: infile, suffix);
361: continue;
362: }
363: }
1.1 mickey 364: }
365:
1.19 millert 366: exists = !stat(outfile, &osb);
367: if (!force && exists && S_ISREG(osb.st_mode) &&
368: !permission(outfile))
1.1 mickey 369: continue;
370:
1.19 millert 371: oreg = !exists || S_ISREG(osb.st_mode);
1.1 mickey 372:
373: if (verbose > 0)
374: fprintf(stderr, "%s:\t", infile);
375:
1.19 millert 376: error = (decomp ? decompress : compress)
377: (infile, outfile, method, bits, entry->fts_statp);
1.1 mickey 378:
1.19 millert 379: if (!error && !cat && !testmode && stat(outfile, &osb) == 0) {
380: if (!force && !decomp &&
381: osb.st_size >= entry->fts_statp->st_size) {
1.1 mickey 382: if (verbose > 0)
383: fprintf(stderr, "file would grow; "
384: "left unmodified\n");
385: error = 1;
1.19 millert 386: rc = rc ? rc : 2;
1.1 mickey 387: } else {
1.19 millert 388: setfile(outfile, entry->fts_statp);
1.1 mickey 389:
390: if (unlink(infile) && verbose >= 0)
1.18 mickey 391: warn("input: %s", infile);
1.1 mickey 392:
393: if (verbose > 0) {
394: u_int ratio;
1.19 millert 395: ratio = (1000 * osb.st_size)
396: / entry->fts_statp->st_size;
1.1 mickey 397: fprintf(stderr, "%u", ratio / 10);
398: if (ratio % 10)
399: fprintf(stderr, ".%u",
400: ratio % 10);
401: fputc('%', stderr);
402: fputc(' ', stderr);
403: }
404: }
405: }
406:
1.21 millert 407: if (error > 0 && oreg && unlink(outfile) && errno != ENOENT &&
1.18 mickey 408: verbose >= 0) {
409: if (force) {
410: warn("output: %s", outfile);
411: rc = 1;
412: } else
413: err(1, "output: %s", outfile);
414: } else if (!error && verbose > 0)
1.1 mickey 415: fputs("OK\n", stderr);
1.19 millert 416: }
1.1 mickey 417:
1.18 mickey 418: exit(rc);
1.1 mickey 419: }
420:
421: int
1.24 ! deraadt 422: compress(const char *in, const char *out, const struct compressor *method,
! 423: int bits, struct stat *sb)
1.1 mickey 424: {
1.18 mickey 425: u_char buf[Z_BUFSIZE];
426: int error, ifd, ofd;
1.16 mpech 427: void *cookie;
428: ssize_t nr;
1.1 mickey 429:
430: error = 0;
431: cookie = NULL;
1.3 mickey 432:
1.21 millert 433: if ((ifd = open(in, O_RDONLY)) < 0) {
434: if (verbose >= 0)
435: warn("%s", out);
436: return (-1);
437: }
438:
1.3 mickey 439: if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) < 0) {
440: if (verbose >= 0)
441: warn("%s", out);
1.19 millert 442: return (-1);
1.3 mickey 443: }
444:
1.4 mickey 445: if (method != M_COMPRESS && !force && isatty(ofd)) {
1.3 mickey 446: if (verbose >= 0)
447: warnx("%s: won't write compressed data to terminal",
448: out);
1.19 millert 449: return (-1);
1.3 mickey 450: }
1.1 mickey 451:
1.18 mickey 452: if ((cookie = (*method->open)(ofd, "w", bits)) != NULL) {
1.1 mickey 453:
1.12 mickey 454: while ((nr = read(ifd, buf, sizeof(buf))) > 0)
1.1 mickey 455: if ((method->write)(cookie, buf, nr) != nr) {
456: if (verbose >= 0)
457: warn("%s", out);
458: error++;
459: break;
460: }
461: }
462:
1.18 mickey 463: if (cookie == NULL || nr < 0) {
1.1 mickey 464: if (!error && verbose >= 0)
465: warn("%s", in);
466: error++;
467: }
468:
1.3 mickey 469: if (cookie == NULL || (method->close)(cookie)) {
1.1 mickey 470: if (!error && verbose >= 0)
471: warn("%s", out);
472: error++;
473: (void) close(ofd);
474: }
475:
1.18 mickey 476: if (close(ifd)) {
477: if (!error && verbose >= 0)
478: warn("%s", out);
479: error++;
480: }
481:
1.21 millert 482: return (error);
1.1 mickey 483: }
484:
1.18 mickey 485: const struct compressor *
1.24 ! deraadt 486: check_method(int fd, struct stat *sb, const char *out)
1.1 mickey 487: {
1.18 mickey 488: const struct compressor *method;
1.1 mickey 489:
490: for (method = &c_table[0];
1.18 mickey 491: method->name != NULL && !(*method->check_header)(fd, sb, out);
1.1 mickey 492: method++)
493: ;
494:
495: if (method->name == NULL)
496: method = NULL;
497:
1.19 millert 498: return (method);
1.1 mickey 499: }
500:
501: int
1.24 ! deraadt 502: decompress(const char *in, const char *out, const struct compressor *method,
! 503: int bits, struct stat *sb)
1.1 mickey 504: {
1.18 mickey 505: u_char buf[Z_BUFSIZE];
506: int error, ifd, ofd;
1.16 mpech 507: void *cookie;
508: ssize_t nr;
1.1 mickey 509:
510: error = 0;
511: cookie = NULL;
512:
1.3 mickey 513: if ((ifd = open(in, O_RDONLY)) < 0) {
514: if (verbose >= 0)
515: warn("%s", in);
516: return -1;
517: }
518:
519: if (!force && isatty(ifd)) {
520: if (verbose >= 0)
521: warnx("%s: won't read compressed data from terminal",
522: in);
523: close (ifd);
524: return -1;
525: }
526:
1.18 mickey 527: if (!pipin && (method = check_method(ifd, sb, out)) == NULL) {
1.3 mickey 528: if (verbose >= 0)
529: warnx("%s: unrecognized file format", in);
1.13 d 530: close (ifd);
1.3 mickey 531: return -1;
532: }
533:
1.18 mickey 534: if ((cookie = (*method->open)(ifd, "r", bits)) != NULL) {
1.21 millert 535: if ((ofd = open(out, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR)) < 0) {
536: if (verbose >= 0)
537: warn("%s", in);
538: (method->close)(cookie);
539: return -1;
540: }
1.1 mickey 541:
1.12 mickey 542: while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0)
1.1 mickey 543: if (write(ofd, buf, nr) != nr) {
544: if (verbose >= 0)
545: warn("%s", out);
546: error++;
547: break;
548: }
549: }
550:
1.18 mickey 551: if (cookie == NULL || (method->close)(cookie) || nr < 0) {
1.1 mickey 552: if (!error && verbose >= 0)
1.18 mickey 553: warn("%s", in);
1.1 mickey 554: error++;
1.18 mickey 555: close (ifd);
1.1 mickey 556: }
557:
1.18 mickey 558: if (close(ofd)) {
1.1 mickey 559: if (!error && verbose >= 0)
1.18 mickey 560: warn("%s", out);
1.1 mickey 561: error++;
562: }
563:
1.21 millert 564: return (error);
1.1 mickey 565: }
566:
567: void
1.24 ! deraadt 568: setfile(const char *name, struct stat *fs)
1.1 mickey 569: {
1.18 mickey 570: struct timeval tv[2];
1.1 mickey 571:
572: fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
573:
574: TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
575: TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
576: if (utimes(name, tv))
577: warn("utimes: %s", name);
578:
579: /*
580: * Changing the ownership probably won't succeed, unless we're root
581: * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
582: * the mode; current BSD behavior is to remove all setuid bits on
583: * chown. If chown fails, lose setuid/setgid bits.
584: */
585: if (chown(name, fs->st_uid, fs->st_gid)) {
586: if (errno != EPERM)
587: warn("chown: %s", name);
588: fs->st_mode &= ~(S_ISUID|S_ISGID);
589: }
590: if (chmod(name, fs->st_mode))
591: warn("chown: %s", name);
592:
1.8 millert 593: if (fs->st_flags && chflags(name, fs->st_flags))
1.1 mickey 594: warn("chflags: %s", name);
595: }
596:
597: int
1.24 ! deraadt 598: permission(const char *fname)
1.1 mickey 599: {
600: int ch, first;
601:
602: if (!isatty(fileno(stderr)))
603: return (0);
604: (void)fprintf(stderr, "overwrite %s? ", fname);
605: first = ch = getchar();
606: while (ch != '\n' && ch != EOF)
607: ch = getchar();
608: return (first == 'y');
609: }
610:
611: void
1.24 ! deraadt 612: usage(void)
1.1 mickey 613: {
614: fprintf(stderr,
1.18 mickey 615: "usage: %s [-cdfghlnLOqrStvV] [-b <bits>] [-[0-9]] [file ...]\n",
616: __progname);
1.1 mickey 617: exit(1);
618: }