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