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