[comp.os.vms] how to get $STATUS from a subprocess at DCL level

DSTEVENS@VAXC.STEVENS-TECH.EDU (David L. Stevens) (09/11/87)

 To whom it may concern,
	I sent a message out a couple of days ago about getting the status
 of a command from a subprocess that is still active.  The subprocess would
 have the following charicteristics:

	1: Would have CLI loaded into it
	2: Would be running at DCL level
	3: SYS$INPUT , SYS$OUTPUT, & SYS$ERROR would be defined as mailboxes
	4: Parent process would continue executing while the subprocess existed
	5: Subprocess will not exit after executing a command

 I now have a program that does all of the above.  The program will create the
 subprocess, allow a calling program to issue commands to it and, will return
 the integer value of the $STATUS symbol to the calling routine.  The
 subprocess will stay there until you call the shutdown routine.  The modules
 are both written in VAX-C, they work at our site here, but then again since
 this is a college, reallity may be warped beyond all recognition.  So I can't
 guarentee that it will run anywhere else.  But for what its worth, here it is:

 -------------------------Cut here -------------------------------------------


/*
**
**  INCLUDE FILES
**
**/

#include    stdio
#include    descrip
#include    "acct"


main()
{
#define	COMMAND	    "What ever command you want"
#define	NXT_COMMAND "Any other command you want"
    unsigned	int	    status;
    unsigned	int	    final_status;
    static	readonly
	$DESCRIPTOR(show_command, "FINGER");
    static	readonly
	$DESCRIPTOR(command_1, COMMAND);
    static	readonly
	$DESCRIPTOR(command_2, NXT_COMMAND);

    status = dcl_initialize();
    if (TEST_STATUS)
    {
	status = dcl_execute_command(&show_command, &final_status);
	if (! TEST_STATUS)
	    printf("\n DCL command failed with status = %x", status);
	else
	    printf("\n The status of the show command was: %x",
		     final_status);

	status = dcl_execute_command(&command_1, &final_status);
	if (! TEST_STATUS)
	    printf("\n DCL command failed with status = %x", status);
	else
	    printf("\n The status of the command was: %x",
		     final_status);

	status = dcl_execute_command(&command_2, &final_status);
	if (! TEST_STATUS)
	    printf("\n DCL command failed with status = %x", status);
	else
	    printf("\n The status of the command was: %x",
		     final_status);
    }
    dcl_shutdown();
    exit (status);
}

--------------------------------End of first module----------------------------

--------------------------------DCL command module-----------------------------
#module acct_dcl_command "1.0.001"

/*
**++
**  FACILITY:
**
**      Stevens accounting system
**
**  ABSTRACT:
**
**      This module implements an interface to the Digital Command Language
**	(DCL).  This is used to implement an interface to those items that are
**	not callable.
**
**  AUTHORS:
**
**      Robert McQueen
**	David L. Stevens
**
**  CREATION DATE:     19-August-1987
**
**  MODIFICATION HISTORY:
**--
**/



/*
**
**  INCLUDE FILES
**
**/

#include    stdio		    /* Standard I/O definitions */
#include    dvidef		    /* GETDVI symbol definitions */
#include    iodef		    /* IO symbol definitions */
#include    prvdef		    /* Process privilege definitions */
#include    ssdef		    /* System Service status definitions */
#include    descrip		    /* Descriptor definitions */
#include    "acct"		    /* Accounting definitions */


#define	    SUBPROCESS_LOG_FILE	    "Acct$subprocess_log_file.log"


/*
** The following are prototype function definitions
*/

unsigned    int	dcl_initialize(void);

unsigned    int	dcl_shutdown(void);

unsigned    int	dcl_execute_command(
		    struct	dsc$descriptor	*command,
		    unsigned	int		*final_status);

unsigned    int	dcl_final_status(
		    struct	dcl_structure	*process_info,
		    unsigned	int		*final_status);

unsigned    int	dcl_assign_mbx(
		    unsigned int    *channel,
		    unsigned int    *unit,
		    struct dsc$descriptor *name);

unsigned    int	dcl_write_command(
		    struct  dcl_structure   *process_info,
		    struct  dsc$descriptor  *command_line);

unsigned    int	dcl_wait_until_done(
		    struct  dcl_structure   *process_info);

unsigned    int	dcl_post_read_io(
		    char	*buffer_address,
		    int		buffer_length);

unsigned    int	post_termination_read(
		    struct	dcl_structure	*process_info);

unsigned    int	dcl_create_the_process(
		    unsigned	int		*privileges);

wake_ast();

term_ast();

read_ast();

/*
** Local structure definitions
*/

struct	dcl_structure
{
    unsigned	long		    pid;		/* PID */

    unsigned	short		    input_channel;	/* Input channel */
    unsigned	short		    input_unit;		/* Input unit number */
    struct	dsc$descriptor	    input_name;		/* Input mailbox name */
    struct	io_status_block	    input_iosb;		/* Input IOSB */

    unsigned	short		    output_channel;	/* Output channel */
    unsigned	short		    output_unit;	/* Output unit number */
    struct	dsc$descriptor	    output_name;	/* Output mailbox name */
    struct	io_status_block	    output_iosb;	/* Output IOSB */

    unsigned	short		    term_channel;	/* Termination channel */
    unsigned	short		    term_unit;		/* Termination unit number */
    struct	dsc$descriptor	    term_name;		/* Termination mailbox name */
    struct	io_status_block	    term_iosb;		/* Termination IOSB */
};

struct	    dcl_structure   dcl;
unsigned    int		    process_privileges[2] = {0, 0};
char			    term_buffer[255];
FILE			    *subproc_log_file;


/*		    DCL INITIALIZE
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function will initialize the DCL command processing.  This routine
**	should be called as part of the initialization of the accounting system.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      Module initialized
**
**  COMPLETION CODES:
**
**      System service condition codes
**
**  SIDE EFFECTS:
**
**      none
**
**--
**/
unsigned int	dcl_initialize(void)
{
/*
** Permanent strings
*/
    static struct dsc$descriptor dyn_string =	    /* Prototype dynamic string */
			$INIT_DESCRIPTOR;	    /* Initialize it */
/*
** Local variables
*/
    unsigned	int	status;			/* Condition code to return */
/*
** Main logic:
** - Initialize all dynamic strings
** - Create the input, output and termination mailboxes
*/
    dcl.input_name  = dyn_string;	/* Initialize the dynamic strings */
    dcl.output_name = dyn_string;	/* Initialize the output string */
    dcl.term_name   = dyn_string;	/* Initialize the termination string */
/*
** Assign an input mailbox for interprocess communications
*/
    status = dcl_assign_mbx(		/* Assign the mailbox */
		&dcl.input_channel,	/* Place to store the channel */
		&dcl.input_unit,	/* and the unit number */
		&dcl.input_name);	/* and the mailbox name */

    if (! TEST_STATUS)			/* Did this win? */
	return status;			/* No, return the failure status */
/*
** Assign an output mailbox for interprocess communications
*/
    status = dcl_assign_mbx(		/* Assign the mailbox */
		&dcl.output_channel,	/* Place to store the channel */
		&dcl.output_unit,	/* and the unit number */
		&dcl.output_name);	/* and the mailbox name */
    if (! TEST_STATUS)			/* Did this win? */
	return status;			/* No, return the failure status */
/*
** Assign a termination mailbox for interprocess communications
*/
    status = dcl_assign_mbx(		/* Assign the mailbox */
		&dcl.term_channel,	/* Place to store the channel */
		&dcl.term_unit,		/* and the unit number */
		&dcl.term_name);	/* and the mailbox name */
    if (! TEST_STATUS)			/* Did this win? */
	return status;			/* No, return the failure status */
/*
** Post ar read for when the process terminates
*/
    post_termination_read(&dcl);
/*
** Set up the privileges that the subprocess will have
*/
    process_privileges[0] = PRV$M_NETMBX | PRV$M_TMPMBX;
/*
** Now create the process and return to the caller
*/
    return  dcl_create_the_process(&process_privileges);
}

/*		    DCL SHUTDOWN
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function will shutdown the DCL command interface.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      Local data structures
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      System service condition code
**
**  SIDE EFFECTS:
**
**      none
**
**--
**/
unsigned int	dcl_shutdown(void)
{
/*
** Local variables
*/
    unsigned int    status;		/* Condition code to return */
/*
** Main logic:
** - Cancel all I/O
** - Deassign the channels
** - Kill the process
*/

/*
** Cancel IO on the Input channel,
**  and then deassign the IO channel
*/
    status = sys$cancel(dcl.input_channel);	/* Cancel input I/O */
    if (TEST_STATUS)
	status = sys$dassgn(dcl.input_channel);	/* Deassign the input channel */
    if (! TEST_STATUS)
	return status;				/* Pass back failing status */
/*
** Cancel IO on the Output channel,
**  and then deassign the IO channel
*/
    status = sys$cancel(dcl.output_channel);	/* Cancel the output I/O requests */
    if (TEST_STATUS)
	status = sys$dassgn(dcl.output_channel);/* Deassign the output channel */
    if (! TEST_STATUS)
	return status;				/* Return the failing status */
/*
** Cancel IO on the Termination channel,
**  and then deassign the IO channel
*/
    status = sys$cancel(dcl.term_channel);	/* Cancel the termination mailbox read */
    if (TEST_STATUS)
	status = sys$dassgn(dcl.term_channel);	/* Deassign the channel */
    if (! TEST_STATUS)
	return status;			/* Pass back any errors */
/*
** Now kill off the process, if it is still around
*/
    status = sys$delprc(
		 &dcl.pid,		/* Delete the process with this PID */
		 0);			/* Don't use the process name */
    if (status == SS$_NONEXPR)		/* Already gone? */
	status = SS$_NORMAL;		/* Yes, convert this to a normal */
    if (! TEST_STATUS)
	return status;			/* Return the failing status */
/*
** Return any dynamic strings that are in the block now.
*/
    STR$FREE1_DX(&dcl.input_name);	/* Return the input name */
    STR$FREE1_DX(&dcl.output_name);	/* Return the output name */
    STR$FREE1_DX(&dcl.term_name);	/* Free up the termination MBX name */
/*
** Close the Subprocess Log File
*/
    fprintf(subproc_log_file, "\n\n   End of Subprocess log file\n");
    fclose(subproc_log_file);
/*
** At this point we are basically finished, so return the final status for the
** caller.
*/
    return status;			/* Return the final status */
}

/*		    DCL EXECUTE COMMAND
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function will execute a DCL command.  It will wait until the
**	command is complete and return the status of the executed command.
**
**  FORMAL PARAMETERS:
**
**      command - Pointer to a string descriptor which is the command to
**	    execute.
**
**  IMPLICIT INPUTS:
**
**      Local data structures
**
**  IMPLICIT OUTPUTS:
**
**	none
**
**  COMPLETION CODES:
**
**      System service condition code, program condition code
**
**  SIDE EFFECTS:
**
**      DCL command executed
**
**--
**/
unsigned int	dcl_execute_command(
    struct	dsc$descriptor	*command,
    unsigned	int		*final_status)
{
/*
** Local variables
*/
    unsigned	int	status;
/*
** Main logic:
**  - Write the command to the subprocesses input device
**  - Wait for the command to complete
**  - Go get the final status of the command
*/

/*
** Now if the process got created correctly, we can go execute the command
*/
    status = dcl_write_command(		/* Go write the command to the */
			&dcl,		/*   mailbox that */
			command);	/*     we have opened */
/*
** If the previous call succeeded, wait until subprocess
**	finishes the command
*/
    if (TEST_STATUS)
	status = dcl_wait_until_done(   /* Now wait until the subprocess */
			    &dcl);	/*   has executed the command */
/*
** If everything is still fine then go and get the final status
*/
    if (TEST_STATUS)
	status = dcl_final_status(	/* Go get the subprocess status */
			&dcl,		/*   for the command that we */
			final_status);	/*     just executed */
/*
** Return to the caller
*/
    return status;			    /* Return the call status */
}

/*		    DCL FINAL STATUS
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function will determine the final status of the executed command.
**	It will do this by translating the symbol $STATUS in the sub-process.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      Local data structures
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      Status of the command that was executed.
**
**  SIDE EFFECTS:
**
**      none
**
**--
**/
unsigned int	dcl_final_status(
    struct	dcl_structure	*process_info,
    unsigned	int		*final_status)
{
/*
** Local permanent strings
*/
    static  readonly	$DESCRIPTOR(command, "SHOW SYMBOL $STATUS");
    static  readonly	$DESCRIPTOR(search_string, "  $STATUS == \"%X");
/*
** Local variables
*/
    unsigned int    status;		/* Condition code to return */
    char	    read_buffer[255];
/*
** Main logic:
**  - Issue a command to get the value of the symbol status
**  - Read the responce off the subprocesses output channel
*/
    status = dcl_write_command(process_info, &command);
    if (TEST_STATUS)
    {
    /*
    ** Block variables
    */
	struct	    dsc$descriptor  final_status_string = $INIT_DESCRIPTOR;
	unsigned    short	    loop = TRUE;
	unsigned    int		    cmp_state;
	unsigned    int		    string_length;
    /*
    ** Make sure that the buffer is cleared out, then
    **	Wait until the subprocess completes the SHOW SYMBOL $STATUS command
    */
	while ( (TEST_STATUS) && (loop) )
	{
	/*
	** Issue a QIO to read the result of the subprocesses DCL command
	*/
	    memset(&read_buffer, '\0', 255);
	    status = dcl_post_read_io(&read_buffer, 255);
	    if (TEST_STATUS)
	    {
	    /*
	    ** See if the Search_String is contained in the output buffer
	    */
		fprintf(subproc_log_file,"\n%s", read_buffer);
		cmp_state = memcmp(				/* See if the strings match */
				&read_buffer,			/* First string */
				search_string.dsc$a_pointer,	/* Second string */
				search_string.dsc$w_length);	/* Number of bytes to compare */
		if (cmp_state == 0)
		    loop = FALSE;
	    }				/* End IF ... */
	}				/* End While ... */
    /*
    ** Now set up so we can move the string value to a descriptor, and
    **	then move it into the descriptor
    */
	if (TEST_STATUS)
	{
	    string_length = (process_info->output_iosb.count - 1) -
			     search_string.dsc$w_length;
	    STR$COPY_R(
		&final_status_string,
		&string_length,
		&read_buffer[search_string.dsc$w_length]);
	/*
	** Convert the Hexidecimal Text into an Integer
	*/
	    status = OTS$CVT_TZ_L(&final_status_string, final_status);
	}
    }
/*
** Return the final status
*/
    return status;
}

/*		    DCL ASSIGN MAILBOX
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function will create a mailbox and return the unit number and the
**	name of the mailbox.
**
**  FORMAL PARAMETERS:
**
**      channel - Place to store the channel number assigned to the mailbox.
**	unit - Place to store the unit number of the mailbox that is assigned.
**	name - Name of the mailbox that is assigned.
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      System service condition code
**
**  SIDE EFFECTS:
**
**      Assigns a mailbox
**
**--
**/
unsigned int	dcl_assign_mbx(
    unsigned	int		*channel,
    unsigned	int		*unit,
    struct	dsc$descriptor	*name)
{
/*
** Local variables
*/
    unsigned	char	    name_string[128];	    /* Name string */
    short	int	    name_length;	    /* Returned length of the name */
    unsigned	int	    status;		    /* Condition code to return */
    unsigned	int	    dummy;		    /* dummy variable */
    struct	item_list_3 getdvi_itemlist[] =	    /* Itemlist for GETDVIW call */
	{sizeof name_string,	DVI$_DEVNAM,	&name_string, &name_length,
	 4,			DVI$_UNIT,	unit,	      &dummy,
	 0,			0,		0,	      0};
/*
** Main logic:
**  - Create the mailbox
**  - Get the device name and the unit number via GETDVIW
*/
    status = sys$crembx (
		 0,			    /* Perm flag */
		 channel,		    /* Where to store the channel */
		 1024,			    /* Maximum message size */
		 1024,			    /* Buffer quota */
		 0,			    /* Protection mask */
		 0,			    /* Access mode */
		 0);			    /* Logical name */
    if (! TEST_STATUS)			    /* If that failed */
	lib$signal(status);		    /* Note the fact */
/*
** Now get the mailbox device information
*/
    status = sys$getdviw (
		 0,			    /* EFN */
		 *channel,		    /* Channel */
		 0,			    /* Device name */
		 &getdvi_itemlist,	    /* Item list */
		 0,			    /* I/O status block */
		 0,			    /* AST address */
		 0,			    /* AST parameters */
		 0);			    /* Null argument */
/*
** If the GETDVI worked then copy the answere into a Descriptor
*/
    if (TEST_STATUS)
	status = STR$COPY_R(
		     name,		    /* Place to store the name */
		     &name_length,	    /* Length of the name */
		     &name_string);	    /* Place were the name is */
/*
** If failure is indicated then Signal the error
*/
    if (! TEST_STATUS)
	lib$signal(status);
/*
** Return the final status to the caller
*/
    return status;
}

/*		    DCL CREATE the PROCESS
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This routine is called to create the subprocess.  It will run the
**	    program LOGINOUT.EXE in order to create a CLI for the subprocess
**	    to use.  Input and output devices will be based on Mailbox names
**	    defined in the DCL structure.
**
**  FORMAL PARAMETERS:
**
**	privileges	->  Pointer to Quadword containing the privileges the
**				subprocess is to be created with
**
**  IMPLICIT INPUTS:
**
**	none
**
**  IMPLICIT OUTPUTS:
**
**	none
**
**  FUNCTION VALUE:
**
**      System Service value
**
**  SIDE EFFECTS:
**
**      none
**
**--
**/
unsigned    int	dcl_create_the_process(
		    unsigned	int		*privileges)
{
/*
** Readonly strings
*/
    static  readonly
	$DESCRIPTOR(process_name, "Lcl$sub_proc_");
    static  readonly
	$DESCRIPTOR(image_name, "SYS$SYSTEM:LOGINOUT.EXE");
    static  readonly
	$DESCRIPTOR(set_noon_command, "SET NOON");
/*
** Local variables
*/
    struct	dsc$descriptor	final_process_name = $INIT_DESCRIPTOR;
    struct	dsc$descriptor	char_descriptor    = $STATIC_DESCRIPTOR;
    char			character;
    char			base_character;
    unsigned    int		loop;
    unsigned	int		status;
    short	int		sequence_number;
/*
** Set up a static descriptor to point at a sequence character
*/
    char_descriptor.dsc$a_pointer = &character;
    char_descriptor.dsc$w_length  = 1;
    loop = TRUE;
    sequence_number = 0;
    base_character = '0';
/*
** Now Loop until either the process name is unique,
**	or an error other than SS$_DUPLNAM occurs
*/
    while (loop)
    {
    /*
    ** If the sequence number is = 10 and the base character is still an
    **	ASCII Zero, then reset the sequence number and change the base
    **	character to an ASCII Lower-case A
    */
	if ( (base_character == '0') && (sequence_number == 10) )
	{
	    base_character = 'a';	    /* Now use Characters for uniqueness */
	    sequence_number = 0;	    /* Reset the sequence number */
	}
    /*
    ** Create a character form of the Sequence number
    **  and use it with the base name to get an Final Process Name
    */
	character = base_character + sequence_number++;
	STR$CONCAT(			    /* Concatenate the strings */
		&final_process_name,	    /* Resultant string */
		&process_name,		    /* First source string */
		&char_descriptor);	    /* Last source string */
    /*
    ** Now try to create the process with the specified privileges
    */
	status = SYS$CREPRC(		    /* Call the Create Process function */
		     &dcl.pid,		    /* Process ID */
		     &image_name,	    /* Image to run */
		     &dcl.input_name,	    /* Definition for SYS$INPUT */
		     &dcl.output_name,	    /* Definition for SYS$OUTPUT */
		     &dcl.output_name,	    /* Definition for SYS$ERROR */
		     privileges,	    /* Privileges */
		     0,			    /* Quotas */
		     &final_process_name,   /* Name of the process */
		     4,			    /* Base priority */
		     0,			    /* Use my UIC */
		     dcl.term_unit,	    /* Termination mailbox unit number */
		     0);		    /* Status flags */
    /*
    ** Free up the dynamic string and see if we have to loop again
    */
	STR$FREE1_DX(&final_process_name);
	if (status == SS$_DUPLNAM)
	    loop = TRUE;		/* Duplicate process name, try again */
	else
	    loop = FALSE;		/* Jump out of the loop */
    }				/* End WHILE ... */
/*
** If the process was created OK then issue a command to execute
**  the required initial commands
*/
    if (TEST_STATUS)
    {
    /*
    ** Open up a log file for a record of all the command outputs
    */
	subproc_log_file = fopen(SUBPROCESS_LOG_FILE, "w");
	if (subproc_log_file == NULL)
	    printf("\n WARNING, unable to open Subprocess Log File   %s",SUBPROCESS_LOG_FILE);
	else
	    fprintf(subproc_log_file,"\n   Beginning of Log file for subprocess PID = %x (hex)\n", dcl.pid);
    /*
    ** Issue a command to NOT logout the process on an error
    */
	status = dcl_write_command(&dcl, &set_noon_command);
    }
/*
** Return the final status to the caller
*/
    return status;
}

/*		    DCL WRITE COMMAND
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This procedure is used to write a DCL command to the Subprocesses
**	    input device.  Thus forcing the subprocess to actually issue
**	    the DCL command.
**
**  FORMAL PARAMETERS:
**
**      process_info	->  Internal data structure specifying characteristics
**				of the subprocess
**	command_line	->  Pointer to a descriptor containing the DCL command
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  FUNCTION VALUE:
**
**      System service return
**
**  SIDE EFFECTS:
**
**      The DCL command is executed by the subprocess
**
**--
**/
unsigned    int	dcl_write_command(
		    struct  dcl_structure   *process_info,
		    struct  dsc$descriptor  *command_line)
{
/*
** Local variables
*/
    unsigned	int		status;
/*
** Write the command out to the Subprocesses input channel
*/
    status = SYS$QIOW(				/* Issue a syncronous QIO */
		 0,				/* No event flag */
		 process_info->input_channel,	/* IO Channel */
		 IO$_WRITEVBLK | IO$M_NOW,	/* IO operation - WRITE */
		 &process_info->input_iosb,	/* IO status block */
		 0,				/* No AST routine */
		 0,				/* No AST arguement */
		 command_line->dsc$a_pointer,	/* Address of output buffer */
		 command_line->dsc$w_length,	/* Length of output buffer */
		 0, 0, 0, 0);			/* No P3, P4, P5, P6 arguements */
/*
** If the QIO worked then get the status out of the IOSB
*/
    if (TEST_STATUS)
	status = process_info->input_iosb.status;
/*
** Return the final status
*/
    return status;
}

/*		    DCL WAIT UNTIL DONE
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This routine is called to suspend the current process until the
**	    subprocess has completed the command that it was issued.  This
**	    is done by using a set mode I/O to call an AST routine when the
**	    subprocess writes something into the mailbox.  After issueing the
**	    set mode I/O, the process then hibernates.
**
**  FORMAL PARAMETERS:
**
**      process_info	->  Pointer to information block for the subprocess
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  FUNCTION VALUE:
**
**      System Service value
**
**  SIDE EFFECTS:
**
**      The process hibernates until the subprocess writes something
**	    to the output channel
**
**--
**/
unsigned    int	dcl_wait_until_done(
		    struct  dcl_structure   *process_info)
{
    unsigned	int		status;
/*
** Issue the Set WRITE ATTENTION call for the Output mailbod
*/
    status = SYS$QIOW(				/* Issue the QIO */
		 0,				/* No Event flag */
		 process_info->output_channel,	/* IO Channel */
		 IO$_SETMODE | IO$M_WRTATTN,	/* SETMODE IO */
		 &process_info->output_iosb,	/* IO status block */
		 0,				/* No AST address */
		 0,				/* No AST arguement */
		 &wake_ast,			/* Address of AST routine */
		 0, 0, 0, 0, 0);		/* No other P? arguements */
/*
** Determine the result of the Set Mode QIO
*/
    if (TEST_STATUS)
	status = process_info->output_iosb.status;
/*
** If alls well, then goto sleep until the subprocess writes something to the
**  mailbox
*/
    if (TEST_STATUS)
	SYS$HIBER();
/*
** Return the final status to the caller
*/
    return status;
}

/*		    DCL POST READ I/O
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This routine is called to read in the output that the subprocess
**	    got back from the command that it issued.  The routine will
**	    keep reading until there is no more output to be read.
**
**  FORMAL PARAMETERS:
**
**      process_info	->  Pointer to structure containing information on
**				the subprocess
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  FUNCTION VALUE:
**
**      System service return
**
**  SIDE EFFECTS:
**
**      none
**
**--
**/
unsigned    int	dcl_post_read_io(
		    char	*buffer_address,
		    int		buffer_length)
{
    unsigned	int		status = SS$_NORMAL;
/*
** Read in the output that the suprocess has written to the mailbox
*/
    status = SYS$QIO(				/* Issue the IO request */
		 0,				/* No event flag */
		 dcl.output_channel,		/* IO channel */
		 IO$_READVBLK,			/* IO function - READ */
		 &dcl.output_iosb,		/* IO status block */
		 &read_ast,			/* AST address */
		 0,				/* No AST arguement */
		 buffer_address,		/* Buffer address */
		 buffer_length,			/* Buffer length */
		 0, 0, 0, 0);			/* No more arguements */
    SYS$HIBER();
    if (TEST_STATUS)
	status = dcl.output_iosb.status;
    return status;
}

/*		    POST TERMINATION READ
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This routine is used to post an Asyncronous I/O to read information
**	    off the termination channel.  When the subprocess is terminated
**	    a final message will be written to that channel.
**
**  FORMAL PARAMETERS:
**
**      process_info	->  Pointer to block containing information about
**				the subprocess
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  FUNCTION VALUE:
**
**      System Service routine
**
**  SIDE EFFECTS:
**
**      An Asyncronous I/O will be outstanding until subprocess terminates
**
**--
**/
unsigned    int	post_termination_read(
		    struct	dcl_structure	*process_info)
{
/*
** Post an async IO on the termination channel
*/
    return  SYS$QIO(				/* Issue the IO request */
		 0,				/* No event flag */
		 process_info->term_channel,	/* IO channel */
		 IO$_READVBLK,			/* IO function - READ */
		 &process_info->term_iosb,	/* IO status block */
		 &term_ast,			/* Address of AST routine */
		 0,				/* No Ast arguements */
		 &term_buffer,			/* Buffer address */
		 255,				/* Buffer length */
		 0, 0, 0, 0);			/* No more arguements */
}

/*		    WAKE-UP AST
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This is the AST routine called when the subprocess writes something
**	    to the mailbox.  The routine will wake up the current process
**	    and then exit.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      none
**
**  SIDE EFFECTS:
**
**      The process is woken back up
**
**--
**/
wake_ast()
{
/*
** Wake up the process
*/
    SYS$WAKE(		/* Wake up the process */
	0,		/* No PID, use our own */
	0);		/* No Process Name, use our own */
}

/*		    Subprocess TERMINATION AST
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This is the AST routine called when the subprocess terminates.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      none
**
**  SIDE EFFECTS:
**
**      The process is woken back up
**
**--
**/
term_ast()
{
    printf("\n  *** ATTN*** The accounting subprocess has terminated\n");
}

/*		    READ I/O AST
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This AST routine is called when the Asyncronous read of the mailbox
**	    completes.  At that point we will wake up the current process.
**
**  FORMAL PARAMETERS:
**
**      none
**
**  IMPLICIT INPUTS:
**
**      none
**
**  IMPLICIT OUTPUTS:
**
**      none
**
**  COMPLETION CODES:
**
**      none
**
**  SIDE EFFECTS:
**
**      The current process is started up again
**
**--
**/
read_ast()
{
    SYS$WAKE(		/* Wake up the process */
	0,		/* No PID, use our own */
	0);		/* No Process Name, use our own */
}
----------------------------End of DCL command program-------------------------

----------------------------Required include file------------------------------
/*
**++
**  FACILITY:
**
**      Stevens Accounting System
**
**  ABSTRACT:
**
**      [@tbs@]
**
**  AUTHORS:
**
**      [@tbs@]
**
**
**  CREATION DATE:     14-July-1987
**
**  MODIFICATION HISTORY:
**--
**/

/*
** Define TEST_STATUS, to see if an error was returned or not
*/
#define	TEST_STATUS	(status & 1)
/*
** Parameters -- Standard VMS Specific parameters
*/

#define	MAX_USERNAME	39		/* Maximum number of characters in a user name */

/*
 * A macro, $DESCRIPTOR, is defined in the file included above, and can be
 * used to declare descriptors for the usual C strings (NUL-terminated arrays
 * of char).  This macro is not adequate for all the cases requiring descriptors
 * in this program, however, so we must define a few more macros:
 *
 * $DESCRIPTOR1 can be used for 1-character items, or for whole structures
 * $DESCRIPTORM can be used for strings that are structure members, or for
 *		any other string not terminated by a NUL character
 * $DESCRIPTORA generates an array descriptor, and is used to describe
 *		the workspaces and terminal control area
 *
 * LENGTH and POINTER are useful shorthands for accessing descriptor fields
 */

#define $DESCRIPTOR1(name,string)	struct dsc$descriptor_s \
name = { sizeof(string), DSC$K_DTYPE_T, DSC$K_CLASS_S, &string }

#define $DESCRIPTORM(name,string)	struct dsc$descriptor_s \
name = { sizeof(string), DSC$K_DTYPE_T, DSC$K_CLASS_S, string }

#define $DESCRIPTORA(name,array,type)	struct dsc$descriptor_a \
name = { sizeof (type), DSC$K_DTYPE_L, DSC$K_CLASS_A, array, 0, 0, { 0, 0, 0, 0, 0 }, 1, sizeof array }

#define LENGTH(descriptor)	descriptor.dsc$w_length
#define POINTER(descriptor)	descriptor.dsc$a_pointer
#define $INIT_DESCRIPTOR	{0, DSC$K_DTYPE_T, DSC$K_CLASS_D, 0}
#define	$STATIC_DESCRIPTOR	{0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}

/*
** Structure definitions
**
** key_word_list - Structure to define what a keywork list is.
**
*/
struct	key_word_list
{
    struct dsc$descriptor *keyword;	    /* Pointer to keyword */
    unsigned int keyword_information;	    /* keyword specific information */
};

/*
** Standard VAX/VMS item list definitions.  This item lists are used for various
** system service calls.  There are two flavors basic flavors, the three item
** item list and the two item item lits.
*/

struct	item_list_2
{
    unsigned short  buffer_length;		/* Length of the buffer */
    unsigned short  item_code;			/* Item code */
    unsigned int    *buffer_address;		/* Address of the buffer */
};

struct	item_list_3
{
    unsigned short  buffer_length;		/* Length of the buffer */
    unsigned short  item_code;			/* Item code */
    unsigned int    *buffer_address;		/* Address of the buffer */
    unsigned int    *return_length_address;	/* Returned information */
};

struct	item_list_acct
{
    unsigned short  buffer_length;		/* Length of the buffer */
    unsigned short  item_code;			/* Item code */
    unsigned char   buffer[];			/* Buffer */
};

/*
** Definitions for the item list descriptor
*/
#define	DSC$ITEMLIST {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, 0}

/*
** Accounting information is passed in the program as an item list.  That is it
** will conform to Digital's standard idea of what an item list is, except that
** the information that is contained in the item list will no be a pointer to
** any information, but the information itself.
**
** Information types:
**  - String:  - Stored as ASCIZ with a byte count.
**  - Numbers: - Stored as a long word.
**
*/

/*
** All item codes are stored in ranges.
**
**     1 -  99	    UAF associated data
**   100 - 199	    Diskquota associated data
**   200 - 299	    Project associated data
**   300 - 399	    Quantum RS user associated data
**   400 - 499	    Quantum RS group associated data
**
** New items are added on the end.
**
*/

#define	ITM_END_OF_LIST	   0	    /* End of the item list */

/*
** General item codes
*/
#define	ITM_UIC		1	    /* General UIC */

/*
** UAF Item codes
*/

#define	ITM_UAF_OWNER	10	    /* Owner name */
#define	ITM_UAF_ACCOUNT	11	    /* Account */
#define	ITM_UAF_DEVICE	12	    /* Default device name */
#define	ITM_UAF_DIRECTORY 13	    /* Default directory name */
#define	ITM_UAF_EXPIRATION 14	    /* Expiration date/time */
#define	ITM_UAF_UIC	15	    /* UAF UIC */

/*
** Diskquota item codes
*/

#define	ITM_DISK_PERM	30	    /* Diskquota - Perm */

/*
**  Quantum RS user item codes
*/
#define ITM_USN_INACTIVE		310	/* Inactive flag */
#define ITM_USN_BILLABLE		311	/* Billable flag */
#define ITM_USN_POST_DISK_USAGE		312	/* Post disk usage flag */
#define ITM_USN_POST_SESSION_USAGE	313	/* Post session resource usage */

/*
**  Rate schedule associated with each user for each node.
*/
#define ITM_USN_VXA_RATES		320	/* Rate schedule used (SITVXA) */
#define ITM_USN_VXB_RATES		321	/* Rate schedule used (SITVXB) */
#define ITM_USN_VXC_RATES		322	/* Rate schedule used (SITVXC) */
/*
**  Text descriptions of the users for each node.
*/
#define ITM_USN_VXA_DESCRIPTION		330	/* SITVXA user description (text) */

#define ITM_USN_VXB_DESCRIPTION		331	/* SITVXB user description (text) */

#define ITM_USN_VXC_DESCRIPTION		332	/* SITVXC user description (text) */

/*
**  Quantum RS group item codes
*/
#define ITM_GRP_ACTIVE		410	/* Inactive flag */
#define ITM_GRP_BILLABLE	411	/* Billable flag */
#define ITM_GRP_AGGREGATE	412	/* Aggregate usage across group members*/
#define ITM_GRP_RATES		413	/* Group rate schedule */
#define ITM_GRP_CHG_COUNT	414	/* Group charge count */
#define ITM_GRP_INIT_DATE	415	/* Group initialization date */
#define ITM_GRP_MOD_COUNT	416	/* Modification count */
#define ITM_GRP_LAST_MOD_DATE	417	/* Last modification date */
#define ITM_GRP_DESCRIPTION	420	/* Group description (text) */

/*
** The following structure is used to map an item list item to a field in the
** current form that is being processed.  This structure is used by all the
** modules to store the item list items into the form.
*/

struct	item_field_mapping
{
    unsigned short  item_type;		/* Item type to make from */
    struct dsc$descriptor *field_name;	/* Field name */
};

/*
** VAX/VMS I/O status block (IOSB) structure definition
*/

struct	io_status_block
    {
	unsigned short	int status;		/* Final status */
	unsigned short	int count;		/* Byte count for many devices */
	unsigned long	devdepend;		/* Device dependent part */
    };

/*
** The following definitions are required by the routines that handle the
**	processing of the local databases.  These databases include:
**		Available UIC Database
**		Group Definition Database
**		Account Definition Database
*/

/* Define flags to specify which type of UIC Number to process */

#define	    FIND_UIC_MEMBER_NUMBER  1	/* Find a free UIC Member Number */
#define	    FIND_UIC_GROUP_NUMBER   2	/* Find a free UIC Group Number */

/* 
** Define the UIC Number Catagories, which are actually indexes into
**  an initial value array
*/

#define	    CGY$C_SYSTEM	    0   /* Use valid System UIC's */
#define	    CGY$C_COMP_CENTER	    1   /* Use valid Computer Center UIC's */
#define	    CGY$C_DEPARTMENT	    2   /* Use valid Departmental UIC's */
#define	    CGY$C_RESEARCH	    3   /* Use valid Research UIC's */
#define	    CGY$C_SPECIAL	    4   /* Use valid Special project UIC's */
#define	    CGY$C_GRADUATE	    5   /* Use valid Graduate UIC's */
#define	    CGY$C_UNDERGRADUATE	    6   /* Use valid Undergraduate UIC's */

--------------------------End of include file----------------------------------

 Good luck, and thanks for those who sent me help

 David L. Stevens
 Stevens Tech

 CCnet:		SITVXC::DSTEVENS
 BITnet:	DSTEVENS@SITVXA
 INTERnet:	DSTEVENS@VAXC.STEVENS-TECH.EDU

 Disclaimer:	It's not my fault

------------