compress.c revision 87628
1214478Srpaulo/*-
2127668Sbms * Copyright (c) 1992, 1993
3127668Sbms *	The Regents of the University of California.  All rights reserved.
4127668Sbms *
5127668Sbms * Redistribution and use in source and binary forms, with or without
6127668Sbms * modification, are permitted provided that the following conditions
7127668Sbms * are met:
8127668Sbms * 1. Redistributions of source code must retain the above copyright
9127668Sbms *    notice, this list of conditions and the following disclaimer.
10127668Sbms * 2. Redistributions in binary form must reproduce the above copyright
11127668Sbms *    notice, this list of conditions and the following disclaimer in the
12127668Sbms *    documentation and/or other materials provided with the distribution.
13127668Sbms * 3. All advertising materials mentioning features or use of this software
14127668Sbms *    must display the following acknowledgement:
15127668Sbms *	This product includes software developed by the University of
16127668Sbms *	California, Berkeley and its contributors.
17214478Srpaulo * 4. Neither the name of the University nor the names of its contributors
18214478Srpaulo *    may be used to endorse or promote products derived from this software
19127668Sbms *    without specific prior written permission.
20146773Ssam *
21146773Ssam * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22147899Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23146773Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24147899Ssam * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25147899Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26147899Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27146773Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28146773Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29147899Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30190207Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31190207Srpaulo * SUCH DAMAGE.
32190207Srpaulo */
33235530Sdelphij
34146773Ssam#ifndef lint
35147899Ssamstatic const char copyright[] =
36147899Ssam"@(#) Copyright (c) 1992, 1993\n\
37147899Ssam	The Regents of the University of California.  All rights reserved.\n";
38147899Ssam#endif
39147899Ssam
40147899Ssam#if 0
41147899Ssam#ifndef lint
42147899Ssamstatic char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
43147899Ssam#endif
44147899Ssam#endif
45147899Ssam
46127668Sbms#include <sys/cdefs.h>
47127668Sbms__FBSDID("$FreeBSD: head/usr.bin/compress/compress.c 87628 2001-12-10 21:13:08Z dwmalone $");
48147899Ssam
49147899Ssam#include <sys/param.h>
50147899Ssam#include <sys/stat.h>
51127668Sbms#include <sys/time.h>
52147899Ssam
53147899Ssam#include <err.h>
54127668Sbms#include <errno.h>
55127668Sbms#include <stdio.h>
56127668Sbms#include <stdlib.h>
57127668Sbms#include <string.h>
58127668Sbms#include <unistd.h>
59127668Sbms
60127668Sbms#ifdef __STDC__
61127668Sbms#include <stdarg.h>
62127668Sbms#else
63127668Sbms#include <varargs.h>
64147899Ssam#endif
65127668Sbms
66127668Sbms#include "zopen.h"
67147899Ssam
68127668Sbmsvoid	compress __P((const char *, const char *, int));
69147899Ssamvoid	cwarn __P((const char *, ...)) __printflike(1, 2);
70147899Ssamvoid	cwarnx __P((const char *, ...)) __printflike(1, 2);
71127668Sbmsvoid	decompress __P((const char *, const char *, int));
72147899Ssamint	permission __P((const char *));
73147899Ssamvoid	setfile __P((const char *, struct stat *));
74147899Ssamvoid	usage __P((int));
75127668Sbms
76127668Sbmsint eval, force, verbose;
77147899Ssam
78127668Sbmsint
79127668Sbmsmain(argc, argv)
80127668Sbms	int argc;
81147899Ssam	char *argv[];
82147899Ssam{
83	enum {COMPRESS, DECOMPRESS} style;
84	size_t len;
85	int bits, cat, ch;
86	char *p, newname[MAXPATHLEN];
87
88	cat = 0;
89	if ((p = rindex(argv[0], '/')) == NULL)
90		p = argv[0];
91	else
92		++p;
93	if (!strcmp(p, "uncompress"))
94		style = DECOMPRESS;
95	else if (!strcmp(p, "compress"))
96		style = COMPRESS;
97	else if (!strcmp(p, "zcat")) {
98		cat = 1;
99		style = DECOMPRESS;
100	} else
101		errx(1, "unknown program name");
102
103	bits = 0;
104	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
105		switch(ch) {
106		case 'b':
107			bits = strtol(optarg, &p, 10);
108			if (*p)
109				errx(1, "illegal bit count -- %s", optarg);
110			break;
111		case 'c':
112			cat = 1;
113			break;
114		case 'd':		/* Backward compatible. */
115			style = DECOMPRESS;
116			break;
117		case 'f':
118			force = 1;
119			break;
120		case 'v':
121			verbose = 1;
122			break;
123		case '?':
124		default:
125			usage(style == COMPRESS);
126		}
127	argc -= optind;
128	argv += optind;
129
130	if (argc == 0) {
131		switch(style) {
132		case COMPRESS:
133			(void)compress("/dev/stdin", "/dev/stdout", bits);
134			break;
135		case DECOMPRESS:
136			(void)decompress("/dev/stdin", "/dev/stdout", bits);
137			break;
138		}
139		exit (eval);
140	}
141
142	if (cat == 1 && argc > 1)
143		errx(1, "the -c option permits only a single file argument");
144
145	for (; *argv; ++argv)
146		switch(style) {
147		case COMPRESS:
148			if (cat) {
149				compress(*argv, "/dev/stdout", bits);
150				break;
151			}
152			if ((p = rindex(*argv, '.')) != NULL &&
153			    !strcmp(p, ".Z")) {
154				cwarnx("%s: name already has trailing .Z",
155				    *argv);
156				break;
157			}
158			len = strlen(*argv);
159			if (len > sizeof(newname) - 3) {
160				cwarnx("%s: name too long", *argv);
161				break;
162			}
163			memmove(newname, *argv, len);
164			newname[len] = '.';
165			newname[len + 1] = 'Z';
166			newname[len + 2] = '\0';
167			compress(*argv, newname, bits);
168			break;
169		case DECOMPRESS:
170			len = strlen(*argv);
171			if ((p = rindex(*argv, '.')) == NULL ||
172			    strcmp(p, ".Z")) {
173				if (len > sizeof(newname) - 3) {
174					cwarnx("%s: name too long", *argv);
175					break;
176				}
177				memmove(newname, *argv, len);
178				newname[len] = '.';
179				newname[len + 1] = 'Z';
180				newname[len + 2] = '\0';
181				decompress(newname,
182				    cat ? "/dev/stdout" : *argv, bits);
183			} else {
184				if (len - 2 > sizeof(newname) - 1) {
185					cwarnx("%s: name too long", *argv);
186					break;
187				}
188				memmove(newname, *argv, len - 2);
189				newname[len - 2] = '\0';
190				decompress(*argv,
191				    cat ? "/dev/stdout" : newname, bits);
192			}
193			break;
194		}
195	exit (eval);
196}
197
198void
199compress(in, out, bits)
200	const char *in, *out;
201	int bits;
202{
203	size_t nr;
204	struct stat isb, sb;
205	FILE *ifp, *ofp;
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) && !permission(out))
211		return;
212	isreg = oreg = !exists || S_ISREG(sb.st_mode);
213
214	ifp = ofp = NULL;
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 (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)printf("%s: file would grow; left unmodified\n", in);
257			if (unlink(out))
258				cwarn("%s", out);
259			goto err;
260		}
261
262		setfile(out, &isb);
263
264		if (unlink(in))
265			cwarn("%s", in);
266
267		if (verbose) {
268			(void)printf("%s: ", out);
269			if (isb.st_size > sb.st_size)
270				(void)printf("%.0f%% compression\n",
271				    ((float)sb.st_size / isb.st_size) * 100.0);
272			else
273				(void)printf("%.0f%% expansion\n",
274				    ((float)isb.st_size / sb.st_size) * 100.0);
275		}
276	}
277	return;
278
279err:	if (ofp) {
280		if (oreg)
281			(void)unlink(out);
282		(void)fclose(ofp);
283	}
284	if (ifp)
285		(void)fclose(ifp);
286}
287
288void
289decompress(in, out, bits)
290	const char *in, *out;
291	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) && !permission(out))
301		return;
302	isreg = oreg = !exists || S_ISREG(sb.st_mode);
303
304	ifp = ofp = NULL;
305	if ((ofp = fopen(out, "w")) == NULL) {
306		cwarn("%s", out);
307		return;
308	}
309
310	if ((ifp = zopen(in, "r", bits)) == NULL) {
311		cwarn("%s", in);
312		goto err;
313	}
314	if (stat(in, &sb)) {
315		cwarn("%s", in);
316		goto err;
317	}
318	if (!S_ISREG(sb.st_mode))
319		isreg = 0;
320
321	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
322		if (fwrite(buf, 1, nr, ofp) != nr) {
323			cwarn("%s", out);
324			goto err;
325		}
326
327	if (ferror(ifp) || fclose(ifp)) {
328		cwarn("%s", in);
329		goto err;
330	}
331	ifp = NULL;
332
333	if (fclose(ofp)) {
334		cwarn("%s", out);
335		goto err;
336	}
337
338	if (isreg) {
339		setfile(out, &sb);
340
341		if (unlink(in))
342			cwarn("%s", in);
343	}
344	return;
345
346err:	if (ofp) {
347		if (oreg)
348			(void)unlink(out);
349		(void)fclose(ofp);
350	}
351	if (ifp)
352		(void)fclose(ifp);
353}
354
355void
356setfile(name, fs)
357	const char *name;
358	struct stat *fs;
359{
360	static struct timeval tv[2];
361
362	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
363
364	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
365	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
366	if (utimes(name, tv))
367		cwarn("utimes: %s", name);
368
369	/*
370	 * Changing the ownership probably won't succeed, unless we're root
371	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
372	 * the mode; current BSD behavior is to remove all setuid bits on
373	 * chown.  If chown fails, lose setuid/setgid bits.
374	 */
375	if (chown(name, fs->st_uid, fs->st_gid)) {
376		if (errno != EPERM)
377			cwarn("chown: %s", name);
378		fs->st_mode &= ~(S_ISUID|S_ISGID);
379	}
380	if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP)
381		cwarn("chmod: %s", name);
382
383	if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP)
384		cwarn("chflags: %s", name);
385}
386
387int
388permission(fname)
389	const char *fname;
390{
391	int ch, first;
392
393	if (!isatty(fileno(stderr)))
394		return (0);
395	(void)fprintf(stderr, "overwrite %s? ", fname);
396	first = ch = getchar();
397	while (ch != '\n' && ch != EOF)
398		ch = getchar();
399	return (first == 'y');
400}
401
402void
403usage(iscompress)
404	int iscompress;
405{
406	if (iscompress)
407		(void)fprintf(stderr,
408		    "usage: compress [-cfv] [-b bits] [file ...]\n");
409	else
410		(void)fprintf(stderr,
411		    "usage: uncompress [-c] [-b bits] [file ...]\n");
412	exit(1);
413}
414
415void
416#if __STDC__
417cwarnx(const char *fmt, ...)
418#else
419cwarnx(fmt, va_alist)
420	int eval;
421	const char *fmt;
422	va_dcl
423#endif
424{
425	va_list ap;
426#if __STDC__
427	va_start(ap, fmt);
428#else
429	va_start(ap);
430#endif
431	vwarnx(fmt, ap);
432	va_end(ap);
433	eval = 1;
434}
435
436void
437#if __STDC__
438cwarn(const char *fmt, ...)
439#else
440cwarn(fmt, va_alist)
441	int eval;
442	const char *fmt;
443	va_dcl
444#endif
445{
446	va_list ap;
447#if __STDC__
448	va_start(ap, fmt);
449#else
450	va_start(ap);
451#endif
452	vwarn(fmt, ap);
453	va_end(ap);
454	eval = 1;
455}
456