gzip.c revision 194508
1206084Srdivacky/*	$NetBSD: gzip.c,v 1.92 2008/07/21 14:19:22 lukem Exp $	*/
2198092Srdivacky
3353358Sdim/*-
4353358Sdim * Copyright (c) 1997, 1998, 2003, 2004, 2006 Matthew R. Green
5353358Sdim * All rights reserved.
6198092Srdivacky *
7198092Srdivacky * Redistribution and use in source and binary forms, with or without
8198092Srdivacky * modification, are permitted provided that the following conditions
9206084Srdivacky * are met:
10198092Srdivacky * 1. Redistributions of source code must retain the above copyright
11198092Srdivacky *    notice, this list of conditions and the following disclaimer.
12198092Srdivacky * 2. Redistributions in binary form must reproduce the above copyright
13206084Srdivacky *    notice, this list of conditions and the following disclaimer in the
14249423Sdim *    documentation and/or other materials provided with the distribution.
15249423Sdim *
16198092Srdivacky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17198092Srdivacky * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18218893Sdim * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19198092Srdivacky * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20198092Srdivacky * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21198092Srdivacky * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22344779Sdim * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23249423Sdim * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24249423Sdim * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25249423Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26207619Srdivacky * SUCH DAMAGE.
27276479Sdim *
28207619Srdivacky */
29198092Srdivacky
30198092Srdivacky#include <sys/cdefs.h>
31198092Srdivacky#ifndef lint
32218893Sdim__COPYRIGHT("@(#) Copyright (c) 1997, 1998, 2003, 2004, 2006\
33276479Sdim Matthew R. Green.  All rights reserved.");
34276479Sdim__RCSID("$FreeBSD: head/usr.bin/gzip/gzip.c 194508 2009-06-19 19:28:21Z delphij $");
35276479Sdim#endif /* not lint */
36276479Sdim
37276479Sdim/*
38276479Sdim * gzip.c -- GPL free gzip using zlib.
39276479Sdim *
40276479Sdim * RFC 1950 covers the zlib format
41276479Sdim * RFC 1951 covers the deflate format
42276479Sdim * RFC 1952 covers the gzip format
43276479Sdim *
44276479Sdim * TODO:
45276479Sdim *	- use mmap where possible
46276479Sdim *	- handle some signals better (remove outfile?)
47276479Sdim *	- make bzip2/compress -v/-t/-l support work as well as possible
48276479Sdim */
49276479Sdim
50276479Sdim#include <sys/param.h>
51276479Sdim#include <sys/stat.h>
52276479Sdim#include <sys/time.h>
53276479Sdim
54276479Sdim#include <inttypes.h>
55276479Sdim#include <unistd.h>
56276479Sdim#include <stdio.h>
57276479Sdim#include <string.h>
58276479Sdim#include <stdlib.h>
59276479Sdim#include <err.h>
60276479Sdim#include <errno.h>
61276479Sdim#include <fcntl.h>
62276479Sdim#include <zlib.h>
63276479Sdim#include <fts.h>
64341825Sdim#include <libgen.h>
65276479Sdim#include <stdarg.h>
66276479Sdim#include <getopt.h>
67276479Sdim#include <time.h>
68276479Sdim
69276479Sdim#ifndef PRIdOFF
70276479Sdim#define PRIdOFF PRId64
71276479Sdim#endif
72276479Sdim
73276479Sdim/* what type of file are we dealing with */
74276479Sdimenum filetype {
75276479Sdim	FT_GZIP,
76341825Sdim#ifndef NO_BZIP2_SUPPORT
77276479Sdim	FT_BZIP2,
78276479Sdim#endif
79276479Sdim#ifndef NO_COMPRESS_SUPPORT
80276479Sdim	FT_Z,
81276479Sdim#endif
82276479Sdim	FT_LAST,
83276479Sdim	FT_UNKNOWN
84276479Sdim};
85276479Sdim
86276479Sdim#ifndef NO_BZIP2_SUPPORT
87276479Sdim#include <bzlib.h>
88276479Sdim
89276479Sdim#define BZ2_SUFFIX	".bz2"
90276479Sdim#define BZIP2_MAGIC	"\102\132\150"
91276479Sdim#endif
92276479Sdim
93276479Sdim#ifndef NO_COMPRESS_SUPPORT
94276479Sdim#define Z_SUFFIX	".Z"
95280031Sdim#define Z_MAGIC		"\037\235"
96276479Sdim#endif
97341825Sdim
98276479Sdim#define GZ_SUFFIX	".gz"
99276479Sdim
100276479Sdim#define BUFLEN		(64 * 1024)
101288943Sdim
102288943Sdim#define GZIP_MAGIC0	0x1F
103288943Sdim#define GZIP_MAGIC1	0x8B
104288943Sdim#define GZIP_OMAGIC1	0x9E
105288943Sdim
106288943Sdim#define GZIP_TIMESTAMP	(off_t)4
107288943Sdim#define GZIP_ORIGNAME	(off_t)10
108276479Sdim
109276479Sdim#define HEAD_CRC	0x02
110276479Sdim#define EXTRA_FIELD	0x04
111288943Sdim#define ORIG_NAME	0x08
112288943Sdim#define COMMENT		0x10
113288943Sdim
114288943Sdim#define OS_CODE		3	/* Unix */
115288943Sdim
116288943Sdimtypedef struct {
117288943Sdim    const char	*zipped;
118288943Sdim    int		ziplen;
119288943Sdim    const char	*normal;	/* for unzip - must not be longer than zipped */
120341825Sdim} suffixes_t;
121276479Sdimstatic suffixes_t suffixes[] = {
122276479Sdim#define	SUFFIX(Z, N) {Z, sizeof Z - 1, N}
123309124Sdim	SUFFIX(GZ_SUFFIX,	""),	/* Overwritten by -S .xxx */
124276479Sdim#ifndef SMALL
125341825Sdim	SUFFIX(GZ_SUFFIX,	""),
126276479Sdim	SUFFIX(".z",		""),
127276479Sdim	SUFFIX("-gz",		""),
128276479Sdim	SUFFIX("-z",		""),
129276479Sdim	SUFFIX("_z",		""),
130276479Sdim	SUFFIX(".taz",		".tar"),
131276479Sdim	SUFFIX(".tgz",		".tar"),
132341825Sdim#ifndef NO_BZIP2_SUPPORT
133276479Sdim	SUFFIX(BZ2_SUFFIX,	""),
134276479Sdim	SUFFIX(".tbz",		".tar"),
135276479Sdim	SUFFIX(".tbz2",		".tar"),
136288943Sdim#endif
137288943Sdim#ifndef NO_COMPRESS_SUPPORT
138288943Sdim	SUFFIX(Z_SUFFIX,	""),
139276479Sdim#endif
140276479Sdim	SUFFIX(GZ_SUFFIX,	""),	/* Overwritten by -S "" */
141341825Sdim#endif /* SMALL */
142276479Sdim#undef SUFFIX
143276479Sdim};
144276479Sdim#define NUM_SUFFIXES (sizeof suffixes / sizeof suffixes[0])
145276479Sdim
146276479Sdimstatic	const char	gzip_version[] = "FreeBSD gzip 20070711";
147276479Sdim
148276479Sdim#ifndef SMALL
149276479Sdimstatic	const char	gzip_copyright[] = \
150276479Sdim"   Copyright (c) 1997, 1998, 2003, 2004, 2006 Matthew R. Green\n"
151276479Sdim"   All rights reserved.\n"
152276479Sdim"\n"
153276479Sdim"   Redistribution and use in source and binary forms, with or without\n"
154276479Sdim"   modification, are permitted provided that the following conditions\n"
155288943Sdim"   are met:\n"
156276479Sdim"   1. Redistributions of source code must retain the above copyright\n"
157276479Sdim"      notice, this list of conditions and the following disclaimer.\n"
158288943Sdim"   2. Redistributions in binary form must reproduce the above copyright\n"
159276479Sdim"      notice, this list of conditions and the following disclaimer in the\n"
160276479Sdim"      documentation and/or other materials provided with the distribution.\n"
161276479Sdim"\n"
162276479Sdim"   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
163276479Sdim"   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
164276479Sdim"   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
165276479Sdim"   IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
166276479Sdim"   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n"
167276479Sdim"   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n"
168341825Sdim"   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n"
169276479Sdim"   AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n"
170341825Sdim"   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n"
171276479Sdim"   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n"
172276479Sdim"   SUCH DAMAGE.";
173276479Sdim#endif
174276479Sdim
175276479Sdimstatic	int	cflag;			/* stdout mode */
176276479Sdimstatic	int	dflag;			/* decompress mode */
177276479Sdimstatic	int	lflag;			/* list mode */
178276479Sdimstatic	int	numflag = 6;		/* gzip -1..-9 value */
179341825Sdim
180276479Sdim#ifndef SMALL
181276479Sdimstatic	int	fflag;			/* force mode */
182276479Sdimstatic	int	kflag;			/* don't delete input files */
183341825Sdimstatic	int	nflag;			/* don't save name/timestamp */
184276479Sdimstatic	int	Nflag;			/* don't restore name/timestamp */
185276479Sdimstatic	int	qflag;			/* quiet mode */
186341825Sdimstatic	int	rflag;			/* recursive mode */
187280031Sdimstatic	int	tflag;			/* test */
188341825Sdimstatic	int	vflag;			/* verbose mode */
189276479Sdim#else
190341825Sdim#define		qflag	0
191276479Sdim#define		tflag	0
192276479Sdim#endif
193276479Sdim
194276479Sdimstatic	int	exit_value = 0;		/* exit value */
195276479Sdim
196276479Sdimstatic	char	*infile;		/* name of file coming in */
197276479Sdim
198276479Sdimstatic	void	maybe_err(const char *fmt, ...) __dead2
199276479Sdim    __attribute__((__format__(__printf__, 1, 2)));
200276479Sdim#ifndef NO_BZIP2_SUPPORT
201276479Sdimstatic	void	maybe_errx(const char *fmt, ...) __dead2
202226633Sdim    __attribute__((__format__(__printf__, 1, 2)));
203218893Sdim#endif
204218893Sdimstatic	void	maybe_warn(const char *fmt, ...)
205218893Sdim    __attribute__((__format__(__printf__, 1, 2)));
206218893Sdimstatic	void	maybe_warnx(const char *fmt, ...)
207276479Sdim    __attribute__((__format__(__printf__, 1, 2)));
208276479Sdimstatic	enum filetype file_gettype(u_char *);
209276479Sdim#ifdef SMALL
210206084Srdivacky#define gz_compress(if, of, sz, fn, tm) gz_compress(if, of, sz)
211288943Sdim#endif
212288943Sdimstatic	off_t	gz_compress(int, int, off_t *, const char *, uint32_t);
213206084Srdivackystatic	off_t	gz_uncompress(int, int, char *, size_t, off_t *, const char *);
214276479Sdimstatic	off_t	file_compress(char *, char *, size_t);
215206084Srdivackystatic	off_t	file_uncompress(char *, char *, size_t);
216341825Sdimstatic	void	handle_pathname(char *);
217341825Sdimstatic	void	handle_file(char *, struct stat *);
218341825Sdimstatic	void	handle_stdin(void);
219341825Sdimstatic	void	handle_stdout(void);
220341825Sdimstatic	void	print_ratio(off_t, off_t, FILE *);
221341825Sdimstatic	void	print_list(int fd, off_t, const char *, time_t);
222341825Sdimstatic	void	usage(void);
223206084Srdivackystatic	void	display_version(void);
224276479Sdim#ifndef SMALL
225276479Sdimstatic	void	display_license(void);
226276479Sdim#endif
227276479Sdimstatic	const suffixes_t *check_suffix(char *, int);
228276479Sdimstatic	ssize_t	read_retry(int, void *, size_t);
229276479Sdim
230276479Sdim#ifdef SMALL
231288943Sdim#define unlink_input(f, sb) unlink(f)
232276479Sdim#else
233276479Sdimstatic	off_t	cat_fd(unsigned char *, size_t, off_t *, int fd);
234249423Sdimstatic	void	prepend_gzip(char *, int *, char ***);
235249423Sdimstatic	void	handle_dir(char *);
236249423Sdimstatic	void	print_verbage(const char *, const char *, off_t, off_t);
237249423Sdimstatic	void	print_test(const char *, int);
238276479Sdimstatic	void	copymodes(int fd, const struct stat *, const char *file);
239276479Sdimstatic	int	check_outfile(const char *outfile);
240249423Sdim#endif
241207619Srdivacky
242276479Sdim#ifndef NO_BZIP2_SUPPORT
243276479Sdimstatic	off_t	unbzip2(int, int, char *, size_t, off_t *);
244276479Sdim#endif
245276479Sdim
246276479Sdim#ifndef NO_COMPRESS_SUPPORT
247276479Sdimstatic	FILE 	*zdopen(int);
248341825Sdimstatic	off_t	zuncompress(FILE *, FILE *, char *, size_t, off_t *);
249276479Sdim#endif
250276479Sdim
251276479Sdimint main(int, char **p);
252276479Sdim
253276479Sdim#ifdef SMALL
254276479Sdim#define getopt_long(a,b,c,d,e) getopt(a,b,c)
255276479Sdim#else
256276479Sdimstatic const struct option longopts[] = {
257276479Sdim	{ "stdout",		no_argument,		0,	'c' },
258276479Sdim	{ "to-stdout",		no_argument,		0,	'c' },
259276479Sdim	{ "decompress",		no_argument,		0,	'd' },
260276479Sdim	{ "uncompress",		no_argument,		0,	'd' },
261276479Sdim	{ "force",		no_argument,		0,	'f' },
262276479Sdim	{ "help",		no_argument,		0,	'h' },
263276479Sdim	{ "keep",		no_argument,		0,	'k' },
264276479Sdim	{ "list",		no_argument,		0,	'l' },
265276479Sdim	{ "no-name",		no_argument,		0,	'n' },
266276479Sdim	{ "name",		no_argument,		0,	'N' },
267276479Sdim	{ "quiet",		no_argument,		0,	'q' },
268276479Sdim	{ "recursive",		no_argument,		0,	'r' },
269276479Sdim	{ "suffix",		required_argument,	0,	'S' },
270276479Sdim	{ "test",		no_argument,		0,	't' },
271276479Sdim	{ "verbose",		no_argument,		0,	'v' },
272276479Sdim	{ "version",		no_argument,		0,	'V' },
273276479Sdim	{ "fast",		no_argument,		0,	'1' },
274276479Sdim	{ "best",		no_argument,		0,	'9' },
275249423Sdim	{ "ascii",		no_argument,		0,	'a' },
276353358Sdim	{ "license",		no_argument,		0,	'L' },
277276479Sdim	{ NULL,			no_argument,		0,	0 },
278276479Sdim};
279280031Sdim#endif
280276479Sdim
281276479Sdimint
282276479Sdimmain(int argc, char **argv)
283276479Sdim{
284198092Srdivacky	const char *progname = getprogname();
285198092Srdivacky#ifndef SMALL
286276479Sdim	char *gzip;
287276479Sdim	int len;
288276479Sdim#endif
289280031Sdim	int ch;
290276479Sdim
291276479Sdim	/* XXX set up signals */
292276479Sdim
293280031Sdim#ifndef SMALL
294276479Sdim	if ((gzip = getenv("GZIP")) != NULL)
295276479Sdim		prepend_gzip(gzip, &argc, &argv);
296276479Sdim#endif
297341825Sdim
298276479Sdim	/*
299276479Sdim	 * XXX
300276479Sdim	 * handle being called `gunzip', `zcat' and `gzcat'
301276479Sdim	 */
302276479Sdim	if (strcmp(progname, "gunzip") == 0)
303198092Srdivacky		dflag = 1;
304276479Sdim	else if (strcmp(progname, "zcat") == 0 ||
305276479Sdim		 strcmp(progname, "gzcat") == 0)
306280031Sdim		dflag = cflag = 1;
307280031Sdim
308280031Sdim#ifdef SMALL
309280031Sdim#define OPT_LIST "123456789cdhltV"
310280031Sdim#else
311280031Sdim#define OPT_LIST "123456789acdfhklLNnqrS:tVv"
312288943Sdim#endif
313288943Sdim
314288943Sdim	while ((ch = getopt_long(argc, argv, OPT_LIST, longopts, NULL)) != -1) {
315341825Sdim		switch (ch) {
316341825Sdim		case '1': case '2': case '3':
317288943Sdim		case '4': case '5': case '6':
318280031Sdim		case '7': case '8': case '9':
319280031Sdim			numflag = ch - '0';
320280031Sdim			break;
321280031Sdim		case 'c':
322280031Sdim			cflag = 1;
323280031Sdim			break;
324280031Sdim		case 'd':
325280031Sdim			dflag = 1;
326276479Sdim			break;
327276479Sdim		case 'l':
328276479Sdim			lflag = 1;
329276479Sdim			dflag = 1;
330276479Sdim			break;
331276479Sdim		case 'V':
332198092Srdivacky			display_version();
333276479Sdim			/* NOTREACHED */
334276479Sdim#ifndef SMALL
335276479Sdim		case 'a':
336276479Sdim			fprintf(stderr, "%s: option --ascii ignored on this system\n", progname);
337276479Sdim			break;
338276479Sdim		case 'f':
339276479Sdim			fflag = 1;
340276479Sdim			break;
341276479Sdim		case 'k':
342276479Sdim			kflag = 1;
343276479Sdim			break;
344276479Sdim		case 'L':
345276479Sdim			display_license();
346198092Srdivacky			/* NOT REACHED */
347276479Sdim		case 'N':
348276479Sdim			nflag = 0;
349276479Sdim			Nflag = 1;
350353358Sdim			break;
351276479Sdim		case 'n':
352276479Sdim			nflag = 1;
353276479Sdim			Nflag = 0;
354276479Sdim			break;
355276479Sdim		case 'q':
356353358Sdim			qflag = 1;
357276479Sdim			break;
358276479Sdim		case 'r':
359276479Sdim			rflag = 1;
360276479Sdim			break;
361353358Sdim		case 'S':
362353358Sdim			len = strlen(optarg);
363221345Sdim			if (len != 0) {
364353358Sdim				suffixes[0].zipped = optarg;
365198092Srdivacky				suffixes[0].ziplen = len;
366198092Srdivacky			} else {
367276479Sdim				suffixes[NUM_SUFFIXES - 1].zipped = "";
368276479Sdim				suffixes[NUM_SUFFIXES - 1].ziplen = 0;
369276479Sdim			}
370276479Sdim			break;
371276479Sdim		case 't':
372276479Sdim			cflag = 1;
373276479Sdim			tflag = 1;
374276479Sdim			dflag = 1;
375276479Sdim			break;
376276479Sdim		case 'v':
377276479Sdim			vflag = 1;
378276479Sdim			break;
379276479Sdim#endif
380288943Sdim		default:
381276479Sdim			usage();
382276479Sdim			/* NOTREACHED */
383276479Sdim		}
384341825Sdim	}
385276479Sdim	argv += optind;
386276479Sdim	argc -= optind;
387276479Sdim
388276479Sdim	if (argc == 0) {
389276479Sdim		if (dflag)	/* stdin mode */
390276479Sdim			handle_stdin();
391276479Sdim		else		/* stdout mode */
392276479Sdim			handle_stdout();
393276479Sdim	} else {
394276479Sdim		do {
395276479Sdim			handle_pathname(argv[0]);
396276479Sdim		} while (*++argv);
397276479Sdim	}
398276479Sdim#ifndef SMALL
399276479Sdim	if (qflag == 0 && lflag && argc > 1)
400276479Sdim		print_list(-1, 0, "(totals)", 0);
401276479Sdim#endif
402276479Sdim	exit(exit_value);
403276479Sdim}
404276479Sdim
405276479Sdim/* maybe print a warning */
406207619Srdivackyvoid
407327952Sdimmaybe_warn(const char *fmt, ...)
408341825Sdim{
409327952Sdim	va_list ap;
410327952Sdim
411327952Sdim	if (qflag == 0) {
412341825Sdim		va_start(ap, fmt);
413341825Sdim		vwarn(fmt, ap);
414327952Sdim		va_end(ap);
415327952Sdim	}
416341825Sdim	if (exit_value == 0)
417327952Sdim		exit_value = 1;
418341825Sdim}
419341825Sdim
420341825Sdim/* ... without an errno. */
421341825Sdimvoid
422327952Sdimmaybe_warnx(const char *fmt, ...)
423327952Sdim{
424327952Sdim	va_list ap;
425327952Sdim
426327952Sdim	if (qflag == 0) {
427327952Sdim		va_start(ap, fmt);
428327952Sdim		vwarnx(fmt, ap);
429276479Sdim		va_end(ap);
430276479Sdim	}
431276479Sdim	if (exit_value == 0)
432276479Sdim		exit_value = 1;
433276479Sdim}
434276479Sdim
435276479Sdim/* maybe print an error */
436341825Sdimvoid
437276479Sdimmaybe_err(const char *fmt, ...)
438276479Sdim{
439276479Sdim	va_list ap;
440341825Sdim
441341825Sdim	if (qflag == 0) {
442276479Sdim		va_start(ap, fmt);
443276479Sdim		vwarn(fmt, ap);
444207619Srdivacky		va_end(ap);
445234353Sdim	}
446327952Sdim	exit(2);
447327952Sdim}
448341825Sdim
449341825Sdim#ifndef NO_BZIP2_SUPPORT
450341825Sdim/* ... without an errno. */
451341825Sdimvoid
452327952Sdimmaybe_errx(const char *fmt, ...)
453327952Sdim{
454327952Sdim	va_list ap;
455327952Sdim
456327952Sdim	if (qflag == 0) {
457341825Sdim		va_start(ap, fmt);
458341825Sdim		vwarnx(fmt, ap);
459341825Sdim		va_end(ap);
460341825Sdim	}
461276479Sdim	exit(2);
462276479Sdim}
463276479Sdim#endif
464198092Srdivacky
465198092Srdivacky#ifndef SMALL
466327952Sdim/* split up $GZIP and prepend it to the argument list */
467276479Sdimstatic void
468276479Sdimprepend_gzip(char *gzip, int *argc, char ***argv)
469276479Sdim{
470276479Sdim	char *s, **nargv, **ac;
471276479Sdim	int nenvarg = 0, i;
472276479Sdim
473276479Sdim	/* scan how many arguments there are */
474276479Sdim	for (s = gzip;;) {
475276479Sdim		while (*s == ' ' || *s == '\t')
476276479Sdim			s++;
477327952Sdim		if (*s == 0)
478198092Srdivacky			goto count_done;
479276479Sdim		nenvarg++;
480198092Srdivacky		while (*s != ' ' && *s != '\t')
481276479Sdim			if (*s++ == 0)
482276479Sdim				goto count_done;
483276479Sdim	}
484276479Sdimcount_done:
485276479Sdim	/* punt early */
486276479Sdim	if (nenvarg == 0)
487198092Srdivacky		return;
488276479Sdim
489276479Sdim	*argc += nenvarg;
490276479Sdim	ac = *argv;
491276479Sdim
492288943Sdim	nargv = (char **)malloc((*argc + 1) * sizeof(char *));
493288943Sdim	if (nargv == NULL)
494288943Sdim		maybe_err("malloc");
495276479Sdim
496288943Sdim	/* stash this away */
497296417Sdim	*argv = nargv;
498276479Sdim
499276479Sdim	/* copy the program name first */
500199482Srdivacky	i = 0;
501198092Srdivacky	nargv[i++] = *(ac++);
502198092Srdivacky
503276479Sdim	/* take a copy of $GZIP and add it to the array */
504276479Sdim	s = strdup(gzip);
505276479Sdim	if (s == NULL)
506276479Sdim		maybe_err("strdup");
507276479Sdim	for (;;) {
508276479Sdim		/* Skip whitespaces. */
509276479Sdim		while (*s == ' ' || *s == '\t')
510276479Sdim			s++;
511218893Sdim		if (*s == 0)
512218893Sdim			goto copy_done;
513276479Sdim		nargv[i++] = s;
514276479Sdim		/* Find the end of this argument. */
515276479Sdim		while (*s != ' ' && *s != '\t')
516276479Sdim			if (*s++ == 0)
517276479Sdim				/* Argument followed by NUL. */
518276479Sdim				goto copy_done;
519288943Sdim		/* Terminate by overwriting ' ' or '\t' with NUL. */
520276479Sdim		*s++ = 0;
521276479Sdim	}
522276479Sdimcopy_done:
523276479Sdim
524276479Sdim	/* copy the original arguments and a NULL */
525276479Sdim	while (*ac)
526276479Sdim		nargv[i++] = *(ac++);
527276479Sdim	nargv[i] = NULL;
528276479Sdim}
529276479Sdim#endif
530276479Sdim
531276479Sdim/* compress input to output. Return bytes read, -1 on error */
532276479Sdimstatic off_t
533276479Sdimgz_compress(int in, int out, off_t *gsizep, const char *origname, uint32_t mtime)
534276479Sdim{
535276479Sdim	z_stream z;
536276479Sdim	char *outbufp, *inbufp;
537276479Sdim	off_t in_tot = 0, out_tot = 0;
538276479Sdim	ssize_t in_size;
539276479Sdim	int i, error;
540288943Sdim	uLong crc;
541288943Sdim#ifdef SMALL
542276479Sdim	static char header[] = { GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED, 0,
543276479Sdim				 0, 0, 0, 0,
544276479Sdim				 0, OS_CODE };
545276479Sdim#endif
546276479Sdim
547276479Sdim	outbufp = malloc(BUFLEN);
548276479Sdim	inbufp = malloc(BUFLEN);
549276479Sdim	if (outbufp == NULL || inbufp == NULL) {
550276479Sdim		maybe_err("malloc failed");
551276479Sdim		goto out;
552276479Sdim	}
553208600Srdivacky
554218893Sdim	memset(&z, 0, sizeof z);
555208600Srdivacky	z.zalloc = Z_NULL;
556276479Sdim	z.zfree = Z_NULL;
557276479Sdim	z.opaque = 0;
558276479Sdim
559276479Sdim#ifdef SMALL
560234353Sdim	memcpy(outbufp, header, sizeof header);
561276479Sdim	i = sizeof header;
562276479Sdim#else
563234353Sdim	if (nflag != 0) {
564234353Sdim		mtime = 0;
565234353Sdim		origname = "";
566234353Sdim	}
567276479Sdim
568276479Sdim	i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c%c%c%s",
569276479Sdim		     GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED,
570276479Sdim		     *origname ? ORIG_NAME : 0,
571276479Sdim		     mtime & 0xff,
572276479Sdim		     (mtime >> 8) & 0xff,
573218893Sdim		     (mtime >> 16) & 0xff,
574276479Sdim		     (mtime >> 24) & 0xff,
575276479Sdim		     numflag == 1 ? 4 : numflag == 9 ? 2 : 0,
576276479Sdim		     OS_CODE, origname);
577276479Sdim	if (i >= BUFLEN)
578276479Sdim		/* this need PATH_MAX > BUFLEN ... */
579276479Sdim		maybe_err("snprintf");
580276479Sdim	if (*origname)
581276479Sdim		i++;
582218893Sdim#endif
583276479Sdim
584276479Sdim	z.next_out = (unsigned char *)outbufp + i;
585218893Sdim	z.avail_out = BUFLEN - i;
586276479Sdim
587276479Sdim	error = deflateInit2(&z, numflag, Z_DEFLATED,
588276479Sdim			     (-MAX_WBITS), 8, Z_DEFAULT_STRATEGY);
589276479Sdim	if (error != Z_OK) {
590276479Sdim		maybe_warnx("deflateInit2 failed");
591276479Sdim		in_tot = -1;
592276479Sdim		goto out;
593276479Sdim	}
594218893Sdim
595276479Sdim	crc = crc32(0L, Z_NULL, 0);
596353358Sdim	for (;;) {
597276479Sdim		if (z.avail_out == 0) {
598353358Sdim			if (write(out, outbufp, BUFLEN) != BUFLEN) {
599353358Sdim				maybe_warn("write");
600353358Sdim				out_tot = -1;
601353358Sdim				goto out;
602353358Sdim			}
603353358Sdim
604353358Sdim			out_tot += BUFLEN;
605353358Sdim			z.next_out = (unsigned char *)outbufp;
606353358Sdim			z.avail_out = BUFLEN;
607218893Sdim		}
608276479Sdim
609276479Sdim		if (z.avail_in == 0) {
610276479Sdim			in_size = read(in, inbufp, BUFLEN);
611218893Sdim			if (in_size < 0) {
612218893Sdim				maybe_warn("read");
613218893Sdim				in_tot = -1;
614280031Sdim				goto out;
615280031Sdim			}
616280031Sdim			if (in_size == 0)
617276479Sdim				break;
618280031Sdim
619280031Sdim			crc = crc32(crc, (const Bytef *)inbufp, (unsigned)in_size);
620280031Sdim			in_tot += in_size;
621276479Sdim			z.next_in = (unsigned char *)inbufp;
622276479Sdim			z.avail_in = in_size;
623276479Sdim		}
624276479Sdim
625208600Srdivacky		error = deflate(&z, Z_NO_FLUSH);
626276479Sdim		if (error != Z_OK && error != Z_STREAM_END) {
627276479Sdim			maybe_warnx("deflate failed");
628276479Sdim			in_tot = -1;
629276479Sdim			goto out;
630280031Sdim		}
631280031Sdim	}
632276479Sdim
633208600Srdivacky	/* clean up */
634276479Sdim	for (;;) {
635276479Sdim		size_t len;
636276479Sdim		ssize_t w;
637276479Sdim
638280031Sdim		error = deflate(&z, Z_FINISH);
639280031Sdim		if (error != Z_OK && error != Z_STREAM_END) {
640280031Sdim			maybe_warnx("deflate failed");
641280031Sdim			in_tot = -1;
642280031Sdim			goto out;
643341825Sdim		}
644276479Sdim
645276479Sdim		len = (char *)z.next_out - outbufp;
646201361Srdivacky
647201361Srdivacky		w = write(out, outbufp, len);
648276479Sdim		if (w == -1 || (size_t)w != len) {
649276479Sdim			maybe_warn("write");
650276479Sdim			out_tot = -1;
651276479Sdim			goto out;
652276479Sdim		}
653276479Sdim		out_tot += len;
654276479Sdim		z.next_out = (unsigned char *)outbufp;
655276479Sdim		z.avail_out = BUFLEN;
656276479Sdim
657276479Sdim		if (error == Z_STREAM_END)
658276479Sdim			break;
659309124Sdim	}
660309124Sdim
661276479Sdim	if (deflateEnd(&z) != Z_OK) {
662276479Sdim		maybe_warnx("deflateEnd failed");
663218893Sdim		in_tot = -1;
664276479Sdim		goto out;
665276479Sdim	}
666276479Sdim
667276479Sdim	i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c",
668276479Sdim		 (int)crc & 0xff,
669276479Sdim		 (int)(crc >> 8) & 0xff,
670276479Sdim		 (int)(crc >> 16) & 0xff,
671353358Sdim		 (int)(crc >> 24) & 0xff,
672218893Sdim		 (int)in_tot & 0xff,
673218893Sdim		 (int)(in_tot >> 8) & 0xff,
674276479Sdim		 (int)(in_tot >> 16) & 0xff,
675276479Sdim		 (int)(in_tot >> 24) & 0xff);
676276479Sdim	if (i != 8)
677276479Sdim		maybe_err("snprintf");
678276479Sdim	if (write(out, outbufp, i) != i) {
679276479Sdim		maybe_warn("write");
680276479Sdim		in_tot = -1;
681276479Sdim	} else
682276479Sdim		out_tot += i;
683276479Sdim
684276479Sdimout:
685276479Sdim	if (inbufp != NULL)
686276479Sdim		free(inbufp);
687276479Sdim	if (outbufp != NULL)
688276479Sdim		free(outbufp);
689276479Sdim	if (gsizep)
690198092Srdivacky		*gsizep = out_tot;
691198092Srdivacky	return in_tot;
692198092Srdivacky}
693276479Sdim
694276479Sdim/*
695276479Sdim * uncompress input to output then close the input.  return the
696276479Sdim * uncompressed size written, and put the compressed sized read
697288943Sdim * into `*gsizep'.
698341825Sdim */
699276479Sdimstatic off_t
700276479Sdimgz_uncompress(int in, int out, char *pre, size_t prelen, off_t *gsizep,
701276479Sdim	      const char *filename)
702276479Sdim{
703276479Sdim	z_stream z;
704276479Sdim	char *outbufp, *inbufp;
705198092Srdivacky	off_t out_tot = -1, in_tot = 0;
706276479Sdim	uint32_t out_sub_tot = 0;
707198092Srdivacky	enum {
708276479Sdim		GZSTATE_MAGIC0,
709276479Sdim		GZSTATE_MAGIC1,
710276479Sdim		GZSTATE_METHOD,
711276479Sdim		GZSTATE_FLAGS,
712276479Sdim		GZSTATE_SKIPPING,
713276479Sdim		GZSTATE_EXTRA,
714276479Sdim		GZSTATE_EXTRA2,
715276479Sdim		GZSTATE_EXTRA3,
716276479Sdim		GZSTATE_ORIGNAME,
717276479Sdim		GZSTATE_COMMENT,
718276479Sdim		GZSTATE_HEAD_CRC1,
719200583Srdivacky		GZSTATE_HEAD_CRC2,
720206084Srdivacky		GZSTATE_INIT,
721276479Sdim		GZSTATE_READ,
722276479Sdim		GZSTATE_CRC,
723276479Sdim		GZSTATE_LEN,
724276479Sdim	} state = GZSTATE_MAGIC0;
725276479Sdim	int flags = 0, skip_count = 0;
726276479Sdim	int error = Z_STREAM_ERROR, done_reading = 0;
727198092Srdivacky	uLong crc = 0;
728234353Sdim	ssize_t wr;
729288943Sdim	int needmore = 0;
730198092Srdivacky
731198092Srdivacky#define ADVANCE()       { z.next_in++; z.avail_in--; }
732224145Sdim
733224145Sdim	if ((outbufp = malloc(BUFLEN)) == NULL) {
734280031Sdim		maybe_err("malloc failed");
735198092Srdivacky		goto out2;
736280031Sdim	}
737198092Srdivacky	if ((inbufp = malloc(BUFLEN)) == NULL) {
738218893Sdim		maybe_err("malloc failed");
739276479Sdim		goto out1;
740276479Sdim	}
741276479Sdim
742276479Sdim	memset(&z, 0, sizeof z);
743280031Sdim	z.avail_in = prelen;
744280031Sdim	z.next_in = (unsigned char *)pre;
745276479Sdim	z.avail_out = BUFLEN;
746276479Sdim	z.next_out = (unsigned char *)outbufp;
747276479Sdim	z.zalloc = NULL;
748280031Sdim	z.zfree = NULL;
749280031Sdim	z.opaque = 0;
750280031Sdim
751280031Sdim	in_tot = prelen;
752276479Sdim	out_tot = 0;
753218893Sdim
754218893Sdim	for (;;) {
755276479Sdim		if ((z.avail_in == 0 || needmore) && done_reading == 0) {
756276479Sdim			ssize_t in_size;
757276479Sdim
758276479Sdim			if (z.avail_in > 0) {
759276479Sdim				memmove(inbufp, z.next_in, z.avail_in);
760206084Srdivacky			}
761218893Sdim			z.next_in = (unsigned char *)inbufp;
762276479Sdim			in_size = read(in, z.next_in + z.avail_in,
763206084Srdivacky			    BUFLEN - z.avail_in);
764218893Sdim
765218893Sdim			if (in_size == -1) {
766208600Srdivacky				maybe_warn("failed to read stdin");
767198092Srdivacky				goto stop_and_fail;
768218893Sdim			} else if (in_size == 0) {
769198092Srdivacky				done_reading = 1;
770198092Srdivacky			}
771218893Sdim
772198092Srdivacky			z.avail_in += in_size;
773207619Srdivacky			needmore = 0;
774234353Sdim
775261991Sdim			in_tot += in_size;
776261991Sdim		}
777261991Sdim		if (z.avail_in == 0) {
778261991Sdim			if (done_reading && state != GZSTATE_MAGIC0) {
779261991Sdim				maybe_warnx("%s: unexpected end of file",
780207619Srdivacky					    filename);
781207619Srdivacky				goto stop_and_fail;
782207619Srdivacky			}
783207619Srdivacky			goto stop;
784218893Sdim		}
785218893Sdim		switch (state) {
786218893Sdim		case GZSTATE_MAGIC0:
787243830Sdim			if (*z.next_in != GZIP_MAGIC0) {
788207619Srdivacky				if (in_tot > 0) {
789207619Srdivacky					maybe_warnx("%s: trailing garbage "
790218893Sdim						    "ignored", filename);
791218893Sdim					goto stop;
792218893Sdim				}
793341825Sdim				maybe_warnx("input not gziped (MAGIC0)");
794276479Sdim				goto stop_and_fail;
795218893Sdim			}
796341825Sdim			ADVANCE();
797243830Sdim			state++;
798218893Sdim			out_sub_tot = 0;
799218893Sdim			crc = crc32(0L, Z_NULL, 0);
800341825Sdim			break;
801207619Srdivacky
802341825Sdim		case GZSTATE_MAGIC1:
803243830Sdim			if (*z.next_in != GZIP_MAGIC1 &&
804207619Srdivacky			    *z.next_in != GZIP_OMAGIC1) {
805207619Srdivacky				maybe_warnx("input not gziped (MAGIC1)");
806207619Srdivacky				goto stop_and_fail;
807207619Srdivacky			}
808207619Srdivacky			ADVANCE();
809207619Srdivacky			state++;
810353358Sdim			break;
811353358Sdim
812353358Sdim		case GZSTATE_METHOD:
813353358Sdim			if (*z.next_in != Z_DEFLATED) {
814207619Srdivacky				maybe_warnx("unknown compression method");
815207619Srdivacky				goto stop_and_fail;
816207619Srdivacky			}
817207619Srdivacky			ADVANCE();
818207619Srdivacky			state++;
819207619Srdivacky			break;
820207619Srdivacky
821207619Srdivacky		case GZSTATE_FLAGS:
822341825Sdim			flags = *z.next_in;
823207619Srdivacky			ADVANCE();
824261991Sdim			skip_count = 6;
825207619Srdivacky			state++;
826249423Sdim			break;
827207619Srdivacky
828249423Sdim		case GZSTATE_SKIPPING:
829207619Srdivacky			if (skip_count > 0) {
830249423Sdim				skip_count--;
831249423Sdim				ADVANCE();
832249423Sdim			} else
833249423Sdim				state++;
834249423Sdim			break;
835249423Sdim
836249423Sdim		case GZSTATE_EXTRA:
837249423Sdim			if ((flags & EXTRA_FIELD) == 0) {
838249423Sdim				state = GZSTATE_ORIGNAME;
839249423Sdim				break;
840249423Sdim			}
841249423Sdim			skip_count = *z.next_in;
842249423Sdim			ADVANCE();
843249423Sdim			state++;
844249423Sdim			break;
845249423Sdim
846249423Sdim		case GZSTATE_EXTRA2:
847249423Sdim			skip_count |= ((*z.next_in) << 8);
848249423Sdim			ADVANCE();
849249423Sdim			state++;
850249423Sdim			break;
851207619Srdivacky
852249423Sdim		case GZSTATE_EXTRA3:
853249423Sdim			if (skip_count > 0) {
854249423Sdim				skip_count--;
855207619Srdivacky				ADVANCE();
856207619Srdivacky			} else
857207619Srdivacky				state++;
858206084Srdivacky			break;
859198092Srdivacky
860207619Srdivacky		case GZSTATE_ORIGNAME:
861226633Sdim			if ((flags & ORIG_NAME) == 0) {
862207619Srdivacky				state++;
863218893Sdim				break;
864218893Sdim			}
865341825Sdim			if (*z.next_in == 0)
866212904Sdim				state++;
867207619Srdivacky			ADVANCE();
868207619Srdivacky			break;
869207619Srdivacky
870207619Srdivacky		case GZSTATE_COMMENT:
871207619Srdivacky			if ((flags & COMMENT) == 0) {
872207619Srdivacky				state++;
873207619Srdivacky				break;
874207619Srdivacky			}
875207619Srdivacky			if (*z.next_in == 0)
876207619Srdivacky				state++;
877207619Srdivacky			ADVANCE();
878207619Srdivacky			break;
879207619Srdivacky
880207619Srdivacky		case GZSTATE_HEAD_CRC1:
881207619Srdivacky			if (flags & HEAD_CRC)
882207619Srdivacky				skip_count = 2;
883207619Srdivacky			else
884207619Srdivacky				skip_count = 0;
885207619Srdivacky			state++;
886207619Srdivacky			break;
887207619Srdivacky
888207619Srdivacky		case GZSTATE_HEAD_CRC2:
889207619Srdivacky			if (skip_count > 0) {
890207619Srdivacky				skip_count--;
891309124Sdim				ADVANCE();
892207619Srdivacky			} else
893207619Srdivacky				state++;
894207619Srdivacky			break;
895226633Sdim
896249423Sdim		case GZSTATE_INIT:
897249423Sdim			if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
898249423Sdim				maybe_warnx("failed to inflateInit");
899249423Sdim				goto stop_and_fail;
900249423Sdim			}
901288943Sdim			state++;
902207619Srdivacky			break;
903207619Srdivacky
904309124Sdim		case GZSTATE_READ:
905207619Srdivacky			error = inflate(&z, Z_FINISH);
906207619Srdivacky			switch (error) {
907			/* Z_BUF_ERROR goes with Z_FINISH... */
908			case Z_BUF_ERROR:
909			case Z_STREAM_END:
910			case Z_OK:
911				break;
912
913			case Z_NEED_DICT:
914				maybe_warnx("Z_NEED_DICT error");
915				goto stop_and_fail;
916			case Z_DATA_ERROR:
917				maybe_warnx("data stream error");
918				goto stop_and_fail;
919			case Z_STREAM_ERROR:
920				maybe_warnx("internal stream error");
921				goto stop_and_fail;
922			case Z_MEM_ERROR:
923				maybe_warnx("memory allocation error");
924				goto stop_and_fail;
925
926			default:
927				maybe_warn("unknown error from inflate(): %d",
928				    error);
929			}
930			wr = BUFLEN - z.avail_out;
931
932			if (wr != 0) {
933				crc = crc32(crc, (const Bytef *)outbufp, (unsigned)wr);
934				if (
935#ifndef SMALL
936				    /* don't write anything with -t */
937				    tflag == 0 &&
938#endif
939				    write(out, outbufp, wr) != wr) {
940					maybe_warn("error writing to output");
941					goto stop_and_fail;
942				}
943
944				out_tot += wr;
945				out_sub_tot += wr;
946			}
947
948			if (error == Z_STREAM_END) {
949				inflateEnd(&z);
950				state++;
951			}
952
953			z.next_out = (unsigned char *)outbufp;
954			z.avail_out = BUFLEN;
955
956			break;
957		case GZSTATE_CRC:
958			{
959				uLong origcrc;
960
961				if (z.avail_in < 4) {
962					if (!done_reading) {
963						needmore = 1;
964						continue;
965					}
966					maybe_warnx("truncated input");
967					goto stop_and_fail;
968				}
969				origcrc = ((unsigned)z.next_in[0] & 0xff) |
970					((unsigned)z.next_in[1] & 0xff) << 8 |
971					((unsigned)z.next_in[2] & 0xff) << 16 |
972					((unsigned)z.next_in[3] & 0xff) << 24;
973				if (origcrc != crc) {
974					maybe_warnx("invalid compressed"
975					     " data--crc error");
976					goto stop_and_fail;
977				}
978			}
979
980			z.avail_in -= 4;
981			z.next_in += 4;
982
983			if (!z.avail_in && done_reading) {
984				goto stop;
985			}
986			state++;
987			break;
988		case GZSTATE_LEN:
989			{
990				uLong origlen;
991
992				if (z.avail_in < 4) {
993					if (!done_reading) {
994						needmore = 1;
995						continue;
996					}
997					maybe_warnx("truncated input");
998					goto stop_and_fail;
999				}
1000				origlen = ((unsigned)z.next_in[0] & 0xff) |
1001					((unsigned)z.next_in[1] & 0xff) << 8 |
1002					((unsigned)z.next_in[2] & 0xff) << 16 |
1003					((unsigned)z.next_in[3] & 0xff) << 24;
1004
1005				if (origlen != out_sub_tot) {
1006					maybe_warnx("invalid compressed"
1007					     " data--length error");
1008					goto stop_and_fail;
1009				}
1010			}
1011
1012			z.avail_in -= 4;
1013			z.next_in += 4;
1014
1015			if (error < 0) {
1016				maybe_warnx("decompression error");
1017				goto stop_and_fail;
1018			}
1019			state = GZSTATE_MAGIC0;
1020			break;
1021		}
1022		continue;
1023stop_and_fail:
1024		out_tot = -1;
1025stop:
1026		break;
1027	}
1028	if (state > GZSTATE_INIT)
1029		inflateEnd(&z);
1030
1031	free(inbufp);
1032out1:
1033	free(outbufp);
1034out2:
1035	if (gsizep)
1036		*gsizep = in_tot;
1037	return (out_tot);
1038}
1039
1040#ifndef SMALL
1041/*
1042 * set the owner, mode, flags & utimes using the given file descriptor.
1043 * file is only used in possible warning messages.
1044 */
1045static void
1046copymodes(int fd, const struct stat *sbp, const char *file)
1047{
1048	struct timeval times[2];
1049	struct stat sb;
1050
1051	/*
1052	 * If we have no info on the input, give this file some
1053	 * default values and return..
1054	 */
1055	if (sbp == NULL) {
1056		mode_t mask = umask(022);
1057
1058		(void)fchmod(fd, DEFFILEMODE & ~mask);
1059		(void)umask(mask);
1060		return;
1061	}
1062	sb = *sbp;
1063
1064	/* if the chown fails, remove set-id bits as-per compress(1) */
1065	if (fchown(fd, sb.st_uid, sb.st_gid) < 0) {
1066		if (errno != EPERM)
1067			maybe_warn("couldn't fchown: %s", file);
1068		sb.st_mode &= ~(S_ISUID|S_ISGID);
1069	}
1070
1071	/* we only allow set-id and the 9 normal permission bits */
1072	sb.st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
1073	if (fchmod(fd, sb.st_mode) < 0)
1074		maybe_warn("couldn't fchmod: %s", file);
1075
1076	TIMESPEC_TO_TIMEVAL(&times[0], &sb.st_atimespec);
1077	TIMESPEC_TO_TIMEVAL(&times[1], &sb.st_mtimespec);
1078	if (futimes(fd, times) < 0)
1079		maybe_warn("couldn't utimes: %s", file);
1080
1081	/* only try flags if they exist already */
1082        if (sb.st_flags != 0 && fchflags(fd, sb.st_flags) < 0)
1083		maybe_warn("couldn't fchflags: %s", file);
1084}
1085#endif
1086
1087/* what sort of file is this? */
1088static enum filetype
1089file_gettype(u_char *buf)
1090{
1091
1092	if (buf[0] == GZIP_MAGIC0 &&
1093	    (buf[1] == GZIP_MAGIC1 || buf[1] == GZIP_OMAGIC1))
1094		return FT_GZIP;
1095	else
1096#ifndef NO_BZIP2_SUPPORT
1097	if (memcmp(buf, BZIP2_MAGIC, 3) == 0 &&
1098	    buf[3] >= '0' && buf[3] <= '9')
1099		return FT_BZIP2;
1100	else
1101#endif
1102#ifndef NO_COMPRESS_SUPPORT
1103	if (memcmp(buf, Z_MAGIC, 2) == 0)
1104		return FT_Z;
1105	else
1106#endif
1107		return FT_UNKNOWN;
1108}
1109
1110#ifndef SMALL
1111/* check the outfile is OK. */
1112static int
1113check_outfile(const char *outfile)
1114{
1115	struct stat sb;
1116	int ok = 1;
1117
1118	if (lflag == 0 && stat(outfile, &sb) == 0) {
1119		if (fflag)
1120			unlink(outfile);
1121		else if (isatty(STDIN_FILENO)) {
1122			char ans[10] = { 'n', '\0' };	/* default */
1123
1124			fprintf(stderr, "%s already exists -- do you wish to "
1125					"overwrite (y or n)? " , outfile);
1126			(void)fgets(ans, sizeof(ans) - 1, stdin);
1127			if (ans[0] != 'y' && ans[0] != 'Y') {
1128				fprintf(stderr, "\tnot overwriting\n");
1129				ok = 0;
1130			} else
1131				unlink(outfile);
1132		} else {
1133			maybe_warnx("%s already exists -- skipping", outfile);
1134			ok = 0;
1135		}
1136	}
1137	return ok;
1138}
1139
1140static void
1141unlink_input(const char *file, const struct stat *sb)
1142{
1143	struct stat nsb;
1144
1145	if (kflag)
1146		return;
1147	if (stat(file, &nsb) != 0)
1148		/* Must be gone alrady */
1149		return;
1150	if (nsb.st_dev != sb->st_dev || nsb.st_ino != sb->st_ino)
1151		/* Definitely a different file */
1152		return;
1153	unlink(file);
1154}
1155#endif
1156
1157static const suffixes_t *
1158check_suffix(char *file, int xlate)
1159{
1160	const suffixes_t *s;
1161	int len = strlen(file);
1162	char *sp;
1163
1164	for (s = suffixes; s != suffixes + NUM_SUFFIXES; s++) {
1165		/* if it doesn't fit in "a.suf", don't bother */
1166		if (s->ziplen >= len)
1167			continue;
1168		sp = file + len - s->ziplen;
1169		if (strcmp(s->zipped, sp) != 0)
1170			continue;
1171		if (xlate)
1172			strcpy(sp, s->normal);
1173		return s;
1174	}
1175	return NULL;
1176}
1177
1178/*
1179 * compress the given file: create a corresponding .gz file and remove the
1180 * original.
1181 */
1182static off_t
1183file_compress(char *file, char *outfile, size_t outsize)
1184{
1185	int in;
1186	int out;
1187	off_t size, insize;
1188#ifndef SMALL
1189	struct stat isb, osb;
1190	const suffixes_t *suff;
1191#endif
1192
1193	in = open(file, O_RDONLY);
1194	if (in == -1) {
1195		maybe_warn("can't open %s", file);
1196		return -1;
1197	}
1198
1199	if (cflag == 0) {
1200#ifndef SMALL
1201		if (fstat(in, &isb) == 0) {
1202			if (isb.st_nlink > 1 && fflag == 0) {
1203				maybe_warnx("%s has %d other link%s -- "
1204					    "skipping", file, isb.st_nlink - 1,
1205					    isb.st_nlink == 1 ? "" : "s");
1206				close(in);
1207				return -1;
1208			}
1209		}
1210
1211		if (fflag == 0 && (suff = check_suffix(file, 0))
1212		    && suff->zipped[0] != 0) {
1213			maybe_warnx("%s already has %s suffix -- unchanged",
1214				    file, suff->zipped);
1215			close(in);
1216			return -1;
1217		}
1218#endif
1219
1220		/* Add (usually) .gz to filename */
1221		if ((size_t)snprintf(outfile, outsize, "%s%s",
1222					file, suffixes[0].zipped) >= outsize)
1223			memcpy(outfile - suffixes[0].ziplen - 1,
1224				suffixes[0].zipped, suffixes[0].ziplen + 1);
1225
1226#ifndef SMALL
1227		if (check_outfile(outfile) == 0) {
1228			close(in);
1229			return -1;
1230		}
1231#endif
1232	}
1233
1234	if (cflag == 0) {
1235		out = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
1236		if (out == -1) {
1237			maybe_warn("could not create output: %s", outfile);
1238			fclose(stdin);
1239			return -1;
1240		}
1241	} else
1242		out = STDOUT_FILENO;
1243
1244	insize = gz_compress(in, out, &size, basename(file), (uint32_t)isb.st_mtime);
1245
1246	(void)close(in);
1247
1248	/*
1249	 * If there was an error, insize will be -1.
1250	 * If we compressed to stdout, just return the size.
1251	 * Otherwise stat the file and check it is the correct size.
1252	 * We only blow away the file if we can stat the output and it
1253	 * has the expected size.
1254	 */
1255	if (cflag != 0)
1256		return insize == -1 ? -1 : size;
1257
1258#ifndef SMALL
1259	if (fstat(out, &osb) != 0) {
1260		maybe_warn("couldn't stat: %s", outfile);
1261		goto bad_outfile;
1262	}
1263
1264	if (osb.st_size != size) {
1265		maybe_warnx("output file: %s wrong size (%" PRIdOFF
1266				" != %" PRIdOFF "), deleting",
1267				outfile, osb.st_size, size);
1268		goto bad_outfile;
1269	}
1270
1271	copymodes(out, &isb, outfile);
1272#endif
1273	if (close(out) == -1)
1274		maybe_warn("couldn't close output");
1275
1276	/* output is good, ok to delete input */
1277	unlink_input(file, &isb);
1278	return size;
1279
1280#ifndef SMALL
1281    bad_outfile:
1282	if (close(out) == -1)
1283		maybe_warn("couldn't close output");
1284
1285	maybe_warnx("leaving original %s", file);
1286	unlink(outfile);
1287	return size;
1288#endif
1289}
1290
1291/* uncompress the given file and remove the original */
1292static off_t
1293file_uncompress(char *file, char *outfile, size_t outsize)
1294{
1295	struct stat isb, osb;
1296	off_t size;
1297	ssize_t rbytes;
1298	unsigned char header1[4];
1299	enum filetype method;
1300	int fd, ofd, zfd = -1;
1301#ifndef SMALL
1302	time_t timestamp = 0;
1303	unsigned char name[PATH_MAX + 1];
1304#endif
1305
1306	/* gather the old name info */
1307
1308	fd = open(file, O_RDONLY);
1309	if (fd < 0) {
1310		maybe_warn("can't open %s", file);
1311		goto lose;
1312	}
1313
1314	strlcpy(outfile, file, outsize);
1315	if (check_suffix(outfile, 1) == NULL && !(cflag || lflag)) {
1316		maybe_warnx("%s: unknown suffix -- ignored", file);
1317		goto lose;
1318	}
1319
1320	rbytes = read(fd, header1, sizeof header1);
1321	if (rbytes != sizeof header1) {
1322		/* we don't want to fail here. */
1323#ifndef SMALL
1324		if (fflag)
1325			goto lose;
1326#endif
1327		if (rbytes == -1)
1328			maybe_warn("can't read %s", file);
1329		else
1330			goto unexpected_EOF;
1331		goto lose;
1332	}
1333
1334	method = file_gettype(header1);
1335
1336#ifndef SMALL
1337	if (fflag == 0 && method == FT_UNKNOWN) {
1338		maybe_warnx("%s: not in gzip format", file);
1339		goto lose;
1340	}
1341
1342#endif
1343
1344#ifndef SMALL
1345	if (method == FT_GZIP && Nflag) {
1346		unsigned char ts[4];	/* timestamp */
1347		int rv;
1348
1349		rv = pread(fd, ts, sizeof ts, GZIP_TIMESTAMP);
1350		if (rv >= 0 && (size_t)rv < sizeof ts)
1351			goto unexpected_EOF;
1352		if (rv == -1) {
1353			if (!fflag)
1354				maybe_warn("can't read %s", file);
1355			goto lose;
1356		}
1357		timestamp = ts[3] << 24 | ts[2] << 16 | ts[1] << 8 | ts[0];
1358
1359		if (header1[3] & ORIG_NAME) {
1360			rbytes = pread(fd, name, sizeof name, GZIP_ORIGNAME);
1361			if (rbytes < 0) {
1362				maybe_warn("can't read %s", file);
1363				goto lose;
1364			}
1365			if (name[0] != 0) {
1366				/* preserve original directory name */
1367				char *dp = strrchr(file, '/');
1368				if (dp == NULL)
1369					dp = file;
1370				else
1371					dp++;
1372				snprintf(outfile, outsize, "%.*s%.*s",
1373						(int) (dp - file),
1374						file, (int) rbytes, name);
1375			}
1376		}
1377	}
1378#endif
1379	lseek(fd, 0, SEEK_SET);
1380
1381	if (cflag == 0 || lflag) {
1382		if (fstat(fd, &isb) != 0)
1383			goto lose;
1384#ifndef SMALL
1385		if (isb.st_nlink > 1 && lflag == 0 && fflag == 0) {
1386			maybe_warnx("%s has %d other links -- skipping",
1387			    file, isb.st_nlink - 1);
1388			goto lose;
1389		}
1390		if (nflag == 0 && timestamp)
1391			isb.st_mtime = timestamp;
1392		if (check_outfile(outfile) == 0)
1393			goto lose;
1394#endif
1395	}
1396
1397	if (cflag == 0 && lflag == 0) {
1398		zfd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
1399		if (zfd == STDOUT_FILENO) {
1400			/* We won't close STDOUT_FILENO later... */
1401			zfd = dup(zfd);
1402			close(STDOUT_FILENO);
1403		}
1404		if (zfd == -1) {
1405			maybe_warn("can't open %s", outfile);
1406			goto lose;
1407		}
1408	} else
1409		zfd = STDOUT_FILENO;
1410
1411#ifndef NO_BZIP2_SUPPORT
1412	if (method == FT_BZIP2) {
1413
1414		/* XXX */
1415		if (lflag) {
1416			maybe_warnx("no -l with bzip2 files");
1417			goto lose;
1418		}
1419
1420		size = unbzip2(fd, zfd, NULL, 0, NULL);
1421	} else
1422#endif
1423
1424#ifndef NO_COMPRESS_SUPPORT
1425	if (method == FT_Z) {
1426		FILE *in, *out;
1427
1428		/* XXX */
1429		if (lflag) {
1430			maybe_warnx("no -l with Lempel-Ziv files");
1431			goto lose;
1432		}
1433
1434		if ((in = zdopen(fd)) == NULL) {
1435			maybe_warn("zdopen for read: %s", file);
1436			goto lose;
1437		}
1438
1439		out = fdopen(dup(zfd), "w");
1440		if (out == NULL) {
1441			maybe_warn("fdopen for write: %s", outfile);
1442			fclose(in);
1443			goto lose;
1444		}
1445
1446		size = zuncompress(in, out, NULL, 0, NULL);
1447		/* need to fclose() if ferror() is true... */
1448		if (ferror(in) | fclose(in)) {
1449			maybe_warn("failed infile fclose");
1450			unlink(outfile);
1451			(void)fclose(out);
1452		}
1453		if (fclose(out) != 0) {
1454			maybe_warn("failed outfile fclose");
1455			unlink(outfile);
1456			goto lose;
1457		}
1458	} else
1459#endif
1460
1461#ifndef SMALL
1462	if (method == FT_UNKNOWN) {
1463		if (lflag) {
1464			maybe_warnx("no -l for unknown filetypes");
1465			goto lose;
1466		}
1467		size = cat_fd(NULL, 0, NULL, fd);
1468	} else
1469#endif
1470	{
1471		if (lflag) {
1472			print_list(fd, isb.st_size, outfile, isb.st_mtime);
1473			close(fd);
1474			return -1;	/* XXX */
1475		}
1476
1477		size = gz_uncompress(fd, zfd, NULL, 0, NULL, file);
1478	}
1479
1480	if (close(fd) != 0)
1481		maybe_warn("couldn't close input");
1482	if (zfd != STDOUT_FILENO && close(zfd) != 0)
1483		maybe_warn("couldn't close output");
1484
1485	if (size == -1) {
1486		if (cflag == 0)
1487			unlink(outfile);
1488		maybe_warnx("%s: uncompress failed", file);
1489		return -1;
1490	}
1491
1492	/* if testing, or we uncompressed to stdout, this is all we need */
1493#ifndef SMALL
1494	if (tflag)
1495		return size;
1496#endif
1497	/* if we are uncompressing to stdin, don't remove the file. */
1498	if (cflag)
1499		return size;
1500
1501	/*
1502	 * if we create a file...
1503	 */
1504	/*
1505	 * if we can't stat the file don't remove the file.
1506	 */
1507
1508	ofd = open(outfile, O_RDWR, 0);
1509	if (ofd == -1) {
1510		maybe_warn("couldn't open (leaving original): %s",
1511			   outfile);
1512		return -1;
1513	}
1514	if (fstat(ofd, &osb) != 0) {
1515		maybe_warn("couldn't stat (leaving original): %s",
1516			   outfile);
1517		close(ofd);
1518		return -1;
1519	}
1520	if (osb.st_size != size) {
1521		maybe_warnx("stat gave different size: %" PRIdOFF
1522				" != %" PRIdOFF " (leaving original)",
1523				size, osb.st_size);
1524		close(ofd);
1525		unlink(outfile);
1526		return -1;
1527	}
1528	unlink_input(file, &isb);
1529#ifndef SMALL
1530	copymodes(ofd, &isb, outfile);
1531#endif
1532	close(ofd);
1533	return size;
1534
1535    unexpected_EOF:
1536	maybe_warnx("%s: unexpected end of file", file);
1537    lose:
1538	if (fd != -1)
1539		close(fd);
1540	if (zfd != -1 && zfd != STDOUT_FILENO)
1541		close(fd);
1542	return -1;
1543}
1544
1545#ifndef SMALL
1546static off_t
1547cat_fd(unsigned char * prepend, size_t count, off_t *gsizep, int fd)
1548{
1549	char buf[BUFLEN];
1550	off_t in_tot;
1551	ssize_t w;
1552
1553	in_tot = count;
1554	w = write(STDOUT_FILENO, prepend, count);
1555	if (w == -1 || (size_t)w != count) {
1556		maybe_warn("write to stdout");
1557		return -1;
1558	}
1559	for (;;) {
1560		ssize_t rv;
1561
1562		rv = read(fd, buf, sizeof buf);
1563		if (rv == 0)
1564			break;
1565		if (rv < 0) {
1566			maybe_warn("read from fd %d", fd);
1567			break;
1568		}
1569
1570		if (write(STDOUT_FILENO, buf, rv) != rv) {
1571			maybe_warn("write to stdout");
1572			break;
1573		}
1574		in_tot += rv;
1575	}
1576
1577	if (gsizep)
1578		*gsizep = in_tot;
1579	return (in_tot);
1580}
1581#endif
1582
1583static void
1584handle_stdin(void)
1585{
1586	unsigned char header1[4];
1587	off_t usize, gsize;
1588	enum filetype method;
1589	ssize_t bytes_read;
1590#ifndef NO_COMPRESS_SUPPORT
1591	FILE *in;
1592#endif
1593
1594#ifndef SMALL
1595	if (fflag == 0 && lflag == 0 && isatty(STDIN_FILENO)) {
1596		maybe_warnx("standard input is a terminal -- ignoring");
1597		return;
1598	}
1599#endif
1600
1601	if (lflag) {
1602		struct stat isb;
1603
1604		/* XXX could read the whole file, etc. */
1605		if (fstat(STDIN_FILENO, &isb) < 0) {
1606			maybe_warn("fstat");
1607			return;
1608		}
1609		print_list(STDIN_FILENO, isb.st_size, "stdout", isb.st_mtime);
1610		return;
1611	}
1612
1613	bytes_read = read_retry(STDIN_FILENO, header1, sizeof header1);
1614	if (bytes_read == -1) {
1615		maybe_warn("can't read stdin");
1616		return;
1617	} else if (bytes_read != sizeof(header1)) {
1618		maybe_warnx("(stdin): unexpected end of file");
1619		return;
1620	}
1621
1622	method = file_gettype(header1);
1623	switch (method) {
1624	default:
1625#ifndef SMALL
1626		if (fflag == 0) {
1627			maybe_warnx("unknown compression format");
1628			return;
1629		}
1630		usize = cat_fd(header1, sizeof header1, &gsize, STDIN_FILENO);
1631		break;
1632#endif
1633	case FT_GZIP:
1634		usize = gz_uncompress(STDIN_FILENO, STDOUT_FILENO,
1635			      (char *)header1, sizeof header1, &gsize, "(stdin)");
1636		break;
1637#ifndef NO_BZIP2_SUPPORT
1638	case FT_BZIP2:
1639		usize = unbzip2(STDIN_FILENO, STDOUT_FILENO,
1640				(char *)header1, sizeof header1, &gsize);
1641		break;
1642#endif
1643#ifndef NO_COMPRESS_SUPPORT
1644	case FT_Z:
1645		if ((in = zdopen(STDIN_FILENO)) == NULL) {
1646			maybe_warnx("zopen of stdin");
1647			return;
1648		}
1649
1650		usize = zuncompress(in, stdout, (char *)header1, sizeof header1, &gsize);
1651		fclose(in);
1652		break;
1653#endif
1654	}
1655
1656#ifndef SMALL
1657        if (vflag && !tflag && usize != -1 && gsize != -1)
1658		print_verbage(NULL, NULL, usize, gsize);
1659	if (vflag && tflag)
1660		print_test("(stdin)", usize != -1);
1661#endif
1662
1663}
1664
1665static void
1666handle_stdout(void)
1667{
1668	off_t gsize, usize;
1669	struct stat sb;
1670	time_t systime;
1671	uint32_t mtime;
1672	int ret;
1673
1674#ifndef SMALL
1675	if (fflag == 0 && isatty(STDOUT_FILENO)) {
1676		maybe_warnx("standard output is a terminal -- ignoring");
1677		return;
1678	}
1679#endif
1680	/* If stdin is a file use it's mtime, otherwise use current time */
1681	ret = fstat(STDIN_FILENO, &sb);
1682
1683#ifndef SMALL
1684	if (ret < 0) {
1685		maybe_warn("Can't stat stdin");
1686		return;
1687	}
1688#endif
1689
1690	if (S_ISREG(sb.st_mode))
1691		mtime = (uint32_t)sb.st_mtime;
1692	else {
1693		systime = time(NULL);
1694#ifndef SMALL
1695		if (systime == -1) {
1696			maybe_warn("time");
1697			return;
1698		}
1699#endif
1700		mtime = (uint32_t)systime;
1701	}
1702
1703	usize = gz_compress(STDIN_FILENO, STDOUT_FILENO, &gsize, "", mtime);
1704#ifndef SMALL
1705        if (vflag && !tflag && usize != -1 && gsize != -1)
1706		print_verbage(NULL, NULL, usize, gsize);
1707#endif
1708}
1709
1710/* do what is asked for, for the path name */
1711static void
1712handle_pathname(char *path)
1713{
1714	char *opath = path, *s = NULL;
1715	ssize_t len;
1716	int slen;
1717	struct stat sb;
1718
1719	/* check for stdout/stdin */
1720	if (path[0] == '-' && path[1] == '\0') {
1721		if (dflag)
1722			handle_stdin();
1723		else
1724			handle_stdout();
1725		return;
1726	}
1727
1728retry:
1729	if (stat(path, &sb) != 0) {
1730		/* lets try <path>.gz if we're decompressing */
1731		if (dflag && s == NULL && errno == ENOENT) {
1732			len = strlen(path);
1733			slen = suffixes[0].ziplen;
1734			s = malloc(len + slen + 1);
1735			if (s == NULL)
1736				maybe_err("malloc");
1737			memcpy(s, path, len);
1738			memcpy(s + len, suffixes[0].zipped, slen + 1);
1739			path = s;
1740			goto retry;
1741		}
1742		maybe_warn("can't stat: %s", opath);
1743		goto out;
1744	}
1745
1746	if (S_ISDIR(sb.st_mode)) {
1747#ifndef SMALL
1748		if (rflag)
1749			handle_dir(path);
1750		else
1751#endif
1752			maybe_warnx("%s is a directory", path);
1753		goto out;
1754	}
1755
1756	if (S_ISREG(sb.st_mode))
1757		handle_file(path, &sb);
1758	else
1759		maybe_warnx("%s is not a regular file", path);
1760
1761out:
1762	if (s)
1763		free(s);
1764}
1765
1766/* compress/decompress a file */
1767static void
1768handle_file(char *file, struct stat *sbp)
1769{
1770	off_t usize, gsize;
1771	char	outfile[PATH_MAX];
1772
1773	infile = file;
1774	if (dflag) {
1775		usize = file_uncompress(file, outfile, sizeof(outfile));
1776#ifndef SMALL
1777		if (vflag && tflag)
1778			print_test(file, usize != -1);
1779#endif
1780		if (usize == -1)
1781			return;
1782		gsize = sbp->st_size;
1783	} else {
1784		gsize = file_compress(file, outfile, sizeof(outfile));
1785		if (gsize == -1)
1786			return;
1787		usize = sbp->st_size;
1788	}
1789
1790
1791#ifndef SMALL
1792	if (vflag && !tflag)
1793		print_verbage(file, (cflag) ? NULL : outfile, usize, gsize);
1794#endif
1795}
1796
1797#ifndef SMALL
1798/* this is used with -r to recursively descend directories */
1799static void
1800handle_dir(char *dir)
1801{
1802	char *path_argv[2];
1803	FTS *fts;
1804	FTSENT *entry;
1805
1806	path_argv[0] = dir;
1807	path_argv[1] = 0;
1808	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
1809	if (fts == NULL) {
1810		warn("couldn't fts_open %s", dir);
1811		return;
1812	}
1813
1814	while ((entry = fts_read(fts))) {
1815		switch(entry->fts_info) {
1816		case FTS_D:
1817		case FTS_DP:
1818			continue;
1819
1820		case FTS_DNR:
1821		case FTS_ERR:
1822		case FTS_NS:
1823			maybe_warn("%s", entry->fts_path);
1824			continue;
1825		case FTS_F:
1826			handle_file(entry->fts_path, entry->fts_statp);
1827		}
1828	}
1829	(void)fts_close(fts);
1830}
1831#endif
1832
1833/* print a ratio - size reduction as a fraction of uncompressed size */
1834static void
1835print_ratio(off_t in, off_t out, FILE *where)
1836{
1837	int percent10;	/* 10 * percent */
1838	off_t diff;
1839	char buff[8];
1840	int len;
1841
1842	diff = in - out/2;
1843	if (diff <= 0)
1844		/*
1845		 * Output is more than double size of input! print -99.9%
1846		 * Quite possibly we've failed to get the original size.
1847		 */
1848		percent10 = -999;
1849	else {
1850		/*
1851		 * We only need 12 bits of result from the final division,
1852		 * so reduce the values until a 32bit division will suffice.
1853		 */
1854		while (in > 0x100000) {
1855			diff >>= 1;
1856			in >>= 1;
1857		}
1858		if (in != 0)
1859			percent10 = ((u_int)diff * 2000) / (u_int)in - 1000;
1860		else
1861			percent10 = 0;
1862	}
1863
1864	len = snprintf(buff, sizeof buff, "%2.2d.", percent10);
1865	/* Move the '.' to before the last digit */
1866	buff[len - 1] = buff[len - 2];
1867	buff[len - 2] = '.';
1868	fprintf(where, "%5s%%", buff);
1869}
1870
1871#ifndef SMALL
1872/* print compression statistics, and the new name (if there is one!) */
1873static void
1874print_verbage(const char *file, const char *nfile, off_t usize, off_t gsize)
1875{
1876	if (file)
1877		fprintf(stderr, "%s:%s  ", file,
1878		    strlen(file) < 7 ? "\t\t" : "\t");
1879	print_ratio(usize, gsize, stderr);
1880	if (nfile)
1881		fprintf(stderr, " -- replaced with %s", nfile);
1882	fprintf(stderr, "\n");
1883	fflush(stderr);
1884}
1885
1886/* print test results */
1887static void
1888print_test(const char *file, int ok)
1889{
1890
1891	if (exit_value == 0 && ok == 0)
1892		exit_value = 1;
1893	fprintf(stderr, "%s:%s  %s\n", file,
1894	    strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK");
1895	fflush(stderr);
1896}
1897#endif
1898
1899/* print a file's info ala --list */
1900/* eg:
1901  compressed uncompressed  ratio uncompressed_name
1902      354841      1679360  78.8% /usr/pkgsrc/distfiles/libglade-2.0.1.tar
1903*/
1904static void
1905print_list(int fd, off_t out, const char *outfile, time_t ts)
1906{
1907	static int first = 1;
1908#ifndef SMALL
1909	static off_t in_tot, out_tot;
1910	uint32_t crc = 0;
1911#endif
1912	off_t in = 0, rv;
1913
1914	if (first) {
1915#ifndef SMALL
1916		if (vflag)
1917			printf("method  crc     date  time  ");
1918#endif
1919		if (qflag == 0)
1920			printf("  compressed uncompressed  "
1921			       "ratio uncompressed_name\n");
1922	}
1923	first = 0;
1924
1925	/* print totals? */
1926#ifndef SMALL
1927	if (fd == -1) {
1928		in = in_tot;
1929		out = out_tot;
1930	} else
1931#endif
1932	{
1933		/* read the last 4 bytes - this is the uncompressed size */
1934		rv = lseek(fd, (off_t)(-8), SEEK_END);
1935		if (rv != -1) {
1936			unsigned char buf[8];
1937			uint32_t usize;
1938
1939			rv = read(fd, (char *)buf, sizeof(buf));
1940			if (rv == -1)
1941				maybe_warn("read of uncompressed size");
1942			else if (rv != sizeof(buf))
1943				maybe_warnx("read of uncompressed size");
1944
1945			else {
1946				usize = buf[4] | buf[5] << 8 |
1947					buf[6] << 16 | buf[7] << 24;
1948				in = (off_t)usize;
1949#ifndef SMALL
1950				crc = buf[0] | buf[1] << 8 |
1951				      buf[2] << 16 | buf[3] << 24;
1952#endif
1953			}
1954		}
1955	}
1956
1957#ifndef SMALL
1958	if (vflag && fd == -1)
1959		printf("                            ");
1960	else if (vflag) {
1961		char *date = ctime(&ts);
1962
1963		/* skip the day, 1/100th second, and year */
1964		date += 4;
1965		date[12] = 0;
1966		printf("%5s %08x %11s ", "defla"/*XXX*/, crc, date);
1967	}
1968	in_tot += in;
1969	out_tot += out;
1970#else
1971	(void)&ts;	/* XXX */
1972#endif
1973	printf("%12llu %12llu ", (unsigned long long)out, (unsigned long long)in);
1974	print_ratio(in, out, stdout);
1975	printf(" %s\n", outfile);
1976}
1977
1978/* display the usage of NetBSD gzip */
1979static void
1980usage(void)
1981{
1982
1983	fprintf(stderr, "%s\n", gzip_version);
1984	fprintf(stderr,
1985#ifdef SMALL
1986    "usage: %s [-" OPT_LIST "] [<file> [<file> ...]]\n",
1987#else
1988    "usage: %s [-123456789acdfhklLNnqrtVv] [-S .suffix] [<file> [<file> ...]]\n"
1989    " -1 --fast            fastest (worst) compression\n"
1990    " -2 .. -8             set compression level\n"
1991    " -9 --best            best (slowest) compression\n"
1992    " -c --stdout          write to stdout, keep original files\n"
1993    "    --to-stdout\n"
1994    " -d --decompress      uncompress files\n"
1995    "    --uncompress\n"
1996    " -f --force           force overwriting & compress links\n"
1997    " -h --help            display this help\n"
1998    " -k --keep            don't delete input files during operation\n"
1999    " -l --list            list compressed file contents\n"
2000    " -N --name            save or restore original file name and time stamp\n"
2001    " -n --no-name         don't save original file name or time stamp\n"
2002    " -q --quiet           output no warnings\n"
2003    " -r --recursive       recursively compress files in directories\n"
2004    " -S .suf              use suffix .suf instead of .gz\n"
2005    "    --suffix .suf\n"
2006    " -t --test            test compressed file\n"
2007    " -V --version         display program version\n"
2008    " -v --verbose         print extra statistics\n",
2009#endif
2010	    getprogname());
2011	exit(0);
2012}
2013
2014#ifndef SMALL
2015/* display the license information of FreeBSD gzip */
2016static void
2017display_license(void)
2018{
2019
2020	fprintf(stderr, "%s (based on NetBSD gzip 20060927)\n", gzip_version);
2021	fprintf(stderr, "%s\n", gzip_copyright);
2022	exit(0);
2023}
2024#endif
2025
2026/* display the version of NetBSD gzip */
2027static void
2028display_version(void)
2029{
2030
2031	fprintf(stderr, "%s\n", gzip_version);
2032	exit(0);
2033}
2034
2035#ifndef NO_BZIP2_SUPPORT
2036#include "unbzip2.c"
2037#endif
2038#ifndef NO_COMPRESS_SUPPORT
2039#include "zuncompress.c"
2040#endif
2041
2042static ssize_t
2043read_retry(int fd, void *buf, size_t sz)
2044{
2045	char *cp = buf;
2046	size_t left = MIN(sz, (size_t) SSIZE_MAX);
2047
2048	while (left > 0) {
2049		ssize_t ret;
2050
2051		ret = read(fd, cp, left);
2052		if (ret == -1) {
2053			return ret;
2054		} else if (ret == 0) {
2055			break; /* EOF */
2056		}
2057		cp += ret;
2058		left -= ret;
2059	}
2060
2061	return sz - left;
2062}
2063
2064