xref: /openbsd/usr.bin/compress/main.c (revision 133306f0)
1 /*	$OpenBSD: main.c,v 1.15 2000/12/12 16:23:27 millert 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.15 2000/12/12 16:23:27 millert 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:qS:tv")) != -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 			outfile[sizeof(outfile)-1] = '\0';
184 			break;
185 		case 'q':
186 			verbose = -1;
187 			break;
188 		case 'S':
189 			p = suffix;
190 			if (optarg[0] != '.')
191 				*p++ = '.';
192 			strncpy(p, optarg, sizeof(suffix) - (p - suffix) - 1);
193 			break;
194 		case 't':
195 			testmode++;
196 			break;
197 		case 'v':
198 			verbose++;
199 			break;
200 		case 'h':
201 		case '?':
202 		default:
203 			usage();
204 		}
205 	argc -= optind;
206 	argv += optind;
207 
208 	do {
209 		if (*argv != NULL) {
210 			infile = *argv;
211 			if (outfile[0] == '\0') {
212 				if (!decomp && !cat && outfile[0] == '\0') {
213 					int len;
214 					char *p;
215 
216 					snprintf(outfile, sizeof(outfile),
217 						"%s%s", infile,
218 						method->suffix);
219 
220 					len = strlen(outfile);
221 					if (len > MAXPATHLEN) {
222 						errx(1, "pathname%s too long",
223 							method->suffix);
224 					}
225 
226 					p = strrchr(outfile, '/');
227 					if (p == NULL) p = outfile;
228 					len = strlen(p);
229 					if (len > NAME_MAX) {
230 						errx(1, "filename%s too long",
231 							method->suffix);
232 					}
233 				} else if (decomp && !cat) {
234 					char *p = strrchr(infile, '.');
235 					if (p != NULL)
236 						for (method = &c_table[0];
237 						     method->name != NULL &&
238 							!strcmp(p, method->suffix);
239 						     method++)
240 							;
241 					if (method->name != NULL) {
242 						int l =	min(sizeof(outfile),
243 							    (p - infile));
244 						strncpy(outfile, infile, l);
245 						outfile[l] = '\0';
246 					}
247 				}
248 			}
249 		} else {
250 			infile = "/dev/stdin";
251 			pipin++;
252 		}
253 
254 		if (testmode)
255 			strcpy(outfile, _PATH_DEVNULL);
256 		else if (cat || outfile[0] == '\0') {
257 			strcpy(outfile, "/dev/stdout");
258 			cat++;
259 		}
260 
261 		exists = !stat(outfile, &sb);
262 		if (!force && exists && S_ISREG(sb.st_mode) &&
263 		    !permission(outfile)) {
264 		    	argv++;
265 			continue;
266 		}
267 		isreg = oreg = !exists || S_ISREG(sb.st_mode);
268 
269 		if (stat(infile, &sb) != 0 && verbose >= 0)
270 			err(1, "%s", infile);
271 
272 		if (!S_ISREG(sb.st_mode))
273 			isreg = 0;
274 
275 		if (verbose > 0)
276 			fprintf(stderr, "%s:\t", infile);
277 
278 		error = (decomp? decompress: compress)
279 			(infile, outfile, method, bits);
280 
281 		if (!error && isreg && stat(outfile, &osb) == 0) {
282 
283 			if (!force && !decomp && osb.st_size >= sb.st_size) {
284 				if (verbose > 0)
285 					fprintf(stderr, "file would grow; "
286 						     "left unmodified\n");
287 				error = 1;
288 				rc = 2;
289 			} else {
290 
291 				setfile(outfile, &sb);
292 
293 				if (unlink(infile) && verbose >= 0)
294 					warn("%s", infile);
295 
296 				if (verbose > 0) {
297 					u_int ratio;
298 					ratio = (1000*osb.st_size)/sb.st_size;
299 					fprintf(stderr, "%u", ratio / 10);
300 					if (ratio % 10)
301 						fprintf(stderr, ".%u",
302 						        ratio % 10);
303 					fputc('%', stderr);
304 					fputc(' ', stderr);
305 				}
306 			}
307 		}
308 
309 		if (error && oreg && unlink(outfile) && errno != ENOENT &&
310 		    verbose >= 0)
311 			warn("%s", outfile);
312 		else if (!error && verbose > 0)
313 			fputs("OK\n", stderr);
314 
315 		outfile[0] = '\0';
316 		if (*argv != NULL)
317 			argv++;
318 
319 	} while (*argv != NULL);
320 
321 	return (rc);
322 }
323 
324 int
325 compress(in, out, method, bits)
326 	const char *in;
327 	const char *out;
328 	register struct compressor *method;
329 	int bits;
330 {
331 	register int ifd;
332 	int ofd;
333 	register void *cookie;
334 	register ssize_t nr;
335 	u_char buf[Z_BUFSIZE];
336 	int error;
337 
338 	error = 0;
339 	cookie  = NULL;
340 
341 	if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) < 0) {
342 		if (verbose >= 0)
343 			warn("%s", out);
344 		return -1;
345 	}
346 
347 	if (method != M_COMPRESS && !force && isatty(ofd)) {
348 		if (verbose >= 0)
349 			warnx("%s: won't write compressed data to terminal",
350 			      out);
351 		return -1;
352 	}
353 
354 	if ((ifd = open(in, O_RDONLY)) >= 0 &&
355 	    (cookie = (*method->open)(ofd, "w", bits)) != NULL) {
356 
357 		while ((nr = read(ifd, buf, sizeof(buf))) > 0)
358 			if ((method->write)(cookie, buf, nr) != nr) {
359 				if (verbose >= 0)
360 					warn("%s", out);
361 				error++;
362 				break;
363 			}
364 	}
365 
366 	if (ifd < 0 || close(ifd) || nr < 0) {
367 		if (!error && verbose >= 0)
368 			warn("%s", in);
369 		error++;
370 	}
371 
372 	if (cookie == NULL || (method->close)(cookie)) {
373 		if (!error && verbose >= 0)
374 			warn("%s", out);
375 		error++;
376 		(void) close(ofd);
377 	}
378 
379 	return error? -1 : 0;
380 }
381 
382 struct compressor *
383 check_method(fd, out)
384 	int fd;
385 	const char *out;
386 {
387 	register struct compressor *method;
388 
389 	for (method = &c_table[0];
390 	     method->name != NULL &&
391 		     !(*method->check_header)(fd, &sb, out);
392 	     method++)
393 		;
394 
395 	if (method->name == NULL)
396 		method = NULL;
397 
398 	return method;
399 }
400 
401 int
402 decompress(in, out, method, bits)
403 	const char *in;
404 	const char *out;
405 	register struct compressor *method;
406 	int bits;
407 {
408 	int ifd;
409 	register int ofd;
410 	register void *cookie;
411 	register ssize_t nr;
412 	u_char buf[Z_BUFSIZE];
413 	int error;
414 
415 	error = 0;
416 	cookie = NULL;
417 
418 	if ((ifd = open(in, O_RDONLY)) < 0) {
419 		if (verbose >= 0)
420 			warn("%s", in);
421 		return -1;
422 	}
423 
424 	if (!force && isatty(ifd)) {
425 		if (verbose >= 0)
426 			warnx("%s: won't read compressed data from terminal",
427 			      in);
428 		close (ifd);
429 		return -1;
430 	}
431 
432 	if (!pipin && (method = check_method(ifd, out)) == NULL) {
433 		if (verbose >= 0)
434 			warnx("%s: unrecognized file format", in);
435 		close (ifd);
436 		return -1;
437 	}
438 
439 	if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) >= 0 &&
440 	    (cookie = (*method->open)(ifd, "r", bits)) != NULL) {
441 
442 		while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0)
443 			if (write(ofd, buf, nr) != nr) {
444 				if (verbose >= 0)
445 					warn("%s", out);
446 				error++;
447 				break;
448 			}
449 	}
450 
451 	if (ofd < 0 || close(ofd)) {
452 		if (!error && verbose >= 0)
453 			warn("%s", out);
454 		error++;
455 	}
456 
457 	if (cookie == NULL || (method->close)(cookie) || nr < 0) {
458 		if (!error && verbose >= 0)
459 			warn("%s", in);
460 		error++;
461 		(void) close (ifd);
462 	}
463 
464 	return error;
465 }
466 
467 void
468 setfile(name, fs)
469 	char *name;
470 	register struct stat *fs;
471 {
472 	static struct timeval tv[2];
473 
474 	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
475 
476 	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
477 	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
478 	if (utimes(name, tv))
479 		warn("utimes: %s", name);
480 
481 	/*
482 	 * Changing the ownership probably won't succeed, unless we're root
483 	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
484 	 * the mode; current BSD behavior is to remove all setuid bits on
485 	 * chown.  If chown fails, lose setuid/setgid bits.
486 	 */
487 	if (chown(name, fs->st_uid, fs->st_gid)) {
488 		if (errno != EPERM)
489 			warn("chown: %s", name);
490 		fs->st_mode &= ~(S_ISUID|S_ISGID);
491 	}
492 	if (chmod(name, fs->st_mode))
493 		warn("chown: %s", name);
494 
495 	if (fs->st_flags && chflags(name, fs->st_flags))
496 		warn("chflags: %s", name);
497 }
498 
499 int
500 permission(fname)
501 	char *fname;
502 {
503 	int ch, first;
504 
505 	if (!isatty(fileno(stderr)))
506 		return (0);
507 	(void)fprintf(stderr, "overwrite %s? ", fname);
508 	first = ch = getchar();
509 	while (ch != '\n' && ch != EOF)
510 		ch = getchar();
511 	return (first == 'y');
512 }
513 
514 void
515 usage()
516 {
517 	fprintf(stderr,
518 		"usage: %s [-cdfghlnOtqv] [-b <bits>] [-[0-9]] [file ...]\n",
519 		__progname);
520 	exit(1);
521 }
522 
523