file_cmds-45.tar.gz
[apple/file_cmds.git] / compress / compress.c
1 /* $NetBSD: compress.c,v 1.16 1998/03/10 12:45:44 kleink 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.16 1998/03/10 12:45:44 kleink 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 = COMPRESS;
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 if (!strcmp(p, "zcat")) {
100 style = DECOMPRESS;
101 cat = 1;
102 }
103 else
104 errx(1, "unknown program name");
105
106 bits = cat = 0;
107 while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
108 switch(ch) {
109 case 'b':
110 bits = strtol(optarg, &p, 10);
111 if (*p)
112 errx(1, "illegal bit count -- %s", optarg);
113 break;
114 case 'c':
115 cat = 1;
116 break;
117 case 'd': /* Backward compatible. */
118 style = DECOMPRESS;
119 break;
120 case 'f':
121 force = 1;
122 break;
123 case 'v':
124 verbose = 1;
125 break;
126 case '?':
127 default:
128 usage(style == COMPRESS);
129 }
130 argc -= optind;
131 argv += optind;
132
133 if (argc == 0) {
134 switch(style) {
135 case COMPRESS:
136 isstdout = 1;
137 isstdin = 1;
138 (void)compress("/dev/stdin", "/dev/stdout", bits);
139 break;
140 case DECOMPRESS:
141 isstdout = 1;
142 isstdin = 1;
143 (void)decompress("/dev/stdin", "/dev/stdout", bits);
144 break;
145 }
146 exit (eval);
147 }
148
149 if (cat == 1 && argc > 1)
150 errx(1, "the -c option permits only a single file argument");
151
152 for (; *argv; ++argv) {
153 isstdout = 0;
154 switch(style) {
155 case COMPRESS:
156 if (cat) {
157 isstdout = 1;
158 compress(*argv, "/dev/stdout", bits);
159 break;
160 }
161 if ((p = strrchr(*argv, '.')) != NULL &&
162 !strcmp(p, ".Z")) {
163 cwarnx("%s: name already has trailing .Z",
164 *argv);
165 break;
166 }
167 len = strlen(*argv);
168 if (len > sizeof(newname) - 3) {
169 cwarnx("%s: name too long", *argv);
170 break;
171 }
172 memmove(newname, *argv, len);
173 newname[len] = '.';
174 newname[len + 1] = 'Z';
175 newname[len + 2] = '\0';
176 compress(*argv, newname, bits);
177 break;
178 case DECOMPRESS:
179 len = strlen(*argv);
180 if ((p = strrchr(*argv, '.')) == NULL ||
181 strcmp(p, ".Z")) {
182 if (len > sizeof(newname) - 3) {
183 cwarnx("%s: name too long", *argv);
184 break;
185 }
186 memmove(newname, *argv, len);
187 newname[len] = '.';
188 newname[len + 1] = 'Z';
189 newname[len + 2] = '\0';
190 decompress(newname,
191 cat ? "/dev/stdout" : *argv, bits);
192 if (cat)
193 isstdout = 1;
194 } else {
195 if (len - 2 > sizeof(newname) - 1) {
196 cwarnx("%s: name too long", *argv);
197 break;
198 }
199 memmove(newname, *argv, len - 2);
200 newname[len - 2] = '\0';
201 decompress(*argv,
202 cat ? "/dev/stdout" : newname, bits);
203 if (cat)
204 isstdout = 1;
205 }
206 break;
207 }
208 }
209 exit (eval);
210 }
211
212 void
213 compress(in, out, bits)
214 char *in, *out;
215 int bits;
216 {
217 int nr;
218 struct stat isb, sb;
219 FILE *ifp, *ofp;
220 int exists, isreg, oreg;
221 u_char buf[BUFSIZ];
222
223 if (!isstdout) {
224 exists = !stat(out, &sb);
225 if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
226 return;
227 oreg = !exists || S_ISREG(sb.st_mode);
228 } else
229 oreg = 0;
230
231 ifp = ofp = NULL;
232 if ((ifp = fopen(in, "r")) == NULL) {
233 cwarn("%s", in);
234 return;
235 }
236
237 if (!isstdin) {
238 if (stat(in, &isb)) { /* DON'T FSTAT! */
239 cwarn("%s", in);
240 goto err;
241 }
242 if (!S_ISREG(isb.st_mode))
243 isreg = 0;
244 else
245 isreg = 1;
246 } else
247 isreg = 0;
248
249 if ((ofp = zopen(out, "w", bits)) == NULL) {
250 cwarn("%s", out);
251 goto err;
252 }
253 while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
254 if (fwrite(buf, 1, nr, ofp) != nr) {
255 cwarn("%s", out);
256 goto err;
257 }
258
259 if (ferror(ifp) || fclose(ifp)) {
260 cwarn("%s", in);
261 goto err;
262 }
263 ifp = NULL;
264
265 if (fclose(ofp)) {
266 cwarn("%s", out);
267 goto err;
268 }
269 ofp = NULL;
270
271 if (isreg && oreg) {
272 if (stat(out, &sb)) {
273 cwarn("%s", out);
274 goto err;
275 }
276
277 if (!force && sb.st_size >= isb.st_size) {
278 if (verbose)
279 (void)printf("%s: file would grow; left unmodified\n", in);
280 if (unlink(out))
281 cwarn("%s", out);
282 goto err;
283 }
284
285 setfile(out, &isb);
286
287 if (unlink(in))
288 cwarn("%s", in);
289
290 if (verbose) {
291 (void)printf("%s: ", out);
292 if (isb.st_size > sb.st_size)
293 (void)printf("%.0f%% compression\n",
294 ((double)sb.st_size / isb.st_size) * 100.0);
295 else
296 (void)printf("%.0f%% expansion\n",
297 ((double)isb.st_size / sb.st_size) * 100.0);
298 }
299 }
300 return;
301
302 err: if (ofp) {
303 if (oreg)
304 (void)unlink(out);
305 (void)fclose(ofp);
306 }
307 if (ifp)
308 (void)fclose(ifp);
309 }
310
311 void
312 decompress(in, out, bits)
313 char *in, *out;
314 int bits;
315 {
316 int nr;
317 struct stat sb;
318 FILE *ifp, *ofp;
319 int exists, isreg, oreg;
320 u_char buf[BUFSIZ];
321
322 if (!isstdout) {
323 exists = !stat(out, &sb);
324 if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
325 return;
326 oreg = !exists || S_ISREG(sb.st_mode);
327 } else
328 oreg = 0;
329
330 ifp = ofp = NULL;
331 if ((ofp = fopen(out, "w")) == NULL) {
332 cwarn("%s", out);
333 return;
334 }
335
336 if ((ifp = zopen(in, "r", bits)) == NULL) {
337 cwarn("%s", in);
338 goto err;
339 }
340 if (!isstdin) {
341 if (stat(in, &sb)) {
342 cwarn("%s", in);
343 goto err;
344 }
345 if (!S_ISREG(sb.st_mode))
346 isreg = 0;
347 else
348 isreg = 1;
349 } else
350 isreg = 0;
351
352 while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
353 if (fwrite(buf, 1, nr, ofp) != nr) {
354 cwarn("%s", out);
355 goto err;
356 }
357
358 if (ferror(ifp) || fclose(ifp)) {
359 cwarn("%s", in);
360 goto err;
361 }
362 ifp = NULL;
363
364 if (fclose(ofp)) {
365 cwarn("%s", out);
366 goto err;
367 }
368
369 if (isreg && oreg) {
370 setfile(out, &sb);
371
372 if (unlink(in))
373 cwarn("%s", in);
374 }
375 return;
376
377 err: if (ofp) {
378 if (oreg)
379 (void)unlink(out);
380 (void)fclose(ofp);
381 }
382 if (ifp)
383 (void)fclose(ifp);
384 }
385
386 void
387 setfile(name, fs)
388 char *name;
389 struct stat *fs;
390 {
391 static struct timeval tv[2];
392
393 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
394
395 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
396 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
397 if (utimes(name, tv))
398 cwarn("utimes: %s", name);
399
400 /*
401 * Changing the ownership probably won't succeed, unless we're root
402 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
403 * the mode; current BSD behavior is to remove all setuid bits on
404 * chown. If chown fails, lose setuid/setgid bits.
405 */
406 if (chown(name, fs->st_uid, fs->st_gid)) {
407 if (errno != EPERM)
408 cwarn("chown: %s", name);
409 fs->st_mode &= ~(S_ISUID|S_ISGID);
410 }
411 if (chmod(name, fs->st_mode))
412 cwarn("chown: %s", name);
413
414 /*
415 * Restore the file's flags. However, do this only if the original
416 * file had any flags set; this avoids a warning on file-systems that
417 * do not support flags.
418 */
419 if (fs->st_flags != 0 && chflags(name, fs->st_flags))
420 cwarn("chflags: %s", name);
421 }
422
423 int
424 permission(fname)
425 char *fname;
426 {
427 int ch, first;
428
429 if (!isatty(fileno(stderr)))
430 return (0);
431 (void)fprintf(stderr, "overwrite %s? ", fname);
432 first = ch = getchar();
433 while (ch != '\n' && ch != EOF)
434 ch = getchar();
435 return (first == 'y');
436 }
437
438 void
439 usage(iscompress)
440 int iscompress;
441 {
442 if (iscompress)
443 (void)fprintf(stderr,
444 "usage: compress [-cfv] [-b bits] [file ...]\n");
445 else
446 (void)fprintf(stderr,
447 "usage: uncompress [-c] [-b bits] [file ...]\n");
448 exit(1);
449 }
450
451 void
452 #if __STDC__
453 cwarnx(const char *fmt, ...)
454 #else
455 cwarnx(fmt, va_alist)
456 int eval;
457 const char *fmt;
458 va_dcl
459 #endif
460 {
461 va_list ap;
462 #if __STDC__
463 va_start(ap, fmt);
464 #else
465 va_start(ap);
466 #endif
467 vwarnx(fmt, ap);
468 va_end(ap);
469 eval = 1;
470 }
471
472 void
473 #if __STDC__
474 cwarn(const char *fmt, ...)
475 #else
476 cwarn(fmt, va_alist)
477 int eval;
478 const char *fmt;
479 va_dcl
480 #endif
481 {
482 va_list ap;
483 #if __STDC__
484 va_start(ap, fmt);
485 #else
486 va_start(ap);
487 #endif
488 vwarn(fmt, ap);
489 va_end(ap);
490 eval = 1;
491 }