Home | History | Annotate | Line # | Download | only in gzip
      1 /*	$NetBSD: gzip.c,v 1.130 2026/04/30 13:35:32 simonb Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997-2026 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  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #if HAVE_NBTOOL_CONFIG_H
     30 #include "nbtool_config.h"
     31 #endif
     32 
     33 #include <sys/cdefs.h>
     34 #ifndef lint
     35 __COPYRIGHT("@(#) Copyright (c) 1997-2026 Matthew R. Green. "
     36 	    "All rights reserved.");
     37 __RCSID("$NetBSD: gzip.c,v 1.130 2026/04/30 13:35:32 simonb Exp $");
     38 #endif /* not lint */
     39 
     40 /*
     41  * gzip.c -- GPL free gzip using zlib.
     42  *
     43  * RFC 1950 covers the zlib format
     44  * RFC 1951 covers the deflate format
     45  * RFC 1952 covers the gzip format
     46  *
     47  * TODO:
     48  *	- use mmap where possible
     49  *	- handle some signals better (remove outfile?)
     50  *	- make bzip2/compress -v/-t/-l support work as well as possible
     51  */
     52 
     53 #include <sys/param.h>
     54 #include <sys/stat.h>
     55 #include <sys/time.h>
     56 
     57 #include <inttypes.h>
     58 #include <unistd.h>
     59 #include <stdio.h>
     60 #include <string.h>
     61 #include <stdlib.h>
     62 #include <err.h>
     63 #include <errno.h>
     64 #include <fcntl.h>
     65 #include <zlib.h>
     66 #include <fts.h>
     67 #include <libgen.h>
     68 #include <stdarg.h>
     69 #include <getopt.h>
     70 #include <time.h>
     71 #include <paths.h>
     72 
     73 #ifndef PRIdOFF
     74 #define PRIdOFF PRId64
     75 #endif
     76 
     77 /* what type of file are we dealing with */
     78 enum filetype {
     79 	FT_GZIP,
     80 #ifndef NO_BZIP2_SUPPORT
     81 	FT_BZIP2,
     82 #endif
     83 #ifndef NO_COMPRESS_SUPPORT
     84 	FT_Z,
     85 #endif
     86 #ifndef NO_PACK_SUPPORT
     87 	FT_PACK,
     88 #endif
     89 #ifndef NO_XZ_SUPPORT
     90 	FT_XZ,
     91 #endif
     92 #ifndef NO_ZSTD_SUPPORT
     93 	FT_ZSTD,
     94 #endif
     95 #ifndef NO_LZ_SUPPORT
     96 	FT_LZ,
     97 #endif
     98 	FT_LAST,
     99 	FT_UNKNOWN
    100 };
    101 
    102 #ifndef NO_BZIP2_SUPPORT
    103 #include <bzlib.h>
    104 
    105 #define BZ2_SUFFIX	".bz2"
    106 #define BZIP2_MAGIC	"\102\132\150"
    107 #endif
    108 
    109 #ifndef NO_COMPRESS_SUPPORT
    110 #define Z_SUFFIX	".Z"
    111 #define Z_MAGIC		"\037\235"
    112 #endif
    113 
    114 #ifndef NO_PACK_SUPPORT
    115 #define PACK_MAGIC	"\037\036"
    116 #endif
    117 
    118 #ifndef NO_XZ_SUPPORT
    119 #include <lzma.h>
    120 #define XZ_SUFFIX	".xz"
    121 #define XZ_MAGIC	"\3757zXZ"
    122 #endif
    123 
    124 #ifndef NO_ZSTD_SUPPORT
    125 #include <zstd.h>
    126 #define ZSTD_SUFFIX	".zst"
    127 #define ZSTD_MAGIC	"\050\265\057\375"
    128 #endif
    129 
    130 #ifndef NO_LZ_SUPPORT
    131 #define LZ_SUFFIX	".lz"
    132 #define LZ_MAGIC	"LZIP"
    133 #endif
    134 
    135 #define GZ_SUFFIX	".gz"
    136 
    137 #define BUFLEN		(64 * 1024)
    138 
    139 #define GZIP_MAGIC0	0x1F
    140 #define GZIP_MAGIC1	0x8B
    141 #define GZIP_OMAGIC1	0x9E
    142 
    143 #define GZIP_TIMESTAMP	(off_t)4
    144 #define GZIP_ORIGNAME	(off_t)10
    145 
    146 #define HEAD_CRC	0x02
    147 #define EXTRA_FIELD	0x04
    148 #define ORIG_NAME	0x08
    149 #define COMMENT		0x10
    150 
    151 #define OS_CODE		3	/* Unix */
    152 
    153 typedef struct {
    154     const char	*zipped;
    155     int		ziplen;
    156     const char	*normal;	/* for unzip - must not be longer than zipped */
    157 } suffixes_t;
    158 static suffixes_t suffixes[] = {
    159 #define	SUFFIX(Z, N) {Z, sizeof Z - 1, N}
    160 	SUFFIX(GZ_SUFFIX,	""),	/* Overwritten by -S .xxx */
    161 #ifndef SMALL
    162 	SUFFIX(GZ_SUFFIX,	""),
    163 	SUFFIX(".z",		""),
    164 	SUFFIX("-gz",		""),
    165 	SUFFIX("-z",		""),
    166 	SUFFIX("_z",		""),
    167 	SUFFIX(".taz",		".tar"),
    168 	SUFFIX(".tgz",		".tar"),
    169 #ifndef NO_BZIP2_SUPPORT
    170 	SUFFIX(BZ2_SUFFIX,	""),
    171 #endif
    172 #ifndef NO_COMPRESS_SUPPORT
    173 	SUFFIX(Z_SUFFIX,	""),
    174 #endif
    175 #ifndef NO_XZ_SUPPORT
    176 	SUFFIX(XZ_SUFFIX,	""),
    177 #endif
    178 #ifndef NO_ZSTD_SUPPORT
    179 	SUFFIX(ZSTD_SUFFIX,	""),
    180 #endif
    181 #ifndef NO_LZ_SUPPORT
    182 	SUFFIX(LZ_SUFFIX,	""),
    183 #endif
    184 	SUFFIX(GZ_SUFFIX,	""),	/* Overwritten by -S "" */
    185 #endif /* SMALL */
    186 #undef SUFFIX
    187 };
    188 #define NUM_SUFFIXES (sizeof suffixes / sizeof suffixes[0])
    189 #define SUFFIX_MAXLEN	30
    190 
    191 static	const char	gzip_version[] = "NetBSD gzip 20260107";
    192 
    193 static	int	cflag;			/* stdout mode */
    194 static	int	dflag;			/* decompress mode */
    195 static	int	lflag;			/* list mode */
    196 static	int	numflag = 6;		/* gzip -1..-9 value */
    197 
    198 #ifndef SMALL
    199 static	int	fflag;			/* force mode */
    200 static	int	kflag;			/* don't delete input files */
    201 static	int	nflag;			/* don't save name/timestamp */
    202 static	int	Nflag;			/* don't restore name/timestamp */
    203 static	int	qflag;			/* quiet mode */
    204 static	int	rflag;			/* recursive mode */
    205 static	int	tflag;			/* test */
    206 static	int	vflag;			/* verbose mode */
    207 #ifdef SIGINFO
    208 static	sig_atomic_t print_info = 0;
    209 #endif
    210 #else
    211 #define		qflag	0
    212 #define		tflag	0
    213 #endif
    214 
    215 static	int	exit_value = 0;		/* exit value */
    216 
    217 static	const char *infile;		/* name of file coming in */
    218 
    219 static	void	maybe_err(const char *fmt, ...) __printflike(1, 2) __dead;
    220 #if !defined(NO_BZIP2_SUPPORT) || !defined(NO_PACK_SUPPORT) ||	\
    221     !defined(NO_XZ_SUPPORT) || !defined(NO_ZSTD_SUPPORT)
    222 static	void	maybe_errx(const char *fmt, ...) __printflike(1, 2) __dead;
    223 #endif
    224 static	void	maybe_warn(const char *fmt, ...) __printflike(1, 2);
    225 static	void	maybe_warnx(const char *fmt, ...) __printflike(1, 2);
    226 static	enum filetype file_gettype(u_char *);
    227 #ifdef SMALL
    228 #define gz_compress(if, of, sz, fn, tm) gz_compress(if, of, sz)
    229 #endif
    230 static	off_t	gz_compress(int, int, off_t *, const char *, uint32_t);
    231 static	off_t	gz_uncompress(int, int, char *, size_t, off_t *, const char *);
    232 static	off_t	file_compress(char *, char *, size_t);
    233 static	off_t	file_uncompress(char *, char *, size_t);
    234 static	void	handle_pathname(char *);
    235 static	void	handle_file(char *, struct stat *);
    236 static	void	handle_fd_decomp(int fd);
    237 static	void	handle_stdout(void);
    238 static	void	print_ratio(off_t, off_t, FILE *);
    239 static	void	print_list(int fd, off_t, const char *, time_t);
    240 static	void	usage(void) __dead;
    241 static	void	display_version(void) __dead;
    242 static	const suffixes_t *check_suffix(char *, int);
    243 static	ssize_t	read_retry(int, void *, size_t);
    244 static	ssize_t	write_retry(int, const void *, size_t);
    245 static void	print_list_out(off_t, off_t, const char*);
    246 
    247 #ifdef SMALL
    248 #define infile_set(f,t) infile_set(f)
    249 #endif
    250 static	void	infile_set(const char *newinfile, off_t total);
    251 
    252 #ifdef SMALL
    253 #define unlink_input(f, sb) unlink(f)
    254 #define check_siginfo() /* nothing */
    255 #define setup_signals() /* nothing */
    256 #define infile_newdata(t) /* nothing */
    257 #else
    258 static	off_t	infile_total;		/* total expected to read/write */
    259 static	off_t	infile_current;		/* current read/write */
    260 
    261 #ifdef SIGINFO
    262 static	void	check_siginfo(void);
    263 #else
    264 #define check_siginfo() /* nothing */
    265 #endif
    266 static	void	license(void) __dead;
    267 static	off_t	cat_fd(unsigned char *, size_t, off_t *, int fd);
    268 static	void	prepend_gzip(char *, int *, char ***);
    269 static	void	handle_dir(char *);
    270 static	void	print_verbage(const char *, const char *, off_t, off_t);
    271 static	void	print_test(const char *, int);
    272 static	void	copymodes(int fd, const struct stat *, const char *file);
    273 static	int	check_outfile(const char *outfile);
    274 static	void	setup_signals(void);
    275 static	void	infile_newdata(size_t newdata);
    276 static	void	infile_clear(void);
    277 #endif
    278 
    279 #ifndef NO_BZIP2_SUPPORT
    280 static	off_t	unbzip2(int, int, char *, size_t, off_t *);
    281 #endif
    282 
    283 #ifndef NO_COMPRESS_SUPPORT
    284 static	FILE 	*zdopen(int);
    285 static	off_t	zuncompress(FILE *, FILE *, char *, size_t, off_t *);
    286 #endif
    287 
    288 #ifndef NO_PACK_SUPPORT
    289 static	off_t	unpack(int, int, char *, size_t, off_t *);
    290 #endif
    291 
    292 #ifndef NO_XZ_SUPPORT
    293 static	off_t	unxz(int, int, char *, size_t, off_t *);
    294 static	off_t	unxz_len(int);
    295 #endif
    296 
    297 #ifndef NO_ZSTD_SUPPORT
    298 static	off_t	unzstd(int, int, char *, size_t, off_t *);
    299 #endif
    300 
    301 #ifndef NO_LZ_SUPPORT
    302 static	off_t	unlz(int, int, char *, size_t, off_t *);
    303 #endif
    304 
    305 #ifdef SMALL
    306 #define getopt_long(a,b,c,d,e) getopt(a,b,c)
    307 #else
    308 static const struct option longopts[] = {
    309 	{ "stdout",		no_argument,		0,	'c' },
    310 	{ "to-stdout",		no_argument,		0,	'c' },
    311 	{ "decompress",		no_argument,		0,	'd' },
    312 	{ "uncompress",		no_argument,		0,	'd' },
    313 	{ "force",		no_argument,		0,	'f' },
    314 	{ "help",		no_argument,		0,	'h' },
    315 	{ "keep",		no_argument,		0,	'k' },
    316 	{ "list",		no_argument,		0,	'l' },
    317 	{ "no-name",		no_argument,		0,	'n' },
    318 	{ "name",		no_argument,		0,	'N' },
    319 	{ "quiet",		no_argument,		0,	'q' },
    320 	{ "recursive",		no_argument,		0,	'r' },
    321 	{ "suffix",		required_argument,	0,	'S' },
    322 	{ "test",		no_argument,		0,	't' },
    323 	{ "verbose",		no_argument,		0,	'v' },
    324 	{ "version",		no_argument,		0,	'V' },
    325 	{ "fast",		no_argument,		0,	'1' },
    326 	{ "best",		no_argument,		0,	'9' },
    327 	{ "ascii",		no_argument,		0,	'a' },
    328 	{ "license",		no_argument,		0,	'L' },
    329 	{ NULL,			no_argument,		0,	0 },
    330 };
    331 #endif
    332 
    333 int
    334 main(int argc, char **argv)
    335 {
    336 	const char *progname = getprogname();
    337 #ifndef SMALL
    338 	char *gzip;
    339 	int len;
    340 #endif
    341 	int ch;
    342 
    343 	setup_signals();
    344 
    345 #ifndef SMALL
    346 	if ((gzip = getenv("GZIP")) != NULL)
    347 		prepend_gzip(gzip, &argc, &argv);
    348 #endif
    349 
    350 	/*
    351 	 * XXX
    352 	 * handle being called `gunzip', `zcat' and `gzcat'
    353 	 */
    354 	if (strcmp(progname, "gunzip") == 0)
    355 		dflag = 1;
    356 	else if (strcmp(progname, "zcat") == 0 ||
    357 		 strcmp(progname, "gzcat") == 0)
    358 		dflag = cflag = 1;
    359 
    360 #ifdef SMALL
    361 #define OPT_LIST "123456789cdhlVn"
    362 #else
    363 #define OPT_LIST "123456789acdfhkLlNnqrS:tVv"
    364 #endif
    365 
    366 	while ((ch = getopt_long(argc, argv, OPT_LIST, longopts, NULL)) != -1) {
    367 		switch (ch) {
    368 		case '1': case '2': case '3':
    369 		case '4': case '5': case '6':
    370 		case '7': case '8': case '9':
    371 			numflag = ch - '0';
    372 			break;
    373 		case 'c':
    374 			cflag = 1;
    375 			break;
    376 		case 'd':
    377 			dflag = 1;
    378 			break;
    379 		case 'l':
    380 			lflag = 1;
    381 			dflag = 1;
    382 			break;
    383 		case 'V':
    384 			display_version();
    385 			/* NOTREACHED */
    386 #ifndef SMALL
    387 		case 'a':
    388 			/* --ascii is already the default. */
    389 			break;
    390 		case 'f':
    391 			fflag = 1;
    392 			break;
    393 		case 'k':
    394 			kflag = 1;
    395 			break;
    396 		case 'L':
    397 			license();
    398 			/* NOTREACHED */
    399 
    400 		case 'N':
    401 			nflag = 0;
    402 			Nflag = 1;
    403 			break;
    404 		case 'n':
    405 			nflag = 1;
    406 			Nflag = 0;
    407 			break;
    408 		case 'q':
    409 			qflag = 1;
    410 			break;
    411 		case 'r':
    412 			rflag = 1;
    413 			break;
    414 		case 'S':
    415 			len = strlen(optarg);
    416 			if (len != 0) {
    417 				if (len > SUFFIX_MAXLEN)
    418 					errx(1, "incorrect suffix: '%s'", optarg);
    419 				suffixes[0].zipped = optarg;
    420 				suffixes[0].ziplen = len;
    421 			} else {
    422 				suffixes[NUM_SUFFIXES - 1].zipped = "";
    423 				suffixes[NUM_SUFFIXES - 1].ziplen = 0;
    424 			}
    425 			break;
    426 		case 't':
    427 			cflag = 1;
    428 			tflag = 1;
    429 			dflag = 1;
    430 			break;
    431 		case 'v':
    432 			vflag = 1;
    433 			break;
    434 #else
    435 		case 'n':
    436 			break;
    437 #endif
    438 		default:
    439 			usage();
    440 			/* NOTREACHED */
    441 		}
    442 	}
    443 	argv += optind;
    444 	argc -= optind;
    445 
    446 	if (argc == 0) {
    447 		if (dflag)	/* stdin mode */
    448 			handle_fd_decomp(STDIN_FILENO);
    449 		else		/* stdout mode */
    450 			handle_stdout();
    451 	} else {
    452 		do {
    453 			handle_pathname(argv[0]);
    454 		} while (*++argv);
    455 	}
    456 #ifndef SMALL
    457 	if (qflag == 0 && lflag && argc > 1)
    458 		print_list(-1, 0, "(totals)", 0);
    459 #endif
    460 	exit(exit_value);
    461 }
    462 
    463 /* maybe print a warning */
    464 void
    465 maybe_warn(const char *fmt, ...)
    466 {
    467 	va_list ap;
    468 
    469 	if (qflag == 0) {
    470 		va_start(ap, fmt);
    471 		vwarn(fmt, ap);
    472 		va_end(ap);
    473 	}
    474 	if (exit_value == 0)
    475 		exit_value = 1;
    476 }
    477 
    478 /* ... without an errno. */
    479 void
    480 maybe_warnx(const char *fmt, ...)
    481 {
    482 	va_list ap;
    483 
    484 	if (qflag == 0) {
    485 		va_start(ap, fmt);
    486 		vwarnx(fmt, ap);
    487 		va_end(ap);
    488 	}
    489 	if (exit_value == 0)
    490 		exit_value = 1;
    491 }
    492 
    493 /* maybe print an error */
    494 void
    495 maybe_err(const char *fmt, ...)
    496 {
    497 	va_list ap;
    498 
    499 	if (qflag == 0) {
    500 		va_start(ap, fmt);
    501 		vwarn(fmt, ap);
    502 		va_end(ap);
    503 	}
    504 	exit(2);
    505 }
    506 
    507 #if !defined(NO_BZIP2_SUPPORT) || !defined(NO_PACK_SUPPORT) ||	\
    508     !defined(NO_XZ_SUPPORT) || !defined(NO_ZSTD_SUPPORT)
    509 /* ... without an errno. */
    510 void
    511 maybe_errx(const char *fmt, ...)
    512 {
    513 	va_list ap;
    514 
    515 	if (qflag == 0) {
    516 		va_start(ap, fmt);
    517 		vwarnx(fmt, ap);
    518 		va_end(ap);
    519 	}
    520 	exit(2);
    521 }
    522 #endif
    523 
    524 #ifndef SMALL
    525 /* split up $GZIP and prepend it to the argument list */
    526 static void
    527 prepend_gzip(char *gzip, int *argc, char ***argv)
    528 {
    529 	char *s, **nargv, **ac;
    530 	int nenvarg = 0, i;
    531 
    532 	/* scan how many arguments there are */
    533 	for (s = gzip;;) {
    534 		while (*s == ' ' || *s == '\t')
    535 			s++;
    536 		if (*s == 0)
    537 			goto count_done;
    538 		nenvarg++;
    539 		while (*s != ' ' && *s != '\t')
    540 			if (*s++ == 0)
    541 				goto count_done;
    542 	}
    543 count_done:
    544 	/* punt early */
    545 	if (nenvarg == 0)
    546 		return;
    547 
    548 	*argc += nenvarg;
    549 	ac = *argv;
    550 
    551 	nargv = (char **)malloc((*argc + 1) * sizeof(char *));
    552 	if (nargv == NULL)
    553 		maybe_err("malloc");
    554 
    555 	/* stash this away */
    556 	*argv = nargv;
    557 
    558 	/* copy the program name first */
    559 	i = 0;
    560 	nargv[i++] = *(ac++);
    561 
    562 	/* take a copy of $GZIP and add it to the array */
    563 	s = strdup(gzip);
    564 	if (s == NULL)
    565 		maybe_err("strdup");
    566 	for (;;) {
    567 		/* Skip whitespaces. */
    568 		while (*s == ' ' || *s == '\t')
    569 			s++;
    570 		if (*s == 0)
    571 			goto copy_done;
    572 		nargv[i++] = s;
    573 		/* Find the end of this argument. */
    574 		while (*s != ' ' && *s != '\t')
    575 			if (*s++ == 0)
    576 				/* Argument followed by NUL. */
    577 				goto copy_done;
    578 		/* Terminate by overwriting ' ' or '\t' with NUL. */
    579 		*s++ = 0;
    580 	}
    581 copy_done:
    582 
    583 	/* copy the original arguments and a NULL */
    584 	while (*ac)
    585 		nargv[i++] = *(ac++);
    586 	nargv[i] = NULL;
    587 }
    588 #endif
    589 
    590 /* compress input to output. Return bytes read, -1 on error */
    591 static off_t
    592 gz_compress(int in, int out, off_t *gsizep, const char *origname, uint32_t mtime)
    593 {
    594 	z_stream z;
    595 	char *outbufp, *inbufp;
    596 	off_t in_tot = 0, out_tot = 0;
    597 	ssize_t in_size;
    598 	int i, error;
    599 	uLong crc;
    600 #ifdef SMALL
    601 	static char header[] = { GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED, 0,
    602 				 0, 0, 0, 0,
    603 				 0, OS_CODE };
    604 #endif
    605 
    606 	outbufp = malloc(BUFLEN);
    607 	inbufp = malloc(BUFLEN);
    608 	if (outbufp == NULL || inbufp == NULL) {
    609 		maybe_err("malloc failed");
    610 		goto out;
    611 	}
    612 
    613 	memset(&z, 0, sizeof z);
    614 	z.zalloc = Z_NULL;
    615 	z.zfree = Z_NULL;
    616 	z.opaque = 0;
    617 
    618 #ifdef SMALL
    619 	memcpy(outbufp, header, sizeof header);
    620 	i = sizeof header;
    621 #else
    622 	if (nflag != 0) {
    623 		mtime = 0;
    624 		origname = "";
    625 	}
    626 
    627 	i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c%c%c%s",
    628 		     GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED,
    629 		     *origname ? ORIG_NAME : 0,
    630 		     mtime & 0xff,
    631 		     (mtime >> 8) & 0xff,
    632 		     (mtime >> 16) & 0xff,
    633 		     (mtime >> 24) & 0xff,
    634 		     numflag == 1 ? 4 : numflag == 9 ? 2 : 0,
    635 		     OS_CODE, origname);
    636 	if (i >= BUFLEN)
    637 		/* this need PATH_MAX > BUFLEN ... */
    638 		maybe_err("snprintf");
    639 	if (*origname)
    640 		i++;
    641 #endif
    642 
    643 	z.next_out = (unsigned char *)outbufp + i;
    644 	z.avail_out = BUFLEN - i;
    645 
    646 	error = deflateInit2(&z, numflag, Z_DEFLATED,
    647 			     (-MAX_WBITS), 8, Z_DEFAULT_STRATEGY);
    648 	if (error != Z_OK) {
    649 		maybe_warnx("deflateInit2 failed");
    650 		in_tot = -1;
    651 		goto out;
    652 	}
    653 
    654 	crc = crc32(0L, Z_NULL, 0);
    655 	for (;;) {
    656 		check_siginfo();
    657 		if (z.avail_out == 0) {
    658 			if (write_retry(out, outbufp, BUFLEN) != BUFLEN) {
    659 				maybe_warn("write");
    660 				out_tot = -1;
    661 				goto out;
    662 			}
    663 
    664 			out_tot += BUFLEN;
    665 			z.next_out = (unsigned char *)outbufp;
    666 			z.avail_out = BUFLEN;
    667 		}
    668 
    669 		if (z.avail_in == 0) {
    670 			in_size = read(in, inbufp, BUFLEN);
    671 			if (in_size < 0) {
    672 				maybe_warn("read");
    673 				in_tot = -1;
    674 				goto out;
    675 			}
    676 			if (in_size == 0)
    677 				break;
    678 			infile_newdata(in_size);
    679 
    680 			crc = crc32(crc, (const Bytef *)inbufp, (unsigned)in_size);
    681 			in_tot += in_size;
    682 			z.next_in = (unsigned char *)inbufp;
    683 			z.avail_in = in_size;
    684 		}
    685 
    686 		error = deflate(&z, Z_NO_FLUSH);
    687 		if (error != Z_OK && error != Z_STREAM_END) {
    688 			maybe_warnx("deflate failed");
    689 			in_tot = -1;
    690 			goto out;
    691 		}
    692 	}
    693 
    694 	/* clean up */
    695 	for (;;) {
    696 		size_t len;
    697 		ssize_t w;
    698 
    699 		error = deflate(&z, Z_FINISH);
    700 		if (error != Z_OK && error != Z_STREAM_END) {
    701 			maybe_warnx("deflate failed");
    702 			in_tot = -1;
    703 			goto out;
    704 		}
    705 
    706 		len = (char *)z.next_out - outbufp;
    707 
    708 		w = write_retry(out, outbufp, len);
    709 		if (w == -1 || (size_t)w != len) {
    710 			maybe_warn("write");
    711 			out_tot = -1;
    712 			goto out;
    713 		}
    714 		out_tot += len;
    715 		z.next_out = (unsigned char *)outbufp;
    716 		z.avail_out = BUFLEN;
    717 
    718 		if (error == Z_STREAM_END)
    719 			break;
    720 	}
    721 
    722 	if (deflateEnd(&z) != Z_OK) {
    723 		maybe_warnx("deflateEnd failed");
    724 		in_tot = -1;
    725 		goto out;
    726 	}
    727 
    728 	i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c",
    729 		 (int)crc & 0xff,
    730 		 (int)(crc >> 8) & 0xff,
    731 		 (int)(crc >> 16) & 0xff,
    732 		 (int)(crc >> 24) & 0xff,
    733 		 (int)in_tot & 0xff,
    734 		 (int)(in_tot >> 8) & 0xff,
    735 		 (int)(in_tot >> 16) & 0xff,
    736 		 (int)(in_tot >> 24) & 0xff);
    737 	if (i != 8)
    738 		maybe_err("snprintf");
    739 #if 0
    740 	if (in_tot > 0xffffffff)
    741 		maybe_warn("input file size >= 4GB cannot be saved");
    742 #endif
    743 	if (write_retry(out, outbufp, i) != i) {
    744 		maybe_warn("write");
    745 		in_tot = -1;
    746 	} else
    747 		out_tot += i;
    748 
    749 out:
    750 	if (inbufp != NULL)
    751 		free(inbufp);
    752 	if (outbufp != NULL)
    753 		free(outbufp);
    754 	if (gsizep)
    755 		*gsizep = out_tot;
    756 	return in_tot;
    757 }
    758 
    759 /*
    760  * uncompress input to output then close the input.  return the
    761  * uncompressed size written, and put the compressed sized read
    762  * into `*gsizep'.
    763  */
    764 static off_t
    765 gz_uncompress(int in, int out, char *pre, size_t prelen, off_t *gsizep,
    766 	      const char *filename)
    767 {
    768 	z_stream z;
    769 	char *outbufp, *inbufp;
    770 	off_t out_tot = -1, in_tot = 0;
    771 	uint32_t out_sub_tot = 0;
    772 	enum {
    773 		GZSTATE_MAGIC0,
    774 		GZSTATE_MAGIC1,
    775 		GZSTATE_METHOD,
    776 		GZSTATE_FLAGS,
    777 		GZSTATE_SKIPPING,
    778 		GZSTATE_EXTRA,
    779 		GZSTATE_EXTRA2,
    780 		GZSTATE_EXTRA3,
    781 		GZSTATE_ORIGNAME,
    782 		GZSTATE_COMMENT,
    783 		GZSTATE_HEAD_CRC1,
    784 		GZSTATE_HEAD_CRC2,
    785 		GZSTATE_INIT,
    786 		GZSTATE_READ,
    787 		GZSTATE_CRC,
    788 		GZSTATE_LEN,
    789 	} state = GZSTATE_MAGIC0;
    790 	int flags = 0, skip_count = 0;
    791 	int error = Z_STREAM_ERROR, done_reading = 0;
    792 	uLong crc = 0;
    793 	ssize_t wr;
    794 	int needmore = 0;
    795 
    796 #define ADVANCE()       { z.next_in++; z.avail_in--; }
    797 
    798 	if ((outbufp = malloc(BUFLEN)) == NULL) {
    799 		maybe_err("malloc failed");
    800 		goto out2;
    801 	}
    802 	if ((inbufp = malloc(BUFLEN)) == NULL) {
    803 		maybe_err("malloc failed");
    804 		goto out1;
    805 	}
    806 
    807 	memset(&z, 0, sizeof z);
    808 	z.avail_in = prelen;
    809 	z.next_in = (unsigned char *)pre;
    810 	z.avail_out = BUFLEN;
    811 	z.next_out = (unsigned char *)outbufp;
    812 	z.zalloc = NULL;
    813 	z.zfree = NULL;
    814 	z.opaque = 0;
    815 
    816 	in_tot = prelen;
    817 	out_tot = 0;
    818 
    819 	for (;;) {
    820 		check_siginfo();
    821 		if ((z.avail_in == 0 || needmore) && done_reading == 0) {
    822 			ssize_t in_size;
    823 
    824 			if (z.avail_in > 0) {
    825 				memmove(inbufp, z.next_in, z.avail_in);
    826 			}
    827 			z.next_in = (unsigned char *)inbufp;
    828 			in_size = read(in, z.next_in + z.avail_in,
    829 			    BUFLEN - z.avail_in);
    830 
    831 			if (in_size == -1) {
    832 				maybe_warn("failed to read stdin");
    833 				goto stop_and_fail;
    834 			} else if (in_size == 0) {
    835 				done_reading = 1;
    836 			}
    837 			infile_newdata(in_size);
    838 
    839 			z.avail_in += in_size;
    840 			needmore = 0;
    841 
    842 			in_tot += in_size;
    843 		}
    844 		if (z.avail_in == 0) {
    845 			if (done_reading && state != GZSTATE_MAGIC0) {
    846 				maybe_warnx("%s: unexpected end of file",
    847 					    filename);
    848 				goto stop_and_fail;
    849 			}
    850 			goto stop;
    851 		}
    852 		switch (state) {
    853 		case GZSTATE_MAGIC0:
    854 			if (*z.next_in != GZIP_MAGIC0) {
    855 				if (in_tot > 0) {
    856 					maybe_warnx("%s: trailing garbage "
    857 						    "ignored", filename);
    858 					goto stop;
    859 				}
    860 				maybe_warnx("input not gziped (MAGIC0)");
    861 				exit_value = 2;
    862 				goto stop_and_fail;
    863 			}
    864 			ADVANCE();
    865 			state++;
    866 			out_sub_tot = 0;
    867 			crc = crc32(0L, Z_NULL, 0);
    868 			break;
    869 
    870 		case GZSTATE_MAGIC1:
    871 			if (*z.next_in != GZIP_MAGIC1 &&
    872 			    *z.next_in != GZIP_OMAGIC1) {
    873 				maybe_warnx("input not gziped (MAGIC1)");
    874 				goto stop_and_fail;
    875 			}
    876 			ADVANCE();
    877 			state++;
    878 			break;
    879 
    880 		case GZSTATE_METHOD:
    881 			if (*z.next_in != Z_DEFLATED) {
    882 				maybe_warnx("unknown compression method");
    883 				goto stop_and_fail;
    884 			}
    885 			ADVANCE();
    886 			state++;
    887 			break;
    888 
    889 		case GZSTATE_FLAGS:
    890 			flags = *z.next_in;
    891 			ADVANCE();
    892 			skip_count = 6;
    893 			state++;
    894 			break;
    895 
    896 		case GZSTATE_SKIPPING:
    897 			if (skip_count > 0) {
    898 				skip_count--;
    899 				ADVANCE();
    900 			} else
    901 				state++;
    902 			break;
    903 
    904 		case GZSTATE_EXTRA:
    905 			if ((flags & EXTRA_FIELD) == 0) {
    906 				state = GZSTATE_ORIGNAME;
    907 				break;
    908 			}
    909 			skip_count = *z.next_in;
    910 			ADVANCE();
    911 			state++;
    912 			break;
    913 
    914 		case GZSTATE_EXTRA2:
    915 			skip_count |= ((*z.next_in) << 8);
    916 			ADVANCE();
    917 			state++;
    918 			break;
    919 
    920 		case GZSTATE_EXTRA3:
    921 			if (skip_count > 0) {
    922 				skip_count--;
    923 				ADVANCE();
    924 			} else
    925 				state++;
    926 			break;
    927 
    928 		case GZSTATE_ORIGNAME:
    929 			if ((flags & ORIG_NAME) == 0) {
    930 				state++;
    931 				break;
    932 			}
    933 			if (*z.next_in == 0)
    934 				state++;
    935 			ADVANCE();
    936 			break;
    937 
    938 		case GZSTATE_COMMENT:
    939 			if ((flags & COMMENT) == 0) {
    940 				state++;
    941 				break;
    942 			}
    943 			if (*z.next_in == 0)
    944 				state++;
    945 			ADVANCE();
    946 			break;
    947 
    948 		case GZSTATE_HEAD_CRC1:
    949 			if (flags & HEAD_CRC)
    950 				skip_count = 2;
    951 			else
    952 				skip_count = 0;
    953 			state++;
    954 			break;
    955 
    956 		case GZSTATE_HEAD_CRC2:
    957 			if (skip_count > 0) {
    958 				skip_count--;
    959 				ADVANCE();
    960 			} else
    961 				state++;
    962 			break;
    963 
    964 		case GZSTATE_INIT:
    965 			if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
    966 				maybe_warnx("failed to inflateInit");
    967 				goto stop_and_fail;
    968 			}
    969 			state++;
    970 			break;
    971 
    972 		case GZSTATE_READ:
    973 			error = inflate(&z, Z_FINISH);
    974 			switch (error) {
    975 			/* Z_BUF_ERROR goes with Z_FINISH... */
    976 			case Z_BUF_ERROR:
    977 				if (z.avail_out > 0 && !done_reading)
    978 					continue;
    979 
    980 			case Z_STREAM_END:
    981 			case Z_OK:
    982 				break;
    983 
    984 			case Z_NEED_DICT:
    985 				maybe_warnx("Z_NEED_DICT error");
    986 				goto stop_and_fail;
    987 			case Z_DATA_ERROR:
    988 				maybe_warnx("data stream error");
    989 				goto stop_and_fail;
    990 			case Z_STREAM_ERROR:
    991 				maybe_warnx("internal stream error");
    992 				goto stop_and_fail;
    993 			case Z_MEM_ERROR:
    994 				maybe_warnx("memory allocation error");
    995 				goto stop_and_fail;
    996 
    997 			default:
    998 				maybe_warn("unknown error from inflate(): %d",
    999 				    error);
   1000 			}
   1001 			wr = BUFLEN - z.avail_out;
   1002 
   1003 			if (wr != 0) {
   1004 				crc = crc32(crc, (const Bytef *)outbufp, (unsigned)wr);
   1005 				if (
   1006 #ifndef SMALL
   1007 				    /* don't write anything with -t */
   1008 				    tflag == 0 &&
   1009 #endif
   1010 				    write_retry(out, outbufp, wr) != wr) {
   1011 					maybe_warn("error writing to output");
   1012 					goto stop_and_fail;
   1013 				}
   1014 
   1015 				out_tot += wr;
   1016 				out_sub_tot += wr;
   1017 			}
   1018 
   1019 			if (error == Z_STREAM_END) {
   1020 				inflateEnd(&z);
   1021 				state++;
   1022 			}
   1023 
   1024 			z.next_out = (unsigned char *)outbufp;
   1025 			z.avail_out = BUFLEN;
   1026 
   1027 			break;
   1028 		case GZSTATE_CRC:
   1029 			{
   1030 				uLong origcrc;
   1031 
   1032 				if (z.avail_in < 4) {
   1033 					if (!done_reading) {
   1034 						needmore = 1;
   1035 						continue;
   1036 					}
   1037 					maybe_warnx("truncated input");
   1038 					goto stop_and_fail;
   1039 				}
   1040 				origcrc = ((unsigned)z.next_in[0] & 0xff) |
   1041 					((unsigned)z.next_in[1] & 0xff) << 8 |
   1042 					((unsigned)z.next_in[2] & 0xff) << 16 |
   1043 					((unsigned)z.next_in[3] & 0xff) << 24;
   1044 				if (origcrc != crc) {
   1045 					maybe_warnx("invalid compressed"
   1046 					     " data--crc error");
   1047 					goto stop_and_fail;
   1048 				}
   1049 			}
   1050 
   1051 			z.avail_in -= 4;
   1052 			z.next_in += 4;
   1053 
   1054 			if (!z.avail_in && done_reading) {
   1055 				goto stop;
   1056 			}
   1057 			state++;
   1058 			break;
   1059 		case GZSTATE_LEN:
   1060 			{
   1061 				uLong origlen;
   1062 
   1063 				if (z.avail_in < 4) {
   1064 					if (!done_reading) {
   1065 						needmore = 1;
   1066 						continue;
   1067 					}
   1068 					maybe_warnx("truncated input");
   1069 					goto stop_and_fail;
   1070 				}
   1071 				origlen = ((unsigned)z.next_in[0] & 0xff) |
   1072 					((unsigned)z.next_in[1] & 0xff) << 8 |
   1073 					((unsigned)z.next_in[2] & 0xff) << 16 |
   1074 					((unsigned)z.next_in[3] & 0xff) << 24;
   1075 
   1076 				if (origlen != out_sub_tot) {
   1077 					maybe_warnx("invalid compressed"
   1078 					     " data--length error");
   1079 					goto stop_and_fail;
   1080 				}
   1081 			}
   1082 
   1083 			z.avail_in -= 4;
   1084 			z.next_in += 4;
   1085 
   1086 			if (error < 0) {
   1087 				maybe_warnx("decompression error");
   1088 				goto stop_and_fail;
   1089 			}
   1090 			state = GZSTATE_MAGIC0;
   1091 			break;
   1092 		}
   1093 		continue;
   1094 stop_and_fail:
   1095 		out_tot = -1;
   1096 stop:
   1097 		break;
   1098 	}
   1099 	if (state > GZSTATE_INIT)
   1100 		inflateEnd(&z);
   1101 
   1102 	free(inbufp);
   1103 out1:
   1104 	free(outbufp);
   1105 out2:
   1106 	if (gsizep)
   1107 		*gsizep = in_tot;
   1108 	return (out_tot);
   1109 }
   1110 
   1111 #ifndef SMALL
   1112 /*
   1113  * set the owner, mode, flags & utimes using the given file descriptor.
   1114  * file is only used in possible warning messages.
   1115  */
   1116 static void
   1117 copymodes(int fd, const struct stat *sbp, const char *file)
   1118 {
   1119 	struct timeval times[2];
   1120 	struct stat sb;
   1121 
   1122 	/*
   1123 	 * If we have no info on the input, give this file some
   1124 	 * default values and return..
   1125 	 */
   1126 	if (sbp == NULL) {
   1127 		mode_t mask = umask(022);
   1128 
   1129 		(void)fchmod(fd, DEFFILEMODE & ~mask);
   1130 		(void)umask(mask);
   1131 		return;
   1132 	}
   1133 	sb = *sbp;
   1134 
   1135 	/* if the chown fails, remove set-id bits as-per compress(1) */
   1136 	if (fchown(fd, sb.st_uid, sb.st_gid) < 0) {
   1137 		if (errno != EPERM)
   1138 			maybe_warn("couldn't fchown: %s", file);
   1139 		sb.st_mode &= ~(S_ISUID|S_ISGID);
   1140 	}
   1141 
   1142 	/* we only allow set-id and the 9 normal permission bits */
   1143 	sb.st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
   1144 	if (fchmod(fd, sb.st_mode) < 0)
   1145 		maybe_warn("couldn't fchmod: %s", file);
   1146 
   1147 #if !HAVE_NBTOOL_CONFIG_H
   1148 	TIMESPEC_TO_TIMEVAL(&times[0], &sb.st_atimespec);
   1149 	TIMESPEC_TO_TIMEVAL(&times[1], &sb.st_mtimespec);
   1150 	if (futimes(fd, times) < 0)
   1151 		maybe_warn("couldn't utimes: %s", file);
   1152 
   1153 	/* finally, only try flags if they exist already */
   1154         if (sb.st_flags != 0 && fchflags(fd, sb.st_flags) < 0)
   1155 		maybe_warn("couldn't fchflags: %s", file);
   1156 #endif
   1157 }
   1158 #endif
   1159 
   1160 /* what sort of file is this? */
   1161 static enum filetype
   1162 file_gettype(u_char *buf)
   1163 {
   1164 
   1165 	if (buf[0] == GZIP_MAGIC0 &&
   1166 	    (buf[1] == GZIP_MAGIC1 || buf[1] == GZIP_OMAGIC1))
   1167 		return FT_GZIP;
   1168 	else
   1169 #ifndef NO_BZIP2_SUPPORT
   1170 	if (memcmp(buf, BZIP2_MAGIC, 3) == 0 &&
   1171 	    buf[3] >= '0' && buf[3] <= '9')
   1172 		return FT_BZIP2;
   1173 	else
   1174 #endif
   1175 #ifndef NO_COMPRESS_SUPPORT
   1176 	if (memcmp(buf, Z_MAGIC, 2) == 0)
   1177 		return FT_Z;
   1178 	else
   1179 #endif
   1180 #ifndef NO_PACK_SUPPORT
   1181 	if (memcmp(buf, PACK_MAGIC, 2) == 0)
   1182 		return FT_PACK;
   1183 	else
   1184 #endif
   1185 #ifndef NO_XZ_SUPPORT
   1186 	if (memcmp(buf, XZ_MAGIC, 4) == 0)	/* XXX: We only have 4 bytes */
   1187 		return FT_XZ;
   1188 	else
   1189 #endif
   1190 #ifndef NO_ZSTD_SUPPORT
   1191 	if (memcmp(buf, ZSTD_MAGIC, 4) == 0)
   1192 		return FT_ZSTD;
   1193 	else
   1194 #endif
   1195 #ifndef NO_LZ_SUPPORT
   1196 	if (memcmp(buf, LZ_MAGIC, 4) == 0)
   1197 		return FT_LZ;
   1198 	else
   1199 #endif
   1200 		return FT_UNKNOWN;
   1201 }
   1202 
   1203 #ifndef SMALL
   1204 /* check the outfile is OK. */
   1205 static int
   1206 check_outfile(const char *outfile)
   1207 {
   1208 	struct stat sb;
   1209 	int ok = 1;
   1210 
   1211 	if (lflag == 0 && stat(outfile, &sb) == 0) {
   1212 		if (fflag)
   1213 			unlink(outfile);
   1214 		else if (isatty(STDIN_FILENO)) {
   1215 			char ans[10] = { 'n', '\0' };	/* default */
   1216 
   1217 			fprintf(stderr, "%s already exists -- do you wish to "
   1218 					"overwrite (y or n)? " , outfile);
   1219 			(void)fgets(ans, sizeof(ans) - 1, stdin);
   1220 			if (ans[0] != 'y' && ans[0] != 'Y') {
   1221 				fprintf(stderr, "\tnot overwriting\n");
   1222 				ok = 0;
   1223 			} else
   1224 				unlink(outfile);
   1225 		} else {
   1226 			maybe_warnx("%s already exists -- skipping", outfile);
   1227 			ok = 0;
   1228 		}
   1229 	}
   1230 	return ok;
   1231 }
   1232 
   1233 static void
   1234 unlink_input(const char *file, const struct stat *sb)
   1235 {
   1236 	struct stat nsb;
   1237 
   1238 	if (kflag)
   1239 		return;
   1240 	if (stat(file, &nsb) != 0)
   1241 		/* Must be gone already */
   1242 		return;
   1243 	if (nsb.st_dev != sb->st_dev || nsb.st_ino != sb->st_ino)
   1244 		/* Definitely a different file */
   1245 		return;
   1246 	unlink(file);
   1247 }
   1248 
   1249 #ifdef SIGINFO
   1250 static void
   1251 got_siginfo(int signo)
   1252 {
   1253 
   1254 	print_info = 1;
   1255 }
   1256 #endif
   1257 
   1258 static void
   1259 setup_signals(void)
   1260 {
   1261 
   1262 #ifdef SIGINFO
   1263 	signal(SIGINFO, got_siginfo);
   1264 #endif
   1265 }
   1266 
   1267 static	void
   1268 infile_newdata(size_t newdata)
   1269 {
   1270 
   1271 	infile_current += newdata;
   1272 }
   1273 #endif
   1274 
   1275 static	void
   1276 infile_set(const char *newinfile, off_t total)
   1277 {
   1278 
   1279 	if (newinfile)
   1280 		infile = newinfile;
   1281 #ifndef SMALL
   1282 	infile_total = total;
   1283 #endif
   1284 }
   1285 
   1286 static	void
   1287 infile_clear(void)
   1288 {
   1289 
   1290 	infile = NULL;
   1291 #ifndef SMALL
   1292 	infile_total = infile_current = 0;
   1293 #endif
   1294 }
   1295 
   1296 static const suffixes_t *
   1297 check_suffix(char *file, int xlate)
   1298 {
   1299 	const suffixes_t *s;
   1300 	int len = strlen(file);
   1301 	char *sp;
   1302 
   1303 	for (s = suffixes; s != suffixes + NUM_SUFFIXES; s++) {
   1304 		/* if it doesn't fit in "a.suf", don't bother */
   1305 		if (s->ziplen >= len)
   1306 			continue;
   1307 		sp = file + len - s->ziplen;
   1308 		if (strcmp(s->zipped, sp) != 0)
   1309 			continue;
   1310 		if (xlate)
   1311 			strcpy(sp, s->normal);
   1312 		return s;
   1313 	}
   1314 	return NULL;
   1315 }
   1316 
   1317 /*
   1318  * compress the given file: create a corresponding .gz file and remove the
   1319  * original.
   1320  */
   1321 static off_t
   1322 file_compress(char *file, char *outfile, size_t outsize)
   1323 {
   1324 	int in;
   1325 	int out;
   1326 	off_t size, in_size;
   1327 #ifndef SMALL
   1328 	struct stat isb, osb;
   1329 	const suffixes_t *suff;
   1330 #endif
   1331 
   1332 	in = open(file, O_RDONLY);
   1333 	if (in == -1) {
   1334 		maybe_warn("can't open %s", file);
   1335 		return -1;
   1336 	}
   1337 
   1338 #ifndef SMALL
   1339 	if (fstat(in, &isb) != 0) {
   1340 		close(in);
   1341 		maybe_warn("can't stat %s", file);
   1342 		return -1;
   1343 	}
   1344 	infile_set(file, isb.st_size);
   1345 #endif
   1346 
   1347 	if (cflag == 0) {
   1348 #ifndef SMALL
   1349 		if (isb.st_nlink > 1 && fflag == 0) {
   1350 			maybe_warnx("%s has %d other link%s -- "
   1351 				    "skipping", file, isb.st_nlink - 1,
   1352 				    isb.st_nlink == 1 ? "" : "s");
   1353 			close(in);
   1354 			return -1;
   1355 		}
   1356 
   1357 		if (fflag == 0 && (suff = check_suffix(file, 0))
   1358 		    && suff->zipped[0] != 0) {
   1359 			maybe_warnx("%s already has %s suffix -- unchanged",
   1360 				    file, suff->zipped);
   1361 			close(in);
   1362 			return -1;
   1363 		}
   1364 #endif
   1365 
   1366 		/* Add (usually) .gz to filename */
   1367 		if ((size_t)snprintf(outfile, outsize, "%s%s",
   1368 					file, suffixes[0].zipped) >= outsize)
   1369 			memcpy(outfile + outsize - suffixes[0].ziplen - 1,
   1370 				suffixes[0].zipped, suffixes[0].ziplen + 1);
   1371 
   1372 #ifndef SMALL
   1373 		if (check_outfile(outfile) == 0) {
   1374 			close(in);
   1375 			return -1;
   1376 		}
   1377 #endif
   1378 	}
   1379 
   1380 	if (cflag == 0) {
   1381 		out = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
   1382 		if (out == -1) {
   1383 			maybe_warn("could not create output: %s", outfile);
   1384 			fclose(stdin);
   1385 			return -1;
   1386 		}
   1387 	} else
   1388 		out = STDOUT_FILENO;
   1389 
   1390 	in_size = gz_compress(in, out, &size, basename(file), (uint32_t)isb.st_mtime);
   1391 
   1392 	(void)close(in);
   1393 
   1394 	/*
   1395 	 * If there was an error, in_size will be -1.
   1396 	 * If we compressed to stdout, just return the size.
   1397 	 * Otherwise stat the file and check it is the correct size.
   1398 	 * We only blow away the file if we can stat the output and it
   1399 	 * has the expected size.
   1400 	 */
   1401 	if (cflag != 0)
   1402 		return in_size == -1 ? -1 : size;
   1403 
   1404 #ifndef SMALL
   1405 	if (fstat(out, &osb) != 0) {
   1406 		maybe_warn("couldn't stat: %s", outfile);
   1407 		goto bad_outfile;
   1408 	}
   1409 
   1410 	if (osb.st_size != size) {
   1411 		maybe_warnx("output file: %s wrong size (%" PRIdOFF
   1412 				" != %" PRIdOFF "), deleting",
   1413 				outfile, osb.st_size, size);
   1414 		goto bad_outfile;
   1415 	}
   1416 
   1417 	copymodes(out, &isb, outfile);
   1418 #endif
   1419 	if (close(out) == -1)
   1420 		maybe_warn("couldn't close output");
   1421 
   1422 	/* output is good, ok to delete input */
   1423 	unlink_input(file, &isb);
   1424 	return size;
   1425 
   1426 #ifndef SMALL
   1427     bad_outfile:
   1428 	if (close(out) == -1)
   1429 		maybe_warn("couldn't close output");
   1430 
   1431 	maybe_warnx("leaving original %s", file);
   1432 	unlink(outfile);
   1433 	return size;
   1434 #endif
   1435 }
   1436 
   1437 /* uncompress the given file and remove the original */
   1438 static off_t
   1439 file_uncompress(char *file, char *outfile, size_t outsize)
   1440 {
   1441 	struct stat isb, osb;
   1442 	off_t size;
   1443 	ssize_t rbytes;
   1444 	unsigned char fourbytes[4];
   1445 	enum filetype method;
   1446 	int fd, ofd, zfd = -1;
   1447 	size_t in_size;
   1448 #ifndef SMALL
   1449 	ssize_t rv;
   1450 	time_t timestamp = 0;
   1451 	char name[PATH_MAX + 1];
   1452 #endif
   1453 
   1454 	/* gather the old name info */
   1455 
   1456 	fd = open(file, O_RDONLY);
   1457 	if (fd < 0) {
   1458 		maybe_warn("can't open %s", file);
   1459 		goto lose;
   1460 	}
   1461 	if (fstat(fd, &isb) != 0) {
   1462 		close(fd);
   1463 		maybe_warn("can't stat %s", file);
   1464 		goto lose;
   1465 	}
   1466 	if (S_ISREG(isb.st_mode))
   1467 		in_size = isb.st_size;
   1468 	else
   1469 		in_size = 0;
   1470 	infile_set(file, in_size);
   1471 
   1472 	strlcpy(outfile, file, outsize);
   1473 	if (check_suffix(outfile, 1) == NULL && !(cflag || lflag)) {
   1474 		maybe_warnx("%s: unknown suffix -- ignored", file);
   1475 		goto lose;
   1476 	}
   1477 
   1478 	rbytes = read(fd, fourbytes, sizeof fourbytes);
   1479 	if (rbytes != sizeof fourbytes) {
   1480 		/* we don't want to fail here. */
   1481 #ifndef SMALL
   1482 		if (fflag)
   1483 			goto lose;
   1484 #endif
   1485 		if (rbytes == -1)
   1486 			maybe_warn("can't read %s", file);
   1487 		else
   1488 			goto unexpected_EOF;
   1489 		goto lose;
   1490 	}
   1491 	infile_newdata(rbytes);
   1492 
   1493 	method = file_gettype(fourbytes);
   1494 #ifndef SMALL
   1495 	if (fflag == 0 && method == FT_UNKNOWN) {
   1496 		maybe_warnx("%s: not in gzip format", file);
   1497 		goto lose;
   1498 	}
   1499 
   1500 #endif
   1501 
   1502 #ifndef SMALL
   1503 	if (method == FT_GZIP && Nflag) {
   1504 		unsigned char ts[4];	/* timestamp */
   1505 
   1506 		rv = pread(fd, ts, sizeof ts, GZIP_TIMESTAMP);
   1507 		if (rv >= 0 && rv < (ssize_t)(sizeof ts))
   1508 			goto unexpected_EOF;
   1509 		if (rv == -1) {
   1510 			if (!fflag)
   1511 				maybe_warn("can't read %s", file);
   1512 			goto lose;
   1513 		}
   1514 		infile_newdata(rv);
   1515 		timestamp = ts[3] << 24 | ts[2] << 16 | ts[1] << 8 | ts[0];
   1516 
   1517 		if (fourbytes[3] & ORIG_NAME) {
   1518 			rbytes = pread(fd, name, sizeof(name) - 1, GZIP_ORIGNAME);
   1519 			if (rbytes < 0) {
   1520 				maybe_warn("can't read %s", file);
   1521 				goto lose;
   1522 			}
   1523 			if (name[0] != '\0') {
   1524 				char *dp, *nf;
   1525 
   1526 				/* Make sure that name is NUL-terminated */
   1527 				name[rbytes] = '\0';
   1528 
   1529 				/* strip saved directory name */
   1530 				nf = strrchr(name, '/');
   1531 				if (nf == NULL)
   1532 					nf = name;
   1533 				else
   1534 					nf++;
   1535 
   1536 				/* preserve original directory name */
   1537 				dp = strrchr(file, '/');
   1538 				if (dp == NULL)
   1539 					dp = file;
   1540 				else
   1541 					dp++;
   1542 				snprintf(outfile, outsize, "%.*s%.*s",
   1543 						(int) (dp - file),
   1544 						file, (int) rbytes, nf);
   1545 			}
   1546 		}
   1547 	}
   1548 #endif
   1549 	lseek(fd, 0, SEEK_SET);
   1550 
   1551 	if (cflag == 0 || lflag) {
   1552 #ifndef SMALL
   1553 		if (isb.st_nlink > 1 && lflag == 0 && fflag == 0) {
   1554 			maybe_warnx("%s has %d other links -- skipping",
   1555 			    file, isb.st_nlink - 1);
   1556 			goto lose;
   1557 		}
   1558 		if (nflag == 0 && timestamp)
   1559 			isb.st_mtime = timestamp;
   1560 		if (check_outfile(outfile) == 0)
   1561 			goto lose;
   1562 #endif
   1563 	}
   1564 
   1565 	if (cflag)
   1566 		zfd = STDOUT_FILENO;
   1567 	else if (lflag)
   1568 		zfd = -1;
   1569 	else {
   1570 		zfd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
   1571 		if (zfd == STDOUT_FILENO) {
   1572 			/* We won't close STDOUT_FILENO later... */
   1573 			zfd = dup(zfd);
   1574 			close(STDOUT_FILENO);
   1575 		}
   1576 		if (zfd == -1) {
   1577 			maybe_warn("can't open %s", outfile);
   1578 			goto lose;
   1579 		}
   1580 	}
   1581 
   1582 	switch (method) {
   1583 #ifndef NO_BZIP2_SUPPORT
   1584 	case FT_BZIP2:
   1585 		/* XXX */
   1586 		if (lflag) {
   1587 			maybe_warnx("no -l with bzip2 files");
   1588 			goto lose;
   1589 		}
   1590 
   1591 		size = unbzip2(fd, zfd, NULL, 0, NULL);
   1592 		break;
   1593 #endif
   1594 
   1595 #ifndef NO_COMPRESS_SUPPORT
   1596 	case FT_Z: {
   1597 		FILE *in, *out;
   1598 
   1599 		/* XXX */
   1600 		if (lflag) {
   1601 			maybe_warnx("no -l with Lempel-Ziv files");
   1602 			goto lose;
   1603 		}
   1604 
   1605 		if ((in = zdopen(fd)) == NULL) {
   1606 			maybe_warn("zdopen for read: %s", file);
   1607 			goto lose;
   1608 		}
   1609 
   1610 		out = fdopen(dup(zfd), "w");
   1611 		if (out == NULL) {
   1612 			maybe_warn("fdopen for write: %s", outfile);
   1613 			fclose(in);
   1614 			goto lose;
   1615 		}
   1616 
   1617 		size = zuncompress(in, out, NULL, 0, NULL);
   1618 		/* need to fclose() if ferror() is true... */
   1619 		if (ferror(in) | fclose(in)) {
   1620 			maybe_warn("failed infile fclose");
   1621 			unlink(outfile);
   1622 			(void)fclose(out);
   1623 		}
   1624 		if (fclose(out) != 0) {
   1625 			maybe_warn("failed outfile fclose");
   1626 			unlink(outfile);
   1627 			goto lose;
   1628 		}
   1629 		break;
   1630 	}
   1631 #endif
   1632 
   1633 #ifndef NO_PACK_SUPPORT
   1634 	case FT_PACK:
   1635 		if (lflag) {
   1636 			maybe_warnx("no -l with packed files");
   1637 			goto lose;
   1638 		}
   1639 
   1640 		size = unpack(fd, zfd, NULL, 0, NULL);
   1641 		break;
   1642 #endif
   1643 
   1644 #ifndef NO_XZ_SUPPORT
   1645 	case FT_XZ:
   1646 		if (lflag) {
   1647 			size = unxz_len(fd);
   1648 			print_list_out(in_size, size, file);
   1649 			return -1;
   1650 		}
   1651 		size = unxz(fd, zfd, NULL, 0, NULL);
   1652 		break;
   1653 #endif
   1654 
   1655 #ifndef NO_ZSTD_SUPPORT
   1656 	case FT_ZSTD:
   1657 		if (lflag) {
   1658 			maybe_warnx("no -l with zstd files");
   1659 			goto lose;
   1660 		}
   1661 		size = unzstd(fd, zfd, NULL, 0, NULL);
   1662 		break;
   1663 #endif
   1664 
   1665 #ifndef NO_LZ_SUPPORT
   1666 	case FT_LZ:
   1667 		if (lflag) {
   1668 			maybe_warnx("no -l with lzip files");
   1669 			goto lose;
   1670 		}
   1671 		size = unlz(fd, zfd, NULL, 0, NULL);
   1672 		break;
   1673 #endif
   1674 #ifndef SMALL
   1675 	case FT_UNKNOWN:
   1676 		if (lflag) {
   1677 			maybe_warnx("no -l for unknown filetypes");
   1678 			goto lose;
   1679 		}
   1680 		size = cat_fd(NULL, 0, NULL, fd);
   1681 		break;
   1682 #endif
   1683 	default:
   1684 		if (lflag) {
   1685 			print_list(fd, in_size, outfile, isb.st_mtime);
   1686 			close(fd);
   1687 			return -1;	/* XXX */
   1688 		}
   1689 
   1690 		size = gz_uncompress(fd, zfd, NULL, 0, NULL, file);
   1691 		break;
   1692 	}
   1693 
   1694 	if (close(fd) != 0)
   1695 		maybe_warn("couldn't close input");
   1696 	if (zfd != STDOUT_FILENO && close(zfd) != 0)
   1697 		maybe_warn("couldn't close output");
   1698 
   1699 	if (size == -1) {
   1700 		if (cflag == 0)
   1701 			unlink(outfile);
   1702 		maybe_warnx("%s: uncompress failed", file);
   1703 		return -1;
   1704 	}
   1705 
   1706 	/* if testing, or we uncompressed to stdout, this is all we need */
   1707 #ifndef SMALL
   1708 	if (tflag)
   1709 		return size;
   1710 #endif
   1711 	/* if we are uncompressing to stdin, don't remove the file. */
   1712 	if (cflag)
   1713 		return size;
   1714 
   1715 	/*
   1716 	 * if we create a file...
   1717 	 */
   1718 	/*
   1719 	 * if we can't stat the file don't remove the file.
   1720 	 */
   1721 
   1722 	ofd = open(outfile, O_RDWR, 0);
   1723 	if (ofd == -1) {
   1724 		maybe_warn("couldn't open (leaving original): %s",
   1725 			   outfile);
   1726 		return -1;
   1727 	}
   1728 	if (fstat(ofd, &osb) != 0) {
   1729 		maybe_warn("couldn't stat (leaving original): %s",
   1730 			   outfile);
   1731 		close(ofd);
   1732 		return -1;
   1733 	}
   1734 	if (osb.st_size != size) {
   1735 		maybe_warnx("stat gave different size: %" PRIdOFF
   1736 				" != %" PRIdOFF " (leaving original)",
   1737 				size, osb.st_size);
   1738 		close(ofd);
   1739 		unlink(outfile);
   1740 		return -1;
   1741 	}
   1742 	unlink_input(file, &isb);
   1743 #ifndef SMALL
   1744 	copymodes(ofd, &isb, outfile);
   1745 #endif
   1746 	close(ofd);
   1747 	return size;
   1748 
   1749     unexpected_EOF:
   1750 	maybe_warnx("%s: unexpected end of file", file);
   1751     lose:
   1752 	if (fd != -1)
   1753 		close(fd);
   1754 	if (zfd != -1 && zfd != STDOUT_FILENO)
   1755 		close(fd);
   1756 	return -1;
   1757 }
   1758 
   1759 #ifndef check_siginfo
   1760 static void
   1761 check_siginfo(void)
   1762 {
   1763 	static int ttyfd = -2;
   1764 	char buf[2048];
   1765 	int n;
   1766 
   1767 	if (print_info == 0)
   1768 		return;
   1769 
   1770 	if (!infile)
   1771 		goto out;
   1772 
   1773 	if (ttyfd == -2)
   1774 		ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC);
   1775 
   1776 	if (ttyfd == -1)
   1777 		goto out;
   1778 
   1779 	if (infile_total) {
   1780 		const double pcent = (100.0 * infile_current) / infile_total;
   1781 
   1782 		n = snprintf(buf, sizeof(buf),
   1783 		    "%s: %s: done %ju/%ju bytes (%3.2f%%)\n",
   1784 		    getprogname(), infile, (uintmax_t)infile_current,
   1785 		    (uintmax_t)infile_total, pcent);
   1786 	} else {
   1787 		n = snprintf(buf, sizeof(buf), "%s: %s: done %ju bytes\n",
   1788 		    getprogname(), infile, (uintmax_t)infile_current);
   1789 	}
   1790 
   1791 	if (n <= 0)
   1792 		goto out;
   1793 
   1794 	write(ttyfd, buf, (size_t)n);
   1795 out:
   1796 	print_info = 0;
   1797 }
   1798 #endif
   1799 
   1800 static off_t
   1801 cat_fd(unsigned char * prepend, size_t count, off_t *gsizep, int fd)
   1802 {
   1803 	char buf[BUFLEN];
   1804 	off_t in_tot;
   1805 	ssize_t w;
   1806 
   1807 	in_tot = count;
   1808 	w = write_retry(STDOUT_FILENO, prepend, count);
   1809 	if (w == -1 || (size_t)w != count) {
   1810 		maybe_warn("write to stdout");
   1811 		return -1;
   1812 	}
   1813 	for (;;) {
   1814 		ssize_t rv;
   1815 
   1816 		rv = read(fd, buf, sizeof buf);
   1817 		if (rv == 0)
   1818 			break;
   1819 		if (rv < 0) {
   1820 			maybe_warn("read from fd %d", fd);
   1821 			break;
   1822 		}
   1823 		infile_newdata(rv);
   1824 
   1825 		if (write_retry(STDOUT_FILENO, buf, rv) != rv) {
   1826 			maybe_warn("write to stdout");
   1827 			break;
   1828 		}
   1829 		in_tot += rv;
   1830 	}
   1831 
   1832 	if (gsizep)
   1833 		*gsizep = in_tot;
   1834 	return (in_tot);
   1835 }
   1836 
   1837 static void
   1838 handle_fd_decomp(int fd)
   1839 {
   1840 	struct stat isb;
   1841 	unsigned char *prebuf = NULL;
   1842 	size_t prebuf_len;
   1843 	const size_t minbuf_len = 4; /* enough to identify the file */
   1844 	size_t in_size;
   1845 	off_t usize, gsize;
   1846 	enum filetype method;
   1847 	ssize_t bytes_read;
   1848 #ifndef NO_COMPRESS_SUPPORT
   1849 	FILE *in;
   1850 #endif
   1851 
   1852 #ifndef SMALL
   1853 	if (fflag == 0 && lflag == 0 && isatty(fd)) {
   1854 		maybe_warnx("standard input is a terminal -- ignoring");
   1855 		goto out;
   1856 	}
   1857 #endif
   1858 
   1859 	if (fstat(fd, &isb) < 0) {
   1860 		maybe_warn("fstat");
   1861 		goto out;
   1862 	}
   1863 	if (S_ISREG(isb.st_mode))
   1864 		in_size = isb.st_size;
   1865 	else
   1866 		in_size = 0;
   1867 	if (isb.st_blksize)
   1868 		prebuf_len = isb.st_blksize;
   1869 	else
   1870 		prebuf_len = minbuf_len;
   1871 	prebuf = malloc(prebuf_len);
   1872 	if (prebuf == NULL)
   1873 		maybe_err("malloc");
   1874 
   1875 	infile_set("(stdin)", in_size);
   1876 
   1877 	if (lflag) {
   1878 		print_list(fd, in_size, infile, isb.st_mtime);
   1879 		goto out;
   1880 	}
   1881 
   1882 	bytes_read = read_retry(fd, prebuf, prebuf_len);
   1883 	if (bytes_read == -1) {
   1884 		maybe_warn("can't read stdin");
   1885 		goto out;
   1886 	} else if ((size_t)bytes_read < minbuf_len) {
   1887 		maybe_warnx("(stdin): unexpected end of file");
   1888 		goto out;
   1889 	}
   1890 
   1891 	method = file_gettype(prebuf);
   1892 	switch (method) {
   1893 	default:
   1894 #ifndef SMALL
   1895 		if (fflag == 0) {
   1896 			maybe_warnx("unknown compression format");
   1897 			goto out;
   1898 		}
   1899 		usize = cat_fd(prebuf, bytes_read, &gsize, fd);
   1900 		break;
   1901 #endif
   1902 	case FT_GZIP:
   1903 		usize = gz_uncompress(fd, STDOUT_FILENO,
   1904 			      (char *)prebuf, bytes_read, &gsize, "(stdin)");
   1905 		break;
   1906 #ifndef NO_BZIP2_SUPPORT
   1907 	case FT_BZIP2:
   1908 		usize = unbzip2(fd, STDOUT_FILENO,
   1909 				(char *)prebuf, bytes_read, &gsize);
   1910 		break;
   1911 #endif
   1912 #ifndef NO_COMPRESS_SUPPORT
   1913 	case FT_Z:
   1914 		if ((in = zdopen(fd)) == NULL) {
   1915 			maybe_warnx("zopen of stdin");
   1916 			goto out;
   1917 		}
   1918 
   1919 		usize = zuncompress(in, stdout, (char *)prebuf,
   1920 		    bytes_read, &gsize);
   1921 		fclose(in);
   1922 		break;
   1923 #endif
   1924 #ifndef NO_PACK_SUPPORT
   1925 	case FT_PACK:
   1926 		usize = unpack(fd, STDOUT_FILENO,
   1927 			       (char *)prebuf, bytes_read, &gsize);
   1928 		break;
   1929 #endif
   1930 #ifndef NO_XZ_SUPPORT
   1931 	case FT_XZ:
   1932 		usize = unxz(fd, STDOUT_FILENO,
   1933 			     (char *)prebuf, bytes_read, &gsize);
   1934 		break;
   1935 #endif
   1936 #ifndef NO_ZSTD_SUPPORT
   1937 	case FT_ZSTD:
   1938 		usize = unzstd(fd, STDOUT_FILENO,
   1939 			     (char *)prebuf, bytes_read, &gsize);
   1940 		break;
   1941 #endif
   1942 #ifndef NO_LZ_SUPPORT
   1943 	case FT_LZ:
   1944 		usize = unlz(fd, STDOUT_FILENO,
   1945 			     (char *)prebuf, bytes_read, &gsize);
   1946 		break;
   1947 #endif
   1948 	}
   1949 
   1950 #ifndef SMALL
   1951         if (vflag && !tflag && usize != -1 && gsize != -1)
   1952 		print_verbage(NULL, NULL, usize, gsize);
   1953 	if (vflag && tflag)
   1954 		print_test("(stdin)", usize != -1);
   1955 #else
   1956 	(void)&usize;
   1957 #endif
   1958 
   1959 out:
   1960 	free(prebuf);
   1961 	infile_clear();
   1962 }
   1963 
   1964 static void
   1965 handle_stdout(void)
   1966 {
   1967 	off_t gsize;
   1968 #ifndef SMALL
   1969 	off_t usize;
   1970 	struct stat sb;
   1971 	time_t systime;
   1972 	uint32_t mtime;
   1973 	int ret;
   1974 
   1975 	infile_set("<stdout>", 0);
   1976 
   1977 	if (fflag == 0 && isatty(STDOUT_FILENO)) {
   1978 		maybe_warnx("standard output is a terminal -- ignoring");
   1979 		return;
   1980 	}
   1981 
   1982 	/* If stdin is a file use its mtime, otherwise use current time */
   1983 	ret = fstat(STDIN_FILENO, &sb);
   1984 	if (ret < 0) {
   1985 		maybe_warn("Can't stat stdin");
   1986 		return;
   1987 	}
   1988 
   1989 	if (S_ISREG(sb.st_mode)) {
   1990 		infile_set("<stdout>", sb.st_size);
   1991 		mtime = (uint32_t)sb.st_mtime;
   1992 	} else {
   1993 		systime = time(NULL);
   1994 		if (systime == -1) {
   1995 			maybe_warn("time");
   1996 			return;
   1997 		}
   1998 		mtime = (uint32_t)systime;
   1999 	}
   2000 
   2001 	usize =
   2002 #endif
   2003 		gz_compress(STDIN_FILENO, STDOUT_FILENO, &gsize, "", mtime);
   2004 #ifndef SMALL
   2005         if (vflag && !tflag && usize != -1 && gsize != -1)
   2006 		print_verbage(NULL, NULL, usize, gsize);
   2007 #endif
   2008 }
   2009 
   2010 /* do what is asked for, for the path name */
   2011 static void
   2012 handle_pathname(char *path)
   2013 {
   2014 	char *opath = path, *s = NULL;
   2015 	ssize_t len;
   2016 	int slen;
   2017 	struct stat sb;
   2018 
   2019 	/* check for stdout/stdin */
   2020 	if (path[0] == '-' && path[1] == '\0') {
   2021 		if (dflag)
   2022 			handle_fd_decomp(STDIN_FILENO);
   2023 		else
   2024 			handle_stdout();
   2025 		return;
   2026 	}
   2027 
   2028 retry:
   2029 	if (stat(path, &sb) != 0) {
   2030 		/* lets try <path>.gz if we're decompressing */
   2031 		if (dflag && s == NULL && errno == ENOENT) {
   2032 			len = strlen(path);
   2033 			slen = suffixes[0].ziplen;
   2034 			s = malloc(len + slen + 1);
   2035 			if (s == NULL)
   2036 				maybe_err("malloc");
   2037 			memcpy(s, path, len);
   2038 			memcpy(s + len, suffixes[0].zipped, slen + 1);
   2039 			path = s;
   2040 			goto retry;
   2041 		}
   2042 		maybe_warn("can't stat: %s", opath);
   2043 		goto out;
   2044 	}
   2045 
   2046 	if (S_ISDIR(sb.st_mode)) {
   2047 #ifndef SMALL
   2048 		if (rflag)
   2049 			handle_dir(path);
   2050 		else
   2051 #endif
   2052 			maybe_warnx("%s is a directory", path);
   2053 		goto out;
   2054 	}
   2055 
   2056 	if (S_ISREG(sb.st_mode))
   2057 		handle_file(path, &sb);
   2058 	/* if decompressing to stdout, try to handle non-regular files */
   2059 	else if (dflag && cflag &&
   2060 		 (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode) ||
   2061 		  S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode))) {
   2062 		int fd;
   2063 
   2064 		fd = open(path, O_RDONLY);
   2065 		if (fd < 0) {
   2066 			maybe_warn("open: %s", path);
   2067 		} else {
   2068 			handle_fd_decomp(fd);
   2069 			close(fd);
   2070 		}
   2071 	} else
   2072 		maybe_warnx("%s is not a regular file", path);
   2073 
   2074 out:
   2075 	if (s)
   2076 		free(s);
   2077 }
   2078 
   2079 /* compress/decompress a file */
   2080 static void
   2081 handle_file(char *file, struct stat *sbp)
   2082 {
   2083 	off_t usize, gsize;
   2084 	char	outfile[PATH_MAX];
   2085 
   2086 	infile_set(file, sbp->st_size);
   2087 	if (dflag) {
   2088 		usize = file_uncompress(file, outfile, sizeof(outfile));
   2089 #ifndef SMALL
   2090 		if (vflag && tflag)
   2091 			print_test(file, usize != -1);
   2092 #endif
   2093 		if (usize == -1)
   2094 			return;
   2095 		gsize = sbp->st_size;
   2096 	} else {
   2097 		gsize = file_compress(file, outfile, sizeof(outfile));
   2098 		if (gsize == -1)
   2099 			return;
   2100 		usize = sbp->st_size;
   2101 	}
   2102 	infile_clear();
   2103 
   2104 #ifndef SMALL
   2105 	if (vflag && !tflag)
   2106 		print_verbage(file, (cflag) ? NULL : outfile, usize, gsize);
   2107 #endif
   2108 }
   2109 
   2110 #ifndef SMALL
   2111 /* this is used with -r to recursively descend directories */
   2112 static void
   2113 handle_dir(char *dir)
   2114 {
   2115 	char *path_argv[2];
   2116 	FTS *fts;
   2117 	FTSENT *entry;
   2118 
   2119 	path_argv[0] = dir;
   2120 	path_argv[1] = 0;
   2121 	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
   2122 	if (fts == NULL) {
   2123 		warn("couldn't fts_open %s", dir);
   2124 		return;
   2125 	}
   2126 
   2127 	while ((entry = fts_read(fts))) {
   2128 		switch(entry->fts_info) {
   2129 		case FTS_D:
   2130 		case FTS_DP:
   2131 			continue;
   2132 
   2133 		case FTS_DNR:
   2134 		case FTS_ERR:
   2135 		case FTS_NS:
   2136 			maybe_warn("%s", entry->fts_path);
   2137 			continue;
   2138 		case FTS_F:
   2139 			handle_file(entry->fts_path, entry->fts_statp);
   2140 		}
   2141 	}
   2142 	(void)fts_close(fts);
   2143 }
   2144 #endif
   2145 
   2146 /* print a ratio - size reduction as a fraction of uncompressed size */
   2147 static void
   2148 print_ratio(off_t in, off_t out, FILE *where)
   2149 {
   2150 	int percent10;	/* 10 * percent */
   2151 	off_t diff;
   2152 	char buff[8];
   2153 	int len;
   2154 
   2155 	diff = in - out/2;
   2156 	if (in == 0 && out == 0)
   2157 		percent10 = 0;
   2158 	else if (diff < 0)
   2159 		/*
   2160 		 * Output is more than double size of input! print -99.9%
   2161 		 * Quite possibly we've failed to get the original size.
   2162 		 */
   2163 		percent10 = -999;
   2164 	else {
   2165 		/*
   2166 		 * We only need 12 bits of result from the final division,
   2167 		 * so reduce the values until a 32bit division will suffice.
   2168 		 */
   2169 		while (in > 0x100000) {
   2170 			diff >>= 1;
   2171 			in >>= 1;
   2172 		}
   2173 		if (in != 0)
   2174 			percent10 = ((u_int)diff * 2000) / (u_int)in - 1000;
   2175 		else
   2176 			percent10 = 0;
   2177 	}
   2178 
   2179 	len = snprintf(buff, sizeof buff, "%2.2d.", percent10);
   2180 	/* Move the '.' to before the last digit */
   2181 	buff[len - 1] = buff[len - 2];
   2182 	buff[len - 2] = '.';
   2183 	fprintf(where, "%5s%%", buff);
   2184 }
   2185 
   2186 #ifndef SMALL
   2187 /* print compression statistics, and the new name (if there is one!) */
   2188 static void
   2189 print_verbage(const char *file, const char *nfile, off_t usize, off_t gsize)
   2190 {
   2191 	if (file)
   2192 		fprintf(stderr, "%s:%s  ", file,
   2193 		    strlen(file) < 7 ? "\t\t" : "\t");
   2194 	print_ratio(usize, gsize, stderr);
   2195 	if (nfile)
   2196 		fprintf(stderr, " -- replaced with %s", nfile);
   2197 	fprintf(stderr, "\n");
   2198 	fflush(stderr);
   2199 }
   2200 
   2201 /* print test results */
   2202 static void
   2203 print_test(const char *file, int ok)
   2204 {
   2205 
   2206 	if (exit_value == 0 && ok == 0)
   2207 		exit_value = 1;
   2208 	fprintf(stderr, "%s:%s  %s\n", file,
   2209 	    strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK");
   2210 	fflush(stderr);
   2211 }
   2212 #endif
   2213 
   2214 /* print a file's info ala --list */
   2215 /* eg:
   2216   compressed uncompressed  ratio uncompressed_name
   2217       354841      1679360  78.8% /usr/pkgsrc/distfiles/libglade-2.0.1.tar
   2218 */
   2219 static void
   2220 print_list(int fd, off_t out, const char *outfile, time_t ts)
   2221 {
   2222 	static int first = 1;
   2223 #ifndef SMALL
   2224 	static off_t in_tot, out_tot;
   2225 	uint32_t crc = 0;
   2226 #endif
   2227 	off_t in = 0, rv;
   2228 
   2229 	if (first) {
   2230 #ifndef SMALL
   2231 		if (vflag)
   2232 			printf("method  crc     date  time  ");
   2233 #endif
   2234 		if (qflag == 0)
   2235 			printf("  compressed uncompressed  "
   2236 			       "ratio uncompressed_name\n");
   2237 	}
   2238 	first = 0;
   2239 
   2240 	/* print totals? */
   2241 #ifndef SMALL
   2242 	if (fd == -1) {
   2243 		in = in_tot;
   2244 		out = out_tot;
   2245 	} else
   2246 #endif
   2247 	{
   2248 		/* read the last 4 bytes - this is the uncompressed size */
   2249 		rv = lseek(fd, (off_t)(-8), SEEK_END);
   2250 		if (rv != -1) {
   2251 			unsigned char buf[8];
   2252 			uint32_t usize;
   2253 
   2254 			rv = read(fd, (char *)buf, sizeof(buf));
   2255 			if (rv == -1)
   2256 				maybe_warn("read of uncompressed size");
   2257 			else if (rv != sizeof(buf))
   2258 				maybe_warnx("read of uncompressed size");
   2259 
   2260 			else {
   2261 				usize = buf[4];
   2262 				usize |= (unsigned int)buf[5] << 8;
   2263 				usize |= (unsigned int)buf[6] << 16;
   2264 				usize |= (unsigned int)buf[7] << 24;
   2265 				in = (off_t)usize;
   2266 #ifndef SMALL
   2267 				crc = buf[0];
   2268 				crc |= (unsigned int)buf[1] << 8;
   2269 				crc |= (unsigned int)buf[2] << 16;
   2270 				crc |= (unsigned int)buf[3] << 24;
   2271 #endif
   2272 			}
   2273 		}
   2274 	}
   2275 
   2276 #ifndef SMALL
   2277 	if (vflag && fd == -1)
   2278 		printf("                            ");
   2279 	else if (vflag) {
   2280 		char *date = ctime(&ts);
   2281 
   2282 		/* skip the day, 1/100th second, and year */
   2283 		date += 4;
   2284 		date[12] = 0;
   2285 		printf("%5s %08x %11s ", "defla"/*XXX*/, crc, date);
   2286 	}
   2287 	in_tot += in;
   2288 	out_tot += out;
   2289 #endif
   2290 	print_list_out(out, in, outfile);
   2291 }
   2292 
   2293 static void
   2294 print_list_out(off_t out, off_t in, const char *outfile)
   2295 {
   2296 	printf("%12llu %12llu ", (unsigned long long)out, (unsigned long long)in);
   2297 	print_ratio(in, out, stdout);
   2298 	printf(" %s\n", outfile);
   2299 }
   2300 
   2301 #ifndef SMALL
   2302 static void
   2303 license(void)
   2304 {
   2305 	fprintf(stderr, "%s\n", gzip_version);
   2306 	fprintf(stderr, "%s\n",
   2307 	    "Copyright (c) 1997-2026 Matthew R. Green\n"
   2308 	    "All rights reserved.\n"
   2309 	    "Redistribution and use in source and binary forms, with or without\n"
   2310 	    "modification, are permitted provided that the following conditions\n"
   2311 	    "are met:\n"
   2312 	    "1. Redistributions of source code must retain the above copyright\n"
   2313 	       "notice, this list of conditions and the following disclaimer.\n"
   2314 	    "2. Redistributions in binary form must reproduce the above copyright\n"
   2315 	       "notice, this list of conditions and the following disclaimer in the\n"
   2316 	       "documentation and/or other materials provided with the distribution.");
   2317 	exit(0);
   2318 }
   2319 #endif
   2320 
   2321 /* display the usage of NetBSD gzip */
   2322 static void
   2323 usage(void)
   2324 {
   2325 
   2326 	fprintf(stderr, "%s\n", gzip_version);
   2327 	fprintf(stderr,
   2328     "usage: %s [-" OPT_LIST "] [<file> [<file> ...]]\n"
   2329 #ifndef SMALL
   2330     " -1 --fast            fastest (worst) compression\n"
   2331     " -2 .. -8             set compression level\n"
   2332     " -9 --best            best (slowest) compression\n"
   2333     " -a --ascii           ignored on NetBSD\n"
   2334     " -c --stdout          write to stdout, keep original files\n"
   2335     "    --to-stdout\n"
   2336     " -d --decompress      uncompress files\n"
   2337     "    --uncompress\n"
   2338     " -f --force           force overwriting & compress links\n"
   2339     " -h --help            display this help\n"
   2340     " -k --keep            don't delete input files during operation\n"
   2341     " -L --license         show the program copyright and license\n"
   2342     " -l --list            list compressed file contents\n"
   2343     " -N --name            save or restore original file name and time stamp\n"
   2344     " -n --no-name         don't save original file name or time stamp\n"
   2345     " -q --quiet           output no warnings\n"
   2346     " -r --recursive       recursively compress files in directories\n"
   2347     " -S .suf              use suffix .suf instead of .gz\n"
   2348     "    --suffix .suf\n"
   2349     " -t --test            test compressed file\n"
   2350     " -V --version         display program version\n"
   2351     " -v --verbose         print extra statistics\n",
   2352 #else
   2353     ,
   2354 #endif
   2355 	    getprogname());
   2356 	exit(0);
   2357 }
   2358 
   2359 /* display the version of NetBSD gzip */
   2360 static void
   2361 display_version(void)
   2362 {
   2363 
   2364 	fprintf(stderr, "%s\n", gzip_version);
   2365 	exit(0);
   2366 }
   2367 
   2368 #ifndef NO_BZIP2_SUPPORT
   2369 #include "unbzip2.c"
   2370 #endif
   2371 #ifndef NO_COMPRESS_SUPPORT
   2372 #include "zuncompress.c"
   2373 #endif
   2374 #ifndef NO_PACK_SUPPORT
   2375 #include "unpack.c"
   2376 #endif
   2377 #ifndef NO_XZ_SUPPORT
   2378 #include "unxz.c"
   2379 #endif
   2380 #ifndef NO_ZSTD_SUPPORT
   2381 #include "unzstd.c"
   2382 #endif
   2383 #ifndef NO_LZ_SUPPORT
   2384 #include "unlz.c"
   2385 #endif
   2386 
   2387 static ssize_t
   2388 read_retry(int fd, void *buf, size_t sz)
   2389 {
   2390 	char *cp = buf;
   2391 	size_t left = MIN(sz, (size_t) SSIZE_MAX);
   2392 
   2393 	while (left > 0) {
   2394 		ssize_t ret;
   2395 
   2396 		ret = read(fd, cp, left);
   2397 		if (ret == -1) {
   2398 			return ret;
   2399 		} else if (ret == 0) {
   2400 			break; /* EOF */
   2401 		}
   2402 		cp += ret;
   2403 		left -= ret;
   2404 	}
   2405 
   2406 	return sz - left;
   2407 }
   2408 
   2409 static ssize_t
   2410 write_retry(int fd, const void *buf, size_t sz)
   2411 {
   2412 	const char *cp = buf;
   2413 	size_t left = MIN(sz, (size_t) SSIZE_MAX);
   2414 
   2415 	while (left > 0) {
   2416 		ssize_t ret;
   2417 
   2418 		ret = write(fd, cp, left);
   2419 		if (ret == -1) {
   2420 			return ret;
   2421 		} else if (ret == 0) {
   2422 			abort();	/* Can't happen */
   2423 		}
   2424 		cp += ret;
   2425 		left -= ret;
   2426 	}
   2427 
   2428 	return sz - left;
   2429 }
   2430