/*
 * High level cache routines for caching file data.  Data is saved as a
 * sequence of records.  The maximum records size you can store in a cache
 * entry is TCACHE_MAX_RECSIZE (defined in data_cache.h).
 *
 * Cache operation is controlled by 3 parameters (arguments to tfc_init()):
 *
 *    refresh_time	Maximum time in seconds that a cache entry may
 *			be stale (contains a previous version of the file).
 *			A zero refresh time is permitted, which causes
 *			the cache entry's modification date to be compared
 *			with the source file's on every access.
 *			
 *    total_size	Size in bytes to allocate to data cache.  Data is
 *			managed in TCACHE_MAX_RECSIZE-sized units known as 
 *			chunks.  Each cache entry will allocate at least 1 
 *			chunk and each chunk can hold multiple records.
 *
 *    data_limit	Maximum number of bytes a cache entry may consume.
 *			This limit includes 12 bytes of overhead for each
 *			record saved but does not include internal
 *			fragmentation losses at the end of allocated chunks.
 *
 * Author:	David L. Jones
 * Date:	10-aug-1997
 * Revised:	20-AUG-1997	Added tfc_display_entries function.
 * Revsied:	27-AUG-1997	Add zero flag to display_counters.
 * Revised:	17-NOV-1997	Add tfc_expire_all() function.
 * Revised:	12-DEC-1997	Cleanup for inclusion in base_mst.
 * Revised:	14-DEC-1997	Add bitmap routines.
 * Revised:	22-APR-1999	Minor fix to include statment.
 * Revised:	16-SEP-1999	Fix bug in tfc_cached_open whereby it was
 *				improperly checking the status on a cache
 *				entry refresh and deaccessing entries twice.
 *				This bug should only happen if there are more
 *				threads than cache items available.
 * Revised:	31-DEC-1999	Include hit rate percentage in statistics
 *				 display.
 * Revised:	11-JAN-2000	Set pthread object names.
 * Revised:	2-MAR-2000	Fix non-VMS time() call in tfc_cached_open.
 * Revised:	20-APR-2000	Fix format problem with hit rate.
 * Revised:	27-AUG-2000	Add expiration support.
 * Revised:	3-SEP-2001	Don't test against missing expiration.
 */
#include <stdlib.h>
#include "file_cache.h"
#include "file_access.h"
#include "pthread_1c_np.h"
#include "fast_utc.h"
#include "tutil.h"
#include <string.h>		/* required for VAX builds */

#define tlog_putlog (*tlog_putlog_cb)
#ifdef VAXC
globalref
#endif
int http_log_level, tlog_putlog ( int, char *, ... );

static int tfc_refresh_time;		/* seconds before file moddate check */
static int tfc_data_limit;		/* private copy of data_limit param. */
#ifdef VMS
static long tfc_refresh_delta[2];	/* refresh time as VMS delta time */
extern int LIB$EMUL(), LIB$SUBX(), SYS$GETTIM();
#endif
static pthread_mutex_t tfc_hdr_lock;	/* Allows in-place mods records */
/*
 * The 65k bitmap used by tfc_{mark/test} port is allocated in 1024 bit chunks.
 * represented as arrays of 32 longs.
 */
static long *port_map[64] = { (long *) 0 };
static long zero_bits[32];
static int efs_flags = 0;
/****************************************************************************/
/* Set set options for file_access but keep a copy of the result so we
 * know when we need to retrieve extra data.
 */
int tfc_set_tf_options ( int flags, int *enabled )
{
    int status;
    status = tf_set_options ( flags, enabled );
    if ( (status&1) == 1 ) efs_flags = *enabled;
    if ( http_log_level > 3 ) tlog_putlog ( 4, 
	"Set tfc_tf_opts, status: !SL, flags: !SL!/", status, efs_flags);
    return status;
}
/****************************************************************************/
/* The init routine must be called in single-threaded mode.  Refresh_time
 * is seconds item can stay in cache before modification check (may be zero).
 * total_size is size in bytes (should be > 200,000) of cache.
 *
 * Note that these routines do not set up exit handlers to rundown contexts
 * on thread exit, so caller of tfc_cached_open must call tfc_rundown_context
 * before context argument goes out of scope.
 */
int tfc_init ( int refresh_time, int total_size, int item_size_limit )
{
    int status, i;
    tfc_refresh_time = refresh_time;
    tfc_data_limit = item_size_limit;
    INITIALIZE_MUTEX ( &tfc_hdr_lock );
    SET_MUTEX_NAME(&tfc_hdr_lock,"OSU tfc header_lock")
#ifdef VMS
    /*
     * Convert refresh time to VMS delta time format'
     */
    tfc_refresh_delta[0] = tfc_refresh_delta[1] = 0;
    if ( refresh_time > 0 ) {
	int offset = 0, factor = -10000000;
	LIB$EMUL ( &tfc_refresh_time, &factor, &offset, tfc_refresh_delta );
	tlog_putlog ( 2, "Setting refresh delta to !SL !SL!/",
		tfc_refresh_delta[0], tfc_refresh_delta[1] );
    }
#endif
    /*
     * Initialize port bitmap to all zeros, sharing single copy of bits array.
     */
    if ( port_map[0] == (long *) 0 ) for ( i = 0; i < 32; i++ ) {
	zero_bits[i] = 0;
	port_map[i] = zero_bits;
	port_map[i+32] = zero_bits;
    }
    /*
     * Initialize lower level data cache (part that handles data storage).
     */
    status = tcache_init ( total_size, item_size_limit );
    return status;
}
/****************************************************************************/
/* Mainipulate 65K bitmap as long arrays.
 * tfc_mark_port is not thread-safe, it may be called before tfc_init.
 * 
 */
int tfc_mark_port_number ( int port_num )
{
    int ndx, off, i;
    long mask;
    /*
     * test if bitmap has been initialized.
     */
    if ( port_map[0] == (long *) 0 ) for ( i = 0; i < 32; i++ ) {
	zero_bits[i] = 0;
	port_map[i] = zero_bits;
	port_map[i+32] = zero_bits;
    }
    ndx = port_num >> 10;
    if ( (ndx < 0) || (ndx > 63) ) return 0;	/* out of range */

    if ( port_map[ndx] == zero_bits ) {
	port_map[ndx] = (long *) calloc ( 32, sizeof(long) );
    }
    off = (port_num>>5) & 31;
    mask = 1 << (port_num&31);
    port_map[ndx][off] |= mask;
    return 1;
}
int tfc_test_port_number ( int port_num )
{
    int ndx, off;
    long mask;
    ndx = port_num >> 10;
    off = (port_num>>5) & 31;
    mask = 1 << (port_num&31);
    if ( (port_map[ndx][off] & mask) == 0 ) return 0;
    return 1;
}
/****************************************************************************/
/* Lookup cache item named by type and ident.  Open is done via tf_open()
 * (file_access.h).  If type is 0 (binary), open mode is "rb"; if type is 1
 * (text), mode is  "rd".  Char buffer recieves error message from tf_open.
 *
 * return values: 0 - cache disabled or other failure, ignore.
 *		     1 - Valid item found in cache, fptr set to NULL.
 *		     2 - New item created or existing item stale, fptr opened
 *			 for read access on ident.
 */
int tfc_cached_open ( int type, char *ident, void **fptr, char *buffer,
	tfc_ctxptr tfc )
{
    int status, length, refresh_time, usr_hdr_size;
    struct tfc_finfo *saved_hdr;

    usr_hdr_size = sizeof(tfc->hdr);
    status = tcache_access ( type, ident, &tfc->ctx, &tfc->hdr_rec, 
	&tfc->hdr_size );
    if ( http_log_level > 3 ) tlog_putlog ( 2,
	"Cache check for '!AZ', status: !SL, length: !SL!/", ident, status,
	tfc->hdr_size );

    switch ( status ) {
	case TCACHE_ACCESS_ACCESSED:
	    /*
	     * Found ident in cache, restore file information from last record.
	     */
	    tfc->mode = 1;
	    *fptr = (void *) 0;
	    *buffer = '\0';
	    saved_hdr = (struct tfc_finfo *) tfc->hdr_rec;
	    if ( http_log_level > 3 ) tlog_putlog ( 2, 
		"Cached owner: !XL size: !SL!/", saved_hdr->uic, 
		saved_hdr->size );
	    /*
	     * Check for cache expiration and skip file open if within refresh
	     * period.  Access to *saved_hdr is protected by mutex.
	     */
	    if ( tfc_refresh_time > 0 ) {
#ifdef VMS
		long now[2], delta[2];
		SYS$GETTIM ( now );
		/*
		 * Update expiration time and make local copy of header.
		 */
		if ( http_log_level > 3 ) tlog_putlog(2,
			"Current time: !XL!XL exp: !XL!XL!/", now[1], now[0],
			 saved_hdr->cache_expiration[1],
			 saved_hdr->cache_expiration[0]);
		pthread_mutex_lock ( &tfc_hdr_lock );
		saved_hdr->hits++;
		LIB$SUBX ( saved_hdr->cache_expiration, now, delta );
		if ( delta[1] < 0 ) LIB$SUBX ( now, saved_hdr->cache_retention,
			saved_hdr->cache_expiration );
		if ( usr_hdr_size == sizeof(tfc->hdr) ) tfc->hdr = *saved_hdr;
		else {
		    /* Caller is using a short context block */
		    tfc->hdr_size = usr_hdr_size;
		    memcpy ( &tfc->hdr, saved_hdr, usr_hdr_size );
		}
		pthread_mutex_unlock ( &tfc_hdr_lock );
		/*
		 * Ignore the refresh time-based cache expiration and force
		 * entry stale if the file's actual expiration date is 
		 * available to us and is before the current time.
		 */
		if ( (efs_flags & TF_OPT_HDR_EDATE) && tfc->hdr.edate ) {
		    /* File has expiration date, check for expiration */
		    time_t u_now;
		    futc_current_time ( &u_now );
		    /* if ( http_log_level > 3 ) tlog_putlog(2,
			   "Testing if !XL (now) >= !XL (file edate)...\n",
			   u_now, tfc->hdr.edate ); */
		    if ( u_now >= tfc->hdr.edate ) {
			delta[1] = -1;
		    }
		}
		/*
		 * If old expiration time still within refresh period we can
		 * use this cache entry as is.   If head flagged a data
		 * overrun writing to cache, force re-read of file.
		 */
		if ( http_log_level > 3 ) tlog_putlog(2,"Cache entry !AZ!/",
			(delta[1]<0)?"expired" : "not-expired" );
		if ( delta[1] >= 0 && tfc->hdr.size >= 0 ) return 1;
#else
		time_t now;
		int is_current;
		now = time ( & now );
		pthread_mutex_lock ( &tfc_hdr_lock );
		saved_hdr->hits++;
		if ( saved_hdr->cache_expiration <= now ) {
		   is_current = 0;
		   saved_hdr->cache_expiration = now + 
			saved_hdr->cache_retention;
		} else is_current = 1;
		if ( usr_hdr_size == sizeof(tfc->hdr) ) tfc->hdr = *saved_hdr;
		else {
		    /* Caller is using a short context block */
		    tfc->hdr_size = usr_hdr_size;
		    memcpy ( &tfc->hdr, saved_hdr, usr_hdr_size );
		}
		pthread_mutex_unlock ( &tfc_hdr_lock );

		if ( is_current && (tfc->hdr.size >= 0) ) return 1;
#endif
	    } else {
		/*
		 * No need to lock header since header won't be updated.
		 */
		if ( usr_hdr_size == sizeof(tfc->hdr) ) tfc->hdr = *saved_hdr;
		else {
		    /* Caller is using a short context block */
		    tfc->hdr_size = usr_hdr_size;
		    memcpy ( &tfc->hdr, saved_hdr, usr_hdr_size );
		}
	    }
	    break;

	case TCACHE_ACCESS_CREATED:
	    /*
	     * New entry created, compute expiration time for cache entry.
	     */
	    tfc->mode = 2;
	    tfc->hdr_size = usr_hdr_size;
	    if ( tfc_refresh_time > 0 ) {
#ifdef VMS
		long now[2];
		SYS$GETTIM ( now );
		tfc->hdr.cache_retention[0] = tfc_refresh_delta[0];
		tfc->hdr.cache_retention[1] = tfc_refresh_delta[1];
		LIB$SUBX ( now, tfc_refresh_delta, tfc->hdr.cache_expiration );
#else
		time_t now;
		now = time ( &now );
		tfc->hdr.cache_retention = tfc_refresh_time;
		tfc->hdr.cache_expiration = now + tfc_refresh_time;
#endif
	    }
	    tfc->hdr.hits = 0;
	    break;
	case TCACHE_ACCESS_BLOCKED:
	    tfc->mode = 0;
	    break;
	case TCACHE_FAILURE:
	default:
	    tfc->mode = 0;
	    break;
    }
    /*
     * Attempt to open file.
     */
    *fptr = tf_open ( ident, type ? "rd" : "rb", buffer );
    if ( !*fptr ) {
	int ident_len;
	/*
	 * Open failure.  Reset cache context.
	 */
	if ( tfc->mode ) tfc_rundown_context ( tfc, 1 );
	/*
	 * Check last 16 characters in string for magic numbers.
	 */
	ident_len = tu_strlen ( ident );
	if ( ident_len > 16 ) {
	    if (tu_strncmp(&ident[ident_len-16],"%CacheInvalidate", 16) == 0) {
	        tcache_delete_all();
		tlog_putlog(0,"File cache invalidated\n");
	    }
	}
	return tfc->mode;
    }
    /*
     * Get file header information and check against mdate.
     */
    tf_header_info ( *fptr, &length, &tfc->hdr.uic, &tfc->hdr.cdate,
		&tfc->hdr.mdate );
    if ( efs_flags & (TF_OPT_HDR_EDATE) ) {
	/* 
	 * Fetch the file expiration date 
	 */
	tf_ext_header_info ( *fptr, &tfc->hdr.edate, (void *) 0, 0,
		(int *) 0 );
    } else tfc->hdr.edate = 0;		/* no expiration date */

    if ( tfc->mode == 1 ) {
	/*
	 * Entry was found in cache, compare saved mdate with actual.
	 */
	if ( saved_hdr->mdate != tfc->hdr.mdate ) {
	    /* 
	     * Cached copy invalid.  Delete it and re-open as mode 2. 
	     * The expiration time was updated above.
	     */
	    tcache_deaccess ( &tfc->ctx, 1 );	/* delete */
	    tfc->hdr.size = length;
	    status = tcache_access ( type, ident, &tfc->ctx, &tfc->hdr_rec, 
			&length );
	    if ( status == TCACHE_ACCESS_CREATED ) {
		tfc->mode = 2;
	    } else {
		/*
		 * Unexpected response, access failed or between our deaccess
		 * and reaccess another thread reloaded the entry.
		 */
		if ( status == TCACHE_ACCESS_ACCESSED ) {
		     /* Don't bother recusrively testing the date, give up */
		     tcache_deaccess ( &tfc->ctx, 0 );
		}
		tfc->mode = 0;		/* give up */
	    }
	} else if ( tfc->hdr.size >= 0 ) {
	    /* Cached copy still valid, close file access */
	    tf_close ( *fptr );
	    *fptr = (void *) 0;
	} else {
	    /*
	     * Data for file could not be saved (too large), skip caching.
	     */
    	    if ( http_log_level > 3 ) tlog_putlog(2,
			"Cached overflow status on file!/");
	    tcache_deaccess ( &tfc->ctx, 0 );
	    tfc->mode = 0;
	    tfc->hdr.size = length;	/* restore real file size */
	}
    } else if ( (type==0) && (length >= tfc_data_limit) ) {
	/*
	 * (mode==2) For binary files, length returned by get_header_info is
	 * accurate count of final data_bytes value. Change entry to overflow
	 * and save.
	 */
	if ( http_log_level > 3 ) tlog_putlog(2,
		"Binary file obviously too large, skipping cache!/");
	tfc->data_size = -1;		/* Mark as put overflow */
	tfc_rundown_context ( tfc, 0 );
	tfc->hdr.size = length;		/* restore real file size */
    } else {
	tfc->hdr.size = length;
    }
    tfc->data_size = 0;
    return tfc->mode;
}
/*****************************************************************************/
/* Context rundown will automatically write the tfc as the last record
 * if mode=2 and deaccess entry.  If abort_flag true, entry is deleted.
 */
int tfc_rundown_context ( tfc_ctxptr tfc, int abort_flag )
{
    if ( tfc->mode < 1 || tfc->mode > 2 ) return 1;
    if ( tfc->mode == 2 ) {
	int status;
	tfc->hdr.size = tfc->data_size;
	status  = tcache_put ( &tfc->ctx, (void **) &tfc->hdr, 
		sizeof(tfc->hdr) );
	if ( status == TCACHE_FAILURE ) {
	    tfc->mode = 0;
	    tcache_deaccess ( &tfc->ctx, 1 );
	    return 0;
	}
    }
    tcache_deaccess ( &tfc->ctx, abort_flag );
    tfc->mode = 0;		/* reset context to null state */
    return 1;
}

/*****************************************************************************/
/* Return address of next saved record.
 * return values: 
 *	-1  -  tfc context invalid.
 *	 0  -  no more records in cache (excluding header record).
 *	 1  -  Normal, successful completion.
 */
int tfc_get ( tfc_ctxptr tfc, void **next_rec, int *length )
{
    int status;
    if ( tfc->mode != 1 ) return -1;
    status = tcache_get ( &tfc->ctx, next_rec, length );
    if ( (status == TCACHE_FAILURE) || ((*next_rec) == tfc->hdr_rec) ) {
	tcache_deaccess ( &tfc->ctx, 0 );
	tfc->mode = 0;
	return 0;
    }
    return 1;
}
/*****************************************************************************/
/* Save next record in currently open cache context.
 */
int tfc_put ( tfc_ctxptr tfc, void *rec, int length )
{
    int status;
    if ( tfc->mode != 2 ) return -1;
    status = tcache_put ( &tfc->ctx, rec, length );
    if ( status == TCACHE_FAILURE ) {
	/*
	 * Allocation faliure on write, delete the entry to return the used
	 * memory.
	 */
	tfc->mode = 0;
	tcache_deaccess ( &tfc->ctx, 1 );
	return 0;
    } else if ( status == TCACHE_OVERRUN ) {
	/*
	 * Put exceeded data limit, clear the data and record entry as
	 * having a -1 size to indicate the overflow condition.  Caching the
	 * overflow condition saves us from repeating the wasted puts on the
	 * next retrieval.
	 */
	tcache_clear ( &tfc->ctx );
	tfc->data_size = -1;			/* flag as invalid */
	tfc_rundown_context ( tfc, 0 );
    } else {
	/*
	 * Track data bytes written, rundown will save this information for
	 * next time ident is retrieved.
	 */
	tfc->data_size += length;
    }
    return 1;
}
/*****************************************************************************/
/* Read data_cache counters and format  into text buffer.
 */
void tfc_display_counters ( char *buffer, int bufsize, int *length, int zero )
{
    struct tu_textbuf buf;
    int item_stats[8], chunk_stats[8], tcache_get_counters (), n, d;
    double hit_rate, attempts, hits;
    char numstr[16];

    buf.l = 0;
    buf.s = buffer;
    buf.size = bufsize;

    tcache_get_counters ( item_stats, chunk_stats, zero );
    attempts = item_stats[0];
    hits = item_stats[1];		/* 'active' list hits */
    hits += item_stats[2];		/* LRU list hists */
    hit_rate = (attempts <= 0) ? 0.0 : (100.0 * (hits/attempts));

    tu_add_text ( &buf, "\r\n\r\nFile Cache Statistics:\r\n", 40 );
    tu_add_text ( &buf, "   Hit rate: ", 13 );
    n = hit_rate;
    d = ((hit_rate-n) * 10.0) + 0.5;	/* get first decimal digit */
    if ( d >= 10 ) { n++; d = 0; }
    tu_add_text ( &buf, tu_strint(n, numstr), 16 );
    tu_add_text ( &buf, ".", 1 );
    tu_add_text ( &buf, tu_strint(d, numstr), 16 );
    tu_add_text ( &buf, "%\r\n   items: ", 13 );
    tu_add_text ( &buf, tu_strint(item_stats[0],numstr), 16 );
    tu_add_text ( &buf, " lookups, ", 12 );
    tu_add_text ( &buf, tu_strint(item_stats[1],numstr), 16 );
    tu_add_text ( &buf, "/", 2 );
    tu_add_text ( &buf, tu_strint(item_stats[2],numstr), 16 );
    tu_add_text ( &buf, " hits (act/lru), ", 30 );

    tu_add_text ( &buf, tu_strint(item_stats[3],numstr), 16 );
    tu_add_text ( &buf, "/", 2 );
    tu_add_text ( &buf, tu_strint(item_stats[4],numstr), 16 );
    tu_add_text ( &buf, "/", 2 );
    tu_add_text ( &buf, tu_strint(item_stats[5],numstr), 16 );
    tu_add_text ( &buf, " misses (new/lock/fail)\r\n   chunks: ", 90 );

    tu_add_text ( &buf, tu_strint(chunk_stats[1],numstr), 16);
    tu_add_text ( &buf, " used (", 8 );
    tu_add_text ( &buf, tu_strint(item_stats[6],numstr), 16);
    tu_add_text ( &buf, " dir), ", 8 );
    tu_add_text ( &buf, tu_strint(chunk_stats[2],numstr), 16);
    tu_add_text ( &buf, " total, ", 14 );
    tu_add_text ( &buf, tu_strint(chunk_stats[0],numstr), 16);
    tu_add_text ( &buf, " alloc attempts", 20 );

    *length = buf.l;
}
/*****************************************************************************/
/* Update saved expiration date to sometime before current time to force
 * refresh of all accesses.  Entries currently openned for write (new entries)
 * will be marked for delete since they don't yet have expiration times saved
 * in the entry.
 */
int tfc_expire_all()
{
    int status, type, state, ref_count, hdr_len, locked, i;
    char *name;
    char numstr[32];
    struct tcache_context tc;
    struct tfc_finfo *hdr;
    struct tu_textbuf buf;
    /*
     * Mark create-in-progress entries for delete.
     */
    tcache_delete_writelocked();
    /*
     * Scan all entries and update the expiration dates for open-for-read
     * and idle entries.
     */
    tc.cache_item = (tcache_item_ptr) 0;		/* begin new scan */
    pthread_mutex_lock ( &tfc_hdr_lock );
    while ( tcache_scan_entries ( &tc, &type, &name, &state, &ref_count,
		(void **) &hdr, &hdr_len ) ) {

	if ( (state==0) || (state==1) ) {
#ifdef VMS
	    hdr->cache_expiration[0] = 0;
	    hdr->cache_expiration[1] = 0;	/* 17-NOV-1858 */
#else
	    hdr->cache_expiration = (time_t) 0;	/* 1-JAN-1970 */
#endif
	}
    }
    tcache_end_scan ( &tc );				/* unlock context */
    pthread_mutex_unlock ( &tfc_hdr_lock );
    return 1;
}
/*****************************************************************************/
/* Format.
 */
int tfc_display_entries ( tcache_ctxptr ctx, char *buffer, int bufsize,
	int *length, int *count )
{
    int status, type, state, ref_count, hdr_len, locked, i;
    char *name;
    char numstr[32];
    struct tfc_finfo *hdr;
    struct tu_textbuf buf;

    buf.l = 0;
    buf.s = buffer;
    buf.size = bufsize;

    *count = 0;
    *length = 0;
    for ( status = 1; buf.l+200 < bufsize; (*count)++ ) {
	/*
	 * Read next entry.
	 */
	status = tcache_scan_entries ( ctx, &type, &name, &state, &ref_count,
		(void **) &hdr, &hdr_len );
	if ( !status ) break;
	/*
	 * format entry.
	 */
	tu_add_text ( &buf, "State: ", 10 );
        tu_add_text ( &buf, tu_strint(state,numstr), 16 );
	if ( state >= 0 ) {
	    tu_add_text ( &buf, ", type: ", 10 );
            tu_add_text ( &buf, tu_strint(type,numstr), 16 );
	    tu_add_text ( &buf, ", refcnt: ", 10 );
            tu_add_text ( &buf, tu_strint(ref_count,numstr), 16 );
	    tu_add_text ( &buf, ", name: ", 10 );
	    if ( name ) tu_add_text ( &buf, name, 300 );

	    if ( hdr_len == sizeof (struct tfc_finfo) && hdr ) {
		tu_add_text ( &buf, "\r\n    Hdr: ", 20 );
		tu_add_text ( &buf, "size=", 8 );
		tu_add_text ( &buf, tu_strint(hdr->size,numstr), 16 );
		tu_add_text ( &buf, ", mdate='", 12 );
		tf_format_time ( hdr->mdate, numstr );
		tu_add_text ( &buf, numstr, sizeof(numstr) );
#ifdef VMS
		if ( tfc_refresh_time ) {
		    /* Format binary time */
		    int SYS$ASCTIM(), tlen=0;
		    struct { long l; char *s; } timestr;
		    timestr.l = sizeof(numstr);
		    timestr.s = numstr;

		    tu_add_text ( &buf, "', ret='", 12 );
		    SYS$ASCTIM ( &tlen, &timestr, hdr->cache_retention, 1 );
		    tu_add_text ( &buf, numstr, tlen );

		    tu_add_text ( &buf, "', exp='", 12 );
		    SYS$ASCTIM ( &tlen, &timestr, hdr->cache_expiration, 1 );
		    tu_add_text ( &buf, numstr, tlen );
		}
#endif
		tu_add_text ( &buf, tfc_refresh_time ? "', hits= " : "'", 9 );
		if ( tfc_refresh_time ) tu_add_text ( &buf, 
			tu_strint(hdr->hits,numstr), 16 );
	    } else if ( hdr_len > 0 ) {
	        tu_add_text ( &buf, "\r\n    Invalid header info\r\n",40);
	        status = 0;
	        break;
	    }
	}
        tu_add_text ( &buf, "\r\n", 2 );
    }
    if ( !status ) {
	tcache_end_scan ( ctx );
	ctx->cache_item = (tcache_ctxptr) 0;
    }
    *length = buf.l;
    return status;
}
/*
 * Set the expiration time for the currently open entry to the current
 * time plus retention seconds.   Entry must be openned for update (mode 2).
 */
int tfc_update_expiration ( tfc_ctxptr tfc, int retention )
{
    if ( (tfc->mode > 2) || (tfc->mode < 1) ) return -1;
    else  {
        struct tfc_finfo *hdr;
#ifdef VMS
	long now[2], delta[2];
	int offset = 0, factor = -10000000;

        SYS$GETTIM ( now );
	LIB$EMUL ( &retention, &factor, &offset, delta );
	/*
	 * Update expiration time and make local copy of header.
	 */
	pthread_mutex_lock ( &tfc_hdr_lock );
	hdr = (tfc->mode == 1) ? (struct tfc_finfo *) tfc->hdr_rec : &tfc->hdr;
	hdr->cache_retention[0] = delta[0];
	hdr->cache_retention[1] = delta[1];
	LIB$SUBX ( now, hdr->cache_retention, hdr->cache_expiration );
	if ( http_log_level > 3 ) tlog_putlog(2,
		"Current time: !XL!XL exp: !XL!XL!/", now[1], now[0],
		 hdr->cache_expiration[1],
		 hdr->cache_expiration[0]);
 	
	pthread_mutex_unlock ( &tfc_hdr_lock );
#else
	time_t now, delta;
	now = time ( & now );
	pthread_mutex_lock ( &tfc_hdr_lock );
	hdr = (tfc->mode == 1) ? (struct tfc_finfo *) tfc->hdr_rec : &tfc->hdr;
	hdr->cache_retention = retention;
	hdr->cache_expiration = now + hdr->cache_retention;
	pthread_mutex_unlock ( &tfc_hdr_lock );
#endif
    }
    return 1;
}
/*
 * Verify that cache expiration time for entry is not later than edate,
 * resetting it to current time if reset_if flag is true.
 *    Return values:
 *	-1	invalid tfc mode.
 *	 0	edate is later than tfc->cache_expiration time.
 *	 1	edate is not later that tfc->cache_expiration.
 */
int tfc_verify_expiration ( tfc_ctxptr tfc, unsigned int edate, int reset_if )
{
    int result;
    result = 1;
    if ( (tfc->mode > 2) || (tfc->mode < 1) ) return -1;
    else  {
	struct tfc_finfo *hdr;
#ifdef VMS
        time_t cur_exp;
	/*
	 * Fetch expriation time.
	 */
	pthread_mutex_lock ( &tfc_hdr_lock );
	hdr = (tfc->mode == 1) ? (struct tfc_finfo *) tfc->hdr_rec : &tfc->hdr;
	futc_vms_to_gmt ( hdr->cache_expiration, &cur_exp );
	if ( cur_exp > edate ) {
	    result = 0;
	    if ( http_log_level > 3 ) tlog_putlog ( 3,
		    "Cache expiration verify failed, reset flag: !SL!/",
		    reset_if );
	    if ( reset_if ) SYS$GETTIM ( hdr->cache_expiration ); 
	 } else if ( http_log_level > 3 ) tlog_putlog ( 3,
		    "Cache entry valid, cur: !UL, edate: !UL!/", cur_exp,edate);
	pthread_mutex_unlock ( &tfc_hdr_lock );
#else
	pthread_mutex_lock ( &tfc_hdr_lock );
	hdr = (tfc->mode == 1) ? (struct tfc_finfo *) tfc->hdr_rec : &tfc->hdr;
	if ( hdr->cache_expiration > edate ) {
	    result = 0;
	    if ( http_log_level > 3 ) tlog_putlog ( 3,
		    "Cache expiration verify failed, reset flag: !SL!/",
		    reset_if );
	    if ( reset_if ) hdr->cache_expiration = edate;
	}
	pthread_mutex_unlock ( &tfc_hdr_lock );
#endif
    }
    return result;
}
