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