root/lib/zlib/examples/gzlog.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. lock
  2. unlock
  3. log_clean
  4. make_ulg
  5. make_off
  6. dice_ulg
  7. dice_off
  8. gzlog_open
  9. gzlog_write
  10. recomp
  11. gzlog_close

   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)
     /* [<][>][^][v][top][bottom][index][help] */
  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)
     /* [<][>][^][v][top][bottom][index][help] */
  58 {
  59     (void)flock(fd, LOCK_UN);
  60 }
  61 
  62 /* release a log object */
  63 local void log_clean(gz_log *log)
     /* [<][>][^][v][top][bottom][index][help] */
  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)
     /* [<][>][^][v][top][bottom][index][help] */
  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)
     /* [<][>][^][v][top][bottom][index][help] */
  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)
     /* [<][>][^][v][top][bottom][index][help] */
  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)
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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 }

/* [<][>][^][v][top][bottom][index][help] */