xref: /netbsd-src/external/gpl3/binutils.old/dist/zlib/examples/gzjoin.c (revision 16dce51364ebe8aeafbae46bc5aa167b8115bc45)
1*16dce513Schristos /* gzjoin -- command to join gzip files into one gzip file
2*16dce513Schristos 
3*16dce513Schristos   Copyright (C) 2004, 2005, 2012 Mark Adler, all rights reserved
4*16dce513Schristos   version 1.2, 14 Aug 2012
5*16dce513Schristos 
6*16dce513Schristos   This software is provided 'as-is', without any express or implied
7*16dce513Schristos   warranty.  In no event will the author be held liable for any damages
8*16dce513Schristos   arising from the use of this software.
9*16dce513Schristos 
10*16dce513Schristos   Permission is granted to anyone to use this software for any purpose,
11*16dce513Schristos   including commercial applications, and to alter it and redistribute it
12*16dce513Schristos   freely, subject to the following restrictions:
13*16dce513Schristos 
14*16dce513Schristos   1. The origin of this software must not be misrepresented; you must not
15*16dce513Schristos      claim that you wrote the original software. If you use this software
16*16dce513Schristos      in a product, an acknowledgment in the product documentation would be
17*16dce513Schristos      appreciated but is not required.
18*16dce513Schristos   2. Altered source versions must be plainly marked as such, and must not be
19*16dce513Schristos      misrepresented as being the original software.
20*16dce513Schristos   3. This notice may not be removed or altered from any source distribution.
21*16dce513Schristos 
22*16dce513Schristos   Mark Adler    madler@alumni.caltech.edu
23*16dce513Schristos  */
24*16dce513Schristos 
25*16dce513Schristos /*
26*16dce513Schristos  * Change history:
27*16dce513Schristos  *
28*16dce513Schristos  * 1.0  11 Dec 2004     - First version
29*16dce513Schristos  * 1.1  12 Jun 2005     - Changed ssize_t to long for portability
30*16dce513Schristos  * 1.2  14 Aug 2012     - Clean up for z_const usage
31*16dce513Schristos  */
32*16dce513Schristos 
33*16dce513Schristos /*
34*16dce513Schristos    gzjoin takes one or more gzip files on the command line and writes out a
35*16dce513Schristos    single gzip file that will uncompress to the concatenation of the
36*16dce513Schristos    uncompressed data from the individual gzip files.  gzjoin does this without
37*16dce513Schristos    having to recompress any of the data and without having to calculate a new
38*16dce513Schristos    crc32 for the concatenated uncompressed data.  gzjoin does however have to
39*16dce513Schristos    decompress all of the input data in order to find the bits in the compressed
40*16dce513Schristos    data that need to be modified to concatenate the streams.
41*16dce513Schristos 
42*16dce513Schristos    gzjoin does not do an integrity check on the input gzip files other than
43*16dce513Schristos    checking the gzip header and decompressing the compressed data.  They are
44*16dce513Schristos    otherwise assumed to be complete and correct.
45*16dce513Schristos 
46*16dce513Schristos    Each joint between gzip files removes at least 18 bytes of previous trailer
47*16dce513Schristos    and subsequent header, and inserts an average of about three bytes to the
48*16dce513Schristos    compressed data in order to connect the streams.  The output gzip file
49*16dce513Schristos    has a minimal ten-byte gzip header with no file name or modification time.
50*16dce513Schristos 
51*16dce513Schristos    This program was written to illustrate the use of the Z_BLOCK option of
52*16dce513Schristos    inflate() and the crc32_combine() function.  gzjoin will not compile with
53*16dce513Schristos    versions of zlib earlier than 1.2.3.
54*16dce513Schristos  */
55*16dce513Schristos 
56*16dce513Schristos #include <stdio.h>      /* fputs(), fprintf(), fwrite(), putc() */
57*16dce513Schristos #include <stdlib.h>     /* exit(), malloc(), free() */
58*16dce513Schristos #include <fcntl.h>      /* open() */
59*16dce513Schristos #include <unistd.h>     /* close(), read(), lseek() */
60*16dce513Schristos #include "zlib.h"
61*16dce513Schristos     /* crc32(), crc32_combine(), inflateInit2(), inflate(), inflateEnd() */
62*16dce513Schristos 
63*16dce513Schristos #define local static
64*16dce513Schristos 
65*16dce513Schristos /* exit with an error (return a value to allow use in an expression) */
bail(char * why1,char * why2)66*16dce513Schristos local int bail(char *why1, char *why2)
67*16dce513Schristos {
68*16dce513Schristos     fprintf(stderr, "gzjoin error: %s%s, output incomplete\n", why1, why2);
69*16dce513Schristos     exit(1);
70*16dce513Schristos     return 0;
71*16dce513Schristos }
72*16dce513Schristos 
73*16dce513Schristos /* -- simple buffered file input with access to the buffer -- */
74*16dce513Schristos 
75*16dce513Schristos #define CHUNK 32768         /* must be a power of two and fit in unsigned */
76*16dce513Schristos 
77*16dce513Schristos /* bin buffered input file type */
78*16dce513Schristos typedef struct {
79*16dce513Schristos     char *name;             /* name of file for error messages */
80*16dce513Schristos     int fd;                 /* file descriptor */
81*16dce513Schristos     unsigned left;          /* bytes remaining at next */
82*16dce513Schristos     unsigned char *next;    /* next byte to read */
83*16dce513Schristos     unsigned char *buf;     /* allocated buffer of length CHUNK */
84*16dce513Schristos } bin;
85*16dce513Schristos 
86*16dce513Schristos /* close a buffered file and free allocated memory */
bclose(bin * in)87*16dce513Schristos local void bclose(bin *in)
88*16dce513Schristos {
89*16dce513Schristos     if (in != NULL) {
90*16dce513Schristos         if (in->fd != -1)
91*16dce513Schristos             close(in->fd);
92*16dce513Schristos         if (in->buf != NULL)
93*16dce513Schristos             free(in->buf);
94*16dce513Schristos         free(in);
95*16dce513Schristos     }
96*16dce513Schristos }
97*16dce513Schristos 
98*16dce513Schristos /* open a buffered file for input, return a pointer to type bin, or NULL on
99*16dce513Schristos    failure */
bopen(char * name)100*16dce513Schristos local bin *bopen(char *name)
101*16dce513Schristos {
102*16dce513Schristos     bin *in;
103*16dce513Schristos 
104*16dce513Schristos     in = malloc(sizeof(bin));
105*16dce513Schristos     if (in == NULL)
106*16dce513Schristos         return NULL;
107*16dce513Schristos     in->buf = malloc(CHUNK);
108*16dce513Schristos     in->fd = open(name, O_RDONLY, 0);
109*16dce513Schristos     if (in->buf == NULL || in->fd == -1) {
110*16dce513Schristos         bclose(in);
111*16dce513Schristos         return NULL;
112*16dce513Schristos     }
113*16dce513Schristos     in->left = 0;
114*16dce513Schristos     in->next = in->buf;
115*16dce513Schristos     in->name = name;
116*16dce513Schristos     return in;
117*16dce513Schristos }
118*16dce513Schristos 
119*16dce513Schristos /* load buffer from file, return -1 on read error, 0 or 1 on success, with
120*16dce513Schristos    1 indicating that end-of-file was reached */
bload(bin * in)121*16dce513Schristos local int bload(bin *in)
122*16dce513Schristos {
123*16dce513Schristos     long len;
124*16dce513Schristos 
125*16dce513Schristos     if (in == NULL)
126*16dce513Schristos         return -1;
127*16dce513Schristos     if (in->left != 0)
128*16dce513Schristos         return 0;
129*16dce513Schristos     in->next = in->buf;
130*16dce513Schristos     do {
131*16dce513Schristos         len = (long)read(in->fd, in->buf + in->left, CHUNK - in->left);
132*16dce513Schristos         if (len < 0)
133*16dce513Schristos             return -1;
134*16dce513Schristos         in->left += (unsigned)len;
135*16dce513Schristos     } while (len != 0 && in->left < CHUNK);
136*16dce513Schristos     return len == 0 ? 1 : 0;
137*16dce513Schristos }
138*16dce513Schristos 
139*16dce513Schristos /* get a byte from the file, bail if end of file */
140*16dce513Schristos #define bget(in) (in->left ? 0 : bload(in), \
141*16dce513Schristos                   in->left ? (in->left--, *(in->next)++) : \
142*16dce513Schristos                     bail("unexpected end of file on ", in->name))
143*16dce513Schristos 
144*16dce513Schristos /* get a four-byte little-endian unsigned integer from file */
bget4(bin * in)145*16dce513Schristos local unsigned long bget4(bin *in)
146*16dce513Schristos {
147*16dce513Schristos     unsigned long val;
148*16dce513Schristos 
149*16dce513Schristos     val = bget(in);
150*16dce513Schristos     val += (unsigned long)(bget(in)) << 8;
151*16dce513Schristos     val += (unsigned long)(bget(in)) << 16;
152*16dce513Schristos     val += (unsigned long)(bget(in)) << 24;
153*16dce513Schristos     return val;
154*16dce513Schristos }
155*16dce513Schristos 
156*16dce513Schristos /* skip bytes in file */
bskip(bin * in,unsigned skip)157*16dce513Schristos local void bskip(bin *in, unsigned skip)
158*16dce513Schristos {
159*16dce513Schristos     /* check pointer */
160*16dce513Schristos     if (in == NULL)
161*16dce513Schristos         return;
162*16dce513Schristos 
163*16dce513Schristos     /* easy case -- skip bytes in buffer */
164*16dce513Schristos     if (skip <= in->left) {
165*16dce513Schristos         in->left -= skip;
166*16dce513Schristos         in->next += skip;
167*16dce513Schristos         return;
168*16dce513Schristos     }
169*16dce513Schristos 
170*16dce513Schristos     /* skip what's in buffer, discard buffer contents */
171*16dce513Schristos     skip -= in->left;
172*16dce513Schristos     in->left = 0;
173*16dce513Schristos 
174*16dce513Schristos     /* seek past multiples of CHUNK bytes */
175*16dce513Schristos     if (skip > CHUNK) {
176*16dce513Schristos         unsigned left;
177*16dce513Schristos 
178*16dce513Schristos         left = skip & (CHUNK - 1);
179*16dce513Schristos         if (left == 0) {
180*16dce513Schristos             /* exact number of chunks: seek all the way minus one byte to check
181*16dce513Schristos                for end-of-file with a read */
182*16dce513Schristos             lseek(in->fd, skip - 1, SEEK_CUR);
183*16dce513Schristos             if (read(in->fd, in->buf, 1) != 1)
184*16dce513Schristos                 bail("unexpected end of file on ", in->name);
185*16dce513Schristos             return;
186*16dce513Schristos         }
187*16dce513Schristos 
188*16dce513Schristos         /* skip the integral chunks, update skip with remainder */
189*16dce513Schristos         lseek(in->fd, skip - left, SEEK_CUR);
190*16dce513Schristos         skip = left;
191*16dce513Schristos     }
192*16dce513Schristos 
193*16dce513Schristos     /* read more input and skip remainder */
194*16dce513Schristos     bload(in);
195*16dce513Schristos     if (skip > in->left)
196*16dce513Schristos         bail("unexpected end of file on ", in->name);
197*16dce513Schristos     in->left -= skip;
198*16dce513Schristos     in->next += skip;
199*16dce513Schristos }
200*16dce513Schristos 
201*16dce513Schristos /* -- end of buffered input functions -- */
202*16dce513Schristos 
203*16dce513Schristos /* skip the gzip header from file in */
gzhead(bin * in)204*16dce513Schristos local void gzhead(bin *in)
205*16dce513Schristos {
206*16dce513Schristos     int flags;
207*16dce513Schristos 
208*16dce513Schristos     /* verify gzip magic header and compression method */
209*16dce513Schristos     if (bget(in) != 0x1f || bget(in) != 0x8b || bget(in) != 8)
210*16dce513Schristos         bail(in->name, " is not a valid gzip file");
211*16dce513Schristos 
212*16dce513Schristos     /* get and verify flags */
213*16dce513Schristos     flags = bget(in);
214*16dce513Schristos     if ((flags & 0xe0) != 0)
215*16dce513Schristos         bail("unknown reserved bits set in ", in->name);
216*16dce513Schristos 
217*16dce513Schristos     /* skip modification time, extra flags, and os */
218*16dce513Schristos     bskip(in, 6);
219*16dce513Schristos 
220*16dce513Schristos     /* skip extra field if present */
221*16dce513Schristos     if (flags & 4) {
222*16dce513Schristos         unsigned len;
223*16dce513Schristos 
224*16dce513Schristos         len = bget(in);
225*16dce513Schristos         len += (unsigned)(bget(in)) << 8;
226*16dce513Schristos         bskip(in, len);
227*16dce513Schristos     }
228*16dce513Schristos 
229*16dce513Schristos     /* skip file name if present */
230*16dce513Schristos     if (flags & 8)
231*16dce513Schristos         while (bget(in) != 0)
232*16dce513Schristos             ;
233*16dce513Schristos 
234*16dce513Schristos     /* skip comment if present */
235*16dce513Schristos     if (flags & 16)
236*16dce513Schristos         while (bget(in) != 0)
237*16dce513Schristos             ;
238*16dce513Schristos 
239*16dce513Schristos     /* skip header crc if present */
240*16dce513Schristos     if (flags & 2)
241*16dce513Schristos         bskip(in, 2);
242*16dce513Schristos }
243*16dce513Schristos 
244*16dce513Schristos /* write a four-byte little-endian unsigned integer to out */
put4(unsigned long val,FILE * out)245*16dce513Schristos local void put4(unsigned long val, FILE *out)
246*16dce513Schristos {
247*16dce513Schristos     putc(val & 0xff, out);
248*16dce513Schristos     putc((val >> 8) & 0xff, out);
249*16dce513Schristos     putc((val >> 16) & 0xff, out);
250*16dce513Schristos     putc((val >> 24) & 0xff, out);
251*16dce513Schristos }
252*16dce513Schristos 
253*16dce513Schristos /* Load up zlib stream from buffered input, bail if end of file */
zpull(z_streamp strm,bin * in)254*16dce513Schristos local void zpull(z_streamp strm, bin *in)
255*16dce513Schristos {
256*16dce513Schristos     if (in->left == 0)
257*16dce513Schristos         bload(in);
258*16dce513Schristos     if (in->left == 0)
259*16dce513Schristos         bail("unexpected end of file on ", in->name);
260*16dce513Schristos     strm->avail_in = in->left;
261*16dce513Schristos     strm->next_in = in->next;
262*16dce513Schristos }
263*16dce513Schristos 
264*16dce513Schristos /* Write header for gzip file to out and initialize trailer. */
gzinit(unsigned long * crc,unsigned long * tot,FILE * out)265*16dce513Schristos local void gzinit(unsigned long *crc, unsigned long *tot, FILE *out)
266*16dce513Schristos {
267*16dce513Schristos     fwrite("\x1f\x8b\x08\0\0\0\0\0\0\xff", 1, 10, out);
268*16dce513Schristos     *crc = crc32(0L, Z_NULL, 0);
269*16dce513Schristos     *tot = 0;
270*16dce513Schristos }
271*16dce513Schristos 
272*16dce513Schristos /* Copy the compressed data from name, zeroing the last block bit of the last
273*16dce513Schristos    block if clr is true, and adding empty blocks as needed to get to a byte
274*16dce513Schristos    boundary.  If clr is false, then the last block becomes the last block of
275*16dce513Schristos    the output, and the gzip trailer is written.  crc and tot maintains the
276*16dce513Schristos    crc and length (modulo 2^32) of the output for the trailer.  The resulting
277*16dce513Schristos    gzip file is written to out.  gzinit() must be called before the first call
278*16dce513Schristos    of gzcopy() to write the gzip header and to initialize crc and tot. */
gzcopy(char * name,int clr,unsigned long * crc,unsigned long * tot,FILE * out)279*16dce513Schristos local void gzcopy(char *name, int clr, unsigned long *crc, unsigned long *tot,
280*16dce513Schristos                   FILE *out)
281*16dce513Schristos {
282*16dce513Schristos     int ret;                /* return value from zlib functions */
283*16dce513Schristos     int pos;                /* where the "last block" bit is in byte */
284*16dce513Schristos     int last;               /* true if processing the last block */
285*16dce513Schristos     bin *in;                /* buffered input file */
286*16dce513Schristos     unsigned char *start;   /* start of compressed data in buffer */
287*16dce513Schristos     unsigned char *junk;    /* buffer for uncompressed data -- discarded */
288*16dce513Schristos     z_off_t len;            /* length of uncompressed data (support > 4 GB) */
289*16dce513Schristos     z_stream strm;          /* zlib inflate stream */
290*16dce513Schristos 
291*16dce513Schristos     /* open gzip file and skip header */
292*16dce513Schristos     in = bopen(name);
293*16dce513Schristos     if (in == NULL)
294*16dce513Schristos         bail("could not open ", name);
295*16dce513Schristos     gzhead(in);
296*16dce513Schristos 
297*16dce513Schristos     /* allocate buffer for uncompressed data and initialize raw inflate
298*16dce513Schristos        stream */
299*16dce513Schristos     junk = malloc(CHUNK);
300*16dce513Schristos     strm.zalloc = Z_NULL;
301*16dce513Schristos     strm.zfree = Z_NULL;
302*16dce513Schristos     strm.opaque = Z_NULL;
303*16dce513Schristos     strm.avail_in = 0;
304*16dce513Schristos     strm.next_in = Z_NULL;
305*16dce513Schristos     ret = inflateInit2(&strm, -15);
306*16dce513Schristos     if (junk == NULL || ret != Z_OK)
307*16dce513Schristos         bail("out of memory", "");
308*16dce513Schristos 
309*16dce513Schristos     /* inflate and copy compressed data, clear last-block bit if requested */
310*16dce513Schristos     len = 0;
311*16dce513Schristos     zpull(&strm, in);
312*16dce513Schristos     start = in->next;
313*16dce513Schristos     last = start[0] & 1;
314*16dce513Schristos     if (last && clr)
315*16dce513Schristos         start[0] &= ~1;
316*16dce513Schristos     strm.avail_out = 0;
317*16dce513Schristos     for (;;) {
318*16dce513Schristos         /* if input used and output done, write used input and get more */
319*16dce513Schristos         if (strm.avail_in == 0 && strm.avail_out != 0) {
320*16dce513Schristos             fwrite(start, 1, strm.next_in - start, out);
321*16dce513Schristos             start = in->buf;
322*16dce513Schristos             in->left = 0;
323*16dce513Schristos             zpull(&strm, in);
324*16dce513Schristos         }
325*16dce513Schristos 
326*16dce513Schristos         /* decompress -- return early when end-of-block reached */
327*16dce513Schristos         strm.avail_out = CHUNK;
328*16dce513Schristos         strm.next_out = junk;
329*16dce513Schristos         ret = inflate(&strm, Z_BLOCK);
330*16dce513Schristos         switch (ret) {
331*16dce513Schristos         case Z_MEM_ERROR:
332*16dce513Schristos             bail("out of memory", "");
333*16dce513Schristos         case Z_DATA_ERROR:
334*16dce513Schristos             bail("invalid compressed data in ", in->name);
335*16dce513Schristos         }
336*16dce513Schristos 
337*16dce513Schristos         /* update length of uncompressed data */
338*16dce513Schristos         len += CHUNK - strm.avail_out;
339*16dce513Schristos 
340*16dce513Schristos         /* check for block boundary (only get this when block copied out) */
341*16dce513Schristos         if (strm.data_type & 128) {
342*16dce513Schristos             /* if that was the last block, then done */
343*16dce513Schristos             if (last)
344*16dce513Schristos                 break;
345*16dce513Schristos 
346*16dce513Schristos             /* number of unused bits in last byte */
347*16dce513Schristos             pos = strm.data_type & 7;
348*16dce513Schristos 
349*16dce513Schristos             /* find the next last-block bit */
350*16dce513Schristos             if (pos != 0) {
351*16dce513Schristos                 /* next last-block bit is in last used byte */
352*16dce513Schristos                 pos = 0x100 >> pos;
353*16dce513Schristos                 last = strm.next_in[-1] & pos;
354*16dce513Schristos                 if (last && clr)
355*16dce513Schristos                     in->buf[strm.next_in - in->buf - 1] &= ~pos;
356*16dce513Schristos             }
357*16dce513Schristos             else {
358*16dce513Schristos                 /* next last-block bit is in next unused byte */
359*16dce513Schristos                 if (strm.avail_in == 0) {
360*16dce513Schristos                     /* don't have that byte yet -- get it */
361*16dce513Schristos                     fwrite(start, 1, strm.next_in - start, out);
362*16dce513Schristos                     start = in->buf;
363*16dce513Schristos                     in->left = 0;
364*16dce513Schristos                     zpull(&strm, in);
365*16dce513Schristos                 }
366*16dce513Schristos                 last = strm.next_in[0] & 1;
367*16dce513Schristos                 if (last && clr)
368*16dce513Schristos                     in->buf[strm.next_in - in->buf] &= ~1;
369*16dce513Schristos             }
370*16dce513Schristos         }
371*16dce513Schristos     }
372*16dce513Schristos 
373*16dce513Schristos     /* update buffer with unused input */
374*16dce513Schristos     in->left = strm.avail_in;
375*16dce513Schristos     in->next = in->buf + (strm.next_in - in->buf);
376*16dce513Schristos 
377*16dce513Schristos     /* copy used input, write empty blocks to get to byte boundary */
378*16dce513Schristos     pos = strm.data_type & 7;
379*16dce513Schristos     fwrite(start, 1, in->next - start - 1, out);
380*16dce513Schristos     last = in->next[-1];
381*16dce513Schristos     if (pos == 0 || !clr)
382*16dce513Schristos         /* already at byte boundary, or last file: write last byte */
383*16dce513Schristos         putc(last, out);
384*16dce513Schristos     else {
385*16dce513Schristos         /* append empty blocks to last byte */
386*16dce513Schristos         last &= ((0x100 >> pos) - 1);       /* assure unused bits are zero */
387*16dce513Schristos         if (pos & 1) {
388*16dce513Schristos             /* odd -- append an empty stored block */
389*16dce513Schristos             putc(last, out);
390*16dce513Schristos             if (pos == 1)
391*16dce513Schristos                 putc(0, out);               /* two more bits in block header */
392*16dce513Schristos             fwrite("\0\0\xff\xff", 1, 4, out);
393*16dce513Schristos         }
394*16dce513Schristos         else {
395*16dce513Schristos             /* even -- append 1, 2, or 3 empty fixed blocks */
396*16dce513Schristos             switch (pos) {
397*16dce513Schristos             case 6:
398*16dce513Schristos                 putc(last | 8, out);
399*16dce513Schristos                 last = 0;
400*16dce513Schristos             case 4:
401*16dce513Schristos                 putc(last | 0x20, out);
402*16dce513Schristos                 last = 0;
403*16dce513Schristos             case 2:
404*16dce513Schristos                 putc(last | 0x80, out);
405*16dce513Schristos                 putc(0, out);
406*16dce513Schristos             }
407*16dce513Schristos         }
408*16dce513Schristos     }
409*16dce513Schristos 
410*16dce513Schristos     /* update crc and tot */
411*16dce513Schristos     *crc = crc32_combine(*crc, bget4(in), len);
412*16dce513Schristos     *tot += (unsigned long)len;
413*16dce513Schristos 
414*16dce513Schristos     /* clean up */
415*16dce513Schristos     inflateEnd(&strm);
416*16dce513Schristos     free(junk);
417*16dce513Schristos     bclose(in);
418*16dce513Schristos 
419*16dce513Schristos     /* write trailer if this is the last gzip file */
420*16dce513Schristos     if (!clr) {
421*16dce513Schristos         put4(*crc, out);
422*16dce513Schristos         put4(*tot, out);
423*16dce513Schristos     }
424*16dce513Schristos }
425*16dce513Schristos 
426*16dce513Schristos /* join the gzip files on the command line, write result to stdout */
main(int argc,char ** argv)427*16dce513Schristos int main(int argc, char **argv)
428*16dce513Schristos {
429*16dce513Schristos     unsigned long crc, tot;     /* running crc and total uncompressed length */
430*16dce513Schristos 
431*16dce513Schristos     /* skip command name */
432*16dce513Schristos     argc--;
433*16dce513Schristos     argv++;
434*16dce513Schristos 
435*16dce513Schristos     /* show usage if no arguments */
436*16dce513Schristos     if (argc == 0) {
437*16dce513Schristos         fputs("gzjoin usage: gzjoin f1.gz [f2.gz [f3.gz ...]] > fjoin.gz\n",
438*16dce513Schristos               stderr);
439*16dce513Schristos         return 0;
440*16dce513Schristos     }
441*16dce513Schristos 
442*16dce513Schristos     /* join gzip files on command line and write to stdout */
443*16dce513Schristos     gzinit(&crc, &tot, stdout);
444*16dce513Schristos     while (argc--)
445*16dce513Schristos         gzcopy(*argv++, argc, &crc, &tot, stdout);
446*16dce513Schristos 
447*16dce513Schristos     /* done */
448*16dce513Schristos     return 0;
449*16dce513Schristos }
450