/* 
 * Support routines to let outside processes access a web server's realtime
 * counters.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <descrip.h>		/* VMS string descriptors */
#include <ssdef.h>		/* VMS system service completion codes */
#include <lckdef.h>		/* VMS distrib. lock manager */
#include <secdef.h>		/* VMS global sections */
#include <syidef.h>		/* VMS system information */

#include "counters.h"
#include "icb_access.h"
#include "icb_pool.h"

static char lock_name[64];
static $DESCRIPTOR(lock_name_dx,lock_name);
static struct {
    int match_criteria;
    int version;
} pool_ident = { SEC$K_MATEQU, ICB_POOL_VERSION };
/*
 * Define context structure that handle will point to.
 */
struct icb_ctx {
    struct icb_lock_block lksb;		/* lock status block */
    struct pool_def *p;			/* Pointer to shared memory */
    int wait_state;
};

int SYS$ENQW(), SYS$DEQ(), SYS$CRMPSC(), SYS$HIBER(), SYS$WAKE(), LIB$GETSYI();
int LIB$EMUL(), SYS$SETIMR(), SYS$CANTIM();
/*
 * Utility function to compute number of valid ICB from pointers in
 * lock status block.
 */
static int icb_count ( struct icb_ctx *ctx )
{
    if ( ctx->lksb.status == SS$_VALNOTVALID ) return 0;
    if ( ctx->lksb.pid == 0 ) return 0;
    if ( ctx->lksb.first_valid < 0 ) return 0;
    if ( ctx->lksb.last_valid < ctx->lksb.first_valid ) {
	/*
	 * Valid range wraps past end of table, sum the 2 segments.
	 */
	return (ctx->lksb.last_valid+(ctx->p->icb_count-ctx->lksb.first_valid));
    }
    /*
     * Valid range is consecutive locations in icb array.
     */
    return ctx->lksb.last_valid - ctx->lksb.first_valid + 1;
}
/****************************************************************************/
/* Define function to stall until Lock status block changes.  Upon return,
 * the pool lock will be held in protected read mode.
 */
static void pool_blocked ( int *flag )
{
    *flag = 1;
    SYS$WAKE(0,0);
}
static void timeout_ast ( int *flag )
{
    *flag = 2;
    SYS$WAKE(0,0);
}
/*
 * Stall process until at least min_count blocks are in global section.
 */
static int wait_for_icb ( struct icb_ctx *ctx, int min_count, int timeout )
{
    int status, tstatus, sequence, off, ticks_per_sec, count;
    int delta[2];
    /*
     * Convert timeout seconds to VMS delta time.
     */
    off = 0;
    ticks_per_sec = -10000000;		/* 100-nsec per tick */
    LIB$EMUL ( &timeout, &ticks_per_sec, &off, delta );
    /*
     * Loop until sequence number in lock status block changes.
     */
    sequence = ctx->lksb.sequence;
    for ( ; ; ) {
	/*
	 * Re-fetch lock value block and exit loop if sequence has changed
	 * and at least 1 valid block in global section.
	 */
	ctx->wait_state = 0;
	status = SYS$ENQW ( 6, LCK$K_PRMODE, &ctx->lksb, 
		LCK$M_VALBLK|LCK$M_CONVERT,
		0, 0, 0, &ctx->wait_state, pool_blocked, 0, 0, 0 );
    	if ( (status&1) == 1 ) status = ctx->lksb.status;
	if ( (status&1) == 0 ) break;
	if ( (sequence != ctx->lksb.sequence) ) {
	    count = icb_count ( ctx );
#ifdef DEBUG
printf("Wait over, sts: %d, lksb: %x %d %d %d\n", status, ctx->lksb.pid,
ctx->lksb.first_valid, ctx->lksb.last_valid, ctx->lksb.sequence );
#endif
	    if ( count >= min_count ) break;
	}
	/*
	 * Set timer for aborting wait.
	 */
	tstatus = SYS$SETIMR ( 7, delta, timeout_ast, &ctx->wait_state, 0 );
	if ( (tstatus&1) == 0 ) { 
	    /*
	     * Failed to queue timer entry,  set flag to force return
	     * of tstatus after we clear the blocking AST.
	     */
	    ctx->wait_state = 3;
	};
	/*
	 * wait for blocking AST to signal us and convert again to
	 * clear blocking AST.
	 */
	while ( ctx->wait_state == 0 ) SYS$HIBER();
	SYS$CANTIM ( &ctx->wait_state, 0 );
	status = SYS$ENQW ( 6, 
		(ctx->wait_state==1) ? LCK$K_CRMODE : LCK$K_PRMODE, 
		&ctx->lksb, LCK$M_VALBLK|LCK$M_CONVERT,
		0, 0, 0, 0, 0, 0, 0, 0 );
    	if ( (status&1) == 1 ) status = ctx->lksb.status;
#ifdef DEBUG
printf("wait ended with flag: %d (%d), lksb: %d %d %d\n", ctx->wait_state, 
status, ctx->lksb.sequence,ctx->lksb.first_valid, ctx->lksb.last_valid );
#endif
	if ( (status&1) == 0 ) break;
	/*
	 * Check whether interrupt was timeout.
         */
	if ( ctx->wait_state == 2 ) {
	    status = SS$_TIMEOUT;
	    break;
	} else if ( ctx->wait_state == 3 ) {
	    status = tstatus;		/* setimr call failed */
	    break;
	}
    }
    ctx->wait_state = 0;
    return status;
}
/****************************************************************************/
/* Initialization routine.
 */
int icbmap_open ( 
   int port,				/* primary server port number */
    int sysflag,			/* if non-zero use system lock. */
    int timeout,			/* seconds */
    icbmap_handle *handle )		/* returns handle. */
{
    void *inaddr[2], *outaddr[2];
    int flags, size, system, prot, status;
    int csid, code;
    struct icb_ctx tmp, *ctx;
    /*
     * Construct lock/global section name.
     */
    *handle = (void *) 0;
    code = SYI$_NODE_CSID;
    status = LIB$GETSYI ( &code, &csid, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    sprintf ( lock_name, ICB_POOL_NAME_FMT, csid, port );
    lock_name_dx.dsc$w_length = strlen(lock_name);
    /*
     * Map global section.
     */
    size = ICB_POOL_SECTION_SIZE;
    inaddr[0] = 0;			/* choose P0 region */
    inaddr[1] = (void *)(size - 1);
    flags = SEC$M_GBL | SEC$M_WRT | SEC$M_PAGFIL | SEC$M_EXPREG;
    if ( sysflag ) flags |= SEC$M_SYSGBL;
    prot = 0x0EE00;		/* wo:r, gr:r, ow: rwed, sy: rwed */

    status = SYS$CRMPSC ( inaddr, outaddr, 0, flags,
	&lock_name_dx, &pool_ident, 0, 0, size/512, 0, prot, 128 );
    if ( (status&1) == 0 ) {
	return status;
    }
    tmp.p = outaddr[0];
    tmp.wait_state = 0;
    /*
     * Get global lock.
     */
    flags = LCK$M_VALBLK;
    if ( sysflag ) flags |= LCK$M_SYSTEM;
    status = SYS$ENQW ( 6, LCK$K_PRMODE, &tmp.lksb, LCK$M_VALBLK,
	&lock_name_dx, 0, 0, 0, 0, 0, 0, 0 );
    if ( (status&1) == 1 ) status = tmp.lksb.status;
    if ( status == SS$_VALNOTVALID ) {
	printf("Invalid value block\n");
	tmp.lksb.pid = 0;
	tmp.lksb.first_valid = -1;
	tmp.lksb.last_valid = -1;
	tmp.lksb.sequence = 0;
	if ( timeout > 0 ) wait_for_icb ( &tmp, 0, timeout );
    }
    /*
     * Do sanity checks on lock status block and global section.
     */
    if ( tmp.p->icb_size != sizeof(struct interval_counter) ) {
    }
    /*
     * Lower to CR mode.
     */
    status = SYS$ENQW ( 6, LCK$K_CRMODE, &tmp.lksb, LCK$M_VALBLK|LCK$M_CONVERT,
	0, 0, 0, 0, 0, 0, 0, 0 );
    if ( (status&1) == 1 ) status = tmp.lksb.status;
    /*
     * allocate a handle and copy context into it.
     */
    if ( status&1 ) {
        ctx = (struct icb_ctx *) malloc ( sizeof(tmp) );
        *ctx = tmp;
        *handle = (void *) ctx;
    } else {
	/* Cleanup */
	SYS$DEQ ( tmp.lksb.id, 0, 0, 0 );
    }

    return status;
}
/**************************************************************************/
int icbmap_read ( 
	icbmap_handle handle,		/* handle returned by init() */
	struct interval_counter *icb, 	/* Array to recieve read blocks */
	int n,	 			/* size of icb array */
	int timeout,			/* Max seconds to wait */
	int *num_read )			/* Number of icb block written */
{
    int status, new_count, i, ndx;
    struct icb_ctx *ctx;
    /*
     * Expose the handle.
     */
    ctx = (struct icb_ctx *) handle;
    /*
     * Raise lock to block changes to pool structure while reading it,
     * retrieving lock value block.
     */
    *num_read = 0;
    status = SYS$ENQW ( 6, LCK$K_PRMODE, &ctx->lksb, LCK$M_VALBLK|LCK$M_CONVERT,
	0, 0, 0, 0, 0, 0, 0, 0 );
    if ( (status&1) == 1 ) status = ctx->lksb.status;
#ifdef DEBUG
printf("initial enq, sts: %d, lksb: %x %d %d %d\n", status, ctx->lksb.pid,
ctx->lksb.first_valid, ctx->lksb.last_valid, ctx->lksb.sequence );
#endif
    if ( (status&1) == 1 ) new_count = icb_count ( ctx );
    else return status;
    /*
     * Stall until next update if wait_val is non-zero, value being
     * interpreted as maximum number of seconds to wait.
     */
    while ( (timeout > 0) && (new_count <= 0) ) {
	status = wait_for_icb ( ctx, 1, timeout );
	if ( (status&1) == 0 ) return status;
	new_count = icb_count ( ctx );
    }
    /*
     * Copy up to last n blocks.
     */
    *num_read = (n > new_count) ? new_count : n;
    ndx = ctx->lksb.last_valid;
    for ( i = (*num_read)-1; i >= 0; --i ) {
	icb[i] = ctx->p->icb[ndx];
	ndx = (ndx > 0) ? ndx-1 : ctx->p->icb_count-1;
    }
    /*
     * Lower lock.  PR mode does not update value block when lowered
     */
    status = SYS$ENQW ( 6, LCK$K_CRMODE, &ctx->lksb, LCK$M_VALBLK|LCK$M_CONVERT,
	0, 0, 0, 0, 0, 0, 0, 0 );
    if ( (status&1) == 1 ) status = ctx->lksb.status;
    return status;
}

/*
 * Test program, usage:
 *   icb_access [port] [timeout] [stalls]
 */
static void show_icb ( FILE *fp, struct interval_counter *icb )
{
    int i, ts_len, SYS$ASCTIM();
    static char timestamp[24];
    static $DESCRIPTOR(timestamp_dx, timestamp);

    ts_len = 0;
    timestamp_dx.dsc$w_length = sizeof(timestamp)-1;
    SYS$ASCTIM ( &ts_len, &timestamp_dx, icb->reset_time, 1 );
    /* while (ts_len < timestamp_dx.dsc$w_length) timestamp[ts_len++] = ' ';*/
    timestamp[ts_len] = '\0';

    fprintf(fp, "%s, peak concur: %d, resp: %d, data bytes: %d,", 
	timestamp, icb->peak_concurrency, icb->responses, icb->data_bytes );
    fprintf ( fp, " req: %d %d %d %d, timeouts: %d\n",
	icb->requests.file, icb->requests.script, icb->requests.redirect,
	icb->requests.invalid, icb->tcp_timeouts );
}
int main ( int argc, char **argv )
{
    int port, j, status, interval, fetched, i, ts_len, SYS$ASCTIM(), flag;
    int timeout;
    char *envval;
    struct interval_counter icb[40];
    void *handle;

    port = 80;
    if ( argc > 1 ) {
	if ( (strcmp ( argv[1], "?" ) == 0) || (strcmp(argv[1],"-?")==0) ||
		(strcmp(argv[1],"-h") == 0) ) {
	    printf("Usage: icb_access [port] [timeout] [stalls]\n");
	    return 1;
	}
	port = atoi ( argv[1] );
    }
    envval = getenv("ICB_ACCESS_SYSFLAG");
    flag = envval ? atoi(envval) : 0;
    status = icbmap_open ( port, flag, 180, &handle );
    printf("pool map status: %d, handle addr: %x\n", status, handle );
    if ( (status&1) == 0 ) return status;

    timeout = (argc > 2) ? atoi(argv[2]) : 0;
    status = icbmap_read ( handle, icb, 40, timeout, &fetched );
    printf( "icbmap_read status: %d, fetched: %d\n", status, fetched );
    if ( (status&1) == 0 ) return status;

    for ( i = 0; i < fetched; i++ ) {
	show_icb ( stdout, &icb[i] );
    }
    if ( argc> 3 ) {
	int count = atoi ( argv[3] );
	for ( j = 0; j < count; j++ ) {
	    /* printf ("Waiting for new data...\n"); */
	    status = wait_for_icb ( (struct icb_ctx *) handle, 1, 120 );
	    if ( (status&1) == 0 ) break;
	    status = icbmap_read ( handle, icb, 1, timeout, &fetched );
	    if ( (status&1) == 0 ) 
		printf("Fetched next:  %d, %d\n", status, fetched );
	    for ( i = 0; i < fetched; i++ ) {
		show_icb ( stdout, &icb[i] );
	    }
	}
    }
    return status;
}
