gzappend.c revision 1.1.1.2 1 1.1 christos /* gzappend -- command to append to a gzip file
2 1.1 christos
3 1.1.1.2 christos Copyright (C) 2003, 2012 Mark Adler, all rights reserved
4 1.1.1.2 christos version 1.2, 11 Oct 2012
5 1.1 christos
6 1.1 christos This software is provided 'as-is', without any express or implied
7 1.1 christos warranty. In no event will the author be held liable for any damages
8 1.1 christos arising from the use of this software.
9 1.1 christos
10 1.1 christos Permission is granted to anyone to use this software for any purpose,
11 1.1 christos including commercial applications, and to alter it and redistribute it
12 1.1 christos freely, subject to the following restrictions:
13 1.1 christos
14 1.1 christos 1. The origin of this software must not be misrepresented; you must not
15 1.1 christos claim that you wrote the original software. If you use this software
16 1.1 christos in a product, an acknowledgment in the product documentation would be
17 1.1 christos appreciated but is not required.
18 1.1 christos 2. Altered source versions must be plainly marked as such, and must not be
19 1.1 christos misrepresented as being the original software.
20 1.1 christos 3. This notice may not be removed or altered from any source distribution.
21 1.1 christos
22 1.1 christos Mark Adler madler (at) alumni.caltech.edu
23 1.1 christos */
24 1.1 christos
25 1.1 christos /*
26 1.1 christos * Change history:
27 1.1 christos *
28 1.1 christos * 1.0 19 Oct 2003 - First version
29 1.1 christos * 1.1 4 Nov 2003 - Expand and clarify some comments and notes
30 1.1 christos * - Add version and copyright to help
31 1.1 christos * - Send help to stdout instead of stderr
32 1.1 christos * - Add some preemptive typecasts
33 1.1 christos * - Add L to constants in lseek() calls
34 1.1 christos * - Remove some debugging information in error messages
35 1.1 christos * - Use new data_type definition for zlib 1.2.1
36 1.1 christos * - Simplfy and unify file operations
37 1.1 christos * - Finish off gzip file in gztack()
38 1.1 christos * - Use deflatePrime() instead of adding empty blocks
39 1.1 christos * - Keep gzip file clean on appended file read errors
40 1.1 christos * - Use in-place rotate instead of auxiliary buffer
41 1.1 christos * (Why you ask? Because it was fun to write!)
42 1.1.1.2 christos * 1.2 11 Oct 2012 - Fix for proper z_const usage
43 1.1.1.2 christos * - Check for input buffer malloc failure
44 1.1 christos */
45 1.1 christos
46 1.1 christos /*
47 1.1 christos gzappend takes a gzip file and appends to it, compressing files from the
48 1.1 christos command line or data from stdin. The gzip file is written to directly, to
49 1.1 christos avoid copying that file, in case it's large. Note that this results in the
50 1.1 christos unfriendly behavior that if gzappend fails, the gzip file is corrupted.
51 1.1 christos
52 1.1 christos This program was written to illustrate the use of the new Z_BLOCK option of
53 1.1 christos zlib 1.2.x's inflate() function. This option returns from inflate() at each
54 1.1 christos block boundary to facilitate locating and modifying the last block bit at
55 1.1 christos the start of the final deflate block. Also whether using Z_BLOCK or not,
56 1.1 christos another required feature of zlib 1.2.x is that inflate() now provides the
57 1.1 christos number of unusued bits in the last input byte used. gzappend will not work
58 1.1 christos with versions of zlib earlier than 1.2.1.
59 1.1 christos
60 1.1 christos gzappend first decompresses the gzip file internally, discarding all but
61 1.1 christos the last 32K of uncompressed data, and noting the location of the last block
62 1.1 christos bit and the number of unused bits in the last byte of the compressed data.
63 1.1 christos The gzip trailer containing the CRC-32 and length of the uncompressed data
64 1.1 christos is verified. This trailer will be later overwritten.
65 1.1 christos
66 1.1 christos Then the last block bit is cleared by seeking back in the file and rewriting
67 1.1 christos the byte that contains it. Seeking forward, the last byte of the compressed
68 1.1 christos data is saved along with the number of unused bits to initialize deflate.
69 1.1 christos
70 1.1 christos A deflate process is initialized, using the last 32K of the uncompressed
71 1.1 christos data from the gzip file to initialize the dictionary. If the total
72 1.1 christos uncompressed data was less than 32K, then all of it is used to initialize
73 1.1 christos the dictionary. The deflate output bit buffer is also initialized with the
74 1.1 christos last bits from the original deflate stream. From here on, the data to
75 1.1 christos append is simply compressed using deflate, and written to the gzip file.
76 1.1 christos When that is complete, the new CRC-32 and uncompressed length are written
77 1.1 christos as the trailer of the gzip file.
78 1.1 christos */
79 1.1 christos
80 1.1 christos #include <stdio.h>
81 1.1 christos #include <stdlib.h>
82 1.1 christos #include <string.h>
83 1.1 christos #include <fcntl.h>
84 1.1 christos #include <unistd.h>
85 1.1 christos #include "zlib.h"
86 1.1 christos
87 1.1 christos #define local static
88 1.1 christos #define LGCHUNK 14
89 1.1 christos #define CHUNK (1U << LGCHUNK)
90 1.1 christos #define DSIZE 32768U
91 1.1 christos
92 1.1 christos /* print an error message and terminate with extreme prejudice */
93 1.1 christos local void bye(char *msg1, char *msg2)
94 1.1 christos {
95 1.1 christos fprintf(stderr, "gzappend error: %s%s\n", msg1, msg2);
96 1.1 christos exit(1);
97 1.1 christos }
98 1.1 christos
99 1.1 christos /* return the greatest common divisor of a and b using Euclid's algorithm,
100 1.1 christos modified to be fast when one argument much greater than the other, and
101 1.1 christos coded to avoid unnecessary swapping */
102 1.1 christos local unsigned gcd(unsigned a, unsigned b)
103 1.1 christos {
104 1.1 christos unsigned c;
105 1.1 christos
106 1.1 christos while (a && b)
107 1.1 christos if (a > b) {
108 1.1 christos c = b;
109 1.1 christos while (a - c >= c)
110 1.1 christos c <<= 1;
111 1.1 christos a -= c;
112 1.1 christos }
113 1.1 christos else {
114 1.1 christos c = a;
115 1.1 christos while (b - c >= c)
116 1.1 christos c <<= 1;
117 1.1 christos b -= c;
118 1.1 christos }
119 1.1 christos return a + b;
120 1.1 christos }
121 1.1 christos
122 1.1 christos /* rotate list[0..len-1] left by rot positions, in place */
123 1.1 christos local void rotate(unsigned char *list, unsigned len, unsigned rot)
124 1.1 christos {
125 1.1 christos unsigned char tmp;
126 1.1 christos unsigned cycles;
127 1.1 christos unsigned char *start, *last, *to, *from;
128 1.1 christos
129 1.1 christos /* normalize rot and handle degenerate cases */
130 1.1 christos if (len < 2) return;
131 1.1 christos if (rot >= len) rot %= len;
132 1.1 christos if (rot == 0) return;
133 1.1 christos
134 1.1 christos /* pointer to last entry in list */
135 1.1 christos last = list + (len - 1);
136 1.1 christos
137 1.1 christos /* do simple left shift by one */
138 1.1 christos if (rot == 1) {
139 1.1 christos tmp = *list;
140 1.1 christos memcpy(list, list + 1, len - 1);
141 1.1 christos *last = tmp;
142 1.1 christos return;
143 1.1 christos }
144 1.1 christos
145 1.1 christos /* do simple right shift by one */
146 1.1 christos if (rot == len - 1) {
147 1.1 christos tmp = *last;
148 1.1 christos memmove(list + 1, list, len - 1);
149 1.1 christos *list = tmp;
150 1.1 christos return;
151 1.1 christos }
152 1.1 christos
153 1.1 christos /* otherwise do rotate as a set of cycles in place */
154 1.1 christos cycles = gcd(len, rot); /* number of cycles */
155 1.1 christos do {
156 1.1 christos start = from = list + cycles; /* start index is arbitrary */
157 1.1 christos tmp = *from; /* save entry to be overwritten */
158 1.1 christos for (;;) {
159 1.1 christos to = from; /* next step in cycle */
160 1.1 christos from += rot; /* go right rot positions */
161 1.1 christos if (from > last) from -= len; /* (pointer better not wrap) */
162 1.1 christos if (from == start) break; /* all but one shifted */
163 1.1 christos *to = *from; /* shift left */
164 1.1 christos }
165 1.1 christos *to = tmp; /* complete the circle */
166 1.1 christos } while (--cycles);
167 1.1 christos }
168 1.1 christos
169 1.1 christos /* structure for gzip file read operations */
170 1.1 christos typedef struct {
171 1.1 christos int fd; /* file descriptor */
172 1.1 christos int size; /* 1 << size is bytes in buf */
173 1.1 christos unsigned left; /* bytes available at next */
174 1.1 christos unsigned char *buf; /* buffer */
175 1.1.1.2 christos z_const unsigned char *next; /* next byte in buffer */
176 1.1 christos char *name; /* file name for error messages */
177 1.1 christos } file;
178 1.1 christos
179 1.1 christos /* reload buffer */
180 1.1 christos local int readin(file *in)
181 1.1 christos {
182 1.1 christos int len;
183 1.1 christos
184 1.1 christos len = read(in->fd, in->buf, 1 << in->size);
185 1.1 christos if (len == -1) bye("error reading ", in->name);
186 1.1 christos in->left = (unsigned)len;
187 1.1 christos in->next = in->buf;
188 1.1 christos return len;
189 1.1 christos }
190 1.1 christos
191 1.1 christos /* read from file in, exit if end-of-file */
192 1.1 christos local int readmore(file *in)
193 1.1 christos {
194 1.1 christos if (readin(in) == 0) bye("unexpected end of ", in->name);
195 1.1 christos return 0;
196 1.1 christos }
197 1.1 christos
198 1.1 christos #define read1(in) (in->left == 0 ? readmore(in) : 0, \
199 1.1 christos in->left--, *(in->next)++)
200 1.1 christos
201 1.1 christos /* skip over n bytes of in */
202 1.1 christos local void skip(file *in, unsigned n)
203 1.1 christos {
204 1.1 christos unsigned bypass;
205 1.1 christos
206 1.1 christos if (n > in->left) {
207 1.1 christos n -= in->left;
208 1.1 christos bypass = n & ~((1U << in->size) - 1);
209 1.1 christos if (bypass) {
210 1.1 christos if (lseek(in->fd, (off_t)bypass, SEEK_CUR) == -1)
211 1.1 christos bye("seeking ", in->name);
212 1.1 christos n -= bypass;
213 1.1 christos }
214 1.1 christos readmore(in);
215 1.1 christos if (n > in->left)
216 1.1 christos bye("unexpected end of ", in->name);
217 1.1 christos }
218 1.1 christos in->left -= n;
219 1.1 christos in->next += n;
220 1.1 christos }
221 1.1 christos
222 1.1 christos /* read a four-byte unsigned integer, little-endian, from in */
223 1.1 christos unsigned long read4(file *in)
224 1.1 christos {
225 1.1 christos unsigned long val;
226 1.1 christos
227 1.1 christos val = read1(in);
228 1.1 christos val += (unsigned)read1(in) << 8;
229 1.1 christos val += (unsigned long)read1(in) << 16;
230 1.1 christos val += (unsigned long)read1(in) << 24;
231 1.1 christos return val;
232 1.1 christos }
233 1.1 christos
234 1.1 christos /* skip over gzip header */
235 1.1 christos local void gzheader(file *in)
236 1.1 christos {
237 1.1 christos int flags;
238 1.1 christos unsigned n;
239 1.1 christos
240 1.1 christos if (read1(in) != 31 || read1(in) != 139) bye(in->name, " not a gzip file");
241 1.1 christos if (read1(in) != 8) bye("unknown compression method in", in->name);
242 1.1 christos flags = read1(in);
243 1.1 christos if (flags & 0xe0) bye("unknown header flags set in", in->name);
244 1.1 christos skip(in, 6);
245 1.1 christos if (flags & 4) {
246 1.1 christos n = read1(in);
247 1.1 christos n += (unsigned)(read1(in)) << 8;
248 1.1 christos skip(in, n);
249 1.1 christos }
250 1.1 christos if (flags & 8) while (read1(in) != 0) ;
251 1.1 christos if (flags & 16) while (read1(in) != 0) ;
252 1.1 christos if (flags & 2) skip(in, 2);
253 1.1 christos }
254 1.1 christos
255 1.1 christos /* decompress gzip file "name", return strm with a deflate stream ready to
256 1.1 christos continue compression of the data in the gzip file, and return a file
257 1.1 christos descriptor pointing to where to write the compressed data -- the deflate
258 1.1 christos stream is initialized to compress using level "level" */
259 1.1 christos local int gzscan(char *name, z_stream *strm, int level)
260 1.1 christos {
261 1.1 christos int ret, lastbit, left, full;
262 1.1 christos unsigned have;
263 1.1 christos unsigned long crc, tot;
264 1.1 christos unsigned char *window;
265 1.1 christos off_t lastoff, end;
266 1.1 christos file gz;
267 1.1 christos
268 1.1 christos /* open gzip file */
269 1.1 christos gz.name = name;
270 1.1 christos gz.fd = open(name, O_RDWR, 0);
271 1.1 christos if (gz.fd == -1) bye("cannot open ", name);
272 1.1 christos gz.buf = malloc(CHUNK);
273 1.1 christos if (gz.buf == NULL) bye("out of memory", "");
274 1.1 christos gz.size = LGCHUNK;
275 1.1 christos gz.left = 0;
276 1.1 christos
277 1.1 christos /* skip gzip header */
278 1.1 christos gzheader(&gz);
279 1.1 christos
280 1.1 christos /* prepare to decompress */
281 1.1 christos window = malloc(DSIZE);
282 1.1 christos if (window == NULL) bye("out of memory", "");
283 1.1 christos strm->zalloc = Z_NULL;
284 1.1 christos strm->zfree = Z_NULL;
285 1.1 christos strm->opaque = Z_NULL;
286 1.1 christos ret = inflateInit2(strm, -15);
287 1.1 christos if (ret != Z_OK) bye("out of memory", " or library mismatch");
288 1.1 christos
289 1.1 christos /* decompress the deflate stream, saving append information */
290 1.1 christos lastbit = 0;
291 1.1 christos lastoff = lseek(gz.fd, 0L, SEEK_CUR) - gz.left;
292 1.1 christos left = 0;
293 1.1 christos strm->avail_in = gz.left;
294 1.1 christos strm->next_in = gz.next;
295 1.1 christos crc = crc32(0L, Z_NULL, 0);
296 1.1 christos have = full = 0;
297 1.1 christos do {
298 1.1 christos /* if needed, get more input */
299 1.1 christos if (strm->avail_in == 0) {
300 1.1 christos readmore(&gz);
301 1.1 christos strm->avail_in = gz.left;
302 1.1 christos strm->next_in = gz.next;
303 1.1 christos }
304 1.1 christos
305 1.1 christos /* set up output to next available section of sliding window */
306 1.1 christos strm->avail_out = DSIZE - have;
307 1.1 christos strm->next_out = window + have;
308 1.1 christos
309 1.1 christos /* inflate and check for errors */
310 1.1 christos ret = inflate(strm, Z_BLOCK);
311 1.1 christos if (ret == Z_STREAM_ERROR) bye("internal stream error!", "");
312 1.1 christos if (ret == Z_MEM_ERROR) bye("out of memory", "");
313 1.1 christos if (ret == Z_DATA_ERROR)
314 1.1 christos bye("invalid compressed data--format violated in", name);
315 1.1 christos
316 1.1 christos /* update crc and sliding window pointer */
317 1.1 christos crc = crc32(crc, window + have, DSIZE - have - strm->avail_out);
318 1.1 christos if (strm->avail_out)
319 1.1 christos have = DSIZE - strm->avail_out;
320 1.1 christos else {
321 1.1 christos have = 0;
322 1.1 christos full = 1;
323 1.1 christos }
324 1.1 christos
325 1.1 christos /* process end of block */
326 1.1 christos if (strm->data_type & 128) {
327 1.1 christos if (strm->data_type & 64)
328 1.1 christos left = strm->data_type & 0x1f;
329 1.1 christos else {
330 1.1 christos lastbit = strm->data_type & 0x1f;
331 1.1 christos lastoff = lseek(gz.fd, 0L, SEEK_CUR) - strm->avail_in;
332 1.1 christos }
333 1.1 christos }
334 1.1 christos } while (ret != Z_STREAM_END);
335 1.1 christos inflateEnd(strm);
336 1.1 christos gz.left = strm->avail_in;
337 1.1 christos gz.next = strm->next_in;
338 1.1 christos
339 1.1 christos /* save the location of the end of the compressed data */
340 1.1 christos end = lseek(gz.fd, 0L, SEEK_CUR) - gz.left;
341 1.1 christos
342 1.1 christos /* check gzip trailer and save total for deflate */
343 1.1 christos if (crc != read4(&gz))
344 1.1 christos bye("invalid compressed data--crc mismatch in ", name);
345 1.1 christos tot = strm->total_out;
346 1.1 christos if ((tot & 0xffffffffUL) != read4(&gz))
347 1.1 christos bye("invalid compressed data--length mismatch in", name);
348 1.1 christos
349 1.1 christos /* if not at end of file, warn */
350 1.1 christos if (gz.left || readin(&gz))
351 1.1 christos fprintf(stderr,
352 1.1 christos "gzappend warning: junk at end of gzip file overwritten\n");
353 1.1 christos
354 1.1 christos /* clear last block bit */
355 1.1 christos lseek(gz.fd, lastoff - (lastbit != 0), SEEK_SET);
356 1.1 christos if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name);
357 1.1 christos *gz.buf = (unsigned char)(*gz.buf ^ (1 << ((8 - lastbit) & 7)));
358 1.1 christos lseek(gz.fd, -1L, SEEK_CUR);
359 1.1 christos if (write(gz.fd, gz.buf, 1) != 1) bye("writing after seek to ", name);
360 1.1 christos
361 1.1 christos /* if window wrapped, build dictionary from window by rotating */
362 1.1 christos if (full) {
363 1.1 christos rotate(window, DSIZE, have);
364 1.1 christos have = DSIZE;
365 1.1 christos }
366 1.1 christos
367 1.1 christos /* set up deflate stream with window, crc, total_in, and leftover bits */
368 1.1 christos ret = deflateInit2(strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
369 1.1 christos if (ret != Z_OK) bye("out of memory", "");
370 1.1 christos deflateSetDictionary(strm, window, have);
371 1.1 christos strm->adler = crc;
372 1.1 christos strm->total_in = tot;
373 1.1 christos if (left) {
374 1.1 christos lseek(gz.fd, --end, SEEK_SET);
375 1.1 christos if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name);
376 1.1 christos deflatePrime(strm, 8 - left, *gz.buf);
377 1.1 christos }
378 1.1 christos lseek(gz.fd, end, SEEK_SET);
379 1.1 christos
380 1.1 christos /* clean up and return */
381 1.1 christos free(window);
382 1.1 christos free(gz.buf);
383 1.1 christos return gz.fd;
384 1.1 christos }
385 1.1 christos
386 1.1 christos /* append file "name" to gzip file gd using deflate stream strm -- if last
387 1.1 christos is true, then finish off the deflate stream at the end */
388 1.1 christos local void gztack(char *name, int gd, z_stream *strm, int last)
389 1.1 christos {
390 1.1 christos int fd, len, ret;
391 1.1 christos unsigned left;
392 1.1 christos unsigned char *in, *out;
393 1.1 christos
394 1.1 christos /* open file to compress and append */
395 1.1 christos fd = 0;
396 1.1 christos if (name != NULL) {
397 1.1 christos fd = open(name, O_RDONLY, 0);
398 1.1 christos if (fd == -1)
399 1.1 christos fprintf(stderr, "gzappend warning: %s not found, skipping ...\n",
400 1.1 christos name);
401 1.1 christos }
402 1.1 christos
403 1.1 christos /* allocate buffers */
404 1.1.1.2 christos in = malloc(CHUNK);
405 1.1 christos out = malloc(CHUNK);
406 1.1.1.2 christos if (in == NULL || out == NULL) bye("out of memory", "");
407 1.1 christos
408 1.1 christos /* compress input file and append to gzip file */
409 1.1 christos do {
410 1.1 christos /* get more input */
411 1.1.1.2 christos len = read(fd, in, CHUNK);
412 1.1 christos if (len == -1) {
413 1.1 christos fprintf(stderr,
414 1.1 christos "gzappend warning: error reading %s, skipping rest ...\n",
415 1.1 christos name);
416 1.1 christos len = 0;
417 1.1 christos }
418 1.1 christos strm->avail_in = (unsigned)len;
419 1.1 christos strm->next_in = in;
420 1.1 christos if (len) strm->adler = crc32(strm->adler, in, (unsigned)len);
421 1.1 christos
422 1.1 christos /* compress and write all available output */
423 1.1 christos do {
424 1.1 christos strm->avail_out = CHUNK;
425 1.1 christos strm->next_out = out;
426 1.1 christos ret = deflate(strm, last && len == 0 ? Z_FINISH : Z_NO_FLUSH);
427 1.1 christos left = CHUNK - strm->avail_out;
428 1.1 christos while (left) {
429 1.1 christos len = write(gd, out + CHUNK - strm->avail_out - left, left);
430 1.1 christos if (len == -1) bye("writing gzip file", "");
431 1.1 christos left -= (unsigned)len;
432 1.1 christos }
433 1.1 christos } while (strm->avail_out == 0 && ret != Z_STREAM_END);
434 1.1 christos } while (len != 0);
435 1.1 christos
436 1.1 christos /* write trailer after last entry */
437 1.1 christos if (last) {
438 1.1 christos deflateEnd(strm);
439 1.1 christos out[0] = (unsigned char)(strm->adler);
440 1.1 christos out[1] = (unsigned char)(strm->adler >> 8);
441 1.1 christos out[2] = (unsigned char)(strm->adler >> 16);
442 1.1 christos out[3] = (unsigned char)(strm->adler >> 24);
443 1.1 christos out[4] = (unsigned char)(strm->total_in);
444 1.1 christos out[5] = (unsigned char)(strm->total_in >> 8);
445 1.1 christos out[6] = (unsigned char)(strm->total_in >> 16);
446 1.1 christos out[7] = (unsigned char)(strm->total_in >> 24);
447 1.1 christos len = 8;
448 1.1 christos do {
449 1.1 christos ret = write(gd, out + 8 - len, len);
450 1.1 christos if (ret == -1) bye("writing gzip file", "");
451 1.1 christos len -= ret;
452 1.1 christos } while (len);
453 1.1 christos close(gd);
454 1.1 christos }
455 1.1 christos
456 1.1 christos /* clean up and return */
457 1.1 christos free(out);
458 1.1.1.2 christos free(in);
459 1.1 christos if (fd > 0) close(fd);
460 1.1 christos }
461 1.1 christos
462 1.1 christos /* process the compression level option if present, scan the gzip file, and
463 1.1 christos append the specified files, or append the data from stdin if no other file
464 1.1 christos names are provided on the command line -- the gzip file must be writable
465 1.1 christos and seekable */
466 1.1 christos int main(int argc, char **argv)
467 1.1 christos {
468 1.1 christos int gd, level;
469 1.1 christos z_stream strm;
470 1.1 christos
471 1.1 christos /* ignore command name */
472 1.1.1.2 christos argc--; argv++;
473 1.1 christos
474 1.1 christos /* provide usage if no arguments */
475 1.1 christos if (*argv == NULL) {
476 1.1.1.2 christos printf(
477 1.1.1.2 christos "gzappend 1.2 (11 Oct 2012) Copyright (C) 2003, 2012 Mark Adler\n"
478 1.1.1.2 christos );
479 1.1 christos printf(
480 1.1 christos "usage: gzappend [-level] file.gz [ addthis [ andthis ... ]]\n");
481 1.1 christos return 0;
482 1.1 christos }
483 1.1 christos
484 1.1 christos /* set compression level */
485 1.1 christos level = Z_DEFAULT_COMPRESSION;
486 1.1 christos if (argv[0][0] == '-') {
487 1.1 christos if (argv[0][1] < '0' || argv[0][1] > '9' || argv[0][2] != 0)
488 1.1 christos bye("invalid compression level", "");
489 1.1 christos level = argv[0][1] - '0';
490 1.1 christos if (*++argv == NULL) bye("no gzip file name after options", "");
491 1.1 christos }
492 1.1 christos
493 1.1 christos /* prepare to append to gzip file */
494 1.1 christos gd = gzscan(*argv++, &strm, level);
495 1.1 christos
496 1.1 christos /* append files on command line, or from stdin if none */
497 1.1 christos if (*argv == NULL)
498 1.1 christos gztack(NULL, gd, &strm, 1);
499 1.1 christos else
500 1.1 christos do {
501 1.1 christos gztack(*argv, gd, &strm, argv[1] == NULL);
502 1.1 christos } while (*++argv != NULL);
503 1.1 christos return 0;
504 1.1 christos }
505