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