1212397c6Schristos /* gzappend -- command to append to a gzip file
2212397c6Schristos
3ba340e45Schristos Copyright (C) 2003, 2012 Mark Adler, all rights reserved
4ba340e45Schristos version 1.2, 11 Oct 2012
5212397c6Schristos
6212397c6Schristos This software is provided 'as-is', without any express or implied
7212397c6Schristos warranty. In no event will the author be held liable for any damages
8212397c6Schristos arising from the use of this software.
9212397c6Schristos
10212397c6Schristos Permission is granted to anyone to use this software for any purpose,
11212397c6Schristos including commercial applications, and to alter it and redistribute it
12212397c6Schristos freely, subject to the following restrictions:
13212397c6Schristos
14212397c6Schristos 1. The origin of this software must not be misrepresented; you must not
15212397c6Schristos claim that you wrote the original software. If you use this software
16212397c6Schristos in a product, an acknowledgment in the product documentation would be
17212397c6Schristos appreciated but is not required.
18212397c6Schristos 2. Altered source versions must be plainly marked as such, and must not be
19212397c6Schristos misrepresented as being the original software.
20212397c6Schristos 3. This notice may not be removed or altered from any source distribution.
21212397c6Schristos
22212397c6Schristos Mark Adler madler@alumni.caltech.edu
23212397c6Schristos */
24212397c6Schristos
25212397c6Schristos /*
26212397c6Schristos * Change history:
27212397c6Schristos *
28212397c6Schristos * 1.0 19 Oct 2003 - First version
29212397c6Schristos * 1.1 4 Nov 2003 - Expand and clarify some comments and notes
30212397c6Schristos * - Add version and copyright to help
31212397c6Schristos * - Send help to stdout instead of stderr
32212397c6Schristos * - Add some preemptive typecasts
33212397c6Schristos * - Add L to constants in lseek() calls
34212397c6Schristos * - Remove some debugging information in error messages
35212397c6Schristos * - Use new data_type definition for zlib 1.2.1
36212397c6Schristos * - Simplfy and unify file operations
37212397c6Schristos * - Finish off gzip file in gztack()
38212397c6Schristos * - Use deflatePrime() instead of adding empty blocks
39212397c6Schristos * - Keep gzip file clean on appended file read errors
40212397c6Schristos * - Use in-place rotate instead of auxiliary buffer
41212397c6Schristos * (Why you ask? Because it was fun to write!)
42ba340e45Schristos * 1.2 11 Oct 2012 - Fix for proper z_const usage
43ba340e45Schristos * - Check for input buffer malloc failure
44212397c6Schristos */
45212397c6Schristos
46212397c6Schristos /*
47212397c6Schristos gzappend takes a gzip file and appends to it, compressing files from the
48212397c6Schristos command line or data from stdin. The gzip file is written to directly, to
49212397c6Schristos avoid copying that file, in case it's large. Note that this results in the
50212397c6Schristos unfriendly behavior that if gzappend fails, the gzip file is corrupted.
51212397c6Schristos
52212397c6Schristos This program was written to illustrate the use of the new Z_BLOCK option of
53212397c6Schristos zlib 1.2.x's inflate() function. This option returns from inflate() at each
54212397c6Schristos block boundary to facilitate locating and modifying the last block bit at
55212397c6Schristos the start of the final deflate block. Also whether using Z_BLOCK or not,
56212397c6Schristos another required feature of zlib 1.2.x is that inflate() now provides the
57212397c6Schristos number of unusued bits in the last input byte used. gzappend will not work
58212397c6Schristos with versions of zlib earlier than 1.2.1.
59212397c6Schristos
60212397c6Schristos gzappend first decompresses the gzip file internally, discarding all but
61212397c6Schristos the last 32K of uncompressed data, and noting the location of the last block
62212397c6Schristos bit and the number of unused bits in the last byte of the compressed data.
63212397c6Schristos The gzip trailer containing the CRC-32 and length of the uncompressed data
64212397c6Schristos is verified. This trailer will be later overwritten.
65212397c6Schristos
66212397c6Schristos Then the last block bit is cleared by seeking back in the file and rewriting
67212397c6Schristos the byte that contains it. Seeking forward, the last byte of the compressed
68212397c6Schristos data is saved along with the number of unused bits to initialize deflate.
69212397c6Schristos
70212397c6Schristos A deflate process is initialized, using the last 32K of the uncompressed
71212397c6Schristos data from the gzip file to initialize the dictionary. If the total
72212397c6Schristos uncompressed data was less than 32K, then all of it is used to initialize
73212397c6Schristos the dictionary. The deflate output bit buffer is also initialized with the
74212397c6Schristos last bits from the original deflate stream. From here on, the data to
75212397c6Schristos append is simply compressed using deflate, and written to the gzip file.
76212397c6Schristos When that is complete, the new CRC-32 and uncompressed length are written
77212397c6Schristos as the trailer of the gzip file.
78212397c6Schristos */
79212397c6Schristos
80212397c6Schristos #include <stdio.h>
81212397c6Schristos #include <stdlib.h>
82212397c6Schristos #include <string.h>
83212397c6Schristos #include <fcntl.h>
84212397c6Schristos #include <unistd.h>
85212397c6Schristos #include "zlib.h"
86212397c6Schristos
87212397c6Schristos #define local static
88212397c6Schristos #define LGCHUNK 14
89212397c6Schristos #define CHUNK (1U << LGCHUNK)
90212397c6Schristos #define DSIZE 32768U
91212397c6Schristos
92212397c6Schristos /* print an error message and terminate with extreme prejudice */
bye(char * msg1,char * msg2)93212397c6Schristos local void bye(char *msg1, char *msg2)
94212397c6Schristos {
95212397c6Schristos fprintf(stderr, "gzappend error: %s%s\n", msg1, msg2);
96212397c6Schristos exit(1);
97212397c6Schristos }
98212397c6Schristos
99212397c6Schristos /* return the greatest common divisor of a and b using Euclid's algorithm,
100212397c6Schristos modified to be fast when one argument much greater than the other, and
101212397c6Schristos coded to avoid unnecessary swapping */
gcd(unsigned a,unsigned b)102212397c6Schristos local unsigned gcd(unsigned a, unsigned b)
103212397c6Schristos {
104212397c6Schristos unsigned c;
105212397c6Schristos
106212397c6Schristos while (a && b)
107212397c6Schristos if (a > b) {
108212397c6Schristos c = b;
109212397c6Schristos while (a - c >= c)
110212397c6Schristos c <<= 1;
111212397c6Schristos a -= c;
112212397c6Schristos }
113212397c6Schristos else {
114212397c6Schristos c = a;
115212397c6Schristos while (b - c >= c)
116212397c6Schristos c <<= 1;
117212397c6Schristos b -= c;
118212397c6Schristos }
119212397c6Schristos return a + b;
120212397c6Schristos }
121212397c6Schristos
122212397c6Schristos /* rotate list[0..len-1] left by rot positions, in place */
rotate(unsigned char * list,unsigned len,unsigned rot)123212397c6Schristos local void rotate(unsigned char *list, unsigned len, unsigned rot)
124212397c6Schristos {
125212397c6Schristos unsigned char tmp;
126212397c6Schristos unsigned cycles;
127212397c6Schristos unsigned char *start, *last, *to, *from;
128212397c6Schristos
129212397c6Schristos /* normalize rot and handle degenerate cases */
130212397c6Schristos if (len < 2) return;
131212397c6Schristos if (rot >= len) rot %= len;
132212397c6Schristos if (rot == 0) return;
133212397c6Schristos
134212397c6Schristos /* pointer to last entry in list */
135212397c6Schristos last = list + (len - 1);
136212397c6Schristos
137212397c6Schristos /* do simple left shift by one */
138212397c6Schristos if (rot == 1) {
139212397c6Schristos tmp = *list;
140*4b169a6bSchristos memmove(list, list + 1, len - 1);
141212397c6Schristos *last = tmp;
142212397c6Schristos return;
143212397c6Schristos }
144212397c6Schristos
145212397c6Schristos /* do simple right shift by one */
146212397c6Schristos if (rot == len - 1) {
147212397c6Schristos tmp = *last;
148212397c6Schristos memmove(list + 1, list, len - 1);
149212397c6Schristos *list = tmp;
150212397c6Schristos return;
151212397c6Schristos }
152212397c6Schristos
153212397c6Schristos /* otherwise do rotate as a set of cycles in place */
154212397c6Schristos cycles = gcd(len, rot); /* number of cycles */
155212397c6Schristos do {
156212397c6Schristos start = from = list + cycles; /* start index is arbitrary */
157212397c6Schristos tmp = *from; /* save entry to be overwritten */
158212397c6Schristos for (;;) {
159212397c6Schristos to = from; /* next step in cycle */
160212397c6Schristos from += rot; /* go right rot positions */
161212397c6Schristos if (from > last) from -= len; /* (pointer better not wrap) */
162212397c6Schristos if (from == start) break; /* all but one shifted */
163212397c6Schristos *to = *from; /* shift left */
164212397c6Schristos }
165212397c6Schristos *to = tmp; /* complete the circle */
166212397c6Schristos } while (--cycles);
167212397c6Schristos }
168212397c6Schristos
169212397c6Schristos /* structure for gzip file read operations */
170212397c6Schristos typedef struct {
171212397c6Schristos int fd; /* file descriptor */
172212397c6Schristos int size; /* 1 << size is bytes in buf */
173212397c6Schristos unsigned left; /* bytes available at next */
174212397c6Schristos unsigned char *buf; /* buffer */
175ba340e45Schristos z_const unsigned char *next; /* next byte in buffer */
176212397c6Schristos char *name; /* file name for error messages */
177212397c6Schristos } file;
178212397c6Schristos
179212397c6Schristos /* reload buffer */
readin(file * in)180212397c6Schristos local int readin(file *in)
181212397c6Schristos {
182212397c6Schristos int len;
183212397c6Schristos
184212397c6Schristos len = read(in->fd, in->buf, 1 << in->size);
185212397c6Schristos if (len == -1) bye("error reading ", in->name);
186212397c6Schristos in->left = (unsigned)len;
187212397c6Schristos in->next = in->buf;
188212397c6Schristos return len;
189212397c6Schristos }
190212397c6Schristos
191212397c6Schristos /* read from file in, exit if end-of-file */
readmore(file * in)192212397c6Schristos local int readmore(file *in)
193212397c6Schristos {
194212397c6Schristos if (readin(in) == 0) bye("unexpected end of ", in->name);
195212397c6Schristos return 0;
196212397c6Schristos }
197212397c6Schristos
198212397c6Schristos #define read1(in) (in->left == 0 ? readmore(in) : 0, \
199212397c6Schristos in->left--, *(in->next)++)
200212397c6Schristos
201212397c6Schristos /* skip over n bytes of in */
skip(file * in,unsigned n)202212397c6Schristos local void skip(file *in, unsigned n)
203212397c6Schristos {
204212397c6Schristos unsigned bypass;
205212397c6Schristos
206212397c6Schristos if (n > in->left) {
207212397c6Schristos n -= in->left;
208212397c6Schristos bypass = n & ~((1U << in->size) - 1);
209212397c6Schristos if (bypass) {
210212397c6Schristos if (lseek(in->fd, (off_t)bypass, SEEK_CUR) == -1)
211212397c6Schristos bye("seeking ", in->name);
212212397c6Schristos n -= bypass;
213212397c6Schristos }
214212397c6Schristos readmore(in);
215212397c6Schristos if (n > in->left)
216212397c6Schristos bye("unexpected end of ", in->name);
217212397c6Schristos }
218212397c6Schristos in->left -= n;
219212397c6Schristos in->next += n;
220212397c6Schristos }
221212397c6Schristos
222212397c6Schristos /* read a four-byte unsigned integer, little-endian, from in */
read4(file * in)223212397c6Schristos unsigned long read4(file *in)
224212397c6Schristos {
225212397c6Schristos unsigned long val;
226212397c6Schristos
227212397c6Schristos val = read1(in);
228212397c6Schristos val += (unsigned)read1(in) << 8;
229212397c6Schristos val += (unsigned long)read1(in) << 16;
230212397c6Schristos val += (unsigned long)read1(in) << 24;
231212397c6Schristos return val;
232212397c6Schristos }
233212397c6Schristos
234212397c6Schristos /* skip over gzip header */
gzheader(file * in)235212397c6Schristos local void gzheader(file *in)
236212397c6Schristos {
237212397c6Schristos int flags;
238212397c6Schristos unsigned n;
239212397c6Schristos
240212397c6Schristos if (read1(in) != 31 || read1(in) != 139) bye(in->name, " not a gzip file");
241212397c6Schristos if (read1(in) != 8) bye("unknown compression method in", in->name);
242212397c6Schristos flags = read1(in);
243212397c6Schristos if (flags & 0xe0) bye("unknown header flags set in", in->name);
244212397c6Schristos skip(in, 6);
245212397c6Schristos if (flags & 4) {
246212397c6Schristos n = read1(in);
247212397c6Schristos n += (unsigned)(read1(in)) << 8;
248212397c6Schristos skip(in, n);
249212397c6Schristos }
250212397c6Schristos if (flags & 8) while (read1(in) != 0) ;
251212397c6Schristos if (flags & 16) while (read1(in) != 0) ;
252212397c6Schristos if (flags & 2) skip(in, 2);
253212397c6Schristos }
254212397c6Schristos
255212397c6Schristos /* decompress gzip file "name", return strm with a deflate stream ready to
256212397c6Schristos continue compression of the data in the gzip file, and return a file
257212397c6Schristos descriptor pointing to where to write the compressed data -- the deflate
258212397c6Schristos stream is initialized to compress using level "level" */
gzscan(char * name,z_stream * strm,int level)259212397c6Schristos local int gzscan(char *name, z_stream *strm, int level)
260212397c6Schristos {
261212397c6Schristos int ret, lastbit, left, full;
262212397c6Schristos unsigned have;
263212397c6Schristos unsigned long crc, tot;
264212397c6Schristos unsigned char *window;
265212397c6Schristos off_t lastoff, end;
266212397c6Schristos file gz;
267212397c6Schristos
268212397c6Schristos /* open gzip file */
269212397c6Schristos gz.name = name;
270212397c6Schristos gz.fd = open(name, O_RDWR, 0);
271212397c6Schristos if (gz.fd == -1) bye("cannot open ", name);
272212397c6Schristos gz.buf = malloc(CHUNK);
273212397c6Schristos if (gz.buf == NULL) bye("out of memory", "");
274212397c6Schristos gz.size = LGCHUNK;
275212397c6Schristos gz.left = 0;
276212397c6Schristos
277212397c6Schristos /* skip gzip header */
278212397c6Schristos gzheader(&gz);
279212397c6Schristos
280212397c6Schristos /* prepare to decompress */
281212397c6Schristos window = malloc(DSIZE);
282212397c6Schristos if (window == NULL) bye("out of memory", "");
283212397c6Schristos strm->zalloc = Z_NULL;
284212397c6Schristos strm->zfree = Z_NULL;
285212397c6Schristos strm->opaque = Z_NULL;
286212397c6Schristos ret = inflateInit2(strm, -15);
287212397c6Schristos if (ret != Z_OK) bye("out of memory", " or library mismatch");
288212397c6Schristos
289212397c6Schristos /* decompress the deflate stream, saving append information */
290212397c6Schristos lastbit = 0;
291212397c6Schristos lastoff = lseek(gz.fd, 0L, SEEK_CUR) - gz.left;
292212397c6Schristos left = 0;
293212397c6Schristos strm->avail_in = gz.left;
294212397c6Schristos strm->next_in = gz.next;
295212397c6Schristos crc = crc32(0L, Z_NULL, 0);
296212397c6Schristos have = full = 0;
297212397c6Schristos do {
298212397c6Schristos /* if needed, get more input */
299212397c6Schristos if (strm->avail_in == 0) {
300212397c6Schristos readmore(&gz);
301212397c6Schristos strm->avail_in = gz.left;
302212397c6Schristos strm->next_in = gz.next;
303212397c6Schristos }
304212397c6Schristos
305212397c6Schristos /* set up output to next available section of sliding window */
306212397c6Schristos strm->avail_out = DSIZE - have;
307212397c6Schristos strm->next_out = window + have;
308212397c6Schristos
309212397c6Schristos /* inflate and check for errors */
310212397c6Schristos ret = inflate(strm, Z_BLOCK);
311212397c6Schristos if (ret == Z_STREAM_ERROR) bye("internal stream error!", "");
312212397c6Schristos if (ret == Z_MEM_ERROR) bye("out of memory", "");
313212397c6Schristos if (ret == Z_DATA_ERROR)
314212397c6Schristos bye("invalid compressed data--format violated in", name);
315212397c6Schristos
316212397c6Schristos /* update crc and sliding window pointer */
317212397c6Schristos crc = crc32(crc, window + have, DSIZE - have - strm->avail_out);
318212397c6Schristos if (strm->avail_out)
319212397c6Schristos have = DSIZE - strm->avail_out;
320212397c6Schristos else {
321212397c6Schristos have = 0;
322212397c6Schristos full = 1;
323212397c6Schristos }
324212397c6Schristos
325212397c6Schristos /* process end of block */
326212397c6Schristos if (strm->data_type & 128) {
327212397c6Schristos if (strm->data_type & 64)
328212397c6Schristos left = strm->data_type & 0x1f;
329212397c6Schristos else {
330212397c6Schristos lastbit = strm->data_type & 0x1f;
331212397c6Schristos lastoff = lseek(gz.fd, 0L, SEEK_CUR) - strm->avail_in;
332212397c6Schristos }
333212397c6Schristos }
334212397c6Schristos } while (ret != Z_STREAM_END);
335212397c6Schristos inflateEnd(strm);
336212397c6Schristos gz.left = strm->avail_in;
337212397c6Schristos gz.next = strm->next_in;
338212397c6Schristos
339212397c6Schristos /* save the location of the end of the compressed data */
340212397c6Schristos end = lseek(gz.fd, 0L, SEEK_CUR) - gz.left;
341212397c6Schristos
342212397c6Schristos /* check gzip trailer and save total for deflate */
343212397c6Schristos if (crc != read4(&gz))
344212397c6Schristos bye("invalid compressed data--crc mismatch in ", name);
345212397c6Schristos tot = strm->total_out;
346212397c6Schristos if ((tot & 0xffffffffUL) != read4(&gz))
347212397c6Schristos bye("invalid compressed data--length mismatch in", name);
348212397c6Schristos
349212397c6Schristos /* if not at end of file, warn */
350212397c6Schristos if (gz.left || readin(&gz))
351212397c6Schristos fprintf(stderr,
352212397c6Schristos "gzappend warning: junk at end of gzip file overwritten\n");
353212397c6Schristos
354212397c6Schristos /* clear last block bit */
355212397c6Schristos lseek(gz.fd, lastoff - (lastbit != 0), SEEK_SET);
356212397c6Schristos if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name);
357212397c6Schristos *gz.buf = (unsigned char)(*gz.buf ^ (1 << ((8 - lastbit) & 7)));
358212397c6Schristos lseek(gz.fd, -1L, SEEK_CUR);
359212397c6Schristos if (write(gz.fd, gz.buf, 1) != 1) bye("writing after seek to ", name);
360212397c6Schristos
361212397c6Schristos /* if window wrapped, build dictionary from window by rotating */
362212397c6Schristos if (full) {
363212397c6Schristos rotate(window, DSIZE, have);
364212397c6Schristos have = DSIZE;
365212397c6Schristos }
366212397c6Schristos
367212397c6Schristos /* set up deflate stream with window, crc, total_in, and leftover bits */
368212397c6Schristos ret = deflateInit2(strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
369212397c6Schristos if (ret != Z_OK) bye("out of memory", "");
370212397c6Schristos deflateSetDictionary(strm, window, have);
371212397c6Schristos strm->adler = crc;
372212397c6Schristos strm->total_in = tot;
373212397c6Schristos if (left) {
374212397c6Schristos lseek(gz.fd, --end, SEEK_SET);
375212397c6Schristos if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name);
376212397c6Schristos deflatePrime(strm, 8 - left, *gz.buf);
377212397c6Schristos }
378212397c6Schristos lseek(gz.fd, end, SEEK_SET);
379212397c6Schristos
380212397c6Schristos /* clean up and return */
381212397c6Schristos free(window);
382212397c6Schristos free(gz.buf);
383212397c6Schristos return gz.fd;
384212397c6Schristos }
385212397c6Schristos
386212397c6Schristos /* append file "name" to gzip file gd using deflate stream strm -- if last
387212397c6Schristos is true, then finish off the deflate stream at the end */
gztack(char * name,int gd,z_stream * strm,int last)388212397c6Schristos local void gztack(char *name, int gd, z_stream *strm, int last)
389212397c6Schristos {
390212397c6Schristos int fd, len, ret;
391212397c6Schristos unsigned left;
392212397c6Schristos unsigned char *in, *out;
393212397c6Schristos
394212397c6Schristos /* open file to compress and append */
395212397c6Schristos fd = 0;
396212397c6Schristos if (name != NULL) {
397212397c6Schristos fd = open(name, O_RDONLY, 0);
398212397c6Schristos if (fd == -1)
399212397c6Schristos fprintf(stderr, "gzappend warning: %s not found, skipping ...\n",
400212397c6Schristos name);
401212397c6Schristos }
402212397c6Schristos
403212397c6Schristos /* allocate buffers */
404ba340e45Schristos in = malloc(CHUNK);
405212397c6Schristos out = malloc(CHUNK);
406ba340e45Schristos if (in == NULL || out == NULL) bye("out of memory", "");
407212397c6Schristos
408212397c6Schristos /* compress input file and append to gzip file */
409212397c6Schristos do {
410212397c6Schristos /* get more input */
411ba340e45Schristos len = read(fd, in, CHUNK);
412212397c6Schristos if (len == -1) {
413212397c6Schristos fprintf(stderr,
414212397c6Schristos "gzappend warning: error reading %s, skipping rest ...\n",
415212397c6Schristos name);
416212397c6Schristos len = 0;
417212397c6Schristos }
418212397c6Schristos strm->avail_in = (unsigned)len;
419212397c6Schristos strm->next_in = in;
420212397c6Schristos if (len) strm->adler = crc32(strm->adler, in, (unsigned)len);
421212397c6Schristos
422212397c6Schristos /* compress and write all available output */
423212397c6Schristos do {
424212397c6Schristos strm->avail_out = CHUNK;
425212397c6Schristos strm->next_out = out;
426212397c6Schristos ret = deflate(strm, last && len == 0 ? Z_FINISH : Z_NO_FLUSH);
427212397c6Schristos left = CHUNK - strm->avail_out;
428212397c6Schristos while (left) {
429212397c6Schristos len = write(gd, out + CHUNK - strm->avail_out - left, left);
430212397c6Schristos if (len == -1) bye("writing gzip file", "");
431212397c6Schristos left -= (unsigned)len;
432212397c6Schristos }
433212397c6Schristos } while (strm->avail_out == 0 && ret != Z_STREAM_END);
434212397c6Schristos } while (len != 0);
435212397c6Schristos
436212397c6Schristos /* write trailer after last entry */
437212397c6Schristos if (last) {
438212397c6Schristos deflateEnd(strm);
439212397c6Schristos out[0] = (unsigned char)(strm->adler);
440212397c6Schristos out[1] = (unsigned char)(strm->adler >> 8);
441212397c6Schristos out[2] = (unsigned char)(strm->adler >> 16);
442212397c6Schristos out[3] = (unsigned char)(strm->adler >> 24);
443212397c6Schristos out[4] = (unsigned char)(strm->total_in);
444212397c6Schristos out[5] = (unsigned char)(strm->total_in >> 8);
445212397c6Schristos out[6] = (unsigned char)(strm->total_in >> 16);
446212397c6Schristos out[7] = (unsigned char)(strm->total_in >> 24);
447212397c6Schristos len = 8;
448212397c6Schristos do {
449212397c6Schristos ret = write(gd, out + 8 - len, len);
450212397c6Schristos if (ret == -1) bye("writing gzip file", "");
451212397c6Schristos len -= ret;
452212397c6Schristos } while (len);
453212397c6Schristos close(gd);
454212397c6Schristos }
455212397c6Schristos
456212397c6Schristos /* clean up and return */
457212397c6Schristos free(out);
458ba340e45Schristos free(in);
459212397c6Schristos if (fd > 0) close(fd);
460212397c6Schristos }
461212397c6Schristos
462212397c6Schristos /* process the compression level option if present, scan the gzip file, and
463212397c6Schristos append the specified files, or append the data from stdin if no other file
464212397c6Schristos names are provided on the command line -- the gzip file must be writable
465212397c6Schristos and seekable */
main(int argc,char ** argv)466212397c6Schristos int main(int argc, char **argv)
467212397c6Schristos {
468212397c6Schristos int gd, level;
469212397c6Schristos z_stream strm;
470212397c6Schristos
471212397c6Schristos /* ignore command name */
472ba340e45Schristos argc--; argv++;
473212397c6Schristos
474212397c6Schristos /* provide usage if no arguments */
475212397c6Schristos if (*argv == NULL) {
476ba340e45Schristos printf(
477ba340e45Schristos "gzappend 1.2 (11 Oct 2012) Copyright (C) 2003, 2012 Mark Adler\n"
478ba340e45Schristos );
479212397c6Schristos printf(
480212397c6Schristos "usage: gzappend [-level] file.gz [ addthis [ andthis ... ]]\n");
481212397c6Schristos return 0;
482212397c6Schristos }
483212397c6Schristos
484212397c6Schristos /* set compression level */
485212397c6Schristos level = Z_DEFAULT_COMPRESSION;
486212397c6Schristos if (argv[0][0] == '-') {
487212397c6Schristos if (argv[0][1] < '0' || argv[0][1] > '9' || argv[0][2] != 0)
488212397c6Schristos bye("invalid compression level", "");
489212397c6Schristos level = argv[0][1] - '0';
490212397c6Schristos if (*++argv == NULL) bye("no gzip file name after options", "");
491212397c6Schristos }
492212397c6Schristos
493212397c6Schristos /* prepare to append to gzip file */
494212397c6Schristos gd = gzscan(*argv++, &strm, level);
495212397c6Schristos
496212397c6Schristos /* append files on command line, or from stdin if none */
497212397c6Schristos if (*argv == NULL)
498212397c6Schristos gztack(NULL, gd, &strm, 1);
499212397c6Schristos else
500212397c6Schristos do {
501212397c6Schristos gztack(*argv, gd, &strm, argv[1] == NULL);
502212397c6Schristos } while (*++argv != NULL);
503212397c6Schristos return 0;
504212397c6Schristos }
505