/*
 * This HPSS application provides a utility to monitor and control
 * print queues.
 *
 * Command line:
 *    hpps_qman text-db [-log lfile] [-mbxtype n] [-service mbxnam[/user=account]]
 *
 * where:
 *    text-db		Name of data file containing text and configuration
 *			information.
 *    lfile		Name of file to log actions.
 *    n			Mailbox logical type: 0-group  1-system.
 *    mbxnam		HPSS service name, logical used for service mailbox.
 *    account		UIC (username identifier) of web server account that
 *			will be sending requests to service mailbox.
 *
 * Author:	David Jones
 * Date:	8-APR-2000
 */
#include <stdio.h>
#include <stdlib.h>
#include <stat.h>
#include <ctype.h>
#include <string.h>
#include <ssdef.h>		/* VMS system service return codes */
#include <descrip.h>		/* String descriptors */
#include  <quidef.h>		/* VMS queue manager definitions */
#include "hpss_share.h"
#include "hpss_qman.h"
int SYS$GETQUIW(), SYS$ASCTIM();
/*
 * Global variables.
 */
static char asctim[24];
static $DESCRIPTOR (asctim_dx, asctim);
static char *mailbox_name = "HPSS_SRV_QMAN";
static char *summary_title = "Queues";
static int mailbox_type = 0;	/* group-wide mailbox */
/*
 * Job_def structures form a linked list of jobs currently in a queue.
 */
static struct job_def *free_job_defs;
static struct queue_def *known_queues;
static known_queues_generation = 0;
struct itmlst {
    unsigned short length, code;
    void *buffer;
    int *ret_len;
};
#define ITMSET(x,cod,len,buf,retaddr) (x)->code=(cod); (x)->length=(len);\
  (x)->buffer = (buf); (x)->ret_len = (int *)(retaddr);
#define ITMEOL(x) (x)->code = 0; (x)->length = 0;

static struct {
    long mask;
    char *name;
    int has_module;
} queue_states[] = {
    /* List the mutally exclusive bits first, then qualifiers */
    { QUI$M_QUEUE_BUSY, "Busy", 1 }, 
    { QUI$M_QUEUE_AVAILABLE, "Available", 1 },
    { QUI$M_QUEUE_DISABLED, "Disabled", 1 }, 
    { QUI$M_QUEUE_IDLE, "Idle", 1 },
    { QUI$M_QUEUE_PAUSED, "Paused", 1 }, 
    { QUI$M_QUEUE_PAUSING, "Pausing", 1 },
    { QUI$M_QUEUE_RESUMING, "Resuming", 1 }, 
    { QUI$M_QUEUE_STALLED, "Stalled", 1 },
    { QUI$M_QUEUE_STARTING, "Starting", 1 }, 
    { QUI$M_QUEUE_STOPPED, "Stopped", 1 },
    { QUI$M_QUEUE_STOPPING, "Stopping", 1 },
    { QUI$M_QUEUE_UNAVAILABLE, ", unavailable", 0 },
    { QUI$M_QUEUE_RESETTING, ", resetting", 0 },
    { QUI$M_QUEUE_STOP_PENDING, ", stop pending", 0 },
    { QUI$M_QUEUE_ALIGNING, ", aligning", 0 },
    { QUI$M_QUEUE_CLOSED, ", closed", 0 },
    { 0, "EOL", 0 }
};
static struct {
    long mask;
    char *name;
} job_states[] = {
    /* List the mutally exclusive bits first, then qualifiers */
    { QUI$M_JOB_ABORTING, "Aborting" },
    { QUI$M_JOB_EXECUTING, "Executing" },
    { QUI$M_JOB_HOLDING, "Holding" },
    { QUI$M_JOB_INACCESSIBLE, "Inaccessible" },
    { QUI$M_JOB_PENDING, "Pending" },
    { QUI$M_JOB_REFUSED, "Refused" },
    { QUI$M_JOB_RETAINED, "Retained" },
    { QUI$M_JOB_STALLED, "Stalled" },
    { QUI$M_JOB_STARTING, "Starting" },
    { QUI$M_JOB_SUSPENDED, "Suspended" },
    { QUI$M_JOB_TIMED_RELEASE, "Timed" },
    { 0, "EOL" }
};
struct text_line {
    struct text_line *next;
    int length;
    char text[1];		/* variable length */
};
static struct text_line *text_modules[100];
static char text_source[512];
static int text_generation = 0;
static time_t text_date;
/***********************************************************************/
/* Read text file into memory and build table of separate partitions.
 */
static int load_text_file ( char *filename )
{
    FILE *tf;
    char line[4096], *nl;
    int length, i, j;
    struct text_line *first, *last, *cur, *prev;
    struct stat info;
    struct queue_def *qd;
    /*
     * Compare file with previously loaded file and skip load if same.
     */
    if ( strlen ( filename ) >= sizeof(text_source) ) {
	return -1;
    }
    if ( 0 > stat ( filename, &info ) ) {
	fprintf(stderr,"Error statting file '%s'\n", filename );
	return -1;
    }
    if ( strcmp ( text_source, filename ) == 0 ) {
	if ( info.st_mtime == text_date ) return 0;	/* no change */
    } else {
        strcpy ( text_source, filename );
    }
    text_date = info.st_mtime;
    /*
     * Deallocate previously allocated structures and remove references
     * in known queue list;
     */
    for ( i = 0; text_modules[i]; i++ ) {
	for ( cur = text_modules[i]; cur; cur = last ) {
	    last = cur->next;
	    free ( cur );
	}
	text_modules[i] = (struct text_line *) 0;
    }
    text_generation++;
    /*
     * Read file lines into memory and build linked list of structures.
     */
    tf = fopen ( text_source, "r" );
    if ( !tf ) { fprintf(stderr,"Error opening file '%s'\n", filename );
	return 0; }
    first = (struct text_line *) 0;
    while ( fgets ( line, sizeof(line)-2, tf ) ) {
	nl = strchr ( line, '\n' );
	if ( nl ) *nl = '\0'; else line[sizeof(line)-1] = '\0';
	length = strlen ( line );

	cur = (struct text_line *) malloc ( sizeof(struct text_line) + length );
	if ( !cur ) {
	}
	cur->next = (struct text_line *) 0;
	cur->length = length;
	strcpy ( cur->text, line );
	if ( first == (struct text_line *) 0 ) {
	    first = cur;
	} else {
	    last->next = cur;
	}
	last = cur;
    }
    fclose ( tf );
    /*
     * Scan lines for those beginning with '/' and build table of
     * separate text modules each beginning with these lines.
     */
    i = 0;
    prev = (struct text_line *) 0;
    for ( cur = first; cur; cur = cur->next ) {
	if ( cur->text[0] == '/' ) {
	    /*
	     * Upcase the string and terminate current module.  At first
	     * whitespace terminate the string and stop uppcasing
	     * (Remainder of line is short text).
	     */
	    for (j=1; cur->text[j]; j++) {
		if ( isspace(cur->text[j]) ) {
		    cur->text[j] = '\0'; break;
		}
		cur->text[j] = toupper(cur->text[j]);
	    }
	    if ( prev ) prev->next = (struct text_line *) 0;
	    text_modules[i++] = cur;
	    if ( i >= 99 ) break;	/* table full */
	}
	prev = cur;
    }
    text_modules[i] = (struct text_line *) 0;
    return i;
}
/*
 * Seatch for module with specified name.
 */
static struct text_line *module_lines ( char *module_name )
{
    char name[128];
    int i, length, status;
    /*
     * Make upcase string with '/' prefix for comparision.
     */
    length = strlen ( module_name );
    if ( length >= sizeof(name)-1 ) length = sizeof(name) - 1;
    name[0] = '/';
    for ( i = 0; i < length; i++ ) name[i+1] = toupper(module_name[i]);
    name[i+1] = '\0';
    /*
     * Look for first module whose first line is comparision string.
     */
    for ( i = 0; text_modules[i]; i++ ) {
	if ( 0 == strcmp ( name, text_modules[i]->text ) ) {
	    /*
	     * Module found, return pointer to the header line.
	     */
	    return text_modules[i];
	}
    }
    return (struct text_line *) 0;	/* not found */
}

int send_text ( int *ctx, char *module_name )
{
    int i, length, status;
    struct text_line *text, *line;
    /*
     * Locate text lines in module table.
     */
    text = module_lines ( module_name );
    if ( !text ) return SS$_ENDOFFILE;		/* not found */
    /*
     * Module found, send records.
     */
    for ( line = text->next; line; line = line->next ) {
	/* Suppress final line if it is null */
	if ( (line->length == 0) && !line->next ) break;
	status = hpss_write_c ( ctx, line->text, line->length, 1 );
	if ( (status&1) == 0 ) return status;
    }
    return SS$_NORMAL;
}

int get_module_line ( char *module_name, void **ctx, char *line,
	int line_size, int *line_length )
{
    struct text_line *text;
    int length;

    text = (struct text_line *) *ctx;
     *line_length = 0;
    if ( !text ) {
	/*
	 * Initial call, do lookup and return header line.
	 */
	text = module_lines ( module_name );
	if ( !text ) return SS$_ENDOFFILE;	/* module not found */

	length = text->length;
	if ( length >= line_size ) length = line_size - 1;
	memcpy ( line, text->text, length );
	line[length] = '\0';
	*line_length = length;

    } else if ( text->next ) {
	/*
	 * Advance to next line and return it.
	 */
	text = text->next;
	length = text->length;
	if ( length >= line_size ) length = line_size - 1;
	memcpy ( line, text->text, length );
	line[length] = '\0';
	*line_length = length;
	
    } else {
	/*
	 * No next line, return EOF.
	 */
	line[0] = '\0';
	return SS$_ENDOFFILE;
    }
    *ctx = (void *) text;	/* save for next call */
    return SS$_NORMAL;
}
/***********************************************************************/
/*
 * Verify known_queues generation against the text file generation
 * and initialize.
 */
static int update_known_queues ( char *key )
{
    int i, j, plus;
    struct queue_def *def, *first, *last;
    struct text_line *qlist;
    char *name;
    int status;
    /*
     * Ignore request if gernation number match.
     */
    if ( known_queues_generation == text_generation ) return 3;
    /*
     * Locate known queues table.
     */
    qlist = module_lines ( key );
    if ( !qlist ) {
	fprintf(stderr,"No /%s in text file\n", key ); 
	return SS$_ENDOFFILE;
    }
    /*
     * Free previous generation.
     */
    for ( def = known_queues; def; def = last ) {
	last = def->next;
	if ( def->name ) free ( def->name );
	if ( def->gateway ) free ( def->gateway );
	free ( def );
    }
    known_queues = (struct queue_def *) 0;
    /*
     * make new table.
     */
    first = (struct queue_def *) 0;
    for ( qlist = qlist->next; qlist; qlist = qlist->next ) {
	/*
	 * Parse line, format: queue[+gateway]: location
	 */
	char *text, *qname, *gwname, *loc;
	text = qlist->text;
	qname = text;
 	gwname = loc = (char *) 0;
	for ( i = 0; text[i]; i++ ) {
	    if ( text[i] == '+' ) {
		text[i] = '\0';
		gwname = &text[i+1];
	    } else if ( text[i] == ':' ) {
		text[i] = '\0';
		loc = &text[i+1];
		while ( (*loc == ' ') || (*loc=='\t')) loc++;
		break;
	    } else text[i] = toupper(text[i]);
	}
	/*
	 * Add queue defintion.
	 */
        def = (struct queue_def *) malloc ( sizeof(struct queue_def) );
	if ( first == (struct queue_def *) 0 ) first = def;
	else last->next = def;
	last = def;
	def->next = (struct queue_def *) 0;
        def->name = malloc ( strlen(qname) + 1 );
	strcpy ( def->name, qname );
	if ( gwname ) {
	    def->gateway = malloc ( strlen ( gwname ) );
	    strcpy ( def->gateway, gwname );
	} else def->gateway = (char *) 0;
        def->location = loc ? loc : "";

	def->first_job = (struct job_def *) 0;
	def->last_job = (struct job_def *) 0;
	def->name_dx = asctim_dx;
	def->name_dx.dsc$w_length = strlen ( def->name );
        def->name_dx.dsc$a_pointer = def->name;
        def->gateway_dx = def->name_dx;
        if ( def->gateway ) {
	    def->gateway_dx.dsc$w_length = strlen ( def->gateway );
	    def->gateway_dx.dsc$a_pointer = def->gateway;
	}
    }
    known_queues_generation = text_generation;
    known_queues = first;
    return SS$_NORMAL;
}
/***********************************************************************/
/* Examine command line arguments for switches and queue names.
 *
 * Recognized switches:
 *    -mbxtype n			set mailbox type: 0-group 1-system
 *    -service  name[/acc=user]         Set service name/acess.
 */
static int process_argv ( int argc, char **argv )
{
    int i, j, plus;
    struct queue_def *def, *tmp_list;
    struct text_line *qlist;
    char *name;
    int status;
    /*
     * Loop through the argument list.
     */
    text_modules[0] = (struct text_line *) 0;
    strcpy ( text_source, "" );
    known_queues = (struct queue_def *) 0;
    tmp_list = known_queues;
    for ( i = 1; i < argc; i++ ) {
	if ( argv[i][0] == '-' ) {
	    /*
	     * special action.
	     */
	    if ( strcmp ( argv[i], "-mbxtype" ) == 0 ) {
		if ( (i+1) < argc ) {
		    i++;
		    mailbox_type = atoi ( argv[i] );
		} else {
		    fprintf(stderr,"Missing argument for -mbxtype\n");
		}
	    } else if ( strcmp ( argv[i], "-service" ) == 0 ) {
		if ( (i+1) < argc ) {
		    i++;
		    mailbox_name = malloc ( strlen(argv[i]) + 1 );
		    if ( !mailbox_name ) {
			fprintf(stderr, "fatal error: malloc failed\n");
			exit(0);
		    }
		    strcpy ( mailbox_name, argv[i] );
		} else {
		    fprintf(stderr,"Missing argument for -service\n");
		}
	    } else if ( strcmp ( argv[i], "-log" ) == 0 ) {
		/*
		 * Open log file for recording actions.
		 */
		if ( (i+1) < argc ) {
		    i++;
		    status = set_action_log ( argv[i] );
		    if ( (status&1) == 0 ) {
			/*
			 * The log file is important, failure to open is fatal.
			 */
			fprintf(stderr, "Error openning file '%s'\n",argv[i]);
			exit(0);
		    }
		} else {
		    fprintf(stderr,"Missing argument for -log\n");
		}
	    } else {
		fprintf(stderr,"known option: '%s'\n", argv[i] );
	    }
	} else {
	    /*
	     * Load text file and process /queues section.
	     */
	    status = load_text_file ( argv[i] );
	    if ( status <= 0 ) continue;

	    status = update_known_queues ( "queues" );
	    if ( (status&1) == 0 ) {
		fprintf(stderr,"Error loading /queues list from '%s'\n",
			argv[i]);
	    }
	}
    }
    /*
     * Give warning if queue list is empty.
     */
    if ( !known_queues ) {
	fprintf(stderr,"Missing or invalid queue list\n");
    }
    return argc;
}
/***********************************************************************/
/* Utility function to send error response.
 */
static int send_abort ( int *ctx, char *stsline, char *message )
{
    int status;
    status = hpss_printf_c ( ctx, 
	"Content-type: text/plain\r\nstatus: %s\r\n\r\n", stsline );
    status = hpss_write_c ( ctx, message, strlen(message), 1 );
    return status;
}
/***********************************************************************/
/* Wrapper routinnes for getqui calls.
 */
int get_queue_jobs ( struct queue_def *qd, int in_gateway, char *user )
{
    struct itmlst item[10];
    int status, flags, i, ulen, nlen;
    struct { int status, spare; } iosb;
    struct job_def info;
    /*
     * Build item list for job display.
     */
    flags = QUI$M_SEARCH_ALL_JOBS;
    ITMSET(&item[0],QUI$_SEARCH_FLAGS,sizeof(flags), &flags, 0)
    ITMSET(&item[1],QUI$_ENTRY_NUMBER,sizeof(info.entry_number),
		&info.entry_number, 0)
    ITMSET(&item[2],QUI$_JOB_STATUS,sizeof(info.job_status), 
		&info.job_status, 0)
    ITMSET(&item[3] ,QUI$_FILE_COUNT,sizeof(info.file_count),
		&info.file_count, 0)
    ITMSET(&item[3], QUI$_USERNAME, sizeof(info.username)-1,
		info.username, &ulen )
    ITMSET(&item[4], QUI$_SUBMISSION_TIME, sizeof(info.submission_time),
		info.submission_time, 0)
    ITMSET(&item[5], QUI$_JOB_NAME, sizeof(info.job_name)-1,
		info.job_name, &nlen )
    ITMSET(&item[6], QUI$_JOB_SIZE, sizeof(info.job_size),
		&info.job_size, 0 )

    ITMEOL(&item[7])
    info.next = (struct job_def *) 0;
    info.in_gateway = in_gateway;	/* flag which queue */
    ulen = nlen = 0;
    /*
     * Call getqui multiple times to get all entiries.
     */
    for ( status = 1; status&1; ) {
	status = SYS$GETQUIW ( 0, QUI$_DISPLAY_JOB, 0, item, &iosb, 0, 0 );
#ifdef DEBUG
printf("   get job result: %d %d %d\n", status, iosb.status, info.entry_number );
#endif
	if ( (status&1) == 1 ) status = iosb.status;
	if ( status&1 ) {
	    /*
	     * Allocate job_def block from free list and copy
	     * current job infor into into it.
	     */
	    if ( !free_job_defs ) {
		free_job_defs = (struct job_def *) malloc 
			(sizeof(struct job_def) * 10 );
		if ( !free_job_defs ) return SS$_INSFMEM;
		for ( i = 0; i < 9; i++ ) 
			free_job_defs[i].next = &free_job_defs[i+1];
		free_job_defs[9].next = (struct job_def *) 0;
	    }
	    if ( qd->first_job == (struct job_def *) 0 ) {
		qd->first_job = free_job_defs;
	    } else {
		qd->last_job->next = free_job_defs;
	    }
	    qd->last_job = free_job_defs;
	    free_job_defs = free_job_defs->next;

	    info.username[ulen] = '\0';
	    info.job_name[nlen] = '\0';
	    *qd->last_job = info;
	}
    }
}
int get_queue_status ( struct queue_def *qd, int nested, char *username )
{
    struct itmlst item[8];
    int status, flags;
    struct { int status, spare; } iosb;
    /*
     * Build item list for queue display.
     */
    flags = 0;
    if ( nested ) flags = QUI$M_SEARCH_WILDCARD;
    ITMSET(&item[0],QUI$_SEARCH_FLAGS,sizeof(flags), &flags, 0)
    ITMSET(&item[1],QUI$_SEARCH_NAME,strlen(qd->name), qd->name, 0)
    ITMSET(&item[2],QUI$_QUEUE_STATUS,sizeof(qd->state), &qd->state, 0)
    ITMSET(&item[3] ,QUI$_PENDING_JOB_COUNT,sizeof(qd->pending_count),
	&qd->pending_count, 0)
    ITMEOL(&item[4])
    /*
     * Retrieve info.
     */
    status = SYS$GETQUIW ( 0, QUI$_DISPLAY_QUEUE, 0, item, &iosb, 0, 0 );
    if ( (status&1) == 1 ) status = iosb.status;
    if ( (status&1) == 0 ) return status;
#ifdef DEBUG
    printf("status of %s: %d %x\n", qd->name, status,  qd->state );
#endif
    if ( nested ) {
	status = get_queue_jobs ( qd, 0, username );
	SYS$GETQUIW ( 0, QUI$_CANCEL_OPERATION, 0, 0, 0, 0, 0 );
    }
    if ( !qd->gateway ) return status;	/* no gateway queue */
    /*
     * Fetch info for gateway queue.
     */
    ITMSET(&item[1],QUI$_SEARCH_NAME,strlen(qd->gateway), qd->gateway, 0)
    ITMSET(&item[2],QUI$_QUEUE_STATUS,sizeof(qd->gateway_state), 
	&qd->gateway_state, 0)
    ITMSET(&item[3] ,QUI$_PENDING_JOB_COUNT,sizeof(qd->gateway_pending),
	&qd->gateway_pending, 0)

    status = SYS$GETQUIW ( 0, QUI$_DISPLAY_QUEUE, 0, item, &iosb, 0, 0 );
#ifdef DEBUG
    printf("status of %s: %d %x\n", qd->gateway, status,  qd->gateway_state );
#endif
    if ( (status&1) == 1 ) status = iosb.status;
    if ( nested ) {
	if ( status&1 ) status = get_queue_jobs ( qd, 1, username );
	SYS$GETQUIW ( 0, QUI$_CANCEL_OPERATION, 0, 0, 0, 0, 0 );
    }
    return status;

}
/***********************************************************************/
/*
 * Display list of queues with a summary of their state.
 */
static int queue_summary ( int *ctx )
{
    struct queue_def *qd;
    int status, length;
    /*
     * Send back CGI response header and HTML prologue.
     */
    hpss_printf_c ( ctx, "x-sig: 12345\r\n" );
    hpss_printf_c ( ctx, "Content-type: text/html\r\n\r\n" );
    hpss_printf_c ( ctx, "<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY>\r\n",
	summary_title );
    status = send_text ( ctx, "summary" );
    /*
     * Print list of queue items.  Put in table form.
     */
    length = 0;
    status = SYS$ASCTIM ( &length, &asctim_dx, 0, 0 );
    asctim[length<sizeof(asctim)?length:sizeof(asctim)-1] = '\0';
    hpss_printf_c ( ctx, "<DL><DT>Queue summary at %s</DT><BR>\r\n", asctim);
    hpss_printf_c ( ctx, "<DD><P><TABLE COL=\"4\" BORDER=\"1\">\r\n" );
    hpss_printf_c ( ctx, 
	"<TR><TH>Queue</TH><TH>Location</TH><TH>Status</TH><TH>Pending</TH></TR>\r\n");
    
    for ( qd = known_queues; qd; qd = qd->next ) {
	/*
	 * Get summary information for queue and make row in table.
	 */
	char qstatus[1200];
	int pending_count, i;
	/*
	 * Get current state of queue and format bits.
	 */
	status = get_queue_status ( qd, 0, "" );
	if ( (status&1) == 0 ) {
	    sprintf(qstatus,"getqui error=%d\n", status );
	} else {
	    int state;
	    strcpy(qstatus,"");
	    state = qd->state;
	    for ( i = 0; queue_states[i].mask; i++ ) {
		if ( (queue_states[i].mask & state) != 0 ) {
		    char *qs_name;
		    qs_name = queue_states[i].name;	/* default text */
		    if ( queue_states[i].has_module ) {
			/*
			 * Replace default text with module description text.
			 */
			struct text_line *desc;
			int k;
			desc = module_lines ( qs_name );
			if ( desc ) {
			    k = strlen ( desc->text );
			    if ( k < desc->length ) qs_name = &desc->text[k+1];
			}
		    }
		    strcat ( qstatus, qs_name );
		}
	    }
	}
	pending_count = qd->gateway ? qd->gateway_pending : qd->pending_count;
        hpss_printf_c ( ctx, 
	   "<TR ALIGN=\"CENTER\"><TD><A HREF=\"show/%s\">%s</A></TD><TD>%s</TD><TD>%s</TD><TD>%d</TD></TR>\r\n",
		qd->name, qd->name, qd->location, qstatus, pending_count );
    }
    /*
     * Finish.
     */
    hpss_printf_c ( ctx,"</TABLE></DD></DL>\r\n" );
    hpss_printf_c ( ctx, "</BODY>" );
    return 0;
}
/***********************************************************************/
/* Format list of job_def structures as a table.
 */
static int display_entry_table ( int *ctx, char *qname,
	struct job_def *job_list, int show_gateway, struct job_def **final )
{
    int status, i, state, length, LIB$SUB_TIMES(), SYS$GETTIM();
    struct job_def *job;
    char jstatus[128], jopt[128];
    long delta[2], now[2];
    /*
     * Output table header.
     */
    SYS$GETTIM(now);
    status = hpss_printf_c ( ctx, "<TABLE COL=\"7\" BORDER=\"1\">\r\n" );
    if ( (status&1) == 0 ) return status;
    status = hpss_printf_c ( ctx,
	"<TR><TH>%s</TH><TH>%s</TH><TH>%s</TH><TH>%s</TH><TH>%s</TH><TH>%s</TH><TH>%s</TH></TR>\r\n",
	    "Entry", "Username", "Job Name", "Size", "State", 
	    "Age", "Actions" );
    /*
     * Output table rows, one for each entry.
     */
    for ( job = job_list; ((status&1)==1) && job; job = job->next ) {
	/*
	 * Break out of loop if next job is a gateway queue job.
	 */
	if ( !show_gateway && (job->in_gateway ==  1) ) break;
	/*
	 * Convert job status to text.
	 */
	jstatus[0] = '\0';
	length = 0;
	state = job->job_status;
	for ( i = 0; job_states[i].mask; i++ ) {
	    if ( state&job_states[i].mask ) {
		if ( jstatus[0] ) strcat ( jstatus, ", " );
		strcat ( jstatus, job_states[i].name );
	    }
	}
	job->job_name[25] = '\0';	/* truncate job name */
        /*
	 * Calculate delta time since job submitted, trim hundreths.
	 */
        LIB$SUB_TIMES ( now, job->submission_time, delta );
	status = SYS$ASCTIM ( &length, &asctim_dx, delta, 0 );
	if ( length >= sizeof(asctim) ) length = sizeof(asctim)-1;
	if ( length > 3 ) if ( asctim[length-3] == '.' )  length -= 3;
	asctim[length] = '\0';
        /*
	 * Construct HREFs for deleting/holding the job.
	 */
	sprintf ( jopt, "<A HREF=\"../delete/%s/%d\">delete</A>", qname,
		job->entry_number );
	/*
	 * Output the table row HTML.
	 */
	status = hpss_printf_c ( ctx, 
	    "<TR><TD>%d</TD><TD>%s</TD><TD>%s</TD><TD ALIGN=\"RIGHT\">%d</TD><TD>%s</TD><TD>%s</TD><TD>%s</TD></TR>\r\n",
	    job->entry_number, job->username, job->job_name, job->job_size,
	    jstatus, asctim, jopt );
    }
    /*
     * Save entry after final one displayed and close table.
     */
    *final = job;
    if ( (status&1) == 1 ) status = hpss_printf_c ( ctx, "</TABLE><P>\r\n" );
    return status;
}
/***********************************************************************/
/*
 * Top level routine for displaying detailed information about a queue.
 */
static void display_queue ( int *ctx, struct queue_def *qd, char *arg )
{
    int status, state, i, length;
    struct text_line *desc;
    char *disp_status, qstatus[128], qopt[128];
    struct job_def *gw_jobs, *job;

    status = get_queue_status ( qd, 1, "" );
    if ( (status&1) == 0 ) {
    }
    /*
     * Convert the queue status.
     */
    desc = (struct text_line *) 0;
    strcpy(qstatus,"");
    state = qd->state;
    for ( i = 0; queue_states[i].mask; i++ ) {
	if ( (queue_states[i].mask & state) != 0 ) {
	    /*
	     * Lookup module and display string.
	     */
    	    disp_status = queue_states[i].name;
	    if ( queue_states[i].has_module ) {
		desc = module_lines ( queue_states[i].name );
		if ( desc ) {
		    int k;
		    k = strlen ( desc->text );
		    if ( k < desc->length ) disp_status = &desc->text[k+1];
		}
	    }
	    strcat ( qstatus, disp_status );
	}
    }
    hpss_printf_c ( ctx, "content-type: text/html\r\n\r\n" );
    hpss_printf_c ( ctx, 
	"<HTML><HEAD><TITLE>Queue %s status</TITLE></HEAD><BODY>\r\n",
	qd->name );
    send_text ( ctx, "show" );
    length = 0;
    status = SYS$ASCTIM ( &length, &asctim_dx, 0, 0 );
    asctim[length<sizeof(asctim)?length:sizeof(asctim)-1] = '\0';
    sprintf ( qopt, 
	"<A HREF=\"../stop/%s\">stop</A>, <A HREF=\"../start/%s\">start</A>",
	qd->name, qd->name );
    hpss_printf_c ( ctx, 
	"<DL><DT>Queue %s is %s at %s, actions: %s</DT><DD><P>\r\n", qd->name,
	qstatus, asctim, qopt );
    if ( desc ) {
	/*
	 * Text module for this state found, include it.
	 */
	for ( desc = desc->next; desc; desc = desc->next ) {
	    status = hpss_write_c ( ctx, desc->text, desc->length, 1 );
	    if ( (status&1) == 0 ) break;
	}
	hpss_printf_c ( ctx, "<P>\r\n" );
    } 
    gw_jobs = qd->first_job;
    if ( qd->first_job ) if ( qd->first_job->in_gateway == 0 ) {
	/*
	 * include table of queue entries but only show the device
	 * queue entries.  Save first gateway entry in gw_jobs pointer.
	 */
	status = display_entry_table ( ctx, qd->name,
		qd->first_job, 0, &gw_jobs );
	if ( (status&1) == 0 ) return;
    }
    hpss_printf_c ( ctx, "</DD></DL><P><HR>\r\n" );
    /*
     * Show the gateway queue if present.
     */
    if ( qd->gateway ) {
        strcpy(qstatus,"");
        state = qd->gateway_state;
        for ( i = 0; queue_states[i].mask; i++ ) {
	    if ( (queue_states[i].mask & state) != 0 )
	        strcat ( qstatus, queue_states[i].name );
	}
        sprintf ( qopt, 
	  "<A HREF=\"../stop/%s\">stop</A>, <A HREF=\"../start/%s\">start</A>",
	   qd->gateway, qd->gateway );
	hpss_printf_c ( ctx, 
		"<DL><DT>Gateway queue is %s, actions: %s</DT><DD><P>\r\n",
		qstatus, qopt );

	if ( gw_jobs ) {
	    status = display_entry_table ( ctx, qd->gateway,
		gw_jobs, 1, &gw_jobs );
	    if ( (status&1) == 0 ) return;
	}
    }
    /*
     * Reset the queue context.
     */
    if ( qd->first_job ) {
	/* move job_def blocks to free list */
	qd->last_job->next = free_job_defs;
	free_job_defs = qd->first_job;
	qd->first_job = qd->last_job = (struct job_def *) 0;
    }
}
/***********************************************************************/
/* Handle opeations of specific queues.  Path_info  is URL data following
 * the queue name in the URL.
 *
 * Operations:
 *     SHOW
 */
static int manage_queue ( int *ctx, char *operation,
	struct queue_def *qd, char *queue_name, char *path_info )
{
    char username[256];
    int status, ulen;
    /*
     * All operations but show require authentication.
     */
    if ( strcmp ( operation, "SHOW" ) != 0 ) {
	/*
         * Get the username.
	 */
	hpss_printf_c ( ctx, 
		"x-warning: Operation should have authentication (%s)\r\n",
		path_info ? path_info : "" );
	status = hpss_getenv_c ( ctx, "REMOTE_USER", username, 
		sizeof(username), &ulen );
	printf ( "getenv REMOTE_USER status: %d, %s\n", status,
	    (status&1) ? username : "" );
	if ( (status&1) == 0 ) ulen = 0;
	username[ulen] = '\0';
	if ( !username[0] ) {
	    send_abort ( ctx, "400 Authentication info missing",
		"Operation must be authenticated" );
	    return 0;
	}
    } else {
	username[0] = '\0';
    }
    /*
     * Dispatch on operation requested.
     */
    if ( strcmp ( operation, "SHOW" ) == 0 ) {
	display_queue ( ctx, qd, path_info );
    } else if ( strcmp ( operation, "DELETE" ) == 0 ) {
	status = delete_queue_entry ( ctx, qd, queue_name, username,
		path_info );
    } else if ( strcmp ( operation, "START" ) == 0 ) {
	status = start_queue ( ctx, qd, queue_name, username );
    } else if ( strcmp ( operation, "STOP" ) == 0 ) {
        send_abort ( ctx, "404 Invalid operation.", "stop not implemented" );
    } else if ( strcmp ( operation, "RELEASE" ) == 0 ) {
	status = release_queue_entry ( ctx, qd, queue_name, username,
	    path_info );
    } else {
        send_abort ( ctx, "404 Invalid operation.", "Invalid operation." );
    }
    return 0;
}
/***********************************************************************/
/*
 * Main routine.
 */
int main ( int argc, char **argv )
{
    int status, context, pid, i, astatus, length;
    char subfunc[64], *operation, *queue_name, *queue_arg, path_info[4096];
    struct queue_def *qd;
    /*
     * Determine queue list and declare service.
     */
    if ( process_argv ( argc, argv ) == 0 ) {
	fprintf ( stderr, "Failure processing command line\n" );
	return 1;
    }
    status = hpss_initialize_c ( mailbox_name, mailbox_type, &context );
    if ( (status&1) == 0 ) {
	fprintf(stderr,"Failuring initializing HPSS library: %d\n", status);
    }
    /*
     * Main loop.
     */
    while ( (status&1) == 1 ) {
	/*
	 * Get next request.
	 */
	astatus = hpss_accept_c (&context, 71, &pid, subfunc, sizeof(subfunc));
	if ( (astatus&1) == 1 ) {
	    /*
	     * Get path_info and determine mode.
	     */
	    status = hpss_getenv_c ( &context, "PATH_INFO", path_info,
			sizeof(path_info), &length );
	    if ( (status&1) == 0 ) {
		status = send_abort ( &context, "500 internal error", 
			"failed to get PATH_INFO variable" );
		continue;
	    }
	    if ( (length == 0) ) {
		/*
		 * Just 'script' name specified with no path, redirect.
		 */
		status = hpss_getenv_c ( &context, "SCRIPT_NAME",
			path_info, sizeof(path_info), &length );
		hpss_printf_c ( &context, "Location: *://*:*%s/\r\n\r\n\r\n",
			path_info );
	    } else if ( strcmp(path_info,"/") == 0 ) {
		/*
		 * 'root' request, display the top-level queue list.
		 */
		load_text_file ( text_source );
		update_known_queues ( "queues" );
		queue_summary ( &context );

	    } else {
		/*
		 * Requesting display/control of particular queue.  Extract
		 * and upcase the queue name.
		 */
		load_text_file ( text_source );
		update_known_queues ( "queues" );
		operation = strtok ( path_info, "/" );
		queue_name = strtok ( (char *) 0, "/" );
		queue_arg = strtok ( (char *) 0, " " );
		if ( operation ) {
		   for ( i=0; operation[i]; i++ ) 
			operation[i] = toupper(operation[i]);
		}
		if ( queue_name ) {
		    /*
		     * Queue name parsed OK, see if valid.
		     */
		   for ( i=0; queue_name[i]; i++ ) 
			queue_name[i] = toupper(queue_name[i]);
		    for ( qd = known_queues; qd; qd = qd->next ) {
			if ( strcmp ( queue_name, qd->name ) == 0 ) break;
			if ( qd->gateway ) {
			    if ( strcmp(queue_name, qd->gateway) == 0 ) break;
			}
		    }

		    if ( qd ) manage_queue ( &context, operation, qd,
			queue_name, queue_arg );
		    else {
			send_abort ( &context, "404 Unknown queue name\n",
				"Queue name not configured" );
		    }
		} else {
		    send_abort ( &context, "400 Missing queue name\n",
				"Queue name must be supplied." );
		}
	    }
	    /*
	     * Prepare for next connection.
	     */
	    hpss_disconnect ( &context );

	} else if ( astatus == SS$_TIMEOUT ) {
	    flush_action_log ( );
	}
    }
    return status;
}
