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