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