kusano 7d535a
/*
kusano 7d535a
 * gzlog.c
kusano 7d535a
 * Copyright (C) 2004, 2008 Mark Adler, all rights reserved
kusano 7d535a
 * For conditions of distribution and use, see copyright notice in gzlog.h
kusano 7d535a
 * version 2.0, 25 Apr 2008
kusano 7d535a
 */
kusano 7d535a
kusano 7d535a
/*
kusano 7d535a
   gzlog provides a mechanism for frequently appending short strings to a gzip
kusano 7d535a
   file that is efficient both in execution time and compression ratio.  The
kusano 7d535a
   strategy is to write the short strings in an uncompressed form to the end of
kusano 7d535a
   the gzip file, only compressing when the amount of uncompressed data has
kusano 7d535a
   reached a given threshold.
kusano 7d535a
kusano 7d535a
   gzlog also provides protection against interruptions in the process due to
kusano 7d535a
   system crashes.  The status of the operation is recorded in an extra field
kusano 7d535a
   in the gzip file, and is only updated once the gzip file is brought to a
kusano 7d535a
   valid state.  The last data to be appended or compressed is saved in an
kusano 7d535a
   auxiliary file, so that if the operation is interrupted, it can be completed
kusano 7d535a
   the next time an append operation is attempted.
kusano 7d535a
kusano 7d535a
   gzlog maintains another auxiliary file with the last 32K of data from the
kusano 7d535a
   compressed portion, which is preloaded for the compression of the subsequent
kusano 7d535a
   data.  This minimizes the impact to the compression ratio of appending.
kusano 7d535a
 */
kusano 7d535a
kusano 7d535a
/*
kusano 7d535a
   Operations Concept:
kusano 7d535a
kusano 7d535a
   Files (log name "foo"):
kusano 7d535a
   foo.gz -- gzip file with the complete log
kusano 7d535a
   foo.add -- last message to append or last data to compress
kusano 7d535a
   foo.dict -- dictionary of the last 32K of data for next compression
kusano 7d535a
   foo.temp -- temporary dictionary file for compression after this one
kusano 7d535a
   foo.lock -- lock file for reading and writing the other files
kusano 7d535a
   foo.repairs -- log file for log file recovery operations (not compressed)
kusano 7d535a
kusano 7d535a
   gzip file structure:
kusano 7d535a
   - fixed-length (no file name) header with extra field (see below)
kusano 7d535a
   - compressed data ending initially with empty stored block
kusano 7d535a
   - uncompressed data filling out originally empty stored block and
kusano 7d535a
     subsequent stored blocks as needed (16K max each)
kusano 7d535a
   - gzip trailer
kusano 7d535a
   - no junk at end (no other gzip streams)
kusano 7d535a
kusano 7d535a
   When appending data, the information in the first three items above plus the
kusano 7d535a
   foo.add file are sufficient to recover an interrupted append operation.  The
kusano 7d535a
   extra field has the necessary information to restore the start of the last
kusano 7d535a
   stored block and determine where to append the data in the foo.add file, as
kusano 7d535a
   well as the crc and length of the gzip data before the append operation.
kusano 7d535a
kusano 7d535a
   The foo.add file is created before the gzip file is marked for append, and
kusano 7d535a
   deleted after the gzip file is marked as complete.  So if the append
kusano 7d535a
   operation is interrupted, the data to add will still be there.  If due to
kusano 7d535a
   some external force, the foo.add file gets deleted between when the append
kusano 7d535a
   operation was interrupted and when recovery is attempted, the gzip file will
kusano 7d535a
   still be restored, but without the appended data.
kusano 7d535a
kusano 7d535a
   When compressing data, the information in the first two items above plus the
kusano 7d535a
   foo.add file are sufficient to recover an interrupted compress operation.
kusano 7d535a
   The extra field has the necessary information to find the end of the
kusano 7d535a
   compressed data, and contains both the crc and length of just the compressed
kusano 7d535a
   data and of the complete set of data including the contents of the foo.add
kusano 7d535a
   file.
kusano 7d535a
kusano 7d535a
   Again, the foo.add file is maintained during the compress operation in case
kusano 7d535a
   of an interruption.  If in the unlikely event the foo.add file with the data
kusano 7d535a
   to be compressed is missing due to some external force, a gzip file with
kusano 7d535a
   just the previous compressed data will be reconstructed.  In this case, all
kusano 7d535a
   of the data that was to be compressed is lost (approximately one megabyte).
kusano 7d535a
   This will not occur if all that happened was an interruption of the compress
kusano 7d535a
   operation.
kusano 7d535a
kusano 7d535a
   The third state that is marked is the replacement of the old dictionary with
kusano 7d535a
   the new dictionary after a compress operation.  Once compression is
kusano 7d535a
   complete, the gzip file is marked as being in the replace state.  This
kusano 7d535a
   completes the gzip file, so an interrupt after being so marked does not
kusano 7d535a
   result in recompression.  Then the dictionary file is replaced, and the gzip
kusano 7d535a
   file is marked as completed.  This state prevents the possibility of
kusano 7d535a
   restarting compression with the wrong dictionary file.
kusano 7d535a
kusano 7d535a
   All three operations are wrapped by a lock/unlock procedure.  In order to
kusano 7d535a
   gain exclusive access to the log files, first a foo.lock file must be
kusano 7d535a
   exclusively created.  When all operations are complete, the lock is
kusano 7d535a
   released by deleting the foo.lock file.  If when attempting to create the
kusano 7d535a
   lock file, it already exists and the modify time of the lock file is more
kusano 7d535a
   than five minutes old (set by the PATIENCE define below), then the old
kusano 7d535a
   lock file is considered stale and deleted, and the exclusive creation of
kusano 7d535a
   the lock file is retried.  To assure that there are no false assessments
kusano 7d535a
   of the staleness of the lock file, the operations periodically touch the
kusano 7d535a
   lock file to update the modified date.
kusano 7d535a
kusano 7d535a
   Following is the definition of the extra field with all of the information
kusano 7d535a
   required to enable the above append and compress operations and their
kusano 7d535a
   recovery if interrupted.  Multi-byte values are stored little endian
kusano 7d535a
   (consistent with the gzip format).  File pointers are eight bytes long.
kusano 7d535a
   The crc's and lengths for the gzip trailer are four bytes long.  (Note that
kusano 7d535a
   the length at the end of a gzip file is used for error checking only, and
kusano 7d535a
   for large files is actually the length modulo 2^32.)  The stored block
kusano 7d535a
   length is two bytes long.  The gzip extra field two-byte identification is
kusano 7d535a
   "ap" for append.  It is assumed that writing the extra field to the file is
kusano 7d535a
   an "atomic" operation.  That is, either all of the extra field is written
kusano 7d535a
   to the file, or none of it is, if the operation is interrupted right at the
kusano 7d535a
   point of updating the extra field.  This is a reasonable assumption, since
kusano 7d535a
   the extra field is within the first 52 bytes of the file, which is smaller
kusano 7d535a
   than any expected block size for a mass storage device (usually 512 bytes or
kusano 7d535a
   larger).
kusano 7d535a
kusano 7d535a
   Extra field (35 bytes):
kusano 7d535a
   - Pointer to first stored block length -- this points to the two-byte length
kusano 7d535a
     of the first stored block, which is followed by the two-byte, one's
kusano 7d535a
     complement of that length.  The stored block length is preceded by the
kusano 7d535a
     three-bit header of the stored block, which is the actual start of the
kusano 7d535a
     stored block in the deflate format.  See the bit offset field below.
kusano 7d535a
   - Pointer to the last stored block length.  This is the same as above, but
kusano 7d535a
     for the last stored block of the uncompressed data in the gzip file.
kusano 7d535a
     Initially this is the same as the first stored block length pointer.
kusano 7d535a
     When the stored block gets to 16K (see the MAX_STORE define), then a new
kusano 7d535a
     stored block as added, at which point the last stored block length pointer
kusano 7d535a
     is different from the first stored block length pointer.  When they are
kusano 7d535a
     different, the first bit of the last stored block header is eight bits, or
kusano 7d535a
     one byte back from the block length.
kusano 7d535a
   - Compressed data crc and length.  This is the crc and length of the data
kusano 7d535a
     that is in the compressed portion of the deflate stream.  These are used
kusano 7d535a
     only in the event that the foo.add file containing the data to compress is
kusano 7d535a
     lost after a compress operation is interrupted.
kusano 7d535a
   - Total data crc and length.  This is the crc and length of all of the data
kusano 7d535a
     stored in the gzip file, compressed and uncompressed.  It is used to
kusano 7d535a
     reconstruct the gzip trailer when compressing, as well as when recovering
kusano 7d535a
     interrupted operations.
kusano 7d535a
   - Final stored block length.  This is used to quickly find where to append,
kusano 7d535a
     and allows the restoration of the original final stored block state when
kusano 7d535a
     an append operation is interrupted.
kusano 7d535a
   - First stored block start as the number of bits back from the final stored
kusano 7d535a
     block first length byte.  This value is in the range of 3..10, and is
kusano 7d535a
     stored as the low three bits of the final byte of the extra field after
kusano 7d535a
     subtracting three (0..7).  This allows the last-block bit of the stored
kusano 7d535a
     block header to be updated when a new stored block is added, for the case
kusano 7d535a
     when the first stored block and the last stored block are the same.  (When
kusano 7d535a
     they are different, the numbers of bits back is known to be eight.)  This
kusano 7d535a
     also allows for new compressed data to be appended to the old compressed
kusano 7d535a
     data in the compress operation, overwriting the previous first stored
kusano 7d535a
     block, or for the compressed data to be terminated and a valid gzip file
kusano 7d535a
     reconstructed on the off chance that a compression operation was
kusano 7d535a
     interrupted and the data to compress in the foo.add file was deleted.
kusano 7d535a
   - The operation in process.  This is the next two bits in the last byte (the
kusano 7d535a
     bits under the mask 0x18).  The are interpreted as 0: nothing in process,
kusano 7d535a
     1: append in process, 2: compress in process, 3: replace in process.
kusano 7d535a
   - The top three bits of the last byte in the extra field are reserved and
kusano 7d535a
     are currently set to zero.
kusano 7d535a
kusano 7d535a
   Main procedure:
kusano 7d535a
   - Exclusively create the foo.lock file using the O_CREAT and O_EXCL modes of
kusano 7d535a
     the system open() call.  If the modify time of an existing lock file is
kusano 7d535a
     more than PATIENCE seconds old, then the lock file is deleted and the
kusano 7d535a
     exclusive create is retried.
kusano 7d535a
   - Load the extra field from the foo.gz file, and see if an operation was in
kusano 7d535a
     progress but not completed.  If so, apply the recovery procedure below.
kusano 7d535a
   - Perform the append procedure with the provided data.
kusano 7d535a
   - If the uncompressed data in the foo.gz file is 1MB or more, apply the
kusano 7d535a
     compress procedure.
kusano 7d535a
   - Delete the foo.lock file.
kusano 7d535a
kusano 7d535a
   Append procedure:
kusano 7d535a
   - Put what to append in the foo.add file so that the operation can be
kusano 7d535a
     restarted if this procedure is interrupted.
kusano 7d535a
   - Mark the foo.gz extra field with the append operation in progress.
kusano 7d535a
   + Restore the original last-block bit and stored block length of the last
kusano 7d535a
     stored block from the information in the extra field, in case a previous
kusano 7d535a
     append operation was interrupted.
kusano 7d535a
   - Append the provided data to the last stored block, creating new stored
kusano 7d535a
     blocks as needed and updating the stored blocks last-block bits and
kusano 7d535a
     lengths.
kusano 7d535a
   - Update the crc and length with the new data, and write the gzip trailer.
kusano 7d535a
   - Write over the extra field (with a single write operation) with the new
kusano 7d535a
     pointers, lengths, and crc's, and mark the gzip file as not in process.
kusano 7d535a
     Though there is still a foo.add file, it will be ignored since nothing
kusano 7d535a
     is in process.  If a foo.add file is leftover from a previously
kusano 7d535a
     completed operation, it is truncated when writing new data to it.
kusano 7d535a
   - Delete the foo.add file.
kusano 7d535a
kusano 7d535a
   Compress and replace procedures:
kusano 7d535a
   - Read all of the uncompressed data in the stored blocks in foo.gz and write
kusano 7d535a
     it to foo.add.  Also write foo.temp with the last 32K of that data to
kusano 7d535a
     provide a dictionary for the next invocation of this procedure.
kusano 7d535a
   - Rewrite the extra field marking foo.gz with a compression in process.
kusano 7d535a
   * If there is no data provided to compress (due to a missing foo.add file
kusano 7d535a
     when recovering), reconstruct and truncate the foo.gz file to contain
kusano 7d535a
     only the previous compressed data and proceed to the step after the next
kusano 7d535a
     one.  Otherwise ...
kusano 7d535a
   - Compress the data with the dictionary in foo.dict, and write to the
kusano 7d535a
     foo.gz file starting at the bit immediately following the last previously
kusano 7d535a
     compressed block.  If there is no foo.dict, proceed anyway with the
kusano 7d535a
     compression at slightly reduced efficiency.  (For the foo.dict file to be
kusano 7d535a
     missing requires some external failure beyond simply the interruption of
kusano 7d535a
     a compress operation.)  During this process, the foo.lock file is
kusano 7d535a
     periodically touched to assure that that file is not considered stale by
kusano 7d535a
     another process before we're done.  The deflation is terminated with a
kusano 7d535a
     non-last empty static block (10 bits long), that is then located and
kusano 7d535a
     written over by a last-bit-set empty stored block.
kusano 7d535a
   - Append the crc and length of the data in the gzip file (previously
kusano 7d535a
     calculated during the append operations).
kusano 7d535a
   - Write over the extra field with the updated stored block offsets, bits
kusano 7d535a
     back, crc's, and lengths, and mark foo.gz as in process for a replacement
kusano 7d535a
     of the dictionary.
kusano 7d535a
   @ Delete the foo.add file.
kusano 7d535a
   - Replace foo.dict with foo.temp.
kusano 7d535a
   - Write over the extra field, marking foo.gz as complete.
kusano 7d535a
kusano 7d535a
   Recovery procedure:
kusano 7d535a
   - If not a replace recovery, read in the foo.add file, and provide that data
kusano 7d535a
     to the appropriate recovery below.  If there is no foo.add file, provide
kusano 7d535a
     a zero data length to the recovery.  In that case, the append recovery
kusano 7d535a
     restores the foo.gz to the previous compressed + uncompressed data state.
kusano 7d535a
     For the the compress recovery, a missing foo.add file results in foo.gz
kusano 7d535a
     being restored to the previous compressed-only data state.
kusano 7d535a
   - Append recovery:
kusano 7d535a
     - Pick up append at + step above
kusano 7d535a
   - Compress recovery:
kusano 7d535a
     - Pick up compress at * step above
kusano 7d535a
   - Replace recovery:
kusano 7d535a
     - Pick up compress at @ step above
kusano 7d535a
   - Log the repair with a date stamp in foo.repairs
kusano 7d535a
 */
kusano 7d535a
kusano 7d535a
#include <sys types.h=""></sys>
kusano 7d535a
#include <stdio.h>      /* rename, fopen, fprintf, fclose */</stdio.h>
kusano 7d535a
#include <stdlib.h>     /* malloc, free */</stdlib.h>
kusano 7d535a
#include <string.h>     /* strlen, strrchr, strcpy, strncpy, strcmp */</string.h>
kusano 7d535a
#include <fcntl.h>      /* open */</fcntl.h>
kusano 7d535a
#include <unistd.h>     /* lseek, read, write, close, unlink, sleep, */</unistd.h>
kusano 7d535a
                        /* ftruncate, fsync */
kusano 7d535a
#include <errno.h>      /* errno */</errno.h>
kusano 7d535a
#include <time.h>       /* time, ctime */</time.h>
kusano 7d535a
#include <sys stat.h="">   /* stat */</sys>
kusano 7d535a
#include <sys time.h="">   /* utimes */</sys>
kusano 7d535a
#include "zlib.h"       /* crc32 */
kusano 7d535a
kusano 7d535a
#include "gzlog.h"      /* header for external access */
kusano 7d535a
kusano 7d535a
#define local static
kusano 7d535a
typedef unsigned int uint;
kusano 7d535a
typedef unsigned long ulong;
kusano 7d535a
kusano 7d535a
/* Macro for debugging to deterministically force recovery operations */
kusano 7d535a
#ifdef DEBUG
kusano 7d535a
    #include <setjmp.h>         /* longjmp */</setjmp.h>
kusano 7d535a
    jmp_buf gzlog_jump;         /* where to go back to */
kusano 7d535a
    int gzlog_bail = 0;         /* which point to bail at (1..8) */
kusano 7d535a
    int gzlog_count = -1;       /* number of times through to wait */
kusano 7d535a
#   define BAIL(n) do { if (n == gzlog_bail && gzlog_count-- == 0) \
kusano 7d535a
                            longjmp(gzlog_jump, gzlog_bail); } while (0)
kusano 7d535a
#else
kusano 7d535a
#   define BAIL(n)
kusano 7d535a
#endif
kusano 7d535a
kusano 7d535a
/* how old the lock file can be in seconds before considering it stale */
kusano 7d535a
#define PATIENCE 300
kusano 7d535a
kusano 7d535a
/* maximum stored block size in Kbytes -- must be in 1..63 */
kusano 7d535a
#define MAX_STORE 16
kusano 7d535a
kusano 7d535a
/* number of stored Kbytes to trigger compression (must be >= 32 to allow
kusano 7d535a
   dictionary construction, and <= 204 * MAX_STORE, in order for >> 10 to
kusano 7d535a
   discard the stored block headers contribution of five bytes each) */
kusano 7d535a
#define TRIGGER 1024
kusano 7d535a
kusano 7d535a
/* size of a deflate dictionary (this cannot be changed) */
kusano 7d535a
#define DICT 32768U
kusano 7d535a
kusano 7d535a
/* values for the operation (2 bits) */
kusano 7d535a
#define NO_OP 0
kusano 7d535a
#define APPEND_OP 1
kusano 7d535a
#define COMPRESS_OP 2
kusano 7d535a
#define REPLACE_OP 3
kusano 7d535a
kusano 7d535a
/* macros to extract little-endian integers from an unsigned byte buffer */
kusano 7d535a
#define PULL2(p) ((p)[0]+((uint)((p)[1])<<8))
kusano 7d535a
#define PULL4(p) (PULL2(p)+((ulong)PULL2(p+2)<<16))
kusano 7d535a
#define PULL8(p) (PULL4(p)+((off_t)PULL4(p+4)<<32))
kusano 7d535a
kusano 7d535a
/* macros to store integers into a byte buffer in little-endian order */
kusano 7d535a
#define PUT2(p,a) do {(p)[0]=a;(p)[1]=(a)>>8;} while(0)
kusano 7d535a
#define PUT4(p,a) do {PUT2(p,a);PUT2(p+2,a>>16);} while(0)
kusano 7d535a
#define PUT8(p,a) do {PUT4(p,a);PUT4(p+4,a>>32);} while(0)
kusano 7d535a
kusano 7d535a
/* internal structure for log information */
kusano 7d535a
#define LOGID "\106\035\172"    /* should be three non-zero characters */
kusano 7d535a
struct log {
kusano 7d535a
    char id[4];     /* contains LOGID to detect inadvertent overwrites */
kusano 7d535a
    int fd;         /* file descriptor for .gz file, opened read/write */
kusano 7d535a
    char *path;     /* allocated path, e.g. "/var/log/foo" or "foo" */
kusano 7d535a
    char *end;      /* end of path, for appending suffices such as ".gz" */
kusano 7d535a
    off_t first;    /* offset of first stored block first length byte */
kusano 7d535a
    int back;       /* location of first block id in bits back from first */
kusano 7d535a
    uint stored;    /* bytes currently in last stored block */
kusano 7d535a
    off_t last;     /* offset of last stored block first length byte */
kusano 7d535a
    ulong ccrc;     /* crc of compressed data */
kusano 7d535a
    ulong clen;     /* length (modulo 2^32) of compressed data */
kusano 7d535a
    ulong tcrc;     /* crc of total data */
kusano 7d535a
    ulong tlen;     /* length (modulo 2^32) of total data */
kusano 7d535a
    time_t lock;    /* last modify time of our lock file */
kusano 7d535a
};
kusano 7d535a
kusano 7d535a
/* gzip header for gzlog */
kusano 7d535a
local unsigned char log_gzhead[] = {
kusano 7d535a
    0x1f, 0x8b,                 /* magic gzip id */
kusano 7d535a
    8,                          /* compression method is deflate */
kusano 7d535a
    4,                          /* there is an extra field (no file name) */
kusano 7d535a
    0, 0, 0, 0,                 /* no modification time provided */
kusano 7d535a
    0, 0xff,                    /* no extra flags, no OS specified */
kusano 7d535a
    39, 0, 'a', 'p', 35, 0      /* extra field with "ap" subfield */
kusano 7d535a
                                /* 35 is EXTRA, 39 is EXTRA + 4 */
kusano 7d535a
};
kusano 7d535a
kusano 7d535a
#define HEAD sizeof(log_gzhead)     /* should be 16 */
kusano 7d535a
kusano 7d535a
/* initial gzip extra field content (52 == HEAD + EXTRA + 1) */
kusano 7d535a
local unsigned char log_gzext[] = {
kusano 7d535a
    52, 0, 0, 0, 0, 0, 0, 0,    /* offset of first stored block length */
kusano 7d535a
    52, 0, 0, 0, 0, 0, 0, 0,    /* offset of last stored block length */
kusano 7d535a
    0, 0, 0, 0, 0, 0, 0, 0,     /* compressed data crc and length */
kusano 7d535a
    0, 0, 0, 0, 0, 0, 0, 0,     /* total data crc and length */
kusano 7d535a
    0, 0,                       /* final stored block data length */
kusano 7d535a
    5                           /* op is NO_OP, last bit 8 bits back */
kusano 7d535a
};
kusano 7d535a
kusano 7d535a
#define EXTRA sizeof(log_gzext)     /* should be 35 */
kusano 7d535a
kusano 7d535a
/* initial gzip data and trailer */
kusano 7d535a
local unsigned char log_gzbody[] = {
kusano 7d535a
    1, 0, 0, 0xff, 0xff,        /* empty stored block (last) */
kusano 7d535a
    0, 0, 0, 0,                 /* crc */
kusano 7d535a
    0, 0, 0, 0                  /* uncompressed length */
kusano 7d535a
};
kusano 7d535a
kusano 7d535a
#define BODY sizeof(log_gzbody)
kusano 7d535a
kusano 7d535a
/* Exclusively create foo.lock in order to negotiate exclusive access to the
kusano 7d535a
   foo.* files.  If the modify time of an existing lock file is greater than
kusano 7d535a
   PATIENCE seconds in the past, then consider the lock file to have been
kusano 7d535a
   abandoned, delete it, and try the exclusive create again.  Save the lock
kusano 7d535a
   file modify time for verification of ownership.  Return 0 on success, or -1
kusano 7d535a
   on failure, usually due to an access restriction or invalid path.  Note that
kusano 7d535a
   if stat() or unlink() fails, it may be due to another process noticing the
kusano 7d535a
   abandoned lock file a smidge sooner and deleting it, so those are not
kusano 7d535a
   flagged as an error. */
kusano 7d535a
local int log_lock(struct log *log)
kusano 7d535a
{
kusano 7d535a
    int fd;
kusano 7d535a
    struct stat st;
kusano 7d535a
kusano 7d535a
    strcpy(log->end, ".lock");
kusano 7d535a
    while ((fd = open(log->path, O_CREAT | O_EXCL, 0644)) < 0) {
kusano 7d535a
        if (errno != EEXIST)
kusano 7d535a
            return -1;
kusano 7d535a
        if (stat(log->path, &st) == 0 && time(NULL) - st.st_mtime > PATIENCE) {
kusano 7d535a
            unlink(log->path);
kusano 7d535a
            continue;
kusano 7d535a
        }
kusano 7d535a
        sleep(2);       /* relinquish the CPU for two seconds while waiting */
kusano 7d535a
    }
kusano 7d535a
    close(fd);
kusano 7d535a
    if (stat(log->path, &st) == 0)
kusano 7d535a
        log->lock = st.st_mtime;
kusano 7d535a
    return 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Update the modify time of the lock file to now, in order to prevent another
kusano 7d535a
   task from thinking that the lock is stale.  Save the lock file modify time
kusano 7d535a
   for verification of ownership. */
kusano 7d535a
local void log_touch(struct log *log)
kusano 7d535a
{
kusano 7d535a
    struct stat st;
kusano 7d535a
kusano 7d535a
    strcpy(log->end, ".lock");
kusano 7d535a
    utimes(log->path, NULL);
kusano 7d535a
    if (stat(log->path, &st) == 0)
kusano 7d535a
        log->lock = st.st_mtime;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Check the log file modify time against what is expected.  Return true if
kusano 7d535a
   this is not our lock.  If it is our lock, touch it to keep it. */
kusano 7d535a
local int log_check(struct log *log)
kusano 7d535a
{
kusano 7d535a
    struct stat st;
kusano 7d535a
kusano 7d535a
    strcpy(log->end, ".lock");
kusano 7d535a
    if (stat(log->path, &st) || st.st_mtime != log->lock)
kusano 7d535a
        return 1;
kusano 7d535a
    log_touch(log);
kusano 7d535a
    return 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Unlock a previously acquired lock, but only if it's ours. */
kusano 7d535a
local void log_unlock(struct log *log)
kusano 7d535a
{
kusano 7d535a
    if (log_check(log))
kusano 7d535a
        return;
kusano 7d535a
    strcpy(log->end, ".lock");
kusano 7d535a
    unlink(log->path);
kusano 7d535a
    log->lock = 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Check the gzip header and read in the extra field, filling in the values in
kusano 7d535a
   the log structure.  Return op on success or -1 if the gzip header was not as
kusano 7d535a
   expected.  op is the current operation in progress last written to the extra
kusano 7d535a
   field.  This assumes that the gzip file has already been opened, with the
kusano 7d535a
   file descriptor log->fd. */
kusano 7d535a
local int log_head(struct log *log)
kusano 7d535a
{
kusano 7d535a
    int op;
kusano 7d535a
    unsigned char buf[HEAD + EXTRA];
kusano 7d535a
kusano 7d535a
    if (lseek(log->fd, 0, SEEK_SET) < 0 ||
kusano 7d535a
        read(log->fd, buf, HEAD + EXTRA) != HEAD + EXTRA ||
kusano 7d535a
        memcmp(buf, log_gzhead, HEAD)) {
kusano 7d535a
        return -1;
kusano 7d535a
    }
kusano 7d535a
    log->first = PULL8(buf + HEAD);
kusano 7d535a
    log->last = PULL8(buf + HEAD + 8);
kusano 7d535a
    log->ccrc = PULL4(buf + HEAD + 16);
kusano 7d535a
    log->clen = PULL4(buf + HEAD + 20);
kusano 7d535a
    log->tcrc = PULL4(buf + HEAD + 24);
kusano 7d535a
    log->tlen = PULL4(buf + HEAD + 28);
kusano 7d535a
    log->stored = PULL2(buf + HEAD + 32);
kusano 7d535a
    log->back = 3 + (buf[HEAD + 34] & 7);
kusano 7d535a
    op = (buf[HEAD + 34] >> 3) & 3;
kusano 7d535a
    return op;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Write over the extra field contents, marking the operation as op.  Use fsync
kusano 7d535a
   to assure that the device is written to, and in the requested order.  This
kusano 7d535a
   operation, and only this operation, is assumed to be atomic in order to
kusano 7d535a
   assure that the log is recoverable in the event of an interruption at any
kusano 7d535a
   point in the process.  Return -1 if the write to foo.gz failed. */
kusano 7d535a
local int log_mark(struct log *log, int op)
kusano 7d535a
{
kusano 7d535a
    int ret;
kusano 7d535a
    unsigned char ext[EXTRA];
kusano 7d535a
kusano 7d535a
    PUT8(ext, log->first);
kusano 7d535a
    PUT8(ext + 8, log->last);
kusano 7d535a
    PUT4(ext + 16, log->ccrc);
kusano 7d535a
    PUT4(ext + 20, log->clen);
kusano 7d535a
    PUT4(ext + 24, log->tcrc);
kusano 7d535a
    PUT4(ext + 28, log->tlen);
kusano 7d535a
    PUT2(ext + 32, log->stored);
kusano 7d535a
    ext[34] = log->back - 3 + (op << 3);
kusano 7d535a
    fsync(log->fd);
kusano 7d535a
    ret = lseek(log->fd, HEAD, SEEK_SET) < 0 ||
kusano 7d535a
          write(log->fd, ext, EXTRA) != EXTRA ? -1 : 0;
kusano 7d535a
    fsync(log->fd);
kusano 7d535a
    return ret;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Rewrite the last block header bits and subsequent zero bits to get to a byte
kusano 7d535a
   boundary, setting the last block bit if last is true, and then write the
kusano 7d535a
   remainder of the stored block header (length and one's complement).  Leave
kusano 7d535a
   the file pointer after the end of the last stored block data.  Return -1 if
kusano 7d535a
   there is a read or write failure on the foo.gz file */
kusano 7d535a
local int log_last(struct log *log, int last)
kusano 7d535a
{
kusano 7d535a
    int back, len, mask;
kusano 7d535a
    unsigned char buf[6];
kusano 7d535a
kusano 7d535a
    /* determine the locations of the bytes and bits to modify */
kusano 7d535a
    back = log->last == log->first ? log->back : 8;
kusano 7d535a
    len = back > 8 ? 2 : 1;                 /* bytes back from log->last */
kusano 7d535a
    mask = 0x80 >> ((back - 1) & 7);        /* mask for block last-bit */
kusano 7d535a
kusano 7d535a
    /* get the byte to modify (one or two back) into buf[0] -- don't need to
kusano 7d535a
       read the byte if the last-bit is eight bits back, since in that case
kusano 7d535a
       the entire byte will be modified */
kusano 7d535a
    buf[0] = 0;
kusano 7d535a
    if (back != 8 && (lseek(log->fd, log->last - len, SEEK_SET) < 0 ||
kusano 7d535a
                      read(log->fd, buf, 1) != 1))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* change the last-bit of the last stored block as requested -- note
kusano 7d535a
       that all bits above the last-bit are set to zero, per the type bits
kusano 7d535a
       of a stored block being 00 and per the convention that the bits to
kusano 7d535a
       bring the stream to a byte boundary are also zeros */
kusano 7d535a
    buf[1] = 0;
kusano 7d535a
    buf[2 - len] = (*buf & (mask - 1)) + (last ? mask : 0);
kusano 7d535a
kusano 7d535a
    /* write the modified stored block header and lengths, move the file
kusano 7d535a
       pointer to after the last stored block data */
kusano 7d535a
    PUT2(buf + 2, log->stored);
kusano 7d535a
    PUT2(buf + 4, log->stored ^ 0xffff);
kusano 7d535a
    return lseek(log->fd, log->last - len, SEEK_SET) < 0 ||
kusano 7d535a
           write(log->fd, buf + 2 - len, len + 4) != len + 4 ||
kusano 7d535a
           lseek(log->fd, log->stored, SEEK_CUR) < 0 ? -1 : 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Append len bytes from data to the locked and open log file.  len may be zero
kusano 7d535a
   if recovering and no .add file was found.  In that case, the previous state
kusano 7d535a
   of the foo.gz file is restored.  The data is appended uncompressed in
kusano 7d535a
   deflate stored blocks.  Return -1 if there was an error reading or writing
kusano 7d535a
   the foo.gz file. */
kusano 7d535a
local int log_append(struct log *log, unsigned char *data, size_t len)
kusano 7d535a
{
kusano 7d535a
    uint put;
kusano 7d535a
    off_t end;
kusano 7d535a
    unsigned char buf[8];
kusano 7d535a
kusano 7d535a
    /* set the last block last-bit and length, in case recovering an
kusano 7d535a
       interrupted append, then position the file pointer to append to the
kusano 7d535a
       block */
kusano 7d535a
    if (log_last(log, 1))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* append, adding stored blocks and updating the offset of the last stored
kusano 7d535a
       block as needed, and update the total crc and length */
kusano 7d535a
    while (len) {
kusano 7d535a
        /* append as much as we can to the last block */
kusano 7d535a
        put = (MAX_STORE << 10) - log->stored;
kusano 7d535a
        if (put > len)
kusano 7d535a
            put = (uint)len;
kusano 7d535a
        if (put) {
kusano 7d535a
            if (write(log->fd, data, put) != put)
kusano 7d535a
                return -1;
kusano 7d535a
            BAIL(1);
kusano 7d535a
            log->tcrc = crc32(log->tcrc, data, put);
kusano 7d535a
            log->tlen += put;
kusano 7d535a
            log->stored += put;
kusano 7d535a
            data += put;
kusano 7d535a
            len -= put;
kusano 7d535a
        }
kusano 7d535a
kusano 7d535a
        /* if we need to, add a new empty stored block */
kusano 7d535a
        if (len) {
kusano 7d535a
            /* mark current block as not last */
kusano 7d535a
            if (log_last(log, 0))
kusano 7d535a
                return -1;
kusano 7d535a
kusano 7d535a
            /* point to new, empty stored block */
kusano 7d535a
            log->last += 4 + log->stored + 1;
kusano 7d535a
            log->stored = 0;
kusano 7d535a
        }
kusano 7d535a
kusano 7d535a
        /* mark last block as last, update its length */
kusano 7d535a
        if (log_last(log, 1))
kusano 7d535a
            return -1;
kusano 7d535a
        BAIL(2);
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* write the new crc and length trailer, and truncate just in case (could
kusano 7d535a
       be recovering from partial append with a missing foo.add file) */
kusano 7d535a
    PUT4(buf, log->tcrc);
kusano 7d535a
    PUT4(buf + 4, log->tlen);
kusano 7d535a
    if (write(log->fd, buf, 8) != 8 ||
kusano 7d535a
        (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* write the extra field, marking the log file as done, delete .add file */
kusano 7d535a
    if (log_mark(log, NO_OP))
kusano 7d535a
        return -1;
kusano 7d535a
    strcpy(log->end, ".add");
kusano 7d535a
    unlink(log->path);          /* ignore error, since may not exist */
kusano 7d535a
    return 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Replace the foo.dict file with the foo.temp file.  Also delete the foo.add
kusano 7d535a
   file, since the compress operation may have been interrupted before that was
kusano 7d535a
   done.  Returns 1 if memory could not be allocated, or -1 if reading or
kusano 7d535a
   writing foo.gz fails, or if the rename fails for some reason other than
kusano 7d535a
   foo.temp not existing.  foo.temp not existing is a permitted error, since
kusano 7d535a
   the replace operation may have been interrupted after the rename is done,
kusano 7d535a
   but before foo.gz is marked as complete. */
kusano 7d535a
local int log_replace(struct log *log)
kusano 7d535a
{
kusano 7d535a
    int ret;
kusano 7d535a
    char *dest;
kusano 7d535a
kusano 7d535a
    /* delete foo.add file */
kusano 7d535a
    strcpy(log->end, ".add");
kusano 7d535a
    unlink(log->path);         /* ignore error, since may not exist */
kusano 7d535a
    BAIL(3);
kusano 7d535a
kusano 7d535a
    /* rename foo.name to foo.dict, replacing foo.dict if it exists */
kusano 7d535a
    strcpy(log->end, ".dict");
kusano 7d535a
    dest = malloc(strlen(log->path) + 1);
kusano 7d535a
    if (dest == NULL)
kusano 7d535a
        return -2;
kusano 7d535a
    strcpy(dest, log->path);
kusano 7d535a
    strcpy(log->end, ".temp");
kusano 7d535a
    ret = rename(log->path, dest);
kusano 7d535a
    free(dest);
kusano 7d535a
    if (ret && errno != ENOENT)
kusano 7d535a
        return -1;
kusano 7d535a
    BAIL(4);
kusano 7d535a
kusano 7d535a
    /* mark the foo.gz file as done */
kusano 7d535a
    return log_mark(log, NO_OP);
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Compress the len bytes at data and append the compressed data to the
kusano 7d535a
   foo.gz deflate data immediately after the previous compressed data.  This
kusano 7d535a
   overwrites the previous uncompressed data, which was stored in foo.add
kusano 7d535a
   and is the data provided in data[0..len-1].  If this operation is
kusano 7d535a
   interrupted, it picks up at the start of this routine, with the foo.add
kusano 7d535a
   file read in again.  If there is no data to compress (len == 0), then we
kusano 7d535a
   simply terminate the foo.gz file after the previously compressed data,
kusano 7d535a
   appending a final empty stored block and the gzip trailer.  Return -1 if
kusano 7d535a
   reading or writing the log.gz file failed, or -2 if there was a memory
kusano 7d535a
   allocation failure. */
kusano 7d535a
local int log_compress(struct log *log, unsigned char *data, size_t len)
kusano 7d535a
{
kusano 7d535a
    int fd;
kusano 7d535a
    uint got, max;
kusano 7d535a
    ssize_t dict;
kusano 7d535a
    off_t end;
kusano 7d535a
    z_stream strm;
kusano 7d535a
    unsigned char buf[DICT];
kusano 7d535a
kusano 7d535a
    /* compress and append compressed data */
kusano 7d535a
    if (len) {
kusano 7d535a
        /* set up for deflate, allocating memory */
kusano 7d535a
        strm.zalloc = Z_NULL;
kusano 7d535a
        strm.zfree = Z_NULL;
kusano 7d535a
        strm.opaque = Z_NULL;
kusano 7d535a
        if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8,
kusano 7d535a
                         Z_DEFAULT_STRATEGY) != Z_OK)
kusano 7d535a
            return -2;
kusano 7d535a
kusano 7d535a
        /* read in dictionary (last 32K of data that was compressed) */
kusano 7d535a
        strcpy(log->end, ".dict");
kusano 7d535a
        fd = open(log->path, O_RDONLY, 0);
kusano 7d535a
        if (fd >= 0) {
kusano 7d535a
            dict = read(fd, buf, DICT);
kusano 7d535a
            close(fd);
kusano 7d535a
            if (dict < 0) {
kusano 7d535a
                deflateEnd(&strm);
kusano 7d535a
                return -1;
kusano 7d535a
            }
kusano 7d535a
            if (dict)
kusano 7d535a
                deflateSetDictionary(&strm, buf, (uint)dict);
kusano 7d535a
        }
kusano 7d535a
        log_touch(log);
kusano 7d535a
kusano 7d535a
        /* prime deflate with last bits of previous block, position write
kusano 7d535a
           pointer to write those bits and overwrite what follows */
kusano 7d535a
        if (lseek(log->fd, log->first - (log->back > 8 ? 2 : 1),
kusano 7d535a
                SEEK_SET) < 0 ||
kusano 7d535a
            read(log->fd, buf, 1) != 1 || lseek(log->fd, -1, SEEK_CUR) < 0) {
kusano 7d535a
            deflateEnd(&strm);
kusano 7d535a
            return -1;
kusano 7d535a
        }
kusano 7d535a
        deflatePrime(&strm, (8 - log->back) & 7, *buf);
kusano 7d535a
kusano 7d535a
        /* compress, finishing with a partial non-last empty static block */
kusano 7d535a
        strm.next_in = data;
kusano 7d535a
        max = (((uint)0 - 1) >> 1) + 1; /* in case int smaller than size_t */
kusano 7d535a
        do {
kusano 7d535a
            strm.avail_in = len > max ? max : (uint)len;
kusano 7d535a
            len -= strm.avail_in;
kusano 7d535a
            do {
kusano 7d535a
                strm.avail_out = DICT;
kusano 7d535a
                strm.next_out = buf;
kusano 7d535a
                deflate(&strm, len ? Z_NO_FLUSH : Z_PARTIAL_FLUSH);
kusano 7d535a
                got = DICT - strm.avail_out;
kusano 7d535a
                if (got && write(log->fd, buf, got) != got) {
kusano 7d535a
                    deflateEnd(&strm);
kusano 7d535a
                    return -1;
kusano 7d535a
                }
kusano 7d535a
                log_touch(log);
kusano 7d535a
            } while (strm.avail_out == 0);
kusano 7d535a
        } while (len);
kusano 7d535a
        deflateEnd(&strm);
kusano 7d535a
        BAIL(5);
kusano 7d535a
kusano 7d535a
        /* find start of empty static block -- scanning backwards the first one
kusano 7d535a
           bit is the second bit of the block, if the last byte is zero, then
kusano 7d535a
           we know the byte before that has a one in the top bit, since an
kusano 7d535a
           empty static block is ten bits long */
kusano 7d535a
        if ((log->first = lseek(log->fd, -1, SEEK_CUR)) < 0 ||
kusano 7d535a
            read(log->fd, buf, 1) != 1)
kusano 7d535a
            return -1;
kusano 7d535a
        log->first++;
kusano 7d535a
        if (*buf) {
kusano 7d535a
            log->back = 1;
kusano 7d535a
            while ((*buf & ((uint)1 << (8 - log->back++))) == 0)
kusano 7d535a
                ;       /* guaranteed to terminate, since *buf != 0 */
kusano 7d535a
        }
kusano 7d535a
        else
kusano 7d535a
            log->back = 10;
kusano 7d535a
kusano 7d535a
        /* update compressed crc and length */
kusano 7d535a
        log->ccrc = log->tcrc;
kusano 7d535a
        log->clen = log->tlen;
kusano 7d535a
    }
kusano 7d535a
    else {
kusano 7d535a
        /* no data to compress -- fix up existing gzip stream */
kusano 7d535a
        log->tcrc = log->ccrc;
kusano 7d535a
        log->tlen = log->clen;
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* complete and truncate gzip stream */
kusano 7d535a
    log->last = log->first;
kusano 7d535a
    log->stored = 0;
kusano 7d535a
    PUT4(buf, log->tcrc);
kusano 7d535a
    PUT4(buf + 4, log->tlen);
kusano 7d535a
    if (log_last(log, 1) || write(log->fd, buf, 8) != 8 ||
kusano 7d535a
        (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end))
kusano 7d535a
        return -1;
kusano 7d535a
    BAIL(6);
kusano 7d535a
kusano 7d535a
    /* mark as being in the replace operation */
kusano 7d535a
    if (log_mark(log, REPLACE_OP))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* execute the replace operation and mark the file as done */
kusano 7d535a
    return log_replace(log);
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* log a repair record to the .repairs file */
kusano 7d535a
local void log_log(struct log *log, int op, char *record)
kusano 7d535a
{
kusano 7d535a
    time_t now;
kusano 7d535a
    FILE *rec;
kusano 7d535a
kusano 7d535a
    now = time(NULL);
kusano 7d535a
    strcpy(log->end, ".repairs");
kusano 7d535a
    rec = fopen(log->path, "a");
kusano 7d535a
    if (rec == NULL)
kusano 7d535a
        return;
kusano 7d535a
    fprintf(rec, "%.24s %s recovery: %s\n", ctime(&now), op == APPEND_OP ?
kusano 7d535a
            "append" : (op == COMPRESS_OP ? "compress" : "replace"), record);
kusano 7d535a
    fclose(rec);
kusano 7d535a
    return;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Recover the interrupted operation op.  First read foo.add for recovering an
kusano 7d535a
   append or compress operation.  Return -1 if there was an error reading or
kusano 7d535a
   writing foo.gz or reading an existing foo.add, or -2 if there was a memory
kusano 7d535a
   allocation failure. */
kusano 7d535a
local int log_recover(struct log *log, int op)
kusano 7d535a
{
kusano 7d535a
    int fd, ret = 0;
kusano 7d535a
    unsigned char *data = NULL;
kusano 7d535a
    size_t len = 0;
kusano 7d535a
    struct stat st;
kusano 7d535a
kusano 7d535a
    /* log recovery */
kusano 7d535a
    log_log(log, op, "start");
kusano 7d535a
kusano 7d535a
    /* load foo.add file if expected and present */
kusano 7d535a
    if (op == APPEND_OP || op == COMPRESS_OP) {
kusano 7d535a
        strcpy(log->end, ".add");
kusano 7d535a
        if (stat(log->path, &st) == 0 && st.st_size) {
kusano 7d535a
            len = (size_t)(st.st_size);
kusano 7d535a
            if (len != st.st_size || (data = malloc(st.st_size)) == NULL) {
kusano 7d535a
                log_log(log, op, "allocation failure");
kusano 7d535a
                return -2;
kusano 7d535a
            }
kusano 7d535a
            if ((fd = open(log->path, O_RDONLY, 0)) < 0) {
kusano 7d535a
                log_log(log, op, ".add file read failure");
kusano 7d535a
                return -1;
kusano 7d535a
            }
kusano 7d535a
            ret = read(fd, data, len) != len;
kusano 7d535a
            close(fd);
kusano 7d535a
            if (ret) {
kusano 7d535a
                log_log(log, op, ".add file read failure");
kusano 7d535a
                return -1;
kusano 7d535a
            }
kusano 7d535a
            log_log(log, op, "loaded .add file");
kusano 7d535a
        }
kusano 7d535a
        else
kusano 7d535a
            log_log(log, op, "missing .add file!");
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* recover the interrupted operation */
kusano 7d535a
    switch (op) {
kusano 7d535a
    case APPEND_OP:
kusano 7d535a
        ret = log_append(log, data, len);
kusano 7d535a
        break;
kusano 7d535a
    case COMPRESS_OP:
kusano 7d535a
        ret = log_compress(log, data, len);
kusano 7d535a
        break;
kusano 7d535a
    case REPLACE_OP:
kusano 7d535a
        ret = log_replace(log);
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* log status */
kusano 7d535a
    log_log(log, op, ret ? "failure" : "complete");
kusano 7d535a
kusano 7d535a
    /* clean up */
kusano 7d535a
    if (data != NULL)
kusano 7d535a
        free(data);
kusano 7d535a
    return ret;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Close the foo.gz file (if open) and release the lock. */
kusano 7d535a
local void log_close(struct log *log)
kusano 7d535a
{
kusano 7d535a
    if (log->fd >= 0)
kusano 7d535a
        close(log->fd);
kusano 7d535a
    log->fd = -1;
kusano 7d535a
    log_unlock(log);
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* Open foo.gz, verify the header, and load the extra field contents, after
kusano 7d535a
   first creating the foo.lock file to gain exclusive access to the foo.*
kusano 7d535a
   files.  If foo.gz does not exist or is empty, then write the initial header,
kusano 7d535a
   extra, and body content of an empty foo.gz log file.  If there is an error
kusano 7d535a
   creating the lock file due to access restrictions, or an error reading or
kusano 7d535a
   writing the foo.gz file, or if the foo.gz file is not a proper log file for
kusano 7d535a
   this object (e.g. not a gzip file or does not contain the expected extra
kusano 7d535a
   field), then return true.  If there is an error, the lock is released.
kusano 7d535a
   Otherwise, the lock is left in place. */
kusano 7d535a
local int log_open(struct log *log)
kusano 7d535a
{
kusano 7d535a
    int op;
kusano 7d535a
kusano 7d535a
    /* release open file resource if left over -- can occur if lock lost
kusano 7d535a
       between gzlog_open() and gzlog_write() */
kusano 7d535a
    if (log->fd >= 0)
kusano 7d535a
        close(log->fd);
kusano 7d535a
    log->fd = -1;
kusano 7d535a
kusano 7d535a
    /* negotiate exclusive access */
kusano 7d535a
    if (log_lock(log) < 0)
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* open the log file, foo.gz */
kusano 7d535a
    strcpy(log->end, ".gz");
kusano 7d535a
    log->fd = open(log->path, O_RDWR | O_CREAT, 0644);
kusano 7d535a
    if (log->fd < 0) {
kusano 7d535a
        log_close(log);
kusano 7d535a
        return -1;
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* if new, initialize foo.gz with an empty log, delete old dictionary */
kusano 7d535a
    if (lseek(log->fd, 0, SEEK_END) == 0) {
kusano 7d535a
        if (write(log->fd, log_gzhead, HEAD) != HEAD ||
kusano 7d535a
            write(log->fd, log_gzext, EXTRA) != EXTRA ||
kusano 7d535a
            write(log->fd, log_gzbody, BODY) != BODY) {
kusano 7d535a
            log_close(log);
kusano 7d535a
            return -1;
kusano 7d535a
        }
kusano 7d535a
        strcpy(log->end, ".dict");
kusano 7d535a
        unlink(log->path);
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* verify log file and load extra field information */
kusano 7d535a
    if ((op = log_head(log)) < 0) {
kusano 7d535a
        log_close(log);
kusano 7d535a
        return -1;
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* check for interrupted process and if so, recover */
kusano 7d535a
    if (op != NO_OP && log_recover(log, op)) {
kusano 7d535a
        log_close(log);
kusano 7d535a
        return -1;
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* touch the lock file to prevent another process from grabbing it */
kusano 7d535a
    log_touch(log);
kusano 7d535a
    return 0;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* See gzlog.h for the description of the external methods below */
kusano 7d535a
gzlog *gzlog_open(char *path)
kusano 7d535a
{
kusano 7d535a
    size_t n;
kusano 7d535a
    struct log *log;
kusano 7d535a
kusano 7d535a
    /* check arguments */
kusano 7d535a
    if (path == NULL || *path == 0)
kusano 7d535a
        return NULL;
kusano 7d535a
kusano 7d535a
    /* allocate and initialize log structure */
kusano 7d535a
    log = malloc(sizeof(struct log));
kusano 7d535a
    if (log == NULL)
kusano 7d535a
        return NULL;
kusano 7d535a
    strcpy(log->id, LOGID);
kusano 7d535a
    log->fd = -1;
kusano 7d535a
kusano 7d535a
    /* save path and end of path for name construction */
kusano 7d535a
    n = strlen(path);
kusano 7d535a
    log->path = malloc(n + 9);              /* allow for ".repairs" */
kusano 7d535a
    if (log->path == NULL) {
kusano 7d535a
        free(log);
kusano 7d535a
        return NULL;
kusano 7d535a
    }
kusano 7d535a
    strcpy(log->path, path);
kusano 7d535a
    log->end = log->path + n;
kusano 7d535a
kusano 7d535a
    /* gain exclusive access and verify log file -- may perform a
kusano 7d535a
       recovery operation if needed */
kusano 7d535a
    if (log_open(log)) {
kusano 7d535a
        free(log->path);
kusano 7d535a
        free(log);
kusano 7d535a
        return NULL;
kusano 7d535a
    }
kusano 7d535a
kusano 7d535a
    /* return pointer to log structure */
kusano 7d535a
    return log;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* gzlog_compress() return values:
kusano 7d535a
    0: all good
kusano 7d535a
   -1: file i/o error (usually access issue)
kusano 7d535a
   -2: memory allocation failure
kusano 7d535a
   -3: invalid log pointer argument */
kusano 7d535a
int gzlog_compress(gzlog *logd)
kusano 7d535a
{
kusano 7d535a
    int fd, ret;
kusano 7d535a
    uint block;
kusano 7d535a
    size_t len, next;
kusano 7d535a
    unsigned char *data, buf[5];
kusano 7d535a
    struct log *log = logd;
kusano 7d535a
kusano 7d535a
    /* check arguments */
kusano 7d535a
    if (log == NULL || strcmp(log->id, LOGID) || len < 0)
kusano 7d535a
        return -3;
kusano 7d535a
kusano 7d535a
    /* see if we lost the lock -- if so get it again and reload the extra
kusano 7d535a
       field information (it probably changed), recover last operation if
kusano 7d535a
       necessary */
kusano 7d535a
    if (log_check(log) && log_open(log))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* create space for uncompressed data */
kusano 7d535a
    len = ((size_t)(log->last - log->first) & ~(((size_t)1 << 10) - 1)) +
kusano 7d535a
          log->stored;
kusano 7d535a
    if ((data = malloc(len)) == NULL)
kusano 7d535a
        return -2;
kusano 7d535a
kusano 7d535a
    /* do statement here is just a cheap trick for error handling */
kusano 7d535a
    do {
kusano 7d535a
        /* read in the uncompressed data */
kusano 7d535a
        if (lseek(log->fd, log->first - 1, SEEK_SET) < 0)
kusano 7d535a
            break;
kusano 7d535a
        next = 0;
kusano 7d535a
        while (next < len) {
kusano 7d535a
            if (read(log->fd, buf, 5) != 5)
kusano 7d535a
                break;
kusano 7d535a
            block = PULL2(buf + 1);
kusano 7d535a
            if (next + block > len ||
kusano 7d535a
                read(log->fd, (char *)data + next, block) != block)
kusano 7d535a
                break;
kusano 7d535a
            next += block;
kusano 7d535a
        }
kusano 7d535a
        if (lseek(log->fd, 0, SEEK_CUR) != log->last + 4 + log->stored)
kusano 7d535a
            break;
kusano 7d535a
        log_touch(log);
kusano 7d535a
kusano 7d535a
        /* write the uncompressed data to the .add file */
kusano 7d535a
        strcpy(log->end, ".add");
kusano 7d535a
        fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
kusano 7d535a
        if (fd < 0)
kusano 7d535a
            break;
kusano 7d535a
        ret = write(fd, data, len) != len;
kusano 7d535a
        if (ret | close(fd))
kusano 7d535a
            break;
kusano 7d535a
        log_touch(log);
kusano 7d535a
kusano 7d535a
        /* write the dictionary for the next compress to the .temp file */
kusano 7d535a
        strcpy(log->end, ".temp");
kusano 7d535a
        fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
kusano 7d535a
        if (fd < 0)
kusano 7d535a
            break;
kusano 7d535a
        next = DICT > len ? len : DICT;
kusano 7d535a
        ret = write(fd, (char *)data + len - next, next) != next;
kusano 7d535a
        if (ret | close(fd))
kusano 7d535a
            break;
kusano 7d535a
        log_touch(log);
kusano 7d535a
kusano 7d535a
        /* roll back to compressed data, mark the compress in progress */
kusano 7d535a
        log->last = log->first;
kusano 7d535a
        log->stored = 0;
kusano 7d535a
        if (log_mark(log, COMPRESS_OP))
kusano 7d535a
            break;
kusano 7d535a
        BAIL(7);
kusano 7d535a
kusano 7d535a
        /* compress and append the data (clears mark) */
kusano 7d535a
        ret = log_compress(log, data, len);
kusano 7d535a
        free(data);
kusano 7d535a
        return ret;
kusano 7d535a
    } while (0);
kusano 7d535a
kusano 7d535a
    /* broke out of do above on i/o error */
kusano 7d535a
    free(data);
kusano 7d535a
    return -1;
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* gzlog_write() return values:
kusano 7d535a
    0: all good
kusano 7d535a
   -1: file i/o error (usually access issue)
kusano 7d535a
   -2: memory allocation failure
kusano 7d535a
   -3: invalid log pointer argument */
kusano 7d535a
int gzlog_write(gzlog *logd, void *data, size_t len)
kusano 7d535a
{
kusano 7d535a
    int fd, ret;
kusano 7d535a
    struct log *log = logd;
kusano 7d535a
kusano 7d535a
    /* check arguments */
kusano 7d535a
    if (log == NULL || strcmp(log->id, LOGID) || len < 0)
kusano 7d535a
        return -3;
kusano 7d535a
    if (data == NULL || len == 0)
kusano 7d535a
        return 0;
kusano 7d535a
kusano 7d535a
    /* see if we lost the lock -- if so get it again and reload the extra
kusano 7d535a
       field information (it probably changed), recover last operation if
kusano 7d535a
       necessary */
kusano 7d535a
    if (log_check(log) && log_open(log))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* create and write .add file */
kusano 7d535a
    strcpy(log->end, ".add");
kusano 7d535a
    fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
kusano 7d535a
    if (fd < 0)
kusano 7d535a
        return -1;
kusano 7d535a
    ret = write(fd, data, len) != len;
kusano 7d535a
    if (ret | close(fd))
kusano 7d535a
        return -1;
kusano 7d535a
    log_touch(log);
kusano 7d535a
kusano 7d535a
    /* mark log file with append in progress */
kusano 7d535a
    if (log_mark(log, APPEND_OP))
kusano 7d535a
        return -1;
kusano 7d535a
    BAIL(8);
kusano 7d535a
kusano 7d535a
    /* append data (clears mark) */
kusano 7d535a
    if (log_append(log, data, len))
kusano 7d535a
        return -1;
kusano 7d535a
kusano 7d535a
    /* check to see if it's time to compress -- if not, then done */
kusano 7d535a
    if (((log->last - log->first) >> 10) + (log->stored >> 10) < TRIGGER)
kusano 7d535a
        return 0;
kusano 7d535a
kusano 7d535a
    /* time to compress */
kusano 7d535a
    return gzlog_compress(log);
kusano 7d535a
}
kusano 7d535a
kusano 7d535a
/* gzlog_close() return values:
kusano 7d535a
    0: ok
kusano 7d535a
   -3: invalid log pointer argument */
kusano 7d535a
int gzlog_close(gzlog *logd)
kusano 7d535a
{
kusano 7d535a
    struct log *log = logd;
kusano 7d535a
kusano 7d535a
    /* check arguments */
kusano 7d535a
    if (log == NULL || strcmp(log->id, LOGID))
kusano 7d535a
        return -3;
kusano 7d535a
kusano 7d535a
    /* close the log file and release the lock */
kusano 7d535a
    log_close(log);
kusano 7d535a
kusano 7d535a
    /* free structure and return */
kusano 7d535a
    if (log->path != NULL)
kusano 7d535a
        free(log->path);
kusano 7d535a
    strcpy(log->id, "bad");
kusano 7d535a
    free(log);
kusano 7d535a
    return 0;
kusano 7d535a
}