/* This module provides log output for the http server.
 *
 * The output may be routed to any of three files:
 *     Error log
 *     Access log
 *     Trace log
 *
 * This module provides output to an application log file.  It formats
 * data via SYS$FAO to facilitate putting timestamps in the control strings.
 *
 *  int tlog_init ( char *log_file_name );
 *  int tlog_initlog ( int level, char * fname );	! type 1 
 *  int tlog_putlog ( int level, char *ctlstr, ... );
 *  int tlog_flush;
 *  int tlog_reopen ( level );
 *  int tlog_rundown();
 *
 * Author:	David Jones
 * Revised:	27-AUG-1994		Added tlog_reopen().
 * Revised:      3-NOV-1994		Added tlog_rundown().
 * Revised:	3-MAR-1995		Use pthreads timing for flushing, add
 *					structure for daily rollover of files.
 * Revised:	20-MAY-1995		Update for OSF.
 * Revised:	5-SEP-1995		Bug fix, set trc_valid in tlog_initlog
 * Revised:	18-OCT-1995		Support multiple access logs.
 * Revised:	10-NOV-1995		Do selective flush of access logs.
 * Revised:	 1-MAR-1996		Cleanup status check for V7 decthreads
 * Revised:	6-JAN-1996		Fix for DEC C 5.5 eliminating fileno
 *					macro.
 * Revised:	11-MAR-1997		Avoid multiple opens of same access log.
 * Revised:	9-DEC-1998		Avoid stack_reports if level not set.
 * Revsied:	22-APR-1999		Tweak for Digital Unix.
 * Revsied:	29-APR-1999		Add exception handler for clean
 *					rundown.
 * Revised:	29-SEP-1999		Enusure shr=put always used in
 *					combination with ctx=rec.
 * Revised:	11-JAN-2000		Support object naming.
 * Revised:	16-JAN-2000		Major rework.  Add double-buffering.
 * Revised:	27-JUL-2000		Centralize fopen calls and add rat=cr 
 *					and fop=dfw to opens.
 *					Force flush after 40 buffers.
 * Revised:	16-JAN-2002		Modify tlog_reopen to not create a
 *					new version of access log(s) if newer
 *					version already exists.
 * Revised:	26-FEB-2002		Initialize tlog_putlog_cb pointer.
 */
#ifndef VMS
/* fixup for newer va_list def */
#define _a0 a0
#define _offset offset
#endif
#include <stdio.h>
#include "pthread_1c_np.h"
#include <stdarg.h>
#include <stdlib.h>
#ifdef VMS
#include <stat.h>
#include <descrip.h>
#include <ssdef.h>
#include <devdef.h>
#include <dvidef.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#define SYS$FAOL SYS_FAOL
struct dsc$descriptor {
    short int dsc$w_length;
    unsigned char dsc$b_dtype, dsc$b_class;
    void *dsc$a_pointer;
};
#define DSC$K_DTYPE_T 0
#define DSC$K_CLASS_S 0
#endif
#include <string.h>
#include <errno.h>
#include <time.h>
/*
 * Define buffer structure for buffering data to be sent to file(s).  Writes
 * that tlog_putlog used to do to the log files are appended to the current
 * buffer list instead and written later by the flusher thread.  There are
 * 2 buffers, when one buffer needs flushed the other is made the 'current'
 * buffer so subsequent writers don't need to wait.
 *
 * A buffer is a list of variable-sized 'records' where each record represents
 * an deferred fwrite call to an error or access file.  Each record has
 * a header that indicates the file number (trace level) and size to write
 * and is followed by the data to be written and enough pad bytes to ensure
 * the next record begins on an 8-byte boundary (header size).
 *
 * Global variable	guard mutex
 *   buffer_a/b				statically allocated buffers.
 *   current_buffer    log_append	Points to 'current' buffer to write to.
 *   flusher_state     log_append	Written to only by flusher thread.
 *					read by tlog_putlog()
 *   flushing_buffer   log_flush	Points to buffer needing/being flushed.
 */
#define ELEMENTS_PER_BUFFER 2048		/*16K */
#define MAX_PUTLOG_WRITE 1000

union buffer_elem {
    struct rec_hdr {
        int level;				/* file number */
        int size;				/* data bytes */
    } hdr;
    char text[sizeof(struct rec_hdr)];
};
struct log_buffer {
    int used;				/* last buffer element # allocated */
    int last_header;			/* element # of last header */
    char *reserved;			/*  */
    int sync_after_flush;
    union buffer_elem a[ELEMENTS_PER_BUFFER];
};

static struct log_buffer *current_buffer, buffer_a, buffer_b;
static struct log_buffer *flushing_buffer;
/*
 * flushes_since sync_counts the number of buffer switches made without
 * sync_after_flush being set.  If flushes_since_sync reaches the sync skip
 * limit, force a sync to disk.
 */
static int flushes_since_sync;
#define SYNC_SKIP_LIMIT 40

static FILE *log_file;			/* error messages (type 0) */
static FILE *acc_deffile[] = { (FILE *) 0 };
static FILE **acc_file = acc_deffile;	/* access log (type -1) */
static FILE *trc_file;			/* trace file */
static int log_valid = 0;
static int log_buffered = 0;		/* buffer log file */
static int trc_buffered = 0;
static int acc_alloc = 0;		/* size of allocated acc_file arrays */
static int acc_maxvalid = 0;		/* size of acc_valid array -1 */
static int acc_defvalid[] = {0};
static int acc_defdirty[] = {0};
static int *acc_valid = acc_defvalid;
static int *acc_dirty = acc_defdirty;
static int trc_valid = 0;
static int trace_level = 0;
static int stack_trace_level = 0;
static int (*stack_report)();		/* callback */
static struct dsc$descriptor control;	/* control string */
static struct dsc$descriptor output;	/* control string */
static char log_file_spec[256];
static char **acc_file_spec;
static char acc_deffilespec[256];
static char trc_file_spec[256];
static pthread_mutex_t log_append;	/* serialize access to current_buffer*/
static pthread_mutex_t log_flush;	/* serialize access to flushing_buffer*/
static pthread_cond_t flusher_wake;
static pthread_cond_t flush_done;
static pthread_cond_t new_current_buffer;
static pthread_t flusher_id;		/* thread id of flusher thread */
static int flusher_state;		/* guarded by log_append mutex */
#define FLUSHER_ABSENT -1
#define FLUSHER_IDLE 0
#define FLUSHER_COLLECTING 1
#define FLUSHER_WRITING 2
int http_log_level;
#ifdef DEBUG
#define FLUSH_INTERVAL 20
static int dbg_flag = 1;
#else
#define FLUSH_INTERVAL 60
static int dbg_flag = 0;
#endif
#define DBG_PRINTF if (dbg_flag==0); else printf
#ifndef SET_MUTEX_NAME
#define SET_MUTEX_NAME(mutex,name) 
#define SET_COND_NAME(cond,name)
#define SET_THREAD_NAME(thread,name)
#endif
/*
 * Work around problems with fsync() not being ANSI standard 
 */
#ifdef __DECC
#ifdef VMS
#ifndef fileno
#ifdef fileno_macro_fixup
#define fileno(stream) ((*(stream))->_file)
#else
#define fileno decc$fileno
int decc$fileno(FILE *fp);
#endif
#endif
#ifndef fsync
#define fsync decc$fsync
int decc$fsync(int fd);
#endif
#endif
#else	/* __DECC */
int fsync();
#endif
static int null_report() { return -1; }
/*
 * declare global callback function so we can initialize it.
 */
int (*tlog_putlog_cb) ( int, char *, ... );
int tlog_putlog ( int, char *, ... );		/* forward reference */
/***************************************************************************/
/* Common routine for file open/creation.  On VMS include extensions in
 * fopen() call.  The is_acc argument is set to 1 if the file is to have
 * the properties of an access log file and 0 if the file is an error/trace
 * log file.
 */
static FILE *extended_fopen ( char *fname, char *mode, int is_acc )
{
    FILE *fptr;
    fptr = fopen ( fname, mode
#ifdef VMS
	, 			/* Argument separator  */
#ifdef TLOG_DNA_ARG
	TLOG_DNA_ARG,		/* Default file name (Crashes some C RTLs) */
#endif
	"alq=100",		/* allocation quantity (initial file size ) */
	is_acc ? "deq=1000"     /* Default ext. quant., access.log file */
	       : "deq=600",	/*   "      "     "   , trace/error log file */
	"rfm=var",		/* record format */
	"rat=cr",		/* Carriage return carriage control */
	"ctx=rec",		/* Use record context */
	is_acc ? "mrs=900"	/* Max. record size, access.log file */
	       : "mrs=1200",	/*  "      "     " , error/trace file */
	"shr=upd",		/* File share mode: update */
	"fop=dfw"		/* File options: deferred write */
#endif
       );
    return fptr;
}
/****************************************************************************/
/* Low-level routines for manipulating the buffer headers:
 *    alloc_rec	 - Append record header to buffer and return address to
 *                 receive record data, returning null if buffer full.
 *		   If destination file matches that of previous record,
 *		   extend it rather than add new record header.
 *
 *    commit_rec - Adjust record header in buffer to reflect actual bytes
 *		   added and update buffer's 'used' field to point to
 *		   next allocation point.
 * 
 * We assume caller has acquired the necessary mutexes.
 */
static char *alloc_rec ( int level, int size, struct log_buffer *buf )
{
    int alloc, i, off;
    /*
     * Compute number of buffer element required to hold text and header,
     * then see if it will fit.
     */
    alloc = (size+sizeof(union buffer_elem)-1)/sizeof(union buffer_elem);
    alloc++;						/* include header */
    if ( alloc+buf->used > ELEMENTS_PER_BUFFER ) return (char *) 0;  /* no room */
    /*
     * We have room, see if previous record added was to same file
     * and consolidate.
     */
    i = buf->used;
    if ( (i > 0) && ((buf->a[buf->last_header].hdr.size == 0) ||
		(buf->a[buf->last_header].hdr.level == level)) ) {
	/*
	 * Allocate space at end of current record because it is null or
	 * level is the same.
	 */
	i = buf->last_header;
	buf->a[i].hdr.level = level;
	off = buf->a[i].hdr.size;
	buf->reserved = &buf->a[i+1].text[off];
    } else {
	/*
	 * new record.
	 */
	buf->last_header = i;
        buf->a[i].hdr.level = level;
        buf->a[i].hdr.size = 0;
	buf->reserved = buf->a[i+1].text;
    }
    return buf->reserved;
}
static int commit_rec ( int size, struct log_buffer *buf )
{
    int i, alloc;
    /*
     * Update the last record size.
     */
    i = buf->last_header;
    buf->a[i].hdr.size += size;
    /*
     * Update the used counter in the buffer header based on the
     * final size of the last record.
     */
    alloc = (buf->a[i].hdr.size+sizeof(union buffer_elem)-1) /
		sizeof(union buffer_elem);
    buf->used = i + alloc + 1;
    return 1;
}

/****************************************************************************/
/* High-level routines for double-buffering logfile data:
 *    reserve_buffer_space - Lock current_buffer and allocate data region
 *			     within that buffer, initiating flush and swapping
 *			     buffers if needed.
 *
 *    commit_buffer_space  - Adjust current_buffer to reflect actual bytes
 *			     written and unlock.
 *
 */
static char *reserve_buffer_space ( int level, int limit )
{
    char *reserved_buf;
    /*
     * Take out log_apend mutex, which we don't release until commit.
     */
    pthread_mutex_lock ( &log_append );
    /*
     * Wait for current buffer to become available, assume caller locked
     * log_append mutex.
     */
    while ( current_buffer == (struct log_buffer *) 0 ) {
	/* Another writer has handed off the buffer to the flusher thread. */
	pthread_cond_wait ( &new_current_buffer, &log_append );
    }
    /*
     * Add new record to current buffer if possible.
     */
    for ( reserved_buf = alloc_rec ( level, limit, current_buffer );
	  reserved_buf == (char *) 0;
	  reserved_buf = alloc_rec ( level, limit, current_buffer ) ) {
        /*
         * Not enough room in buffer, we need to make the current buffer the
         * buffer being flushed and the other buffer the new current buffer.
	 * Kick flusher again so it sees urgent need for new buffer
	 * (current_buffer null) and wait for it to finish.
	 *
	 * To prevent a busy server from never flushing to disk, force every
	 * 40th buffer started to have the sync_after_flush flag set initially
	 * (normally only set by flusher thread timeout).
         */
	struct log_buffer *buf;
	static int skip_sync_count = 0;	  /* protected by log_append mutex */

	buf = current_buffer;
	current_buffer = (struct log_buffer *) 0;
	flushes_since_sync++;
	if ( flushes_since_sync >= SYNC_SKIP_LIMIT ) {
	    flushes_since_sync = 0;
	    buf->sync_after_flush = 1;		/* force flush */
	} else buf->sync_after_flush = 0;
	pthread_mutex_unlock ( &log_append );

	pthread_mutex_lock ( &log_flush );
	if ( flushing_buffer ) {
	    /*
	     * Flush in progress on the other buffer, wait on it to finish.
	     */
	    while ( flushing_buffer ) {
		pthread_cond_wait ( &flush_done, &log_flush );
	    }
	}
	pthread_cond_signal ( &flusher_wake );
	flushing_buffer = buf;
	pthread_mutex_unlock ( &log_flush );

	pthread_mutex_lock ( &log_append );
        /*
         * Make new current buffer and notify any potential waiters.
         */
	if ( buf == &buffer_a ) {
	    current_buffer = &buffer_b;
	} else {
	    current_buffer = &buffer_a;
	}
	pthread_cond_broadcast ( &new_current_buffer );
    }
    return reserved_buf;
}
static void commit_buffer_space ( int level, int size )
{

    /*
     * Handle non-buffered case.
     */
    if ( level >= 0 && 
	((log_valid && !log_buffered) || (trc_valid && !trc_buffered)) ) {
	int status;
	char *reserved_buf;
	reserved_buf = current_buffer->reserved;
	LOCK_C_RTL
	status = log_valid && !log_buffered && (0==level) ? 
		fwrite ( reserved_buf, size, 1, log_file ) : 1;
	if ( status != 1 ) {
	    fprintf(stderr, "Error writing to error log file\n");
	}
	status = ( trc_valid && !trc_buffered && (trace_level >= level)) ?
		fwrite ( reserved_buf, size, 1, trc_file ) : 1;
	if ( status != 1 ) {
	    fprintf(stderr, "Error writing to error/trace file\n");
	}
	UNLOCK_C_RTL
	/*
	 * Return if we are done.
	 */
	if ((log_valid && log_buffered) || (trc_valid && trc_buffered));
	else {
	    size = 0;
	}
    }
    /*
     * update the current buffer and release it.
     */
    commit_rec ( size, current_buffer );
    pthread_mutex_unlock ( &log_append );
}
/****************************************************************************/
/* Define start routine for thread that flushes log files to disk peiodically
 * as well as takes action every midnight.
 *
 * Logic:
 *    Sleep, till new_day.
 *    if no buffer to flush:
 *         sleep 1 minute or until buffer to flush, check for new day.
 *	   if no buffer to flush:
 *		switch current buffer, set old current buffer to buffer to flush
 *
 *    if buffer to flush:
 *	   flush current buffer.
 *    if new day:
 *	   update midnight.
 *    repeat.
 *
 */
static int tlog_new_day(int, struct timespec *);
static void flush_buffer ( struct log_buffer * );
static int tlog_flusher ( char * fname )
{
    int status, new_day, i, is_idle;
    struct timespec delta, abstime, midnight;
    /*
     * Any time this routine executes, we will have the log_flush mutex.
     * (mutex is relesed while waiting). Make initial call to new_day
     * routine to get absolute time of first midnight.
     *
     * Set up TRY block to ensure mutex released should rundown function
     * cancel us.
     */
    pthread_mutex_lock ( &log_flush );
    TRY {
    SET_THREAD_NAME(pthread_self(),"OSU Log Flusher")
    new_day = 0;
    is_idle = 1;
    tlog_new_day ( new_day, &midnight );
    /*
     * Main loop,
     */
    for ( ; ; ) {
        /*
         *  Wait for someone (anyone, even spurious) to signal us, skip the
	 * wait if current_buffer non-empty.
         */
	if ( is_idle ) {
	    status = pthread_cond_timedwait ( &flusher_wake, &log_flush, &midnight);
	}
	pthread_mutex_lock ( &log_append );
	flusher_state = FLUSHER_COLLECTING;
	pthread_mutex_unlock ( &log_append );
	DBG_PRINTF("flusher %s, flushing: %x, (%s collect)\n", 
		is_idle ? "awoke from idle" : "skipped idle sleep",
		flushing_buffer, flushing_buffer ? "won't" : "will" );
	if ( !flushing_buffer ) {
	    /*
	     * Wait for up to 1 minute.
	     */
	    delta.tv_sec = FLUSH_INTERVAL;
	    delta.tv_nsec = 0;
	    if ( 0 == pthread_get_expiration_np ( &delta, &abstime ) ) {
	        /* Don't wait past midnight */
	        if ( abstime.tv_sec >= midnight.tv_sec ) {
		    abstime = midnight;
		    new_day = 1;
	        }
	        while ( !flushing_buffer ) {
		    if ( 0 != pthread_cond_timedwait ( &flusher_wake,
			&log_flush, &abstime ) ) break; /* timeout */
	        }
	    }
	    DBG_PRINTF("flusher collection period done, flushing: %x\n", 
		flushing_buffer );
	    if ( !flushing_buffer ) {
		/*
		 * The minute expired, swap buffer.  Tricky because we
		 * need log_append mutex and we already hold log_flush
		 * mutex.  Make sure that any other threads that lock both
		 * lock log_flush first.
		 */
		struct log_buffer *old_current, *new_flushing;
		flushing_buffer = &current_buffer[1];  /* block update */
		pthread_mutex_lock ( &log_append );
		flusher_state = FLUSHER_WRITING;
		if ( !current_buffer ) {
		    /*
		     * A writer is already trying to give us a buffer to
		     * flush but we caught him just before he could lock
		     * the log_flush mutex.  Stall again, we shouldn't
		     * have to wait long.
		     */
		    DBG_PRINTF("flusher collision with writer, re-stall\n");
		    while ( !flushing_buffer ) {
			pthread_cond_wait ( &flusher_wake, &log_flush );
		    }
		} else {
		    /*
		     * Normal swap.
		     */
		    DBG_PRINTF("flusher swapping current_buffer: %x\n", 
			current_buffer);
		    flushing_buffer = current_buffer;
		    current_buffer = (current_buffer == &buffer_a) ?
			&buffer_b : &buffer_a;
		    flushes_since_sync = 0;
		    flushing_buffer->sync_after_flush = 1;
		}
		pthread_mutex_unlock ( &log_append );
	    }
	}
	if ( flushing_buffer ) {
	    DBG_PRINTF("Flusher flushing %x, used=%d, sync_flag: %d\n", 
		flushing_buffer, flushing_buffer->used, 
		flushing_buffer->sync_after_flush );
	    flush_buffer ( flushing_buffer );
	    flushing_buffer = (struct log_buffer *) 0;
	    pthread_cond_broadcast ( &flush_done );
	}
	if ( new_day ) {
	    /*
	     * Mark flush state as 'new day' and call routine to do
	     * start-of-day process.  This routine must update midnight.
	     */
	    tlog_new_day ( new_day, &midnight );
	    new_day = 0;
	}
	pthread_mutex_lock ( &log_append );
	if ( current_buffer && (current_buffer->used > 1) ) {
	    is_idle = 0;
	} else {
	    flusher_state = FLUSHER_IDLE;
	    is_idle = 1;
	}
	pthread_mutex_unlock ( &log_append );
    }
    /*
     * Code that will always execute.
     */
    }
    FINALLY {
	pthread_mutex_unlock ( &log_flush );
	pthread_mutex_lock ( &log_append );
	flusher_state = FLUSHER_ABSENT;
	pthread_mutex_unlock ( &log_append );
    }
    ENDTRY
    return 0;		/* can't really get here */
}
/*
 * Scan specified buffer and write out the data to the various files.
 */
static void flush_buffer ( struct log_buffer *buf )
{
    int i, alloc, size, level, status, used;
    used = buf->used;
    buf->used = 0;		/* reset the buffer */
    buf->last_header = 0;
    LOCK_C_RTL
    for ( i = 0; i < used; i += alloc ) {
	/*
	 * Compute allocation.
	 */
	level = buf->a[i].hdr.level;
	size = buf->a[i].hdr.size;
        alloc = (size+sizeof(union buffer_elem)-1) / sizeof(union buffer_elem);
        alloc++;
	if ( alloc == 1 ) continue;	/* empty record */
	/*
         * Write data to the indicated file.
	 */
	if ( level < 0 ) {
	    status = fwrite ( buf->a[i+1].text, size, 1, acc_file[-1-level] );
	    if ( status != 1 ) {
		fprintf(stderr,"Error writing to access log (l=%d)\n", size );
	    }
	    acc_dirty[-1-level] = 1;	/* flag that we need to flush */
	} else {
	    status = log_valid && (0==level) ?
		fwrite(buf->a[i+1].text, size, 1, log_file) : 1;
	    if ( status != 1 ) {
		fprintf(stderr,"Error writing errorlog file (l=%d)\n", size);
	    }
	    status = trc_valid && (trace_level >= level) ?
		fwrite(buf->a[i+1].text, size, 1, trc_file) : 1;
	    if ( status != 1 ) {
		fprintf(stderr,"Error writing to trace file (l=%d)\n", size);
	    }
	}
    }
    UNLOCK_C_RTL
    /*
     * Flush and sync the files.
     */
    if ( buf->sync_after_flush ) {
	for ( i = acc_maxvalid; i >= 0; i-- ) if ( acc_valid[i] < 0 ) {
	    /* propagate dirty flag up to parent. */
	    if ( acc_dirty[i] ) {
		acc_dirty[i] = 0;
		acc_dirty[-1-acc_valid[i]] = 1;
	    }
	}
        if ( log_valid ) {
	        LOCK_C_RTL
	        fflush ( log_file ); status = fsync ( fileno(log_file) );
	        UNLOCK_C_RTL
	}
	DBG_PRINTF("acc_maxvalid: %d\n", acc_maxvalid);
        for ( i = 0; i <= acc_maxvalid; i++ ) {
	    if ( acc_valid[i] && acc_dirty[i] ) {
		LOCK_C_RTL
		fflush ( acc_file[i] );
		status = fsync ( fileno(acc_file[i]) );
		DBG_PRINTF("status of access.file sync: %d\n", status );
		UNLOCK_C_RTL
		acc_dirty[i] = 0;
	    }
	}
        if ( trc_valid ) {
	        LOCK_C_RTL
	        fflush ( trc_file ); status = fsync ( fileno(trc_file) );
	        UNLOCK_C_RTL
	}
    }
}
/*****************************************************************************/
static int tlog_new_day ( int new_day, struct timespec *midnight )
{
    time_t bintim;
    struct tm now;
    struct timespec delta;
    /*
     * If we want to roll over log files, do it here while we still
     * have log_flush mutex.
     */
    pthread_mutex_unlock ( &log_flush );
    /*
     * Reset for next midnight and re-acquire mutex before returning.
     */
    pthread_lock_global_np();		/* localtime() not reentrant */
    time ( &bintim );
    now = *(localtime(&bintim));
    pthread_unlock_global_np();
    delta.tv_sec = 86400-(((now.tm_hour*60)+now.tm_min)*60+now.tm_sec);
    delta.tv_nsec = 0;
    pthread_get_expiration_np ( &delta, midnight );
    pthread_mutex_lock ( &log_flush );
    return 0;
}
/*****************************************************************************/
/* Expand acc arrays to accomodate indicated level (negative number). 
 * Search existing array for matching fname and clone file pointer into
 * slot and sent acc_valid[] entry to matching level number.
 */
static int tlog_alloc_acc_array ( int level, char *fname ) {

    int size, alloc, i;
    size = 0 - level;
    if ( size <= 0 ) return 20;		/* bad parameter */
    if ( acc_alloc == 0 ) {
	/*
	 * First call, make initial allocation.
	 */
	acc_alloc = 8;
	while ( acc_alloc < size ) acc_alloc = acc_alloc * 2;
	acc_valid = (int *) malloc ( acc_alloc * sizeof(int) );
	acc_dirty = (int *) malloc ( acc_alloc * sizeof(int) );
	acc_file = (FILE **) malloc ( acc_alloc * sizeof(FILE *) );
	acc_file_spec = (char **) malloc ( acc_alloc * sizeof(char *) );
	for ( i = 0; i < acc_alloc; i ++ ) {
	    acc_valid[i] = acc_dirty[i] = 0;
	    acc_file[i] = (FILE *) 0;
	    acc_file_spec[i] = (char *) 0;
	}
	acc_valid[0] = acc_defvalid[0];
	acc_file[0] = acc_deffile[0];
	acc_file_spec[0] = acc_deffilespec;
    }
    if ( acc_alloc < size ) {
	/*
	 * Realloc the arrays.
	 */
	for ( alloc = acc_alloc * 2; alloc < size; alloc = alloc * 2 );
	acc_valid = realloc ( acc_valid, alloc * sizeof(int) );
	acc_dirty = realloc ( acc_dirty, alloc * sizeof(int) );
	acc_file = realloc ( acc_file, alloc * sizeof(FILE *) );
	acc_file_spec = realloc ( acc_file_spec, alloc * sizeof(char *) );
	/*
	 * Zero extended portion.
	 */
	while ( acc_alloc < alloc ) {
	    acc_valid[acc_alloc] = acc_dirty[acc_alloc] = 0;
	    acc_file[acc_alloc] = (FILE *) 0;
	    acc_file_spec[acc_alloc] = (char *) 0;
	    acc_alloc++;
	}
    }
    if ( acc_file_spec[-1-level] == (char *) 0 ) {
	/*
	 * Allocate new buffer and copy name (truncate);
	 */
	acc_file_spec[-1-level] = (char *) malloc ( 256 );
	strncpy ( acc_file_spec[-1-level], fname, 255 );
	acc_file_spec[-1-level][255] = '\0';
	/*
	 * Search for matching fname and clone file pointer if found.
	 */
	for ( i = -2-level; i >= 0; i-- ) if ( acc_file_spec[i] ) {
	    if ( strncmp ( acc_file_spec[i], 
			acc_file_spec[-1-level], 255 )	== 0 ) {
		acc_file[-1-level] = acc_file[i];
		acc_valid[-1-level] = -1-i;
		if ( (-1-level) > acc_maxvalid ) acc_maxvalid = (-1-level);
		break;
	    }
	}
    }
    return 1;
}
/*****************************************************************************/
int tlog_init ( char *log_file_name )
{
    int status, pthread_create();
    if ( log_valid ) return 3;
    log_file = extended_fopen ( log_file_name, "w", 0 );
    strcpy ( log_file_spec, log_file_name );
    if ( log_file ) log_valid = 1;
    log_buffered = 0;
    if ( !log_valid ) perror ( "Can't open log file" );
    /*
     * See if log file should be buffered.
     */
    else {
	struct stat statblk;
	int code, value, status;
	if ( fstat ( fileno(log_file), &statblk ) == 0 ) {
#ifdef VMS
	    /*  
	     * Don't buffer 'record' devices: mailboxes, terminals.
	     */
	    int LIB$GETDVI();
	    static $DESCRIPTOR(device_dx,"");
	    device_dx.dsc$w_length = strlen ( statblk.st_dev );
	    device_dx.dsc$a_pointer = statblk.st_dev;
	    code = DVI$_DEVCHAR;
	    status = LIB$GETDVI ( &code, 0, &device_dx, &value, 0, 0 );
	    if ( (status&1) == 1 ) {
		if ( (value&DEV$M_REC) == 0 ) log_buffered = 1;
	    } else {
		fprintf(stderr,"Error getting file device info: %d\n", status);
	    }
#else
	    if ( S_ISREG(statblk.st_mode) ) log_buffered = 1;
#endif
	}
    }
    /*
     * initialize static fields in descriptors.
     */
    control.dsc$b_dtype = DSC$K_DTYPE_T;
    control.dsc$b_class = DSC$K_CLASS_S;
    output.dsc$b_dtype = DSC$K_DTYPE_T;
    output.dsc$b_class = DSC$K_CLASS_S;
    /*
     * Initialize mutexes and conditions.
     */
    INITIALIZE_MUTEX ( &log_append );
    SET_MUTEX_NAME(&log_append,"OSU log append")
    INITIALIZE_MUTEX ( &log_flush );
    SET_MUTEX_NAME(&log_flush,"OSU log flush")
    INITIALIZE_CONDITION ( &flusher_wake );
    SET_COND_NAME(&flusher_wake, "OSU log flusher wake")
    INITIALIZE_CONDITION ( &flush_done );
    SET_COND_NAME(&flush_done, "OSU log flush done")
    INITIALIZE_CONDITION ( &new_current_buffer );
    SET_COND_NAME(&new_current_buffer, "OSU log buffer change")
    if ( stack_trace_level == 0 ) {
	stack_trace_level = -1;		/* http_log_level is never zero */
	stack_report = null_report;	/* dummy */
    }
    /*
     * Initialize the buffers, writing is double-buffered.
     */
    buffer_a.used = 0;
    buffer_a.last_header = 0;
    buffer_b.used = 0;
    buffer_b.last_header = 0;
    current_buffer = &buffer_a;
    flushing_buffer = (struct log_buffer *) 0;
    /*
     * Start thread whose job in life is to flush the log files to
     * disk periodically.
     */
    flusher_state = FLUSHER_IDLE;
#ifdef PTHREAD_USE_D4
    status = pthread_create ( &flusher_id, pthread_attr_default,
	tlog_flusher, (pthread_addr_t) log_file_name );
#else
    status = pthread_create ( &flusher_id, NULL,
	tlog_flusher, (void *) log_file_name );
#endif
    if ( status ) perror ( "Can't start log flusher thread" );
    tlog_putlog_cb = tlog_putlog;

    return log_valid;
}
/***********************************************************************/
/* Initialize secondary log files (access log and trace log).
 * If trace log file name is empty, send trace into to error log.
 */

int tlog_initlog ( int level,		/* which file: -1 access, 1 trace */
	char *fname )
{
    if ( level < 0 ) {		/* access log */
	tlog_alloc_acc_array ( level, fname );
       if ( acc_valid[-1-level] ) return 3;
        strcpy ( acc_file_spec[-1-level], fname );
        acc_file[-1-level] = extended_fopen ( fname, "a", 1 );
	if ( acc_file[-1-level] ) acc_valid[-1-level] = 1; 
	else acc_valid[-1-level] = 0;
	if ( (-1-level) > acc_maxvalid ) acc_maxvalid = (-1-level);
	return acc_valid[-1-level];

    } else {
	trace_level = level;
        if ( trc_valid ) return 3;		/* Already selected */
	if ( *fname == '\0' ) fname = log_file_spec;
        strcpy ( trc_file_spec, fname );
	if ( strcmp ( fname, log_file_spec ) == 0 ) {
	    /*
	     * Trace log and error log are same, make log file invalid.
	     */
	    trc_file = log_file;
	    trc_buffered = log_buffered;
	    log_valid = 0;
	    trc_valid = 1;
	    return trc_valid;
	}
        trc_file = extended_fopen ( fname, "w", 0 );
	trc_valid = (trc_file != NULL);
	/*
	 * See if log file should be buffered.
	 */
	if ( trc_valid ) {
	    struct stat statblk;

	    trc_buffered = 0;
	    if ( fstat ( fileno(trc_file), &statblk ) == 0 ) {
#ifdef VMS
		/*  
	 	 * Don't buffer 'record' devices: mailboxes, terminals.
	 	 */
	        int LIB$GETDVI(), code, value, status;
		 static $DESCRIPTOR(device_dx,"");
		 device_dx.dsc$w_length = strlen ( statblk.st_dev );
		 device_dx.dsc$a_pointer = statblk.st_dev;
		 code = DVI$_DEVCHAR;
		 status = LIB$GETDVI ( &code, 0, &device_dx, &value, 0, 0 );
		 if ( (status&1) == 1 ) {
		    if ( (value&DEV$M_REC) == 0 ) trc_buffered = 1;
		 } else {
		    fprintf(stderr,"Error getting file device info: %d\n", status);
		 }
#else
	         if ( S_ISREG(statblk.st_mode) ) trc_buffered = 1;
#endif
	     }
	}

    }
    return trc_valid;
}

int tlog_set_stack_trace ( int level, int (*stack_used)() )
{
    stack_report = stack_used;
    stack_trace_level = level;
    return 1;
}
/*
 * Format log output and send to appropriate log file based upon log
 * level argument and trace level set with tlog_initlog;
 *     0	Error, write to trace file and error log.
 *    <0	Normal access, write to access log.
 *    >0	Write to trace file if trace_level greater or equal to level.
 */
int tlog_putlog ( int level, char *ctlstr, ... )
{
    va_list param_list;
#ifdef __ALPHA
    unsigned int __VA_COUNT_BUILTIN(void), arg_count;
    int i, fao_param[32];
#endif
    int status, SYS$FAOL(), length, clen, valid;
    char *outbuf;
    FILE *fp;
    va_start(param_list,ctlstr);
    /*
     * See if log file available and synchronize access to static areas.
     */
    if ( level < 0 ) {
	if ( !acc_valid[-1-level] ) return 0;
    } else if ( !log_valid && !trc_valid ) return 0;
    if ( (level > 1) && (http_log_level == stack_trace_level) ) {
	tlog_putlog ( 1, "Stack usage: !SL bytes!/", (*stack_report)() );
    }
    for ( clen = 0; ctlstr[clen]; clen++ );
    /*
     * reserve space and get exclusive access to buffers and string descriptors.
     */
    outbuf = reserve_buffer_space ( level, MAX_PUTLOG_WRITE );
    if ( !outbuf ) {	/* should never happen */
	fprintf(stderr,"Bugcheck, tlogger failed to reserve buffer space\n");
    }
    control.dsc$w_length = clen;
    control.dsc$a_pointer = ctlstr;
    output.dsc$w_length = MAX_PUTLOG_WRITE;
    output.dsc$a_pointer = outbuf;
    /*
     * Format the data, writing directly into the reserved space.
     */
    length = 0;
#if defined(__ALPHA) && defined(VMS)
    arg_count = __VA_COUNT_BUILTIN() - 1;
    for ( i = 0; i < arg_count; i++ ) fao_param[i] = va_arg(param_list,int);
    LOCK_VMS_RTL
    status = SYS$FAOL ( &control, &length, &output, fao_param );
#else
    LOCK_VMS_RTL
    status = SYS$FAOL ( &control, &length, &output, 
#if defined(VMS) || defined(linux)
	param_list );
#else
	(char *) param_list.a0 + param_list.offset );	/* OSF/1 */
#endif
#endif
    UNLOCK_VMS_RTL
    if ( (status&1) == 0 ) {
	LOCK_C_RTL
	fprintf(log_file,"FAO error: %d '%s'\n", status, ctlstr );
	UNLOCK_C_RTL
    }
    /*
     * Restart flush timer if idle and update the buffer header.
     */
    if ( flusher_state == FLUSHER_IDLE ) {
	pthread_cond_signal ( &flusher_wake );
    }
    commit_buffer_space ( level, length);
    return status;
}
/*
 * Flush pending data to log file periodically.
 */

int tlog_flush()
{
    int i;
    return 0;
}
/*****************************************************************************/
/* Make new version of open log file. */
tlog_reopen ( int level )
{
    int i, status;
    struct log_buffer *buf;
    /*
     * Flush all buffers.
     */
    pthread_mutex_lock ( &log_append );
    while ( !current_buffer ) {
	/* Another writer has handed off the buffer to the flusher thread. */
	pthread_cond_wait ( &new_current_buffer, &log_append );
    }
    buf = current_buffer;
    current_buffer = (struct log_buffer *) 0;	/* lock out subsequent writers*/
    pthread_mutex_unlock ( &log_append );
    pthread_mutex_lock ( &log_flush );
    if ( buf->used > 0 ) {
	/*
	 * Flush the current buffer.
	 */
	flushing_buffer = buf;
	pthread_cond_signal ( &flusher_wake );
	while ( flushing_buffer ) {
	    pthread_cond_wait ( &flush_done, &log_flush );
	}
    }
   /*
    * At this stage both current_buffer and flushing buffer are null so
    * we can reopen files.
    */
   if ( level < 0 ) {		/* access log */
        /*
	 * Make new version of access logs.  If the currently open log file
         * is the latest version, create a new version.  Otherwise open
	 * the latest version for append access.
	 */
#ifdef VMS
	static stat_t current, ondisk;	/* serialized by log_flush mutex! */
	int cur_status, ondisk_status;
#endif
	char *mode;

	for ( status = i = 0; i <= acc_maxvalid; i++ ) {
            if ( acc_valid[i] > 0 ) {
	        LOCK_C_RTL
		/*
		 * Determine if a mode 'a' fopen would open the same
		 * file or a different file by comparing device and ino.
		 */
		mode = "w";		/* default to create new */
#ifdef VMS
		cur_status = fstat ( fileno ( acc_file[i] ), &current );
		ondisk_status = stat ( acc_file_spec[i], &ondisk );
		if ( (cur_status == 0) && (ondisk_status == 0) ) {
		    if ( (current.st_ino[0] != ondisk.st_ino[0]) ||
			(current.st_ino[1] != ondisk.st_ino[1]) ||
			(current.st_ino[2] != ondisk.st_ino[2]) ||
			(strcmp (current.st_dev, ondisk.st_dev) != 0) ) {

			mode = "a";	/* latest was NOT what was open */
		    }
		}
#endif
		/*
		 * Close and reopen with the mode we determined.
		 */
	        fclose ( acc_file[i] );
		acc_dirty[i] = 0;
                acc_file[i] = extended_fopen ( acc_file_spec[i], mode, 1 );
	        UNLOCK_C_RTL
	        if ( !acc_file[i] ) acc_valid[i] = 0;
		else status = 1;
            } else if ( acc_valid[i] < 0 ) {
		/*
		 * re-clone file pointer, assumes original is always in
		 * earlier slot.  Mark invalid if previous re-open failed.
		 */
		acc_file[i] = acc_file[-1-acc_valid[i]];
		if ( !acc_file[i] ) acc_valid[i] = 0;
	    }
	}

    } else {
	/*
	 * Make new version of trace log and set logger level.
	 */
	if ( trc_valid ) {
	    trace_level = level;
	    LOCK_C_RTL
	    fclose ( trc_file );
            trc_file = extended_fopen ( trc_file_spec, "w", 0 );
	    UNLOCK_C_RTL
	    if ( !trc_file ) trc_valid = 0;
	}
	status = trc_valid;
    }
    pthread_mutex_unlock ( &log_flush );
    /*
     * Resume action.
     */
    pthread_mutex_lock ( &log_append );
    current_buffer = buf;
    pthread_mutex_unlock ( &log_append );
    pthread_cond_broadcast ( &new_current_buffer );
    return status;
}
/*************************************************************************/
/* Close open files. to cleanly flush. 
*/
int tlog_rundown()
{
    int status, i;
    void *value;
    /*
     * Kill the flusher thread and wait on it to complete.
     */
    pthread_mutex_lock ( &log_flush );
    while ( flushing_buffer ) {
	/* Another writer has handed off the buffer to the flusher thread. */
	pthread_cond_wait ( &flush_done, &log_flush );
    }
    status = pthread_cancel ( flusher_id );
    pthread_mutex_unlock ( &log_flush );
    if ( status != 0 ) printf ( "error killing flusher thread: %d %d\n",
		status, errno );
    if ( status == 0 ) {
	status = pthread_join ( flusher_id, &value );
	if ( status != 0 ) printf ( "error joining flusher thread: %d %d\n",
		status, errno );
    }
    /*
     * Flush data.
     */
    pthread_mutex_lock ( &log_flush );
    pthread_mutex_lock ( &log_append );
    if ( current_buffer ) {
	if ( flushing_buffer ) flush_buffer ( flushing_buffer );
	current_buffer->sync_after_flush = 0;
	flush_buffer ( current_buffer );
    }
    /*
     * close files.
     */
    if ( log_valid ) {
	log_valid = 0;
	LOCK_C_RTL
	fclose ( log_file );
	UNLOCK_C_RTL
    }
    for ( i = 0; i <= acc_maxvalid; i++ )  if ( acc_valid[i] ) {
	if ( acc_valid[i] > 0 ) {	/* not a clone */
	    LOCK_C_RTL
	    fclose ( acc_file[i] );
            UNLOCK_C_RTL
	}
	acc_valid[i] = 0;
	acc_dirty[i] = 0;
    }
    if ( trc_valid ) {
	trc_valid = 0;
	LOCK_C_RTL
	fclose ( trc_file );
	UNLOCK_C_RTL
    }

    pthread_mutex_unlock ( &log_append );
    pthread_mutex_unlock ( &log_flush );

    return 1;
}
