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