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