1/*-
2 * Copyright (c) 1992, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#ifndef lint
31static const char copyright[] =
32"@(#) Copyright (c) 1992, 1993\n\
33	The Regents of the University of California.  All rights reserved.\n";
34#endif
35
36#if 0
37#ifndef lint
38static char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
39#endif
40#endif
41
42#include <sys/cdefs.h>
43__FBSDID("$FreeBSD: releng/11.0/usr.bin/compress/compress.c 278896 2015-02-17 13:12:54Z jilles $");
44
45#include <sys/param.h>
46#include <sys/stat.h>
47#include <sys/time.h>
48
49#include <err.h>
50#include <errno.h>
51#include <fcntl.h>
52#include <stdarg.h>
53#include <stdio.h>
54#include <stdlib.h>
55#include <string.h>
56#include <unistd.h>
57
58#include "zopen.h"
59
60static void	compress(const char *, const char *, int);
61static void	cwarn(const char *, ...) __printflike(1, 2);
62static void	cwarnx(const char *, ...) __printflike(1, 2);
63static void	decompress(const char *, const char *, int);
64static int	permission(const char *);
65static void	setfile(const char *, struct stat *);
66static void	usage(int);
67
68static int eval, force, verbose;
69
70int
71main(int argc, char *argv[])
72{
73	enum {COMPRESS, DECOMPRESS} style;
74	size_t len;
75	int bits, cat, ch;
76	char *p, newname[MAXPATHLEN];
77
78	cat = 0;
79	if ((p = strrchr(argv[0], '/')) == NULL)
80		p = argv[0];
81	else
82		++p;
83	if (!strcmp(p, "uncompress"))
84		style = DECOMPRESS;
85	else if (!strcmp(p, "compress"))
86		style = COMPRESS;
87	else if (!strcmp(p, "zcat")) {
88		cat = 1;
89		style = DECOMPRESS;
90	} else
91		errx(1, "unknown program name");
92
93	bits = 0;
94	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
95		switch(ch) {
96		case 'b':
97			bits = strtol(optarg, &p, 10);
98			if (*p)
99				errx(1, "illegal bit count -- %s", optarg);
100			break;
101		case 'c':
102			cat = 1;
103			break;
104		case 'd':		/* Backward compatible. */
105			style = DECOMPRESS;
106			break;
107		case 'f':
108			force = 1;
109			break;
110		case 'v':
111			verbose = 1;
112			break;
113		case '?':
114		default:
115			usage(style == COMPRESS);
116		}
117	argc -= optind;
118	argv += optind;
119
120	if (argc == 0) {
121		switch(style) {
122		case COMPRESS:
123			(void)compress("/dev/stdin", "/dev/stdout", bits);
124			break;
125		case DECOMPRESS:
126			(void)decompress("/dev/stdin", "/dev/stdout", bits);
127			break;
128		}
129		exit (eval);
130	}
131
132	if (cat == 1 && argc > 1)
133		errx(1, "the -c option permits only a single file argument");
134
135	for (; *argv; ++argv)
136		switch(style) {
137		case COMPRESS:
138			if (strcmp(*argv, "-") == 0) {
139				compress("/dev/stdin", "/dev/stdout", bits);
140				break;
141			} else if (cat) {
142				compress(*argv, "/dev/stdout", bits);
143				break;
144			}
145			if ((p = strrchr(*argv, '.')) != NULL &&
146			    !strcmp(p, ".Z")) {
147				cwarnx("%s: name already has trailing .Z",
148				    *argv);
149				break;
150			}
151			len = strlen(*argv);
152			if (len > sizeof(newname) - 3) {
153				cwarnx("%s: name too long", *argv);
154				break;
155			}
156			memmove(newname, *argv, len);
157			newname[len] = '.';
158			newname[len + 1] = 'Z';
159			newname[len + 2] = '\0';
160			compress(*argv, newname, bits);
161			break;
162		case DECOMPRESS:
163			if (strcmp(*argv, "-") == 0) {
164				decompress("/dev/stdin", "/dev/stdout", bits);
165				break;
166			}
167			len = strlen(*argv);
168			if ((p = strrchr(*argv, '.')) == NULL ||
169			    strcmp(p, ".Z")) {
170				if (len > sizeof(newname) - 3) {
171					cwarnx("%s: name too long", *argv);
172					break;
173				}
174				memmove(newname, *argv, len);
175				newname[len] = '.';
176				newname[len + 1] = 'Z';
177				newname[len + 2] = '\0';
178				decompress(newname,
179				    cat ? "/dev/stdout" : *argv, bits);
180			} else {
181				if (len - 2 > sizeof(newname) - 1) {
182					cwarnx("%s: name too long", *argv);
183					break;
184				}
185				memmove(newname, *argv, len - 2);
186				newname[len - 2] = '\0';
187				decompress(*argv,
188				    cat ? "/dev/stdout" : newname, bits);
189			}
190			break;
191		}
192	exit (eval);
193}
194
195static void
196compress(const char *in, const char *out, int bits)
197{
198	size_t nr;
199	struct stat isb, sb;
200	FILE *ifp, *ofp;
201	int exists, isreg, oreg;
202	u_char buf[1024];
203
204	exists = !stat(out, &sb);
205	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
206		return;
207	isreg = oreg = !exists || S_ISREG(sb.st_mode);
208
209	ifp = ofp = NULL;
210	if ((ifp = fopen(in, "r")) == NULL) {
211		cwarn("%s", in);
212		return;
213	}
214	if (stat(in, &isb)) {		/* DON'T FSTAT! */
215		cwarn("%s", in);
216		goto err;
217	}
218	if (!S_ISREG(isb.st_mode))
219		isreg = 0;
220
221	if ((ofp = zopen(out, "w", bits)) == NULL) {
222		cwarn("%s", out);
223		goto err;
224	}
225	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
226		if (fwrite(buf, 1, nr, ofp) != nr) {
227			cwarn("%s", out);
228			goto err;
229		}
230
231	if (ferror(ifp) || fclose(ifp)) {
232		cwarn("%s", in);
233		goto err;
234	}
235	ifp = NULL;
236
237	if (fclose(ofp)) {
238		cwarn("%s", out);
239		goto err;
240	}
241	ofp = NULL;
242
243	if (isreg) {
244		if (stat(out, &sb)) {
245			cwarn("%s", out);
246			goto err;
247		}
248
249		if (!force && sb.st_size >= isb.st_size) {
250			if (verbose)
251		(void)fprintf(stderr, "%s: file would grow; left unmodified\n",
252		    in);
253			eval = 2;
254			if (unlink(out))
255				cwarn("%s", out);
256			goto err;
257		}
258
259		setfile(out, &isb);
260
261		if (unlink(in))
262			cwarn("%s", in);
263
264		if (verbose) {
265			(void)fprintf(stderr, "%s: ", out);
266			if (isb.st_size > sb.st_size)
267				(void)fprintf(stderr, "%.0f%% compression\n",
268				    ((float)sb.st_size / isb.st_size) * 100.0);
269			else
270				(void)fprintf(stderr, "%.0f%% expansion\n",
271				    ((float)isb.st_size / sb.st_size) * 100.0);
272		}
273	}
274	return;
275
276err:	if (ofp) {
277		if (oreg)
278			(void)unlink(out);
279		(void)fclose(ofp);
280	}
281	if (ifp)
282		(void)fclose(ifp);
283}
284
285static void
286decompress(const char *in, const char *out, int bits)
287{
288	size_t nr;
289	struct stat sb;
290	FILE *ifp, *ofp;
291	int exists, isreg, oreg;
292	u_char buf[1024];
293
294	exists = !stat(out, &sb);
295	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
296		return;
297	isreg = oreg = !exists || S_ISREG(sb.st_mode);
298
299	ifp = ofp = NULL;
300	if ((ifp = zopen(in, "r", bits)) == NULL) {
301		cwarn("%s", in);
302		return;
303	}
304	if (stat(in, &sb)) {
305		cwarn("%s", in);
306		goto err;
307	}
308	if (!S_ISREG(sb.st_mode))
309		isreg = 0;
310
311	/*
312	 * Try to read the first few uncompressed bytes from the input file
313	 * before blindly truncating the output file.
314	 */
315	if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) {
316		cwarn("%s", in);
317		(void)fclose(ifp);
318		return;
319	}
320	if ((ofp = fopen(out, "w")) == NULL ||
321	    (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) {
322		cwarn("%s", out);
323		(void)fclose(ifp);
324		return;
325	}
326
327	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
328		if (fwrite(buf, 1, nr, ofp) != nr) {
329			cwarn("%s", out);
330			goto err;
331		}
332
333	if (ferror(ifp) || fclose(ifp)) {
334		cwarn("%s", in);
335		goto err;
336	}
337	ifp = NULL;
338
339	if (fclose(ofp)) {
340		cwarn("%s", out);
341		goto err;
342	}
343
344	if (isreg) {
345		setfile(out, &sb);
346
347		if (unlink(in))
348			cwarn("%s", in);
349	}
350	return;
351
352err:	if (ofp) {
353		if (oreg)
354			(void)unlink(out);
355		(void)fclose(ofp);
356	}
357	if (ifp)
358		(void)fclose(ifp);
359}
360
361static void
362setfile(const char *name, struct stat *fs)
363{
364	static struct timespec tspec[2];
365
366	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
367
368	tspec[0] = fs->st_atim;
369	tspec[1] = fs->st_mtim;
370	if (utimensat(AT_FDCWD, name, tspec, 0))
371		cwarn("utimensat: %s", name);
372
373	/*
374	 * Changing the ownership probably won't succeed, unless we're root
375	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
376	 * the mode; current BSD behavior is to remove all setuid bits on
377	 * chown.  If chown fails, lose setuid/setgid bits.
378	 */
379	if (chown(name, fs->st_uid, fs->st_gid)) {
380		if (errno != EPERM)
381			cwarn("chown: %s", name);
382		fs->st_mode &= ~(S_ISUID|S_ISGID);
383	}
384	if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP)
385		cwarn("chmod: %s", name);
386
387	if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP)
388		cwarn("chflags: %s", name);
389}
390
391static int
392permission(const char *fname)
393{
394	int ch, first;
395
396	if (!isatty(fileno(stderr)))
397		return (0);
398	(void)fprintf(stderr, "overwrite %s? ", fname);
399	first = ch = getchar();
400	while (ch != '\n' && ch != EOF)
401		ch = getchar();
402	return (first == 'y');
403}
404
405static void
406usage(int iscompress)
407{
408	if (iscompress)
409		(void)fprintf(stderr,
410		    "usage: compress [-cfv] [-b bits] [file ...]\n");
411	else
412		(void)fprintf(stderr,
413		    "usage: uncompress [-c] [-b bits] [file ...]\n");
414	exit(1);
415}
416
417static void
418cwarnx(const char *fmt, ...)
419{
420	va_list ap;
421
422	va_start(ap, fmt);
423	vwarnx(fmt, ap);
424	va_end(ap);
425	eval = 1;
426}
427
428static void
429cwarn(const char *fmt, ...)
430{
431	va_list ap;
432
433	va_start(ap, fmt);
434	vwarn(fmt, ap);
435	va_end(ap);
436	eval = 1;
437}
438