Home | History | Annotate | Line # | Download | only in gzip
gzip.c revision 1.55
      1 /*	$NetBSD: gzip.c,v 1.55 2004/07/11 07:01:03 mrg Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997, 1998, 2003, 2004 Matthew R. Green
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. The name of the author may not be used to endorse or promote products
     16  *    derived from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     23  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     26  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     28  * SUCH DAMAGE.
     29  */
     30 
     31 #include <sys/cdefs.h>
     32 #ifndef lint
     33 __COPYRIGHT("@(#) Copyright (c) 1997, 1998, 2003, 2004 Matthew R. Green\n\
     34      All rights reserved.\n");
     35 __RCSID("$NetBSD: gzip.c,v 1.55 2004/07/11 07:01:03 mrg Exp $");
     36 #endif /* not lint */
     37 
     38 /*
     39  * gzip.c -- GPL free gzip using zlib.
     40  *
     41  * TODO:
     42  *	- handle .taz/.tgz files?
     43  *	- use mmap where possible
     44  *	- handle some signals better (remove outfile?)
     45  *	- make bzip2/compress -v/-t/-l support work as well as possible
     46  */
     47 
     48 #include <sys/param.h>
     49 #include <sys/stat.h>
     50 #include <sys/time.h>
     51 
     52 #include <unistd.h>
     53 #include <stdio.h>
     54 #include <string.h>
     55 #include <stdlib.h>
     56 #include <err.h>
     57 #include <errno.h>
     58 #include <fcntl.h>
     59 #include <zlib.h>
     60 #include <fts.h>
     61 #include <libgen.h>
     62 #include <stdarg.h>
     63 #include <getopt.h>
     64 
     65 /* what type of file are we dealing with */
     66 enum filetype {
     67 	FT_GZIP,
     68 #ifndef NO_BZIP2_SUPPORT
     69 	FT_BZIP2,
     70 #endif
     71 #ifndef NO_COMPRESS_SUPPORT
     72 	FT_Z,
     73 #endif
     74 	FT_LAST,
     75 	FT_UNKNOWN
     76 };
     77 
     78 #ifndef NO_BZIP2_SUPPORT
     79 #include <bzlib.h>
     80 
     81 #define BZ2_SUFFIX	".bz2"
     82 #define BZIP2_MAGIC	"\102\132\150"
     83 #endif
     84 
     85 #ifndef NO_COMPRESS_SUPPORT
     86 #define Z_SUFFIX	".Z"
     87 #define Z_MAGIC		"\037\235"
     88 #endif
     89 
     90 #define GZ_SUFFIX	".gz"
     91 
     92 #define BUFLEN		(64 * 1024)
     93 
     94 #define GZIP_MAGIC0	0x1F
     95 #define GZIP_MAGIC1	0x8B
     96 #define GZIP_OMAGIC1	0x9E
     97 
     98 #define GZIP_TIMESTAMP	(off_t)4
     99 #define GZIP_ORIGNAME	(off_t)10
    100 
    101 #define HEAD_CRC	0x02
    102 #define EXTRA_FIELD	0x04
    103 #define ORIG_NAME	0x08
    104 #define COMMENT		0x10
    105 
    106 #define OS_CODE		3	/* Unix */
    107 
    108 static	const char	gzip_version[] = "NetBSD gzip 20040711";
    109 
    110 static	int	cflag;			/* stdout mode */
    111 static	int	dflag;			/* decompress mode */
    112 static	int	lflag;			/* list mode */
    113 static	int	numflag = 6;		/* gzip -1..-9 value */
    114 
    115 #ifndef SMALL
    116 static	int	fflag;			/* force mode */
    117 static	int	nflag;			/* don't save name/timestamp */
    118 static	int	Nflag;			/* don't restore name/timestamp */
    119 static	int	qflag;			/* quiet mode */
    120 static	int	rflag;			/* recursive mode */
    121 static	int	tflag;			/* test */
    122 static	char	*Sflag;
    123 static	int	vflag;			/* verbose mode */
    124 #else
    125 #define		qflag	0
    126 #endif
    127 
    128 static	int	exit_value = 0;		/* exit value */
    129 
    130 static	const char	*suffix;
    131 #define suffix_len	(strlen(suffix) + 1)	/* len + nul */
    132 static	char	*infile;		/* name of file coming in */
    133 
    134 static	void	maybe_err(const char *fmt, ...);
    135 static	void	maybe_errx(const char *fmt, ...);
    136 static	void	maybe_warn(const char *fmt, ...);
    137 static	void	maybe_warnx(const char *fmt, ...);
    138 static	enum filetype file_gettype(u_char *);
    139 static	int	check_outfile(const char *outfile, struct stat *sb);
    140 static	off_t	gz_compress(FILE *, int, off_t *, const char *, time_t);
    141 static	off_t	gz_uncompress(int, int, char *, size_t, off_t *, const char *);
    142 static	off_t	file_compress(char *, char *, size_t);
    143 static	off_t	file_uncompress(char *, char *, size_t);
    144 static	off_t	cat_fd(unsigned char *, size_t, off_t *, int fd);
    145 static	void	handle_pathname(char *);
    146 static	void	handle_file(char *, struct stat *);
    147 static	void	handle_stdin(void);
    148 static	void	handle_stdout(void);
    149 static	void	print_ratio(off_t, off_t, FILE *);
    150 static	void	print_list(int fd, off_t, const char *, time_t);
    151 static	void	usage(void);
    152 static	void	display_version(void);
    153 
    154 #ifndef SMALL
    155 static	void	prepend_gzip(char *, int *, char ***);
    156 static	void	handle_dir(char *, struct stat *);
    157 static	void	print_verbage(char *, char *, off_t, off_t);
    158 static	void	print_test(const char *, int);
    159 static	void	copymodes(const char *, struct stat *);
    160 #endif
    161 
    162 #ifndef NO_BZIP2_SUPPORT
    163 static	off_t	unbzip2(int, int, char *, size_t, off_t *);
    164 #endif
    165 
    166 #ifndef NO_COMPRESS_SUPPORT
    167 static	FILE 	*zopen(const char *, FILE *);
    168 static	off_t	zuncompress(FILE *, FILE *, char *, size_t, off_t *);
    169 #endif
    170 
    171 int main(int, char *p[]);
    172 
    173 #ifdef SMALL
    174 #define getopt_long(a,b,c,d,e) getopt(a,b,c)
    175 #else
    176 static const struct option longopts[] = {
    177 	{ "stdout",		no_argument,		0,	'c' },
    178 	{ "to-stdout",		no_argument,		0,	'c' },
    179 	{ "decompress",		no_argument,		0,	'd' },
    180 	{ "uncompress",		no_argument,		0,	'd' },
    181 	{ "force",		no_argument,		0,	'f' },
    182 	{ "help",		no_argument,		0,	'h' },
    183 	{ "list",		no_argument,		0,	'l' },
    184 	{ "no-name",		no_argument,		0,	'n' },
    185 	{ "name",		no_argument,		0,	'N' },
    186 	{ "quiet",		no_argument,		0,	'q' },
    187 	{ "recursive",		no_argument,		0,	'r' },
    188 	{ "suffix",		required_argument,	0,	'S' },
    189 	{ "test",		no_argument,		0,	't' },
    190 	{ "verbose",		no_argument,		0,	'v' },
    191 	{ "version",		no_argument,		0,	'V' },
    192 	{ "fast",		no_argument,		0,	'1' },
    193 	{ "best",		no_argument,		0,	'9' },
    194 #if 0
    195 	/*
    196 	 * This is what else GNU gzip implements.  --ascii isn't useful
    197 	 * on NetBSD, and I don't care to have a --license.
    198 	 */
    199 	{ "ascii",		no_argument,		0,	'a' },
    200 	{ "license",		no_argument,		0,	'L' },
    201 #endif
    202 	{ NULL,			no_argument,		0,	0 },
    203 };
    204 #endif
    205 
    206 int
    207 main(int argc, char **argv)
    208 {
    209 	const char *progname = getprogname();
    210 #ifndef SMALL
    211 	char *gzip;
    212 #endif
    213 	int ch;
    214 
    215 	/* XXX set up signals */
    216 
    217 	suffix = GZ_SUFFIX;;
    218 
    219 #ifndef SMALL
    220 	if ((gzip = getenv("GZIP")) != NULL)
    221 		prepend_gzip(gzip, &argc, &argv);
    222 #endif
    223 
    224 	/*
    225 	 * XXX
    226 	 * handle being called `gunzip', `zcat' and `gzcat'
    227 	 */
    228 	if (strcmp(progname, "gunzip") == 0)
    229 		dflag = 1;
    230 	else if (strcmp(progname, "zcat") == 0 ||
    231 		 strcmp(progname, "gzcat") == 0)
    232 		dflag = cflag = 1;
    233 
    234 #ifdef SMALL
    235 #define OPT_LIST "cdhHltV123456789"
    236 #else
    237 #define OPT_LIST "cdfhHlnNqrS:tvV123456789"
    238 #endif
    239 
    240 	while ((ch = getopt_long(argc, argv, OPT_LIST, longopts, NULL)) != -1)
    241 		switch (ch) {
    242 		case 'c':
    243 			cflag = 1;
    244 			break;
    245 		case 'd':
    246 			dflag = 1;
    247 			break;
    248 		case 'l':
    249 			lflag = 1;
    250 			dflag = 1;
    251 			break;
    252 		case 'V':
    253 			display_version();
    254 			/* NOTREACHED */
    255 		case '1': case '2': case '3':
    256 		case '4': case '5': case '6':
    257 		case '7': case '8': case '9':
    258 			numflag = ch - '0';
    259 			break;
    260 #ifndef SMALL
    261 		case 'f':
    262 			fflag = 1;
    263 			break;
    264 		case 'n':
    265 			nflag = 1;
    266 			Nflag = 0;
    267 			break;
    268 		case 'N':
    269 			nflag = 0;
    270 			Nflag = 1;
    271 			break;
    272 		case 'q':
    273 			qflag = 1;
    274 			break;
    275 		case 'r':
    276 			rflag = 1;
    277 			break;
    278 		case 'S':
    279 			Sflag = optarg;
    280 			break;
    281 		case 't':
    282 			cflag = 1;
    283 			tflag = 1;
    284 			dflag = 1;
    285 			break;
    286 		case 'v':
    287 			vflag = 1;
    288 			break;
    289 #endif
    290 		default:
    291 			usage();
    292 			/* NOTREACHED */
    293 		}
    294 	argv += optind;
    295 	argc -= optind;
    296 
    297 	if (argc == 0) {
    298 		if (dflag)	/* stdin mode */
    299 			handle_stdin();
    300 		else		/* stdout mode */
    301 			handle_stdout();
    302 	} else {
    303 		do {
    304 			handle_pathname(argv[0]);
    305 		} while (*++argv);
    306 	}
    307 #ifndef SMALL
    308 	if (qflag == 0 && lflag && argc > 1)
    309 		print_list(-1, 0, "(totals)", 0);
    310 #endif
    311 	exit(exit_value);
    312 }
    313 
    314 /* maybe print a warning */
    315 void
    316 maybe_warn(const char *fmt, ...)
    317 {
    318 	va_list ap;
    319 
    320 	if (qflag == 0) {
    321 		va_start(ap, fmt);
    322 		vwarn(fmt, ap);
    323 		va_end(ap);
    324 	}
    325 	if (exit_value == 0)
    326 		exit_value = 1;
    327 }
    328 
    329 /* ... without an errno. */
    330 void
    331 maybe_warnx(const char *fmt, ...)
    332 {
    333 	va_list ap;
    334 
    335 	if (qflag == 0) {
    336 		va_start(ap, fmt);
    337 		vwarnx(fmt, ap);
    338 		va_end(ap);
    339 	}
    340 	if (exit_value == 0)
    341 		exit_value = 1;
    342 }
    343 
    344 /* maybe print an error */
    345 void
    346 maybe_err(const char *fmt, ...)
    347 {
    348 	va_list ap;
    349 
    350 	if (qflag == 0) {
    351 		va_start(ap, fmt);
    352 		vwarn(fmt, ap);
    353 		va_end(ap);
    354 	}
    355 	exit(2);
    356 }
    357 
    358 /* ... without an errno. */
    359 void
    360 maybe_errx(const char *fmt, ...)
    361 {
    362 	va_list ap;
    363 
    364 	if (qflag == 0) {
    365 		va_start(ap, fmt);
    366 		vwarnx(fmt, ap);
    367 		va_end(ap);
    368 	}
    369 	exit(2);
    370 }
    371 
    372 #ifndef SMALL
    373 /* split up $GZIP and prepend it to the argument list */
    374 static void
    375 prepend_gzip(char *gzip, int *argc, char ***argv)
    376 {
    377 	char *s, **nargv, **ac;
    378 	int nenvarg = 0, i;
    379 
    380 	/* scan how many arguments there are */
    381 	for (s = gzip; *s; s++) {
    382 		if (*s == ' ' || *s == '\t')
    383 			continue;
    384 		nenvarg++;
    385 		for (; *s; s++)
    386 			if (*s == ' ' || *s == '\t')
    387 				break;
    388 		if (*s == 0x0)
    389 			break;
    390 	}
    391 	/* punt early */
    392 	if (nenvarg == 0)
    393 		return;
    394 
    395 	*argc += nenvarg;
    396 	ac = *argv;
    397 
    398 	nargv = (char **)malloc((*argc + 1) * sizeof(char *));
    399 	if (nargv == NULL)
    400 		maybe_err("malloc");
    401 
    402 	/* stash this away */
    403 	*argv = nargv;
    404 
    405 	/* copy the program name first */
    406 	i = 0;
    407 	nargv[i++] = *(ac++);
    408 
    409 	/* take a copy of $GZIP and add it to the array */
    410 	s = strdup(gzip);
    411 	if (s == NULL)
    412 		maybe_err("strdup");
    413 	for (; *s; s++) {
    414 		if (*s == ' ' || *s == '\t')
    415 			continue;
    416 		nargv[i++] = s;
    417 		for (; *s; s++)
    418 			if (*s == ' ' || *s == '\t') {
    419 				*s = 0;
    420 				break;
    421 			}
    422 	}
    423 
    424 	/* copy the original arguments and a NULL */
    425 	while (*ac)
    426 		nargv[i++] = *(ac++);
    427 	nargv[i] = NULL;
    428 }
    429 #endif
    430 
    431 /* compress input to output then close both files */
    432 static off_t
    433 gz_compress(FILE *in, int out, off_t *gsizep, const char *origname, time_t mtime)
    434 {
    435 	z_stream z;
    436 	char inbuf[BUFLEN], outbuf[BUFLEN];
    437 	off_t in_tot = 0, out_tot = 0;
    438 	ssize_t in_size;
    439 	char *str;
    440 	int i, error;
    441 	uLong crc;
    442 
    443 	i = asprintf(&str, "%c%c%c%c%c%c%c%c%c%c%s",
    444 		     GZIP_MAGIC0, GZIP_MAGIC1,
    445 		     Z_DEFLATED, origname ? ORIG_NAME : 0,
    446 		     (int)mtime & 0xff,
    447 		     (int)(mtime >> 8) & 0xff,
    448 		     (int)(mtime >> 16) & 0xff,
    449 		     (int)(mtime >> 24) & 0xff,
    450 		     0, OS_CODE, origname ? origname : "");
    451 	if (i == -1)
    452 		maybe_err("asprintf");
    453 	if (origname)
    454 		i++;
    455 	if (write(out, str, i) != i) {
    456 		maybe_warn("write");
    457 		in_tot = -1;
    458 		goto out;
    459 	}
    460 	free(str);
    461 
    462 	memset(&z, 0, sizeof z);
    463 	z.next_out = outbuf;
    464 	z.avail_out = sizeof outbuf;
    465 	z.zalloc = Z_NULL;
    466 	z.zfree = Z_NULL;
    467 	z.opaque = 0;
    468 
    469 	error = deflateInit2(&z, numflag, Z_DEFLATED,
    470 			     -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
    471 	if (error != Z_OK) {
    472 		maybe_warnx("deflateInit2 failed");
    473 		in_tot = -1;
    474 		goto out;
    475 	}
    476 
    477 	crc = crc32(0L, Z_NULL, 0);
    478 	for (;;) {
    479 		if (z.avail_out == 0) {
    480 			if (write(out, outbuf, sizeof outbuf) != sizeof outbuf) {
    481 				maybe_warn("write");
    482 				in_tot = -1;
    483 				goto out;
    484 			}
    485 
    486 			out_tot += sizeof outbuf;
    487 			z.next_out = outbuf;
    488 			z.avail_out = sizeof outbuf;
    489 		}
    490 
    491 		if (z.avail_in == 0) {
    492 			in_size = fread(inbuf, 1, sizeof inbuf, in);
    493 			if (ferror(in)) {
    494 				maybe_warn("fread");
    495 				in_tot = -1;
    496 				goto out;
    497 			}
    498 			if (in_size == 0)
    499 				break;
    500 
    501 			crc = crc32(crc, (const Bytef *)inbuf, (unsigned)in_size);
    502 			in_tot += in_size;
    503 			z.next_in = inbuf;
    504 			z.avail_in = in_size;
    505 		}
    506 
    507 		error = deflate(&z, Z_NO_FLUSH);
    508 		if (error != Z_OK && error != Z_STREAM_END) {
    509 			maybe_warnx("deflate failed");
    510 			in_tot = -1;
    511 			goto out;
    512 		}
    513 	}
    514 
    515 	/* clean up */
    516 	for (;;) {
    517 		size_t len;
    518 
    519 		error = deflate(&z, Z_FINISH);
    520 		if (error != Z_OK && error != Z_STREAM_END) {
    521 			maybe_warnx("deflate failed");
    522 			in_tot = -1;
    523 			goto out;
    524 		}
    525 
    526 		len = sizeof outbuf - z.avail_out;
    527 
    528 		if (write(out, outbuf, len) != len) {
    529 			maybe_warn("write");
    530 			out_tot = -1;
    531 			goto out;
    532 		}
    533 		out_tot += len;
    534 		z.next_out = outbuf;
    535 		z.avail_out = sizeof outbuf;
    536 
    537 		if (error == Z_STREAM_END)
    538 			break;
    539 	}
    540 
    541 	if (deflateEnd(&z) != Z_OK) {
    542 		maybe_warnx("deflateEnd failed");
    543 		in_tot = -1;
    544 		goto out;
    545 	}
    546 
    547 	i = asprintf(&str, "%c%c%c%c%c%c%c%c",
    548 		 (int)crc & 0xff,
    549 		 (int)(crc >> 8) & 0xff,
    550 		 (int)(crc >> 16) & 0xff,
    551 		 (int)(crc >> 24) & 0xff,
    552 		 (int)in_tot & 0xff,
    553 		 (int)(in_tot >> 8) & 0xff,
    554 		 (int)(in_tot >> 16) & 0xff,
    555 		 (int)(in_tot >> 24) & 0xff);
    556 	if (i != 8)
    557 		maybe_err("asprintf");
    558 	if (write(out, str, i) != i) {
    559 		maybe_warn("write");
    560 		in_tot = -1;
    561 	}
    562 	free(str);
    563 
    564 out:
    565 	if (gsizep)
    566 		*gsizep = out_tot;
    567 	return in_tot;
    568 }
    569 
    570 /*
    571  * uncompress input to output then close the input.  return the
    572  * uncompressed size written, and put the compressed sized read
    573  * into `*gsizep'.
    574  */
    575 static off_t
    576 gz_uncompress(int in, int out, char *pre, size_t prelen, off_t *gsizep,
    577 	      const char *filename)
    578 {
    579 	z_stream z;
    580 	char outbuf[BUFLEN], inbuf[BUFLEN];
    581 	off_t out_tot, out_sub_tot, in_tot;
    582 	enum {
    583 		GZSTATE_MAGIC0,
    584 		GZSTATE_MAGIC1,
    585 		GZSTATE_METHOD,
    586 		GZSTATE_FLAGS,
    587 		GZSTATE_SKIPPING,
    588 		GZSTATE_EXTRA,
    589 		GZSTATE_EXTRA2,
    590 		GZSTATE_EXTRA3,
    591 		GZSTATE_ORIGNAME,
    592 		GZSTATE_COMMENT,
    593 		GZSTATE_HEAD_CRC1,
    594 		GZSTATE_HEAD_CRC2,
    595 		GZSTATE_INIT,
    596 		GZSTATE_READ,
    597 		GZSTATE_CRC,
    598 		GZSTATE_LEN,
    599 	} state = GZSTATE_MAGIC0;
    600 	int flags = 0, skip_count = 0;
    601 	int error, done_reading = 0;
    602 	uLong crc;
    603 
    604 #define ADVANCE()       { z.next_in++; z.avail_in--; }
    605 
    606 	memset(&z, 0, sizeof z);
    607 	z.avail_in = prelen;
    608 	z.next_in = pre;
    609 	z.avail_out = sizeof outbuf;
    610 	z.next_out = outbuf;
    611 	z.zalloc = NULL;
    612 	z.zfree = NULL;
    613 	z.opaque = 0;
    614 
    615 	in_tot = prelen;
    616 	out_tot = 0;
    617 
    618 	for (;;) {
    619 		if (z.avail_in == 0 && done_reading == 0) {
    620 			size_t in_size = read(in, inbuf, BUFLEN);
    621 
    622 			if (in_size == -1) {
    623 #ifndef SMALL
    624 				if (tflag && vflag)
    625 					print_test(filename, 0);
    626 #endif
    627 				maybe_warn("failed to read stdin");
    628 				out_tot = -1;
    629 				goto stop;
    630 			} else if (in_size == 0)
    631 				done_reading = 1;
    632 
    633 			z.avail_in = in_size;
    634 			z.next_in = inbuf;
    635 
    636 			in_tot += in_size;
    637 		}
    638 		if (z.avail_in == 0) {
    639 			if (done_reading && state != GZSTATE_MAGIC0)
    640 				maybe_warnx("%s: unexpected end of file",
    641 					    filename);
    642 			goto stop;
    643 		}
    644 		switch (state) {
    645 		case GZSTATE_MAGIC0:
    646 			if (*z.next_in != GZIP_MAGIC0) {
    647 				maybe_warnx("input not gziped (MAGIC0)");
    648 				out_tot = -1;
    649 				goto stop;
    650 			}
    651 			ADVANCE();
    652 			state++;
    653 			out_sub_tot = 0;
    654 			crc = crc32(0L, Z_NULL, 0);
    655 			break;
    656 
    657 		case GZSTATE_MAGIC1:
    658 			if (*z.next_in != GZIP_MAGIC1 &&
    659 			    *z.next_in != GZIP_OMAGIC1) {
    660 				maybe_warnx("input not gziped (MAGIC1)");
    661 				out_tot = -1;
    662 				goto stop;
    663 			}
    664 			ADVANCE();
    665 			state++;
    666 			break;
    667 
    668 		case GZSTATE_METHOD:
    669 			if (*z.next_in != Z_DEFLATED) {
    670 				maybe_warnx("unknown compression method");
    671 				out_tot = -1;
    672 				goto stop;
    673 			}
    674 			ADVANCE();
    675 			state++;
    676 			break;
    677 
    678 		case GZSTATE_FLAGS:
    679 			flags = *z.next_in;
    680 			ADVANCE();
    681 			skip_count = 6;
    682 			state++;
    683 			break;
    684 
    685 		case GZSTATE_SKIPPING:
    686 			if (skip_count > 0) {
    687 				skip_count--;
    688 				ADVANCE();
    689 			} else
    690 				state++;
    691 			break;
    692 
    693 		case GZSTATE_EXTRA:
    694 			if ((flags & EXTRA_FIELD) == 0) {
    695 				state = GZSTATE_ORIGNAME;
    696 				break;
    697 			}
    698 			skip_count = *z.next_in;
    699 			ADVANCE();
    700 			state++;
    701 			break;
    702 
    703 		case GZSTATE_EXTRA2:
    704 			skip_count |= ((*z.next_in) << 8);
    705 			ADVANCE();
    706 			state++;
    707 			break;
    708 
    709 		case GZSTATE_EXTRA3:
    710 			if (skip_count > 0) {
    711 				skip_count--;
    712 				ADVANCE();
    713 			} else
    714 				state++;
    715 			break;
    716 
    717 		case GZSTATE_ORIGNAME:
    718 			if ((flags & ORIG_NAME) == 0) {
    719 				state++;
    720 				break;
    721 			}
    722 			if (*z.next_in == 0)
    723 				state++;
    724 			ADVANCE();
    725 			break;
    726 
    727 		case GZSTATE_COMMENT:
    728 			if ((flags & COMMENT) == 0) {
    729 				state++;
    730 				break;
    731 			}
    732 			if (*z.next_in == 0)
    733 				state++;
    734 			ADVANCE();
    735 			break;
    736 
    737 		case GZSTATE_HEAD_CRC1:
    738 			if (flags & HEAD_CRC)
    739 				skip_count = 2;
    740 			else
    741 				skip_count = 0;
    742 			state++;
    743 			break;
    744 
    745 		case GZSTATE_HEAD_CRC2:
    746 			if (skip_count > 0) {
    747 				skip_count--;
    748 				ADVANCE();
    749 			} else
    750 				state++;
    751 			break;
    752 
    753 		case GZSTATE_INIT:
    754 			if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
    755 				maybe_warnx("failed to inflateInit");
    756 				out_tot = -1;
    757 				goto stop;
    758 			}
    759 			state++;
    760 			break;
    761 
    762 		case GZSTATE_READ:
    763 			error = inflate(&z, Z_FINISH);
    764 			/* Z_BUF_ERROR goes with Z_FINISH... */
    765 			if (error == Z_STREAM_END || error == Z_BUF_ERROR) {
    766 				size_t wr = BUFLEN - z.avail_out;
    767 
    768 				/* Nothing left? */
    769 				if (wr == 0)
    770 					goto stop;
    771 
    772 				crc = crc32(crc, outbuf, wr);
    773 
    774 				if (
    775 #ifndef SMALL
    776 				    /* don't write anything with -t */
    777 				    tflag == 0 &&
    778 #endif
    779 				    write(out, outbuf, wr) != wr) {
    780 					maybe_warn("error writing to output");
    781 					out_tot = -1;
    782 					goto stop;
    783 				}
    784 
    785 				out_tot += wr;
    786 				out_sub_tot += wr;
    787 
    788 				if (error == Z_STREAM_END) {
    789 					inflateEnd(&z);
    790 					state++;
    791 				}
    792 
    793 				z.next_out = outbuf;
    794 				z.avail_out = BUFLEN;
    795 
    796 				break;
    797 			}
    798 		case GZSTATE_CRC:
    799 			{
    800 				static int empty_buffer = 0;
    801 				uLong origcrc;
    802 
    803 				if (z.avail_in < 4) {
    804 					if (!done_reading && empty_buffer++ < 4)
    805 						continue;
    806 					maybe_warnx("truncated input");
    807 					goto stop;
    808 				}
    809 				empty_buffer = 0;
    810 				origcrc = z.next_in[0] |
    811 					z.next_in[1] << 8 |
    812 					z.next_in[2] << 16 |
    813 					z.next_in[3] << 24;
    814 				if (origcrc != crc)
    815 					maybe_warnx("invalid compressed"
    816 					     " data--crc error");
    817 			}
    818 
    819 			z.avail_in -= 4;
    820 			z.next_in += 4;
    821 
    822 			if (!z.avail_in)
    823 				goto stop;
    824 			state++;
    825 			break;
    826 		case GZSTATE_LEN:
    827 			{
    828 				static int empty_buffer = 0;
    829 				uLong origlen;
    830 
    831 				if (z.avail_in < 4) {
    832 					if (!done_reading && empty_buffer++ < 4)
    833 						continue;
    834 					maybe_warnx("truncated input");
    835 					goto stop;
    836 				}
    837 				empty_buffer = 0;
    838 				origlen = z.next_in[0] |
    839 					z.next_in[1] << 8 |
    840 					z.next_in[2] << 16 |
    841 					z.next_in[3] << 24;
    842 
    843 				if (origlen != out_sub_tot)
    844 					maybe_warnx("invalid compressed"
    845 					     " data--length error");
    846 			}
    847 
    848 			z.avail_in -= 4;
    849 			z.next_in += 4;
    850 
    851 			if (error < 0) {
    852 				maybe_warnx("decompression error");
    853 				out_tot = -1;
    854 				goto stop;
    855 			}
    856 			state = GZSTATE_MAGIC0;
    857 			break;
    858 		}
    859 		continue;
    860 stop:
    861 		break;
    862 	}
    863 	if (state > GZSTATE_INIT)
    864 		inflateEnd(&z);
    865 
    866 #ifndef SMALL
    867 	if (tflag && vflag)
    868 		print_test(filename, out_tot != -1);
    869 #endif
    870 
    871 	if (gsizep)
    872 		*gsizep = in_tot;
    873 	return (out_tot);
    874 }
    875 
    876 #ifndef SMALL
    877 /*
    878  * set the owner, mode, flags & utimes for a file
    879  */
    880 static void
    881 copymodes(const char *file, struct stat *sbp)
    882 {
    883 	struct timeval times[2];
    884 
    885 	/*
    886 	 * If we have no info on the input, give this file some
    887 	 * default values and return..
    888 	 */
    889 	if (sbp == NULL) {
    890 		mode_t mask = umask(022);
    891 
    892 		(void)chmod(file, DEFFILEMODE & ~mask);
    893 		(void)umask(mask);
    894 		return;
    895 	}
    896 
    897 	/* if the chown fails, remove set-id bits as-per compress(1) */
    898 	if (chown(file, sbp->st_uid, sbp->st_gid) < 0) {
    899 		if (errno != EPERM)
    900 			maybe_warn("couldn't chown: %s", file);
    901 		sbp->st_mode &= ~(S_ISUID|S_ISGID);
    902 	}
    903 
    904 	/* we only allow set-id and the 9 normal permission bits */
    905 	sbp->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
    906 	if (chmod(file, sbp->st_mode) < 0)
    907 		maybe_warn("couldn't chmod: %s", file);
    908 
    909 	/* only try flags if they exist already */
    910         if (sbp->st_flags != 0 && chflags(file, sbp->st_flags) < 0)
    911 		maybe_warn("couldn't chflags: %s", file);
    912 
    913 	TIMESPEC_TO_TIMEVAL(&times[0], &sbp->st_atimespec);
    914 	TIMESPEC_TO_TIMEVAL(&times[1], &sbp->st_mtimespec);
    915 	if (utimes(file, times) < 0)
    916 		maybe_warn("couldn't utimes: %s", file);
    917 }
    918 #endif
    919 
    920 /* what sort of file is this? */
    921 static enum filetype
    922 file_gettype(u_char *buf)
    923 {
    924 
    925 	if (buf[0] == GZIP_MAGIC0 &&
    926 	    (buf[1] == GZIP_MAGIC1 || buf[1] == GZIP_OMAGIC1))
    927 		return FT_GZIP;
    928 	else
    929 #ifndef NO_BZIP2_SUPPORT
    930 	if (memcmp(buf, BZIP2_MAGIC, 3) == 0 &&
    931 	    buf[3] >= '0' && buf[3] <= '9')
    932 		return FT_BZIP2;
    933 	else
    934 #endif
    935 #ifndef NO_COMPRESS_SUPPORT
    936 	if (memcmp(buf, Z_MAGIC, 2) == 0)
    937 		return FT_Z;
    938 	else
    939 #endif
    940 		return FT_UNKNOWN;
    941 }
    942 
    943 #ifndef SMALL
    944 /* check the outfile is OK. */
    945 static int
    946 check_outfile(const char *outfile, struct stat *sb)
    947 {
    948 	int ok = 1;
    949 
    950 	if (fflag == 0 && lflag == 0 && stat(outfile, sb) == 0) {
    951 		if (isatty(STDIN_FILENO)) {
    952 			char ans[10] = { 'n', '\0' };	/* default */
    953 
    954 			fprintf(stderr, "%s already exists -- do you wish to "
    955 					"overwrite (y or n)? " , outfile);
    956 			(void)fgets(ans, sizeof(ans) - 1, stdin);
    957 			if (ans[0] != 'y' && ans[0] != 'Y') {
    958 				fprintf(stderr, "\tnot overwritting\n");
    959 				ok = 0;
    960 			} else
    961 				unlink(outfile);
    962 		} else {
    963 			maybe_warnx("%s already exists -- skipping", outfile);
    964 			ok = 0;
    965 		}
    966 	}
    967 	return ok;
    968 }
    969 #endif
    970 
    971 /*
    972  * compress the given file: create a corresponding .gz file and remove the
    973  * original.
    974  */
    975 static off_t
    976 file_compress(char *file, char *outfile, size_t outsize)
    977 {
    978 	FILE *in;
    979 	int out;
    980 	struct stat isb, osb;
    981 	off_t size;
    982 #ifndef SMALL
    983 	u_int32_t mtime = 0;
    984 	char *savename;
    985 #endif
    986 
    987 	if (cflag == 0) {
    988 		(void)strncpy(outfile, file, outsize - suffix_len);
    989 		outfile[outsize - suffix_len] = '\0';
    990 		(void)strlcat(outfile, suffix, outsize);
    991 
    992 #ifndef SMALL
    993 		if (check_outfile(outfile, &osb) == 0)
    994 			goto lose;
    995 
    996 		if (stat(file, &isb) == 0) {
    997 			if (isb.st_nlink > 1 && fflag == 0) {
    998 				maybe_warnx("%s has %d other link%s -- "
    999 					    "skipping", file, isb.st_nlink - 1,
   1000 					    isb.st_nlink == 1 ? "" : "s");
   1001 				goto lose;
   1002 			}
   1003 			if (nflag == 0)
   1004 				mtime = (u_int32_t)isb.st_mtime;
   1005 		}
   1006 #endif
   1007 	}
   1008 	in = fopen(file, "r");
   1009 	if (in == NULL) {
   1010 		maybe_warn("can't fopen %s", file);
   1011 		goto lose;
   1012 	}
   1013 
   1014 #ifndef SMALL
   1015 	if (nflag == 0)
   1016 		savename = basename(file);
   1017 	else
   1018 		savename = NULL;
   1019 #endif
   1020 	if (cflag == 0) {
   1021 		out = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
   1022 		if (out == -1) {
   1023 			maybe_warn("could not create output: %s", outfile);
   1024 			goto lose;
   1025 		}
   1026 	} else
   1027 		out = STDOUT_FILENO;
   1028 
   1029 #ifdef SMALL
   1030 	gz_compress(in, out, NULL, NULL, 0);
   1031 #else
   1032 	gz_compress(in, out, NULL, savename, mtime);
   1033 #endif
   1034 
   1035 	(void)fclose(in);
   1036 
   1037 	/*
   1038 	 * if we compressed to stdout, we don't know the size and
   1039 	 * we don't know the new file name, punt.  if we can't stat
   1040 	 * the file, whine, otherwise set the size from the stat
   1041 	 * buffer.  we only blow away the file if we can stat the
   1042 	 * output, just in case.
   1043 	 */
   1044 	if (cflag == 0) {
   1045 		if (close(out) == -1)
   1046 			maybe_warn("couldn't close ouput");
   1047 
   1048 		if (stat(outfile, &osb) < 0) {
   1049 			maybe_warn("couldn't stat: %s", outfile);
   1050 			maybe_warnx("leaving original %s", file);
   1051 			size = 0;
   1052 		} else {
   1053 			unlink(file);
   1054 			size = osb.st_size;
   1055 		}
   1056 #ifndef SMALL
   1057 		copymodes(outfile, &isb);
   1058 #endif
   1059 	} else {
   1060 lose:
   1061 		size = -1;
   1062 	}
   1063 
   1064 	return (size);
   1065 }
   1066 
   1067 /* uncompress the given file and remove the original */
   1068 static off_t
   1069 file_uncompress(char *file, char *outfile, size_t outsize)
   1070 {
   1071 	struct stat isb, osb;
   1072 	char *s;
   1073 	off_t size;
   1074 	ssize_t len = strlen(file);
   1075 	unsigned char header1[4], name[PATH_MAX + 1];
   1076 	enum filetype method;
   1077 	int fd, zfd;
   1078 #ifndef SMALL
   1079 	time_t timestamp = 0;
   1080 #endif
   1081 
   1082 	/* gather the old name info */
   1083 
   1084 	fd = open(file, O_RDONLY);
   1085 	if (fd < 0) {
   1086 		maybe_warn("can't open %s", file);
   1087 		goto lose;
   1088 	}
   1089 	if (read(fd, header1, sizeof header1) != sizeof header1) {
   1090 		/* we don't want to fail here. */
   1091 #ifndef SMALL
   1092 		if (fflag)
   1093 			goto lose_close_it;
   1094 #endif
   1095 		maybe_warn("can't read %s", file);
   1096 		goto lose;
   1097 	}
   1098 
   1099 	method = file_gettype(header1);
   1100 
   1101 #ifndef SMALL
   1102 	if (Sflag == NULL) {
   1103 # ifndef NO_BZIP2_SUPPORT
   1104 		if (method == FT_BZIP2)
   1105 			suffix = BZ2_SUFFIX;
   1106 		else
   1107 # endif
   1108 # ifndef NO_COMPRESS_SUPPORT
   1109 		if (method == FT_Z)
   1110 			suffix = Z_SUFFIX;
   1111 # endif
   1112 	}
   1113 
   1114 	if (fflag == 0 && method == FT_UNKNOWN) {
   1115 		maybe_warnx("%s: not in gzip format", file);
   1116 		goto lose_close_it;
   1117 	}
   1118 #endif
   1119 
   1120 	if (cflag == 0 || lflag) {
   1121 		s = &file[len - suffix_len + 1];
   1122 		if (strncmp(s, suffix, suffix_len) == 0) {
   1123 			(void)strncpy(outfile, file, len - suffix_len + 1);
   1124 			outfile[len - suffix_len + 1] = '\0';
   1125 		} else if (lflag == 0) {
   1126 			maybe_warnx("unknown suffix %s", s);
   1127 			goto lose_close_it;
   1128 		}
   1129 	}
   1130 
   1131 #ifdef SMALL
   1132 	if (method == FT_GZIP && lflag)
   1133 #else
   1134 	if (method == FT_GZIP && (Nflag || lflag))
   1135 #endif
   1136 	{
   1137 #ifndef SMALL
   1138 		unsigned char header2[4];	/* timestamp */
   1139 
   1140 		if (lseek(fd, GZIP_TIMESTAMP, SEEK_SET) == -1) {
   1141 			maybe_warn("can't lseek %s", file);
   1142 			goto close_header_read;
   1143 		}
   1144 		if (read(fd, header2, sizeof header2) != sizeof header2) {
   1145 			if (fflag)
   1146 				goto lose_close_it;
   1147 			maybe_warn("can't read %s", file);
   1148 			goto lose;
   1149                }
   1150                timestamp = ((time_t)header2[3] << 24)
   1151                          + ((time_t)header2[2] << 16)
   1152                          + ((time_t)header2[1] << 8)
   1153                          +  (time_t)header2[0];
   1154 #endif
   1155 
   1156 		if (header1[3] & ORIG_NAME) {
   1157 			size_t rbytes;
   1158 			int i;
   1159 
   1160 			if (lseek(fd, GZIP_ORIGNAME, SEEK_SET) == -1) {
   1161 				maybe_warn("can't lseek %s", file);
   1162 				goto close_header_read;
   1163 			}
   1164 			rbytes = read(fd, name, PATH_MAX + 1);
   1165 			if (rbytes < 0) {
   1166 				maybe_warn("can't read %s", file);
   1167 				goto lose_close_it;
   1168 			}
   1169 			for (i = 0; i < rbytes && name[i]; i++)
   1170 				;
   1171 			if (i < rbytes) {
   1172 				name[i] = 0;
   1173 				/* now maybe merge old dirname */
   1174 				if (strchr(outfile, '/') == NULL)
   1175 					(void) strlcpy(outfile, name, outsize);
   1176 				else {
   1177 					char	newbuf[PATH_MAX + 1];
   1178 
   1179 					(void) snprintf(newbuf, sizeof(newbuf),
   1180 						"%s/%s", dirname(outfile),
   1181 						name);
   1182 					(void) strlcpy(outfile, newbuf,
   1183 						outsize);
   1184 				}
   1185 			}
   1186 		}
   1187 	}
   1188 close_header_read:
   1189 	close(fd);
   1190 
   1191 	if (cflag == 0 || lflag) {
   1192 #ifndef SMALL
   1193 		if (check_outfile(outfile, &osb) == 0)
   1194 			goto lose;
   1195 
   1196 #endif
   1197 		if (stat(file, &isb) == 0) {
   1198 #ifndef SMALL
   1199 			if (isb.st_nlink > 1 && lflag == 0 && fflag == 0) {
   1200 				maybe_warnx("%s has %d other links -- skipping",
   1201 				    file, isb.st_nlink - 1);
   1202 				goto lose;
   1203 			}
   1204 			if (nflag == 0 && timestamp)
   1205 				isb.st_mtime = timestamp;
   1206 #endif
   1207 		} else
   1208 			goto lose;
   1209 	}
   1210 
   1211 	if (cflag == 0 && lflag == 0) {
   1212 		zfd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
   1213 		if (zfd == -1) {
   1214 			maybe_warn("can't open %s", outfile);
   1215 			goto lose;
   1216 		}
   1217 	} else
   1218 		zfd = STDOUT_FILENO;
   1219 
   1220 #ifndef NO_BZIP2_SUPPORT
   1221 	if (method == FT_BZIP2) {
   1222 		int in;
   1223 
   1224 		/* XXX */
   1225 		if (lflag) {
   1226 			maybe_warnx("no -l with bzip2 files");
   1227 			goto lose;
   1228 		}
   1229 
   1230 		if ((in = open(file, O_RDONLY)) == -1) {
   1231 			maybe_warn("open for read: %s", file);
   1232 			goto lose;
   1233 		}
   1234 
   1235 		size = unbzip2(in, zfd, NULL, 0, NULL);
   1236 		if (size == -1) {
   1237 			if (cflag == 0)
   1238 				unlink(outfile);
   1239 			maybe_warnx("%s: uncompress failed", file);
   1240 			goto lose;
   1241 		}
   1242 		if (close(in) != 0)
   1243 			maybe_warn("couldn't close input");
   1244 		if (cflag == 0 && close(zfd) != 0)
   1245 			maybe_warn("couldn't close output");
   1246 	} else
   1247 #endif
   1248 
   1249 #ifndef NO_COMPRESS_SUPPORT
   1250 	if (method == FT_Z) {
   1251 		FILE *in, *out;
   1252 
   1253 		/* XXX */
   1254 		if (lflag) {
   1255 			maybe_warnx("no -l with Lempel-Ziv files");
   1256 			goto lose;
   1257 		}
   1258 
   1259 		if ((in = zopen(file, NULL)) == NULL) {
   1260 			maybe_warn("open for read: %s", file);
   1261 			goto lose;
   1262 		}
   1263 
   1264 		out = fdopen(zfd, "w");
   1265 		if (out == NULL) {
   1266 			maybe_warn("open for write: %s", outfile);
   1267 			goto lose;
   1268 		}
   1269 
   1270 		size = zuncompress(in, out, NULL, 0, NULL);
   1271 		if (ferror(in) || fclose(in) != 0) {
   1272 			unlink(outfile);
   1273 			(void)fclose(out);
   1274 			maybe_warn("failed infile fclose");
   1275 		}
   1276 		if (cflag == 0) {
   1277 			if (size == -1) {
   1278 				maybe_warnx("%s: uncompress failed", file);
   1279 				(void)fclose(out);
   1280 				unlink(outfile);
   1281 				goto lose;
   1282 			}
   1283 			if (fclose(out) != 0) {
   1284 				unlink(outfile);
   1285 				maybe_warn("failed outfile close");
   1286 				goto lose;
   1287 			}
   1288 		}
   1289 	} else
   1290 #endif
   1291 
   1292 #ifndef SMALL
   1293 	if (method == FT_UNKNOWN) {
   1294 		int in;
   1295 
   1296 		in = open(file, O_RDONLY);
   1297 		if (in == -1) {
   1298 			maybe_warn("can't open %s", file);
   1299 			goto lose;
   1300 		}
   1301 		size = cat_fd(NULL, 0, NULL, in);
   1302 	} else
   1303 #endif
   1304 	{
   1305 		int in;
   1306 
   1307 		if (lflag) {
   1308 			if ((zfd = open(file, O_RDONLY)) == -1) {
   1309 				maybe_warn("open: %s", file);
   1310 				goto lose;
   1311 			}
   1312 			print_list(zfd, isb.st_size, outfile, isb.st_mtime);
   1313 			return 0;	/* XXX */
   1314 		}
   1315 
   1316 		in = open(file, O_RDONLY);
   1317 		if (in == -1) {
   1318 			maybe_warn("can't open %s", file);
   1319 			goto lose;
   1320 		}
   1321 
   1322 		size = gz_uncompress(in, zfd, NULL, 0, NULL, file);
   1323 		(void)close(in);
   1324 		if (cflag == 0) {
   1325 			if (close(zfd))
   1326 				maybe_warn("failed close");
   1327 			if (size == -1) {
   1328 				maybe_warnx("%s: uncompress failed", file);
   1329 				unlink(outfile);
   1330 				goto lose;
   1331 			}
   1332 		}
   1333 	}
   1334 
   1335 	/* if testing, or we uncompressed to stdout, this is all we need */
   1336 #ifndef SMALL
   1337 	if (tflag)
   1338 		return (size);
   1339 #endif
   1340 	if (cflag)
   1341 		return (size);
   1342 
   1343 	/*
   1344 	 * if we create a file...
   1345 	 */
   1346 	if (cflag == 0) {
   1347 		/*
   1348 		 * if we can't stat the file, or we are uncompressing to
   1349 		 * stdin, don't remove the file.
   1350 		 */
   1351 		if (stat(outfile, &osb) < 0) {
   1352 			maybe_warn("couldn't stat (leaving original): %s",
   1353 				   outfile);
   1354 			goto lose;
   1355 		}
   1356 		if (osb.st_size != size) {
   1357 			maybe_warn("stat gave different size: %llu != %llu "
   1358 			    "(leaving original)",
   1359 			    (unsigned long long)size,
   1360 			    (unsigned long long)osb.st_size);
   1361 			goto lose;
   1362 		}
   1363 		if (cflag == 0)
   1364 			unlink(file);
   1365 		size = osb.st_size;
   1366 #ifndef SMALL
   1367 		copymodes(outfile, &isb);
   1368 #endif
   1369 	}
   1370 	return (size);
   1371 
   1372 lose_close_it:
   1373 	close(fd);
   1374 lose:
   1375 	return -1;
   1376 }
   1377 
   1378 #ifndef SMALL
   1379 static off_t
   1380 cat_fd(unsigned char * prepend, size_t count, off_t *gsizep, int fd)
   1381 {
   1382 	char buf[BUFLEN];
   1383 	size_t rv;
   1384 	off_t in_tot;
   1385 
   1386 	in_tot = count;
   1387 	if (write(STDOUT_FILENO, prepend, count) != count) {
   1388 		maybe_warn("write to stdout");
   1389 		return -1;
   1390 	}
   1391 	for (;;) {
   1392 		rv = read(fd, buf, sizeof buf);
   1393 		if (rv == 0)
   1394 			break;
   1395 		if (rv < 0) {
   1396 			maybe_warn("read from fd %d", fd);
   1397 			break;
   1398 		}
   1399 
   1400 		if (write(STDOUT_FILENO, buf, rv) != rv) {
   1401 			maybe_warn("write to stdout");
   1402 			break;
   1403 		}
   1404 		in_tot += rv;
   1405 	}
   1406 
   1407 	if (gsizep)
   1408 		*gsizep = in_tot;
   1409 	return (in_tot);
   1410 }
   1411 #endif
   1412 
   1413 static void
   1414 handle_stdin(void)
   1415 {
   1416 	unsigned char header1[4];
   1417 	off_t usize, gsize;
   1418 	enum filetype method;
   1419 #ifndef NO_COMPRESS_SUPPORT
   1420 	FILE *in;
   1421 #endif
   1422 
   1423 #ifndef SMALL
   1424 	if (fflag == 0 && lflag == 0 && isatty(STDIN_FILENO)) {
   1425 		maybe_warnx("standard input is a terminal -- ignoring");
   1426 		return;
   1427 	}
   1428 #endif
   1429 
   1430 	if (lflag) {
   1431 		struct stat isb;
   1432 
   1433 		/* XXX could read the whole file, etc. */
   1434 		if (fstat(STDIN_FILENO, &isb) < 0) {
   1435 			maybe_warn("fstat");
   1436 			return;
   1437 		}
   1438 		print_list(STDIN_FILENO, isb.st_size, "stdout", isb.st_mtime);
   1439 		return;
   1440 	}
   1441 
   1442 	if (read(STDIN_FILENO, header1, sizeof header1) != sizeof header1) {
   1443 		maybe_warn("can't read stdin");
   1444 		return;
   1445 	}
   1446 
   1447 	method = file_gettype(header1);
   1448 	switch (method) {
   1449 	default:
   1450 #ifndef SMALL
   1451 		if (fflag == 0) {
   1452 			maybe_warnx("unknown compression format");
   1453 			return;
   1454 		}
   1455 		usize = cat_fd(header1, sizeof header1, &gsize, STDIN_FILENO);
   1456 		break;
   1457 #endif
   1458 	case FT_GZIP:
   1459 		usize = gz_uncompress(STDIN_FILENO, STDOUT_FILENO,
   1460 			      header1, sizeof header1, &gsize, "(stdin)");
   1461 		break;
   1462 #ifndef NO_BZIP2_SUPPORT
   1463 	case FT_BZIP2:
   1464 		usize = unbzip2(STDIN_FILENO, STDOUT_FILENO,
   1465 				header1, sizeof header1, &gsize);
   1466 		break;
   1467 #endif
   1468 #ifndef NO_COMPRESS_SUPPORT
   1469 	case FT_Z:
   1470 		if ((in = zopen(NULL, stdin)) == NULL) {
   1471 			maybe_warnx("zopen of stdin");
   1472 			return;
   1473 		}
   1474 
   1475 		usize = zuncompress(in, stdout, header1, sizeof header1, &gsize);
   1476 		break;
   1477 #endif
   1478 	}
   1479 
   1480 #ifndef SMALL
   1481         if (vflag && !tflag && usize != -1 && gsize != -1)
   1482 		print_verbage(NULL, 0, usize, gsize);
   1483 #endif
   1484 
   1485 }
   1486 
   1487 static void
   1488 handle_stdout(void)
   1489 {
   1490 	off_t gsize, usize;
   1491 
   1492 #ifndef SMALL
   1493 	if (fflag == 0 && isatty(STDOUT_FILENO)) {
   1494 		maybe_warnx("standard output is a terminal -- ignoring");
   1495 		return;
   1496 	}
   1497 #endif
   1498 	usize = gz_compress(stdin, STDOUT_FILENO, &gsize, NULL, 0);
   1499 
   1500 #ifndef SMALL
   1501         if (vflag && !tflag && usize != -1 && gsize != -1)
   1502 		print_verbage(NULL, 0, usize, gsize);
   1503 #endif
   1504 }
   1505 
   1506 /* do what is asked for, for the path name */
   1507 static void
   1508 handle_pathname(char *path)
   1509 {
   1510 	char *opath = path, *s = NULL;
   1511 	ssize_t len;
   1512 	struct stat sb;
   1513 
   1514 	/* check for stdout/stdin */
   1515 	if (path[0] == '-' && path[1] == '\0') {
   1516 		if (dflag)
   1517 			handle_stdin();
   1518 		else
   1519 			handle_stdout();
   1520 		return;
   1521 	}
   1522 
   1523 retry:
   1524 	if (stat(path, &sb) < 0) {
   1525 		/* lets try <path>.gz if we're decompressing */
   1526 		if (dflag && s == NULL && errno == ENOENT) {
   1527 			len = strlen(path);
   1528 			s = malloc(len + suffix_len + 1);
   1529 			if (s == NULL)
   1530 				maybe_err("malloc");
   1531 			memmove(s, path, len);
   1532 			memmove(&s[len], suffix, suffix_len);
   1533 			s[len + suffix_len] = 0x0;
   1534 			path = s;
   1535 			goto retry;
   1536 		}
   1537 		maybe_warn("can't stat: %s", opath);
   1538 		goto out;
   1539 	}
   1540 
   1541 	if (S_ISDIR(sb.st_mode)) {
   1542 #ifndef SMALL
   1543 		if (rflag)
   1544 			handle_dir(path, &sb);
   1545 		else
   1546 #endif
   1547 			maybe_warn("%s is a directory", path);
   1548 		goto out;
   1549 	}
   1550 
   1551 	if (S_ISREG(sb.st_mode))
   1552 		handle_file(path, &sb);
   1553 
   1554 out:
   1555 	if (s)
   1556 		free(s);
   1557 }
   1558 
   1559 /* compress/decompress a file */
   1560 static void
   1561 handle_file(char *file, struct stat *sbp)
   1562 {
   1563 	off_t usize, gsize;
   1564 	char	outfile[PATH_MAX];
   1565 
   1566 	infile = file;
   1567 	if (dflag) {
   1568 		usize = file_uncompress(file, outfile, sizeof(outfile));
   1569 		if (usize == -1)
   1570 			return;
   1571 		gsize = sbp->st_size;
   1572 	} else {
   1573 		gsize = file_compress(file, outfile, sizeof(outfile));
   1574 		if (gsize == -1)
   1575 			return;
   1576 		usize = sbp->st_size;
   1577 	}
   1578 
   1579 
   1580 #ifndef SMALL
   1581 	if (vflag && !tflag)
   1582 		print_verbage(file, (cflag) ? NULL : outfile, usize, gsize);
   1583 #endif
   1584 }
   1585 
   1586 #ifndef SMALL
   1587 /* this is used with -r to recursively decend directories */
   1588 static void
   1589 handle_dir(char *dir, struct stat *sbp)
   1590 {
   1591 	char *path_argv[2];
   1592 	FTS *fts;
   1593 	FTSENT *entry;
   1594 
   1595 	path_argv[0] = dir;
   1596 	path_argv[1] = 0;
   1597 	fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
   1598 	if (fts == NULL) {
   1599 		warn("couldn't fts_open %s", dir);
   1600 		return;
   1601 	}
   1602 
   1603 	while ((entry = fts_read(fts))) {
   1604 		switch(entry->fts_info) {
   1605 		case FTS_D:
   1606 		case FTS_DP:
   1607 			continue;
   1608 
   1609 		case FTS_DNR:
   1610 		case FTS_ERR:
   1611 		case FTS_NS:
   1612 			maybe_warn("%s", entry->fts_path);
   1613 			continue;
   1614 		case FTS_F:
   1615 			handle_file(entry->fts_name, entry->fts_statp);
   1616 		}
   1617 	}
   1618 	(void)fts_close(fts);
   1619 }
   1620 #endif
   1621 
   1622 /* print a ratio */
   1623 static void
   1624 print_ratio(off_t in, off_t out, FILE *where)
   1625 {
   1626 	int64_t percent10;	/* 10 * percent */
   1627 	off_t diff = in - out;
   1628 	char ch;
   1629 
   1630 	if (in == 0)
   1631 		percent10 = 0;
   1632 	else if (diff > 0x400000) 	/* anything with 22 or more bits */
   1633 		percent10 = diff / (in / 1000);
   1634 	else
   1635 		percent10 = (1000 * diff) / in;
   1636 
   1637 	if (percent10 < 0) {
   1638 		percent10 = -percent10;
   1639 		ch = '-';
   1640 	} else
   1641 		ch = ' ';
   1642 
   1643 	/*
   1644 	 * ugh.  for negative percentages < 10, we need to avoid printing a
   1645 	 * a space between the "-" and the single number.
   1646 	 */
   1647 	if (ch == '-' && percent10 / 10LL < 10)
   1648 		fprintf(where, " -%1d.%1u%%", (unsigned)(percent10 / 10LL),
   1649 					      (unsigned)(percent10 % 10LL));
   1650 	else
   1651 		fprintf(where, "%c%2d.%1u%%", ch, (unsigned)(percent10 / 10LL),
   1652 					          (unsigned)(percent10 % 10LL));
   1653 }
   1654 
   1655 #ifndef SMALL
   1656 /* print compression statistics, and the new name (if there is one!) */
   1657 static void
   1658 print_verbage(char *file, char *nfile, off_t usize, off_t gsize)
   1659 {
   1660 	if (file)
   1661 		fprintf(stderr, "%s:%s  ", file,
   1662 		    strlen(file) < 7 ? "\t\t" : "\t");
   1663 	print_ratio((off_t)usize, (off_t)gsize, stderr);
   1664 	if (nfile)
   1665 		fprintf(stderr, " -- replaced with %s", nfile);
   1666 	fprintf(stderr, "\n");
   1667 	fflush(stderr);
   1668 }
   1669 
   1670 /* print test results */
   1671 static void
   1672 print_test(const char *file, int ok)
   1673 {
   1674 
   1675 	if (exit_value == 0 && ok == 0)
   1676 		exit_value = 1;
   1677 	fprintf(stderr, "%s:%s  %s\n", file,
   1678 	    strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK");
   1679 	fflush(stderr);
   1680 }
   1681 #endif
   1682 
   1683 /* print a file's info ala --list */
   1684 /* eg:
   1685   compressed uncompressed  ratio uncompressed_name
   1686       354841      1679360  78.8% /usr/pkgsrc/distfiles/libglade-2.0.1.tar
   1687 */
   1688 static void
   1689 print_list(int fd, off_t out, const char *outfile, time_t ts)
   1690 {
   1691 	static int first = 1;
   1692 #ifndef SMALL
   1693 	static off_t in_tot, out_tot;
   1694 	u_int32_t crc;
   1695 #endif
   1696 	off_t in;
   1697 	int rv;
   1698 
   1699 	if (first) {
   1700 #ifndef SMALL
   1701 		if (vflag)
   1702 			printf("method  crc     date  time  ");
   1703 #endif
   1704 		if (qflag == 0)
   1705 			printf("  compressed uncompressed  "
   1706 			       "ratio uncompressed_name\n");
   1707 	}
   1708 	first = 0;
   1709 
   1710 	/* print totals? */
   1711 #ifndef SMALL
   1712 	if (fd == -1) {
   1713 		in = in_tot;
   1714 		out = out_tot;
   1715 	} else
   1716 #endif
   1717 	{
   1718 		/* read the last 4 bytes - this is the uncompressed size */
   1719 		rv = lseek(fd, (off_t)(-8), SEEK_END);
   1720 		if (rv != -1) {
   1721 			unsigned char buf[8];
   1722 			u_int32_t usize;
   1723 
   1724 			if (read(fd, (char *)buf, sizeof(buf)) != sizeof(buf))
   1725 				maybe_warn("read of uncompressed size");
   1726 			usize = buf[4] | buf[5] << 8 | buf[6] << 16 | buf[7] << 24;
   1727 			in = (off_t)usize;
   1728 #ifndef SMALL
   1729 			crc = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
   1730 #endif
   1731 		}
   1732 	}
   1733 
   1734 #ifndef SMALL
   1735 	if (vflag && fd == -1)
   1736 		printf("                            ");
   1737 	else if (vflag) {
   1738 		char *date = ctime(&ts);
   1739 
   1740 		/* skip the day, 1/100th second, and year */
   1741 		date += 4;
   1742 		date[12] = 0;
   1743 		printf("%5s %08x %11s ", "defla"/*XXX*/, crc, date);
   1744 	}
   1745 	in_tot += in;
   1746 	out_tot += out;
   1747 #endif
   1748 	printf("%12llu %12llu ", (unsigned long long)out, (unsigned long long)in);
   1749 	print_ratio(in, out, stdout);
   1750 	printf(" %s\n", outfile);
   1751 }
   1752 
   1753 /* display the usage of NetBSD gzip */
   1754 static void
   1755 usage(void)
   1756 {
   1757 
   1758 	fprintf(stderr, "%s\n", gzip_version);
   1759 	fprintf(stderr,
   1760     "usage: %s [-" OPT_LIST "] [<file> [<file> ...]]\n"
   1761 #ifndef SMALL
   1762     " -c --stdout          write to stdout, keep original files\n"
   1763     "    --to-stdout\n"
   1764     " -d --decompress      uncompress files\n"
   1765     "    --uncompress\n"
   1766     " -f --force           force overwriting & compress links\n"
   1767     " -h --help            display this help\n"
   1768     " -n --no-name         don't save original file name or time stamp\n"
   1769     " -N --name            save or restore original file name and time stamp\n"
   1770     " -q --quiet           output no warnings\n"
   1771     " -r --recursive       recursively compress files in directories\n"
   1772     " -S .suf              use suffix .suf instead of .gz\n"
   1773     "    --suffix .suf\n"
   1774     " -t --test            test compressed file\n"
   1775     " -v --verbose         print extra statistics\n"
   1776     " -V --version         display program version\n"
   1777     " -1 --fast            fastest (worst) compression\n"
   1778     " -2 .. -8             set compression level\n"
   1779     " -9 --best            best (slowest) compression\n",
   1780 #else
   1781     ,
   1782 #endif
   1783 	    getprogname());
   1784 	exit(0);
   1785 }
   1786 
   1787 /* display the version of NetBSD gzip */
   1788 static void
   1789 display_version(void)
   1790 {
   1791 
   1792 	fprintf(stderr, "%s\n", gzip_version);
   1793 	exit(0);
   1794 }
   1795 
   1796 #ifndef NO_BZIP2_SUPPORT
   1797 #include "unbzip2.c"
   1798 #endif
   1799 #ifndef NO_COMPRESS_SUPPORT
   1800 #include "zuncompress.c"
   1801 #endif
   1802