xref: /original-bsd/usr.bin/compress/compress.c (revision a6d8c59f)
1 /*-
2  * Copyright (c) 1992 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1992 The Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)compress.c	5.24 (Berkeley) 12/02/92";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/time.h>
20 #include <sys/stat.h>
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 void	compress __P((char *, char *, int));
29 void	decompress __P((char *, char *, int));
30 void	err __P((int, const char *, ...));
31 int	permission __P((char *));
32 void	setfile __P((char *, struct stat *));
33 void	usage __P((int));
34 
35 int eval, force, verbose;
36 char *progname;
37 
38 int
39 main(argc, argv)
40 	int argc;
41 	char *argv[];
42 {
43 	enum {COMPRESS, DECOMPRESS} style;
44 	size_t len;
45 	int bits, cat, ch;
46 	char *p, newname[MAXPATHLEN];
47 
48 	if ((p = rindex(argv[0], '/')) == NULL)
49 		p = argv[0];
50 	else
51 		++p;
52 	if (!strcmp(p, "uncompress")) {
53 		progname = "uncompress";
54 		style = DECOMPRESS;
55 	} else if (!strcmp(p, "compress")) {
56 		progname = "compress";
57 		style = COMPRESS;
58 	} else {
59 		progname = *argv;
60 		err(1, "unknown program name");
61 	}
62 
63 	bits = cat = 0;
64 	while ((ch = getopt(argc, argv, "b:cdfv")) != EOF)
65 		switch(ch) {
66 		case 'b':
67 			bits = strtol(optarg, &p, 10);
68 			if (*p)
69 				err(1, "illegal bit count -- %s", optarg);
70 			break;
71 		case 'c':
72 			cat = 1;
73 			break;
74 		case 'd':		/* Backward compatible. */
75 			style = DECOMPRESS;
76 			break;
77 		case 'f':
78 			force = 1;
79 			break;
80 		case 'v':
81 			verbose = 1;
82 			break;
83 		case '?':
84 		default:
85 			usage(style == COMPRESS);
86 		}
87 	argc -= optind;
88 	argv += optind;
89 
90 	if (argc == 0) {
91 		switch(style) {
92 		case COMPRESS:
93 			(void)compress("/dev/stdin", "/dev/stdout", bits);
94 			break;
95 		case DECOMPRESS:
96 			(void)decompress("/dev/stdin", "/dev/stdout", bits);
97 			break;
98 		}
99 		exit (eval);
100 	}
101 
102 	if (cat == 1 && argc > 1)
103 		err(1, "the -c option permits only a single file argument");
104 
105 	for (; *argv; ++argv)
106 		switch(style) {
107 		case COMPRESS:
108 			if (cat) {
109 				compress(*argv, "/dev/stdout", bits);
110 				break;
111 			}
112 			if ((p = rindex(*argv, '.')) != NULL &&
113 			    !strcmp(p, ".Z")) {
114 				err(0, "%s: name already has trailing .Z",
115 				    *argv);
116 				break;
117 			}
118 			len = strlen(*argv);
119 			if (len > sizeof(newname) - 3) {
120 				err(0, "%s: name too long", *argv);
121 				break;
122 			}
123 			memmove(newname, *argv, len);
124 			newname[len] = '.';
125 			newname[len + 1] = 'Z';
126 			newname[len + 2] = '\0';
127 			compress(*argv, newname, bits);
128 			break;
129 		case DECOMPRESS:
130 			len = strlen(*argv);
131 			if ((p = rindex(*argv, '.')) == NULL ||
132 			    strcmp(p, ".Z")) {
133 				if (len > sizeof(newname) - 3) {
134 					err(0, "%s: name too long", *argv);
135 					break;
136 				}
137 				memmove(newname, *argv, len);
138 				newname[len] = '.';
139 				newname[len + 1] = 'Z';
140 				newname[len + 2] = '\0';
141 				decompress(newname,
142 				    cat ? "/dev/stdout" : *argv, bits);
143 			} else {
144 				if (len - 2 > sizeof(newname) - 1) {
145 					err(0, "%s: name too long", *argv);
146 					break;
147 				}
148 				memmove(newname, *argv, len - 2);
149 				newname[len - 2] = '\0';
150 				decompress(*argv,
151 				    cat ? "/dev/stdout" : newname, bits);
152 			}
153 			break;
154 		}
155 	exit (eval);
156 }
157 
158 void
159 compress(in, out, bits)
160 	char *in, *out;
161 	int bits;
162 {
163 	register int nr;
164 	struct stat isb, sb;
165 	FILE *ifp, *ofp;
166 	int exists, isreg, oreg;
167 	u_char buf[1024];
168 
169 	exists = !stat(out, &sb);
170 	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
171 		return;
172 	isreg = oreg = !exists || S_ISREG(sb.st_mode);
173 
174 	ifp = ofp = NULL;
175 	if ((ifp = fopen(in, "r")) == NULL) {
176 		err(0, "%s: %s", in, strerror(errno));
177 		return;
178 	}
179 	if (stat(in, &isb)) {		/* DON'T FSTAT! */
180 		err(0, "%s: %s", in, strerror(errno));
181 		goto err;
182 	}
183 	if (!S_ISREG(isb.st_mode))
184 		isreg = 0;
185 
186 	if ((ofp = zopen(out, "w", bits)) == NULL) {
187 		err(0, "%s: %s", out, strerror(errno));
188 		goto err;
189 	}
190 	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
191 		if (fwrite(buf, 1, nr, ofp) != nr) {
192 			err(0, "%s: %s", out, strerror(errno));
193 			goto err;
194 		}
195 
196 	if (ferror(ifp) || fclose(ifp)) {
197 		err(0, "%s: %s", in, strerror(errno));
198 		goto err;
199 	}
200 	ifp = NULL;
201 
202 	if (fclose(ofp)) {
203 		err(0, "%s: %s", out, strerror(errno));
204 		goto err;
205 	}
206 	ofp = NULL;
207 
208 	if (isreg) {
209 		if (stat(out, &sb)) {
210 			err(0, "%s: %s", out, strerror(errno));
211 			goto err;
212 		}
213 
214 		if (!force && sb.st_size >= isb.st_size) {
215 			if (verbose)
216 		(void)printf("%s: file would grow; left unmodified\n", in);
217 			if (unlink(out))
218 				err(0, "%s: %s", out, strerror(errno));
219 			goto err;
220 		}
221 
222 		setfile(out, &isb);
223 
224 		if (unlink(in))
225 			err(0, "%s: %s", in, strerror(errno));
226 
227 		if (verbose) {
228 			(void)printf("%s: ", out);
229 			if (isb.st_size > sb.st_size)
230 				(void)printf("%.0f%% compression\n",
231 				    ((float)sb.st_size / isb.st_size) * 100.0);
232 			else
233 				(void)printf("%.0f%% expansion\n",
234 				    ((float)isb.st_size / sb.st_size) * 100.0);
235 		}
236 	}
237 	return;
238 
239 err:	if (ofp) {
240 		if (oreg)
241 			(void)unlink(out);
242 		(void)fclose(ofp);
243 	}
244 	if (ifp)
245 		(void)fclose(ifp);
246 }
247 
248 void
249 decompress(in, out, bits)
250 	char *in, *out;
251 	int bits;
252 {
253 	register int nr;
254 	struct stat sb;
255 	FILE *ifp, *ofp;
256 	int exists, isreg, oreg;
257 	u_char buf[1024];
258 
259 	exists = !stat(out, &sb);
260 	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
261 		return;
262 	isreg = oreg = !exists || S_ISREG(sb.st_mode);
263 
264 	ifp = ofp = NULL;
265 	if ((ofp = fopen(out, "w")) == NULL) {
266 		err(0, "%s: %s", out, strerror(errno));
267 		return;
268 	}
269 
270 	if ((ifp = zopen(in, "r", bits)) == NULL) {
271 		err(0, "%s: %s", in, strerror(errno));
272 		goto err;
273 	}
274 	if (stat(in, &sb)) {
275 		err(0, "%s: %s", in, strerror(errno));
276 		goto err;
277 	}
278 	if (!S_ISREG(sb.st_mode))
279 		isreg = 0;
280 
281 	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
282 		if (fwrite(buf, 1, nr, ofp) != nr) {
283 			err(0, "%s: %s", out, strerror(errno));
284 			goto err;
285 		}
286 
287 	if (ferror(ifp) || fclose(ifp)) {
288 		err(0, "%s: %s", in, strerror(errno));
289 		goto err;
290 	}
291 	ifp = NULL;
292 
293 	if (fclose(ofp)) {
294 		err(0, "%s: %s", out, strerror(errno));
295 		goto err;
296 	}
297 
298 	if (isreg) {
299 		setfile(out, &sb);
300 
301 		if (unlink(in))
302 			err(0, "%s: %s", in, strerror(errno));
303 	}
304 	return;
305 
306 err:	if (ofp) {
307 		if (oreg)
308 			(void)unlink(out);
309 		(void)fclose(ofp);
310 	}
311 	if (ifp)
312 		(void)fclose(ifp);
313 }
314 
315 void
316 setfile(name, fs)
317 	char *name;
318 	register struct stat *fs;
319 {
320 	static struct timeval tv[2];
321 
322 	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
323 
324 	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
325 	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
326 	if (utimes(name, tv))
327 		err(0, "utimes: %s: %s", name, strerror(errno));
328 
329 	/*
330 	 * Changing the ownership probably won't succeed, unless we're root
331 	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
332 	 * the mode; current BSD behavior is to remove all setuid bits on
333 	 * chown.  If chown fails, lose setuid/setgid bits.
334 	 */
335 	if (chown(name, fs->st_uid, fs->st_gid)) {
336 		if (errno != EPERM)
337 			err(0, "chown: %s: %s", name, strerror(errno));
338 		fs->st_mode &= ~(S_ISUID|S_ISGID);
339 	}
340 	if (chmod(name, fs->st_mode))
341 		err(0, "chown: %s: %s", name, strerror(errno));
342 
343 	if (chflags(name, fs->st_flags))
344 		err(0, "chflags: %s: %s", name, strerror(errno));
345 }
346 
347 int
348 permission(fname)
349 	char *fname;
350 {
351 	int ch, first;
352 
353 	if (!isatty(fileno(stderr)))
354 		return (0);
355 	(void)fprintf(stderr, "overwrite %s? ", fname);
356 	first = ch = getchar();
357 	while (ch != '\n' && ch != EOF)
358 		ch = getchar();
359 	return (first == 'y');
360 }
361 
362 void
363 usage(iscompress)
364 	int iscompress;
365 {
366 	if (iscompress)
367 		(void)fprintf(stderr,
368 		    "usage: compress [-cfv] [-b bits] [file ...]\n");
369 	else
370 		(void)fprintf(stderr,
371 		    "usage: uncompress [-c] [-b bits] [file ...]\n");
372 	exit(1);
373 }
374 
375 #if __STDC__
376 #include <stdarg.h>
377 #else
378 #include <varargs.h>
379 #endif
380 
381 void
382 #if __STDC__
383 err(int fatal, const char *fmt, ...)
384 #else
385 err(fatal, fmt, va_alist)
386 	int fatal;
387 	char *fmt;
388 	va_dcl
389 #endif
390 {
391 	va_list ap;
392 #if __STDC__
393 	va_start(ap, fmt);
394 #else
395 	va_start(ap);
396 #endif
397 	(void)fprintf(stderr, "%s: ", progname);
398 	(void)vfprintf(stderr, fmt, ap);
399 	va_end(ap);
400 	(void)fprintf(stderr, "\n");
401 	if (fatal)
402 		exit(1);
403 	eval = 1;
404 }
405