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: src/usr.bin/compress/compress.c,v 1.23 2010/12/11 08:32:16 joel Exp $");
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 <stdarg.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#include "zopen.h"
58
59void	compress(const char *, const char *, int);
60void	cwarn(const char *, ...) __printflike(1, 2);
61void	cwarnx(const char *, ...) __printflike(1, 2);
62void	decompress(const char *, const char *, int);
63int	permission(const char *);
64void	setfile(const char *, struct stat *);
65void	usage(int);
66
67int eval, force, verbose, cat;
68
69int
70main(int argc, char *argv[])
71{
72	enum {COMPRESS, DECOMPRESS} style;
73	size_t len;
74	int bits, ch;
75	char *p, newname[MAXPATHLEN];
76
77	if (argc < 1)
78		usage(1);
79	cat = 0;
80	if ((p = rindex(argv[0], '/')) == NULL)
81		p = argv[0];
82	else
83		++p;
84	if (!strcmp(p, "uncompress"))
85		style = DECOMPRESS;
86	else if (!strcmp(p, "compress"))
87		style = COMPRESS;
88	else if (!strcmp(p, "zcat")) {
89		cat = 1;
90		style = DECOMPRESS;
91	} else
92		errx(1, "unknown program name");
93
94	bits = 0;
95	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
96		switch(ch) {
97		case 'b':
98			bits = strtol(optarg, &p, 10);
99			if (*p)
100				errx(1, "illegal bit count -- %s", optarg);
101			break;
102		case 'c':
103			cat = 1;
104			break;
105		case 'd':		/* Backward compatible. */
106			style = DECOMPRESS;
107			break;
108		case 'f':
109			force = 1;
110			break;
111		case 'v':
112			verbose = 1;
113			break;
114		case '?':
115		default:
116			usage(style == COMPRESS);
117		}
118	argc -= optind;
119	argv += optind;
120
121	if (argc == 0) {
122		cat = 1;
123		switch(style) {
124		case COMPRESS:
125			(void)compress("/dev/stdin", "/dev/stdout", bits);
126			break;
127		case DECOMPRESS:
128			(void)decompress("/dev/stdin", "/dev/stdout", bits);
129			break;
130		}
131		exit (eval);
132	}
133
134	if (cat == 1 && argc > 1)
135		errx(1, "the -c option permits only a single file argument");
136
137	for (; *argv; ++argv)
138		switch(style) {
139		case COMPRESS:
140			if (strcmp(*argv, "-") == 0) {
141				cat = 1;
142				compress("/dev/stdin", "/dev/stdout", bits);
143				break;
144			} else if (cat) {
145				compress(*argv, "/dev/stdout", bits);
146				break;
147			}
148			if ((p = rindex(*argv, '.')) != NULL &&
149			    !strcmp(p, ".Z")) {
150				cwarnx("%s: name already has trailing .Z",
151				    *argv);
152				break;
153			}
154			len = strlen(*argv);
155			if (len > sizeof(newname) - 3) {
156				cwarnx("%s: name too long", *argv);
157				break;
158			}
159			memmove(newname, *argv, len);
160			newname[len] = '.';
161			newname[len + 1] = 'Z';
162			newname[len + 2] = '\0';
163			compress(*argv, newname, bits);
164			break;
165		case DECOMPRESS:
166			if (strcmp(*argv, "-") == 0) {
167				cat = 1;
168				decompress("/dev/stdin", "/dev/stdout", bits);
169				break;
170			}
171			len = strlen(*argv);
172			if ((p = rindex(*argv, '.')) == NULL ||
173			    strcmp(p, ".Z")) {
174				if (len > sizeof(newname) - 3) {
175					cwarnx("%s: name too long", *argv);
176					break;
177				}
178				memmove(newname, *argv, len);
179				newname[len] = '.';
180				newname[len + 1] = 'Z';
181				newname[len + 2] = '\0';
182				decompress(newname,
183				    cat ? "/dev/stdout" : *argv, bits);
184			} else {
185				if (len - 2 > sizeof(newname) - 1) {
186					cwarnx("%s: name too long", *argv);
187					break;
188				}
189				memmove(newname, *argv, len - 2);
190				newname[len - 2] = '\0';
191				decompress(*argv,
192				    cat ? "/dev/stdout" : newname, bits);
193			}
194			break;
195		}
196	exit (eval);
197}
198
199void
200compress(const char *in, const char *out, int bits)
201{
202	size_t nr;
203	struct stat isb, sb;
204	FILE *ifp = NULL, *ofp = NULL;
205	int exists, isreg, oreg;
206	u_char buf[1024];
207
208	exists = !stat(out, &sb);
209	if (!force && exists && S_ISREG(sb.st_mode) && !cat && !permission(out)) {
210		cwarnx("%s already exists", out);
211		return;
212	}
213	isreg = oreg = !exists || S_ISREG(sb.st_mode);
214
215	if ((ifp = fopen(in, "r")) == NULL) {
216		cwarn("%s", in);
217		return;
218	}
219	if (stat(in, &isb)) {		/* DON'T FSTAT! */
220		cwarn("%s", in);
221		goto err;
222	}
223	if (!S_ISREG(isb.st_mode))
224		isreg = 0;
225
226	if ((ofp = zopen(out, "w", bits)) == NULL) {
227		cwarn("%s", out);
228		goto err;
229	}
230	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
231		if (fwrite(buf, 1, nr, ofp) != nr) {
232			cwarn("%s", out);
233			goto err;
234		}
235
236	if (ferror(ifp) || fclose(ifp)) {
237		cwarn("%s", in);
238		goto err;
239	}
240	ifp = NULL;
241
242	if (fclose(ofp)) {
243		cwarn("%s", out);
244		goto err;
245	}
246	ofp = NULL;
247
248	if (!cat && isreg) {
249		if (stat(out, &sb)) {
250			cwarn("%s", out);
251			goto err;
252		}
253
254		if (!force && sb.st_size >= isb.st_size) {
255			if (verbose)
256		(void)fprintf(stderr, "%s: file would grow; left unmodified\n",
257		    in);
258			eval = 2;
259			if (unlink(out))
260				cwarn("%s", out);
261			goto err;
262		}
263
264		setfile(out, &isb);
265
266		if (unlink(in))
267			cwarn("%s", in);
268
269		if (verbose) {
270			(void)fprintf(stderr, "%s: ", out);
271			if (isb.st_size > sb.st_size)
272				(void)fprintf(stderr, "%.0f%% compression\n",
273				    ((float)sb.st_size / isb.st_size) * 100.0);
274			else
275				(void)fprintf(stderr, "%.0f%% expansion\n",
276				    ((float)isb.st_size / sb.st_size) * 100.0);
277		}
278	}
279	return;
280
281err:	if (ofp) {
282		if (!cat && oreg)
283			(void)unlink(out);
284		(void)fclose(ofp);
285	}
286	if (ifp)
287		(void)fclose(ifp);
288}
289
290void
291decompress(const char *in, const char *out, int bits)
292{
293	size_t nr;
294	struct stat sb;
295	FILE *ifp, *ofp;
296	int exists, isreg, oreg;
297	u_char buf[1024];
298
299	exists = !stat(out, &sb);
300	if (!force && exists && S_ISREG(sb.st_mode) && !cat && !permission(out)) {
301		cwarnx("%s already exists", out);
302		return;
303	}
304	isreg = oreg = !exists || S_ISREG(sb.st_mode);
305
306	ofp = NULL;
307	if ((ifp = zopen(in, "r", bits)) == NULL) {
308		cwarn("%s", in);
309		return;
310	}
311	if (stat(in, &sb)) {
312		cwarn("%s", in);
313		goto err;
314	}
315	if (!S_ISREG(sb.st_mode))
316		isreg = 0;
317
318	/*
319	 * Try to read the first few uncompressed bytes from the input file
320	 * before blindly truncating the output file.
321	 */
322	if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) {
323		cwarn("%s", in);
324		(void)fclose(ifp);
325		return;
326	}
327	if ((ofp = fopen(out, "w")) == NULL ||
328	    (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) {
329		cwarn("%s", out);
330		(void)fclose(ifp);
331		return;
332	}
333
334	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
335		if (fwrite(buf, 1, nr, ofp) != nr) {
336			cwarn("%s", out);
337			goto err;
338		}
339
340	if (ferror(ifp) || fclose(ifp)) {
341		cwarn("%s", in);
342		goto err;
343	}
344	ifp = NULL;
345
346	if (fclose(ofp)) {
347		cwarn("%s", out);
348		goto err;
349	}
350
351	if (!cat && isreg) {
352		setfile(out, &sb);
353
354		if (unlink(in))
355			cwarn("%s", in);
356		if (verbose) {
357			struct stat isb = sb;
358			stat(out, &sb);
359			(void)fprintf(stderr, "%s: ", out);
360			if (isb.st_size > sb.st_size)
361				(void)fprintf(stderr, "%.0f%% compression\n",
362				    ((float)sb.st_size / isb.st_size) * 100.0);
363			else
364				(void)fprintf(stderr, "%.0f%% expansion\n",
365				    ((float)isb.st_size / sb.st_size) * 100.0);
366		}
367	}
368	return;
369
370err:	if (ofp) {
371		if (!cat && oreg)
372			(void)unlink(out);
373		(void)fclose(ofp);
374	}
375	if (ifp)
376		(void)fclose(ifp);
377}
378
379void
380setfile(const char *name, struct stat *fs)
381{
382	static struct timeval tv[2];
383
384	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
385
386	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
387	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
388	if (utimes(name, tv))
389		cwarn("utimes: %s", name);
390
391	/*
392	 * Changing the ownership probably won't succeed, unless we're root
393	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
394	 * the mode; current BSD behavior is to remove all setuid bits on
395	 * chown.  If chown fails, lose setuid/setgid bits.
396	 */
397	if (chown(name, fs->st_uid, fs->st_gid)) {
398		if (errno != EPERM)
399			cwarn("chown: %s", name);
400		fs->st_mode &= ~(S_ISUID|S_ISGID);
401	}
402	if (chmod(name, fs->st_mode) && errno != ENOTSUP)
403		cwarn("chmod: %s", name);
404
405	if (chflags(name, fs->st_flags) && errno != ENOTSUP)
406		cwarn("chflags: %s", name);
407}
408
409int
410permission(const char *fname)
411{
412	int ch, first;
413
414	if (!isatty(fileno(stderr)))
415		return (0);
416	(void)fprintf(stderr, "overwrite %s? ", fname);
417	first = ch = getchar();
418	while (ch != '\n' && ch != EOF)
419		ch = getchar();
420	return (first == 'y');
421}
422
423void
424usage(int iscompress)
425{
426	if (iscompress)
427		(void)fprintf(stderr,
428		    "usage: compress [-cfv] [-b bits] [file ...]\n");
429	else
430		(void)fprintf(stderr,
431		    "usage: uncompress [-cfv] [-b bits] [file ...]\n");
432	exit(1);
433}
434
435void
436cwarnx(const char *fmt, ...)
437{
438	va_list ap;
439
440	va_start(ap, fmt);
441	vwarnx(fmt, ap);
442	va_end(ap);
443	eval = 1;
444}
445
446void
447cwarn(const char *fmt, ...)
448{
449	va_list ap;
450
451	va_start(ap, fmt);
452	vwarn(fmt, ap);
453	va_end(ap);
454	eval = 1;
455}
456