.TITLE FORK - Fork command stream to a subprocess ; FORK is a program to implement the PUSH side of a TWENEX-style PUSH/POP ; capability. This allows a user to interrupt a program ; via ^Y and initiate a subprocess running a new DCL session. All ; relevant context information from the parent process and DCL session ; is saved on a file to be restored in the subprocess. The file is written ; in the user's default directory, on the assumption that he should be ; allowed to write there. ; ; This program is intended to be run via an merge image activation in the ; context of a DCL internal command. Specifically, it assumes supervisor ; mode, registers pointing to DCL internal structures, and no image I/O ; segment. The code must be PIC since it gets loaded at an arbitrary point ; in P1 space. ; ; Written by: ; Gary L. Grebus ; Battelle Memorial Institute ; Columbus, Ohio ; ; V1.00 - 7-Sep-1981 ; Initial version. ; ; System symbols $JPIDEF ; $GETJPI symbol definitions $DIBDEF ; Symbols for device characteristics $ACCDEF ; Symbols for termination message $FABDEF ; $FAB offsets $PSLDEF ; Codes for access modes $LOGDEF ; Logical name block definitions ; Local symbols EQUIV_NAM_LEN = 128 ; Length of equivalence name buffers P_NAME_SZ = 15 ; Max size of a process name ENV_REC_SZ = 2 * 64 + 1 ; Size of environment record ; Magic internal definitions for DCL PRC_W_FLAGS = ^X54 ; Offset of flag word in work area PRC_M_DISABL = ^X4 ; Mask for ^Y disable bit PRC_L_INDFAB = ^X1C ; Offset to indirect FAB PRC_Q_GLOBAL = ^X28 ; Offset to global symbols listhead PRC_Q_LOCAL = ^X38 ; Offset to local symbols listhead SYM_L_FL = ^X0 ; Offsets in symbol entries - link SYM_B_NESTLEVEL = ^X0B ; Abbrev. point offset SYM_T_SYMBOL = ^X0C ; Offset to symbol name SYM_B_TYPE = ^X0A ; Offset to type field SYM_K_PERM = ^X01 ; Type symbol for permanent symbol ; Local macros .MACRO MK_DESC ASCIC_STR, DESC_ADDR ; Macro to generate a character string descriptor on the stack given ; the symbolic address of a ASCIC string MOVAB ASCIC_STR+1,-(SP) ; Push address of string MOVZBL ASCIC_STR,-(SP) ; Push the string length MOVL SP,DESC_ADDR ; SP contains the descriptor address .ENDM MK_DESC .PAGE .SBTTL RWDATA - Read/write data .PSECT RWDATA RD,WRT,NOEXE,NOSHR,LONG ENV_RAB: $RAB ; Skeleton RAB for environment file INPUT_NAME: .BLKB EQUIV_NAM_LEN+1 ; Buffer for input dev name and count OUTPUT_NAME: .BLKB EQUIV_NAM_LEN+1 ; Buffer for output dev name and count ERROR_NAME: .BLKB EQUIV_NAM_LEN+1 ; Buffer for error dev name and count IMAGE_NAME: .ASCIC /SYS$SYSTEM:LOGINOUT/ ; Name of image to execute in subproc SYSIN: .ASCIC /SYS$INPUT/ ; Logical name for input SYSOUT: .ASCIC /SYS$OUTPUT/ ; Logical name for output SYSERR: .ASCIC /SYS$ERROR/ ; Logical name for error PROC_NAME: .BLKB P_NAME_SZ+1 ; Buffer for process name and count PROC_NAME_LEN: .BLKL 1 ; Buffer for $GETJPI to return length ; of process name TRM_MBX_CHAN: .BLKW 1 ; Buffer to hold termination mailbox ; channel number CHAR_BUF: .BLKB DIB$K_LENGTH ; Buffer to hold termination mailbox ; characteristics. IOSB: .BLKQ 1 ; IO Status Block for read on ; termination mailbox TRM_MSG_BUF: .BLKB ACC$K_TERMLEN ; Buffer for termination mailbox SUB_PID: .BLKL 1 ; Buffer for subprocess PID BASE_PRI: .LONG 6 ; Buffer for base priority for subproc ; Skeleton request list for $GETJPI JPI_LIST: .WORD P_NAME_SZ ; Length of buffer .WORD JPI$_PRCNAM ; Code for process name JPI_P: .LONG 0 ; Space for buffer address .LONG 0 ; Space for address of length variable .WORD 4 ; Length of buffer .WORD JPI$_PRIB ; Code for base priority JPI_B: .LONG 0 ; Space for buffer address .LONG 0,0 ; End of list ENV_FAB_ADR: .BLKL 1 ; Address of FAB for environment file ENV_FSPEC: .ASCIC /SYS$LOGIN:FORK.ENV/ ; File spec for environment file LOGIN_LOG: .ASCIC /SYS$LOGIN/ ; Logical name for login device/dir LOGIN_EQUIV: .BLKB EQUIV_NAM_LEN ; Space for equiv name for above LOGIN_EQUIV_SZ: .BLKL 1 ; Current length of above name CUR_DEFDIR: .BLKB EQUIV_NAM_LEN ; Space for name of current ; default directory DEF_PROT: .BLKW 1 ; Buffer to hold default file ; protection. CUR_DEFDIR_SZ: .BLKL 1 ; Current length of above string DISK_LOG: .ASCIC /SYS$DISK/ ; Logical name for current disk SAVE_PRC_FLAGS: .BLKW 1 ; Saved DCL status flags ENV_REC: .BLKB ENV_REC_SZ ; Buffer for building records for ; environment file .PAGE ; ; This PSECT must get loaded at the very lowest address in the image ; .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY FORK,^M ; Register usage: ; R0-R1 - Scratch ; R2-R6 - Address of string descriptors on stack ; R11 - Assumed to point to DCL work area ; Translate logical names for process permanent files PUSHAL INPUT_NAME ; Translate input logical into PUSHAL SYSIN ; INPUT_NAME CALLS #2,TRANS_LOG PUSHAL OUTPUT_NAME ; Translate output logical into PUSHAL SYSOUT ; OUTPUT_NAME CALLS #2,TRANS_LOG PUSHAL ERROR_NAME ; Translate error logical into PUSHAL SYSERR ; ERROR_NAME CALLS #2,TRANS_LOG ; Do a $GETJPI to get info which we need to supply to subprocess MOVAL PROC_NAME+1,JPI_P ; Fill in buffer addr in $GETJPI list MOVAL PROC_NAME_LEN,JPI_P+4 ; Fill in address of length longword MOVAL BASE_PRI,JPI_B ; Fill in address for base priority $GETJPI_S ITMLST=JPI_LIST ; Get the info BLBS R0,5$ ; Branch if success BRW ERR_EXIT ; Build a process name for the subprocess 5$: SUBL3 PROC_NAME_LEN,- #P_NAME_SZ,R0 ; Compute nr of unused chars in name MOVL PROC_NAME_LEN,R1 ; Get length of name MOVC5 #0,.,#^A/ /,- R0,PROC_NAME+1[R1] ; Blank fill the name SUBL3 #1,#P_NAME_SZ,R0 ; Get nr of append positions MOVAL PROC_NAME+2,R1 ; Get address of buffer ADDL2 R0,R1 ; Compute address to get appendix 7$: CMPW #^A/_F/,-(R1) ; Is this position already appended? BNEQ 9$ ; Branch if not SUBL2 #2,R0 ; Decrement count BGTR 7$ ; Branch if more positions in name 9$: MOVW #^A/_F/,(R1) ; Append the fork appendix MOVB #P_NAME_SZ,PROC_NAME ; Fill in count for name ; Get a termination mailbox $CREMBX_S - CHAN=TRM_MBX_CHAN,- MAXMSG=#120 ; Get mailbox BLBS R0,10$ ; Branch if success BRW ERR_EXIT 10$: MOVAL CHAR_BUF,-(SP) ; Build desc for char buffer MOVL #DIB$K_LENGTH,-(SP) MOVL SP,R2 ; R2 points to descriptor $GETCHN_S - CHAN=TRM_MBX_CHAN,- PRIBUF=(R2) ; Get mailbox unit number BLBS R0,20$ ; Branch if success BRW CLEAN_UP ; Disable ^Y to prevent interruption. Also prevents main process ; from getting control if subprocess gets a ^Y 20$: MOVW PRC_W_FLAGS(R11),- SAVE_PRC_FLAGS ; Get the current ^Y status BISW #PRC_M_DISABL,- PRC_W_FLAGS(R11) ; And disable ^Y CALLS #0,DMP_ENV ; Dump the current environment ; for use by the subprocess BLBS R0,25$ ; Branch if success BRW ENAB_Y 25$: CALLS #0,SET_DEFDIR ; Restore default dev/dir to login ; values ; Create the subprocess MK_DESC INPUT_NAME,R2 ; Make descriptors for string args MK_DESC OUTPUT_NAME,R3 MK_DESC IMAGE_NAME,R4 MK_DESC PROC_NAME,R5 MK_DESC ERROR_NAME,R6 $CREPRC_S - IMAGE=(R4),- INPUT=(R2),- OUTPUT=(R3),- ERROR=(R6),- PRCNAM=(R5),- PIDADR=SUB_PID,- MBXUNT=CHAR_BUF+DIB$W_UNIT,- BASPRI=BASE_PRI ; Create a process BLBS R0,30$ ; Branch if $CREPRC worked BRW RST_DIR ; Read termination mailbox and hibernate. When something hits termination ; mailbox, we will be reawoken 30$: $QIO_S - CHAN=TRM_MBX_CHAN,- FUNC=#IO$_READVBLK,- ASTADR=AST_RTN,- IOSB=IOSB,- P1=TRM_MSG_BUF,- P2=#ACC$K_TERMLEN ; Read for a termination message BLBS R0,40$ ; Branch if success BRW RST_DIR 40$: $HIBER_S ; Go to sleep until subprocess done BLBS IOSB,50$ ; Check QIO status. Branch if ok. MOVZWL IOSB,R0 ; Return the I/O error 50$: MOVL TRM_MSG_BUF+ACC$L_FINALSTS,R0 ; Everything worked on our end. ; Return the process status ; Erase environment file and reset default dir. RST_DIR: $ERASE FAB=@ENV_FAB_ADR ; Zap the file CALLS #0,RESET_DEFDIR ; Reset directory ; Reenable ^Y status. ENAB_Y: MOVW SAVE_PRC_FLAGS,- PRC_W_FLAGS(R11) ; Restore saved ^Y status CLEAN_UP: PUSHL R0 ; Save the current condition value TSTL SUB_PID ; Did we create a subprocess? BEQL 60$ ; Branch if not $DELPRC_S PIDADR=SUB_PID ; Zap subprocess just to be sure 60$: $DASSGN_S - CHAN=TRM_MBX_CHAN ; Clean up the mailbox channel POPL R0 ; Restore the saved value ERR_EXIT: RET ; AST routine invoked when the read on the subprocess termination mailbox ; completes AST_RTN: .WORD 0 $WAKE_S ; Wake up our process RET .PAGE .SBTTL TRANS_LOG - Completely translate a logical name ; This routine completely translates a logical name passes as a counted ; ASCII string. The equivalence string is returned as a counted string. ; The output buffer is assumed to be EQUIV_NAM_LEN bytes long. This ; routine is reentrant and PIC. ; First parameter is the address of the logical name string. Second ; parameter is the address of the buffer to receive the equivalence name ; string. .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY TRANS_LOG,^M ; Register usage: ; R0-R1 - Scratch ; R2 - Address of descriptor for current source string ; R3 - Address of descriptor for current destination string ; R4-R5 - Scratch. ; R6 - Length of logical name ; R7 - Address of output buffer+1 MOVL 8(AP),R7 ; Get addr of dest buffer INCL R7 ; plus one (leave space for count) MOVL 4(AP),R0 ; Get address of input logical name MOVZBL (R0),R6 ; Get length of input name MOVC3 R6,1(R0),(R7) ; Move logical name into output buffer MOVAL (R7),-(SP) ; Build descriptor for input string ; in output buffer. Address MOVL R6,-(SP) ; and length MOVL SP,R2 ; R2 points to input string 10$: MOVAL (R7),-(SP) ; Build desc for output buffer. Addr MOVL #EQUIV_NAM_LEN,-(SP) ; and length MOVL SP,R3 ; Pointer to output desc in R3 $TRNLOG_S - LOGNAM=(R2),- RSLLEN=(R3),- RSLBUF=(R3) ; Translate the current input name BLBC R0,50$ ; Quit if error CMPB (R7),#^X1B ; Does the equiv name contain an ; escape prefix? BNEQ 20$ ; Branch if not SUBL2 #4,(R3) ; Else adjust length and ADDL2 #4,4(R3) ; and address to skip it 20$: CMPL R0,#SS$_NOTRAN ; Are we all done translating? BEQL 50$ ; Branch if so MOVL R3,R2 ; Else make old output into new input BRB 10$ ; And try again 50$: MOVB (R3),-1(R7) ; All done. Store count into output ; buffer RET ; And return .PAGE .SBTTL DMP_ENV - Dump current environment to file ; Routine to save to a file all of the info necessary to recreate the ; correct environment within the subprocess. The environment file ; is written in the login default directory on the assumption that it is ; a writeable place ; Also saves the current default directory in CUR_DEFDIR for use ; by other routines .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY DMP_ENV,^M<> ; Register usage: ; R0-R1 - Scratch ; Create and connect to a file to receive the data MOVL PRC_L_INDFAB(R11),R0 ; Address of indirect FAB MOVL R0,ENV_FAB_ADR ; Stash address for latr CLRW FAB$W_IFI(R0) ; Clear the FAB $FAB_STORE - ALQ=#0,- DEQ=#0,- FAC=PUT,- RAT=CR,- RFM=VAR,- FOP=PPF,- DNA=#0,- DNS=#0,- FNA=ENV_FSPEC+1,- FNS=ENV_FSPEC ; Setup FAB fields $CREATE FAB=@ENV_FAB_ADR ; Create the file BLBS R0,10$ ; Branch if success RET 10$: $RAB_STORE - RAB=ENV_RAB,- FAB=@ENV_FAB_ADR ; Point RAB at FAB $CONNECT RAB=ENV_RAB ; And connect it BLBS R0,20$ ; Branch if success RET ; Write out current default directory string 20$: PUSHAL CUR_DEFDIR ; Build desc to CUR_DEFDIR buffer PUSHL #EQUIV_NAM_LEN PUSHL SP ; Param 3 - Address of desc to receive ; current default dir string PUSHAL CUR_DEFDIR_SZ ; Param 2 - Address to receive length PUSHL #0 ; Param 1 - No new default dir CALLS #3,G^SYS$SETDDIR ; Get current default dir string $RAB_STORE - RAB=ENV_RAB,- RBF=CUR_DEFDIR,- RSZ=CUR_DEFDIR_SZ ; Point RAB at the string $PUT RAB=ENV_RAB ; and write it to file ; Write out current default file protection PUSHAL DEF_PROT ; Param 2 - address to receive prot PUSHL #0 ; Dummy param 1 - don't change prot CALLS #2,G^SYS$SETDFPROT ; Obtain the protection value $RAB_STORE - RAB=ENV_RAB,- RBF=DEF_PROT,- RSZ=#2 ; Point RAB at the protection value $PUT RAB=ENV_RAB ; Write the protection value record ; Write out global CLI symbols PUSHAQ PRC_Q_GLOBAL(R11) ; Param is address of global ; symbol table listhead CALLS #1,DMP_SYM ; Dump global symbols ; Write out local CLI symbols PUSHAQ PRC_Q_LOCAL(R11) ; Param is address of local ; symbol table listhead CALLS #1,DMP_SYM ; Dump local symbols ; Write out process logicals CALLS #0,DMP_PLOG ; Write records for logical names $CLOSE FAB=@ENV_FAB_ADR ; Close the environment file RET .PAGE .SBTTL SET_DEFDIR - Routine to alter default dev/dir ; This routine alters the current default device and directory back to ; their values at login time. Logical name environment must be already ; saved because we will create a new definition for SYS$DISK. .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY SET_DEFDIR,^M ; Register usage: ; R0-R4 - Scratch ; Reset default disk and directory to login defaults ; Start by finding login default device and directory MK_DESC LOGIN_LOG,R2 ; Get desc to logical name PUSHAL LOGIN_EQUIV ; Build desc to equiv name buffer PUSHL #EQUIV_NAM_LEN MOVL SP,R1 ; Get addr of dest desc $TRNLOG_S - LOGNAM=(R2),- RSLLEN=LOGIN_EQUIV_SZ,- RSLBUF=(R1) ; Translate once to get dev:[dir] LOCC #^A/:/,- LOGIN_EQUIV_SZ,- LOGIN_EQUIV ; Find end of device name MOVQ R0,R3 ; Save R0,R1 status from LOCC ; Set the default disk to login value. Actually, we create a new logical ; in Supervisor mode and don't alter the real SYS$DISK definition. SUBL3 R0,LOGIN_EQUIV_SZ,- R0 ; Comput nr of chars in dev name INCL R0 ; Include ":" in the count PUSHAL LOGIN_EQUIV ; Build desc to the device name PUSHL R0 MOVL SP,R1 ; Address of descriptor MK_DESC DISK_LOG,R2 ; Get desc to logical SYS$DISK $CRELOG_S - TBLFLG=#2,- LOGNAM=(R2),- EQLNAM=(R1),- ACMODE=#PSL$C_SUPER ; Redefine SYS$DISK ; Set default directory to login value INCL R4 ; Adjust saved pointer past ":" DECL R3 ; and adjust length PUSHL R4 ; Build desc to directory part of ; LOGIN_EQUIV. PUSHL R3 MOVL SP,R2 ; Address of descriptor CLRQ -(SP) ; Dummy params 2 and 3 PUSHL R2 ; 1st param is new def dir string CALLS #3,G^SYS$SETDDIR ; Reset directory RET .PAGE .SBTTL RESET_DEFDIR - Restore saved dev/dir ; Routine to restore the default disk and directory values present ; before FORK processing. The default directory name was saved by ; DMP_ENV. The new definition for SYS$DISK made by SET_DEFDIR is ; deleted. .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY RESET_DEFDIR,^M ; Register usage: ; R0-R2 - Scratch ; Reset default disk logical name by deleting other definition MK_DESC DISK_LOG,R2 ; Get desc to SYS$DISK string $DELLOG_S - TBLFLG=#2,- LOGNAM=(R2) ; Zap the definition ; Reset default directory string PUSHAL CUR_DEFDIR ; Build desc to save directory name PUSHL CUR_DEFDIR_SZ MOVL SP,R1 ; Address of desc CLRQ -(SP) ; Dummy param 2 and 3 PUSHL R1 ; Param 1 is new def dir string CALLS #3,G^SYS$SETDDIR ; Set directory names RET .PAGE .SBTTL DMP_PLOG - Write environment records for logical names ; Routine to write the contents of the process logical nmae table to ; the environment file. The logical name table is chased directly ; and a record is written for each logical name found. Format of the ; records is: ; +0) access mode of the entry ; +1) length of logical name ; +2) logical name ; +n+2) length of equiv name ; +n+3) equiv name ; We do not dump any logicals for process permanents, since these are ; already created in the subprocess by LOGINOUT (redefining them would ; cause trouble). .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,PIC,GBL .ENTRY DMP_PLOG,^M ; Register usage ; R0-R5 - Scratch ; R6 - Pointer to current logical name block ; R7 - Pointer to logical name listhead $RAB_STORE - RAB=ENV_RAB,- RBF=ENV_REC ; Point RAB at record buffer MOVL G^LOG$AL_LOGTBL+8,R7 ; Get address of name table listhead MOVL R7,R6 ; Copy pointer 10$: MOVL LOG$L_LTFL(R6),R6 ; Get pointer to next logical name blk CMPL R6,R7 ; Do we point back to listhead? BEQL 20$ ; Branch if so ; Build a record and write it MOVAL ENV_REC,R3 ; R3 points to destination MOVB LOG$B_AMOD(R6),(R3)+ ; Store access mode byte MOVZBL LOG$T_NAME(R6),R0 ; Get length of logical name INCL R0 ; Incr to allow for count byte CMPB LOG$T_NAME+1(R6)[R0],- #^X1B ; Equiv name for a PPF? BEQL 10$ ; Skip it if so MOVZBL LOG$T_NAME(R6)[R0],R1 ; Get length of equiv name ADDL2 R1,R0 ; Sum of lengths INCL R0 ; plus one for equiv name count ADDW3 #1,R0,- ENV_RAB+RAB$W_RSZ ; Length of names plus 1 for ; access mode is record length MOVC3 R0,LOG$T_NAME(R6),(R3) ; Move names to buffer $PUT RAB=ENV_RAB ; Write the record BRB 10$ ; Loop for next name ; All records written. Write a zero length terminator 20$: CLRW ENV_RAB+RAB$W_RSZ ; Zero the length field $PUT RAB=ENV_RAB ; Write terminator RET .PAGE .SBTTL DMP_SYM - Dump CLI symbols from CLI symbol table ; This routine is used to dump the contents of a CLI symbol table ; to the environment file. For each symbol, one record is written to ; the file. The record consists of two counted strings, the symbol name ; and the value. Following the last symbol, a zero length record is ; written as a delimiter. Symbols which were defined as allowing an ; abbreviation have a count value which indicates the number of characters ; in the shortest abbreviation. This count is used to reinsert the ; "*" abbreviation character into the symbol record. Symbols marked as ; permanent ($STATUS and $SEVERITY) are not processed. .PSECT _AAAA RD,NOWRT,EXE,SHR,LONG,GBL,PIC .ENTRY DMP_SYM,^M ; Register usage ; R0-R1 - Scratch ; R6 - Pointer to current symbol table entry ; R7 - Scratch $RAB_STORE - RAB=ENV_RAB,- RBF=ENV_REC ; Point RAB at the buffer MOVL 4(AP),R6 ; Get copy of listhead pointer 10$: MOVL SYM_L_FL(R6),R6 ; Point to next symbol in table CMPL R6,4(AP) ; Are we back to listhead? BEQL 20$ ; Branch out if so CMPB SYM_B_TYPE(R6),- #SYM_K_PERM ; Is this a permanent symbol? BEQL 10$ ; Branch if so and skip it. MOVZBL SYM_T_SYMBOL(R6),R0 ; Get length of symbol name MOVZBL SYM_T_SYMBOL+1(R6)[R0],- R1 ; Get length of value ADDL2 R0,R1 ; Compute length of text ADDL2 #2,R1 ; Plus 2 for total record length MOVW R1,ENV_RAB+RAB$W_RSZ ; Store record length in RAB MOVC3 R1,SYM_T_SYMBOL(R6),- ENV_REC ; Move symbol info into buffer MOVZBL SYM_B_NESTLEVEL(R6),R1 ; Is abbreviation allowed? BEQL 15$ ; Branch if not ; R1 contains nr of chars in the symbol name after the abbreviation point CLRL R7 ; Clear dest register SUBB3 R1,ENV_REC,R7 ; Compute nr of chars before abbrev ; point INCL R7 ; plus one for count byte gives ; offset in record of abbrev point. SUBW3 R7,ENV_RAB+RAB$W_RSZ,R1 ; Compute nr of chars in record after ; abbrev point MOVC3 R1,ENV_REC[R7],- ENV_REC+1[R7] ; Slide all chars down by 1 MOVB #^A/*/,ENV_REC[R7] ; Stuff in abbrev character INCB ENV_REC ; Bump count field in record INCW ENV_RAB+RAB$W_RSZ ; Bump record length 15$: $PUT RAB=ENV_RAB ; Write the record BRB 10$ ; Loop thru all symbols ; Write zero length record as terminator 20$: CLRW ENV_RAB+RAB$W_RSZ ; Zero length field $PUT RAB=ENV_RAB ; Write terminator RET .END FORK