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