annotate src/zlib-1.2.7/examples/gzappend.c @ 83:ae30d91d2ffe

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