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