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