From:	CSBVAX::CSBVAX::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX"  3-FEB-1989 01:43
To:	MRGATE::"ARISIA::EVERHART"
Subj:	VMS subprocess command execution 1/2


Received: From KL.SRI.COM by CRVAX.SRI.COM with TCP; Thu,  2 FEB 89 22:01:22 PDT
Received: from B.PSC.EDU (cpwscb.psc.edu) by KL.SRI.COM with TCP; Thu, 2 Feb 89 21:56:39 PST
Date:    Fri, 3 Feb 1989 0:57:29 EST
From:    GENTZEL @ B.PSC.EDU (Dave Gentzel)
Message-Id: <890203005729.21001b51@B.PSC.EDU>
Subject: VMS subprocess command execution 1/2
To:      INFO-VAX @ KL.SRI.COM
X-Vmsmail-To: SMTP%"INFO-VAX@KL.SRI.COM"

$! ..................... Cut between dotted lines and save. ....................
$!.............................................................................
$! VAX/VMS archive file created by VMS_SHARE V06.06A 19-DEC-1988.
$!
$! VMS_SHARE was written by James Gray (Gray:OSBUSouth@Xerox.COM) from
$! VMS_SHAR by Michael Bednarek (U3369429@ucsvc.dn.mu.oz.au).
$!
$! To unpack, simply save, concatinate all parts into one file and
$! execute (@) that file.
$!
$! This archive was created by user GENTZEL
$! on  3-FEB-1989 00:51:59.24.
$!
$! ATTENTION: To keep each article below 32 blocks (16384 bytes), this
$!            program has been transmitted in 2 parts.  You should
$!            concatenate ALL parts to ONE file and execute (@) that file.
$!
$! It contains the following 4 files:
$!        DOC.TXT
$!        SUBPROC.C
$!        SUBPROC.H
$!        TEST.C
$!
$!==============================================================================
$ SET SYMBOL/SCOPE=( NOLOCAL, NOGLOBAL )
$ VERSION = F$GETSYI( "VERSION" )
$ IF VERSION .GES "V4.4" THEN GOTO VERSION_OK
$ WRITE SYS$OUTPUT "You are running VMS ''VERSION'; ", -
    "VMS_SHARE V06.06A 19-DEC-1988 requires VMS V4.4 or higher."
$ EXIT 44 ! SS$_ABORT
$VERSION_OK:
$ GOTO START
$!
$UNPACK_FILE:
$ WRITE SYS$OUTPUT "Creating ''FILE_IS'"
$ DEFINE/USER_MODE SYS$OUTPUT NL:
$ EDIT/TPU/COMMAND=SYS$INPUT/NODISPLAY/OUTPUT='FILE_IS'/NOSECTION -
    VMS_SHARE_DUMMY.DUMMY
b_part := CREATE_BUFFER( "{Part}", GET_INFO( COMMAND_LINE, "file_name" ) )
; s_file_spec := GET_INFO( COMMAND_LINE, "output_file" ); SET( OUTPUT_FILE
, b_part, s_file_spec ); b_errors := CREATE_BUFFER( "{Errors}" ); i_errors 
:= 0; pat_beg_1 := ANCHOR & "-+-+-+ Beginning"; pat_beg_2 := LINE_BEGIN 
& "+-+-+-+ Beginning"; pat_end := ANCHOR & "+-+-+-+-+ End"; POSITION
( BEGINNING_OF( b_part ) ); LOOP EXITIF SEARCH( SPAN( ' ' )@r_trail 
& LINE_END, FORWARD) = 0; POSITION( r_trail ); ERASE( r_trail ); ENDLOOP
; POSITION( BEGINNING_OF( b_part ) ); i_append_line := 0; LOOP EXITIF MARK
( NONE ) = END_OF( b_part ); s_x := ERASE_CHARACTER( 1 )
; IF s_x = '+' THEN r_skip := SEARCH( pat_beg_1, FORWARD, EXACT ); IF r_skip 
<> 0 THEN s_x := ''; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ERASE_LINE; ENDIF
; ENDIF; IF s_x = '-' THEN r_skip := SEARCH( pat_end, FORWARD, EXACT )
; IF r_skip <> 0 THEN s_x := ''; MOVE_HORIZONTAL( -CURRENT_OFFSET ); m_skip 
:= MARK( NONE ); r_skip := SEARCH( pat_beg_2, FORWARD, EXACT ); IF r_skip 
<> 0 THEN POSITION( END_OF( r_skip ) ); MOVE_HORIZONTAL( -CURRENT_OFFSET )
; MOVE_VERTICAL( 1 ); MOVE_HORIZONTAL( -1 ); ELSE POSITION( END_OF( b_part ) 
); ENDIF; ERASE( CREATE_RANGE( m_skip, MARK( NONE ), NONE ) ); ENDIF; ENDIF
; IF s_x = 'V' THEN s_x := ''; IF i_append_line <> 0 THEN APPEND_LINE
; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ENDIF; i_append_line := 1
; MOVE_VERTICAL( 1 ); ENDIF; IF s_x = 'X' THEN s_x := ''; IF i_append_line 
<> 0 THEN APPEND_LINE; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ENDIF
; i_append_line := 0; MOVE_VERTICAL( 1 ); ENDIF; IF s_x <> '' THEN i_errors 
:= i_errors + 1; s_text := CURRENT_LINE; POSITION( b_errors ); COPY_TEXT
( "The following line could not be unpacked properly:" ); SPLIT_LINE
; COPY_TEXT( s_x ); COPY_TEXT( s_text ); POSITION( b_part ); MOVE_VERTICAL
( 1 ); ENDIF; ENDLOOP; POSITION( BEGINNING_OF( b_part ) ); LOOP r_x := SEARCH
( "`", FORWARD, EXACT ); EXITIF r_x = 0; POSITION( r_x ); ERASE_CHARACTER( 1 
); COPY_TEXT( ASCII( INT( ERASE_CHARACTER( 3 ) ) ) ); ENDLOOP
; IF i_errors = 0 THEN SET( NO_WRITE, b_errors, ON ); ELSE POSITION
( BEGINNING_OF( b_errors ) ); COPY_TEXT( FAO
( "The following !UL errors were detected while unpacking !AS", i_errors
, s_file_spec ) ); SPLIT_LINE; SET( OUTPUT_FILE, b_errors, "SYS$COMMAND" )
; ENDIF; EXIT; 
$ DELETE VMS_SHARE_DUMMY.DUMMY;*
$ CHECKSUM 'FILE_IS
$ WRITE SYS$OUTPUT " CHECKSUM ", -
  F$ELEMENT( CHECKSUM_IS .EQ. CHECKSUM$CHECKSUM, ",", "failed!!,passed." )
$ RETURN
$!
$START:
$ FILE_IS = "DOC.TXT"
$ CHECKSUM_IS = 912984068
$ COPY SYS$INPUT VMS_SHARE_DUMMY.DUMMY
X* INITIALIZE
X  ** Assign input, output, termination, and status mbx's.
X  ** Issue read on termination mbx.
X  ** Create process with input mbx name as input, output mbx name as output
X     and error, termination mbx unit as termination.
X  ** Write "SET NOON!'F$VERIFY(0)" and
X     "OPEN/WRITE <status_logical_name> <status_mbx_name>".  This prevents
X     errors from killing the process, supresses the echoing of commands, and
X     opens the file which the process will use to write status codes back to
X     its parent.
X
X* EXECUTE COMMAND
X  ** Write command string to input mbx.
X  ** Write "WRITE <status_logical_name> $STATUS" to input mbx.
X  ** Issue an asynchronous read (with AST routine) on output mbx.
X  ** Issue synchronous read on status mbx.
X
X* PROCESSING
X  ** When output AST routine is called, pass data back to caller and reissue
X     output mbx read.
V  ** When status mbx read is satisfied, command is complete.  String read wil
Xl
V     be an 8 character hex number preceded by "%X" which is the exit status o
Xf
X     the command.  $CANCEL any pending read on output mbx.
X
X* MISC
X  ** If the termination mbx read is ever satisfied, the process has died.
X     $CANCEL any pending reads on the status mbx (note that this will in turn
X     $CANCEL any reads on the output mbx).
X  ** The commands string will not be echoed by the subprocess.  If echoing is
X     needed, it must be done manually.
X  ** The caller should supply an exit handler to kill all subprocesses (as
X     well as do application specific cleanup such as deleting an incomplete
X     object file for MAKE).
X
X* TODO
X  ** If we REALLY want to support multiple, concurrent processes, need to not
X     have the process_execute_command call wait for it's read on the status
X     mbx.
X  ** To properly support output suppression for FANCY_OUTPUT, need to not
X     write the new command (and change output routine) until the previous
X     command completes.  We can check this by means of a READATTN ast on the
V     output channel or when the status read is satisfied (if there IS a statu
Xs
X     read).  Perhaps the process structure should contain a queue of pending
X     commands?
X  ** Need a way for the caller to wait on all pending commands.
V  ** Need to genericise the status read/interpretation stuff.  Need to be abl
Xe
V     to specify what string (if any) should be sent and what routine should b
Xe
V     called to convert the string read into an integer.  Need some way for th
Xe
X     caller to find the status mbx name (just look into structure?).
X  ** If the status genericising is done, allow caller to specify image to run
X     if USE_CREPRC (and supress writing of default setup strings).
X  ** Need a way for the caller to wait for some set of processes to complete
X     their commands.  EF's in the process structure?
$ GOSUB UNPACK_FILE
$ FILE_IS = "SUBPROC.C"
$ CHECKSUM_IS = 1143994837
$ COPY SYS$INPUT VMS_SHARE_DUMMY.DUMMY
X/*
X * SUBPROC: VMS subprocess manipulation package.
X *
X * Version: 1.0 (1-FEB-1989)
X *
X * Author: David B. Gentzel
X *`009of
X * Lexeme Corporation
X * gentzel%gold.scribe.com@vb.cc.cmu.edu
X *`009and/or
X * Pittsburgh Supercomputing Center
X * gentzel@godot.psc.edu/gentzel@cpwpsca.bitnet
X *
X * Loosely based on original code from David L. Stevens
X * (DSTEVENS@VAXC.STEVENS-TECH.EDU) which was posted to INFO-VAX on
X * 11-Sep-87.
X *
X * Heavily rewritten from the original:
X *`0091. Use a mailbox to get the status from the process rather than trying
X *`009   to look at the output of a "SHOW SYMBOL $STATUS" command.
X *`0092. Send the output from commands back to the caller (optional).
X *`0093. Make reading of process output asynchronous.
X *`0094. Caller allocates process structures to allow multiple concurrent
X *`009   processes (not yet fully implemented).
X *`0095. Propagate the priority of the parent process to the child.
X *`0096. Turn off verify in the child process.  It's up to the caller to
X *`009   echo the command if necessary.
X */
X
X#include <accdef.h>
X#include <dvidef.h>
X#include <iodef.h>
X#include <ssdef.h>
X#include <descrip.h>
X
X#include "subproc.h"`009/* Subprocess definitions */
X
X#ifdef USE_CREPRC
X# include <jpidef.h>`009/* GETJPI symbol definitions */
X#else
X# define CLI$M_NOWAIT 1
X# define CLI$M_NOKEYPAD 8
X#endif
X
X#define DEV_NAME_MAX 64`009/* Maximum length of DVI$_DEVNAM from $GETDVI */
X
Xstatic unsigned int assign_mbx(PROTO((unsigned int *channel,
X`009`009`009`009      unsigned int *unit,
X`009`009`009`009      struct dsc$descriptor *name)));
Xstatic unsigned int create_process(PROTO((struct process *process_info)));
Vstatic unsigned int write_setup_commands(PROTO((struct process *process_info)
X));
Xstatic unsigned int write_command(PROTO((struct process *process_info,
X`009`009`009`009`009 struct dsc$descriptor *command_line)));
X#ifdef FANCY_OUTPUT
Xstatic unsigned int post_output_read(PROTO((struct process *process_info)));
X#endif
X#ifdef USE_CREPRC
Vstatic unsigned int post_termination_read(PROTO((struct process *process_info
X)));
X#endif
Xstatic unsigned int post_status_read(PROTO((struct process *process_info,
X`009`009`009`009`009    char *buffer_address,
X`009`009`009`009`009    int buffer_length)));
X#ifdef FANCY_OUTPUT
Xstatic void read_ast(PROTO((struct process *process_info)));
Xstatic void status_ast(PROTO((struct process *process_info)));
X#endif
Xstatic void term_ast(PROTO((struct process *process_info)));
X
X#ifdef USE_CREPRC
Xchar term_buffer[ACC$K_TERMLEN];
X#endif
X
X/* Initialize the DCL command processing.  Returns status code. */
Xunsigned int
X#ifdef __STDC__
Xprocess_initialize(struct process *process_info)
X#else
Xprocess_initialize(process_info)
Xstruct process *process_info;
X#endif
X`123
X    /* Prototype dynamic string */
X    static struct dsc$descriptor dyn_string = DYNAMIC_DESCRIPTOR;
X    unsigned int status;`009`009/* Condition code to return */
X
X    /* Assign input mailbox */
X    process_info->input_name = dyn_string;
X    status = assign_mbx(&process_info->input_channel,
X`009`009`009&process_info->input_unit,
X`009`009`009&process_info->input_name);
X    if (!(status & 1))
X`009return status;
X#ifdef FANCY_OUTPUT
X    /* Assign output mailbox */
X    process_info->output_name = dyn_string;
X    status = assign_mbx(&process_info->output_channel,
X`009`009`009&process_info->output_unit,
X`009`009`009&process_info->output_name);
X    if (!(status & 1))
X`009return status;
X#endif
X    /* Assign status mailbox */
X    process_info->status_name = dyn_string;
X    status = assign_mbx(&process_info->status_channel,
X`009`009`009&process_info->status_unit,
X`009`009`009&process_info->status_name);
X    if (!(status & 1))
X`009return status;
X
X#ifdef USE_CREPRC
X    /* Assign termination mailbox */
X    process_info->term_name = dyn_string;
X    status = assign_mbx(&process_info->term_channel,
X`009`009`009&process_info->term_unit,
X`009`009`009&process_info->term_name);
X    if (!(status & 1))
X`009return status;
X    /* Post a read for when the process terminates */
X    status = post_termination_read(process_info);
X    if (!(status & 1))
X`009return status;
X#endif
X
X    /* Now create the process */
X    status = create_process(process_info);
X
X    if (status & 1)
X`009status = write_setup_commands(process_info);
X
X    return status;
X`125
X
X/* Shutdown the DCL command interface.  Returns status code. */
Xunsigned int
X#ifdef __STDC__
Xprocess_shutdown(struct process *process_info)
X#else
Xprocess_shutdown(process_info)
Xstruct process *process_info;
X#endif
X`123
X    unsigned short channels[] = `123
X`009process_info->input_channel,
X#ifdef FANCY_OUTPUT
X`009process_info->output_channel,
X#endif
X#ifdef USE_CREPRC
X`009process_info->term_channel,
X#endif
X`009process_info->status_channel
X    `125;
X    int i;
X    unsigned int return_status = SS$_NORMAL;
X    unsigned int status;`009`009/* Condition code to return */
X
X    for (i = 0; i < (sizeof channels / sizeof channels[0]); i++)
X    `123
X`009/* Cancel IO on the channel and deassign */
X`009status = SYS$CANCEL(channels[i]);
X`009if (!(status & 1))
X`009    return_status = status;
X`009status = SYS$DASSGN(channels[i]);
X`009if (!(status & 1))
X`009    return_status = status;
X    `125
X
X    /* Return any dynamic strings that are in the block now. */
X    STR$FREE1_DX(&process_info->input_name);
X    STR$FREE1_DX(&process_info->status_name);
X#ifdef FANCY_OUTPUT
X    STR$FREE1_DX(&process_info->output_name);
X#endif
X#ifdef USE_CREPRC
X    STR$FREE1_DX(&process_info->term_name);
X#endif
X
X    if (process_info->pid)
X    `123
X`009/* Now kill off the process, if it is still around */
X`009status = SYS$DELPRC(&process_info->pid, 0);
X`009if (status == SS$_NONEXPR)`009/* Already gone? */
X`009    status = SS$_NORMAL;`009/* Yes, convert this to a normal */
X`009if (!(status & 1))
X`009    return_status = status;
X`009process_info->pid = 0;
X    `125
X
X    return return_status;
X`125
X
X/* Execute a DCL command.  It will wait until the command is complete and
X   return the status of the executed command. */
Xunsigned int
X#ifdef __STDC__
Xprocess_execute_command(struct process *process_info,
X`009`009`009struct dsc$descriptor *command,
X`009`009`009unsigned int *command_status)
X#else
Xprocess_execute_command(process_info, command, command_status)
Xstruct process *process_info;
Xstruct dsc$descriptor *command;
Xunsigned int *command_status;
X#endif
X`123
X    static $DESCRIPTOR(status_command, "WRITE MAKE_STATUS_MBX $STATUS");
X    static char status_buffer[10];
X    static DESCRIPTOR_N(final_status_string,
X`009`009`009status_buffer + 2, sizeof status_buffer - 2);
X    static unsigned short count = sizeof status_buffer - 2;
X    unsigned int status;
X
V    /* Now if the process got created correctly, we can execute the command *
X/
X    status = write_command(process_info, command);
X    if (status & 1)
X`009status = write_command(process_info, &status_command);
X#ifdef FANCY_OUTPUT
X    if (status & 1)
X`009/* Issue a QIO to read the result of the subprocesses command */
X`009status = post_output_read(process_info);
X#endif
X    if (status & 1)
X`009status = post_status_read(process_info,
X`009`009`009`009  status_buffer, sizeof status_buffer);
X    if (status & 1)
X`009/* Convert the hexidecimal text into an integer */
X`009status = OTS$CVT_TZ_L(&final_status_string, command_status);
X    return status;
X`125
X
X/* Create a mailbox and return the channel number, unit number, and name of
X   the mailbox.  Returns condition code. */
Xstatic unsigned int
X#ifdef __STDC__
Xassign_mbx(unsigned int *channel, unsigned int *unit,
X`009   struct dsc$descriptor *name)
X#else
Xassign_mbx(channel, unit, name)
Xunsigned int *channel;
Xunsigned int *unit;
Xstruct dsc$descriptor *name;
X#endif
X`123
X    unsigned char name_string[DEV_NAME_MAX];
X    unsigned short int name_length, dummy;
X    struct item_list_3 getdvi_itemlist[] = `123
X`009`123 sizeof name_string, DVI$_DEVNAM, &name_string, &name_length `125,
X`009`123 sizeof *unit, DVI$_UNIT, unit, &dummy `125,
X`009`123 0, 0 `125
X    `125;
X    struct io_status_block iosb;
X    unsigned int status;
X
X    status = SYS$CREMBX(
X`0090,`009`009`009/* Temporary MBX */
X`009channel,`009`009/* Address of channel */
X`0090,`009`009`009/* Maximum message size */
X`0090,`009`009`009/* Buffer quota */
X`0090x0030,`009`009`009/* Protection mask (Owner RW) */
X`0090,`009`009`009/* Access mode */
X`0090);`009`009`009/* Logical name */
X    if (status & 1)
X`009/* Now get the mailbox device information */
-+-+-+-+-+ End of part 1 +-+-+-+-+-

