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