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.1.3 christos * - Simplify 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.1.3 christos number of unused 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.1.3 christos memmove(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