zran.c revision 1.1.1.2 1 1.1 christos /* zran.c -- example of zlib/gzip stream indexing and random access
2 1.1.1.2 christos * Copyright (C) 2005, 2012 Mark Adler
3 1.1 christos * For conditions of distribution and use, see copyright notice in zlib.h
4 1.1.1.2 christos Version 1.1 29 Sep 2012 Mark Adler */
5 1.1.1.2 christos
6 1.1.1.2 christos /* Version History:
7 1.1.1.2 christos 1.0 29 May 2005 First version
8 1.1.1.2 christos 1.1 29 Sep 2012 Fix memory reallocation error
9 1.1.1.2 christos */
10 1.1 christos
11 1.1 christos /* Illustrate the use of Z_BLOCK, inflatePrime(), and inflateSetDictionary()
12 1.1 christos for random access of a compressed file. A file containing a zlib or gzip
13 1.1 christos stream is provided on the command line. The compressed stream is decoded in
14 1.1 christos its entirety, and an index built with access points about every SPAN bytes
15 1.1 christos in the uncompressed output. The compressed file is left open, and can then
16 1.1 christos be read randomly, having to decompress on the average SPAN/2 uncompressed
17 1.1 christos bytes before getting to the desired block of data.
18 1.1 christos
19 1.1 christos An access point can be created at the start of any deflate block, by saving
20 1.1 christos the starting file offset and bit of that block, and the 32K bytes of
21 1.1 christos uncompressed data that precede that block. Also the uncompressed offset of
22 1.1 christos that block is saved to provide a referece for locating a desired starting
23 1.1 christos point in the uncompressed stream. build_index() works by decompressing the
24 1.1 christos input zlib or gzip stream a block at a time, and at the end of each block
25 1.1 christos deciding if enough uncompressed data has gone by to justify the creation of
26 1.1 christos a new access point. If so, that point is saved in a data structure that
27 1.1 christos grows as needed to accommodate the points.
28 1.1 christos
29 1.1 christos To use the index, an offset in the uncompressed data is provided, for which
30 1.1.1.2 christos the latest access point at or preceding that offset is located in the index.
31 1.1 christos The input file is positioned to the specified location in the index, and if
32 1.1 christos necessary the first few bits of the compressed data is read from the file.
33 1.1 christos inflate is initialized with those bits and the 32K of uncompressed data, and
34 1.1 christos the decompression then proceeds until the desired offset in the file is
35 1.1 christos reached. Then the decompression continues to read the desired uncompressed
36 1.1 christos data from the file.
37 1.1 christos
38 1.1 christos Another approach would be to generate the index on demand. In that case,
39 1.1 christos requests for random access reads from the compressed data would try to use
40 1.1 christos the index, but if a read far enough past the end of the index is required,
41 1.1 christos then further index entries would be generated and added.
42 1.1 christos
43 1.1 christos There is some fair bit of overhead to starting inflation for the random
44 1.1 christos access, mainly copying the 32K byte dictionary. So if small pieces of the
45 1.1 christos file are being accessed, it would make sense to implement a cache to hold
46 1.1 christos some lookahead and avoid many calls to extract() for small lengths.
47 1.1 christos
48 1.1 christos Another way to build an index would be to use inflateCopy(). That would
49 1.1 christos not be constrained to have access points at block boundaries, but requires
50 1.1 christos more memory per access point, and also cannot be saved to file due to the
51 1.1 christos use of pointers in the state. The approach here allows for storage of the
52 1.1 christos index in a file.
53 1.1 christos */
54 1.1 christos
55 1.1 christos #include <stdio.h>
56 1.1 christos #include <stdlib.h>
57 1.1 christos #include <string.h>
58 1.1 christos #include "zlib.h"
59 1.1 christos
60 1.1 christos #define local static
61 1.1 christos
62 1.1 christos #define SPAN 1048576L /* desired distance between access points */
63 1.1 christos #define WINSIZE 32768U /* sliding window size */
64 1.1 christos #define CHUNK 16384 /* file input buffer size */
65 1.1 christos
66 1.1 christos /* access point entry */
67 1.1 christos struct point {
68 1.1 christos off_t out; /* corresponding offset in uncompressed data */
69 1.1 christos off_t in; /* offset in input file of first full byte */
70 1.1 christos int bits; /* number of bits (1-7) from byte at in - 1, or 0 */
71 1.1 christos unsigned char window[WINSIZE]; /* preceding 32K of uncompressed data */
72 1.1 christos };
73 1.1 christos
74 1.1 christos /* access point list */
75 1.1 christos struct access {
76 1.1 christos int have; /* number of list entries filled in */
77 1.1 christos int size; /* number of list entries allocated */
78 1.1 christos struct point *list; /* allocated list */
79 1.1 christos };
80 1.1 christos
81 1.1 christos /* Deallocate an index built by build_index() */
82 1.1 christos local void free_index(struct access *index)
83 1.1 christos {
84 1.1 christos if (index != NULL) {
85 1.1 christos free(index->list);
86 1.1 christos free(index);
87 1.1 christos }
88 1.1 christos }
89 1.1 christos
90 1.1 christos /* Add an entry to the access point list. If out of memory, deallocate the
91 1.1 christos existing list and return NULL. */
92 1.1 christos local struct access *addpoint(struct access *index, int bits,
93 1.1 christos off_t in, off_t out, unsigned left, unsigned char *window)
94 1.1 christos {
95 1.1 christos struct point *next;
96 1.1 christos
97 1.1 christos /* if list is empty, create it (start with eight points) */
98 1.1 christos if (index == NULL) {
99 1.1 christos index = malloc(sizeof(struct access));
100 1.1 christos if (index == NULL) return NULL;
101 1.1 christos index->list = malloc(sizeof(struct point) << 3);
102 1.1 christos if (index->list == NULL) {
103 1.1 christos free(index);
104 1.1 christos return NULL;
105 1.1 christos }
106 1.1 christos index->size = 8;
107 1.1 christos index->have = 0;
108 1.1 christos }
109 1.1 christos
110 1.1 christos /* if list is full, make it bigger */
111 1.1 christos else if (index->have == index->size) {
112 1.1 christos index->size <<= 1;
113 1.1 christos next = realloc(index->list, sizeof(struct point) * index->size);
114 1.1 christos if (next == NULL) {
115 1.1 christos free_index(index);
116 1.1 christos return NULL;
117 1.1 christos }
118 1.1 christos index->list = next;
119 1.1 christos }
120 1.1 christos
121 1.1 christos /* fill in entry and increment how many we have */
122 1.1 christos next = index->list + index->have;
123 1.1 christos next->bits = bits;
124 1.1 christos next->in = in;
125 1.1 christos next->out = out;
126 1.1 christos if (left)
127 1.1 christos memcpy(next->window, window + WINSIZE - left, left);
128 1.1 christos if (left < WINSIZE)
129 1.1 christos memcpy(next->window + left, window, WINSIZE - left);
130 1.1 christos index->have++;
131 1.1 christos
132 1.1 christos /* return list, possibly reallocated */
133 1.1 christos return index;
134 1.1 christos }
135 1.1 christos
136 1.1 christos /* Make one entire pass through the compressed stream and build an index, with
137 1.1 christos access points about every span bytes of uncompressed output -- span is
138 1.1 christos chosen to balance the speed of random access against the memory requirements
139 1.1 christos of the list, about 32K bytes per access point. Note that data after the end
140 1.1 christos of the first zlib or gzip stream in the file is ignored. build_index()
141 1.1 christos returns the number of access points on success (>= 1), Z_MEM_ERROR for out
142 1.1 christos of memory, Z_DATA_ERROR for an error in the input file, or Z_ERRNO for a
143 1.1 christos file read error. On success, *built points to the resulting index. */
144 1.1 christos local int build_index(FILE *in, off_t span, struct access **built)
145 1.1 christos {
146 1.1 christos int ret;
147 1.1 christos off_t totin, totout; /* our own total counters to avoid 4GB limit */
148 1.1 christos off_t last; /* totout value of last access point */
149 1.1 christos struct access *index; /* access points being generated */
150 1.1 christos z_stream strm;
151 1.1 christos unsigned char input[CHUNK];
152 1.1 christos unsigned char window[WINSIZE];
153 1.1 christos
154 1.1 christos /* initialize inflate */
155 1.1 christos strm.zalloc = Z_NULL;
156 1.1 christos strm.zfree = Z_NULL;
157 1.1 christos strm.opaque = Z_NULL;
158 1.1 christos strm.avail_in = 0;
159 1.1 christos strm.next_in = Z_NULL;
160 1.1 christos ret = inflateInit2(&strm, 47); /* automatic zlib or gzip decoding */
161 1.1 christos if (ret != Z_OK)
162 1.1 christos return ret;
163 1.1 christos
164 1.1 christos /* inflate the input, maintain a sliding window, and build an index -- this
165 1.1 christos also validates the integrity of the compressed data using the check
166 1.1 christos information at the end of the gzip or zlib stream */
167 1.1 christos totin = totout = last = 0;
168 1.1 christos index = NULL; /* will be allocated by first addpoint() */
169 1.1 christos strm.avail_out = 0;
170 1.1 christos do {
171 1.1 christos /* get some compressed data from input file */
172 1.1 christos strm.avail_in = fread(input, 1, CHUNK, in);
173 1.1 christos if (ferror(in)) {
174 1.1 christos ret = Z_ERRNO;
175 1.1 christos goto build_index_error;
176 1.1 christos }
177 1.1 christos if (strm.avail_in == 0) {
178 1.1 christos ret = Z_DATA_ERROR;
179 1.1 christos goto build_index_error;
180 1.1 christos }
181 1.1 christos strm.next_in = input;
182 1.1 christos
183 1.1 christos /* process all of that, or until end of stream */
184 1.1 christos do {
185 1.1 christos /* reset sliding window if necessary */
186 1.1 christos if (strm.avail_out == 0) {
187 1.1 christos strm.avail_out = WINSIZE;
188 1.1 christos strm.next_out = window;
189 1.1 christos }
190 1.1 christos
191 1.1 christos /* inflate until out of input, output, or at end of block --
192 1.1 christos update the total input and output counters */
193 1.1 christos totin += strm.avail_in;
194 1.1 christos totout += strm.avail_out;
195 1.1 christos ret = inflate(&strm, Z_BLOCK); /* return at end of block */
196 1.1 christos totin -= strm.avail_in;
197 1.1 christos totout -= strm.avail_out;
198 1.1 christos if (ret == Z_NEED_DICT)
199 1.1 christos ret = Z_DATA_ERROR;
200 1.1 christos if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR)
201 1.1 christos goto build_index_error;
202 1.1 christos if (ret == Z_STREAM_END)
203 1.1 christos break;
204 1.1 christos
205 1.1 christos /* if at end of block, consider adding an index entry (note that if
206 1.1 christos data_type indicates an end-of-block, then all of the
207 1.1 christos uncompressed data from that block has been delivered, and none
208 1.1 christos of the compressed data after that block has been consumed,
209 1.1 christos except for up to seven bits) -- the totout == 0 provides an
210 1.1 christos entry point after the zlib or gzip header, and assures that the
211 1.1 christos index always has at least one access point; we avoid creating an
212 1.1 christos access point after the last block by checking bit 6 of data_type
213 1.1 christos */
214 1.1 christos if ((strm.data_type & 128) && !(strm.data_type & 64) &&
215 1.1 christos (totout == 0 || totout - last > span)) {
216 1.1 christos index = addpoint(index, strm.data_type & 7, totin,
217 1.1 christos totout, strm.avail_out, window);
218 1.1 christos if (index == NULL) {
219 1.1 christos ret = Z_MEM_ERROR;
220 1.1 christos goto build_index_error;
221 1.1 christos }
222 1.1 christos last = totout;
223 1.1 christos }
224 1.1 christos } while (strm.avail_in != 0);
225 1.1 christos } while (ret != Z_STREAM_END);
226 1.1 christos
227 1.1 christos /* clean up and return index (release unused entries in list) */
228 1.1 christos (void)inflateEnd(&strm);
229 1.1.1.2 christos index->list = realloc(index->list, sizeof(struct point) * index->have);
230 1.1 christos index->size = index->have;
231 1.1 christos *built = index;
232 1.1 christos return index->size;
233 1.1 christos
234 1.1 christos /* return error */
235 1.1 christos build_index_error:
236 1.1 christos (void)inflateEnd(&strm);
237 1.1 christos if (index != NULL)
238 1.1 christos free_index(index);
239 1.1 christos return ret;
240 1.1 christos }
241 1.1 christos
242 1.1 christos /* Use the index to read len bytes from offset into buf, return bytes read or
243 1.1 christos negative for error (Z_DATA_ERROR or Z_MEM_ERROR). If data is requested past
244 1.1 christos the end of the uncompressed data, then extract() will return a value less
245 1.1 christos than len, indicating how much as actually read into buf. This function
246 1.1 christos should not return a data error unless the file was modified since the index
247 1.1 christos was generated. extract() may also return Z_ERRNO if there is an error on
248 1.1 christos reading or seeking the input file. */
249 1.1 christos local int extract(FILE *in, struct access *index, off_t offset,
250 1.1 christos unsigned char *buf, int len)
251 1.1 christos {
252 1.1 christos int ret, skip;
253 1.1 christos z_stream strm;
254 1.1 christos struct point *here;
255 1.1 christos unsigned char input[CHUNK];
256 1.1 christos unsigned char discard[WINSIZE];
257 1.1 christos
258 1.1 christos /* proceed only if something reasonable to do */
259 1.1 christos if (len < 0)
260 1.1 christos return 0;
261 1.1 christos
262 1.1 christos /* find where in stream to start */
263 1.1 christos here = index->list;
264 1.1 christos ret = index->have;
265 1.1 christos while (--ret && here[1].out <= offset)
266 1.1 christos here++;
267 1.1 christos
268 1.1 christos /* initialize file and inflate state to start there */
269 1.1 christos strm.zalloc = Z_NULL;
270 1.1 christos strm.zfree = Z_NULL;
271 1.1 christos strm.opaque = Z_NULL;
272 1.1 christos strm.avail_in = 0;
273 1.1 christos strm.next_in = Z_NULL;
274 1.1 christos ret = inflateInit2(&strm, -15); /* raw inflate */
275 1.1 christos if (ret != Z_OK)
276 1.1 christos return ret;
277 1.1 christos ret = fseeko(in, here->in - (here->bits ? 1 : 0), SEEK_SET);
278 1.1 christos if (ret == -1)
279 1.1 christos goto extract_ret;
280 1.1 christos if (here->bits) {
281 1.1 christos ret = getc(in);
282 1.1 christos if (ret == -1) {
283 1.1 christos ret = ferror(in) ? Z_ERRNO : Z_DATA_ERROR;
284 1.1 christos goto extract_ret;
285 1.1 christos }
286 1.1 christos (void)inflatePrime(&strm, here->bits, ret >> (8 - here->bits));
287 1.1 christos }
288 1.1 christos (void)inflateSetDictionary(&strm, here->window, WINSIZE);
289 1.1 christos
290 1.1 christos /* skip uncompressed bytes until offset reached, then satisfy request */
291 1.1 christos offset -= here->out;
292 1.1 christos strm.avail_in = 0;
293 1.1 christos skip = 1; /* while skipping to offset */
294 1.1 christos do {
295 1.1 christos /* define where to put uncompressed data, and how much */
296 1.1 christos if (offset == 0 && skip) { /* at offset now */
297 1.1 christos strm.avail_out = len;
298 1.1 christos strm.next_out = buf;
299 1.1 christos skip = 0; /* only do this once */
300 1.1 christos }
301 1.1 christos if (offset > WINSIZE) { /* skip WINSIZE bytes */
302 1.1 christos strm.avail_out = WINSIZE;
303 1.1 christos strm.next_out = discard;
304 1.1 christos offset -= WINSIZE;
305 1.1 christos }
306 1.1 christos else if (offset != 0) { /* last skip */
307 1.1 christos strm.avail_out = (unsigned)offset;
308 1.1 christos strm.next_out = discard;
309 1.1 christos offset = 0;
310 1.1 christos }
311 1.1 christos
312 1.1 christos /* uncompress until avail_out filled, or end of stream */
313 1.1 christos do {
314 1.1 christos if (strm.avail_in == 0) {
315 1.1 christos strm.avail_in = fread(input, 1, CHUNK, in);
316 1.1 christos if (ferror(in)) {
317 1.1 christos ret = Z_ERRNO;
318 1.1 christos goto extract_ret;
319 1.1 christos }
320 1.1 christos if (strm.avail_in == 0) {
321 1.1 christos ret = Z_DATA_ERROR;
322 1.1 christos goto extract_ret;
323 1.1 christos }
324 1.1 christos strm.next_in = input;
325 1.1 christos }
326 1.1 christos ret = inflate(&strm, Z_NO_FLUSH); /* normal inflate */
327 1.1 christos if (ret == Z_NEED_DICT)
328 1.1 christos ret = Z_DATA_ERROR;
329 1.1 christos if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR)
330 1.1 christos goto extract_ret;
331 1.1 christos if (ret == Z_STREAM_END)
332 1.1 christos break;
333 1.1 christos } while (strm.avail_out != 0);
334 1.1 christos
335 1.1 christos /* if reach end of stream, then don't keep trying to get more */
336 1.1 christos if (ret == Z_STREAM_END)
337 1.1 christos break;
338 1.1 christos
339 1.1 christos /* do until offset reached and requested data read, or stream ends */
340 1.1 christos } while (skip);
341 1.1 christos
342 1.1 christos /* compute number of uncompressed bytes read after offset */
343 1.1 christos ret = skip ? 0 : len - strm.avail_out;
344 1.1 christos
345 1.1 christos /* clean up and return bytes read or error */
346 1.1 christos extract_ret:
347 1.1 christos (void)inflateEnd(&strm);
348 1.1 christos return ret;
349 1.1 christos }
350 1.1 christos
351 1.1 christos /* Demonstrate the use of build_index() and extract() by processing the file
352 1.1 christos provided on the command line, and the extracting 16K from about 2/3rds of
353 1.1 christos the way through the uncompressed output, and writing that to stdout. */
354 1.1 christos int main(int argc, char **argv)
355 1.1 christos {
356 1.1 christos int len;
357 1.1 christos off_t offset;
358 1.1 christos FILE *in;
359 1.1.1.2 christos struct access *index = NULL;
360 1.1 christos unsigned char buf[CHUNK];
361 1.1 christos
362 1.1 christos /* open input file */
363 1.1 christos if (argc != 2) {
364 1.1 christos fprintf(stderr, "usage: zran file.gz\n");
365 1.1 christos return 1;
366 1.1 christos }
367 1.1 christos in = fopen(argv[1], "rb");
368 1.1 christos if (in == NULL) {
369 1.1 christos fprintf(stderr, "zran: could not open %s for reading\n", argv[1]);
370 1.1 christos return 1;
371 1.1 christos }
372 1.1 christos
373 1.1 christos /* build index */
374 1.1 christos len = build_index(in, SPAN, &index);
375 1.1 christos if (len < 0) {
376 1.1 christos fclose(in);
377 1.1 christos switch (len) {
378 1.1 christos case Z_MEM_ERROR:
379 1.1 christos fprintf(stderr, "zran: out of memory\n");
380 1.1 christos break;
381 1.1 christos case Z_DATA_ERROR:
382 1.1 christos fprintf(stderr, "zran: compressed data error in %s\n", argv[1]);
383 1.1 christos break;
384 1.1 christos case Z_ERRNO:
385 1.1 christos fprintf(stderr, "zran: read error on %s\n", argv[1]);
386 1.1 christos break;
387 1.1 christos default:
388 1.1 christos fprintf(stderr, "zran: error %d while building index\n", len);
389 1.1 christos }
390 1.1 christos return 1;
391 1.1 christos }
392 1.1 christos fprintf(stderr, "zran: built index with %d access points\n", len);
393 1.1 christos
394 1.1 christos /* use index by reading some bytes from an arbitrary offset */
395 1.1 christos offset = (index->list[index->have - 1].out << 1) / 3;
396 1.1 christos len = extract(in, index, offset, buf, CHUNK);
397 1.1 christos if (len < 0)
398 1.1 christos fprintf(stderr, "zran: extraction failed: %s error\n",
399 1.1 christos len == Z_MEM_ERROR ? "out of memory" : "input corrupted");
400 1.1 christos else {
401 1.1 christos fwrite(buf, 1, len, stdout);
402 1.1 christos fprintf(stderr, "zran: extracted %d bytes at %llu\n", len, offset);
403 1.1 christos }
404 1.1 christos
405 1.1 christos /* clean up and exit */
406 1.1 christos free_index(index);
407 1.1 christos fclose(in);
408 1.1 christos return 0;
409 1.1 christos }
410