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