compress.c revision 40534
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 = COMPRESS;
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 if (!strcmp(p, "zcat")) {
92		style = DECOMPRESS;
93		cat = 1;
94	} else
95		errx(1, "unknown program name");
96
97	bits = cat = 0;
98	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
99		switch(ch) {
100		case 'b':
101			bits = strtol(optarg, &p, 10);
102			if (*p)
103				errx(1, "illegal bit count -- %s", optarg);
104			break;
105		case 'c':
106			cat = 1;
107			break;
108		case 'd':		/* Backward compatible. */
109			style = DECOMPRESS;
110			break;
111		case 'f':
112			force = 1;
113			break;
114		case 'v':
115			verbose = 1;
116			break;
117		case '?':
118		default:
119			usage(style == COMPRESS);
120		}
121	argc -= optind;
122	argv += optind;
123
124	if (argc == 0) {
125		switch(style) {
126		case COMPRESS:
127			(void)compress("/dev/stdin", "/dev/stdout", bits);
128			break;
129		case DECOMPRESS:
130			(void)decompress("/dev/stdin", "/dev/stdout", bits);
131			break;
132		}
133		exit (eval);
134	}
135
136	if (cat == 1 && argc > 1)
137		errx(1, "the -c option permits only a single file argument");
138
139	for (; *argv; ++argv)
140		switch(style) {
141		case COMPRESS:
142			if (cat) {
143				compress(*argv, "/dev/stdout", bits);
144				break;
145			}
146			if ((p = rindex(*argv, '.')) != NULL &&
147			    !strcmp(p, ".Z")) {
148				cwarnx("%s: name already has trailing .Z",
149				    *argv);
150				break;
151			}
152			len = strlen(*argv);
153			if (len > sizeof(newname) - 3) {
154				cwarnx("%s: name too long", *argv);
155				break;
156			}
157			memmove(newname, *argv, len);
158			newname[len] = '.';
159			newname[len + 1] = 'Z';
160			newname[len + 2] = '\0';
161			compress(*argv, newname, bits);
162			break;
163		case DECOMPRESS:
164			len = strlen(*argv);
165			if ((p = rindex(*argv, '.')) == NULL ||
166			    strcmp(p, ".Z")) {
167				if (len > sizeof(newname) - 3) {
168					cwarnx("%s: name too long", *argv);
169					break;
170				}
171				memmove(newname, *argv, len);
172				newname[len] = '.';
173				newname[len + 1] = 'Z';
174				newname[len + 2] = '\0';
175				decompress(newname,
176				    cat ? "/dev/stdout" : *argv, bits);
177			} else {
178				if (len - 2 > sizeof(newname) - 1) {
179					cwarnx("%s: name too long", *argv);
180					break;
181				}
182				memmove(newname, *argv, len - 2);
183				newname[len - 2] = '\0';
184				decompress(*argv,
185				    cat ? "/dev/stdout" : newname, bits);
186			}
187			break;
188		}
189	exit (eval);
190}
191
192void
193compress(in, out, bits)
194	char *in, *out;
195	int bits;
196{
197	register int 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)printf("%s: file would grow; left unmodified\n", in);
251			if (unlink(out))
252				cwarn("%s", out);
253			goto err;
254		}
255
256		setfile(out, &isb);
257
258		if (unlink(in))
259			cwarn("%s", in);
260
261		if (verbose) {
262			(void)printf("%s: ", out);
263			if (isb.st_size > sb.st_size)
264				(void)printf("%.0f%% compression\n",
265				    ((float)sb.st_size / isb.st_size) * 100.0);
266			else
267				(void)printf("%.0f%% expansion\n",
268				    ((float)isb.st_size / sb.st_size) * 100.0);
269		}
270	}
271	return;
272
273err:	if (ofp) {
274		if (oreg)
275			(void)unlink(out);
276		(void)fclose(ofp);
277	}
278	if (ifp)
279		(void)fclose(ifp);
280}
281
282void
283decompress(in, out, bits)
284	char *in, *out;
285	int bits;
286{
287	register int 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 ((ofp = fopen(out, "w")) == NULL) {
300		cwarn("%s", out);
301		return;
302	}
303
304	if ((ifp = zopen(in, "r", bits)) == NULL) {
305		cwarn("%s", in);
306		goto err;
307	}
308	if (stat(in, &sb)) {
309		cwarn("%s", in);
310		goto err;
311	}
312	if (!S_ISREG(sb.st_mode))
313		isreg = 0;
314
315	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
316		if (fwrite(buf, 1, nr, ofp) != nr) {
317			cwarn("%s", out);
318			goto err;
319		}
320
321	if (ferror(ifp) || fclose(ifp)) {
322		cwarn("%s", in);
323		goto err;
324	}
325	ifp = NULL;
326
327	if (fclose(ofp)) {
328		cwarn("%s", out);
329		goto err;
330	}
331
332	if (isreg) {
333		setfile(out, &sb);
334
335		if (unlink(in))
336			cwarn("%s", in);
337	}
338	return;
339
340err:	if (ofp) {
341		if (oreg)
342			(void)unlink(out);
343		(void)fclose(ofp);
344	}
345	if (ifp)
346		(void)fclose(ifp);
347}
348
349void
350setfile(name, fs)
351	char *name;
352	register struct stat *fs;
353{
354	static struct timeval tv[2];
355
356	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
357
358	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
359	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
360	if (utimes(name, tv))
361		cwarn("utimes: %s", name);
362
363	/*
364	 * Changing the ownership probably won't succeed, unless we're root
365	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
366	 * the mode; current BSD behavior is to remove all setuid bits on
367	 * chown.  If chown fails, lose setuid/setgid bits.
368	 */
369	if (chown(name, fs->st_uid, fs->st_gid)) {
370		if (errno != EPERM)
371			cwarn("chown: %s", name);
372		fs->st_mode &= ~(S_ISUID|S_ISGID);
373	}
374	if (chmod(name, fs->st_mode))
375		cwarn("chown: %s", name);
376
377	if (chflags(name, fs->st_flags))
378		cwarn("chflags: %s", name);
379}
380
381int
382permission(fname)
383	char *fname;
384{
385	int ch, first;
386
387	if (!isatty(fileno(stderr)))
388		return (0);
389	(void)fprintf(stderr, "overwrite %s? ", fname);
390	first = ch = getchar();
391	while (ch != '\n' && ch != EOF)
392		ch = getchar();
393	return (first == 'y');
394}
395
396void
397usage(iscompress)
398	int iscompress;
399{
400	if (iscompress)
401		(void)fprintf(stderr,
402		    "usage: compress [-cfv] [-b bits] [file ...]\n");
403	else
404		(void)fprintf(stderr,
405		    "usage: uncompress [-c] [-b bits] [file ...]\n");
406	exit(1);
407}
408
409void
410#if __STDC__
411cwarnx(const char *fmt, ...)
412#else
413cwarnx(fmt, va_alist)
414	int eval;
415	const char *fmt;
416	va_dcl
417#endif
418{
419	va_list ap;
420#if __STDC__
421	va_start(ap, fmt);
422#else
423	va_start(ap);
424#endif
425	vwarnx(fmt, ap);
426	va_end(ap);
427	eval = 1;
428}
429
430void
431#if __STDC__
432cwarn(const char *fmt, ...)
433#else
434cwarn(fmt, va_alist)
435	int eval;
436	const char *fmt;
437	va_dcl
438#endif
439{
440	va_list ap;
441#if __STDC__
442	va_start(ap, fmt);
443#else
444	va_start(ap);
445#endif
446	vwarn(fmt, ap);
447	va_end(ap);
448	eval = 1;
449}
450