/*
 * This module handles management transactions requested by privileged
 * manage programs.  When the remote port and address of a connection is
 * the management port and address, the listener calls this routine rather
 * than create a normal client thread.
 *
 * Requests:				    return value
 *	SHUTDOWN[/grace-period]			1
 *	RESTART[/grace-period]			3
 *	INVCACHE				0
 *	DSPCACHE				0
 *	NEWLOG					0
 *	NEWTRACE[/log-level]			0
 *      STATISTICS[/ZERO]			0
 *      PEEK_MAILBOX_UNIT			0
 *	HELP					0
 *
 * If the return value is non-zero, the port receiving the request is
 * shutdown and the thread exits with the return value as its status.
 *
 * The http_manage_request function is called directly by the port listener 
 * thread created by ts_declare_tcp_port() and therefore has the following
 * considerations:
 *
 *	> Context is not fully initialized, only safe actions are ts_tcp_read
 *	  and ts_tcp_write.
 *
 *	> New connections on the receiving port are suspended until this
 *	  routine returns.  Note that since more than one listener thread is
 *	  allowed, multiple active management requests are still possible.
 *
 *	> Stacksize is much smaller than for a normal client port, limiting
 *	  max request size
 *
 *	> Scheduler policy is FIFO, rather than DEFAULT, giving this routine
 *	  priority over client threads.
 *
 *  Author:	David Jones
 *  Date:	20-AUG-1994
 *  Revised:	26-AUG-1994	Support timeout argument on SHUTDOWN, RESTART.
 *  Revised:	27-AUG-1994	Support NEWLOG and NEWTRACE
 *  Revised:	21-APR-1995	Support STATISTICS
 *  Revised:	 2-SEP-1995	Include statistics counters in log file.
 *  Revised:	19-MAR-1996	Fiddle with statistics format.
 *  Revised:	25-JUL-1996	Add script-based management support.
 *  Revised:	26-OCT-1996	Added missing null in newtrace response.
 *  Revised:	 7-DEC-1996	Include keep-alive offered counter.
 *  Revised:	26-AUG-1997	Support management operations on file cache.
 *				Fix bug in statistics/zero.
 *  Revised:	12-SEP-1997	One more try at fixing statistics display.
 *  Revised:	8-DEC-1997	Add PEEK_MAILBOX command
 *  Revised:	12-DEC-1997	propagate log_level change to base_mst image
 *				via http_base_mst_globals pointer.
 *  Revsied:	1-JAN-1999	Show extra information in statisics display.
 *  Revised:	26-FEB-2000	Display histograms using 4 columns.
 *  Revised:	 5-MAR-2000	Fix for non-VMS support (FUTC calls).
 *  Revised:	20-APR-2000	Fix format problem in requests/connect and
 *				typo in histogram (1->0).
 */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "tutil.h"
#include "tserver_tcp.h"
#include "counters.h"
#include "script_manage.h"
#include "file_access.h"
#include "file_cache.h"
#ifdef VMS
#include "fast_utc.h"
#endif
int http_log_level;			/* global variable */
#ifdef VAXC
#define http_base_mst_globals http_base_mst_globals_p
globalref
#endif
#define COLUMATOR_GROUP_SIZE 240
struct columator {
    int count;				/* number of feilds defined */
    int lflag;				/* true if putlog desired */
    void *ctx;
    char fields[COLUMATOR_GROUP_SIZE][20];
};
struct global_share {
   int log_level;
   int (*log_callback)(int,char*,...); 
   int reentrant_c, reentrant_vms;
} *http_base_mst_globals;
int http_session_style=0;		/* 0-thread/request, 1-thread/connect */
int tlog_putlog(), tlog_reopen();
int peek_mailbox_unit;			/* diagnositic mailbox unit */
static int convert_int ( char *string );
static int display_client(void *ctx, int flags, struct client_counter *client);
static int display_hostcount ( void *ctx, struct host_counter *host,int lflag );
static int display_keepalive 
	( void *ctx, int *hist, int size, int thread_total, int lflag,
	struct columator *col );

static char *helpmsg = "200 Management commands:\r\n\
     HELP				Return this message\r\n\
     INVCACHE				Invalidate document cache\r\n\
     DSPCACHE				Show current cache entries\r\n\
     NEWLOG				Create new access log\r\n\
     NEWTRACE[/logger-level]		Create new trace file/set log level\r\n\
     RESTART[/timeout]			Restart server (default timeout=10)\r\n\
     SHUTDOWN[/timeout]			Shutdown (default timeout=10)\r\n\
     STATISTICS[/ZERO] 			Display counters\r\n\
\r\nThe command may be in either upper or lower case.";

/*************************************************************************/
/* The following routines manager the 'columator' (sic) structure for
 * displaying histograms as 4 columns instead of 1.  Histogram elements
 * are added to the current display set by calling format_hist_entry,
 * and dumped to the tcp link when flush_columator() is called.
 */
static void init_columator ( void *ctx, int lflag, struct columator *col )
{
    col->count = 0;
    col->lflag = lflag;
    col->ctx = ctx;
}

static int flush_columator ( struct columator *col )
{
    int ios, i, c1, c2, c3, c4, height, len;
    char line[100];

    if ( col->count == 0 ) return 1;
    height = (col->count+3) / 4;
    c1 = 0;
    c2 = height;
    c3 = c2 + height;
    c4 = c3 + height;
    for ( c1 = 0; c1 < height; c1++ ) {
	tu_strcpy ( line, "\r\n  " ); len = 4;
	if ( c1 < col->count ) {
	    tu_strcpy ( &line[len], col->fields[c1] ); 
	    for ( i = len + tu_strlen(col->fields[c1]); i < (len+19); i++ ) 
		line[i] = ' ';
	    len += 19;
	}

	if ( c2 < col->count ) {
	    tu_strcpy ( &line[len], col->fields[c2] ); 
	    for ( i = len + tu_strlen(col->fields[c2]); i < (len+19); i++ ) 
		line[i] = ' ';
	    len += 19; c2++;
	}

	if ( c3 < col->count ) {
	    tu_strcpy ( &line[len], col->fields[c3] ); 
	    for ( i = len + tu_strlen(col->fields[c3]); i < (len+19); i++ ) 
		line[i] = ' ';
	    len += 19; c3++;
	}

	if ( c4 < col->count ) {
	    tu_strcpy ( &line[len], col->fields[c4] );
	    len += tu_strlen ( col->fields[c4] ); c4++;
	}
	line[len] = '\0';
	ios = ts_tcp_write ( col->ctx, line, len );
	if ( col->lflag ) {
	    tlog_putlog ( 0, "MGR: !AZ!/", &line[2] );
	}
	if ( (ios&1) == 0 ) break;
    }
    col->count = 0;
}
/*
 * Define function to format arguments i,n to " ii: nnn     " and
 * output.
 */
static void format_hist_entry ( int index, int count,
   struct columator *col )
{
    int len, ios;
    char field[32];

    tu_strcpy ( field, "    " );
    tu_strint ( index, &field[index < 10 ? 3 : index < 100 ? 2 : 
		index < 1000 ? 1 : 0] );
    len = tu_strlen ( field );
    field[len++] = ':';
    field[len++] = ' ';
    tu_strint ( count, &field[len] );
    if ( col->count >= COLUMATOR_GROUP_SIZE ) {
	flush_columator ( col );
    }
    tu_strcpy ( col->fields[col->count], field );
    col->count++;
}
/***********************************************************************/
/* Main function for handling command received via the management interface.
 */
int http_manage_request ( void *ctx,	/* I/O context (tserver_tcp handle) */
	short port,			/* Local port number */
	int *shutdown_time )
{
    int status, i, length, valid, clen, flags;
    char *stsmsg, *slash, cmd[320], stsbuf[100];
    struct tu_streambuf stream;
    struct columator col;
    /*
     * Initialize stream and read command (delimited by newline).
     */
    tu_init_stream ( ctx, ts_tcp_read, &stream );

    status = tu_read_line ( &stream, cmd, sizeof(cmd)-1, &length );
    if ( (status&1) == 0 ) return 0;	/* ignore errors */
    /*
     * Upcase command string.
     */
    status = 0;
    *shutdown_time = 10;		/* Default to 10 seconds */
    cmd[length] = '\0';
    tlog_putlog ( 1, "Management request to port !SW: '!AZ'!/", port, cmd );
    tu_strupcase ( cmd, cmd );
    /*
     * Check if script-command pending. and that command is TRIGGER, if so
     * verify routine will clear the pending state and rewrite cmd string.
     * Error will abort connection.
     */
    if ( 0 == http_verify_script_request ( ctx, cmd, sizeof(cmd)-1, 
		&length )  ) return 0;
    /*
     * look for first slash, which delimts command from switches.
     */
    for ( slash = "", i = 0; cmd[i]; i++ ) if ( cmd[i] == '/' ) {
	cmd[i] = '\0';
	slash = &cmd[i+1];
	break;
    }
    clen = i;
    if ( (clen == 0) || (0 == tu_strncmp(cmd,"HELP", clen)) ) {
	/*
	 * Return statically declared help message.
	 */
	stsmsg = helpmsg;

    } else if ( (clen > 1) && (0 == tu_strncmp(cmd,"SHUTDOWN",clen)) ) {
	/*
	 * Client is requesting port shutdown, check for optional
	 * parameter (number of seconds to give current sessions to complete).
	 */
	if ( *slash ) *shutdown_time = convert_int ( slash );
	/*
	 * Set return status to value that causes port to shutdown,
	 * waiting at most shutdown_time seconds.
	 */
	stsmsg = "201 Shutting down server port, active: ";
	status = 1;

    } else if ( 0 == tu_strncmp(cmd,"RESTART",clen) ) {
	/*
	 * Client is requesting server restart, check for optional
	 * parameter (number of seconds to give current sessions to complete).
	 */
	if ( *slash ) *shutdown_time = convert_int ( slash );
	/*
	 * For restart, set status to value that causes port listener to
	 * rundown with status that causes server restart.
	 */
	stsmsg = "201 Restarting server, active clients: ";
        status = 3;

    } else if ( 0 == tu_strncmp(cmd,"INVCACHE", clen) ) {
	/*
	 * Mark document cache invalid.
	 */
	int http_invalidate_doc_cache();
	http_invalidate_doc_cache();	/* clear small doc cache */
	stsmsg = "200 Cache marked invalid\r\n";

    } else if ( 0 == tu_strncmp(cmd,"DSPCACHE", clen) ) {
	struct tcache_context tc;
	int count=0;
	stsmsg = "200 Dump of cache entries\r\n";
	ts_tcp_write ( ctx, stsmsg, tu_strlen(stsmsg) );
	tc.cache_item = (tcache_item_ptr) 0;	/* reset context */
	while ( tfc_display_entries(&tc, cmd, sizeof(cmd), &length, &count)) {
	    if ( length > 0 ) ts_tcp_write ( ctx, cmd, length );
	}
	if ( count > 0 && length > 0 ) ts_tcp_write ( ctx, cmd, length );
        stsmsg = "\r\n";		/* handle final write */
    } else if ( (clen > 3) && (0 == tu_strncmp(cmd,"NEWLOG", clen) )) {
	/*
	 * Create a new access log file.
	 */
	if ( tlog_reopen ( -1 ) )
	    stsmsg = "200 New version of access log opened\r\n";
	else
	    stsmsg = "500 No access log or error on re-open\r\n";

    } else if ( (clen > 3) && (0 == tu_strncmp(cmd,"NEWTRACE", clen)) ) {
	int level;
	/*
	 * Create a new trace file, setting optional logger level.
	 */
	if ( *slash ) level = convert_int ( slash );
	else level = http_log_level;

	if ( tlog_reopen ( level ) ) {
	    /*
	     * Format status line with old and new log levels.
	     */
	    stsmsg = stsbuf;
	    tu_strnzcpy ( stsmsg, 
		"200 New version of trace log opened at logger level: ", 60 );
	    i = tu_strlen ( stsmsg );
	    tu_strint ( level, &stsmsg[i] ); i = tu_strlen ( stsmsg );
	    tu_strnzcpy ( &stsmsg[i], ", old level: ", 15 );
	    i = tu_strlen ( stsmsg );
	    tu_strint ( http_log_level, &stsmsg[i] ); i = tu_strlen ( stsmsg );
	    stsmsg[i++] = '\r'; stsmsg[i++] = '\n'; stsmsg[i++] = '\0';
	    /*
	     * The following is an unsafe (unsynchonized) update of 
	     * http_log_level.  We'll risk it because the worst that
	     * could happen is a thread in progress fails a test to 
	     * call tlog_putlog.
	     */
	    http_log_level = level;
	    http_base_mst_globals->log_level = level;
	} else
	    stsmsg = "500 No trace log or error on re-open\r\n";

    } else if ( (clen > 1) && (0 == tu_strncmp(cmd,"STATISTICS", clen)) ) {
	/*
	 * Get counters.
	 */
	if ( http_counters ) {
	    int i, j, count, total, ios, lflag, zflag, high_nonzero, height;
	    time_t ztime, now;
	    char tstring[40];

	    tu_strcpy ( stsbuf,"200 counter display, zeroed at " );
	    http_lock_counters();
	    /* set lflag to zero to inhibit trace file output */
	    lflag = *slash ? 
		tu_strncmp ( slash, "NOLOG", tu_strlen(slash) ) : 1;
	    zflag = *slash ?
		(tu_strncmp ( slash, "ZERO", tu_strlen(slash) )==0) : 0;
	    /*
	     * Convert zero time string to unix time (seconds) and get
	     * current time as well so we can compute seconds since counters
	     * last zeroed.
	     */
	    tu_strnzcpy (tstring, http_counters->zero_time, sizeof(tstring)-1);
#ifdef VMS
	    for ( i = j = 0; tstring[i]; i++ ) {
		/* Remove spaces from string for tf_decode_time() */
		if ( i != j ) tstring[j] = tstring[i];
		if ( tstring[i] != ' ' ) j++;
	    }
	    tstring[j] = '\0';
	    ztime = futc_local_to_gmt ( tf_decode_time ( tstring ) );
	    now = futc_current_time ( &now );
#else
	    if ( tstring[0] ) {
	        /* Decode date string format of 'month day hh:mm:ss year' */
	        struct tm zero_time;
	        strptime ( tstring, "%h %e %T %Y", &zero_time );
	        ztime = mktime ( &zero_time );
	    } else ztime = 0;
	    now = time ( &now );	/* assume function is thread safe */
#endif

	    tu_strcpy ( &stsbuf[31], http_counters->zero_time );
	    i = tu_strlen ( stsbuf );
	    tu_strcpy ( &stsbuf[i], " (" ); i += 2;
	    tu_strint ( now - ztime, &stsbuf[i] ); i += tu_strlen(&stsbuf[i]);
	    tu_strcpy ( &stsbuf[i], " seconds)" );
	    ts_tcp_write ( ctx, stsbuf, i+9 );
	    if ( lflag ) tlog_putlog(0,
		"MGR: counters snapshot at !%D, zeroed at !AZ (!UL second!%S)!/", 0,
		http_counters->zero_time, now - ztime );
	    ios = 1;
	    high_nonzero = -1;
	    init_columator ( ctx, lflag, &col );
	    for ( total = i = 0; i < http_counters->active_size; i++ ) {
		count = http_counters->active_hist[i];
		if ( count > 0 ) {
		    if ( total == 0 ) {
			ts_tcp_write ( ctx, 
			"\r\n\r\nConcurrency histogram (c-level: threads):", 
				45 );
			if ( lflag ) tlog_putlog(0,
			"MGR: Concurrency histogram (c-level: threads)!/");
		    }
		    format_hist_entry ( i, count, &col );
		    high_nonzero = i;
		    total += count;
		}
	    }
	    if ( total > 0 ) flush_columator ( &col );

	    tu_strcpy ( stsbuf, "\r\n\r\nTotal threads: " );
	    tu_strint ( total, &stsbuf[19] );
	    if ( (ios&1) ) ts_tcp_write ( ctx, stsbuf, tu_strlen(stsbuf) );
	    if ( lflag ) tlog_putlog(0,"MGR: !AZ!/", &stsbuf[4] );

	    if ( http_counters->active_sp > 0 ) {
		ts_tcp_write ( ctx,"\r\n\r\nPending requests:", 21 );
		flags = http_counters->flags;
		if ( flags&COUNTER_FLAG_ACTIVE_TIMESTAMP ) ts_tcp_write
		    ( ctx, " (time since open in paren)", 27 );
		for ( i = 0; i < http_counters->active_sp; i++ ) {
		    display_client(ctx, flags, &http_counters->active_stack[i]);
		}
	    }

	    if ( http_counters->ka_histogram_size > 0 ) {
		display_keepalive ( ctx, http_counters->ka_hist,
			http_counters->ka_histogram_size, total, lflag, &col );
	    }

	    tfc_display_counters ( cmd, sizeof(cmd), &length, zflag );
	    ts_tcp_write ( ctx, cmd, length );

	    if ( http_counters->host ) 
		display_hostcount ( ctx, http_counters->host, lflag );


	    if ( zflag ) {
		http_zero_counters(1);
	        http_unlock_counters();
    		tlog_putlog ( 0, "MGR counters zeroed!/");
	    } else http_unlock_counters();

	    stsmsg = "\r\n";		/* handle final write */
	} else {
	    stsmsg = "500 Statistics counters not enabled\r\n";
	}

    } else if ( 0 == tu_strncmp ( cmd, "PEEK_MAILBOX_UNIT", clen ) ) {
	/*
	 * Display mailbox unit.
	 */
	if ( peek_mailbox_unit <= 0 ) {
	    stsmsg = "500 Peek mailbox not enabled\r\n";
	} else {
	    stsmsg = stsbuf;
	    tu_strcpy ( stsmsg, "200 peek mailbox is MBA" );
	    tu_strint ( peek_mailbox_unit, &stsmsg[23] );
	    ts_tcp_write ( ctx, stsmsg, tu_strlen ( stsmsg ) );
	    stsmsg = "\r\n";
	}
    } else {
	stsmsg = "400 Management request invalid, use HELP to list commands\r\n";
    }
    /*
     * Write response to client port and return.
     */
    ts_tcp_write ( ctx, stsmsg, tu_strlen ( stsmsg ) );
    return status;
}
/**************************************************************************/
/* Convert numeric string to integer. 
 */
static int convert_int ( char *str )
{
    int i, value;
    for ( value = i = 0; (str[i] >= '0') && (str[i] <= '9'); i++ ) {
	value = (value * 10) + (str[i] - (int)'0');
	if ( value > 99999999 ) break;
    }
    return value;
}
/****************************************************************************/
static int display_client (void *ctx, int flags, struct client_counter *client)
{
    char line[100];
    int i, ndx, j,status,  port, age;
    /*
     * Format index number and remote host.
     */
    tu_strcpy ( line, "\r\n         " );
    ndx = client->ndx;
    tu_strint (ndx, &line[ndx < 10 ? 7 : ndx < 100 ? 6 : ndx < 1000 ? 5 : 4]);
    line[8] = ':';
    for ( i = 4, j = 10; i < 8; i++ ) {
	tu_strint ( client->address[i], &line[j] );
	while ( line[j] ) j++;
	line[j++] = '.';
    }
    line[j-1] = ':';
    port = client->address[2];
    tu_strint ( port*256 + client->address[3], &line[j] );
    while ( line[j] ) j++;
    if ( flags&COUNTER_FLAG_ACTIVE_TIMESTAMP ) {
#ifdef VMS
	/*
	 * Compute the seconds since counter opened.
	 */
	long now[2], delta[2], ticks_per_sec, remainder;
	int SYS$GETTIM(), LIB$SUBX(), LIB$EDIV();
	SYS$GETTIM(now);
	ticks_per_sec = 10000000;
	LIB$SUBX ( now, client->timestamp, delta );
	LIB$EDIV ( &ticks_per_sec, delta, &age, &remainder );
	if ( remainder >= (ticks_per_sec/2) ) age++;
#else
	age = 0;
#endif
	/*
	 * Display.
	 */
	line[j++] = '(';
	tu_strint ( age, &line[j] ); j += tu_strlen(&line[j]);
	line[j++] = ')';
	line[j] = '\0';
    }
    if ( client->method ) {
	while ( line[j] ) j++;
	tu_strcpy ( &line[j], ", req: " ); j += 7;
    }
    status = ts_tcp_write ( ctx, line, j );
    if ( client->method ) {
	status = ts_tcp_write ( ctx, client->ident, tu_strlen(client->ident) );
    }
    return status;
}
/****************************************************************************/
/* Display list of host classes and counts */
static int display_hostcount (void *ctx, struct host_counter *host, int lflag)
{
    char line[100];
    int i, ndx, j,status,  port;
    ts_tcp_write ( ctx, "\r\n\r\nHost class counters:", 24 );
    if ( lflag ) tlog_putlog(0,"MGR: Host class counters:!/" );
    /*
     * Format each host class that has been defined.
     */
    for ( status = 1; host; host = host->next ) {
	tu_strcpy ( line, "\r\n   " );
	tu_strnzcpy ( &line[5], host->class_name, sizeof(line) - 18 );
	i = tu_strlen ( line );
        line[i++] = ':'; line[i++] = ' ';
	tu_strint ( host->count, &line[i] );
	while ( line[i] ) i++;
	status = ts_tcp_write ( ctx, line, i );
	if ( (status&1) == 0 ) break;
	if ( lflag ) tlog_putlog(0,"MGR: !AD!/", i-2, &line[2] );
    }
    return status;
}
static int display_keepalive ( void *ctx, int *histogram, int size, 
	int thread_total, int lflag, struct columator *col )
{
    char line[100];
    int i,j, reuse, tot_used, total, tot_cnx, total_requests;

    if ( histogram[0] <= 0 ) {
        ts_tcp_write ( ctx, "\r\n\r\nNo KeepAlive activity", 25 );
	if ( lflag ) tlog_putlog(0,"MGR: No KeepAlive activity!/");
    } else {
	tot_used = tot_cnx = 0;
	total_requests = 0;
	for ( i = 1; i < size; i++ ) tot_used += histogram[i];

	tu_strcpy ( line, "\r\n\r\nKeepAlives offered (client): " );
	j = tu_strlen ( line );

	tu_strint (http_counters->ka_offered, &line[j]); 
	j += tu_strlen(&line[j]);

	tu_strcpy ( &line[j], ", granted (server): " ); j += 20;
	tu_strint ( histogram[0], &line[j] ); j += tu_strlen(&line[j]);

	tu_strcpy ( &line[j], ", dropped: " ); j += 11;
	tu_strint ( histogram[0]-tot_used, &line[j] ); j += tu_strlen(&line[j]);

	ts_tcp_write ( ctx, line, j );
	if ( lflag ) tlog_putlog(0,"MGR: !AZ!/", &line[4] );

	ts_tcp_write ( ctx, 
		"\r\n\r\nConnection reuse histogram (req/cnnct: connects)", 52 );
	if ( lflag ) tlog_putlog(0,
		"MGR: Connection reuse histogram: (req/cnnct:connects)!/");
	if ( http_session_style == 0 ) {
	    reuse = thread_total - histogram[0];
	} else {
	    reuse = thread_total;
	    if ( size > 1 ) reuse = reuse - histogram[1];
	}
	init_columator ( ctx, lflag, col );
	total_requests = reuse;
	if ( reuse > 0 ) format_hist_entry ( 0, reuse, col );
	tot_cnx = reuse;
	for ( i = 1; i < size; i++ ) if ( histogram[i] ) {
	    reuse = histogram[i];
	    if ( i < (size-1) ) reuse = reuse - histogram[i+1];
	    tot_cnx += reuse;

	    if ( reuse ) {	/* only show non-zero counts */
		format_hist_entry ( i, reuse, col );
		total_requests += (reuse*(i+1));
	    }
	}
	if ( col->count > 0 ) flush_columator ( col );
	tu_strcpy ( line, "\r\n   sum: " );
	tu_strint ( tot_cnx, &line[10] ); j = tu_strlen ( line );
	if ( tot_cnx > 0 ) {
	    /*
	     * Compute the average requests/connection performed.
	     */
	    int n, d;
	    double rate;
	    tu_strcpy ( &line[j], "    requests/connect: " ); j+= 22;
	    rate = total_requests;
	    rate = rate / tot_cnx;
	    n = rate;
	    d = ((rate-n) * 10.0) + 0.5;
	    if ( d >= 10 ) { n++; d = 0; }
	    tu_strint ( n, &line[j] ); j += tu_strlen ( &line[j] );
	    line[j++] = '.';
	    tu_strint ( d, &line[j] ); j += tu_strlen ( &line[j] );
	}
	ts_tcp_write ( ctx, line, j );
	if ( lflag ) tlog_putlog(0,"MGR: !AZ!/", &line[2] );
    }
    return 1;
}
