Home | History | Annotate | Line # | Download | only in examples
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