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