xref: /openbsd-src/usr.bin/compress/main.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: main.c,v 1.43 2003/07/29 18:33:11 millert Exp $	*/
2 
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";
7 
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. 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"
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";
33 
34 #ifndef lint
35 #if 0
36 static char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
37 #else
38 static const char main_rcsid[] = "$OpenBSD: main.c,v 1.43 2003/07/29 18:33:11 millert Exp $";
39 #endif
40 #endif /* not lint */
41 
42 #include <sys/param.h>
43 #include <sys/time.h>
44 #include <sys/stat.h>
45 
46 #include <getopt.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <fts.h>
50 #include <libgen.h>
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 
61 int pipin, force, verbose, testmode, list, nosave;
62 int savename, recurse;
63 int cat, decomp;
64 extern char *__progname;
65 
66 const struct compressor {
67 	char *name;
68 	char *suffix;
69 	u_char *magic;
70 	void *(*open)(int, const char *, char *, int, u_int32_t, int);
71 	int (*read)(void *, char *, int);
72 	int (*write)(void *, const char *, int);
73 	int (*close)(void *, struct z_info *);
74 } c_table[] = {
75 #define M_COMPRESS (&c_table[0])
76   { "compress", ".Z", "\037\235", z_open,  zread,   zwrite,   z_close },
77 #define M_DEFLATE (&c_table[1])
78   { "deflate", ".gz", "\037\213", gz_open, gz_read, gz_write, gz_close },
79 #if 0
80 #define M_LZH (&c_table[2])
81   { "lzh", ".lzh", "\037\240", lzh_open, lzh_read, lzh_write, lzh_close },
82 #define M_ZIP (&c_table[3])
83   { "zip", ".zip", "PK", zip_open, zip_read, zip_write, zip_close },
84 #define M_PACK (&c_table[4])
85   { "pack", ".pak", "\037\036", pak_open, pak_read, pak_write, pak_close },
86 #endif
87   { NULL }
88 };
89 
90 int permission(const char *);
91 void setfile(const char *, struct stat *);
92 __dead void usage(int);
93 int compress(const char *, char *, const struct compressor *,
94     int, struct stat *);
95 int decompress(const char *, char *, const struct compressor *,
96     int, struct stat *);
97 const struct compressor *check_method(int);
98 const char *check_suffix(const char *);
99 char *set_outfile(const char *, char *, size_t);
100 void list_stats(const char *, const struct compressor *, struct z_info *);
101 void verbose_info(const char *, off_t, off_t, u_int32_t);
102 
103 #define	OPTSTRING	"123456789ab:cdfghlLnNOo:qrS:tvV"
104 const struct option longopts[] = {
105 	{ "ascii",	no_argument,		0, 'a' },
106 	{ "stdout",	no_argument,		0, 'c' },
107 	{ "to-stdout",	no_argument,		0, 'c' },
108 	{ "decompress",	no_argument,		0, 'd' },
109 	{ "uncompress",	no_argument,		0, 'd' },
110 	{ "force",	no_argument,		0, 'f' },
111 	{ "help",	no_argument,		0, 'h' },
112 	{ "list",	no_argument,		0, 'l' },
113 	{ "license",	no_argument,		0, 'L' },
114 	{ "no-name",	no_argument,		0, 'n' },
115 	{ "name",	no_argument,		0, 'N' },
116 	{ "quiet",	no_argument,		0, 'q' },
117 	{ "recursive",	no_argument,		0, 'r' },
118 	{ "suffix",	required_argument,	0, 'S' },
119 	{ "test",	no_argument,		0, 't' },
120 	{ "verbose",	no_argument,		0, 'v' },
121 	{ "version",	no_argument,		0, 'V' },
122 	{ "fast",	no_argument,		0, '1' },
123 	{ "best",	no_argument,		0, '9' },
124 	{ NULL }
125 };
126 
127 int
128 main(int argc, char *argv[])
129 {
130 	FTS *ftsp;
131 	FTSENT *entry;
132 	struct stat osb;
133 	const struct compressor *method;
134 	const char *s;
135 	char *p, *infile;
136 	char outfile[MAXPATHLEN], _infile[MAXPATHLEN], suffix[16];
137 	char *nargv[512];	/* some estimate based on ARG_MAX */
138 	int bits, exists, oreg, ch, error, i, rc, oflag;
139 
140 	bits = cat = oflag = decomp = 0;
141 	nosave = -1;
142 	p = __progname;
143 	if (p[0] == 'g') {
144 		method = M_DEFLATE;
145 		bits = 6;
146 		p++;
147 	} else
148 		method = M_COMPRESS;
149 
150 	decomp = 0;
151 	if (!strcmp(p, "zcat")) {
152 		decomp++;
153 		cat = 1;
154 	} else {
155 		if (p[0] == 'u' && p[1] == 'n') {
156 			p += 2;
157 			decomp++;
158 		}
159 
160 		if (strcmp(p, "zip") &&
161 		    strcmp(p, "compress"))
162 			errx(1, "unknown program name");
163 	}
164 
165 	strlcpy(suffix, method->suffix, sizeof(suffix));
166 
167 	nargv[0] = NULL;
168 	if ((p = getenv("GZIP")) != NULL) {
169 		char *last;
170 
171 		nargv[0] = *argv++;
172 		for (i = 1, (p = strtok_r(p, " ", &last)); p != NULL;
173 		    (p = strtok_r(NULL, " ", &last)), i++)
174 			if (i < sizeof(nargv)/sizeof(nargv[1]) - argc - 1)
175 				nargv[i] = p;
176 			else {
177 				errx(1, "GZIP is too long");
178 			}
179 		argc += i - 1;
180 		while ((nargv[i++] = *argv++))
181 			;
182 		argv = nargv;
183 	}
184 
185 	while ((ch = getopt_long(argc, argv, OPTSTRING, longopts, NULL)) != -1)
186 		switch(ch) {
187 		case '1':
188 		case '2':
189 		case '3':
190 		case '4':
191 		case '5':
192 		case '6':
193 		case '7':
194 		case '8':
195 		case '9':
196 			method = M_DEFLATE;
197 			strlcpy(suffix, method->suffix, sizeof(suffix));
198 			bits = ch - '0';
199 			break;
200 		case 'a':
201 			warnx("option -a is ignored on this system");
202 			break;
203 		case 'b':
204 			bits = strtol(optarg, &p, 10);
205 			/*
206 			 * POSIX 1002.3 says 9 <= bits <= 14 for portable
207 			 * apps, but says the implementation may allow
208 			 * greater.
209 			 */
210 			if (*p)
211 				errx(1, "illegal bit count -- %s", optarg);
212 			break;
213 		case 'c':
214 			cat = 1;
215 			break;
216 		case 'd':		/* Backward compatible. */
217 			decomp++;
218 			break;
219 		case 'f':
220 			force++;
221 			break;
222 		case 'g':
223 			method = M_DEFLATE;
224 			strlcpy(suffix, method->suffix, sizeof(suffix));
225 			bits = 6;
226 			break;
227 		case 'l':
228 			list++;
229 			testmode++;
230 			decomp++;
231 			break;
232 		case 'n':
233 			nosave = 1;
234 			break;
235 		case 'N':
236 			nosave = 0;
237 			break;
238 		case 'O':
239 			method = M_COMPRESS;
240 			strlcpy(suffix, method->suffix, sizeof(suffix));
241 			break;
242 		case 'o':
243 			if (strlcpy(outfile, optarg,
244 			    sizeof(outfile)) >= sizeof(outfile))
245 				errx(1, "-o argument is too long");
246 			oflag = 1;
247 			break;
248 		case 'q':
249 			verbose = -1;
250 			break;
251 		case 'S':
252 			p = suffix;
253 			if (optarg[0] != '.')
254 				*p++ = '.';
255 			strlcpy(p, optarg, sizeof(suffix) - (p - suffix));
256 			p = optarg;
257 			break;
258 		case 't':
259 			testmode = 1;
260 			decomp++;
261 			break;
262 		case 'V':
263 			printf("%s\n%s\n%s\n", main_rcsid,
264 			    z_rcsid, gz_rcsid);
265 			exit (0);
266 		case 'v':
267 			verbose++;
268 			break;
269 		case 'L':
270 			fputs(copyright, stderr);
271 			fputs(license, stderr);
272 			exit (0);
273 		case 'r':
274 			recurse++;
275 			break;
276 
277 		case 'h':
278 			usage(0);
279 			break;
280 		default:
281 			usage(1);
282 		}
283 	argc -= optind;
284 	argv += optind;
285 
286 	if (argc == 0) {
287 		if (nargv[0] == NULL)
288 			argv = nargv;
289 		/* XXX - make sure we don't oflow nargv in $GZIP case (millert) */
290 		argv[0] = "/dev/stdin";
291 		argv[1] = NULL;
292 		pipin++;
293 		if (!oflag)
294 			cat = 1;
295 	} else {
296 		for (i = 0; i < argc; i++) {
297 			if (argv[i][0] == '-' && argv[i][1] == '\0') {
298 				argv[i] = "/dev/stdin";
299 				pipin++;
300 				cat = 1;
301 			}
302 		}
303 	}
304 	if (oflag && (recurse || argc > 1))
305 		errx(1, "-o option may only be used with a single input file");
306 
307 	if ((cat && argc) + testmode + oflag > 1)
308 		errx(1, "may not mix -o, -c, or -t options");
309 	if (nosave == -1)
310 		nosave = decomp;
311 
312 	if ((ftsp = fts_open(argv, FTS_PHYSICAL|FTS_NOCHDIR, 0)) == NULL)
313 		err(1, NULL);
314 	for (rc = SUCCESS; (entry = fts_read(ftsp)) != NULL;) {
315 		infile = entry->fts_path;
316 		switch (entry->fts_info) {
317 		case FTS_D:
318 			if (!recurse) {
319 				warnx("%s is a directory: ignored",
320 				    infile);
321 				fts_set(ftsp, entry, FTS_SKIP);
322 			}
323 			continue;
324 		case FTS_DP:
325 			continue;
326 		case FTS_NS:
327 			/*
328 			 * If file does not exist and has no suffix,
329 			 * tack on the default suffix and try that.
330 			 */
331 			/* XXX - is overwriting fts_statp legal? (millert) */
332 			if (entry->fts_errno == ENOENT &&
333 			    strchr(entry->fts_accpath, '.') == NULL &&
334 			    snprintf(_infile, sizeof(_infile), "%s%s", infile,
335 			    suffix) < sizeof(_infile) &&
336 			    stat(_infile, entry->fts_statp) == 0 &&
337 			    S_ISREG(entry->fts_statp->st_mode)) {
338 				infile = _infile;
339 				break;
340 			}
341 		case FTS_ERR:
342 		case FTS_DNR:
343 			warnx("%s: %s", infile, strerror(entry->fts_errno));
344 			rc = rc ? rc : WARNING;
345 			continue;
346 		default:
347 			if (!S_ISREG(entry->fts_statp->st_mode) && !pipin) {
348 				warnx("%s not a regular file%s",
349 				    infile, cat ? "" : ": unchanged");
350 				rc = rc ? rc : WARNING;
351 				continue;
352 			}
353 			break;
354 		}
355 
356 		if (!decomp && !pipin && (s = check_suffix(infile)) != NULL) {
357 			warnx("%s already has %s suffix -- unchanged",
358 			    infile, s);
359 			rc = rc ? rc : WARNING;
360 			continue;
361 		}
362 
363 		if (cat)
364 			strlcpy(outfile, "/dev/stdout", sizeof outfile);
365 		else if (!oflag) {
366 			if (decomp) {
367 				if (set_outfile(infile, outfile,
368 				    sizeof outfile) == NULL) {
369 					if (!recurse)
370 						warnx("%s: unknown suffix: "
371 						    "ignored", infile);
372 					continue;
373 				}
374 			} else {
375 				if (snprintf(outfile, sizeof(outfile),
376 				    "%s%s", infile, suffix) >= sizeof(outfile)) {
377 					warnx("%s%s: name too long",
378 					    infile, suffix);
379 					continue;
380 				}
381 			}
382 		}
383 
384 		exists = !stat(outfile, &osb);
385 		if (!force && exists && S_ISREG(osb.st_mode) &&
386 		    !permission(outfile)) {
387 			rc = rc ? rc : WARNING;
388 			continue;
389 		}
390 
391 		oreg = !exists || S_ISREG(osb.st_mode);
392 
393 		if (verbose > 0 && !pipin && !list)
394 			fprintf(stderr, "%s:\t", infile);
395 
396 		error = (decomp ? decompress : compress)
397 			(infile, outfile, method, bits, entry->fts_statp);
398 
399 		switch (error) {
400 		case SUCCESS:
401 			if (!cat && !testmode) {
402 				setfile(outfile, entry->fts_statp);
403 				if (!pipin && unlink(infile) && verbose >= 0)
404 					warn("input: %s", infile);
405 			}
406 			break;
407 		case WARNING:
408 			rc = rc ? rc : WARNING;
409 			break;
410 		default:
411 			rc = FAILURE;
412 			if (oreg && unlink(outfile) && errno != ENOENT &&
413 			    verbose >= 0) {
414 				if (force)
415 					warn("output: %s", outfile);
416 				else
417 					err(1, "output: %s", outfile);
418 			}
419 			break;
420 		}
421 	}
422 	if (list)
423 		list_stats(NULL, NULL, NULL);
424 
425 	exit(rc);
426 }
427 
428 int
429 compress(const char *in, char *out, const struct compressor *method,
430     int bits, struct stat *sb)
431 {
432 	u_char buf[Z_BUFSIZE];
433 	char *name;
434 	int error, ifd, ofd, flags;
435 	void *cookie;
436 	ssize_t nr;
437 	u_int32_t mtime;
438 	struct z_info info;
439 
440 	mtime = 0;
441 	flags = 0;
442 	error = SUCCESS;
443 	name = NULL;
444 	cookie  = NULL;
445 
446 	if ((ifd = open(in, O_RDONLY)) < 0) {
447 		if (verbose >= 0)
448 			warn("%s", out);
449 		return (FAILURE);
450 	}
451 
452 	if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) < 0) {
453 		if (verbose >= 0)
454 			warn("%s", out);
455 		(void) close(ifd);
456 		return (FAILURE);
457 	}
458 
459 	if (method != M_COMPRESS && !force && isatty(ofd)) {
460 		if (verbose >= 0)
461 			warnx("%s: won't write compressed data to terminal",
462 			    out);
463 		(void) close(ofd);
464 		(void) close(ifd);
465 		return (FAILURE);
466 	}
467 
468 	if (!pipin && !nosave) {
469 		name = basename(in);
470 		mtime = (u_int32_t)sb->st_mtime;
471 	}
472 	if ((cookie = (*method->open)(ofd, "w", name, bits, mtime, flags)) == NULL) {
473 		if (verbose >= 0)
474 			warn("%s", in);
475 		(void) close(ofd);
476 		(void) close(ifd);
477 		return (FAILURE);
478 	}
479 
480 	while ((nr = read(ifd, buf, sizeof(buf))) > 0)
481 		if ((method->write)(cookie, buf, nr) != nr) {
482 			if (verbose >= 0)
483 				warn("%s", out);
484 			error = FAILURE;
485 			break;
486 		}
487 
488 	if (!error && nr < 0) {
489 		if (verbose >= 0)
490 			warn("%s", in);
491 		error = FAILURE;
492 	}
493 
494 	if ((method->close)(cookie, &info)) {
495 		if (!error && verbose >= 0)
496 			warn("%s", out);
497 		error = FAILURE;
498 	}
499 
500 	if (close(ifd)) {
501 		if (!error && verbose >= 0)
502 			warn("%s", in);
503 		error = FAILURE;
504 	}
505 
506 	if (!force && info.total_out >= info.total_in) {
507 		if (verbose > 0)
508 			fprintf(stderr, "file would grow; left unmodified\n");
509 		error = FAILURE;
510 	}
511 
512 	if (!error && verbose > 0)
513 		verbose_info(out, info.total_out, info.total_in, info.hlen);
514 
515 	return (error);
516 }
517 
518 const struct compressor *
519 check_method(int fd)
520 {
521 	const struct compressor *method;
522 	u_char magic[2];
523 
524 	if (read(fd, magic, sizeof(magic)) != 2)
525 		return (NULL);
526 	for (method = &c_table[0]; method->name != NULL; method++) {
527 		if (magic[0] == method->magic[0] &&
528 		    magic[1] == method->magic[1])
529 			return (method);
530 	}
531 	return (NULL);
532 }
533 
534 int
535 decompress(const char *in, char *out, const struct compressor *method,
536     int bits, struct stat *sb)
537 {
538 	u_char buf[Z_BUFSIZE];
539 	int error, ifd, ofd;
540 	void *cookie;
541 	ssize_t nr;
542 	struct z_info info;
543 
544 	error = SUCCESS;
545 	cookie = NULL;
546 
547 	if ((ifd = open(in, O_RDONLY)) < 0) {
548 		if (verbose >= 0)
549 			warn("%s", in);
550 		return -1;
551 	}
552 
553 	if (!force && isatty(ifd)) {
554 		if (verbose >= 0)
555 			warnx("%s: won't read compressed data from terminal",
556 			    in);
557 		close (ifd);
558 		return -1;
559 	}
560 
561 	if ((method = check_method(ifd)) == NULL) {
562 		if (verbose >= 0)
563 			warnx("%s: unrecognized file format", in);
564 		close (ifd);
565 		return -1;
566 	}
567 
568 	/* XXX - open constrains outfile to MAXPATHLEN so this is safe */
569 	if ((cookie = (*method->open)(ifd, "r", nosave ? NULL : out,
570 	    bits, 0, 1)) == NULL) {
571 		if (verbose >= 0)
572 			warn("%s", in);
573 		close (ifd);
574 		return (FAILURE);
575 	}
576 
577 	if (testmode)
578 		ofd = -1;
579 	else if ((ofd = open(out, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR)) < 0) {
580 		if (verbose >= 0)
581 			warn("%s", in);
582 		(method->close)(cookie, NULL);
583 		return (FAILURE);
584 	}
585 
586 	while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0) {
587 		if (ofd != -1 && write(ofd, buf, nr) != nr) {
588 			if (verbose >= 0)
589 				warn("%s", out);
590 			error = FAILURE;
591 			break;
592 		}
593 	}
594 
595 	if (!error && nr < 0) {
596 		if (verbose >= 0)
597 			warnx("%s: %s", in,
598 			    errno == EINVAL ? "crc error" : strerror(errno));
599 		error = errno == EINVAL ? WARNING : FAILURE;
600 	}
601 
602 	if ((method->close)(cookie, &info)) {
603 		if (!error && verbose >= 0)
604 			warnx("%s", in);
605 		error = FAILURE;
606 	}
607 
608 	if (!nosave) {
609 		if (info.mtime != 0) {
610 			sb->st_mtimespec.tv_sec =
611 			    sb->st_atimespec.tv_sec = info.mtime;
612 			sb->st_mtimespec.tv_nsec =
613 			    sb->st_atimespec.tv_nsec = 0;
614 		} else
615 			nosave = 1;		/* no timestamp to restore */
616 
617 		if (cat && strcmp(out, "/dev/stdout") != 0)
618 			cat = 0;		/* have a real output name */
619 	}
620 
621 	if (ofd != -1 && close(ofd)) {
622 		if (!error && verbose >= 0)
623 			warn("%s", out);
624 		error = FAILURE;
625 	}
626 
627 	if (!error) {
628 		if (list) {
629 			if (info.mtime == 0)
630 				info.mtime = (u_int32_t)sb->st_mtime;
631 			list_stats(out, method, &info);
632 		} else if (verbose > 0) {
633 			verbose_info(out, info.total_in, info.total_out,
634 			    info.hlen);
635 		}
636 	}
637 
638 	return (error);
639 }
640 
641 void
642 setfile(const char *name, struct stat *fs)
643 {
644 	struct timeval tv[2];
645 
646 	if (!pipin || !nosave) {
647 		TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
648 		TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
649 		if (utimes(name, tv))
650 			warn("utimes: %s", name);
651 	}
652 
653 	/*
654 	 * If input was a pipe we don't have any info to restore but we
655 	 * must set the mode since the current mode on the file is 0200.
656 	 */
657 	if (pipin) {
658 		mode_t mask = umask(022);
659 		chmod(name, DEFFILEMODE & ~mask);
660 		umask(mask);
661 		return;
662 	}
663 
664 	/*
665 	 * Changing the ownership probably won't succeed, unless we're root
666 	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
667 	 * the mode; current BSD behavior is to remove all setuid bits on
668 	 * chown.  If chown fails, lose setuid/setgid bits.
669 	 */
670 	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
671 	if (chown(name, fs->st_uid, fs->st_gid)) {
672 		if (errno != EPERM)
673 			warn("chown: %s", name);
674 		fs->st_mode &= ~(S_ISUID|S_ISGID);
675 	}
676 	if (chmod(name, fs->st_mode))
677 		warn("chown: %s", name);
678 
679 	if (fs->st_flags && chflags(name, fs->st_flags))
680 		warn("chflags: %s", name);
681 }
682 
683 int
684 permission(const char *fname)
685 {
686 	int ch, first;
687 
688 	if (!isatty(fileno(stderr)))
689 		return (0);
690 	(void)fprintf(stderr, "overwrite %s? ", fname);
691 	first = ch = getchar();
692 	while (ch != '\n' && ch != EOF)
693 		ch = getchar();
694 	return (first == 'y');
695 }
696 
697 /*
698  * Check infile for a known suffix and return the suffix portion or NULL.
699  */
700 const char *
701 check_suffix(const char *infile)
702 {
703 	int i;
704 	char *suf, *sep, *separators = ".-_";
705 	static char *suffixes[] = { "Z", "gz", "z", "tgz", "taz", NULL };
706 
707 	for (sep = separators; *sep != '\0'; sep++) {
708 		if ((suf = strrchr(infile, *sep)) == NULL)
709 			continue;
710 		suf++;
711 
712 		for (i = 0; suffixes[i] != NULL; i++) {
713 			if (strcmp(suf, suffixes[i]) == 0)
714 				return (suf - 1);
715 		}
716 	}
717 	return (NULL);
718 }
719 
720 /*
721  * Set outfile based on the suffix.  In most cases we just strip
722  * off the suffix but things like .tgz and .taz are special.
723  */
724 char *
725 set_outfile(const char *infile, char *outfile, size_t osize)
726 {
727 	const char *s;
728 	char *cp;
729 
730 	if ((s = check_suffix(infile)) == NULL)
731 		return (NULL);
732 
733 	(void)strlcpy(outfile, infile, osize);
734 	cp = outfile + (s - infile) + 1;
735 	/*
736 	 * Convert tgz and taz -> tar, else drop the suffix.
737 	 */
738 	if (strcmp(cp, "tgz") == 0) {
739 		cp[1] = 'a';
740 		cp[2] = 'r';
741 	} else if (strcmp(cp, "taz") == 0)
742 		cp[2] = 'r';
743 	else
744 		cp[-1] = '\0';
745 	return (outfile);
746 }
747 
748 /*
749  * Print output for the -l option.
750  */
751 void
752 list_stats(const char *name, const struct compressor *method,
753     struct z_info *info)
754 {
755 	static off_t compressed_total, uncompressed_total, header_total;
756 	static u_int nruns;
757 	char *timestr;
758 
759 	if (name != NULL && strcmp(name, "/dev/stdout") == 0)
760 		name += 5;
761 
762 	if (nruns == 0) {
763 		if (verbose >= 0) {
764 			if (verbose > 0)
765 				fputs("method  crc     date  time  ", stdout);
766 			puts("compressed  uncompr. ratio uncompressed_name");
767 		}
768 	}
769 	nruns++;
770 
771 	if (name != NULL) {
772 		if (verbose > 0) {
773 			timestr = ctime(&info->mtime) + 4;
774 			timestr[12] = '\0';
775 			printf("%.5s %08x %s ", method->name, info->crc, timestr);
776 		}
777 		printf("%9lld %9lld  %4.1f%% %s\n",
778 		    (long long)(info->total_in + info->hlen),
779 		    (long long)info->total_out,
780 		    (info->total_out - info->total_in) *
781 		    100.0 / info->total_out, name);
782 		compressed_total += info->total_in;
783 		uncompressed_total += info->total_out;
784 		header_total += info->hlen;
785 	} else if (verbose >= 0) {
786 		if (nruns < 3)		/* only do totals for > 1 files */
787 			return;
788 		if (verbose > 0)
789 			fputs("                            ", stdout);
790 		printf("%9lld %9lld  %4.1f%% (totals)\n",
791 		    (long long)(compressed_total + header_total),
792 		    (long long)uncompressed_total,
793 		    (uncompressed_total - compressed_total) *
794 		    100.0 / uncompressed_total);
795 	}
796 }
797 
798 void
799 verbose_info(const char *file, off_t compressed, off_t uncompressed,
800     u_int32_t hlen)
801 {
802 	if (testmode) {
803 		fputs("OK\n", stderr);
804 		return;
805 	}
806 	if (!pipin) {
807 		fprintf(stderr, "\t%4.1f%% -- replaced with %s\n",
808 		    (uncompressed - compressed) * 100.0 / uncompressed, file);
809 	}
810 	compressed += hlen;
811 	fprintf(stderr, "%lld bytes in, %lld bytes out\n",
812 	    (long long)(decomp ? compressed : uncompressed),
813 	    (long long)(decomp ? uncompressed : compressed));
814 }
815 
816 __dead void
817 usage(int status)
818 {
819 	fprintf(stderr,
820 	    "usage: %s [-cdfghOqrtvV] [-b bits] [-S suffix] [-[1-9]] [file ...]\n",
821 	    __progname);
822 	exit(status);
823 }
824