gzip.c revision 1.41 1 /* $NetBSD: gzip.c,v 1.41 2004/04/27 11:26:28 mrg Exp $ */
2
3 /*
4 * Copyright (c) 1997, 1998, 2003, 2004 Matthew R. Green
5 * 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. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 #ifndef lint
33 __COPYRIGHT("@(#) Copyright (c) 1997, 1998, 2003, 2004 Matthew R. Green\n\
34 All rights reserved.\n");
35 __RCSID("$NetBSD: gzip.c,v 1.41 2004/04/27 11:26:28 mrg Exp $");
36 #endif /* not lint */
37
38 /*
39 * gzip.c -- GPL free gzip using zlib.
40 *
41 * TODO:
42 * - handle .taz/.tgz files?
43 * - use mmap where possible
44 * - handle some signals better (remove outfile?)
45 * - audit maybe_err()/maybe_warn() usage
46 * - make bzip2/compress -v/-t/-l support work as well as possible
47 */
48
49 #include <sys/param.h>
50 #include <sys/stat.h>
51 #include <sys/time.h>
52
53 #include <unistd.h>
54 #include <stdio.h>
55 #include <string.h>
56 #include <stdlib.h>
57 #include <err.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <zlib.h>
61 #include <fts.h>
62 #include <libgen.h>
63 #include <stdarg.h>
64 #include <getopt.h>
65
66 /* what type of file are we dealing with */
67 enum filetype {
68 FT_GZIP,
69 #ifndef NO_BZIP2_SUPPORT
70 FT_BZIP2,
71 #endif
72 #ifndef NO_COMPRESS_SUPPORT
73 FT_Z,
74 #endif
75 FT_LAST,
76 FT_UNKNOWN
77 };
78
79 #ifndef NO_BZIP2_SUPPORT
80 #include <bzlib.h>
81
82 #define BZ2_SUFFIX ".bz2"
83 #define BZIP2_MAGIC "\102\132\150"
84 #endif
85
86 #ifndef NO_COMPRESS_SUPPORT
87 #define Z_SUFFIX ".Z"
88 #define Z_MAGIC "\037\235"
89 #endif
90
91 #define GZ_SUFFIX ".gz"
92
93 #define BUFLEN (64 * 1024)
94
95 #define GZIP_MAGIC0 0x1F
96 #define GZIP_MAGIC1 0x8B
97 #define GZIP_OMAGIC1 0x9E
98
99 #define HEAD_CRC 0x02
100 #define EXTRA_FIELD 0x04
101 #define ORIG_NAME 0x08
102 #define COMMENT 0x10
103
104 #define OS_CODE 3 /* Unix */
105
106 static const char gzip_version[] = "NetBSD gzip 20040427";
107
108 static int cflag; /* stdout mode */
109 static int dflag; /* decompress mode */
110 static int lflag; /* list mode */
111 static int numflag = 6; /* gzip -1..-9 value */
112
113 #ifndef SMALL
114 static int fflag; /* force mode */
115 static int nflag; /* don't save name/timestamp */
116 static int Nflag; /* don't restore name/timestamp */
117 static int qflag; /* quiet mode */
118 static int rflag; /* recursive mode */
119 static int tflag; /* test */
120 static char *Sflag;
121 static int vflag; /* verbose mode */
122 #else
123 #define qflag 0
124 #endif
125
126 static char *suffix;
127 #define suffix_len (strlen(suffix) + 1) /* len + nul */
128 static char *newfile; /* name of newly created file */
129 static char *infile; /* name of file coming in */
130
131 static void maybe_err(int rv, const char *fmt, ...);
132 static void maybe_errx(int rv, const char *fmt, ...);
133 static void maybe_warn(const char *fmt, ...);
134 static void maybe_warnx(const char *fmt, ...);
135 static enum filetype file_gettype(u_char *);
136 static off_t gz_compress(FILE *, int, off_t *, const char *, time_t);
137 static off_t gz_uncompress(int, int, char *, size_t, off_t *);
138 static off_t file_compress(char *);
139 static off_t file_uncompress(char *);
140 static void handle_pathname(char *);
141 static void handle_file(char *, struct stat *);
142 static void handle_stdin(void);
143 static void handle_stdout(void);
144 static void print_ratio(off_t, off_t, FILE *);
145 static void print_list(int fd, off_t, const char *, time_t);
146 static void usage(void);
147 static void display_version(void);
148
149 #ifndef SMALL
150 static void prepend_gzip(char *, int *, char ***);
151 static void handle_dir(char *, struct stat *);
152 static void print_verbage(char *, char *, off_t, off_t);
153 static void print_test(char *, int);
154 static void copymodes(const char *, struct stat *);
155 #endif
156
157 #ifndef NO_BZIP2_SUPPORT
158 static off_t unbzip2(int, int, char *, size_t, off_t *);
159 #endif
160
161 #ifndef NO_COMPRESS_SUPPORT
162 static FILE *zopen(const char *, FILE *);
163 static off_t zuncompress(FILE *, FILE *, char *, size_t, off_t *);
164 #endif
165
166 int main(int, char *p[]);
167
168 #ifdef SMALL
169 #define getopt_long(a,b,c,d,e) getopt(a,b,c)
170 #else
171 static const struct option longopts[] = {
172 { "stdout", no_argument, 0, 'c' },
173 { "to-stdout", no_argument, 0, 'c' },
174 { "decompress", no_argument, 0, 'd' },
175 { "uncompress", no_argument, 0, 'd' },
176 { "force", no_argument, 0, 'f' },
177 { "help", no_argument, 0, 'h' },
178 { "list", no_argument, 0, 'l' },
179 { "no-name", no_argument, 0, 'n' },
180 { "name", no_argument, 0, 'N' },
181 { "quiet", no_argument, 0, 'q' },
182 { "recursive", no_argument, 0, 'r' },
183 { "suffix", required_argument, 0, 'S' },
184 { "test", no_argument, 0, 't' },
185 { "verbose", no_argument, 0, 'v' },
186 { "version", no_argument, 0, 'V' },
187 { "fast", no_argument, 0, '1' },
188 { "best", no_argument, 0, '9' },
189 #if 0
190 /*
191 * This is what else GNU gzip implements. --ascii isn't useful
192 * on NetBSD, and I don't care to have a --license.
193 */
194 { "ascii", no_argument, 0, 'a' },
195 { "license", no_argument, 0, 'L' },
196 #endif
197 { NULL, no_argument, 0, 0 },
198 };
199 #endif
200
201 int
202 main(int argc, char **argv)
203 {
204 const char *progname = getprogname();
205 #ifndef SMALL
206 char *gzip;
207 #endif
208 int ch;
209
210 /* XXX set up signals */
211
212 suffix = GZ_SUFFIX;;
213
214 #ifndef SMALL
215 if ((gzip = getenv("GZIP")) != NULL)
216 prepend_gzip(gzip, &argc, &argv);
217 #endif
218
219 /*
220 * XXX
221 * handle being called `gunzip', `zcat' and `gzcat'
222 */
223 if (strcmp(progname, "gunzip") == 0)
224 dflag = 1;
225 else if (strcmp(progname, "zcat") == 0 ||
226 strcmp(progname, "gzcat") == 0)
227 dflag = cflag = 1;
228
229 #ifdef SMALL
230 #define OPT_LIST "cdhHltV123456789"
231 #else
232 #define OPT_LIST "cdfhHlnNqrS:tvV123456789"
233 #endif
234
235 while ((ch = getopt_long(argc, argv, OPT_LIST, longopts, NULL)) != -1)
236 switch (ch) {
237 case 'c':
238 cflag = 1;
239 break;
240 case 'd':
241 dflag = 1;
242 break;
243 case 'l':
244 lflag = 1;
245 dflag = 1;
246 break;
247 case 'V':
248 display_version();
249 /* NOTREACHED */
250 case '1': case '2': case '3':
251 case '4': case '5': case '6':
252 case '7': case '8': case '9':
253 numflag = ch - '0';
254 break;
255 #ifndef SMALL
256 case 'f':
257 fflag = 1;
258 break;
259 case 'n':
260 nflag = 1;
261 Nflag = 0;
262 break;
263 case 'N':
264 nflag = 0;
265 Nflag = 1;
266 break;
267 case 'q':
268 qflag = 1;
269 break;
270 case 'r':
271 rflag = 1;
272 break;
273 case 'S':
274 Sflag = optarg;
275 break;
276 case 't':
277 cflag = 1;
278 tflag = 1;
279 dflag = 1;
280 break;
281 case 'v':
282 vflag = 1;
283 break;
284 #endif
285 default:
286 usage();
287 /* NOTREACHED */
288 }
289 argv += optind;
290 argc -= optind;
291
292 if (argc == 0) {
293 if (dflag) /* stdin mode */
294 handle_stdin();
295 else /* stdout mode */
296 handle_stdout();
297 } else {
298 do {
299 handle_pathname(argv[0]);
300 } while (*++argv);
301 }
302 #ifndef SMALL
303 if (qflag == 0 && lflag && argc > 1)
304 print_list(-1, 0, "(totals)", 0);
305 #endif
306 exit(0);
307 }
308
309 /* maybe print a warning */
310 void
311 maybe_warn(const char *fmt, ...)
312 {
313 va_list ap;
314
315 if (qflag == 0) {
316 va_start(ap, fmt);
317 vwarn(fmt, ap);
318 va_end(ap);
319 }
320 }
321
322 void
323 maybe_warnx(const char *fmt, ...)
324 {
325 va_list ap;
326
327 if (qflag == 0) {
328 va_start(ap, fmt);
329 vwarnx(fmt, ap);
330 va_end(ap);
331 }
332 }
333
334 /* maybe print a warning */
335 void
336 maybe_err(int rv, const char *fmt, ...)
337 {
338 va_list ap;
339
340 if (qflag == 0) {
341 va_start(ap, fmt);
342 vwarn(fmt, ap);
343 va_end(ap);
344 }
345 exit(rv);
346 }
347
348 /* maybe print a warning */
349 void
350 maybe_errx(int rv, const char *fmt, ...)
351 {
352 va_list ap;
353
354 if (qflag == 0) {
355 va_start(ap, fmt);
356 vwarnx(fmt, ap);
357 va_end(ap);
358 }
359 exit(rv);
360 }
361
362 #ifndef SMALL
363 /* split up $GZIP and prepend it to the argument list */
364 static void
365 prepend_gzip(char *gzip, int *argc, char ***argv)
366 {
367 char *s, **nargv, **ac;
368 int nenvarg = 0, i;
369
370 /* scan how many arguments there are */
371 for (s = gzip; *s; s++) {
372 if (*s == ' ' || *s == '\t')
373 continue;
374 nenvarg++;
375 for (; *s; s++)
376 if (*s == ' ' || *s == '\t')
377 break;
378 if (*s == 0)
379 break;
380 }
381 /* punt early */
382 if (nenvarg == 0)
383 return;
384
385 *argc += nenvarg;
386 ac = *argv;
387
388 nargv = (char **)malloc((*argc + 1) * sizeof(char *));
389 if (nargv == NULL)
390 maybe_err(1, "malloc");
391
392 /* stash this away */
393 *argv = nargv;
394
395 /* copy the program name first */
396 i = 0;
397 nargv[i++] = *(ac++);
398
399 /* take a copy of $GZIP and add it to the array */
400 s = strdup(gzip);
401 if (s == NULL)
402 maybe_err(1, "strdup");
403 for (; *s; s++) {
404 if (*s == ' ' || *s == '\t')
405 continue;
406 nargv[i++] = s;
407 for (; *s; s++)
408 if (*s == ' ' || *s == '\t') {
409 *s = 0;
410 break;
411 }
412 }
413
414 /* copy the original arguments and a NULL */
415 while (*ac)
416 nargv[i++] = *(ac++);
417 nargv[i] = NULL;
418 }
419 #endif
420
421 /* compress input to output then close both files */
422 static off_t
423 gz_compress(FILE *in, int out, off_t *gsizep, const char *origname, time_t mtime)
424 {
425 z_stream z;
426 char inbuf[BUFLEN], outbuf[BUFLEN];
427 off_t in_tot = 0, out_tot = 0;
428 ssize_t in_size;
429 char *str;
430 int i, error;
431 uLong crc;
432
433 i = asprintf(&str, "%c%c%c%c%c%c%c%c%c%c%s",
434 GZIP_MAGIC0, GZIP_MAGIC1,
435 Z_DEFLATED, origname ? ORIG_NAME : 0,
436 (int)mtime & 0xff,
437 (int)(mtime >> 8) & 0xff,
438 (int)(mtime >> 16) & 0xff,
439 (int)(mtime >> 24) & 0xff,
440 0, OS_CODE, origname ? origname : "");
441 if (i == -1)
442 maybe_err(1, "asprintf");
443 if (origname)
444 i++;
445 if (write(out, str, i) != i)
446 maybe_err(1, "write");
447 free(str);
448
449 memset(&z, 0, sizeof z);
450 z.next_out = outbuf;
451 z.avail_out = sizeof outbuf;
452 z.zalloc = Z_NULL;
453 z.zfree = Z_NULL;
454 z.opaque = 0;
455
456 error = deflateInit2(&z, numflag, Z_DEFLATED,
457 -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
458 if (error != Z_OK)
459 maybe_errx(1, "deflateInit2 failed");
460
461 crc = crc32(0L, Z_NULL, 0);
462 for (;;) {
463 if (z.avail_out == 0) {
464 if (write(out, outbuf, sizeof outbuf) != sizeof outbuf)
465 maybe_err(1, "write");
466
467 out_tot += sizeof outbuf;
468 z.next_out = outbuf;
469 z.avail_out = sizeof outbuf;
470 }
471
472 if (z.avail_in == 0) {
473 in_size = fread(inbuf, 1, sizeof inbuf, in);
474 if (ferror(in))
475 maybe_err(1, "fread");
476 if (in_size == 0)
477 break;
478
479 crc = crc32(crc, (const Bytef *)inbuf, (unsigned)in_size);
480 in_tot += in_size;
481 z.next_in = inbuf;
482 z.avail_in = in_size;
483 }
484
485 error = deflate(&z, Z_NO_FLUSH);
486 if (error != Z_OK && error != Z_STREAM_END)
487 maybe_errx(1, "deflate failed");
488 }
489
490 /* clean up */
491 for (;;) {
492 size_t len;
493
494 error = deflate(&z, Z_FINISH);
495 if (error != Z_OK && error != Z_STREAM_END)
496 maybe_errx(1, "deflate failed");
497
498 len = sizeof outbuf - z.avail_out;
499
500 if (write(out, outbuf, len) != len)
501 maybe_err(1, "write");
502 out_tot += len;
503 z.next_out = outbuf;
504 z.avail_out = sizeof outbuf;
505
506 if (error == Z_STREAM_END)
507 break;
508 }
509
510 if (deflateEnd(&z) != Z_OK)
511 maybe_errx(1, "deflateEnd failed");
512
513 i = asprintf(&str, "%c%c%c%c%c%c%c%c",
514 (int)crc & 0xff,
515 (int)(crc >> 8) & 0xff,
516 (int)(crc >> 16) & 0xff,
517 (int)(crc >> 24) & 0xff,
518 (int)in_tot & 0xff,
519 (int)(in_tot >> 8) & 0xff,
520 (int)(in_tot >> 16) & 0xff,
521 (int)(in_tot >> 24) & 0xff);
522 if (i != 8)
523 maybe_err(1, "asprintf");
524 if (write(out, str, i) != i)
525 maybe_err(1, "write");
526 free(str);
527
528 if (fclose(in) < 0)
529 maybe_err(1, "failed fclose");
530
531 if (gsizep)
532 *gsizep = out_tot;
533 return in_tot;
534 }
535
536 /*
537 * uncompress input to output then close the input. return the
538 * uncompressed size written, and put the compressed sized read
539 * into `*gsizep'.
540 */
541 static off_t
542 gz_uncompress(int in, int out, char *pre, size_t prelen, off_t *gsizep)
543 {
544 z_stream z;
545 char outbuf[BUFLEN], inbuf[BUFLEN];
546 off_t out_tot, in_tot;
547 enum {
548 GZSTATE_MAGIC0,
549 GZSTATE_MAGIC1,
550 GZSTATE_METHOD,
551 GZSTATE_FLAGS,
552 GZSTATE_SKIPPING,
553 GZSTATE_EXTRA,
554 GZSTATE_EXTRA2,
555 GZSTATE_EXTRA3,
556 GZSTATE_ORIGNAME,
557 GZSTATE_COMMENT,
558 GZSTATE_HEAD_CRC1,
559 GZSTATE_HEAD_CRC2,
560 GZSTATE_INIT,
561 GZSTATE_READ
562 } state = GZSTATE_MAGIC0;
563 int flags = 0, skip_count = 0;
564 int error, done_reading = 0;
565
566 #define ADVANCE() { z.next_in++; z.avail_in--; }
567
568 memset(&z, 0, sizeof z);
569 z.avail_in = prelen;
570 z.next_in = pre;
571 z.avail_out = sizeof outbuf;
572 z.next_out = outbuf;
573 z.zalloc = NULL;
574 z.zfree = NULL;
575 z.opaque = 0;
576
577 in_tot = prelen;
578 out_tot = 0;
579
580 for (;;) {
581 if (z.avail_in == 0 && done_reading == 0) {
582 size_t in_size = read(in, inbuf, BUFLEN);
583
584 if (in_size == -1) {
585 #ifndef SMALL
586 if (tflag) {
587 print_test("(stdin)", 0);
588 return 0;
589 }
590 #endif
591 maybe_warn("failed to read stdin\n");
592 return -1;
593 } else if (in_size == 0)
594 done_reading = 1;
595
596 z.avail_in = in_size;
597 z.next_in = inbuf;
598
599 in_tot += in_size;
600 }
601 switch (state) {
602 case GZSTATE_MAGIC0:
603 if (*z.next_in != GZIP_MAGIC0)
604 maybe_err(1, "input not gziped\n");
605 ADVANCE();
606 state++;
607 break;
608
609 case GZSTATE_MAGIC1:
610 if (*z.next_in != GZIP_MAGIC1 &&
611 *z.next_in != GZIP_OMAGIC1)
612 maybe_err(1, "input not gziped\n");
613 ADVANCE();
614 state++;
615 break;
616
617 case GZSTATE_METHOD:
618 if (*z.next_in != Z_DEFLATED)
619 maybe_err(1, "unknown compression method\n");
620 ADVANCE();
621 state++;
622 break;
623
624 case GZSTATE_FLAGS:
625 flags = *z.next_in;
626 ADVANCE();
627 skip_count = 6;
628 state++;
629 break;
630
631 case GZSTATE_SKIPPING:
632 if (skip_count > 0) {
633 skip_count--;
634 ADVANCE();
635 } else
636 state++;
637 break;
638
639 case GZSTATE_EXTRA:
640 if ((flags & EXTRA_FIELD) == 0) {
641 state = GZSTATE_ORIGNAME;
642 break;
643 }
644 skip_count = *z.next_in;
645 ADVANCE();
646 state++;
647 break;
648
649 case GZSTATE_EXTRA2:
650 skip_count |= ((*z.next_in) << 8);
651 ADVANCE();
652 state++;
653 break;
654
655 case GZSTATE_EXTRA3:
656 if (skip_count > 0) {
657 skip_count--;
658 ADVANCE();
659 } else
660 state++;
661 break;
662
663 case GZSTATE_ORIGNAME:
664 if ((flags & ORIG_NAME) == 0) {
665 state++;
666 break;
667 }
668 if (*z.next_in == 0)
669 state++;
670 ADVANCE();
671 break;
672
673 case GZSTATE_COMMENT:
674 if ((flags & COMMENT) == 0) {
675 state++;
676 break;
677 }
678 if (*z.next_in == 0)
679 state++;
680 ADVANCE();
681 break;
682
683 case GZSTATE_HEAD_CRC1:
684 if (flags & HEAD_CRC)
685 skip_count = 2;
686 else
687 skip_count = 0;
688 state++;
689 break;
690
691 case GZSTATE_HEAD_CRC2:
692 if (skip_count > 0) {
693 skip_count--;
694 ADVANCE();
695 } else
696 state++;
697 break;
698
699 case GZSTATE_INIT:
700 if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
701 maybe_err(1, "failed to inflateInit\n");
702 goto stop;
703 }
704 state++;
705 break;
706
707 case GZSTATE_READ:
708 error = inflate(&z, Z_FINISH);
709 if (error == Z_STREAM_END || error == Z_BUF_ERROR) {
710 size_t wr = BUFLEN - z.avail_out;
711
712 if (
713 #ifndef SMALL
714 /* don't write anything with -t */
715 tflag == 0 &&
716 #endif
717 write(out, outbuf, wr) != wr)
718 maybe_err(1, "error writing "
719 "to output\n");
720
721 out_tot += wr;
722
723 if (error == Z_STREAM_END)
724 goto stop;
725
726 z.next_out = outbuf;
727 z.avail_out = BUFLEN;
728
729 break;
730 }
731 if (error < 0) {
732 maybe_warnx("decompression error\n");
733 out_tot = -1;
734 goto stop;
735 }
736 break;
737 }
738 continue;
739 stop:
740 break;
741 }
742 if (state > GZSTATE_INIT)
743 inflateEnd(&z);
744
745 #ifndef SMALL
746 if (tflag) {
747 print_test("(stdin)", 1);
748 return 0;
749 }
750 #endif
751
752 if (gsizep)
753 *gsizep = in_tot;
754 return (out_tot);
755 }
756
757 #ifndef SMALL
758 /*
759 * set the owner, mode, flags & utimes for a file
760 */
761 static void
762 copymodes(const char *file, struct stat *sbp)
763 {
764 struct timeval times[2];
765
766 /*
767 * If we have no info on the input, give this file some
768 * default values and return..
769 */
770 if (sbp == NULL) {
771 mode_t mask = umask(022);
772
773 (void)chmod(file, DEFFILEMODE & ~mask);
774 (void)umask(mask);
775 return;
776 }
777
778 /* if the chown fails, remove set-id bits as-per compress(1) */
779 if (chown(file, sbp->st_uid, sbp->st_gid) < 0) {
780 if (errno != EPERM)
781 maybe_warn("couldn't chown: %s", file);
782 sbp->st_mode &= ~(S_ISUID|S_ISGID);
783 }
784
785 /* we only allow set-id and the 9 normal permission bits */
786 sbp->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
787 if (chmod(file, sbp->st_mode) < 0)
788 maybe_warn("couldn't chmod: %s", file);
789
790 /* only try flags if they exist already */
791 if (sbp->st_flags != 0 && chflags(file, sbp->st_flags) < 0)
792 maybe_warn("couldn't chflags: %s", file);
793
794 TIMESPEC_TO_TIMEVAL(×[0], &sbp->st_atimespec);
795 TIMESPEC_TO_TIMEVAL(×[1], &sbp->st_mtimespec);
796 if (utimes(file, times) < 0)
797 maybe_warn("couldn't utimes: %s", file);
798 }
799 #endif
800
801 /* what sort of file is this? */
802 static enum filetype
803 file_gettype(u_char *buf)
804 {
805
806 if (buf[0] == GZIP_MAGIC0 &&
807 (buf[1] == GZIP_MAGIC1 || buf[1] == GZIP_OMAGIC1))
808 return FT_GZIP;
809 else
810 #ifndef NO_BZIP2_SUPPORT
811 if (memcmp(buf, BZIP2_MAGIC, 3) == 0 &&
812 buf[3] >= '0' && buf[3] <= '9')
813 return FT_BZIP2;
814 else
815 #endif
816 #ifndef NO_COMPRESS_SUPPORT
817 if (memcmp(buf, Z_MAGIC, 2) == 0)
818 return FT_Z;
819 else
820 #endif
821 return FT_UNKNOWN;
822 }
823
824 /*
825 * compress the given file: create a corresponding .gz file and remove the
826 * original.
827 */
828 static off_t
829 file_compress(char *file)
830 {
831 FILE *in;
832 int out;
833 struct stat isb, osb;
834 char outfile[MAXPATHLEN];
835 off_t size;
836 #ifndef SMALL
837 u_int32_t mtime = 0;
838 char *savename;
839 #endif
840
841 if (cflag == 0) {
842 (void)strncpy(outfile, file, MAXPATHLEN - suffix_len);
843 outfile[MAXPATHLEN - suffix_len] = '\0';
844 (void)strlcat(outfile, suffix, sizeof(outfile));
845
846 #ifndef SMALL
847 if (fflag == 0) {
848 if (stat(outfile, &osb) == 0) {
849 maybe_warnx("%s already exists -- skipping",
850 outfile);
851 goto lose;
852 }
853 }
854 if (stat(file, &isb) == 0) {
855 if (isb.st_nlink > 1 && fflag == 0) {
856 maybe_warnx("%s has %d other link%s -- "
857 "skipping", file, isb.st_nlink - 1,
858 isb.st_nlink == 1 ? "" : "s");
859 goto lose;
860 }
861 if (nflag == 0)
862 mtime = (u_int32_t)isb.st_mtime;
863 }
864 #endif
865 }
866 in = fopen(file, "r");
867 if (in == 0)
868 maybe_err(1, "can't fopen %s", file);
869
870 if (cflag == 0) {
871 #ifndef SMALL
872 if (nflag == 0)
873 savename = basename(file);
874 else
875 savename = NULL;
876 #endif
877 out = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
878 if (out == -1) {
879 maybe_warn("could not create output: %s", outfile);
880 goto lose;
881 }
882 } else
883 out = STDOUT_FILENO;
884
885 #ifdef SMALL
886 gz_compress(in, out, NULL, NULL, 0);
887 #else
888 gz_compress(in, out, NULL, savename, mtime);
889 #endif
890
891 /*
892 * if we compressed to stdout, we don't know the size and
893 * we don't know the new file name, punt. if we can't stat
894 * the file, whine, otherwise set the size from the stat
895 * buffer. we only blow away the file if we can stat the
896 * output, just in case.
897 */
898 if (cflag == 0) {
899 if (close(out) == -1)
900 maybe_warn("couldn't close ouput");
901
902 if (stat(outfile, &osb) < 0) {
903 maybe_warn("couldn't stat: %s", outfile);
904 maybe_warnx("leaving original %s", file);
905 size = 0;
906 } else {
907 unlink(file);
908 size = osb.st_size;
909 }
910 newfile = outfile;
911 #ifndef SMALL
912 copymodes(outfile, &isb);
913 #endif
914 } else {
915 lose:
916 size = 0;
917 newfile = 0;
918 }
919
920 return (size);
921 }
922
923 /* uncompress the given file and remove the original */
924 static off_t
925 file_uncompress(char *file)
926 {
927 struct stat isb, osb;
928 char buf[PATH_MAX];
929 char *outfile = buf, *s;
930 off_t size;
931 ssize_t len = strlen(file);
932 int fd;
933 unsigned char header1[4], name[PATH_MAX + 1];
934 enum filetype method;
935
936 /* gather the old name info */
937
938 fd = open(file, O_RDONLY);
939 if (fd < 0)
940 maybe_err(1, "can't open %s", file);
941 if (read(fd, header1, sizeof header1) != sizeof header1) {
942 /* we don't want to fail here. */
943 #ifndef SMALL
944 if (fflag)
945 goto close_it;
946 #endif
947 maybe_err(1, "can't read %s", file);
948 }
949
950 method = file_gettype(header1);
951
952 #ifndef SMALL
953 if (Sflag == NULL) {
954 # ifndef NO_BZIP2_SUPPORT
955 if (method == FT_BZIP2)
956 suffix = BZ2_SUFFIX;
957 else
958 # endif
959 # ifndef NO_COMPRESS_SUPPORT
960 if (method == FT_Z)
961 suffix = Z_SUFFIX;
962 # endif
963 }
964
965 if (fflag == 0 && method == FT_UNKNOWN)
966 maybe_errx(1, "%s: not in gzip format", file);
967 #endif
968
969 if (cflag == 0 || lflag) {
970 s = &file[len - suffix_len + 1];
971 if (strncmp(s, suffix, suffix_len) == 0) {
972 (void)strncpy(outfile, file, len - suffix_len + 1);
973 outfile[len - suffix_len + 1] = '\0';
974 } else if (lflag == 0)
975 maybe_errx(1, "unknown suffix %s", s);
976 }
977
978 #ifdef SMALL
979 if (method == FT_GZIP && lflag)
980 #else
981 if (method == FT_GZIP && (Nflag || lflag))
982 #endif
983 {
984 if (header1[3] & ORIG_NAME) {
985 size_t rbytes;
986 int i;
987
988 rbytes = read(fd, name, PATH_MAX + 1);
989 if (rbytes < 0)
990 maybe_err(1, "can't read %s", file);
991 for (i = 0; i < rbytes && name[i]; i++)
992 ;
993 if (i < rbytes) {
994 name[i] = 0;
995 /* now maybe merge old dirname */
996 if (strchr(outfile, '/') == 0)
997 outfile = name;
998 else {
999 char *dir = dirname(outfile);
1000 if (asprintf(&outfile, "%s/%s", dir,
1001 name) == -1)
1002 maybe_err(1, "malloc");
1003 }
1004 }
1005 }
1006 }
1007 #ifndef SMALL
1008 close_it:
1009 #endif
1010 close(fd);
1011
1012 if (cflag == 0 || lflag) {
1013 #ifndef SMALL
1014 if (fflag == 0 && lflag == 0 && stat(outfile, &osb) == 0) {
1015 maybe_warnx("%s already exists -- skipping", outfile);
1016 goto lose;
1017 }
1018 #endif
1019 if (stat(file, &isb) == 0) {
1020 #ifndef SMALL
1021 if (isb.st_nlink > 1 && lflag == 0 && fflag == 0) {
1022 maybe_warnx("%s has %d other links -- skipping",
1023 file, isb.st_nlink - 1);
1024 goto lose;
1025 }
1026 #endif
1027 } else
1028 goto lose;
1029 }
1030
1031 #ifndef NO_BZIP2_SUPPORT
1032 if (method == FT_BZIP2) {
1033 int in, out;
1034
1035 /* XXX */
1036 if (lflag)
1037 maybe_errx(1, "no -l with bzip2 files");
1038
1039 if ((in = open(file, O_RDONLY)) == -1)
1040 maybe_err(1, "open for read: %s", file);
1041 if (cflag == 1)
1042 out = STDOUT_FILENO;
1043 else
1044 out = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
1045 if (out == -1)
1046 maybe_err(1, "open for write: %s", outfile);
1047
1048 if ((size = unbzip2(in, out, NULL, 0, NULL)) == 0) {
1049 if (cflag == 0)
1050 unlink(outfile);
1051 goto lose;
1052 }
1053 } else
1054 #endif
1055
1056 #ifndef NO_COMPRESS_SUPPORT
1057 if (method == FT_Z) {
1058 FILE *in, *out;
1059 int fd;
1060
1061 /* XXX */
1062 if (lflag)
1063 maybe_errx(1, "no -l with Lempel-Ziv files");
1064
1065 if ((in = zopen(file, NULL)) == NULL)
1066 maybe_err(1, "open for read: %s", file);
1067
1068 if (cflag == 1)
1069 fd = STDOUT_FILENO;
1070 else {
1071 fd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
1072 if (fd == -1)
1073 maybe_err(1, "open for write: %s", outfile);
1074 }
1075 out = fdopen(fd, "w");
1076 if (out == NULL)
1077 maybe_err(1, "open for write: %s", outfile);
1078
1079 size = zuncompress(in, out, NULL, 0, NULL);
1080 if (cflag == 0) {
1081 if (size == 0) {
1082 unlink(outfile);
1083 goto lose;
1084 }
1085 if (ferror(in) || fclose(in)) {
1086 unlink(outfile);
1087 maybe_err(1, "failed infile fclose");
1088 }
1089 if (fclose(out)) {
1090 unlink(outfile);
1091 maybe_err(1, "failed outfile close");
1092 }
1093 }
1094 } else
1095 #endif
1096 {
1097 int fd, in;
1098
1099 if (lflag) {
1100 if ((fd = open(file, O_RDONLY)) == -1)
1101 maybe_err(1, "open");
1102 print_list(fd, isb.st_size, outfile, isb.st_mtime);
1103 return 0; /* XXX */
1104 }
1105
1106 in = open(file, O_RDONLY);
1107 if (in == -1)
1108 maybe_err(1, "can't open %s", file);
1109
1110 if (cflag == 0) {
1111 /* Use open(2) directly to get a safe file. */
1112 fd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
1113 if (fd < 0)
1114 maybe_err(1, "can't open %s", outfile);
1115 } else
1116 fd = STDOUT_FILENO;
1117
1118 size = gz_uncompress(in, fd, NULL, 0, NULL);
1119 if (cflag == 0) {
1120 if (size == -1) {
1121 unlink(outfile);
1122 goto lose;
1123 }
1124 if (close(fd))
1125 maybe_err(1, "failed close");
1126 }
1127 }
1128
1129 /* if testing, or we uncompressed to stdout, this is all we need */
1130 #ifndef SMALL
1131 if (tflag)
1132 return (size);
1133 #endif
1134 if (cflag)
1135 return (size);
1136
1137 /*
1138 * if we create a file...
1139 */
1140 if (cflag == 0) {
1141 /*
1142 * if we can't stat the file, or we are uncompressing to
1143 * stdin, don't remove the file.
1144 */
1145 if (stat(outfile, &osb) < 0) {
1146 maybe_warn("couldn't stat (leaving original): %s",
1147 outfile);
1148 goto lose;
1149 }
1150 if (osb.st_size != size) {
1151 maybe_warn("stat gave different size: %llu != %llu "
1152 "(leaving original)",
1153 (unsigned long long)size,
1154 (unsigned long long)osb.st_size);
1155 goto lose;
1156 }
1157 newfile = outfile;
1158 if (cflag == 0)
1159 unlink(file);
1160 size = osb.st_size;
1161 #ifndef SMALL
1162 copymodes(outfile, &isb);
1163 #endif
1164 }
1165 return (size);
1166
1167 lose:
1168 newfile = 0;
1169 return 0;
1170 }
1171
1172 #ifndef SMALL
1173 static off_t
1174 cat_stdin(unsigned char * prepend, size_t count, off_t *gsizep)
1175 {
1176 char buf[BUFLEN];
1177 size_t rv;
1178 off_t in_tot;
1179
1180 in_tot = count;
1181 if (write(STDOUT_FILENO, prepend, count) != count)
1182 maybe_err(1, "write to stdout");
1183 for (;;) {
1184 rv = read(STDIN_FILENO, buf, sizeof buf);
1185
1186 if (write(STDOUT_FILENO, buf, rv) != rv)
1187 maybe_err(1, "write to stdout");
1188 in_tot += rv;
1189 }
1190
1191 if (gsizep)
1192 *gsizep = in_tot;
1193 return (in_tot);
1194 }
1195 #endif
1196
1197 static void
1198 handle_stdin(void)
1199 {
1200 unsigned char header1[4];
1201 off_t usize, gsize;
1202 enum filetype method;
1203 #ifndef NO_COMPRESS_SUPPORT
1204 FILE *in;
1205 #endif
1206
1207 #ifndef SMALL
1208 if (fflag == 0 && lflag == 0 && isatty(STDIN_FILENO)) {
1209 maybe_warnx("standard input is a terminal -- ignoring");
1210 return;
1211 }
1212 #endif
1213
1214 if (lflag) {
1215 struct stat isb;
1216
1217 /* XXX could read the whole file, etc. */
1218 if (fstat(STDIN_FILENO, &isb) < 0)
1219 maybe_err(1, "fstat");
1220 print_list(STDIN_FILENO, isb.st_size, "stdout", isb.st_mtime);
1221 return;
1222 }
1223
1224 if (read(STDIN_FILENO, header1, sizeof header1) != sizeof header1)
1225 maybe_err(1, "can't read stdin");
1226
1227 method = file_gettype(header1);
1228 switch (method) {
1229 default:
1230 #ifndef SMALL
1231 if (fflag == 0)
1232 maybe_errx(1, "unknown compression format");
1233 usize = cat_stdin(header1, sizeof header1, &gsize);
1234 break;
1235 #endif
1236 case FT_GZIP:
1237 usize = gz_uncompress(STDIN_FILENO, STDOUT_FILENO,
1238 header1, sizeof header1, &gsize);
1239 break;
1240 #ifndef NO_BZIP2_SUPPORT
1241 case FT_BZIP2:
1242 usize = unbzip2(STDIN_FILENO, STDOUT_FILENO,
1243 header1, sizeof header1, &gsize);
1244 break;
1245 #endif
1246 #ifndef NO_COMPRESS_SUPPORT
1247 case FT_Z:
1248 if ((in = zopen(NULL, stdin)) == NULL)
1249 maybe_err(1, "zopen of stdin");
1250
1251 usize = zuncompress(in, stdout, header1, sizeof header1, &gsize);
1252 break;
1253 #endif
1254 }
1255
1256 #ifndef SMALL
1257 if (vflag && !tflag && usize != -1 && gsize != -1)
1258 print_verbage(NULL, 0, usize, gsize);
1259 #endif
1260
1261 }
1262
1263 static void
1264 handle_stdout(void)
1265 {
1266 off_t gsize, usize;
1267
1268 #ifndef SMALL
1269 if (fflag == 0 && isatty(STDOUT_FILENO)) {
1270 maybe_warnx("standard output is a terminal -- ignoring");
1271 return;
1272 }
1273 #endif
1274 usize = gz_compress(stdin, STDOUT_FILENO, &gsize, NULL, 0);
1275
1276 #ifndef SMALL
1277 if (vflag && !tflag && usize != -1 && gsize != -1)
1278 print_verbage(NULL, 0, usize, gsize);
1279 #endif
1280 }
1281
1282 /* do what is asked for, for the path name */
1283 static void
1284 handle_pathname(char *path)
1285 {
1286 char *opath = path, *s = 0;
1287 ssize_t len;
1288 struct stat sb;
1289
1290 /* check for stdout/stdin */
1291 if (path[0] == '-' && path[1] == '\0') {
1292 if (dflag)
1293 handle_stdin();
1294 else
1295 handle_stdout();
1296 }
1297
1298 retry:
1299 if (stat(path, &sb) < 0) {
1300 /* lets try <path>.gz if we're decompressing */
1301 if (dflag && s == 0 && errno == ENOENT) {
1302 len = strlen(path);
1303 s = malloc(len + suffix_len);
1304 if (s == 0)
1305 maybe_err(1, "malloc");
1306 memmove(s, path, len);
1307 memmove(&s[len], suffix, suffix_len);
1308 path = s;
1309 goto retry;
1310 }
1311 maybe_warn("can't stat: %s", opath);
1312 goto out;
1313 }
1314
1315 if (S_ISDIR(sb.st_mode)) {
1316 #ifndef SMALL
1317 if (rflag)
1318 handle_dir(path, &sb);
1319 else
1320 #endif
1321 maybe_warn("%s is a directory", path);
1322 goto out;
1323 }
1324
1325 if (S_ISREG(sb.st_mode))
1326 handle_file(path, &sb);
1327
1328 out:
1329 if (s)
1330 free(s);
1331 }
1332
1333 /* compress/decompress a file */
1334 static void
1335 handle_file(char *file, struct stat *sbp)
1336 {
1337 off_t usize, gsize;
1338
1339 infile = file;
1340 if (dflag) {
1341 usize = file_uncompress(file);
1342 if (usize == 0)
1343 return;
1344 gsize = sbp->st_size;
1345 } else {
1346 gsize = file_compress(file);
1347 if (gsize == 0)
1348 return;
1349 usize = sbp->st_size;
1350 }
1351
1352
1353 #ifndef SMALL
1354 if (vflag && !tflag)
1355 print_verbage(file, cflag == 0 ? newfile : 0, usize, gsize);
1356 #endif
1357 }
1358
1359 #ifndef SMALL
1360 /* this is used with -r to recursively decend directories */
1361 static void
1362 handle_dir(char *dir, struct stat *sbp)
1363 {
1364 char *path_argv[2];
1365 FTS *fts;
1366 FTSENT *entry;
1367
1368 path_argv[0] = dir;
1369 path_argv[1] = 0;
1370 fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
1371 if (fts == NULL) {
1372 warn("couldn't fts_open %s", dir);
1373 return;
1374 }
1375
1376 while ((entry = fts_read(fts))) {
1377 switch(entry->fts_info) {
1378 case FTS_D:
1379 case FTS_DP:
1380 continue;
1381
1382 case FTS_DNR:
1383 case FTS_ERR:
1384 case FTS_NS:
1385 maybe_warn("%s", entry->fts_path);
1386 continue;
1387 case FTS_F:
1388 handle_file(entry->fts_name, entry->fts_statp);
1389 }
1390 }
1391 (void)fts_close(fts);
1392 }
1393 #endif
1394
1395 /* print a ratio */
1396 static void
1397 print_ratio(off_t in, off_t out, FILE *where)
1398 {
1399 int64_t percent10; /* 10 * percent */
1400 off_t diff = in - out;
1401 char ch;
1402
1403 if (in == 0)
1404 percent10 = 0;
1405 else if (diff > 0x400000) /* anything with 22 or more bits */
1406 percent10 = diff / (in / 1000);
1407 else
1408 percent10 = (1000 * diff) / in;
1409
1410 if (percent10 < 0) {
1411 percent10 = -percent10;
1412 ch = '-';
1413 } else
1414 ch = ' ';
1415
1416 /*
1417 * ugh. for negative percentages < 10, we need to avoid printing a
1418 * a space between the "-" and the single number.
1419 */
1420 if (ch == '-' && percent10 / 10LL < 10)
1421 fprintf(where, " -%1d.%1u%%", (unsigned)(percent10 / 10LL),
1422 (unsigned)(percent10 % 10LL));
1423 else
1424 fprintf(where, "%c%2d.%1u%%", ch, (unsigned)(percent10 / 10LL),
1425 (unsigned)(percent10 % 10LL));
1426 }
1427
1428 #ifndef SMALL
1429 /* print compression statistics, and the new name (if there is one!) */
1430 static void
1431 print_verbage(char *file, char *nfile, off_t usize, off_t gsize)
1432 {
1433 if (file)
1434 fprintf(stderr, "%s:%s ", file,
1435 strlen(file) < 7 ? "\t\t" : "\t");
1436 print_ratio((off_t)usize, (off_t)gsize, stderr);
1437 if (nfile)
1438 fprintf(stderr, " -- replaced with %s", nfile);
1439 fprintf(stderr, "\n");
1440 fflush(stderr);
1441 }
1442
1443 /* print test results */
1444 static void
1445 print_test(char *file, int ok)
1446 {
1447
1448 fprintf(stderr, "%s:%s %s\n", file,
1449 strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK");
1450 fflush(stderr);
1451 }
1452 #endif
1453
1454 /* print a file's info ala --list */
1455 /* eg:
1456 compressed uncompressed ratio uncompressed_name
1457 354841 1679360 78.8% /usr/pkgsrc/distfiles/libglade-2.0.1.tar
1458 */
1459 static void
1460 print_list(int fd, off_t out, const char *outfile, time_t ts)
1461 {
1462 static int first = 1;
1463 #ifndef SMALL
1464 static off_t in_tot, out_tot;
1465 u_int32_t crc;
1466 #endif
1467 off_t in;
1468 int rv;
1469
1470 if (first) {
1471 #ifndef SMALL
1472 if (vflag)
1473 printf("method crc date time ");
1474 #endif
1475 if (qflag == 0)
1476 printf(" compressed uncompressed "
1477 "ratio uncompressed_name\n");
1478 }
1479 first = 0;
1480
1481 /* print totals? */
1482 #ifndef SMALL
1483 if (fd == -1) {
1484 in = in_tot;
1485 out = out_tot;
1486 } else
1487 #endif
1488 {
1489 /* read the last 4 bytes - this is the uncompressed size */
1490 rv = lseek(fd, (off_t)(-8), SEEK_END);
1491 if (rv != -1) {
1492 unsigned char buf[8];
1493 u_int32_t usize;
1494
1495 if (read(fd, (char *)buf, sizeof(buf)) != sizeof(buf))
1496 maybe_warn("read of uncompressed size");
1497 usize = buf[4] | buf[5] << 8 | buf[6] << 16 | buf[7] << 24;
1498 in = (off_t)usize;
1499 #ifndef SMALL
1500 crc = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
1501 #endif
1502 }
1503 }
1504
1505 #ifndef SMALL
1506 if (vflag && fd == -1)
1507 printf(" ");
1508 else if (vflag) {
1509 char *date = ctime(&ts);
1510
1511 /* skip the day, 1/100th second, and year */
1512 date += 4;
1513 date[12] = 0;
1514 printf("%5s %08x %11s ", "defla"/*XXX*/, crc, date);
1515 }
1516 in_tot += in;
1517 out_tot += out;
1518 #endif
1519 printf("%12llu %12llu ", (unsigned long long)out, (unsigned long long)in);
1520 print_ratio(in, out, stdout);
1521 printf(" %s\n", outfile);
1522 }
1523
1524 /* display the usage of NetBSD gzip */
1525 static void
1526 usage(void)
1527 {
1528
1529 fprintf(stderr, "%s\n", gzip_version);
1530 fprintf(stderr,
1531 "usage: %s [-" OPT_LIST "] [<file> [<file> ...]]\n"
1532 #ifndef SMALL
1533 " -c --stdout write to stdout, keep original files\n"
1534 " --to-stdout\n"
1535 " -d --decompress uncompress files\n"
1536 " --uncompress\n"
1537 " -f --force force overwriting & compress links\n"
1538 " -h --help display this help\n"
1539 " -n --no-name don't save original file name or time stamp\n"
1540 " -N --name save or restore original file name and time stamp\n"
1541 " -q --quiet output no warnings\n"
1542 " -r --recursive recursively compress files in directories\n"
1543 " -S .suf use suffix .suf instead of .gz\n"
1544 " --suffix .suf\n"
1545 " -t --test test compressed file\n"
1546 " -v --verbose print extra statistics\n"
1547 " -V --version display program version\n"
1548 " -1 --fast fastest (worst) compression\n"
1549 " -2 .. -8 set compression level\n"
1550 " -9 --best best (slowest) compression\n",
1551 #else
1552 ,
1553 #endif
1554 getprogname());
1555 exit(0);
1556 }
1557
1558 /* display the version of NetBSD gzip */
1559 static void
1560 display_version(void)
1561 {
1562
1563 fprintf(stderr, "%s\n", gzip_version);
1564 exit(0);
1565 }
1566
1567 #ifndef NO_BZIP2_SUPPORT
1568 #include "unbzip2.c"
1569 #endif
1570 #ifndef NO_COMPRESS_SUPPORT
1571 #include "zuncompress.c"
1572 #endif
1573