src/3rdparty/zlib/examples/gzlog.c
changeset 0 1918ee327afb
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /*
       
     2  * gzlog.c
       
     3  * Copyright (C) 2004 Mark Adler
       
     4  * For conditions of distribution and use, see copyright notice in gzlog.h
       
     5  * version 1.0, 26 Nov 2004
       
     6  *
       
     7  */
       
     8 
       
     9 #include <string.h>             /* memcmp() */
       
    10 #include <stdlib.h>             /* malloc(), free(), NULL */
       
    11 #include <sys/types.h>          /* size_t, off_t */
       
    12 #include <unistd.h>             /* read(), close(), sleep(), ftruncate(), */
       
    13                                 /* lseek() */
       
    14 #include <fcntl.h>              /* open() */
       
    15 #include <sys/file.h>           /* flock() */
       
    16 #include "zlib.h"               /* deflateInit2(), deflate(), deflateEnd() */
       
    17 
       
    18 #include "gzlog.h"              /* interface */
       
    19 #define local static
       
    20 
       
    21 /* log object structure */
       
    22 typedef struct {
       
    23     int id;                 /* object identifier */
       
    24     int fd;                 /* log file descriptor */
       
    25     off_t extra;            /* offset of extra "ap" subfield */
       
    26     off_t mark_off;         /* offset of marked data */
       
    27     off_t last_off;         /* offset of last block */
       
    28     unsigned long crc;      /* uncompressed crc */
       
    29     unsigned long len;      /* uncompressed length (modulo 2^32) */
       
    30     unsigned stored;        /* length of current stored block */
       
    31 } gz_log;
       
    32 
       
    33 #define GZLOGID 19334       /* gz_log object identifier */
       
    34 
       
    35 #define LOCK_RETRY 1            /* retry lock once a second */
       
    36 #define LOCK_PATIENCE 1200      /* try about twenty minutes before forcing */
       
    37 
       
    38 /* acquire a lock on a file */
       
    39 local int lock(int fd)
       
    40 {
       
    41     int patience;
       
    42 
       
    43     /* try to lock every LOCK_RETRY seconds for LOCK_PATIENCE seconds */
       
    44     patience = LOCK_PATIENCE;
       
    45     do {
       
    46         if (flock(fd, LOCK_EX + LOCK_NB) == 0)
       
    47             return 0;
       
    48         (void)sleep(LOCK_RETRY);
       
    49         patience -= LOCK_RETRY;
       
    50     } while (patience > 0);
       
    51 
       
    52     /* we've run out of patience -- give up */
       
    53     return -1;
       
    54 }
       
    55 
       
    56 /* release lock */
       
    57 local void unlock(int fd)
       
    58 {
       
    59     (void)flock(fd, LOCK_UN);
       
    60 }
       
    61 
       
    62 /* release a log object */
       
    63 local void log_clean(gz_log *log)
       
    64 {
       
    65     unlock(log->fd);
       
    66     (void)close(log->fd);
       
    67     free(log);
       
    68 }
       
    69 
       
    70 /* read an unsigned long from a byte buffer little-endian */
       
    71 local unsigned long make_ulg(unsigned char *buf)
       
    72 {
       
    73     int n;
       
    74     unsigned long val;
       
    75 
       
    76     val = (unsigned long)(*buf++);
       
    77     for (n = 8; n < 32; n += 8)
       
    78         val += (unsigned long)(*buf++) << n;
       
    79     return val;
       
    80 }
       
    81 
       
    82 /* read an off_t from a byte buffer little-endian */
       
    83 local off_t make_off(unsigned char *buf)
       
    84 {
       
    85     int n;
       
    86     off_t val;
       
    87 
       
    88     val = (off_t)(*buf++);
       
    89     for (n = 8; n < 64; n += 8)
       
    90         val += (off_t)(*buf++) << n;
       
    91     return val;
       
    92 }
       
    93 
       
    94 /* write an unsigned long little-endian to byte buffer */
       
    95 local void dice_ulg(unsigned long val, unsigned char *buf)
       
    96 {
       
    97     int n;
       
    98 
       
    99     for (n = 0; n < 4; n++) {
       
   100         *buf++ = val & 0xff;
       
   101         val >>= 8;
       
   102     }
       
   103 }
       
   104 
       
   105 /* write an off_t little-endian to byte buffer */
       
   106 local void dice_off(off_t val, unsigned char *buf)
       
   107 {
       
   108     int n;
       
   109 
       
   110     for (n = 0; n < 8; n++) {
       
   111         *buf++ = val & 0xff;
       
   112         val >>= 8;
       
   113     }
       
   114 }
       
   115 
       
   116 /* initial, empty gzip file for appending */
       
   117 local char empty_gz[] = {
       
   118     0x1f, 0x8b,                 /* magic gzip id */
       
   119     8,                          /* compression method is deflate */
       
   120     4,                          /* there is an extra field */
       
   121     0, 0, 0, 0,                 /* no modification time provided */
       
   122     0, 0xff,                    /* no extra flags, no OS */
       
   123     20, 0, 'a', 'p', 16, 0,     /* extra field with "ap" subfield */
       
   124     32, 0, 0, 0, 0, 0, 0, 0,    /* offset of uncompressed data */
       
   125     32, 0, 0, 0, 0, 0, 0, 0,    /* offset of last block */
       
   126     1, 0, 0, 0xff, 0xff,        /* empty stored block (last) */
       
   127     0, 0, 0, 0,                 /* crc */
       
   128     0, 0, 0, 0                  /* uncompressed length */
       
   129 };
       
   130 
       
   131 /* initialize a log object with locking */
       
   132 void *gzlog_open(char *path)
       
   133 {
       
   134     unsigned xlen;
       
   135     unsigned char temp[20];
       
   136     unsigned sub_len;
       
   137     int good;
       
   138     gz_log *log;
       
   139 
       
   140     /* allocate log structure */
       
   141     log = malloc(sizeof(gz_log));
       
   142     if (log == NULL)
       
   143         return NULL;
       
   144     log->id = GZLOGID;
       
   145 
       
   146     /* open file, creating it if necessary, and locking it */
       
   147     log->fd = open(path, O_RDWR | O_CREAT, 0600);
       
   148     if (log->fd < 0) {
       
   149         free(log);
       
   150         return NULL;
       
   151     }
       
   152     if (lock(log->fd)) {
       
   153         close(log->fd);
       
   154         free(log);
       
   155         return NULL;
       
   156     }
       
   157 
       
   158     /* if file is empty, write new gzip stream */
       
   159     if (lseek(log->fd, 0, SEEK_END) == 0) {
       
   160         if (write(log->fd, empty_gz, sizeof(empty_gz)) != sizeof(empty_gz)) {
       
   161             log_clean(log);
       
   162             return NULL;
       
   163         }
       
   164     }
       
   165 
       
   166     /* check gzip header */
       
   167     (void)lseek(log->fd, 0, SEEK_SET);
       
   168     if (read(log->fd, temp, 12) != 12 || temp[0] != 0x1f ||
       
   169         temp[1] != 0x8b || temp[2] != 8 || (temp[3] & 4) == 0) {
       
   170         log_clean(log);
       
   171         return NULL;
       
   172     }
       
   173 
       
   174     /* process extra field to find "ap" sub-field */
       
   175     xlen = temp[10] + (temp[11] << 8);
       
   176     good = 0;
       
   177     while (xlen) {
       
   178         if (xlen < 4 || read(log->fd, temp, 4) != 4)
       
   179             break;
       
   180         sub_len = temp[2];
       
   181         sub_len += temp[3] << 8;
       
   182         xlen -= 4;
       
   183         if (memcmp(temp, "ap", 2) == 0 && sub_len == 16) {
       
   184             good = 1;
       
   185             break;
       
   186         }
       
   187         if (xlen < sub_len)
       
   188             break;
       
   189         (void)lseek(log->fd, sub_len, SEEK_CUR);
       
   190         xlen -= sub_len;
       
   191     }
       
   192     if (!good) {
       
   193         log_clean(log);
       
   194         return NULL;
       
   195     }
       
   196 
       
   197     /* read in "ap" sub-field */
       
   198     log->extra = lseek(log->fd, 0, SEEK_CUR);
       
   199     if (read(log->fd, temp, 16) != 16) {
       
   200         log_clean(log);
       
   201         return NULL;
       
   202     }
       
   203     log->mark_off = make_off(temp);
       
   204     log->last_off = make_off(temp + 8);
       
   205 
       
   206     /* get crc, length of gzip file */
       
   207     (void)lseek(log->fd, log->last_off, SEEK_SET);
       
   208     if (read(log->fd, temp, 13) != 13 ||
       
   209         memcmp(temp, "\001\000\000\377\377", 5) != 0) {
       
   210         log_clean(log);
       
   211         return NULL;
       
   212     }
       
   213     log->crc = make_ulg(temp + 5);
       
   214     log->len = make_ulg(temp + 9);
       
   215 
       
   216     /* set up to write over empty last block */
       
   217     (void)lseek(log->fd, log->last_off + 5, SEEK_SET);
       
   218     log->stored = 0;
       
   219     return (void *)log;
       
   220 }
       
   221 
       
   222 /* maximum amount to put in a stored block before starting a new one */
       
   223 #define MAX_BLOCK 16384
       
   224 
       
   225 /* write a block to a log object */
       
   226 int gzlog_write(void *obj, char *data, size_t len)
       
   227 {
       
   228     size_t some;
       
   229     unsigned char temp[5];
       
   230     gz_log *log;
       
   231 
       
   232     /* check object */
       
   233     log = (gz_log *)obj;
       
   234     if (log == NULL || log->id != GZLOGID)
       
   235         return 1;
       
   236 
       
   237     /* write stored blocks until all of the input is written */
       
   238     do {
       
   239         some = MAX_BLOCK - log->stored;
       
   240         if (some > len)
       
   241             some = len;
       
   242         if (write(log->fd, data, some) != some)
       
   243             return 1;
       
   244         log->crc = crc32(log->crc, data, some);
       
   245         log->len += some;
       
   246         len -= some;
       
   247         data += some;
       
   248         log->stored += some;
       
   249 
       
   250         /* if the stored block is full, end it and start another */
       
   251         if (log->stored == MAX_BLOCK) {
       
   252             (void)lseek(log->fd, log->last_off, SEEK_SET);
       
   253             temp[0] = 0;
       
   254             dice_ulg(log->stored + ((unsigned long)(~log->stored) << 16),
       
   255                      temp + 1);
       
   256             if (write(log->fd, temp, 5) != 5)
       
   257                 return 1;
       
   258             log->last_off = lseek(log->fd, log->stored, SEEK_CUR);
       
   259             (void)lseek(log->fd, 5, SEEK_CUR);
       
   260             log->stored = 0;
       
   261         }
       
   262     } while (len);
       
   263     return 0;
       
   264 }
       
   265 
       
   266 /* recompress the remaining stored deflate data in place */
       
   267 local int recomp(gz_log *log)
       
   268 {
       
   269     z_stream strm;
       
   270     size_t len, max;
       
   271     unsigned char *in;
       
   272     unsigned char *out;
       
   273     unsigned char temp[16];
       
   274 
       
   275     /* allocate space and read it all in (it's around 1 MB) */
       
   276     len = log->last_off - log->mark_off;
       
   277     max = len + (len >> 12) + (len >> 14) + 11;
       
   278     out = malloc(max);
       
   279     if (out == NULL)
       
   280         return 1;
       
   281     in = malloc(len);
       
   282     if (in == NULL) {
       
   283         free(out);
       
   284         return 1;
       
   285     }
       
   286     (void)lseek(log->fd, log->mark_off, SEEK_SET);
       
   287     if (read(log->fd, in, len) != len) {
       
   288         free(in);
       
   289         free(out);
       
   290         return 1;
       
   291     }
       
   292 
       
   293     /* recompress in memory, decoding stored data as we go */
       
   294     /* note: this assumes that unsigned is four bytes or more */
       
   295     /*       consider not making that assumption */
       
   296     strm.zalloc = Z_NULL;
       
   297     strm.zfree = Z_NULL;
       
   298     strm.opaque = Z_NULL;
       
   299     if (deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 8,
       
   300         Z_DEFAULT_STRATEGY) != Z_OK) {
       
   301         free(in);
       
   302         free(out);
       
   303         return 1;
       
   304     }
       
   305     strm.next_in = in;
       
   306     strm.avail_out = max;
       
   307     strm.next_out = out;
       
   308     while (len >= 5) {
       
   309         if (strm.next_in[0] != 0)
       
   310             break;
       
   311         strm.avail_in = strm.next_in[1] + (strm.next_in[2] << 8);
       
   312         strm.next_in += 5;
       
   313         len -= 5;
       
   314         if (strm.avail_in != 0) {
       
   315             if (len < strm.avail_in)
       
   316                 break;
       
   317             len -= strm.avail_in;
       
   318             (void)deflate(&strm, Z_NO_FLUSH);
       
   319             if (strm.avail_in != 0 || strm.avail_out == 0)
       
   320                 break;
       
   321         }
       
   322     }
       
   323     (void)deflate(&strm, Z_SYNC_FLUSH);
       
   324     (void)deflateEnd(&strm);
       
   325     free(in);
       
   326     if (len != 0 || strm.avail_out == 0) {
       
   327         free(out);
       
   328         return 1;
       
   329     }
       
   330 
       
   331     /* overwrite stored data with compressed data */
       
   332     (void)lseek(log->fd, log->mark_off, SEEK_SET);
       
   333     len = max - strm.avail_out;
       
   334     if (write(log->fd, out, len) != len) {
       
   335         free(out);
       
   336         return 1;
       
   337     }
       
   338     free(out);
       
   339 
       
   340     /* write last empty block, crc, and length */
       
   341     log->mark_off = log->last_off = lseek(log->fd, 0, SEEK_CUR);
       
   342     temp[0] = 1;
       
   343     dice_ulg(0xffffL << 16, temp + 1);
       
   344     dice_ulg(log->crc, temp + 5);
       
   345     dice_ulg(log->len, temp + 9);
       
   346     if (write(log->fd, temp, 13) != 13)
       
   347         return 1;
       
   348 
       
   349     /* truncate file to discard remaining stored data and old trailer */
       
   350     ftruncate(log->fd, lseek(log->fd, 0, SEEK_CUR));
       
   351 
       
   352     /* update extra field to point to new last empty block */
       
   353     (void)lseek(log->fd, log->extra, SEEK_SET);
       
   354     dice_off(log->mark_off, temp);
       
   355     dice_off(log->last_off, temp + 8);
       
   356     if (write(log->fd, temp, 16) != 16)
       
   357         return 1;
       
   358     return 0;
       
   359 }
       
   360 
       
   361 /* maximum accumulation of stored blocks before compressing */
       
   362 #define MAX_STORED 1048576
       
   363 
       
   364 /* close log object */
       
   365 int gzlog_close(void *obj)
       
   366 {
       
   367     unsigned char temp[8];
       
   368     gz_log *log;
       
   369 
       
   370     /* check object */
       
   371     log = (gz_log *)obj;
       
   372     if (log == NULL || log->id != GZLOGID)
       
   373         return 1;
       
   374 
       
   375     /* go to start of most recent block being written */
       
   376     (void)lseek(log->fd, log->last_off, SEEK_SET);
       
   377 
       
   378     /* if some stuff was put there, update block */
       
   379     if (log->stored) {
       
   380         temp[0] = 0;
       
   381         dice_ulg(log->stored + ((unsigned long)(~log->stored) << 16),
       
   382                  temp + 1);
       
   383         if (write(log->fd, temp, 5) != 5)
       
   384             return 1;
       
   385         log->last_off = lseek(log->fd, log->stored, SEEK_CUR);
       
   386     }
       
   387 
       
   388     /* write last block (empty) */
       
   389     if (write(log->fd, "\001\000\000\377\377", 5) != 5)
       
   390         return 1;
       
   391 
       
   392     /* write updated crc and uncompressed length */
       
   393     dice_ulg(log->crc, temp);
       
   394     dice_ulg(log->len, temp + 4);
       
   395     if (write(log->fd, temp, 8) != 8)
       
   396         return 1;
       
   397 
       
   398     /* put offset of that last block in gzip extra block */
       
   399     (void)lseek(log->fd, log->extra + 8, SEEK_SET);
       
   400     dice_off(log->last_off, temp);
       
   401     if (write(log->fd, temp, 8) != 8)
       
   402         return 1;
       
   403 
       
   404     /* if more than 1 MB stored, then time to compress it */
       
   405     if (log->last_off - log->mark_off > MAX_STORED) {
       
   406         if (recomp(log))
       
   407             return 1;
       
   408     }
       
   409 
       
   410     /* unlock and close file */
       
   411     log_clean(log);
       
   412     return 0;
       
   413 }