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