/*
 * This module handles C file access in a thread-safe fashion.
 * The special open mode "d" is added for reading directories via
 * LIB$FIND_FILE.
 *
 * typedef void *fhandle;
 * int tf_initialize ( char *prefix )
 * fhandle tf_open ( char *ident, char *mode, errmsg[256] );
 * int tf_last_error ( fhandle f, int *last_errno, int *last_vms_cond, char *s );
 * int tf_close ( fhandle f );
 * int tf_read ( fhandle f, char *buffer, size_t length );
 * int tf_getline ( void *fptr, char *buffer, int bufsize, int max_lines )
 * int tf_isdir ( void *fptr )
 * 
 * Revised: 22-JUL-1994		Fixed locking bug in tf_rundown.
 * Revised:  5-AUG-1994		Added CONDITIONAL_YIELD macro (doesn't
 *				yield if RTL is reentrant).
 * Revised: 14-AUG-1994		Modified tf_header_info to include owner.
 * Revised:  7-DEC-1994		Added tf_format_time().
 * Revised: 17-DEC-1994		Added tf_decode_time().
 * Revised: 19-JAN-1995		Let NUL terminate lines in tf_getline()
 * Revised: 28-FEB-1995		Fixed bug in malloc call in tf_initialize().
 * Revised: 29-MAY-1995		Support "rd" mode option: attempt 'r' open
 *				and if failure attempt 'd' mode open.
 *				Add tf_isdir() function as well.
 * Revised: 10-JUN-1995		Support use in MST shareable images.
 * Revised: 16-JUN-1995		Fixed bug in fclose() (missing '!").
 * Revised:  1-SEP-1995		Open binary files with ctx=stm option.
 * Revised: 5-OCT-1995		Work around bug in DECC fopen(), memory
 *				leak with use of "dna=..." option.
 * Revised: 9-DEC-1995		Check for relative addressing using "-".
 * Revised: 27-JAN-1996		Added tf_a_-eof function
 * Revised:  3-APR-1996		Adjust to V7 UTC time support.
 * Revised:  7-APR-1996		Add tf_set_crlf_newline.
 * Revised:  8-APR-1996		Fix problem with tf_getline and RAT:NONE files.
 * Revised: 13-FEB-1997		Switch to GMT-based timestamps for
 *				tf_get_header_info, utilizing new fast_utc
 *				functions.  fast_utc initiliation parameter
 *				set by new env. var. APPY_DEFAULT_DST.
 * Revised: 17-JUL-1997		Init fast_utc in tf_initialize and use
 *				futc_decode_vms_date in tf_decode_time().
 * Revised: 17-OCT-1997		Use pthread_once for initialization.
 * Revised: 18-DEC-1998		Look for non-dir files if extension missing.
 * Revised: 14-FEB-1999		Support ODS-5 filenames.
 * Revsied: 29-APR-1999		Hack for performance boost: ^/.
 * Revised:  5-AUG-1999		Improve directory scan support: Detect
 *				non-existant directories and support EFS
 *				files in directories.
 * Revised:  9-AUG-1999		Upcase search spec.
 * Revised: 10-AUG-200		Add expriation time hack.
 */
#include "pthread_1c_np.h"
#include <stdio.h>
#include <stdlib.h>
#include <stat.h>
#ifndef __GNUC__
#include <unixlib.h>
#endif
#include <errno.h>
#define strerror strerror_1
#include <string.h>
#undef strerror
char *strerror ( int errnum, ... );	/* work around bugs in early AXP includes */
#include "file_access.h"	/* validate prototypes against actual */
#include "tutil.h"
#include <descrip.h>
#include <fscndef.h>
#include <rmsdef.h>
#include <rms.h>		/* VMS RMS control blocks */
#include "fast_utc.h"		/* local time to GMT conversion */
/*
 * Global(module-wide) variables
 */
struct file_context {
    struct file_context *flink, *blink;		/* Open file list. */
    FILE *fptr;					/* File pointer */
    int cerrno;					/* Status of last operation */
    int vmserrno;				/* VMS condition code if error*/
    int read_mode;				/* true for ru access */
    int write_mode;				/* true for wu access */
    int binary_mode;				/* true for b access */
    int lf_pending;				/* used for cr/lf expansion */
    int at_eof;					/* true if at end-of-file */
    int crlf_newline;				/* getline appends \r if true */
    int dir_mode;				/* Context for find-file */
    struct XABDAT *xabdat;			/* saved XAB block from fopen */
    struct XABPRO *xabpro;			/* saved XAB block from fopen */
    char fspec[512];				/* large enough for ODS 5 */
    char acl_buffer[128];
};
typedef struct file_context file_ctx_struct, *file_ctx;

#ifdef DYNAMIC_MST
#include "mst_share.h"
#else
#define tlog_putlog (*tlog_putlog_cb)
#define TLOG_PUTLOG_CALLBACK tlog_putlog_cb
#ifdef VAXC
globalref
#endif
int http_log_level, tlog_putlog(int level, char *ctlstr, ... );
#endif
int LIB$FIND_FILE(), LIB$FIND_FILE_END(), SYS$FILESCAN();
static file_ctx free_file_ctx;			/* cache of free contexts */
static pthread_mutex_t ctx_list;		/* Guards free list */
static pthread_key_t file_key;			/* Private data for client */
static char *global_prefix = (char *) 0;
static pthread_once_t global_init = PTHREAD_ONCE_INIT;
static $DESCRIPTOR(ff_defdir,"[000000]");
static int tf_read_dir ( int *ff_ctx,		/* Forward reference */
	 char *prev, char *buffer, int bufsize,	int *count );
/*
 * Define global to hold option flags and macros for testing.
 */
static int efs_fixup_flags;			/* bits: 0-efs check, 1-force*/
#define DO_EFS_FIXUP \
	(efs_fixup_flags&(TF_OPT_EFS_FIXUP_COND|TF_OPT_EFS_FIXUP_ALWAYS))
#define DO_EXT_FOPEN (efs_fixup_flags&(TF_OPT_HDR_EDATE|TF_OPT_HDR_ACL))
/************************************************************************/
/* Define context key rundown routine that closes dangling files.
 * Return value 0 for success.
 */
static void tf_rundown ( file_ctx ctx )
{
    file_ctx cur;
    if ( http_log_level > 1 ) tlog_putlog ( 0,
	"Fallback cleanup of file_access context block, adr=!UL!/", ctx );
    if ( !ctx ) return;			/* null context value */
    /*
     * Close open files.
     */
    for ( cur = ctx; cur; cur = cur->flink ) {
	if ( cur->fptr ) { 
	    LOCK_C_RTL
	    fclose ( cur->fptr ); cur->fptr = (FILE *) NULL; 
	    UNLOCK_C_RTL
	}
	if ( cur->dir_mode && cur->lf_pending ) { 
	    LOCK_VMS_RTL
	    LIB$FIND_FILE_END(&cur->lf_pending); cur->dir_mode = 0; 
	    UNLOCK_VMS_RTL
	}
    }
    /*
     * Place blocks onto free-list.
     */
    for ( cur=ctx; cur->flink; cur = cur->flink );
    pthread_mutex_lock ( &ctx_list );
    cur->flink = free_file_ctx;
    free_file_ctx = ctx;
    pthread_mutex_unlock ( &ctx_list );
}
static int null_putlog ( int lvl, char *fmt, ... ) { return 1; }
/************************************************************************/
/*
 * Initialize module-wide variables.  Return value 0 if success, -1 if failure.
 */
static void module_init()
{
    char *dst_opt, *fa_opt;
    int status, default_dst_flag;
    /*
     * Get default daylight savings option and initializ fast UTC module.
     */
    pthread_lock_global_np();
    dst_opt = getenv ( "APPLY_DEFAULT_DST" );

    if ( dst_opt ) default_dst_flag = atoi ( dst_opt );
    else default_dst_flag = 0;
    pthread_unlock_global_np();
#ifdef TLOG_PUTLOG_CALLBACK
    if ( !tlog_putlog_cb ) {
	fprintf(stderr, "Warning, tlog_putlog callback (@%x) not initialized\n", &tlog_putlog_cb );
	tlog_putlog_cb = &null_putlog;
    }
#endif
    
    status = futc_initialize ( default_dst_flag );
    if ( status < 0 ) {		/* serious error */
	tlog_putlog(0,"Error initializing UTC utility functions!/");
    } else if ( status == 0 ) { /* No sys$localtime */
	tlog_putlog(1,
	"Using sys$timezone_differential for time correction!AZ.!/",
	default_dst_flag ? " (plus daylight savings adjust)" : "" );
    }
    /*
     * set special flags for efs fixup from http_efs_fixup_flags:
     *    Bit 0   - Check for efs escape character (^) and convert Unix-style
     *		    specification to VMS if found.
     *    Bit 1   - Always convert Unix-style specifications to VMS format.
     *    Bit 2 - Enable expiration date lookup.
     *    Bit 3 - Enable ACL retrieval.
     */
    fa_opt = getenv ( "HTTP_FILE_ACCESS_FLAGS" );
    efs_fixup_flags = 0;
    if ( fa_opt ) {
	efs_fixup_flags = atoi ( fa_opt );
	tlog_putlog ( 1, "Set file access flags to !SL!/", efs_fixup_flags );
    }
    /*
     * Make context key for running down open files.
     */
    free_file_ctx = (file_ctx) NULL;
    status = INITIALIZE_MUTEX ( &ctx_list );
    SET_MUTEX_NAME(&ctx_list, "OSU file ctx_list")
    status = CREATE_KEY ( &file_key, (pthread_destructor_t) tf_rundown );
    SET_KEY_NAME(file_key,"OSU file rundown")
    if ( status != 0 ) perror ( "Error creating file access context key" );
}
int tf_initialize ( char *prefix )
{
    pthread_once ( &global_init, module_init );
    /*
     * Make copy of prefix string.  !! broken, must use once routine
     */
    pthread_lock_global_np();
    if ( !global_prefix ) {
        if ( prefix ) {
	    global_prefix = malloc ( strlen(prefix)+1 );
	    strcpy ( global_prefix, prefix );
        } else global_prefix = "";
    }
    pthread_unlock_global_np();
    return 0;
}
/***********************************************************************/
/*  The tf_set_options function simply sets the efs_fixup_flags variable
 *  and returns the enabled bits (OS capability may restrict the options).
 */
int tf_set_options ( int flags, int *enabled )
{
    pthread_once ( &global_init, module_init );

    efs_fixup_flags = flags;
#ifdef VAXC
    /*
     * VAXC doesn't support "acc" keyword.
     */
    efs_fixup_flags = (efs_fixup_flags & 
	(TF_OPT_EFS_FIXUP_COND | TF_OPT_EFS_FIXUP_ALWAYS) );
#endif
    *enabled = efs_fixup_flags;
    return 1;
}
/***********************************************************************/
/* In 7.2 C runtime does not handle ODS-5 extended filenames properly
 * when filename is in unix syntax.  Check input specification for
 * this condition and convert to VMS syntax if needed, placing
 * re-written specification in work.  Return value is either
 * original string pointer or work.
 *
 * We assume any EFS specifications properly escape the extended characters
 * with circumflexes.  Note that slashes are not valid in filename
 * so its presence indicates a unix syntax specification.
 */
char *efs_fixup ( char *ident, char *work, int work_size, int fixup_flags )
{
    int i, j, slash_count, last_ddelim;
    char c, *newspec;
    /*
     * Scan for escapes and characters that need escapes.
     */
    if  ( http_log_level > 4 ) tlog_putlog ( 5, 
	"Converting '!AZ' to EFS format!/", ident );
    slash_count = 0;
    for ( i = 0; c = ident[i]; i++ ) {
	if ( c == '/' ) slash_count++;
	if ( c == '^' ) { break; }
    }
    /*
     * Determine if we need to rewrite filespec based upon scan results and
     * options selected by HTTP_FILE_ACCESS_FLAGS.
     */
    if ( slash_count==0 ) return ident;	/* no '/'s => not Unix syntax */
    if ( c ) {
	/* 
	 * At least 1 '^' is present, bit 0 or 1 set will force conversion,
	 * both clear will skip conversion.
	 */
	if ( (fixup_flags&3) == 0 ) return ident;
    } else {
	/*
	 * No '^'s present, bit 1 set will force a conversion.
	 */
	if ( (fixup_flags&2) == 0 ) return ident;
    }
    /*
     * do fixup.  Break ident into elements.
     */
    j = 0;			/* output length */
    i = 1;			/* input position */
    newspec = work;
    work_size = work_size - 2;
    if ( ident[0] == '/' ) {	/* first token is 'device' */
	last_ddelim = 0;	/* directory delimiter position */
    } else {
	newspec[j++] = '[';
	newspec[j++] = '.';	/* sub-directory */
        newspec[j++] = ident[0];
	last_ddelim = 1;
    }
    for ( ; c = ident[i]; i++ ) {
	if ( c == '/' ) {
	    if ( last_ddelim == 0 ) {
	        newspec[j++] = ':';
	        newspec[j++] = '[';
	    } else {
	        newspec[j++] = '.';
	    }
	    if ( ((j-last_ddelim) == 2) && (newspec[j-1] == '.') ) {
		/* ignore /./ sequences */
		j = j - 1;
	    } else if ( ((j-last_ddelim) == 3) &&
		(tu_strncmp ( &newspec[j-1], "..", 2 ) == 0) ) {
		/*
		 * Replace /../ with -
		 */
		j = j - 1;
		newspec[j-1] = '-';
	    }
	    last_ddelim = j-1;
        } else {
	    newspec[j++] = c;
	}
	/*
	 * Give up if we overflow the buffer.
	 */
	if ( j >= (work_size) ) {
	    tlog_putlog ( 0, "efs_fixup buffer overflow converting !AZ!/",
		ident );
	    return ident;
	}
    }
    newspec[j] = '\0';
    /*
     * final fixup.
     */
    if ( newspec[last_ddelim] == '.' ) newspec[last_ddelim] = ']';
    else {
	/* Convert "xxx:[yyy.type" to "xxx:yyy.type" */
	for ( j = last_ddelim; j > 0; --j ) newspec[j] = newspec[j-1];
	newspec++;
    }
    if  ( http_log_level > 4 ) tlog_putlog ( 5, 
	"Converted '!AZ' to EFS '!AZ'!/", ident, newspec );
    return newspec;
}
/***********************************************************************/
/* Define Special callback routines passed to fopen() calls when bit 2 of
 * ef_fixup_flags  is set.  The sav_xabdat callback is called by the
 * CRTL prior to opening the file and the sav_xabdat_abort callback is
 * called if the open fails.  We save the addess of the XABDAT block
 * so the extended tf_header_info call can extract the expiration date.
 * (we assume the XAB is not deallocated until file close).
 *
 * The CRTL examines the return value and treats a negative result as
 * a fatal error.
 */
static struct XABDAT dummy_xab, failed_xab;
static int sav_xabdat ( file_ctx ctx, struct FAB *fab, struct RAB *rab )
{
    struct XAB *xab, *last_xab;
    /*
     * Scan XAB chain linked to the FAB for an XABDAT (Date XAB) block.
     * Use casts since some header files define fab$l_xab as a char ptr.
     */
    ctx->xabdat = (struct XABDAT *) 0;
    ctx->xabpro = (struct XABPRO *) 0;
    for ( last_xab = xab = (struct XAB *) fab->fab$l_xab; xab; 
		xab = (struct XAB *) xab->xab$l_nxt ) {
	if ( (XAB$C_DAT == xab->xab$b_cod) &&
		(efs_fixup_flags&TF_OPT_HDR_EDATE) ) {
	    /*
	     * Save pointer to the XAB.
	     */
	    ctx->xabdat = (struct XABDAT *) xab;

	} else if ( (XAB$C_PRO == xab->xab$b_cod) &&
		(efs_fixup_flags&TF_OPT_HDR_ACL) ) {
	    /*
	     * Save pointer to the protection XAB and supply our own ACL
	     * buffer if missing.
	     */
	    ctx->xabpro = (struct XABPRO *) xab;
	    if ( !ctx->xabpro->xab$l_aclbuf ) {
		ctx->xabpro->xab$l_aclbuf = ctx->acl_buffer;
		ctx->xabpro->xab$w_aclsiz = sizeof(ctx->acl_buffer);
	    }
	}
	last_xab = xab;
    }
    if ( !ctx->xabdat && (efs_fixup_flags&TF_OPT_HDR_EDATE) ) {
	/*
	 * Date xab not found.  Theoretically, we could append our own XAB
	 * to the end of the chain (last_xab) and get the data, but that
	 * is pretty dangerous so we won't.
	 */
    }
    return 0;		/* success always */
}
static int sav_xabdat_abort ( file_ctx ctx, struct FAB *fab, struct RAB *rab )
{
    /*
     * The open failed, zero the xabdat pointer since fopen() rundown will
     * be deallocating it.
     */
    ctx->xabdat = (struct XABDAT *) 0;
    if ( ctx->xabpro ) if ( ctx->xabpro->xab$l_aclbuf == ctx->acl_buffer ) {
	/*
	 * Undo the XAB modification we made.
	 */
	ctx->xabpro->xab$l_aclbuf = (char *) 0;
	ctx->xabpro->xab$w_aclsiz = 0;
    }
    ctx->xabpro = (struct XABPRO *) 0;

    return 0;
}

/***********************************************************************/
/*
 * Open file.  If open failure, error code is formatted.  errmsg may be null.
 * Return value is opaque pointer if success, NULL if error.
 */
void *tf_open ( char *ident, char *mode, char errmsg[256] )
{
    file_ctx cur, new;
    char *mptr;
    /*
     * Allocate context block.
     */
    GET_SPECIFIC(file_key, cur)

    pthread_mutex_lock ( &ctx_list );
    new = free_file_ctx;
    if ( new ) free_file_ctx = new->flink;
    pthread_mutex_unlock ( &ctx_list );
    if ( !new ) {
	/*
	 * Free list is empty, allocate a new block.
	 */
	LOCK_C_RTL
	new = (file_ctx) malloc ( sizeof(file_ctx_struct) );
	UNLOCK_C_RTL
	if ( !new ) return (void *) NULL;
    }
    /*
     * insert into per-thread file list;
     */
    new->blink = (file_ctx) NULL;
    new->flink = cur;
    if ( cur ) cur->blink = new;
    pthread_setspecific ( file_key, new );
    /*
     * Zero fields in block, scan mode argument and set flags.
     */
    new->read_mode = new->write_mode = new->binary_mode = new->dir_mode = 0;
    new->lf_pending = new->at_eof = 0;
    new->crlf_newline = 1;
    new->xabdat = (struct XABDAT *) 0;
    for ( mptr = mode; *mptr; mptr++ ) switch ( *mptr ) {
	case 'd':
	    new->dir_mode = 1;
	    break;
	case 'r':
	    new->read_mode = 1;
	    break;
	case 'w':
	    new->write_mode = 1;
	    break;
	case 'u':
	    new->read_mode = new->write_mode = 1;
	    break;
	case 'b':
	    new->binary_mode = 1;
	default:
	    break;
    }
    /*
     * Attempt to open file.
     */
    if ( !new->dir_mode  || new->read_mode ) {
	char *fspec;
	/*
	 * RTL is not thread-safe, only have 1 file operation active at a time.
	 */
        LOCK_C_RTL
	if ( new->binary_mode ) {
	    fspec = DO_EFS_FIXUP ? efs_fixup ( ident, 
		new->fspec, sizeof(new->fspec), efs_fixup_flags ) : ident;
            new->fptr = DO_EXT_FOPEN ? 
		    fopen ( fspec, mode, "mbc=64", "ctx=stm", 
		    "acc", sav_xabdat, new, "err", sav_xabdat_abort, new )
		: fopen ( fspec, mode, "mbc=64", "ctx=stm" );
		/* "dna=[000000]" ); */
	} else if ( new->dir_mode ) {
	    /*
	     * Append .dir if no suffix.
	     */
	    int i, j, dir_appended;
	    tu_strnzcpy ( new->fspec, ident, sizeof(new->fspec)-6 );
	    i = tu_strlen ( new->fspec );
	    for ( j = i - 1; (j > 0) && new->fspec[j] != '.'
		&& new->fspec[j] != ']' && new->fspec[j] != '>'
		&& new->fspec[j] != '/' ; --j );
	    dir_appended = 0;
	    if ( new->fspec[j] != '.' ) {
		tu_strcpy ( &new->fspec[i], ".dir" );
		dir_appended = 1;
		fspec = new->fspec;
	    } else {
	        fspec = DO_EFS_FIXUP ? efs_fixup ( ident, new->fspec, 
			sizeof(new->fspec), efs_fixup_flags ) : ident;
	    }

            new->fptr = DO_EXT_FOPEN ? fopen ( fspec, "r", "mbc=64",
		    "acc", sav_xabdat, new, "err", sav_xabdat_abort, new )
		: fopen ( fspec, "r" ); /*, "dna=[000000].dir");*/
	    if ( http_log_level > 10 ) tlog_putlog(11,
		"Prospective dir-mode open on '!AZ': !XL, dir-appended: !SL!/",
		fspec, new->fptr, dir_appended );
	    if ( !new->fptr && dir_appended ) {
		/* Retry open without the .dir */
		new->fspec[i+1] = '\0';
                new->fptr = DO_EXT_FOPEN ? fopen ( new->fspec, "r", "mbc=64",
		    "acc", sav_xabdat, new, "err", sav_xabdat_abort, new )
		: fopen ( new->fspec, "r", "mbc=64" );
		if ( new->fptr ) new->dir_mode = 0;
	    }
	} else {
	    fspec = DO_EFS_FIXUP ? efs_fixup ( ident, new->fspec, 
			sizeof(new->fspec), efs_fixup_flags ) : ident;
            new->fptr = DO_EXT_FOPEN ?
		  fopen ( fspec, mode, "mbc=64",
		    "acc", sav_xabdat, new, "err", sav_xabdat_abort, new )
		: fopen ( fspec, mode, "mbc=64" );
	}
        new->cerrno = errno;
        new->vmserrno = vaxc$errno;
        UNLOCK_C_RTL
#ifdef DEBUG
	tlog_putlog(0,"Saved xabdat: !XL dummy=!XL, fail=!XL!/", new->xabdat, 
		&dummy_xab,&failed_xab );
#endif
        if ( !new->fptr ) {
	    /*
	     * Open failure, format error message and cleanup.  Call close to
	     * deallocate block.
	     */
	    if ( errmsg ) {
	        char *errstr; /* , *strerror(); */
	        LOCK_C_RTL
	        errstr = strerror ( new->cerrno, new->vmserrno );
	        tu_strnzcpy ( errmsg, errstr, 255 );
	        UNLOCK_C_RTL
	    }
	    tf_close ( new );
	    new = (file_ctx) NULL;

        } else {
	    /*
	     * Open succeeded but it may be a .dir.
	     */
	    if ( errmsg ) errmsg[0] = '\0';  /* success */

	    if ( new->dir_mode ) {
		/*
		 * see if directory file openned (no suffix) and
		 * change mode to 'd'.
		 */
		int i, slash, dot;

		for ( i=slash=dot=0; ident[i]; i++ )
		    if ( ident[i] == '/' ) slash = i; else
		    if ( ident[i] == '.' ) dot = i;
		if ( slash >= dot ) {
		    /* Was a directory, close file */
		    LOCK_C_RTL
		    fclose ( new->fptr );
		    UNLOCK_C_RTL
		    new->read_mode = 0;
		    new->fptr = (FILE *) NULL;
		} 
		else new->dir_mode = 0;
	    }
        }
    }

    if ( new ) if ( new->dir_mode ) { 
	/*
	 * go into directory mode.
	 */
	int i, j, scnt;		/* src, dst index and slash count */
	char *find_spec, work[512];	/* VMS spec for first FIND_FILE call. */
	new->cerrno = 0;
	new->vmserrno = 1;
	new->fptr = (FILE *) NULL;
	/*
	 * Convert unix syntax for VMS spec for find_file.
	 */
	find_spec = efs_fixup ( ident, work, sizeof(work)-15, 3 );
	if ( find_spec == ident ) {
	    /*
	     * no conversion took place.
	     */
	    find_spec = work;
	    tu_strnzcpy ( find_spec, ident, sizeof(work)-15 );
	}
	/*
	 * Check for illegal constructs: "[-]", "[-.", ".-]", ".-."
	 */
	for ( j = 0; find_spec[j]; j++ ) {
	    char c;
	    c = find_spec[j];
	    if ( ((c=='[') || (c=='<') || (c=='.')) && (find_spec[j+1]=='-')) {
		c = find_spec[j+2];
		if ( (c=='.') || (c==']') || (c=='>') ) {
		    /* 
		     * Illegal syntax (/-.../), mimic same error that fopen()
		     * would produce.
		     */
	            tu_strnzcpy ( errmsg, 
			"Directory specification syntax error", 255 );
		    
		    tf_close ( new );		/* deallocates block */
		    return (file_ctx) NULL;
		}
	    }
	    
	}
	tu_strcpy ( &find_spec[j], "*.*;" );
	/*
	 * Do dummy read, if successful, reset find_file context.
	 */
	if ( j > 1 ) {
	    int status, count;
	    tu_strcpy ( new->fspec, find_spec );
	    new->vmserrno = tf_read_dir 
		( &new->lf_pending, new->fspec, errmsg, 4, &count );
	    if ( new->vmserrno & 1 ) {
	    } else if ( (new->vmserrno == RMS$_FNF) ||
			(new->vmserrno == RMS$_PRV) ) {
		/* Directory is empty or protected, count as success anyway. */
	    } else {
		tf_close ( new );		/* cleanup */
		new = (file_ctx) NULL;
		tu_strcpy ( errmsg, "Error opening directory" );
	    }
	}
    }

    return (void *) new;
}
/***************************************************************************/
/* Close file opened by tf_open.  Return status is 0 for success, EOF for
 * failure.
 */
int tf_close ( void *fptr )
{
    file_ctx ptr, ctx, cur;
    int status;

    ptr = (file_ctx) fptr;		/* cast user's argument */
    /*
     * Find ptr block in threads context list.
     */
    GET_SPECIFIC ( file_key, ctx )
    for ( cur=ctx; cur && (cur != ptr); cur = cur->flink );
    if ( !cur ) return -1;	/* not found */
    /*
     * Close file or rundown filescan.
     */
    if ( cur->fptr ) {
	LOCK_C_RTL
	status = fclose ( cur->fptr );
	UNLOCK_C_RTL
    } else status = 0;	/* fptr null: file not open */
    cur->fptr = (FILE *) NULL;

    if ( cur->dir_mode && cur->lf_pending ) { 
        LOCK_VMS_RTL
	LIB$FIND_FILE_END(&cur->lf_pending); cur->dir_mode = 0; 
        UNLOCK_VMS_RTL
    }
    /*
     * remove block from list.
     */
    if ( cur == ctx ) {
	/*
	 * List head is updated.
	 */
	pthread_setspecific ( file_key, cur->flink );
    }
    if ( cur->flink != (file_ctx) NULL ) cur->flink->blink = cur->blink;
    if ( cur->blink != (file_ctx) NULL ) cur->blink->flink = cur->flink;
    /*
     * Place context block on free list.
     */
    pthread_mutex_lock ( &ctx_list );
    cur->flink = free_file_ctx;
    free_file_ctx = cur;
    pthread_mutex_unlock ( &ctx_list );

    return status;
}
/***************************************************************************/
/*  Read from file into caller's buffer, returning number of characters read
 * or -1 if error.
 */
int tf_read ( void *fptr, char *buffer, int bufsize )
{
    int status;
    file_ctx ctx;

    ctx = (file_ctx) fptr;
    if ( !ctx->fptr ) {
	/*
	 * File is either no opened or openned in 'dir' mode.
	 */
	if ( ctx->dir_mode ) {
	    /* Read directory entries into buffer */
	    int count;
	    ctx->vmserrno = tf_read_dir 
		( &ctx->lf_pending, ctx->fspec, buffer, bufsize, &count );
	    if ( (ctx->vmserrno&1) == 0 ) {
		ctx->cerrno = EVMSERR;
		ctx->at_eof = 1;
		return -1;
	    } else {
		ctx->cerrno = 0;
		if ( !ctx->fspec[0] ) ctx->at_eof = 1;
	        return count;
	    }
	}
	return -1;
    }
    /*
     * Synchronize with global lock to do stdio call.  If I/O generates
     * error, save error codes in thread-private data structure before
     * releasing lock.
     */
    LOCK_C_RTL
    status = fread ( buffer, sizeof(char), bufsize, ctx->fptr );
    if ( status < 0 ) { 
	ctx->cerrno = errno; ctx->vmserrno = vaxc$errno; 
	ctx->at_eof = 1;
    } else { ctx->cerrno = 0; ctx->vmserrno = 1; }
    UNLOCK_C_RTL
    return status;

}
/***************************************************************************/
/* Return status of file's at_eof flag.  If at_eof is true, file pointer is 
 * at end offile and next read will fail.  If at_eof is false, the file
 * pointer may or may not be at the end file (flag is set on the first read
 * failure).
 */
int tf_at_eof ( void *fptr )
{
    file_ctx ctx;
    ctx = (file_ctx) fptr;
    return ctx->at_eof;
}
/***************************************************************************/
/* Change setting of open files crlf_newline flag and return the previous mode.
 * Mode values:
 *    0	   tf_getline() uses single linefeed to delimit lines.
 *    1    tf_getline() delimits lines with carriage-return+linefeed (default).
 */
int tf_set_crlf_newline ( void *fptr, int new_mode )
{
    int old_mode;
    file_ctx ctx;
    ctx = (file_ctx) fptr;
    old_mode = ctx->crlf_newline;
    ctx->crlf_newline = new_mode;
    return old_mode;
}
/***************************************************************************/
/*  Read from file into caller's buffer, returning a CRLF terminated lines
 *  for the read records.  Return value is number of characters moved into
 *  buffer (0 on EOF, -1 on error).  To read a single line, specify 1 as
 * the max_lines parameter.
 */
int tf_getline ( void *fptr, char *buffer, int bufsize, int max_lines )
{
    int status, line_begin, used;
    file_ctx ctx;
    char *line;

    ctx = (file_ctx) fptr;
    if ( !ctx->fptr ) return -1;
    if ( ctx->binary_mode ) {
	ctx->cerrno = EINVAL;
	ctx->vmserrno = 20;
	return -1;
    }
    /*
     * Tack on linefeed if it didn't fit in last buffer.
     */
    used = 0;
    if ( ctx->lf_pending ) {
	buffer[used++] = '\n';
	ctx->lf_pending = 0;
    }
    /*
     * Synchronize with global lock to to stdio call.  If I/O generates
     * error, save error codes in thread-private data structure before
     * releasing lock.
     */
    LOCK_C_RTL
    line_begin = used;
    for ( ; (max_lines > 0) && (used<bufsize-1); --max_lines ) {
        line = fgets ( &buffer[used], bufsize-used, ctx->fptr );
        if ( ! line ) { 
	    ctx->cerrno = errno; ctx->vmserrno = vaxc$errno; 
	    /*
	     * Last line may have ended in zero, not \n.  Get correct EOL.
	     */
	    ctx->at_eof = 1;
	    while ( line_begin < used ) {
		if ( !buffer[line_begin] ) used = line_begin;
		line_begin++;
	    }
	    break;
	} else { 
	    /*
	     * Find line feed and insert carriage return.
	     */
	    char *p;
	    ctx->cerrno = 0; ctx->vmserrno = 1;
	    line_begin = used;			/* Last line loaded */
	    for ( ; used < bufsize; used++ ) if ( buffer[used] == '\n' ) {
		/*
		 * We found line-feed, convert it to carriage return and
		 * append line-feed if room or flag it pending for next getline.
		 */
		if ( ctx->crlf_newline ) buffer[used++] = '\r';
		if ( used < bufsize ) buffer[used++] = '\n';
		else ctx->lf_pending = 1;
		used++;		/* psuedo null */
		break;
	     } else if ( buffer[used] == '\0' ) {
		/*
		 * A null byte in the record may be a data byte or
		 * a long record.
		 */
		if ( feof ( ctx->fptr ) ) { used++; break; }
	     }
	     --used;		/* trim trailing null */
	}
    }
    UNLOCK_C_RTL
    return used;

}
/***************************************************************************/
/* Retrieve error codes saved by tf_routine operation.  Return value is
 * last error number.
 */
int tf_last_error ( void *fptr, int *last_errno, int *last_vms_cond )
{
    file_ctx ctx;

    ctx = (file_ctx) fptr;
    if ( ctx->fptr ) {
        *last_errno = ctx->cerrno;
        *last_vms_cond = ctx->vmserrno;
    } else {
	/*  No open file for this context block */
	*last_errno = EINVAL;
	*last_vms_cond = 20;
    }
    return *last_errno;
}
/***************************************************************************/
/* Read directory contents into buffer.  Return filenames, separating each
 * by a linefeed.  Return value is VMS status code (odd for success).
 */
static int tf_read_dir ( int *ff_ctx,		/* Find_file context */
	char prev_buf[512],
	char *buffer,
	int bufsize,
	int *count )				/* Chars returned, 0 on EOF */
{
    int status, length, used;
    char *p;
    struct { short length, code; char *component; } item[3];
    struct dsc$descriptor ff_result, prev, fspec;
    char spec_buf[512];

    if ( prev_buf[0] ) {
	/*
	 * Retrieve next set of files in directory.
	 */
	fspec.dsc$b_dtype = DSC$K_DTYPE_T;
	fspec.dsc$b_class = DSC$K_CLASS_S;
	fspec.dsc$w_length = 511;
	fspec.dsc$a_pointer = prev_buf;

	ff_result = fspec;
	prev_buf[511] = '\0';

	item[0].code = FSCN$_NAME;
	item[1].code = FSCN$_TYPE;
	item[2].code = item[2].length = 0;	/* terminate list */
	/*
	 * Assume find_file is not thread-reentrant.  Read as many names
	 * as will fit in user's buffer, separating each with an ASCII NUL.
	 */
	LOCK_VMS_RTL
	status = 1;
	for ( used = 0; (status&1); buffer[used++] = '\0' ) {
	    /*
	     * Parse name out of previous_buffer and read next.
	     */
	    status = SYS$FILESCAN ( &ff_result, item, 0 );
	    if ( (status&1) == 0 ) {
		*prev_buf = '\0';
		break;
	    }
	    length = item[0].length + item[1].length;
	    if ( length == 0 ) {
		*prev_buf = '\0';
		status = RMS$_EOF;
		break;
	    }
	    if ( (used +length+1) >= bufsize ) {
		/*
		 * If we added at least 1 name, save buffer for next time.
		 * otherwise truncate and continue with next name.
		 */
		if ( used > 0 ) break;
		length = bufsize - used - 1;
	    }
	    tu_strncpy ( &buffer[used], item[0].component, length );
	    /*
	     * Set the file specification for the LIB$FIND_FILE call.
	     */
	    if ( (used == 0) || (fspec.dsc$a_pointer == prev_buf) ) {
		/*
		 * overwrite the name portion with *.*; and upcase
		 */
		tu_strcpy ( item[0].component, "*.*;" );
	    	tu_strupcase ( prev_buf, prev_buf );

		if ( used > 0 ) {
		    /*
		     * Second time through loop, switch buffer for fspec.
		     */
		    tu_strnzcpy ( spec_buf, prev_buf, sizeof(spec_buf)-1 );
		    fspec.dsc$a_pointer = spec_buf;
		}
		fspec.dsc$w_length = tu_strlen ( fspec.dsc$a_pointer );
	    if  ( http_log_level > 10 )
		tlog_putlog(10,"search spec for find_file: '!AZ'\n", 
			fspec.dsc$a_pointer );
	    }
	    used += length;
	    /*
	     * Get next file and save into ff_result;
	     */
	    status = LIB$FIND_FILE ( &fspec, &ff_result, ff_ctx, &ff_defdir );
	    if  ( http_log_level > 10 ) {
		char *space;
		space = tu_strstr(prev_buf," ");
		if ( space ) *space = '\0';  /* truncate result */
		tlog_putlog(10,"   result: !SL, !AZ!/", status, prev_buf );
		if ( space ) *space = ' ';   /* restore */
	    }
	    /*
	     * Check for no more files.  Convert NoMoreFiles to success.
	     * An empty directory may also be returned.
	     */
	    if ( (status&1) == 0 ) {
	        prev_buf[0] = '\0';			/* mark EOF */
		if ( status == RMS$_NMF ) status = 1;
		buffer[used++] = '\0';
		break;
	    };
	}
	UNLOCK_VMS_RTL
        *count = used;
	return status;

    } else {
	/*
	 * A null fspec indicates all data returned, report EOF.
	 */
	*count = 0;
	return 1;
    }
}
/***************************************************************************/
extern int lib$emul(), lib$ediv(), lib$addx(), lib$subx(), sys$bintim();
static unsigned long base_time[2], zero_time[2];
static pthread_once_t base_time_setup = PTHREAD_ONCE_INIT;

static void init_base_time() {
    int status, tdiff, conv, offset;
    unsigned long diff_time[2];
    $DESCRIPTOR ( unix_zero, "1-JAN-1970 00:00:00.0" );
    /*
     * Convert unix base time to VMS format.
     */
    status = sys$bintim ( &unix_zero, zero_time );
    base_time[0] = zero_time[0];
    base_time[1] = zero_time[1];
}
/***************************************************************************/
/* return extended VMS-specific information about file open on fptr:
 *    edate		Expiration date, converted to unix (GMT) time.
 *    aclbuf		Returns ACL.
 */
int tf_ext_header_info ( void *fptr, 
	unsigned int *edate,
	void *aclbuf, int aclbuf_size, int *acl_len )
{
    struct XABDAT *xab;
    struct XABPRO *xabpro;
    int unix_ticks[2];
    int remainder, ticks_per_sec;
    unsigned int unix_now;
    file_ctx ctx;
    /*
     * Drill though opaque pointer and verify file is still open.
     */
    ctx = (file_ctx) fptr;
    if ( !ctx->fptr ) {
	/*
	 * File is either not open or openned in 'dir' mode.
	 */
	if ( ctx->dir_mode ) {
	    /* Read directory entries into buffer */
	    int count;
	    return -1;
	}
	return -1;
    }
    /*
     * Test if caller wants expiration date returned.
     */
    if ( edate && (efs_fixup_flags&TF_OPT_HDR_EDATE) ) {
	/*
	 * Expiration date requested, init constants for GMT conversion.
	 */
	pthread_once ( &base_time_setup, init_base_time );
	/*
	 * The expiration time, xab->xab$q_edt, is a VMS binary time which
	 * must be converted to a unix GMT time.  Conversion done by
	 * subtracting unix epoch start from time to be converted, dividing
         * to convert to seconds, then converting to GMT.
	 */
	xab = ctx->xabdat;
	if ( xab ) {
	    lib$subx ( &xab->xab$q_edt, zero_time, unix_ticks );
	} else {
	    /* xab missing, use invalid time */
	    unix_ticks[0] = unix_ticks[1] = -1;
	}
#ifdef DEBUG
	tlog_putlog(0,"Unix_tick for exp.: !SL !SL!/", unix_ticks[1], unix_ticks[0] );
#endif
	if ( unix_ticks[1] < 0 ) {
	    /*
	     * Time too early (i.e. before 1970) to be represented as unix time.
	     */
	    *edate = 0;
	    return 0;
	}
	ticks_per_sec = 10000000;	/* 100-nanosecond ticks */
	lib$ediv ( &ticks_per_sec, unix_ticks, &unix_now, &remainder );
	*edate = futc_local_to_gmt ( unix_now );

    } else if ( edate ) {
	/*
	 * Expiration date retrieval not enabled or not supported by OS/RTL.
	 */
	tlog_putlog ( 0, 
		"Attempt to read expiration date when !AZ bit 2 is clear!/",
		"HTTP_FILE_ACCESS_FLAGS" );
	*edate = 0;
    }
    /*
     * Test if caller wants file ACL returned.
     */
    if ( aclbuf && (efs_fixup_flags&TF_OPT_HDR_ACL) && ctx->xabpro ) {
	xabpro = ctx->xabpro;
	*acl_len = 0;
	if ( xabpro->xab$l_aclbuf == ctx->acl_buffer ) {
	    /*
	     * Copy ACL saved in file context block to caller's buffer.
	     */
	    *acl_len = xabpro->xab$w_acllen;
	    if ( aclbuf_size > xabpro->xab$w_acllen ) 
		aclbuf_size = xabpro->xab$w_acllen;
	    if ( aclbuf_size > sizeof(ctx->acl_buffer) ) {
		/*
		 * ACL for file is larger than ctx->acl_buffer, lookup ACL
		 * the long way.
		 */
		
	    } else {
		memcpy ( aclbuf, ctx->acl_buffer, aclbuf_size );
	    }
	}
    } else if ( aclbuf ) {
	/*
	 * ACL retrieval not enabled or not supported by OS/RTL.
	 */
	*acl_len = 0;
    }
    return 0;
}
/***************************************************************************/
/* Perform fstat on indicate file
 */
int tf_header_info ( void *fptr, int *size, unsigned *uic,
	unsigned int *cdate, unsigned int *mdate )
{
    int status;
    file_ctx ctx;
    struct stat statblk;
#define local_time_fstat fstat
#if defined(__VMS_VER) && !defined(_DECC_V4_SOURCE)
#if __VMS_VER >= 70000000
    /* DECC 5.x under version defines fstat to point to a UTC version, for
	force use of old routine (mdate and cdate returned in local tz) */
#undef local_time_fstat
#define local_time_fstat decc$fstat
    int decc$fstat ( int, struct stat * );
#endif
#endif

    ctx = (file_ctx) fptr;
    if ( !ctx->fptr ) {
	/*
	 * File is either not open or openned in 'dir' mode.
	 */
	if ( ctx->dir_mode ) {
	    /* Read directory entries into buffer */
	    int count;
	    return -1;
	}
	return -1;
    }
    /*
     * Initialize constants used in GMT conversion.
     */
    pthread_once ( &base_time_setup, init_base_time );
    /*
     * Synchronize with global lock to do stdio call.  If I/O generates
     * error, save error codes in thread-private data structure before
     * releasing lock.
     */
    LOCK_C_RTL
    status = local_time_fstat ( fileno (ctx->fptr), &statblk );
    if ( status < 0 ) { ctx->cerrno = errno; ctx->vmserrno = vaxc$errno; }
    else { ctx->cerrno = 0; ctx->vmserrno = 1; }
    UNLOCK_C_RTL

    if ( status >= 0 ) {
	/*
	 * Stat succeeded, return data.
	 */
	*uic = statblk.st_uid;
	*size = statblk.st_size;
	*cdate = futc_local_to_gmt ( statblk.st_ctime );
	*mdate = futc_local_to_gmt ( statblk.st_mtime );
    } else {
	*size = *cdate = *mdate = 0;
    }
    return status;

}
/****************************************************************************/
/* Return boolean value indicating whether fptr is openned in 'd' mode.
 */
int tf_isdir ( void *fptr )
{
    int status;
    file_ctx ctx;
    struct stat statblk;

    ctx = (file_ctx) fptr;
    if ( !ctx ) return 0;		/* invalid ctx */
    if ( ctx->fptr ) return 0;
    return ctx->dir_mode;
}
/****************************************************************************/
/* Convert unix binary time to RFC 1123 time format:
 *  	Wdy, DD Mon YY HH:MM:SS TIMEZONE
 *
 * If DATE_FORMAT_850 defined, use older format.
 *  	Weekday, DD-Mon-YY HH:MM:SS TIMEZONE
 *
 */
char *tf_format_time ( unsigned long bintim, char *buffer )
{
#ifdef DATE_FORMAT_850
    static char *weekday[] = {"Thursday", "Friday", "Saturday", "Sunday", 
		"Monday", "Tuesday", "Wednesday"};
#define MONTH_DELIMITER '-'
#else
    static char *weekday[] = {"Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"};
#define MONTH_DELIMITER ' '
#endif
    static char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    unsigned long offset, conv, quad_time[2], final_time[2];
    int status, i, wdy, mon, sys$numtim();
    unsigned short num_time[7];
    /*
     * Initialize constants used in conversion.
     */
    pthread_once ( &base_time_setup, init_base_time );
    /*
     * Convert Unix time (seconds since 1970) to VMS binary time, base
     * time is adjust to result time in UTC (GMT) timezone.
     */
    offset = 0;
    conv = 10000000;		/* 100 nanosecond ticks to seconds */
    lib$emul ( &bintim, &conv, &offset, quad_time );
    lib$addx ( quad_time, base_time, final_time );
    status = sys$numtim ( num_time, final_time );
    /*
     * Get day of week.
     */
    wdy = bintim / 86400;	/* day-number since 1970 */
    wdy = wdy % 7;		/* Day of week */
    for ( i = 0; weekday[wdy][i]; i++ ) buffer[i] = weekday[wdy][i];
    buffer[i++] = ','; buffer[i++] = ' ';
    /*
     * Add date;
     */
    buffer[i] = (char) (48+(num_time[2]/10)); /* Day of month */
    i++;					/* always include leading 0 */
    buffer[i++] = (char) (48+(num_time[2]%10));
    buffer[i++] = MONTH_DELIMITER;

    mon = num_time[1] - 1;		/* Month of year */
    buffer[i++] = month[mon][0]; buffer[i++] = month[mon][1];
    buffer[i++] = month[mon][2]; buffer[i++] = MONTH_DELIMITER;

#ifndef DATE_FORMAT_850
    buffer[i++] = (char) (48+(num_time[0]/1000));  /* high order digit */
    num_time[0] = num_time[0]%1000;		   /* Get rid of millenia */
    buffer[i++] = (char) (48+(num_time[0]/100));   /* century digit */
#endif
    num_time[0] = num_time[0]%100;		/* Get rid of centuries */
    buffer[i++] = (char) (48+(num_time[0]/10));   /* Show last 2 digits of year*/
    buffer[i++] = (char) (48+(num_time[0]%10));
    buffer[i++] = ' ';
    /*
     * add time.
     */
    buffer[i++] = (char) (48+(num_time[3]/10));	/* hours */
    buffer[i++] = (char) (48+(num_time[3]%10));
    buffer[i++] = ':';

    buffer[i++] = (char) (48+(num_time[4]/10)); /* minutes */
    buffer[i++] = (char) (48+(num_time[4]%10));
    buffer[i++] = ':';

    buffer[i++] = (char) (48+(num_time[5]/10)); /* seconds */
    buffer[i++] = (char) (48+(num_time[5]%10));
    buffer[i++] = ' '; 
    /*
     * add timezone.
     */
    buffer[i++] = 'G'; buffer[i++] = 'M'; buffer[i++] = 'T';
    buffer[i++] = '\0';

    return buffer;
}
/****************************************************************************/
/* Convert text time into binary time.  Input format may be 1 of following:
 *    sun,06nov199408:49:37GMT		; RFC 822 (1123)
 *    sunday,6-nov-9408:49:37GMT	; RFC 850 (1036)
 *    sunnonv608:49:371994		; asctime() function.
 *
 * Return value is zero on format error.
 */
unsigned long tf_decode_time ( char *string )
{
    int i, comma, hyphen1, hyphen2, colon1, colon2, status;
    unsigned long vms_time[2], offset[2], seconds, remainder, conv;
    char asctim[32];
    /*
     * Scan string for landmarks.
     */
    comma = hyphen1 = hyphen2 = colon1 = colon2 = -1;
    for ( i = 0; string[i]; i++ ) {
	char c;
	c = string[i];
	if ( c == ',' ) comma = i;
	else if ( c == '-' ) {
	    if ( hyphen1 < 0 ) hyphen1 = i; else hyphen2 = i;
	} else if ( c == ':' ) {
	    if ( colon1 < 0 ) colon1 = i; else colon2 = i;
	}
    }
    /*
     * Do some sanity checks on landmarks.
     */
#ifdef DEBUG
    tlog_putlog(1,"h1: !SL, h2: !SL, c1: !SL, c2: !SL, comma: !SL!/", hyphen1,
    	hyphen2, colon1, colon2, comma );
#endif
    if ( (hyphen1 > hyphen2) || (colon1 > colon2) || (hyphen2 > colon1) ||
	(comma > colon1) || (colon1+3 != colon2) || (colon2+5 >= i) ) return 0;
    /*
     * Rearrange components into VMS absolute time string:
     *      "DD-MMM-YYYY HH:MM:SS"
     *       012345678901234567890
     */
    tu_strcpy ( asctim, "01-JAN-1970 00:00:00.00" );
    asctim[19] = string[colon2+2];
    asctim[18] = string[colon2+1];
    asctim[16] = string[colon2-1];
    asctim[15] = string[colon2-2];
    asctim[13] = string[colon1-1];
    asctim[12] = string[colon1-2];		/* Finished HH::MM::SS */

    if ( comma >= 0 ) {
	asctim[10] = string[colon1-3];
	asctim[9] = string[colon1-4];
	if ( hyphen2 != colon1-5 ) {
	    /*
	     * No hyphens in time string, synthesize them.
	     */
	    asctim[8] = string[colon1-5];
	    asctim[7] = string[colon1-6];	/* 4 digit year */
	    hyphen2 = colon1 - 6;
	    hyphen1 = colon1 - 9;
	} else if ( asctim[9] < '5' ) {
	    /* 2-digit year less than 50, bump up past 2000 */
	    asctim[8] = '0';
	    asctim[7] = '2';
	}
	asctim[5] = string[hyphen2-1];
	asctim[4] = string[hyphen2-2];
	asctim[3] = string[hyphen2-3];
	asctim[1] = string[hyphen1-1];
	asctim[0] = string[hyphen1-2];

    } else {	/* ctime() */
	asctim[1] = string[colon1-3];
	if ( (string[colon1-4] >= '0') && (string[colon1-4] <= '9') ) {
	    if ( colon1 < 7 ) return 0;
	    asctim[0] = string[colon1-4];
	    asctim[3] = string[colon1-7];
	    asctim[4] = string[colon1-6];
	    asctim[5] = string[colon1-5];
	} else {
	    if ( colon1 < 6 ) return 0;
	    asctim[3] = string[colon1-6];
	    asctim[4] = string[colon1-5];
	    asctim[5] = string[colon1-4];
	}
	asctim[10] = string[colon2+6];
	asctim[9] = string[colon2+5];
	asctim[8] = string[colon2+4];
	asctim[7] = string[colon2+3];
    }
    /*
     * Convert string to binary time.  Month must be in upper case.
     */
    tu_strupcase ( asctim, asctim );
    status = futc_decode_vms_date ( 23, asctim, (time_t *) &seconds );
    if ( (status&1) == 0 ) return 0;		/* Invalid time */
    return seconds;
}
