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