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