xref: /openbsd/usr.bin/compress/main.c (revision f2dfb0a4)
1 /*	$OpenBSD: main.c,v 1.9 1998/03/10 16:34:03 mickey 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 static char copyright[] =
37 "@(#) Copyright (c) 1992, 1993\n\
38 	The Regents of the University of California.  All rights reserved.\n";
39 
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
43 #else
44 static char rcsid[] = "$OpenBSD: main.c,v 1.9 1998/03/10 16:34:03 mickey Exp $";
45 #endif
46 #endif /* not lint */
47 
48 #include <sys/param.h>
49 #include <sys/time.h>
50 #include <sys/stat.h>
51 
52 #include <err.h>
53 #include <errno.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58 #include <fcntl.h>
59 #include <paths.h>
60 #include "compress.h"
61 
62 #define min(a,b) ((a) < (b)? (a) : (b))
63 
64 int pipin = 0, force = 0, verbose = 0, testmode = 0, list = 0, nosave = 0;
65 extern char *__progname;
66 
67 struct compressor {
68 	char *name;
69 	char *suffix;
70 	int (*check_header) __P((int, struct stat *, const char *));
71 	void *(*open) __P((int, const char *, int));
72 	int (*read) __P((void *, char *, int));
73 	int (*write) __P((void *, const char *, int));
74 	int (*close) __P((void *));
75 } c_table[] = {
76 #define M_COMPRESS (&c_table[0])
77   { "compress", ".Z", z_check_header,  z_open,  zread,   zwrite,   zclose },
78 #define M_DEFLATE (&c_table[1])
79   { "deflate", ".gz", gz_check_header, gz_open, gz_read, gz_write, gz_close },
80   { NULL }
81 };
82 
83 int permission __P((char *));
84 void setfile __P((char *, struct stat *));
85 void usage __P((void));
86 int compress
87 	__P((const char *, const char *, register struct compressor *, int));
88 int decompress
89 	__P((const char *, const char *, register struct compressor *, int));
90 struct compressor *check_method __P((int, const char *));
91 
92 struct stat sb, osb;
93 
94 int
95 main(argc, argv)
96 	int argc;
97 	char *argv[];
98 {
99 	int ch, bits, cat, decomp, error;
100 	struct compressor *method;
101 	int exists, isreg, oreg;
102 	char *infile, outfile[MAXPATHLEN+4], suffix[16];
103 	char *p;
104 	int rc = 0;
105 
106 	bits = cat = decomp = 0;
107 	p = __progname;
108 	if (p[0] == 'g') {
109 		method = M_DEFLATE;
110 		p++;
111 	} else
112 		method = M_COMPRESS;
113 
114 	decomp = 0;
115 	if (!strcmp(p, "zcat")) {
116 		decomp++;
117 		cat++;
118 	} else {
119 		if (p[0] == 'u' && p[1] == 'n') {
120 			p += 2;
121 			decomp++;
122 		}
123 
124 		if (strcmp(p, "zip") &&
125 		    strcmp(p, "compress"))
126 			errx(1, "unknown program name");
127 	}
128 
129 	outfile[0] = '\0';
130 	while ((ch = getopt(argc, argv, "0123456789b:cdfghlnOo:qStv")) != -1)
131 		switch(ch) {
132 		case '0':
133 		case '1':
134 		case '2':
135 		case '3':
136 		case '4':
137 		case '5':
138 		case '6':
139 		case '7':
140 		case '8':
141 		case '9':
142 			method = M_DEFLATE;
143 			bits = ch - '0';
144 			break;
145 		case 'b':
146 			bits = strtol(optarg, &p, 10);
147 			/*
148 			 * POSIX 1002.3 says 9 <= bits <= 14 for portable
149 			 * apps, but says the implementation may allow
150 			 * greater.
151 			 */
152 			if (*p)
153 				errx(1, "illegal bit count -- %s", optarg);
154 			break;
155 		case 'c':
156 			cat++;
157 			break;
158 		case 'd':		/* Backward compatible. */
159 			decomp++;
160 			break;
161 		case 'f':
162 			force++;
163 			break;
164 		case 'g':
165 			method = M_DEFLATE;
166 			break;
167 		case 'l':
168 			list++;
169 			break;
170 		case 'L':
171 			fputs(copyright, stderr);
172 		case 'n':
173 			nosave++;
174 			break;
175 		case 'N':
176 			nosave = 0;
177 			break;
178 		case 'O':
179 			method = M_COMPRESS;
180 			break;
181 		case 'o':
182 			strncpy(outfile, optarg, sizeof(outfile)-1);
183 			break;
184 		case 'q':
185 			verbose = -1;
186 			break;
187 		case 'S':
188 			p = suffix;
189 			if (optarg[0] != '.')
190 				*p++ = '.';
191 			strncpy(p, optarg, sizeof(suffix) - (p - suffix) - 1);
192 			break;
193 		case 't':
194 			testmode++;
195 			break;
196 		case 'v':
197 			verbose++;
198 			break;
199 		case 'h':
200 		case '?':
201 		default:
202 			usage();
203 		}
204 	argc -= optind;
205 	argv += optind;
206 
207 	do {
208 		if (*argv != NULL) {
209 			infile = *argv;
210 			if (outfile[0] == '\0') {
211 				if (!decomp && !cat && outfile[0] == '\0') {
212 					int len;
213 					char *p;
214 
215 					snprintf(outfile, sizeof(outfile),
216 						"%s%s", infile,
217 						method->suffix);
218 
219 					len = strlen(outfile);
220 					if (len > MAXPATHLEN) {
221 						errx(1, "pathname%s too long",
222 							method->suffix);
223 					}
224 
225 					p = strrchr(outfile, '/');
226 					if (p == NULL) p = outfile;
227 					len = strlen(p);
228 					if (len > NAME_MAX) {
229 						errx(1, "filename%s too long",
230 							method->suffix);
231 					}
232 				} else if (decomp && !cat) {
233 					char *p = strrchr(infile, '.');
234 					if (p != NULL)
235 						for (method = &c_table[0];
236 						     method->name != NULL &&
237 							!strcmp(p, method->suffix);
238 						     method++)
239 							;
240 					if (method->name != NULL) {
241 						int l =	min(sizeof(outfile),
242 							    (p - infile));
243 						strncpy(outfile, infile, l);
244 						outfile[l] = '\0';
245 					}
246 				}
247 			}
248 		} else {
249 			infile = "/dev/stdin";
250 			pipin++;
251 		}
252 
253 		if (testmode)
254 			strcpy(outfile, _PATH_DEVNULL);
255 		else if (cat || outfile[0] == '\0') {
256 			strcpy(outfile, "/dev/stdout");
257 			cat++;
258 		}
259 
260 		exists = !stat(outfile, &sb);
261 		if (!force && exists && S_ISREG(sb.st_mode) &&
262 		    !permission(outfile)) {
263 		    	argv++;
264 			continue;
265 		}
266 		isreg = oreg = !exists || S_ISREG(sb.st_mode);
267 
268 		if (stat(infile, &sb) != 0 && verbose >= 0)
269 			err(1, infile);
270 
271 		if (!S_ISREG(sb.st_mode))
272 			isreg = 0;
273 
274 		if (verbose > 0)
275 			fprintf(stderr, "%s:\t", infile);
276 
277 		error = (decomp? decompress: compress)
278 			(infile, outfile, method, bits);
279 
280 		if (!error && isreg && stat(outfile, &osb) == 0) {
281 
282 			if (!force && !decomp && osb.st_size >= sb.st_size) {
283 				if (verbose > 0)
284 					fprintf(stderr, "file would grow; "
285 						     "left unmodified\n");
286 				error = 1;
287 				rc = 2;
288 			} else {
289 
290 				setfile(outfile, &sb);
291 
292 				if (unlink(infile) && verbose >= 0)
293 					warn("%s", infile);
294 
295 				if (verbose > 0) {
296 					u_int ratio;
297 					ratio = (1000*osb.st_size)/sb.st_size;
298 					fprintf(stderr, "%u", ratio / 10);
299 					if (ratio % 10)
300 						fprintf(stderr, ".%u",
301 						        ratio % 10);
302 					fputc('%', stderr);
303 					fputc(' ', stderr);
304 				}
305 			}
306 		}
307 
308 		if (error && oreg && unlink(outfile) && errno != ENOENT &&
309 		    verbose >= 0)
310 			warn("%s", outfile);
311 		else if (!error && verbose > 0)
312 			fputs("OK\n", stderr);
313 
314 		outfile[0] = '\0';
315 		if (*argv != NULL)
316 			argv++;
317 
318 	} while (*argv != NULL);
319 
320 	return (rc);
321 }
322 
323 int
324 compress(in, out, method, bits)
325 	const char *in;
326 	const char *out;
327 	register struct compressor *method;
328 	int bits;
329 {
330 	register int ifd;
331 	int ofd;
332 	register void *cookie;
333 	register size_t nr;
334 	u_char buf[Z_BUFSIZE];
335 	int error;
336 
337 	error = 0;
338 	cookie  = NULL;
339 
340 	if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) < 0) {
341 		if (verbose >= 0)
342 			warn("%s", out);
343 		return -1;
344 	}
345 
346 	if (method != M_COMPRESS && !force && isatty(ofd)) {
347 		if (verbose >= 0)
348 			warnx("%s: won't write compressed data to terminal",
349 			      out);
350 		return -1;
351 	}
352 
353 	if ((ifd = open(in, O_RDONLY)) >= 0 &&
354 	    (cookie = (*method->open)(ofd, "w", bits)) != NULL) {
355 
356 		while ((nr = read(ifd, buf, sizeof(buf))) > 0)
357 			if ((method->write)(cookie, buf, nr) != nr) {
358 				if (verbose >= 0)
359 					warn("%s", out);
360 				error++;
361 				break;
362 			}
363 	}
364 
365 	if (ifd < 0 || close(ifd) || nr < 0) {
366 		if (!error && verbose >= 0)
367 			warn("%s", in);
368 		error++;
369 	}
370 
371 	if (cookie == NULL || (method->close)(cookie)) {
372 		if (!error && verbose >= 0)
373 			warn("%s", out);
374 		error++;
375 		(void) close(ofd);
376 	}
377 
378 	return error? -1 : 0;
379 }
380 
381 struct compressor *
382 check_method(fd, out)
383 	int fd;
384 	const char *out;
385 {
386 	register struct compressor *method;
387 
388 	for (method = &c_table[0];
389 	     method->name != NULL &&
390 		     !(*method->check_header)(fd, &sb, out);
391 	     method++)
392 		;
393 
394 	if (method->name == NULL)
395 		method = NULL;
396 
397 	return method;
398 }
399 
400 int
401 decompress(in, out, method, bits)
402 	const char *in;
403 	const char *out;
404 	register struct compressor *method;
405 	int bits;
406 {
407 	int ifd;
408 	register int ofd;
409 	register void *cookie;
410 	register size_t nr;
411 	u_char buf[Z_BUFSIZE];
412 	int error;
413 
414 	error = 0;
415 	cookie = NULL;
416 
417 	if ((ifd = open(in, O_RDONLY)) < 0) {
418 		if (verbose >= 0)
419 			warn("%s", in);
420 		return -1;
421 	}
422 
423 	if (!force && isatty(ifd)) {
424 		if (verbose >= 0)
425 			warnx("%s: won't read compressed data from terminal",
426 			      in);
427 		close (ifd);
428 		return -1;
429 	}
430 
431 	if (!pipin && (method = check_method(ifd, out)) == NULL) {
432 		if (verbose >= 0)
433 			warnx("%s: unrecognized file format", in);
434 		return -1;
435 	}
436 
437 	if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) >= 0 &&
438 	    (cookie = (*method->open)(ifd, "r", bits)) != NULL) {
439 
440 		while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0)
441 			if (write(ofd, buf, nr) != nr) {
442 				if (verbose >= 0)
443 					warn("%s", out);
444 				error++;
445 				break;
446 			}
447 	}
448 
449 	if (ofd < 0 || close(ofd)) {
450 		if (!error && verbose >= 0)
451 			warn("%s", out);
452 		error++;
453 	}
454 
455 	if (cookie == NULL || (method->close)(cookie) || nr < 0) {
456 		if (!error && verbose >= 0)
457 			warn("%s", in);
458 		error++;
459 		(void) close (ifd);
460 	}
461 
462 	return error;
463 }
464 
465 void
466 setfile(name, fs)
467 	char *name;
468 	register struct stat *fs;
469 {
470 	static struct timeval tv[2];
471 
472 	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
473 
474 	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
475 	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
476 	if (utimes(name, tv))
477 		warn("utimes: %s", name);
478 
479 	/*
480 	 * Changing the ownership probably won't succeed, unless we're root
481 	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
482 	 * the mode; current BSD behavior is to remove all setuid bits on
483 	 * chown.  If chown fails, lose setuid/setgid bits.
484 	 */
485 	if (chown(name, fs->st_uid, fs->st_gid)) {
486 		if (errno != EPERM)
487 			warn("chown: %s", name);
488 		fs->st_mode &= ~(S_ISUID|S_ISGID);
489 	}
490 	if (chmod(name, fs->st_mode))
491 		warn("chown: %s", name);
492 
493 	if (fs->st_flags && chflags(name, fs->st_flags))
494 		warn("chflags: %s", name);
495 }
496 
497 int
498 permission(fname)
499 	char *fname;
500 {
501 	int ch, first;
502 
503 	if (!isatty(fileno(stderr)))
504 		return (0);
505 	(void)fprintf(stderr, "overwrite %s? ", fname);
506 	first = ch = getchar();
507 	while (ch != '\n' && ch != EOF)
508 		ch = getchar();
509 	return (first == 'y');
510 }
511 
512 void
513 usage()
514 {
515 	fprintf(stderr,
516 		"usage: %s [-cdfghlnOtqv] [-b <bits>] [-[0-9]] [file ...]\n",
517 		__progname);
518 	exit(1);
519 }
520 
521